dev 分支暂存
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user