package main import ( "context" "encoding/json" "fmt" "log" "net/http" "os" "os/signal" "syscall" "time" "github.com/yourname/cyrene-ai/ai-core/internal/context" "github.com/yourname/cyrene-ai/ai-core/internal/llm" "github.com/yourname/cyrene-ai/ai-core/internal/memory" "github.com/yourname/cyrene-ai/ai-core/internal/model" "github.com/yourname/cyrene-ai/ai-core/internal/orchestrator" "github.com/yourname/cyrene-ai/ai-core/internal/persona" ) func main() { log.SetFlags(log.LstdFlags | log.Lshortfile) log.Println("🧠 AI-Core 服务启动中...") // 加载配置 cfg := loadConfig() // 初始化人格加载器 personaDir := cfg.PersonaDir if personaDir == "" { personaDir = "./internal/persona" } personaLoader, err := persona.NewLoader(personaDir) if err != nil { log.Fatalf("加载人格配置失败: %v", err) } log.Printf("已加载 %d 个人格: %v", len(personaLoader.List()), personaLoader.List()) // 初始化LLM适配器 llmProvider := llm.NewOpenAIProvider(llm.OpenAIConfig{ BaseURL: cfg.LLMBaseURL, APIKey: cfg.LLMAPIKey, Model: cfg.LLMModel, FallbackModel: cfg.LLMFallbackModel, Timeout: 120 * time.Second, }) llmAdapter := llm.NewAdapter(llmProvider) log.Printf("LLM适配器已就绪: 模型=%s", llmAdapter.ModelName()) // 初始化记忆系统 var memStore *memory.Store var memRetriever *memory.Retriever var memExtractor *memory.Extractor if cfg.DatabaseURL != "" { memStore, err = memory.NewStore(cfg.DatabaseURL) if err != nil { log.Printf("⚠ 记忆存储初始化失败 (将跳过记忆功能): %v", err) } else { defer memStore.Close() log.Println("记忆存储已就绪") memRetriever = memory.NewRetriever(memStore, nil) // 记忆提取器使用LLM memExtractor = memory.NewExtractor(memStore, func(ctx context.Context, messages []model.LLMMessage) (*model.LLMResponse, error) { return llmAdapter.Chat(ctx, messages) }) log.Println("记忆提取器已就绪") } } // 初始化上下文构建器 ctxBuilder := &context.Builder{} // 手动注入 Injector 到 orchestrator(临时方案,后续会用依赖注入框架) personaInjector := &persona.Injector{} // 健康检查与对话API的HTTP mux mux := http.NewServeMux() // 手动构建 orchestrator 用于处理(因为现有orchestrator结构体已定义但未导出构造函数) orch := &orchestrator.Orchestrator{} // 注册对话API端点 mux.HandleFunc("/api/v1/chat", func(w http.ResponseWriter, r *http.Request) { handleChat(w, r, orch, ctxBuilder, llmAdapter, personaLoader, personaInjector, memRetriever, memExtractor) }) mux.HandleFunc("/api/v1/health", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Write([]byte(`{"status":"ok","service":"ai-core","model":"` + llmAdapter.ModelName() + `"}`)) }) // 启动HTTP服务 srv := &http.Server{ Addr: ":" + cfg.Port, Handler: mux, } go func() { log.Printf("🚀 AI-Core 服务已启动在端口 %s", cfg.Port) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("服务启动失败: %v", err) } }() // 优雅关闭 quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Println("正在关闭 AI-Core 服务...") ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() srv.Shutdown(ctx) log.Println("AI-Core 服务已关闭") } // Config AI-Core配置 type Config struct { Port string PersonaDir string LLMBaseURL string LLMAPIKey string LLMModel string LLMFallbackModel string DatabaseURL string } func loadConfig() Config { return Config{ Port: getEnv("AI_CORE_PORT", "8081"), PersonaDir: getEnv("PERSONA_DIR", "./internal/persona"), LLMBaseURL: getEnv("LLM_API_URL", "https://api.openai.com/v1"), LLMAPIKey: getEnv("LLM_API_KEY", ""), LLMModel: getEnv("LLM_MODEL", "gpt-4o"), LLMFallbackModel: getEnv("LLM_FALLBACK_MODEL", "gpt-4o-mini"), DatabaseURL: buildDatabaseURL(), } } func buildDatabaseURL() string { host := getEnv("POSTGRES_HOST", "localhost") port := getEnv("POSTGRES_PORT", "5432") user := getEnv("POSTGRES_USER", "cyrene") password := getEnv("POSTGRES_PASSWORD", "change_me") dbname := getEnv("POSTGRES_DB", "cyrene_ai") sslmode := getEnv("POSTGRES_SSLMODE", "disable") return fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=%s", user, password, host, port, dbname, sslmode) } func getEnv(key, fallback string) string { if v := os.Getenv(key); v != "" { return v } return fallback } // handleChat 处理对话请求 func handleChat( w http.ResponseWriter, r *http.Request, _ *orchestrator.Orchestrator, ctxBuilder *context.Builder, llmAdapter *llm.Adapter, personaLoader *persona.Loader, personaInjector *persona.Injector, memRetriever *memory.Retriever, memExtractor *memory.Extractor, ) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } // 解析请求 var req struct { UserID string `json:"user_id"` SessionID string `json:"session_id"` Message string `json:"message"` Mode string `json:"mode"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "无效的请求体", http.StatusBadRequest) return } if req.Mode == "" { req.Mode = "text" } ctx := r.Context() // 1. 检索相关记忆 var memories []memory.MemoryEntry if memRetriever != nil { var err error memories, err = memRetriever.Retrieve(ctx, req.UserID, req.Message) if err != nil { log.Printf("[chat] 记忆检索失败: %v", err) } } // 2. 加载人格配置 personaConfig, err := personaLoader.Get("cyrene") if err != nil { http.Error(w, fmt.Sprintf("加载人格失败: %v", err), http.StatusInternalServerError) return } // 3. 构建对话上下文 llmMessages, err := ctxBuilder.Build(ctx, context.BuildParams{ UserID: req.UserID, SessionID: req.SessionID, UserMessage: req.Message, Persona: personaConfig, Memories: memories, HistoryLimit: 20, }) if err != nil { http.Error(w, fmt.Sprintf("构建上下文失败: %v", err), http.StatusInternalServerError) return } // 4. 调用LLM llmResp, err := llmAdapter.Chat(ctx, llmMessages) if err != nil { http.Error(w, fmt.Sprintf("LLM调用失败: %v", err), http.StatusInternalServerError) return } // 5. 异步提取记忆 if memExtractor != nil { go memExtractor.ExtractAndStore(context.Background(), req.UserID, req.SessionID, req.Message, llmResp.Content) } // 6. 构建响应 resp := map[string]interface{}{ "text": llmResp.Content, "mode": req.Mode, "message_id": fmt.Sprintf("msg-%d", time.Now().UnixNano()), } // 语音助手模式断句 if req.Mode == "voice_assistant" { resp["segments"] = llm.SplitIntoSegments(llmResp.Content) } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(resp) } // 确保未使用变量不报错 var _ = personaInjector