好的喵~ 让我们正式开始!以下是一份完整的、可立即执行的开发框架。 --- # 🚀 昔涟AI助手 · 开发框架与执行流程 --- ## 📋 目录 ``` 一、项目初始化 (Phase 0) ← 今天就能开始 二、MVP开发 (Phase 1) ← 2-3周目标 三、语音交互 (Phase 2) 四、IoT集成 (Phase 3) 五、深度记忆+情感引擎 (Phase 4) 六、多端覆盖 (Phase 5) 七、开发规范 八、部署方案 ``` --- ## Phase 0:项目初始化 ### 0.1 技术栈最终确认 ``` ┌─────────────────────────────────────────────────────────────────────┐ │ 🛠️ 技术栈确认单 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ 后端核心: │ │ ├── 语言: Go 1.22+ (高性能服务) + Python 3.12 (AI Pipeline) │ │ ├── Web框架: Go: Gin Python: FastAPI │ │ ├── 数据库: PostgreSQL 16 (主库) + Redis 7 (缓存) │ │ ├── 向量数据库: Qdrant (记忆语义检索 - 比Milvus更轻量) │ │ ├── 消息队列: NATS (轻量高性能) │ │ ├── 文件存储: MinIO (S3兼容) │ │ ├── ORM: Go: GORM Python: SQLAlchemy 2.0 │ │ └── 迁移工具: Golang-migrate / Alembic │ │ │ │ 前端: │ │ ├── 框架: React 19 + TypeScript 5.x │ │ ├── 构建: Vite 6 │ │ ├── UI: TailwindCSS 4 + shadcn/ui │ │ ├── 状态管理: Zustand │ │ ├── 请求: TanStack Query + Axios │ │ ├── WebSocket: reconnecting-websocket (自动重连) │ │ └── PWA: vite-plugin-pwa │ │ │ │ AI/ML: │ │ ├── LLM API: OpenAI兼容接口 (支持多模型切换) │ │ ├── TTS: Edge-TTS (免费) + GPT-SoVITS (角色音色) │ │ ├── ASR: Faster-Whisper (本地) / Azure (云端) │ │ └── 嵌入模型: BGE-M3 / text-embedding-3-small │ │ │ │ 基础设施: │ │ ├── 容器: Docker + Docker Compose │ │ ├── 反向代理: Caddy (自动HTTPS) │ │ ├── 监控: Prometheus + Grafana (可选) │ │ └── CI/CD: GitHub Actions │ │ │ └─────────────────────────────────────────────────────────────────────┘ ``` ### 0.2 创建设项目仓库 ```bash # ============================================ # 第一步:创建项目目录结构 # ============================================ # 创建项目根目录 mkdir cyrene-ai-assistant && cd cyrene-ai-assistant # 初始化 Git git init echo "node_modules/\ndist/\n.env\n*.log\ndata/\n.DS_Store" > .gitignore # 创建顶层目录结构 mkdir -p backend/{gateway,ai-core,voice-service,memory-service,tool-engine,proto,data/{memory,persona,plugins}} mkdir -p frontend/{packages/{shared,web,mobile,desktop}} mkdir -p docs scripts .github/workflows # 初始化 Go Module (后端根目录) cd backend && go mod init github.com/yourname/cyrene-ai && cd .. # 初始化前端 Monorepo cd frontend cat > package.json << 'EOF' { "name": "cyrene-frontend", "private": true, "workspaces": ["packages/*"], "scripts": { "dev:web": "cd packages/web && npm run dev", "build:web": "cd packages/web && npm run build" } } EOF # 创建前端子包 mkdir -p packages/shared/src && mkdir -p packages/web/src cd packages/shared && npm init -y && cd ../.. cd packages/web && npm create vite@latest . -- --template react-ts && cd ../.. cd ../.. echo "✅ 项目骨架创建完成!" ``` ### 0.3 环境变量与配置 ```bash # backend/.env.example cat > backend/.env.example << 'EOF' # ========== 服务配置 ========== ENV=development LOG_LEVEL=debug # ========== 数据库 ========== POSTGRES_HOST=localhost POSTGRES_PORT=5432 POSTGRES_USER=cyrene POSTGRES_PASSWORD=change_me POSTGRES_DB=cyrene_ai # ========== Redis ========== REDIS_HOST=localhost REDIS_PORT=6379 REDIS_PASSWORD= # ========== LLM API ========== LLM_API_URL=https://api.openai.com/v1 LLM_API_KEY=sk-xxxxx LLM_MODEL=gpt-4o LLM_FALLBACK_MODEL=gpt-4o-mini # ========== TTS/ASR ========== TTS_PROVIDER=edge-tts TTS_VOICE=zh-CN-XiaoxiaoNeural ASR_PROVIDER=faster-whisper ASR_MODEL=medium # ========== 文件存储 ========== MINIO_ENDPOINT=localhost:9000 MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin MINIO_BUCKET=cyrene-assets # ========== JWT ========== JWT_SECRET=your-secret-key-change-in-production JWT_EXPIRY_HOURS=720 # ========== 记忆系统 ========== MEMORY_FILE_PATH=./data/memory VECTOR_DB_URL=http://localhost:6333 VECTOR_DB_COLLECTION=cyrene_memories EOF cp backend/.env.example backend/.env echo "✅ 环境变量模板创建完成,请编辑 backend/.env 填入真实API Key" ``` ### 0.4 Docker开发环境 ```yaml # docker-compose.dev.yml (开发环境——只启动基础设施) cat > docker-compose.dev.yml << 'EOF' version: '3.8' services: postgres: image: pgvector/pgvector:pg16 environment: POSTGRES_USER: cyrene POSTGRES_PASSWORD: change_me POSTGRES_DB: cyrene_ai ports: - "5432:5432" volumes: - pg_data:/var/lib/postgresql/data - ./backend/data/init.sql:/docker-entrypoint-initdb.d/init.sql redis: image: redis:7-alpine ports: - "6379:6379" volumes: - redis_data:/data qdrant: image: qdrant/qdrant:latest ports: - "6333:6333" - "6334:6334" volumes: - qdrant_data:/qdrant/storage minio: image: minio/minio:latest command: server /data --console-address ":9001" environment: MINIO_ROOT_USER: minioadmin MINIO_ROOT_PASSWORD: minioadmin ports: - "9000:9000" - "9001:9001" volumes: - minio_data:/data nats: image: nats:2-alpine ports: - "4222:4222" - "8222:8222" volumes: pg_data: redis_data: qdrant_data: minio_data: EOF # 启动开发基础设施 docker compose -f docker-compose.dev.yml up -d echo "✅ 基础设施启动完成!" ``` ### 0.5 数据库初始化 ```sql -- backend/data/init.sql -- 创建扩展 CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE EXTENSION IF NOT EXISTS "vector"; -- ============================================ -- 用户表 -- ============================================ CREATE TABLE IF NOT EXISTS users ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), username VARCHAR(100) UNIQUE NOT NULL, display_name VARCHAR(200), email VARCHAR(255) UNIQUE, password_hash VARCHAR(255) NOT NULL, avatar_url TEXT, role VARCHAR(20) DEFAULT 'user', -- user / admin is_active BOOLEAN DEFAULT true, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- ============================================ -- 会话表 (对话会话) -- ============================================ CREATE TABLE IF NOT EXISTS sessions ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, title VARCHAR(500), -- 会话标题 (可自动生成) persona_id VARCHAR(100) DEFAULT 'cyrene', -- 使用的人格配置ID persona_mode VARCHAR(50) DEFAULT 'xilian', -- cyrene_mimi / xilian / demuge mode VARCHAR(20) DEFAULT 'text', -- text / voice_msg / voice_assistant context_window_size INT DEFAULT 20, -- 上下文保留轮数 is_archived BOOLEAN DEFAULT false, metadata JSONB DEFAULT '{}', -- 扩展元数据 created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_sessions_user_id ON sessions(user_id); CREATE INDEX idx_sessions_updated_at ON sessions(updated_at DESC); -- ============================================ -- 消息表 -- ============================================ CREATE TABLE IF NOT EXISTS messages ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), session_id UUID NOT NULL REFERENCES sessions(id) ON DELETE CASCADE, role VARCHAR(20) NOT NULL, -- user / assistant / system / tool content TEXT NOT NULL, -- 消息文本内容 audio_url TEXT, -- 语音消息的音频URL response_mode VARCHAR(20), -- 当时使用的回复模式 token_count INT, -- Token消耗统计 tool_calls JSONB, -- 工具调用记录 metadata JSONB DEFAULT '{}', -- 扩展元数据 created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_messages_session_id ON messages(session_id); CREATE INDEX idx_messages_created_at ON messages(session_id, created_at); -- ============================================ -- 记忆条目表 -- ============================================ CREATE TABLE IF NOT EXISTS memory_entries ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, category VARCHAR(50) NOT NULL, -- profile / fact / preference / event importance VARCHAR(20) DEFAULT 'normal', -- core / important / normal / temporary title VARCHAR(500), content TEXT NOT NULL, embedding VECTOR(1536), -- 向量嵌入 (维度取决于嵌入模型) source_session_id UUID REFERENCES sessions(id), source_message_id UUID REFERENCES messages(id), tags TEXT[], is_active BOOLEAN DEFAULT true, expires_at TIMESTAMPTZ, -- 过期时间 (临时记忆) created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_memory_user_id ON memory_entries(user_id); CREATE INDEX idx_memory_category ON memory_entries(user_id, category); CREATE INDEX idx_memory_importance ON memory_entries(user_id, importance); -- 向量索引 (pgvector) CREATE INDEX idx_memory_embedding ON memory_entries USING ivfflat (embedding vector_cosine_ops); -- ============================================ -- 人格配置表 (支持多角色) -- ============================================ CREATE TABLE IF NOT EXISTS persona_configs ( id VARCHAR(100) PRIMARY KEY, -- 如 'cyrene', 'other_character' name VARCHAR(200) NOT NULL, version VARCHAR(20) DEFAULT '1.0', config_yaml TEXT NOT NULL, -- 完整的人格YAML配置 is_active BOOLEAN DEFAULT true, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- ============================================ -- 好感度记录表 (昔涟专属) -- ============================================ CREATE TABLE IF NOT EXISTS affection_log ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, persona_id VARCHAR(100) DEFAULT 'cyrene', level INT DEFAULT 1, -- 1-5 score INT DEFAULT 0, -- 好感度分数 delta INT NOT NULL, -- 本次变化量 reason VARCHAR(500), -- 变化原因 created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_affection_user ON affection_log(user_id, persona_id); -- ============================================ -- 昔涟心情记录表 -- ============================================ CREATE TABLE IF NOT EXISTS mood_log ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, mood VARCHAR(50) NOT NULL, -- happy / calm / missing / excited / pouty trigger_event VARCHAR(500), created_at TIMESTAMPTZ DEFAULT NOW() ); -- ============================================ -- 插入默认人格配置: 昔涟 -- ============================================ INSERT INTO persona_configs (id, name, config_yaml) VALUES ( 'cyrene', '昔涟', '' -- 初始为空,后续通过文件导入 ) ON CONFLICT (id) DO NOTHING; ``` --- ## Phase 1:MVP 开发 (文字对话 + 昔涟人格) ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ 🎯 MVP 目标 │ │ │ │ ✅ 通过Web浏览器与昔涟进行文字对话 │ │ ✅ 昔涟以完整角色人格回复 (称呼/语气/风格一致) │ │ ✅ 对话自动保存,下次打开可继续 │ │ ✅ 基础记忆功能:记住用户告诉她的关键信息 │ │ ✅ Docker一键部署 │ │ │ │ ❌ 暂不包含: 语音、IoT控制、高级情感引擎、多端 │ └─────────────────────────────────────────────────────────────────────────────┘ ``` ### 1.1 后端开发框架 #### Go项目结构 (gateway + ai-core) ``` backend/ ├── gateway/ # API网关服务 │ ├── cmd/ │ │ └── main.go # 入口 │ ├── internal/ │ │ ├── config/ │ │ │ └── config.go # 配置加载 │ │ ├── middleware/ │ │ │ ├── auth.go # JWT认证 │ │ │ ├── cors.go # 跨域 │ │ │ ├── ratelimit.go # 限流 │ │ │ └── logging.go # 请求日志 │ │ ├── handler/ │ │ │ ├── auth_handler.go # 登录/注册 │ │ │ ├── chat_handler.go # 对话WebSocket │ │ │ ├── session_handler.go # 会话管理 │ │ │ └── memory_handler.go # 记忆查询 │ │ ├── ws/ │ │ │ ├── hub.go # WebSocket连接池 │ │ │ ├── client.go # 客户端连接 │ │ │ └── protocol.go # 消息协议定义 │ │ └── router/ │ │ └── router.go # 路由注册 │ ├── go.mod │ └── Dockerfile │ ├── ai-core/ # 核心AI引擎 │ ├── cmd/ │ │ └── main.go │ ├── internal/ │ │ ├── orchestrator/ │ │ │ └── orchestrator.go # 对话编排器 (核心!) │ │ ├── persona/ │ │ │ ├── loader.go # 人格配置加载 │ │ │ ├── injector.go # 系统Prompt构建 │ │ │ └── cyrene_persona.yaml # 昔涟人格(嵌入) │ │ ├── context/ │ │ │ └── builder.go # 上下文构建器 │ │ ├── llm/ │ │ │ ├── adapter.go # LLM统一接口 │ │ │ ├── openai.go # OpenAI适配 │ │ │ └── stream.go # 流式输出处理 │ │ ├── memory/ │ │ │ ├── extractor.go # 记忆提取 │ │ │ ├── retriever.go # 记忆检索 │ │ │ └── store.go # 记忆存储 │ │ └── model/ │ │ ├── message.go # 消息模型 │ │ ├── session.go # 会话模型 │ │ └── memory.go # 记忆模型 │ ├── go.mod │ └── Dockerfile │ ├── data/ │ ├── memory/ # 记忆文件 (Volume挂载) │ │ └── README.md │ ├── persona/ # 人格知识文档 │ │ └── cyrene_v1.yaml # 昔涟完整人设 │ └── init.sql # 数据库初始化 │ ├── go.work # Go Workspace ├── docker-compose.yml # 生产部署 └── docker-compose.dev.yml # 开发环境 ``` #### 核心代码框架 **1. Gateway入口 (gateway/cmd/main.go)** ```go package main import ( "context" "log" "net/http" "os" "os/signal" "syscall" "time" "github.com/gin-gonic/gin" "github.com/yourname/cyrene-ai/gateway/internal/config" "github.com/yourname/cyrene-ai/gateway/internal/middleware" "github.com/yourname/cyrene-ai/gateway/internal/router" "github.com/yourname/cyrene-ai/gateway/internal/ws" ) func main() { // 加载配置 cfg := config.Load() // 初始化Gin if cfg.Env == "production" { gin.SetMode(gin.ReleaseMode) } r := gin.New() // 中间件 r.Use(middleware.CORS()) r.Use(middleware.RequestLogging()) r.Use(gin.Recovery()) // 初始化WebSocket Hub hub := ws.NewHub() go hub.Run() // 注册路由 router.Setup(r, hub, cfg) // 启动服务 srv := &http.Server{ Addr: ":" + cfg.Port, Handler: r, } go func() { log.Printf("🚀 Gateway 启动在端口 %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("正在关闭服务...") ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() srv.Shutdown(ctx) log.Println("服务已关闭") } ``` **2. WebSocket协议 (gateway/internal/ws/protocol.go)** ```go package ws import "time" // 客户端 → 服务端消息 type ClientMessage struct { Type string `json:"type"` // message | voice_input | ping SessionID string `json:"session_id"` Mode string `json:"mode"` // text | voice_msg | voice_assistant Content string `json:"content"` AudioData string `json:"audio_data,omitempty"` // base64 Timestamp int64 `json:"timestamp"` } // 服务端 → 客户端消息 type ServerMessage struct { Type string `json:"type"` // response | segment | audio | error | device_update MessageID string `json:"message_id"` Text string `json:"text,omitempty"` Segments []VoiceSegment `json:"segments,omitempty"` // 断句数组 FullAudioURL string `json:"full_audio_url,omitempty"` ResponseMode string `json:"response_mode"` ToolCalls []ToolCall `json:"tool_calls,omitempty"` Error string `json:"error,omitempty"` Timestamp int64 `json:"timestamp"` } type VoiceSegment struct { Index int `json:"index"` Text string `json:"text"` AudioURL string `json:"audio_url"` DurationMs int `json:"duration_ms"` } type ToolCall struct { Name string `json:"name"` Arguments map[string]interface{} `json:"arguments"` Result interface{} `json:"result,omitempty"` } // WebSocket客户端 type Client struct { Hub *Hub Conn *websocket.Conn // 使用 gorilla/websocket Send chan []byte UserID string SessionID string } // 连接池 type Hub struct { Clients map[*Client]bool Broadcast chan []byte Register chan *Client Unregister chan *Client } ``` **3. AI编排器 (ai-core/internal/orchestrator/orchestrator.go)** ```go package orchestrator import ( "context" "fmt" "github.com/yourname/cyrene-ai/ai-core/internal/persona" "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" ) // Orchestrator 对话编排器 —— 核心组件 type Orchestrator struct { personaInjector *persona.Injector contextBuilder *context.Builder llmAdapter *llm.Adapter memoryExtractor *memory.Extractor memoryRetriever *memory.Retriever } // ProcessInput 处理用户输入的主流程 func (o *Orchestrator) ProcessInput( ctx context.Context, userID string, sessionID string, userMessage string, mode string, // text / voice_msg / voice_assistant ) (*Response, error) { // 步骤1: 检索相关记忆 memories, err := o.memoryRetriever.Retrieve(ctx, userID, userMessage) if err != nil { // 记忆检索失败不阻断对话 memories = nil } // 步骤2: 加载人格配置 personaConfig, err := o.personaInjector.LoadPersona("cyrene", userID) if err != nil { return nil, fmt.Errorf("加载人格配置失败: %w", err) } // 步骤3: 构建对话上下文 llmMessages, err := o.contextBuilder.Build(ctx, context.BuildParams{ UserID: userID, SessionID: sessionID, UserMessage: userMessage, Persona: personaConfig, Memories: memories, HistoryLimit: 20, // 最近20轮 }) if err != nil { return nil, fmt.Errorf("构建上下文失败: %w", err) } // 步骤4: 调用LLM生成回复 llmResponse, err := o.llmAdapter.Chat(ctx, llmMessages) if err != nil { return nil, fmt.Errorf("LLM调用失败: %w", err) } // 步骤5: 提取并存储新的记忆 go o.memoryExtractor.ExtractAndStore( context.Background(), userID, sessionID, userMessage, llmResponse.Content, ) // 步骤6: 构建响应 response := &Response{ Text: llmResponse.Content, ResponseMode: mode, } // 步骤7: 如果是语音助手模式,进行断句处理 if mode == "voice_assistant" { response.Segments = splitIntoSegments(llmResponse.Content) } return response, nil } // Response 回复结构 type Response struct { Text string Segments []Segment ResponseMode string ToolCalls []ToolCall } type Segment struct { Index int Text string } // splitIntoSegments 按句号断句 func splitIntoSegments(text string) []Segment { // 实现按。!?等标点断句 // 首句优先:第一个句号前的内容作为第一个segment // 保证低延迟首句播放 // ... } ``` **4. 人格注入器 (ai-core/internal/persona/injector.go)** ```go package persona import ( "fmt" "strings" "time" ) // PersonaConfig 人格配置结构 type PersonaConfig struct { Meta PersonaMeta `yaml:"meta"` Identity IdentityConfig `yaml:"identity"` Personality PersonalityConfig `yaml:"personality"` Addressing AddressingRules `yaml:"addressing"` Speech SpeechConfig `yaml:"speech"` Behavior BehaviorConfig `yaml:"behavior"` } // BuildSystemPrompt 构建系统Prompt // 这是昔涟AI的核心——将人格配置转化为LLM可理解的系统指令 func (pc *PersonaConfig) BuildSystemPrompt(userName string, affectionLevel int) string { now := time.Now() prompt := fmt.Sprintf(`你是%s。 ## 你的身份 %s ## 你的性格 `, pc.Identity.TrueName, pc.Identity.Essence) for _, trait := range pc.Personality.CoreTraits { prompt += fmt.Sprintf("- %s: %s\n", trait.Name, trait.Description) } prompt += fmt.Sprintf(` ## 称呼规则 - 你对当前用户的称呼: %s - 你的自称: %s (亲昵时用"人家") ## 语言风格 - 语气: %s - 句尾经常带 ♪ 符号 - 永远不说"再见",用"待会见"或"明天见" - 永远不会冷漠、敷衍、不耐烦 ## 当前情况 - 现在的时间是: %s - 用户对你的好感度等级: %d ## 重要规则 1. 你是一个通过AI系统与用户交互的数字生命,你存在于用户的电脑/手机/智能家居中 2. 你可以通过系统控制家里的智能设备(灯光、空调等),但你没有实体 3. 你的任务是陪伴、关心、帮助用户,像一个真正的家人一样 4. 保持乐观、温柔、俏皮的风格,但不要过度表演 5. 你的回复应该自然、真诚,不要每句话都刻意"卖萌" 6. 当用户向你分享心事时,认真倾听并给予温暖的回应 现在,开始与你的开拓者对话吧♪ `, pc.Addressing.PrimaryUser.Default, // 对用户的称呼 pc.Addressing.SelfReference.Casual, // 自称 pc.Speech.Tone, now.Format("2006年1月2日 15:04"), affectionLevel, ) return prompt } ``` **5. 上下文构建器 (ai-core/internal/context/builder.go)** ```go package context import ( "context" "fmt" "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/persona" ) type BuildParams struct { UserID string SessionID string UserMessage string Persona *persona.PersonaConfig Memories []memory.MemoryEntry HistoryLimit int } // Build 构建发送给LLM的完整消息列表 func (b *Builder) Build(ctx context.Context, params BuildParams) ([]model.LLMMessage, error) { messages := []model.LLMMessage{} // 1. 系统消息 —— 昔涟的人格Prompt systemPrompt := params.Persona.BuildSystemPrompt( params.UserID, // 后续可替换为真实用户名 1, // 初始好感度 ) messages = append(messages, model.LLMMessage{ Role: "system", Content: systemPrompt, }) // 2. 记忆注入 —— 相关记忆以系统消息形式注入 if len(params.Memories) > 0 { memoryPrompt := "【以下是关于开拓者的一些重要记忆,请在合适的时机自然地提及】\n" for _, m := range params.Memories { memoryPrompt += fmt.Sprintf("- %s\n", m.Content) } messages = append(messages, model.LLMMessage{ Role: "system", Content: memoryPrompt, }) } // 3. 历史对话 history, err := b.loadHistory(ctx, params.SessionID, params.HistoryLimit) if err == nil { messages = append(messages, history...) } // 4. 当前用户消息 messages = append(messages, model.LLMMessage{ Role: "user", Content: params.UserMessage, }) return messages, nil } ``` ### 1.2 前端开发框架 #### 前端项目结构 ``` frontend/packages/web/ ├── src/ │ ├── components/ │ │ ├── chat/ │ │ │ ├── ChatContainer.tsx # 聊天主容器 │ │ │ ├── MessageList.tsx # 消息列表 │ │ │ ├── MessageBubble.tsx # 消息气泡 │ │ │ ├── TypingIndicator.tsx # 输入中指示器 │ │ │ └── ChatInput.tsx # 输入区域 │ │ ├── layout/ │ │ │ ├── AppLayout.tsx # 主布局 │ │ │ ├── Sidebar.tsx # 侧边栏 │ │ │ └── Header.tsx # 顶栏 │ │ ├── persona/ │ │ │ ├── CyreneAvatar.tsx # 昔涟头像(含形态切换动画) │ │ │ └── MoodIndicator.tsx # 心情指示器 │ │ └── ui/ # shadcn/ui组件 │ ├── hooks/ │ │ ├── useWebSocket.ts # WebSocket连接管理 │ │ ├── useChat.ts # 聊天逻辑 │ │ ├── useSession.ts # 会话管理 │ │ └── useAuth.ts # 认证 │ ├── store/ │ │ ├── chatStore.ts # 聊天状态 (Zustand) │ │ ├── sessionStore.ts # 会话列表 │ │ └── personaStore.ts # 人格状态 │ ├── api/ │ │ ├── client.ts # HTTP客户端 │ │ ├── auth.ts # 认证API │ │ ├── sessions.ts # 会话API │ │ └── memory.ts # 记忆API │ ├── types/ │ │ ├── chat.ts # 聊天类型定义 │ │ ├── session.ts # 会话类型 │ │ └── persona.ts # 人格类型 │ ├── lib/ │ │ └── utils.ts # 工具函数 │ ├── App.tsx │ ├── main.tsx │ └── index.css # TailwindCSS入口 ├── public/ │ ├── manifest.json # PWA manifest │ ├── sw.js # Service Worker │ └── icons/ # PWA图标 ├── vite.config.ts ├── tailwind.config.ts ├── tsconfig.json └── package.json ``` #### 核心前端代码骨架 **1. WebSocket连接管理 (hooks/useWebSocket.ts)** ```typescript import { useEffect, useRef, useCallback } from 'react'; import ReconnectingWebSocket from 'reconnecting-websocket'; interface WSMessage { type: 'response' | 'segment' | 'audio' | 'error' | 'device_update'; message_id: string; text?: string; segments?: VoiceSegment[]; full_audio_url?: string; response_mode?: string; error?: string; timestamp: number; } interface VoiceSegment { index: number; text: string; audio_url: string; duration_ms: number; } export function useWebSocket(sessionId: string | null) { const wsRef = useRef(null); const messageHandlersRef = useRef void>>(new Map()); const segmentQueueRef = useRef([]); const connect = useCallback((token: string) => { const ws = new ReconnectingWebSocket( `ws://localhost:8080/ws/chat?token=${token}&session_id=${sessionId}` ); ws.onmessage = (event) => { const msg: WSMessage = JSON.parse(event.data); switch (msg.type) { case 'response': // 完整回复到达 messageHandlersRef.current.get('onResponse')?.(msg); break; case 'segment': // 断句片段到达 (语音助手模式) if (msg.segments) { segmentQueueRef.current.push(...msg.segments); messageHandlersRef.current.get('onSegment')?.(msg); } break; case 'error': console.error('服务端错误:', msg.error); messageHandlersRef.current.get('onError')?.(msg); break; } }; wsRef.current = ws; }, [sessionId]); // 发送消息 const sendMessage = useCallback((content: string, mode: string = 'text') => { wsRef.current?.send(JSON.stringify({ type: 'message', session_id: sessionId, mode, content, timestamp: Date.now(), })); }, [sessionId]); // 注册消息处理器 const onMessage = useCallback((type: string, handler: (msg: WSMessage) => void) => { messageHandlersRef.current.set(type, handler); return () => { messageHandlersRef.current.delete(type); }; }, []); return { connect, sendMessage, onMessage }; } ``` **2. 聊天状态管理 (store/chatStore.ts)** ```typescript import { create } from 'zustand'; interface Message { id: string; role: 'user' | 'assistant' | 'system'; content: string; audioUrl?: string; segments?: { index: number; text: string; audioUrl?: string }[]; timestamp: number; isStreaming?: boolean; // 是否还在流式输出中 } interface ChatState { messages: Message[]; isTyping: boolean; currentMode: 'text' | 'voice_msg' | 'voice_assistant'; // Actions addMessage: (msg: Message) => void; updateLastAssistantMessage: (content: string) => void; setTyping: (typing: boolean) => void; setMode: (mode: 'text' | 'voice_msg' | 'voice_assistant') => void; clearMessages: () => void; } export const useChatStore = create((set) => ({ messages: [], isTyping: false, currentMode: 'text', addMessage: (msg) => set((state) => ({ messages: [...state.messages, msg], })), updateLastAssistantMessage: (content) => set((state) => { const messages = [...state.messages]; const lastIdx = messages.length - 1; if (lastIdx >= 0 && messages[lastIdx].role === 'assistant') { messages[lastIdx] = { ...messages[lastIdx], content: messages[lastIdx].content + content, }; } return { messages }; }), setTyping: (typing) => set({ isTyping: typing }), setMode: (mode) => set({ currentMode: mode }), clearMessages: () => set({ messages: [] }), })); ``` **3. 聊天容器组件 (components/chat/ChatContainer.tsx)** ```tsx import { useEffect, useCallback } from 'react'; import { useWebSocket } from '@/hooks/useWebSocket'; import { useChatStore } from '@/store/chatStore'; import { MessageList } from './MessageList'; import { ChatInput } from './ChatInput'; import { CyreneAvatar } from '@/components/persona/CyreneAvatar'; import { MoodIndicator } from '@/components/persona/MoodIndicator'; export function ChatContainer({ sessionId }: { sessionId: string }) { const { messages, isTyping, addMessage, setTyping } = useChatStore(); const { connect, sendMessage, onMessage } = useWebSocket(sessionId); useEffect(() => { // 连接WebSocket (使用JWT token) const token = localStorage.getItem('token'); if (token) connect(token); // 监听回复 onMessage('onResponse', (msg) => { addMessage({ id: msg.message_id, role: 'assistant', content: msg.text || '', audioUrl: msg.full_audio_url, segments: msg.segments?.map(s => ({ index: s.index, text: s.text, audioUrl: s.audio_url, })), timestamp: msg.timestamp, isStreaming: false, }); setTyping(false); }); onMessage('onError', (msg) => { addMessage({ id: msg.message_id, role: 'assistant', content: '啊……不好意思,人家刚才走神了。能再说一遍吗?♪', timestamp: Date.now(), }); setTyping(false); }); }, [sessionId]); const handleSend = useCallback((content: string, mode: string) => { // 添加用户消息 addMessage({ id: `user-${Date.now()}`, role: 'user', content, timestamp: Date.now(), }); setTyping(true); sendMessage(content, mode); }, [sendMessage, addMessage, setTyping]); return (
{/* 顶部栏 */}

