docs: 补充 AI 编排层接口规范与 tts_audio 音频播放方案
- 03-接口文档: 新增第三章 AI 服务层接口(STT/LLM/TTS 三个 Service interface + 接入约定) - 03-接口文档: 新增第四章 AI 编排器(句子级流式并行策略、Orchestrator 实现、错误降级表) - 03-接口文档: 锁定 tts_audio 音频格式为 audio/mpeg(MP3 24kHz),新增前端 AudioPlayer 播放方案 - 02-系统架构: 更新 Orchestrator 代码为句子级流式并行实现 - README.md: 更新 03-接口文档描述,补充 AI 服务层和编排器关键词
This commit is contained in:
@@ -84,32 +84,48 @@ Browser Go Gateway STT LLM TTS
|
|||||||
| AI Orchestrator | 编排多路 AI 调用(并行/串行) | context 取消 + 超时控制 |
|
| AI Orchestrator | 编排多路 AI 调用(并行/串行) | context 取消 + 超时控制 |
|
||||||
| Rate Limiter | 防止单用户过度消耗 API 额度 | 令牌桶算法 |
|
| Rate Limiter | 防止单用户过度消耗 API 额度 | 令牌桶算法 |
|
||||||
|
|
||||||
AI Orchestrator 核心代码:
|
AI Orchestrator 核心代码(句子级流式并行):
|
||||||
|
|
||||||
```go
|
```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)
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
// 并行:LLM 推理 + 准备 TTS
|
// Step 1: STT — 识别用户语音(串行)
|
||||||
llmCh := make(chan string, 1)
|
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() {
|
go func() {
|
||||||
resp, _ := o.llm.Chat(ctx, req.Image, req.Text, req.History)
|
defer close(sentenceCh)
|
||||||
llmCh <- resp
|
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
|
// Step 3: TTS 并行消费句子流
|
||||||
// LLM 返回后,流式推送给客户端,同时启动 TTS
|
ttsStream, _ := o.tts.SynthesizeStream(ctx, sentenceCh, TTSOptions{...})
|
||||||
ttsCh := make(chan []byte, 1)
|
for chunk := range ttsStream {
|
||||||
go func() {
|
client.SendTTSAudio(req.RequestID, chunk.Audio, chunk.IsLast)
|
||||||
audio, _ := o.tts.Synthesize(ctx, llmText)
|
}
|
||||||
ttsCh <- audio
|
|
||||||
}()
|
|
||||||
|
|
||||||
return &QueryResponse{Text: llmText, Audio: <-ttsCh}, nil
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **关键优化**:LLM 文本流和 TTS 音频流**并行推送**——客户端先逐 token 展示文字,同时 TTS 逐句子合成并推送音频,用户感知延迟大幅降低。详细的 AI 服务层接口和编排策略见 `03-接口文档.md` 第三、四章。
|
||||||
|
|
||||||
## 前端组件
|
## 前端组件
|
||||||
|
|
||||||
| 组件 | 职责 |
|
| 组件 | 职责 |
|
||||||
|
|||||||
295
docs/03-接口文档.md
295
docs/03-接口文档.md
@@ -143,11 +143,53 @@ interface TTSAudioMessage {
|
|||||||
type: "tts_audio";
|
type: "tts_audio";
|
||||||
request_id: string;
|
request_id: string;
|
||||||
audio: string; // Base64 编码的音频片段
|
audio: string; // Base64 编码的音频片段
|
||||||
mime_type: string; // "audio/mp3" 或 "audio/pcm"
|
mime_type: string; // "audio/mpeg"
|
||||||
is_last: boolean; // 是否为最后一片
|
is_last: boolean; // 是否为最后一片
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**音频格式规范**(前端播放依赖此约定):
|
||||||
|
|
||||||
|
| 属性 | 值 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| 编码 | `audio/mpeg`(MP3) | 浏览器 `<audio>` 原生支持,OpenAI TTS 默认输出 |
|
||||||
|
| 采样率 | 24kHz | OpenAI TTS 默认 |
|
||||||
|
| 声道 | 单声道 | 语音不需要立体声 |
|
||||||
|
| 传输 | Base64 编码的 MP3 片段 | 每个 `tts_audio` 消息携带一个句子的音频 |
|
||||||
|
| 切片粒度 | 按句子切分 | LLM 输出中按 `。!?\n` 等标点切分,每个句子独立合成 |
|
||||||
|
|
||||||
|
**流式播放时序**:`tts_audio` 消息按句子顺序到达,前端应按序排队播放,不要等全部到齐再播。
|
||||||
|
|
||||||
|
**前端播放实现要点**:
|
||||||
|
|
||||||
|
1. **排队播放**:收到 `tts_audio` 时,将 Base64 解码为 Blob URL 并加入播放队列。第一片到达即开始播放,后续片段在 `onended` 回调中自动衔接。
|
||||||
|
2. **错误容错**:单个片段播放失败时跳过,继续播放队列中下一个,不中断整个回复。
|
||||||
|
3. **打断清理**:收到 `interrupt` 消息或用户触发打断时,清空播放队列并释放所有 Blob URL。
|
||||||
|
4. **类型锁定**:`mime_type` 字段固定为 `"audio/mpeg"`,前端解码时直接使用,无需运行时判断。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 前端播放器伪代码
|
||||||
|
class AudioPlayer {
|
||||||
|
private queue: string[] = []; // Blob URL 队列
|
||||||
|
|
||||||
|
enqueue(base64: string) {
|
||||||
|
const url = decodeBase64Audio(base64, "audio/mpeg");
|
||||||
|
this.queue.push(url);
|
||||||
|
if (this.queue.length === 1) this.playNext(); // 第一片到了就开始播
|
||||||
|
}
|
||||||
|
|
||||||
|
private playNext() {
|
||||||
|
if (this.queue.length === 0) return;
|
||||||
|
const audio = new Audio(this.queue[0]);
|
||||||
|
audio.onended = () => { URL.revokeObjectURL(this.queue.shift()!); this.playNext(); };
|
||||||
|
audio.onerror = () => { URL.revokeObjectURL(this.queue.shift()!); this.playNext(); };
|
||||||
|
audio.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() { this.queue.forEach(url => URL.revokeObjectURL(url)); this.queue = []; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### `error` — 错误通知
|
#### `error` — 错误通知
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
@@ -250,7 +292,250 @@ DELETE /api/sessions/{session_id}
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 三、数据模型
|
## 三、AI 服务层接口
|
||||||
|
|
||||||
|
Go 网关内部与外部 AI 服务(Deepgram STT、GPT-4o、OpenAI TTS)的调用契约。前后端联调时,后端需实现这些接口。
|
||||||
|
|
||||||
|
### STT 服务接口
|
||||||
|
|
||||||
|
语音识别:接收前端采集的音频,返回识别文本。
|
||||||
|
|
||||||
|
```go
|
||||||
|
// STTService 语音识别服务契约。
|
||||||
|
type STTService interface {
|
||||||
|
// Recognize 识别一段完整音频,返回最终文本。
|
||||||
|
Recognize(ctx context.Context, audio []byte, opts STTOptions) (string, error)
|
||||||
|
|
||||||
|
// RecognizeStream 流式识别(边说边识别,可选实现)。
|
||||||
|
// audioStream 持续接收音频片段,返回的 channel 持续输出中间结果。
|
||||||
|
RecognizeStream(ctx context.Context, audioStream <-chan []byte, opts STTOptions) (<-chan STTPartial, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// STTOptions 语音识别参数。
|
||||||
|
type STTOptions struct {
|
||||||
|
Encoding string // "pcm_s16le" — 前端 VAD 输出格式
|
||||||
|
SampleRate int // 16000 — 前端麦克风采样率
|
||||||
|
Language string // "zh-CN"
|
||||||
|
}
|
||||||
|
|
||||||
|
// STTPartial 流式识别的中间/最终结果。
|
||||||
|
type STTPartial struct {
|
||||||
|
Text string
|
||||||
|
IsFinal bool
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Deepgram 接入约定**:
|
||||||
|
- 连接方式:WebSocket `wss://api.deepgram.com/v1/listen`
|
||||||
|
- 音频格式:PCM 16-bit signed little-endian,16kHz 单声道(与前端 `MicManager` 输出一致)
|
||||||
|
- 返回格式:`channel.alternatives[0].transcript`,`is_final` 字段标识最终结果
|
||||||
|
- 超时:单次识别 5 秒超时
|
||||||
|
|
||||||
|
### LLM 服务接口
|
||||||
|
|
||||||
|
多模态推理:接收图像 + 文本 + 对话历史,流式返回回复。
|
||||||
|
|
||||||
|
```go
|
||||||
|
// LLMService 多模态大模型服务契约。
|
||||||
|
type LLMService interface {
|
||||||
|
// ChatStream 流式推理,返回增量文本的 channel。
|
||||||
|
// 调用方必须消费 channel 直到 Done=true,否则需 cancel ctx 以释放连接。
|
||||||
|
ChatStream(ctx context.Context, req LLMRequest) (<-chan LLMChunk, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LLMRequest 推理请求。
|
||||||
|
type LLMRequest struct {
|
||||||
|
Image []byte // JPEG 图片(已从 Base64 解码)
|
||||||
|
Text string // 用户语音识别后的文本
|
||||||
|
History []Message // 最近 N 轮对话历史
|
||||||
|
Language string // "zh-CN"
|
||||||
|
}
|
||||||
|
|
||||||
|
// LLMChunk 流式推理的一个增量片段。
|
||||||
|
type LLMChunk struct {
|
||||||
|
Delta string // 增量文本
|
||||||
|
Done bool // 是否结束
|
||||||
|
TokensUsed *TokenUsage // 仅 Done=true 时有值
|
||||||
|
Model string // 实际使用的模型名
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenUsage 用量统计。
|
||||||
|
type TokenUsage struct {
|
||||||
|
Prompt int
|
||||||
|
Completion int
|
||||||
|
Total int
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**OpenAI API 接入约定**:
|
||||||
|
- 端点:`POST https://api.openai.com/v1/chat/completions`
|
||||||
|
- 图片传入:`image_url` 字段使用 `data:image/jpeg;base64,...` 格式
|
||||||
|
- 流式响应:`stream: true`,通过 SSE 逐 chunk 返回
|
||||||
|
- Prompt 结构:
|
||||||
|
|
||||||
|
```
|
||||||
|
system: "你是一个视觉助手。用户通过摄像头看到一个场景,并用语音向你提问。
|
||||||
|
请用简洁自然的中文回答。如果涉及视觉描述,先说'我看到...'。"
|
||||||
|
user: [图片 + 用户语音文本]
|
||||||
|
(重复 History 中的历史消息)
|
||||||
|
```
|
||||||
|
|
||||||
|
- 超时:10 秒,超时返回 `LLM_TIMEOUT` 错误
|
||||||
|
- 模型选择:默认 `gpt-4o`,由 Model Router 按需切换
|
||||||
|
|
||||||
|
### TTS 服务接口
|
||||||
|
|
||||||
|
语音合成:接收文本流,输出音频 chunk 流。
|
||||||
|
|
||||||
|
```go
|
||||||
|
// TTSService 语音合成服务契约。
|
||||||
|
type TTSService interface {
|
||||||
|
// SynthesizeStream 流式合成。
|
||||||
|
// textStream 接收句子级文本(由 Orchestrator 的句子切分器产出),
|
||||||
|
// 返回的 channel 持续输出 MP3 音频 chunk。
|
||||||
|
SynthesizeStream(ctx context.Context, textStream <-chan string, opts TTSOptions) (<-chan TTSChunk, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TTSOptions 合成参数。
|
||||||
|
type TTSOptions struct {
|
||||||
|
Voice string // "alloy" | "nova" | "shimmer" | ...
|
||||||
|
Speed float64 // 1.0 为正常语速
|
||||||
|
OutputFmt string // "mp3" — 固定使用 MP3,浏览器原生支持
|
||||||
|
SampleRate int // 24000
|
||||||
|
}
|
||||||
|
|
||||||
|
// TTSChunk 一个音频片段。
|
||||||
|
type TTSChunk struct {
|
||||||
|
Audio []byte // MP3 音频数据(未 Base64 编码,由发送层编码)
|
||||||
|
IsLast bool // 是否为最后一片
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**OpenAI TTS 接入约定**:
|
||||||
|
- 端点:`POST https://api.openai.com/v1/audio/speech`
|
||||||
|
- 模型:`tts-1`(低延迟优先)或 `tts-1-hd`(高音质)
|
||||||
|
- 输出格式:`mp3`,24kHz
|
||||||
|
- 流式:使用 `response_format: "mp3"` 并读取 response body 流
|
||||||
|
- 超时:单个句子 5 秒超时
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、AI 编排器(Orchestrator)
|
||||||
|
|
||||||
|
### 编排策略:句子级流式并行
|
||||||
|
|
||||||
|
核心矛盾:LLM 流式输出逐 token,TTS 需要完整句子才能合成。解法:**句子切分器 + 管道并行**。
|
||||||
|
|
||||||
|
```
|
||||||
|
LLM 流式输出: "这" "是一" "朵红色" "的花。" "它看起" "来很美" "丽。"
|
||||||
|
↓
|
||||||
|
┌── 句子检测器(按 。!?\n 切分)──┐
|
||||||
|
↓ ↓
|
||||||
|
句子1: "这是一朵红色的花。" 句子2: "它看起来很美丽。"
|
||||||
|
↓ ↓
|
||||||
|
TTS 合成 TTS 合成
|
||||||
|
↓ ↓
|
||||||
|
音频 chunk → 推送前端 音频 chunk → 推送前端
|
||||||
|
```
|
||||||
|
|
||||||
|
**时序保证**:
|
||||||
|
- `llm_chunk` 消息一定先于对应句子的 `tts_audio` 到达客户端
|
||||||
|
- 用户先看到文字,紧接着听到语音(感知延迟 < 0.5 秒)
|
||||||
|
- 不必等 LLM 全部输出完才开始 TTS
|
||||||
|
|
||||||
|
### Orchestrator 接口
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Orchestrator AI 编排器,协调 STT → LLM → TTS 全链路。
|
||||||
|
type Orchestrator struct {
|
||||||
|
stt STTService
|
||||||
|
llm LLMService
|
||||||
|
tts TTSService
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessQuery 处理一次完整的视觉对话请求。
|
||||||
|
// 通过 client 向前端实时推送 stt_result、llm_chunk、llm_done、tts_audio 消息。
|
||||||
|
func (o *Orchestrator) ProcessQuery(ctx context.Context, client MessageSender, req *QueryRequest) {
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Step 1: STT — 识别用户语音
|
||||||
|
text, err := o.stt.Recognize(ctx, req.Audio, STTOptions{
|
||||||
|
Encoding: "pcm_s16le", SampleRate: 16000, Language: "zh-CN",
|
||||||
|
})
|
||||||
|
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, Language: "zh-CN",
|
||||||
|
})
|
||||||
|
|
||||||
|
sentenceCh := make(chan string, 4)
|
||||||
|
go func() {
|
||||||
|
defer close(sentenceCh)
|
||||||
|
var buf strings.Builder
|
||||||
|
var fullText strings.Builder
|
||||||
|
for chunk := range llmStream {
|
||||||
|
// 即时推送文字给客户端(逐 token 显示)
|
||||||
|
client.SendLLMChunk(req.RequestID, chunk.Delta)
|
||||||
|
fullText.WriteString(chunk.Delta)
|
||||||
|
buf.WriteString(chunk.Delta)
|
||||||
|
// 遇到句子边界就吐出
|
||||||
|
if isSentenceEnd(chunk.Delta) {
|
||||||
|
sentenceCh <- buf.String()
|
||||||
|
buf.Reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 最后一段不足一句的也吐出
|
||||||
|
if buf.Len() > 0 {
|
||||||
|
sentenceCh <- buf.String()
|
||||||
|
}
|
||||||
|
// 推送 llm_done
|
||||||
|
client.SendLLMDone(req.RequestID, fullText.String(), chunk.TokensUsed, chunk.Model)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Step 3: TTS 并行消费句子流
|
||||||
|
ttsStream, _ := o.tts.SynthesizeStream(ctx, sentenceCh, TTSOptions{
|
||||||
|
Voice: "alloy", OutputFmt: "mp3", SampleRate: 24000,
|
||||||
|
})
|
||||||
|
for chunk := range ttsStream {
|
||||||
|
client.SendTTSAudio(req.RequestID, chunk.Audio, chunk.IsLast)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isSentenceEnd 判断 delta 中是否包含句子结束标志。
|
||||||
|
func isSentenceEnd(delta string) bool {
|
||||||
|
return strings.ContainsAny(delta, "。!?\n.!?\n")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 并发控制
|
||||||
|
|
||||||
|
- 每个 `ProcessQuery` 调用在独立 goroutine 中运行
|
||||||
|
- `context.WithTimeout` 确保 10 秒总超时
|
||||||
|
- `interrupt` 消息触发 `cancel()`,LLM/TTS 流式全部中断
|
||||||
|
- 同一 session 内同时只允许一个活跃请求,新请求自动取消上一个
|
||||||
|
|
||||||
|
### 错误处理与降级
|
||||||
|
|
||||||
|
| 故障点 | 处理策略 | 客户端表现 |
|
||||||
|
|--------|---------|-----------|
|
||||||
|
| STT 失败 | 发送 `STT_ERROR`,终止本次请求 | 回退到纯文本模式 |
|
||||||
|
| LLM 超时(>10s) | 发送 `LLM_TIMEOUT`,取消 TTS | 提示用户重试 |
|
||||||
|
| LLM 部分输出后失败 | 已推送的 `llm_chunk` 保留,发送 `error` 通知中断 | 显示已收到的部分文字 |
|
||||||
|
| TTS 失败 | 静默跳过,`llm_done` 正常发送 | 只有文字回复,无语音 |
|
||||||
|
| TTS 部分失败 | 已推送的音频保留,后续句子跳过 | 部分句子有语音 |
|
||||||
|
| interrupt 打断 | cancel context,清空所有流 | 前端清空播放队列 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、数据模型
|
||||||
|
|
||||||
|
> 注:Go 和 TypeScript 的数据模型定义见下方。AI 服务层的 Go 模型见上方"AI 服务层接口"章节。
|
||||||
|
|
||||||
### Go 后端模型
|
### Go 后端模型
|
||||||
|
|
||||||
@@ -326,7 +611,7 @@ type ClientMessage =
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 四、扩展接口设计
|
## 六、扩展接口设计
|
||||||
|
|
||||||
通过 Repository 接口隔离存储层,MVP 用内存实现,后续替换为数据库——业务逻辑零改动。
|
通过 Repository 接口隔离存储层,MVP 用内存实现,后续替换为数据库——业务逻辑零改动。
|
||||||
|
|
||||||
@@ -402,7 +687,7 @@ func NewApp(cfg *Config) *App {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 五、错误码
|
## 七、错误码
|
||||||
|
|
||||||
| 错误码 | 含义 | 客户端处理建议 |
|
| 错误码 | 含义 | 客户端处理建议 |
|
||||||
|--------|------|--------------|
|
|--------|------|--------------|
|
||||||
@@ -417,7 +702,7 @@ func NewApp(cfg *Config) *App {
|
|||||||
| `TTS_ERROR` | 语音合成失败 | 静默回退到纯文本回复 |
|
| `TTS_ERROR` | 语音合成失败 | 静默回退到纯文本回复 |
|
||||||
| `INTERNAL_ERROR` | 服务端内部错误 | 提示用户重试 |
|
| `INTERNAL_ERROR` | 服务端内部错误 | 提示用户重试 |
|
||||||
|
|
||||||
## 六、连接管理
|
## 八、连接管理
|
||||||
|
|
||||||
**心跳机制**:客户端每 30 秒发送 `ping`,服务端回复 `pong`。超过 60 秒无 `ping`,服务端判定连接断开并清理会话资源。
|
**心跳机制**:客户端每 30 秒发送 `ping`,服务端回复 `pong`。超过 60 秒无 `ping`,服务端判定连接断开并清理会话资源。
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ CamTalk 是一款多模态实时 AI 视觉对话助手。用户通过摄像头
|
|||||||
|------|------|
|
|------|------|
|
||||||
| [01-项目概述](01-项目概述.md) | 项目目标、核心挑战、交付物 |
|
| [01-项目概述](01-项目概述.md) | 项目目标、核心挑战、交付物 |
|
||||||
| [02-系统架构](02-系统架构.md) | 三层架构、技术栈、核心交互流程、前后端模块、存储策略、部署架构 |
|
| [02-系统架构](02-系统架构.md) | 三层架构、技术栈、核心交互流程、前后端模块、存储策略、部署架构 |
|
||||||
| [03-接口文档](03-接口文档.md) | WebSocket 协议、REST API、数据模型、错误码、连接管理(**实现时首先阅读**) |
|
| [03-接口文档](03-接口文档.md) | WebSocket 协议、REST API、**AI 服务层接口(STT/LLM/TTS)**、**编排器设计**、数据模型、错误码、连接管理(**实现时首先阅读**) |
|
||||||
| [04-技术选型](04-技术选型.md) | 持久化层(PostgreSQL)和前端边缘处理层的选型对比与决策理由 |
|
| [04-技术选型](04-技术选型.md) | 持久化层(PostgreSQL)和前端边缘处理层的选型对比与决策理由 |
|
||||||
| [05-用户故事](05-用户故事.md) | P0/P1/P2 用户故事、验收标准、优先级决策依据 |
|
| [05-用户故事](05-用户故事.md) | P0/P1/P2 用户故事、验收标准、优先级决策依据 |
|
||||||
| [06-语音交互](06-语音交互.md) | VAD → STT → LLM → TTS 全链路、延迟优化 |
|
| [06-语音交互](06-语音交互.md) | VAD → STT → LLM → TTS 全链路、延迟优化 |
|
||||||
|
|||||||
Reference in New Issue
Block a user