Files
dreamweaver/docs/technical/voice-co-creation-phase-a-tech-spec.md

660 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 技术方案:语音共创 Phase A MVP
**Version**: 0.1
**Date**: 2026-04-19
**Status**: Draft / Companion to `voice-co-creation-mode-incremental-prd.md`
---
## 1. 目标
这份技术方案用于把 [语音共创模式增量 PRD](../product/voice-co-creation-mode-incremental-prd.md) 收敛成一个可实现的 Phase A MVP。
## 0. 当前实现快照2026-04-20
远端 `main` 已经跑通以下 Phase A 主链路:
- 独立 Voice Studio 入口页与最近会话恢复
- `voice_sessions / voice_turns / voice_session_events` 数据模型与 API
- 文本 fallback 回合、录音上传回合、turn 轮询结果查询
- turn 级语音补发、失败重试、会话 abandon、finalize -> Story 持久化
- 会话事件记录与最近 turn / 最近事件展示
本轮新增收束:
-`transcript_confidence``intent_confidence` 偏低时,后端优先返回确认提示,而不是直接把这一轮写进故事正文
- 已补完整确认流:支持“按这个理解继续”“重说本轮”“改成文本输入”
- 前端明确展示“本轮系统理解为”与“建议家长确认后再继续”提示
- 低置信度确认链路已有后端测试覆盖,可作为下一阶段继续接 ASR 与更细确认交互的基础
- 已新增用户转写安全检查、assistant 输出柔性改写与 `safety_flags` 事件记录
- finalize 会生成更稳定的标题/摘要,并在条件允许时自动排队封面补全 job
- 已新增 `voice session analytics` 聚合指标,可跟踪 turn 成功率、ASR/TTS 失败、低置信度触发、finalize 转化率、输入构成、语音时长、Provider 分布、确认率和平均置信度,并支持按转写 Provider 与会话状态筛选
- `voice session finalize` 现在会返回可追踪的 `generation_job_id`,让正式 Story 资产补全重新接回现有 generation trace 主干
- 语音共创触发的 `asset_generation` job 现在也支持沿用统一 generation job 的取消 / 重试控制
Phase A 的核心目标不是做“完全实时的语音陪伴”,而是验证以下最小闭环:
1. 孩子可以用语音发起一个故事
2. 孩子可以在中途用语音修正故事走向
3. 系统能用语音回应并继续讲述
4. 会话结束后可以保存为正式故事,进入现有故事库
这一步必须尽量复用现有统一生成工作流、Profile、Universe、Memory、TTS 与故事持久化主干,而不是重新搭一套平行系统。
---
## 2. 非目标
Phase A 明确不做以下内容:
- 不做 WebRTC / 全双工实时通话
- 不做复杂 barge-in系统说到一半被自然打断
- 不做多人共创
- 不做绘本共创主链路
- 不做每回合即时插图生成
- 不把 ASR / Realtime 能力立刻并入当前 admin Provider 配置面板
换句话说Phase A 是一个 **回合式 voice session MVP**,不是最终形态。
---
## 3. 推荐体验形态
### 3.1 交互模式
- 前端提供独立“语音共创”入口
- 用户按住说话或点击开始录音
- 一次提交一段语音
- 后端完成:转写 -> 意图识别 -> 更新故事状态 -> 生成回复 -> 合成语音
- 前端轮询或 SSE 获取这一轮结果
- 播放系统语音并展示文本摘要
- 进入下一轮
### 3.2 为什么先做回合式
- 与现有 `generation_job` + 轮询模式更一致
- 更容易做会话恢复和状态排障
- 对延迟要求低于全双工实时
- 能先验证“孩子是否愿意用语音改故事”
---
## 4. 与现有系统的关系
### 4.1 可以直接复用的能力
- `users`
- `child_profiles`
- `story_universes`
- `build_enhanced_memory_context`
- `stories`
- `generation_jobs` / `generation_job_events`
- `text` 能力对应的现有文本模型
- `tts` Provider Router
- 现有故事库、故事详情页和后续资产补全链路
### 4.2 当前明显缺失的能力
- 语音输入识别ASR / STT
- 会话级状态模型
- “剧情修正”语义解析
- 会话级可观测事件
- 从 voice session 保存为正式 Story 的收束服务
---
## 5. 核心设计原则
1. **会话先于结果**
语音共创首先是一个 session不是一次性 generation request。
2. **最终结果仍然落到 Story**
voice session 是过程对象Story 才是最终可回看、可补资产、可沉淀记忆的正式对象。
3. **过程状态与结果状态分离**
`voice_sessions` 管过程,`stories` 管正式结果,避免把会话噪音直接污染正式故事结构。
4. **先复用 `text` / `tts` 主干,再决定是否拆新 capability**
首版把复杂度压到最小,不急着把所有新能力都映射进 admin Provider 面板。
5. **首版允许“文本可用但语音失败”降级**
这与当前 DreamWeaver 主结果优先可读的原则一致。
---
## 6. 数据模型建议
## 6.1 `voice_sessions`
用于承载一个完整的语音共创会话。
**建议字段**
- `id: str`
- `user_id: str`
- `child_profile_id: str | None`
- `universe_id: str | None`
- `status: str`
- `target_mode: str`
Phase A 固定为 `story`
- `current_turn_index: int`
- `working_title: str | None`
- `story_state: dict`
- `latest_user_transcript: str | None`
- `latest_assistant_text: str | None`
- `final_story_id: int | None`
- `last_error: str | None`
- `created_at`
- `updated_at`
**`story_state` 建议结构**
```json
{
"premise": "小猫去太空",
"characters": ["小猫", "月亮朋友"],
"tone": "温暖冒险",
"beats": [
"准备出发",
"遇到困难",
"得到帮助"
],
"narrative_segments": [
"第一段故事",
"第二段故事"
],
"latest_direction": "不要让它哭,给它一个朋友",
"safety_flags": []
}
```
### 6.2 `voice_turns`
用于记录会话中的每一轮输入与响应。
**建议字段**
- `id: str`
- `session_id: str`
- `turn_index: int`
- `status: str`
- `user_audio_path: str | None`
- `user_transcript: str | None`
- `transcript_confidence: float | None`
- `detected_intent: str`
- `intent_confidence: float | None`
- `story_patch: dict`
- `assistant_text: str | None`
- `assistant_audio_path: str | None`
- `assistant_duration_ms: int | None`
- `error_message: str | None`
- `created_at`
- `updated_at`
**`detected_intent` 首版建议值**
- `start_story`
- `continue_story`
- `correct_story`
- `end_story`
- `save_story`
- `unknown`
### 6.3 `voice_session_events`
建议新增轻量 append-only 事件表,而不是强行复用 `generation_job_events`
原因很简单:
- `generation_job_events` 的主语是“后台生成任务”
- `voice_session_events` 的主语是“语音会话过程”
二者关注点不同,不应混在一个表里。
**建议字段**
- `id: int`
- `session_id: str`
- `turn_id: str | None`
- `event_type: str`
- `status: str`
- `message: str | None`
- `event_metadata: dict`
- `created_at`
### 6.4 为什么首版不单独建 `voice_story_snapshots`
首版先把故事中间态压缩进 `voice_sessions.story_state``voice_turns.story_patch` 即可。
如果后续需要:
- 可回滚到任意 turn
- 支持剧情分支
- 做更复杂的 diff / merge
再考虑独立 `voice_story_snapshots` 表。
---
## 7. 状态机建议
## 7.1 Session 状态
**建议值**
- `draft`
- `active`
- `processing_turn`
- `waiting_user`
- `finalizing_story`
- `completed`
- `abandoned`
- `failed`
**状态含义**
- `draft`: 会话已创建但还没有第一轮输入
- `active`: 已进入共创流程
- `processing_turn`: 正在处理某一轮语音
- `waiting_user`: 系统已经返回,等待下一轮输入
- `finalizing_story`: 正在把会话收束为正式 Story
- `completed`: 已完成并成功保存
- `abandoned`: 用户主动结束但未保存
- `failed`: 当前会话不可继续,需要用户重新开始
## 7.2 Turn 状态
**建议值**
- `received`
- `transcribing`
- `intent_resolved`
- `narrative_ready`
- `audio_ready`
- `failed`
---
## 8. API 草图
## 8.1 创建会话
`POST /api/voice-sessions`
**Request**
```json
{
"child_profile_id": "profile-id",
"universe_id": "universe-id",
"target_mode": "story"
}
```
**Response**
```json
{
"id": "session-id",
"status": "draft",
"target_mode": "story",
"current_turn_index": 0,
"final_story_id": null
}
```
## 8.2 查询会话详情
`GET /api/voice-sessions/{session_id}`
返回:
- session 基础信息
- 当前 `story_state`
- 最近若干 turn
- 是否可继续
- 是否可保存
## 8.3 提交一轮语音输入
`POST /api/voice-sessions/{session_id}/turns`
建议首版使用 `multipart/form-data`
- `audio_file`
- `duration_ms`
- `client_turn_id`(可选)
如果后续为了调试和降级,也可以兼容:
- `transcript_text`
**Response**
```json
{
"turn_id": "turn-id",
"session_id": "session-id",
"status": "received"
}
```
后续由前端轮询 turn 或 session detail。
## 8.4 查询 turn 结果
`GET /api/voice-sessions/{session_id}/turns/{turn_id}`
返回:
- `user_transcript`
- `detected_intent`
- `assistant_text`
- `assistant_audio_url` 或音频状态
- `status`
## 8.5 结束并保存为正式故事
`POST /api/voice-sessions/{session_id}/finalize`
**Request**
```json
{
"save_story": true,
"generate_cover": true,
"generate_final_audio": false
}
```
**Response**
```json
{
"session_id": "session-id",
"status": "completed",
"story_id": 123,
"generation_job_id": "optional-asset-job-id"
}
```
## 8.6 放弃会话
`POST /api/voice-sessions/{session_id}/abandon`
用于显式结束但不保存。
---
## 9. 后端处理链路
## 9.1 一轮 turn 的推荐处理顺序
1. 接收语音文件
2. 创建 `voice_turns` 记录,状态为 `received`
3. 保存原始音频文件到会话目录
4. 调用 ASR更新 `user_transcript`
5. 调用意图解析 / 对话编排器,得到:
- `detected_intent`
- `story_patch`
- `assistant_text`
6.`story_patch` 合并进 `voice_sessions.story_state`
7. 调用 TTS 生成本轮系统回应语音
8. 更新 turn 为 `audio_ready``narrative_ready`
9. 更新 session 为 `waiting_user`
## 9.2 为什么首版不直接放进 Celery worker 主链路
当前 `generation_jobs` 很适合“提交一个任务,然后后台处理较长流程”,但语音共创 turn 的特点是:
- 频率更高
- 单轮更短
- 更接近交互而不是离线任务
因此首版建议:
- turn 处理由应用层直接编排
- 必要时用短后台任务或轻量异步任务承接
- 只有最终“保存为正式故事 + 补资产”再接回现有 generation 主干
## 9.3 最终收束为 Story 的方式
当用户选择 finalize 时:
1.`story_state.narrative_segments` 合并为最终正文
2. 生成标题和摘要
3. 写入 `stories`
4. 若需要封面或正式整篇朗读音频,再触发已有资产补全链路
首版建议不要让 finalize 再走完整“文本生成主任务”,因为正文已经在会话中逐步生成出来了。
---
## 10. Provider 与模型接入建议
## 10.1 Phase A 的最小能力分层
### A. ASR
新增会话内语音转写能力。
**建议**
- Phase A 先接单一稳定供应商
- 暂不并入当前 admin Provider CRUD
- 先通过配置文件或单独 service 封装
理由是:
- 当前 admin Provider 已扩展到 `text/image/tts/storybook/asr`
- Phase A Alpha 已把 ASR 纳入最小 Provider 能力,但仍保留 demo fallback避免真实转写不可用时阻塞演示
### B. Dialogue Orchestrator
首版建议直接复用当前 `text` capability而不是另开一套 provider type。
对 orchestrator 来说,我们更需要:
- 意图识别稳定
- 遵循结构化输出
- 能基于已有 `story_state` 生成下一段文本
这本质上仍然可以由现有文本模型承担。
### C. TTS
直接复用当前 `tts` Provider Router。
### D. Final Story Asset Generation
继续复用已有 story asset completion 逻辑。
---
## 11. 文件与存储建议
### 11.1 音频文件目录
建议新增目录:
`storage/voice_sessions/<session_id>/`
目录下可包括:
- `turn-001-user.webm`
- `turn-001-assistant.mp3`
- `turn-002-user.webm`
- `turn-002-assistant.mp3`
### 11.2 为什么不直接复用 story audio cache
因为二者语义不同:
- `story audio cache` 是正式故事整篇或固定文本的可复用结果
- `voice session audio` 是会话过程资产
不应混存。
---
## 12. 安全与家长控制
Phase A 至少要有以下机制:
1. **儿童内容安全 prompt**
2. **转写后文本安全检查**
3. **低置信度识别提示**
4. **必要时家长可见的“系统理解为”**
### 首版推荐策略
- 默认不要求每轮家长确认
-`transcript_confidence``intent_confidence` 低于阈值时,再提示确认
- 如果内容越界,则给出柔性改写或安全拒绝
---
## 13. 可观测性建议
### 13.1 需要记录的关键事件
- `session_created`
- `turn_received`
- `turn_transcribed`
- `intent_resolved`
- `story_state_updated`
- `assistant_text_ready`
- `assistant_audio_ready`
- `session_finalizing`
- `session_saved_as_story`
- `session_failed`
### 13.2 成本记录建议
Phase A 就应该按 turn 记录:
- ASR 成本
- 对话生成成本
- TTS 成本
这部分后续可以汇总到新的语音共创 analytics而不是一开始就挤进现有故事生成 dashboard。
---
## 14. 前端建议
### 14.1 Phase A 最小 UI
- 独立入口页或 modal
- 录音按钮
- 当前会话卡片
- 本轮识别文本
- 系统文本回应
- 系统音频播放按钮
- “继续说”按钮
- “保存到故事库”按钮
### 14.2 状态反馈必须有
至少显示:
- 正在转写
- 正在理解故事走向
- 正在讲述
- 本轮失败,可重试
### 14.3 恢复策略
页面重新打开后:
- 如果存在 `active / waiting_user` session优先恢复最近会话
- 展示最近一轮系统回应
- 允许继续说或结束保存
---
## 15. 推荐实现顺序
### Step 1
- 新增 `voice_sessions`
- 新增 `voice_turns`
- 新增 `voice_session_events`
- 新增基础 schema
### Step 2
- 新增会话 API
- 新增 turn API
- 打通音频文件存储
### Step 3
- 接入 ASR
- 实现 turn 编排 service
- 复用现有 `text``tts` 能力
### Step 4
- 实现 finalize -> Story 持久化
- 接入现有故事库入口
### Step 5
- 补最小 UI
- 补会话恢复
- 补日志和成本记录
---
## 16. 测试建议
### 单元测试
- `story_patch` 合并逻辑
- 意图映射逻辑
- finalize 正文拼接逻辑
### API 集成测试
- 创建会话
- 提交 turn
- 查询 turn
- 恢复会话
- finalize 为 Story
### 失败场景测试
- ASR 失败
- TTS 失败但文本成功
- 低置信度输入
- finalize 重复提交
---
## 17. 当前最合理的技术判断
如果目标是“尽快验证孩子能否通过声音共创故事”,那么最合理的 Phase A 路线是:
1. 新增 `voice session` 层,而不是修改现有 `generation_jobs` 去硬扛会话语义
2. 复用现有 `text``tts` 主干,不急着把所有新能力都产品化成新的 Provider 管理界面
3. 先做回合式体验,不做复杂实时双工
4. 最终结果仍然沉淀为 `Story`,继续接入已有故事库、资产补全和记忆系统
这条路线的优点是:
- 架构增量最小
- 与当前 DreamWeaver 主线一致
- 既能验证产品价值,也不会过早引入过重的实时系统复杂度
---
## 18. 下一步建议
如果这份技术方案方向确认无误,下一轮最值得继续做的是两件事之一:
1.`voice_sessions / voice_turns / voice_session_events` 进一步细化成数据库迁移草案
2. 把 Phase A 的 API request/response schema 写成后端伪代码或 OpenAPI 草图
相比直接写实现代码,这两步能更稳地把范围钉住。