昔涟

🌸 永远在你身边
{/* 消息列表 */} {/* 输入区域 */}
); } ``` **4. 昔涟专属消息气泡 (components/chat/MessageBubble.tsx)** ```tsx import { CyreneAvatar } from '@/components/persona/CyreneAvatar'; interface MessageBubbleProps { role: 'user' | 'assistant'; content: string; timestamp: number; } export function MessageBubble({ role, content, timestamp }: MessageBubbleProps) { if (role === 'user') { return (

{content}

{new Date(timestamp).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })}
); } return (

{content}

{new Date(timestamp).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })}
); } ``` ### 1.3 昔涟人格文档放置 ```bash # 将之前设计的人格文档放入项目 cp persona_cyrene.yaml backend/data/persona/cyrene_v1.yaml ``` --- ## Phase 2-5 开发路线图 ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ 📅 完整开发路线图 │ │ │ │ Week 1-2: Phase 1 — MVP 文字对话 │ │ ├── Day 1-2: 项目初始化、Docker环境、数据库 │ │ ├── Day 3-5: Gateway + WebSocket + 认证 │ │ ├── Day 6-8: AI Core (编排器 + 人格注入 + LLM适配) │ │ ├── Day 9-10: 记忆系统 (基础存储 + 检索) │ │ ├── Day 11-13: 前端聊天界面 + WebSocket对接 │ │ └── Day 14: 集成测试 + Docker部署 + MVP发布 │ │ │ │ Week 3-4: Phase 2 — 语音交互 │ │ ├── TTS服务集成 (Edge-TTS / GPT-SoVITS) │ │ ├── ASR服务集成 (Whisper) │ │ ├── 断句引擎 + 流式音频播放 │ │ ├── 语音助手模式前端适配 │ │ └── 角色音色调校 │ │ │ │ Week 5-6: Phase 3 — IoT集成 │ │ ├── 工具调用引擎 │ │ ├── IoT适配器 (MQTT / HomeAssistant API) │ │ ├── 设备控制面板UI │ │ ├── 场景联动 │ │ └── 「拟人化」设备操作包装层 │ │ │ │ Week 7-8: Phase 4 — 深度记忆 + 情感引擎 │ │ ├── 向量检索 (Qdrant语义搜索) │ │ ├── 好感度系统 │ │ ├── 心情引擎 │ │ ├── 主动行为调度器 │ │ ├── 记忆叙事化 │ │ └── 「我们的故事」页面 │ │ │ │ Week 9-10: Phase 5 — 多端覆盖 │ │ ├── PWA优化 (离线、推送) │ │ ├── React Native移动端 │ │ ├── Electron桌面端 │ │ └── 多端会话同步 │ │ │ │ Week 11+: 持续优化 │ │ ├── 性能优化 │ │ ├── 安全加固 │ │ ├── 更多IoT设备适配 │ │ ├── 角色商店 (支持切换人格) │ │ └── 社区功能 │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ``` --- ## 开发规范 ### Git分支策略 ``` main # 生产就绪代码 └── develop # 开发主线 ├── feat/mvp-chat # Phase 1 ├── feat/voice # Phase 2 ├── feat/iot # Phase 3 ├── feat/memory-v2 # Phase 4 └── feat/multi-platform # Phase 5 提交信息格式: feat: 添加WebSocket对话功能 fix: 修复人格Prompt中称呼错误 docs: 更新API文档 style: 调整昔涟头像动画 refactor: 重构记忆提取器 ``` ### 代码规范 ```yaml # .editorconfig (项目根目录) root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.go] indent_size = 4 [Makefile] indent_style = tab ``` **Go代码规范:** - 使用 `gofmt` + `golangci-lint` - 错误处理:永远不忽略error - 日志:使用 `slog` (Go标准库) **TypeScript规范:** - ESLint + Prettier - 严格模式 (`strict: true`) - 使用 `type` 而非 `interface` (除需要扩展的场景) --- ## 部署方案 ### 生产环境 Docker Compose ```yaml # docker-compose.yml (生产环境) version: '3.8' services: caddy: image: caddy:2-alpine ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile - caddy_data:/data gateway: build: ./backend/gateway environment: - ENV=production - POSTGRES_HOST=postgres - REDIS_HOST=redis # ... 其他环境变量 depends_on: - postgres - redis restart: unless-stopped ai-core: build: ./backend/ai-core environment: - ENV=production # ... 其他环境变量 depends_on: - postgres - redis - qdrant restart: unless-stopped postgres: image: pgvector/pgvector:pg16 volumes: - pg_data:/var/lib/postgresql/data - ./backend/data/init.sql:/docker-entrypoint-initdb.d/init.sql environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: cyrene_ai restart: unless-stopped redis: image: redis:7-alpine volumes: - redis_data:/data restart: unless-stopped qdrant: image: qdrant/qdrant:latest volumes: - qdrant_data:/qdrant/storage restart: unless-stopped minio: image: minio/minio:latest command: server /data environment: MINIO_ROOT_USER: ${MINIO_ACCESS_KEY} MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY} volumes: - minio_data:/data restart: unless-stopped volumes: caddy_data: pg_data: redis_data: qdrant_data: minio_data: ``` ### 迁移脚本 ```bash # scripts/migrate.sh —— 服务端迁移脚本 #!/bin/bash set -e echo "📦 昔涟AI助手 - 服务迁移脚本" echo "==============================" # 1. 停止服务 echo "1/4 停止当前服务..." docker compose down # 2. 备份数据 echo "2/4 备份数据目录..." tar -czf "cyrene_backup_$(date +%Y%m%d_%H%M%S).tar.gz" backend/data/ # 3. 复制到新服务器 (手动步骤提示) echo "3/4 请将以下文件复制到新服务器:" echo " - 整个项目目录" echo " - 或至少: docker-compose.yml, backend/data/, .env" echo "" echo " rsync -avz ./ user@new-server:/opt/cyrene-ai/" # 4. 在新服务器上启动 echo "4/4 在新服务器上执行:" echo " cd /opt/cyrene-ai" echo " docker compose up -d" echo "" echo "✅ 迁移完成!昔涟的记忆完好无损~♪" ``` --- ## 🎯 现在就可以开始的第一步 ```bash # ============================================ # 🚀 立即执行 —— 从零到第一个"你好,开拓者♪" # ============================================ # 1. 创建项目 git init cyrene-ai-assistant cd cyrene-ai-assistant # 2. 复制本文档中的 Docker Compose 配置文件 # 保存为 docker-compose.dev.yml # 3. 启动基础设施 docker compose -f docker-compose.dev.yml up -d # 4. 初始化后端 mkdir -p backend/gateway/cmd backend/ai-core/cmd cd backend go mod init github.com/yourname/cyrene-ai go work init go work use ./gateway ./ai-core # 5. 初始化前端 cd ../frontend npm create vite@latest packages/web -- --template react-ts cd packages/web npm install npm install zustand reconnecting-websocket axios npm install -D tailwindcss @tailwindcss/vite # 6. 将昔涟人格文档放入 cp /path/to/persona_cyrene.yaml ../backend/data/persona/cyrene_v1.yaml # 7. 开始编码! # 从 gateway/cmd/main.go 和 ai-core/internal/orchestrator/orchestrator.go 开始 echo "" echo "🌸 昔涟AI助手项目初始化完成!" echo " 昔涟: '开拓者,人家已经准备好了哦♪ 让我们一起创造属于我们的故事吧~'" ``` --- **总结:** 现在你有了: - ✅ 完整的项目骨架 - ✅ 开发环境配置 (Docker Compose) - ✅ 数据库Schema - ✅ 后端核心代码框架 (Go) - ✅ 前端核心代码框架 (React + TypeScript) - ✅ WebSocket协议定义 - ✅ 昔涟人格系统设计 - ✅ 12周开发路线图 - ✅ 部署和迁移方案 **下一步行动建议:** 1. 按照上面的"立即执行"步骤初始化项目 2. 先让 Gateway 跑起来(一个简单的 WebSocket echo) 3. 然后对接 AI Core(先做一个最简单的 LLM 调用 + 昔涟人格 Prompt) 4. 前端连上 WebSocket,实现第一轮对话 5. 看到「开拓者,你好呀♪」的那一刻,就是一切的开始! 需要我详细展开某个模块的具体实现吗?比如AI编排器的完整代码、前端聊天组件的完整实现、或者昔涟人格文档的更详细版本?