diff --git a/docs/02-系统架构.md b/docs/02-系统架构.md index cd904f5..9dd6a5f 100644 --- a/docs/02-系统架构.md +++ b/docs/02-系统架构.md @@ -35,7 +35,7 @@ | WebSocket | gorilla/websocket | Go 生态最成熟的 WebSocket 库 | | 会话存储 | Redis | 高速 KV 存储,适合会话状态和上下文缓存 | | 持久化存储 | PostgreSQL | 对话历史、用量统计、用户偏好(MVP 阶段可选) | -| 配置管理 | Viper | 支持多格式配置,环境变量覆盖 | +| 配置管理 | Viper | 支持 YAML + 环境变量覆盖,详见 `03-接口文档.md` 第六章 | | 日志 | Zap | 高性能结构化日志 | ### AI 服务 @@ -79,37 +79,53 @@ Browser Go Gateway STT LLM TTS | 模块 | 职责 | 关键实现 | |------|------|---------| | WebSocket Hub | 管理所有客户端连接,广播/定向推送 | goroutine per connection | -| Session Manager | 维护用户会话状态、对话历史 | Redis + TTL 过期策略 | +| Session Manager | 维护用户会话状态、对话历史 | Redis Hash + List,30 分钟 TTL(详见 `03-接口文档.md` 第五章) | | Model Router | 根据请求类型选择 AI 模型 | 规则引擎 + 成本阈值 | | AI Orchestrator | 编排多路 AI 调用(并行/串行) | context 取消 + 超时控制 | | Rate Limiter | 防止单用户过度消耗 API 额度 | 令牌桶算法 | -AI Orchestrator 核心代码: +AI Orchestrator 核心代码(句子级流式并行): ```go -func (o *Orchestrator) ProcessQuery(ctx context.Context, req *QueryRequest) (*QueryResponse, error) { +func (o *Orchestrator) ProcessQuery(ctx context.Context, client MessageSender, req *QueryRequest) { ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() - // 并行:LLM 推理 + 准备 TTS - llmCh := make(chan string, 1) + // Step 1: STT — 识别用户语音(串行) + text, err := o.stt.Recognize(ctx, req.Audio, STTOptions{...}) + if err != nil { + client.SendError(req.RequestID, "STT_ERROR", err.Error()) + return + } + client.SendSTTResult(req.RequestID, text, true) + + // Step 2: LLM 流式输出 + 句子切分(并行) + llmStream, _ := o.llm.ChatStream(ctx, LLMRequest{Image: req.Image, Text: text, ...}) + sentenceCh := make(chan string, 4) go func() { - resp, _ := o.llm.Chat(ctx, req.Image, req.Text, req.History) - llmCh <- resp + defer close(sentenceCh) + var buf strings.Builder + for chunk := range llmStream { + client.SendLLMChunk(req.RequestID, chunk.Delta) // 逐 token 推送文字 + buf.WriteString(chunk.Delta) + if isSentenceEnd(chunk.Delta) { // 按 。!?\n 切分 + sentenceCh <- buf.String() + buf.Reset() + } + } + if buf.Len() > 0 { sentenceCh <- buf.String() } }() - llmText := <-llmCh - // LLM 返回后,流式推送给客户端,同时启动 TTS - ttsCh := make(chan []byte, 1) - go func() { - audio, _ := o.tts.Synthesize(ctx, llmText) - ttsCh <- audio - }() - - return &QueryResponse{Text: llmText, Audio: <-ttsCh}, nil + // Step 3: TTS 并行消费句子流 + ttsStream, _ := o.tts.SynthesizeStream(ctx, sentenceCh, TTSOptions{...}) + for chunk := range ttsStream { + client.SendTTSAudio(req.RequestID, chunk.Audio, chunk.IsLast) + } } ``` +> **关键优化**:LLM 文本流和 TTS 音频流**并行推送**——客户端先逐 token 展示文字,同时 TTS 逐句子合成并推送音频,用户感知延迟大幅降低。详细的 AI 服务层接口和编排策略见 `03-接口文档.md` 第三、四章。 + ## 前端组件 | 组件 | 职责 | diff --git a/docs/03-接口文档.md b/docs/03-接口文档.md index 3aa530e..32b6974 100644 --- a/docs/03-接口文档.md +++ b/docs/03-接口文档.md @@ -143,11 +143,53 @@ interface TTSAudioMessage { type: "tts_audio"; request_id: string; audio: string; // Base64 编码的音频片段 - mime_type: string; // "audio/mp3" 或 "audio/pcm" + mime_type: string; // "audio/mpeg" is_last: boolean; // 是否为最后一片 } ``` +**音频格式规范**(前端播放依赖此约定): + +| 属性 | 值 | 说明 | +|------|------|------| +| 编码 | `audio/mpeg`(MP3) | 浏览器 `