// Cyrene v2 — LLM-powered AI companion platform. // Single binary entry point that wires all services together. package main import ( "context" "fmt" "log" "net/http" "os" "os/signal" "syscall" "git.yeij.top/AskaEth/Cyrene_v2/internal/config" "git.yeij.top/AskaEth/Cyrene_v2/internal/db" "git.yeij.top/AskaEth/Cyrene_v2/internal/nats" "git.yeij.top/AskaEth/Cyrene_v2/internal/platform" "git.yeij.top/AskaEth/Cyrene_v2/internal/plugin" "git.yeij.top/AskaEth/Cyrene_v2/plugins/calculator" "git.yeij.top/AskaEth/Cyrene_v2/plugins/crypto" "git.yeij.top/AskaEth/Cyrene_v2/plugins/datetime" "git.yeij.top/AskaEth/Cyrene_v2/plugins/file" httpplugin "git.yeij.top/AskaEth/Cyrene_v2/plugins/http" iotmock "git.yeij.top/AskaEth/Cyrene_v2/plugins/iot-mock" "git.yeij.top/AskaEth/Cyrene_v2/plugins/json" "git.yeij.top/AskaEth/Cyrene_v2/plugins/markdown" "git.yeij.top/AskaEth/Cyrene_v2/plugins/random" "git.yeij.top/AskaEth/Cyrene_v2/plugins/text" webfetch "git.yeij.top/AskaEth/Cyrene_v2/plugins/web-fetch" websearch "git.yeij.top/AskaEth/Cyrene_v2/plugins/web-search" "git.yeij.top/AskaEth/Cyrene_v2/services/ai-core/llm" aiCoreCtx "git.yeij.top/AskaEth/Cyrene_v2/services/ai-core/context" "git.yeij.top/AskaEth/Cyrene_v2/services/ai-core/orchestrator" "git.yeij.top/AskaEth/Cyrene_v2/services/ai-core/persona" "git.yeij.top/AskaEth/Cyrene_v2/services/ai-core/subsession" "git.yeij.top/AskaEth/Cyrene_v2/services/gateway/api" "git.yeij.top/AskaEth/Cyrene_v2/services/gateway/ws" "git.yeij.top/AskaEth/Cyrene_v2/services/memory" voicesvc "git.yeij.top/AskaEth/Cyrene_v2/services/voice" ) func main() { log.SetFlags(log.LstdFlags | log.Lshortfile) log.Println("🧠 Cyrene v2 启动中...") // === 1. Platform & Config === plat := platform.Detect() log.Printf("[platform] %s", plat.Summary()) cfg, err := config.Load() if err != nil { log.Printf("[config] .env load failed: %v", err) } cfg.ValidateOrFatal() cfg.LogSanitized() plat.EnsureDirs() // === 2. Database === database, err := db.Connect(db.Config{ Host: cfg.PostgresHost, Port: cfg.PostgresPort, User: cfg.PostgresUser, Password: cfg.PostgresPassword, DBName: cfg.PostgresDB, SSLMode: cfg.PostgresSSLMode, MaxOpenConns: 25, MaxIdleConns: 5, }) if err != nil { log.Printf("[db] warning: %v (running without persistence)", err) } else { defer database.Close() db.RunMigration(context.Background(), database, "memories", db.SplitSQL(memory.CreateTableSQL())) } // === 3. NATS (in-process) === natsConn := nats.NewInProcess() log.Printf("[nats] ready") _ = natsConn // === 4. Plugin Registry === reg := plugin.NewRegistry() pluginCfg := plugin.PluginConfig{ DataDir: cfg.DataDir, NATSURL: cfg.NATSUrl, DatabaseURL: cfg.GetDatabaseURL(), LogLevel: cfg.LogLevel, } // Load IoT plugin if cfg.IoTProvider == "mock" || cfg.IoTProvider == "" { reg.Register(iotmock.New(), pluginCfg) } // Load tool plugins reg.Register(calculator.New(), pluginCfg) reg.Register(datetime.New(), pluginCfg) reg.Register(crypto.New(), pluginCfg) reg.Register(random.New(), pluginCfg) reg.Register(text.New(), pluginCfg) reg.Register(json.New(), pluginCfg) reg.Register(markdown.New(), pluginCfg) reg.Register(file.New(), pluginCfg) reg.Register(httpplugin.New(), pluginCfg) reg.Register(webfetch.New(), pluginCfg) if cfg.SearXNGURL != "" { reg.Register(websearch.New(cfg.SearXNGURL), pluginCfg) } log.Printf("[plugin] loaded %d plugins: %v", reg.Count(), reg.ListNames()) // === 5. Voice Service === voiceSvc := voicesvc.New(voicesvc.Config{ STTProvider: cfg.ASRProvider, TTSProviders: []string{cfg.TTSProvider}, DashScopeKey: cfg.DashScopeAPIKey, }) log.Printf("[voice] ready") // === 6. Memory Service === var memSvc *memory.Service if database != nil { memRepo := memory.NewRepository(database) memSvc = memory.NewService(memRepo) log.Printf("[memory] connected") } else { log.Printf("[memory] disabled (no database)") } // === 7. AI-Core: Persona === personaLoader, err := persona.NewLoader(cfg.PersonaDir) if err != nil { log.Printf("[persona] load error: %v (using builtin)", err) } log.Printf("[persona] loaded %d persona(s): %v", personaLoader.Count(), personaLoader.List()) personaInjector := persona.NewInjector(personaLoader.Default()) // === 8. AI-Core: LLM Adapter === llmCfg := llm.DefaultConfig() llmCfg.BaseURL = cfg.LLMBaseURL llmCfg.APIKey = cfg.LLMAPIKey llmCfg.Model = cfg.LLMModel llmCfg.FallbackModel = cfg.LLMFallbackModel llmSelector := llm.NewModelSelector(llmCfg) log.Printf("[llm] adapter ready (model=%s)", cfg.LLMModel) // === 9. AI-Core: Context Store === ctxStore := aiCoreCtx.NewStore(4000, 50) log.Printf("[context] sliding window ready (4000 tokens, 50 turns)") // === 10. AI-Core: Sub-session Manager === subMgr := subsession.NewManager( &subsession.GeneralProvider{}, &subsession.MemoryProvider{}, &subsession.IoTProvider{ GetDeviceState: func(ctx context.Context) (string, error) { iot := reg.GetIoTProvider() if iot == nil { return "", fmt.Errorf("no iot provider") } devices, err := iot.ListDevices(ctx) if err != nil { return "", err } states := make([]orchestrator.DeviceState, len(devices)) for i, d := range devices { states[i] = orchestrator.DeviceState{ID: d.ID, Name: d.Name, State: d.State} } return orchestrator.BuildIoTContext(states), nil }, }, ) log.Printf("[subsession] 4 providers ready") // === 11. AI-Core: Orchestrator === semanticCache := llm.NewSemanticCache() orch := orchestrator.NewPipeline(llmSelector, semanticCache, ctxStore, subMgr, personaInjector) log.Printf("[orchestrator] pipeline ready") // === 12. Gateway === authMgr := api.NewStubAuthManager() apiRouter := api.New(authMgr) wsHub := ws.NewHub() wsMux := http.NewServeMux() wsMux.HandleFunc("/ws/chat", wsHub.UpgradeHandler()) mainMux := http.NewServeMux() mainMux.Handle("/api/", apiRouter) mainMux.Handle("/ws/", wsMux) addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port) srv := &http.Server{Addr: addr, Handler: mainMux} // === 13. Start === go func() { log.Printf("[gateway] listening on %s", addr) log.Printf("[gateway] plugins=%d tools=%d", reg.Count(), len(reg.GetAllToolDefinitions())) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("[gateway] %v", err) } }() quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Println("[gateway] shutting down...") reg.Shutdown() srv.Close() log.Println("Cyrene v2 已安全退出") _ = voiceSvc _ = memSvc _ = orch }