dev 分支暂存

This commit is contained in:
2026-05-16 08:26:56 +08:00
parent 58c8caa570
commit eb4129176c
71 changed files with 8474 additions and 214 deletions
+258
View File
@@ -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