2026-04-24 03:32:44 +08:00
|
|
|
|
# 项目规范与 AI 规范 (agents.md)
|
|
|
|
|
|
|
|
|
|
|
|
## 项目规范
|
|
|
|
|
|
|
|
|
|
|
|
### 代码风格
|
|
|
|
|
|
- 使用 Rust 官方代码风格(rustfmt)
|
|
|
|
|
|
- 所有公开 API 必须包含中文文档注释(///)
|
|
|
|
|
|
- 复杂逻辑必须包含行内中文注释
|
|
|
|
|
|
- 结构体、枚举、trait 使用 PascalCase 命名
|
|
|
|
|
|
- 变量、函数使用 snake_case 命名
|
|
|
|
|
|
|
|
|
|
|
|
### 架构设计
|
|
|
|
|
|
本项目采用 **面向对象 (OOP) + 设计模式** 架构:
|
|
|
|
|
|
|
|
|
|
|
|
1. **Builder 模式**:用于构建复杂的 API 请求对象
|
|
|
|
|
|
2. **Strategy 模式**:不同音色和音频格式的处理策略
|
|
|
|
|
|
3. **Singleton 模式**:全局配置管理器(确保配置只加载一次)
|
|
|
|
|
|
4. **封装**:每个模块负责单一职责,通过 pub 控制可见性
|
|
|
|
|
|
|
|
|
|
|
|
### 错误处理
|
|
|
|
|
|
- 使用 `anyhow::Result<T>` 作为统一返回类型
|
|
|
|
|
|
- 自定义错误类型(如需更精细控制)
|
|
|
|
|
|
- 退出码规范:
|
|
|
|
|
|
- 0: 成功
|
|
|
|
|
|
- 1: 参数错误
|
|
|
|
|
|
- 2: 配置错误(如缺少 API Key)
|
|
|
|
|
|
- 3: API 调用失败
|
|
|
|
|
|
- 4: 文件操作失败
|
|
|
|
|
|
|
|
|
|
|
|
### 文档要求
|
2026-04-25 05:50:28 +08:00
|
|
|
|
- 每次,不管是从plan切换到build,还是开始生成代码,都必须先保存上下文到对应的文档,防止丢失操作记录
|
2026-04-24 03:32:44 +08:00
|
|
|
|
- 每次操作前必须更新对应文档
|
|
|
|
|
|
- 代码变更同步更新 changelog.md
|
|
|
|
|
|
- 踩坑记录及时写入【认知修正】章节
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## AI 规范
|
|
|
|
|
|
|
|
|
|
|
|
### 沟通语言
|
|
|
|
|
|
- 全程使用中文与用户沟通
|
|
|
|
|
|
- 代码注释使用中文
|
|
|
|
|
|
- 文档使用中文编写
|
|
|
|
|
|
|
|
|
|
|
|
### 执行顺序
|
|
|
|
|
|
每次执行任何操作前,按以下顺序更新文档:
|
|
|
|
|
|
1. 更新 `taolun.md` - 记录本次对话要点
|
|
|
|
|
|
2. 更新 `agents.md` 的【认知修正】- 如有踩坑
|
|
|
|
|
|
3. 更新 `changelog.md` - 如有版本变更
|
|
|
|
|
|
4. 执行实际操作
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 认知修正(踩坑记录)
|
|
|
|
|
|
|
|
|
|
|
|
### 2026-04-24 - 项目初始化
|
|
|
|
|
|
|
|
|
|
|
|
#### 问题:Mimo-TTS API 文档无法直接访问
|
|
|
|
|
|
**现象**:使用 webfetch 工具访问 https://platform.xiaomimimo.com/docs/ 返回的内容不完整
|
|
|
|
|
|
**原因**:网站可能有反爬虫机制或需要 JavaScript 渲染
|
|
|
|
|
|
**解决方案**:通过 websearch 搜索相关内容,从第三方文档(如 DMXAPI、GitHub 项目)获取 API 详细信息
|
|
|
|
|
|
**经验**:对于无法直接抓取的文档,可以通过搜索引擎找到镜像或第三方整理的资料
|
|
|
|
|
|
|
|
|
|
|
|
#### 问题:API 认证需要双 Header
|
|
|
|
|
|
**现象**:标准的 OpenAI SDK 方式(仅使用 Authorization Bearer)可能无法正常工作
|
|
|
|
|
|
**原因**:Mimo-TTS API 要求同时提供 `api-key` 和 `Authorization: Bearer` 两个 Header
|
|
|
|
|
|
**解决方案**:在代码中同时设置两个 Header
|
|
|
|
|
|
**经验**:阅读 API 文档时要注意认证方式的特殊性,不能假设与标准 OpenAI API 完全一致
|
|
|
|
|
|
|
2026-04-25 05:50:28 +08:00
|
|
|
|
### 2026-04-25 - 守护进程功能开发
|
|
|
|
|
|
|
|
|
|
|
|
#### 问题:clap derive 模式参数传递
|
|
|
|
|
|
**现象**:`--port` 参数无法传递给 `daemon start` 子命令,报错"unexpected argument '--port' found"
|
|
|
|
|
|
**原因**:在 clap derive 模式中,父命令的参数需要在子命令之前使用,不能放在子命令之后
|
|
|
|
|
|
**解决方案**:修改 `DaemonCommand` 为独立结构体,`--port` 参数定义在父命令级别,使用正确的参数顺序
|
|
|
|
|
|
**正确用法**:`mimo-tts daemon --port 9876 start`
|
|
|
|
|
|
**错误用法**:`mimo-tts daemon start --port 9876`
|
|
|
|
|
|
**经验**:clap derive 模式中,参数位置很重要,父命令的参数应该在子命令之前
|
|
|
|
|
|
|
|
|
|
|
|
#### 问题:chrono 依赖缺失
|
|
|
|
|
|
**现象**:daemon.rs 中使用 `chrono::Local::now()` 但 Cargo.toml 未添加 chrono 依赖
|
|
|
|
|
|
**原因**:代码中使用了 chrono 库但忘记在 Cargo.toml 中添加依赖
|
|
|
|
|
|
**解决方案**:添加 `chrono = "0.4"` 到 Cargo.toml
|
|
|
|
|
|
**经验**:每次使用新的外部 crate 时,都要检查 Cargo.toml 中是否已添加对应依赖
|
|
|
|
|
|
|
|
|
|
|
|
#### 问题:style 参数与文本内标签的并存设计
|
|
|
|
|
|
**现象**:用户要求 style 参数作为宏观场景风格,同时文本内可以包含细粒度风格标签
|
|
|
|
|
|
**原因**:Mimo-TTS 官方文档支持两种方式:`<style>...</style>` 标签(宏观)和文本内 `[xxx]` 标签(细粒度)
|
|
|
|
|
|
**解决方案**:
|
|
|
|
|
|
- style 参数:按官方文档转换为 `<style>...</style>` 标签放到文本开头
|
|
|
|
|
|
- 文本内标签:保留原有逻辑(`[激动]` 等格式)
|
|
|
|
|
|
- 两者可以并存,互不影响
|
|
|
|
|
|
**经验**:设计 API 时要考虑不同层次的需求,宏观参数和微观标签可以互相补充
|
2026-04-25 07:38:26 +08:00
|
|
|
|
|
|
|
|
|
|
#### 问题:daemon.rs 中日志截取文本导致 panic
|
|
|
|
|
|
**现象**:守护进程处理包含中文字符的长文本时 panic: "byte index 50 is not a char boundary; it is inside '嘴' (bytes 48..51)"
|
|
|
|
|
|
**原因**:使用 `text.len()` 获取字节长度,用 `text[..text.len().min(50)]` 按字节索引截取,中文字符占 3 字节,在字符中间截断
|
|
|
|
|
|
**解决方案**:`text.chars().take(50).collect::<String>()` 按字符截取
|
|
|
|
|
|
**经验**:处理多字节字符(中文)时,必须使用字符索引而非字节索引
|
|
|
|
|
|
|
2026-05-09 04:06:47 +08:00
|
|
|
|
### 2026-05-09 - 代码质量提升
|
|
|
|
|
|
|
|
|
|
|
|
#### 问题:HTTP 接口/实现不完整
|
|
|
|
|
|
**现象**:HTTP POST /synthesize 只返回"请求已接收",未实际调用 TTS 合成
|
|
|
|
|
|
**原因**:HTTP 服务器运行在 `std::thread` 中,`process_tts_request` 是 async 函数,无法直接调用
|
|
|
|
|
|
**解决方案**:传递 `tokio::runtime::Handle` 到 HTTP 线程,使用 `handle.block_on()` 执行 async 调用
|
|
|
|
|
|
**经验**:跨线程调用 async 函数时,需要用 `Handle::current()` + `block_on()` 桥接
|
|
|
|
|
|
|
|
|
|
|
|
#### 问题:语气替换长短匹配冲突
|
|
|
|
|
|
**现象**:`...` 被 `.` 的替换误伤,变成 `。(停顿)。(停顿)。(停顿)`
|
|
|
|
|
|
**原因**:`.replace()` 按顺序执行,短匹配(`.`)先于长匹配(`...`)处理
|
|
|
|
|
|
**解决方案**:长模式(`...`/`……`)必须优先于短模式(`.`/`。`)
|
|
|
|
|
|
**经验**:字符串替换时始终让长匹配优先于短匹配
|
|
|
|
|
|
|
|
|
|
|
|
#### 问题:日志文件多线程并发写入
|
|
|
|
|
|
**现象**:HTTP 线程与 async 线程可能同时写入同一日志文件,导致内容交错
|
|
|
|
|
|
**原因**:`write_log` 在多个线程中独立打开文件句柄追加写入
|
|
|
|
|
|
**解决方案**:添加 `std::sync::Mutex<()>` 静态锁保护文件写入操作
|
|
|
|
|
|
**经验**:文件追加写入在多线程环境下仍需要同步
|
|
|
|
|
|
|
2026-04-25 07:38:26 +08:00
|
|
|
|
#### 问题:Windows PowerShell 测试效率
|
|
|
|
|
|
**现象**:测试守护进程时创建了多个 .ps1 临时文件,繁琐且不便管理
|
2026-05-09 04:06:47 +08:00
|
|
|
|
**原因**:不熟悉 PowerShell 命令的直接执行的方式
|
2026-04-25 07:38:26 +08:00
|
|
|
|
**解决方案**:
|
|
|
|
|
|
- 使用 `mimo-tts daemon start -d --port XXXX` 后台启动守护进程
|
|
|
|
|
|
- 使用 PowerShell 一条命令直接发送 TCP 请求测试:
|
|
|
|
|
|
```powershell
|
|
|
|
|
|
powershell -Command "$c=New-Object System.Net.Sockets.TcpClient;$c.Connect('127.0.0.1',9888);$s=$c.GetStream();$w=New-Object System.IO.StreamWriter($s);$w.Write('{\"text\":\"中文文本\"}');$w.Flush();$w.Close();$c.Close()"
|
|
|
|
|
|
```
|
|
|
|
|
|
- 无需创建 .ps1 临时文件
|
|
|
|
|
|
**经验**:Windows PowerShell 可以在命令行中直接执行,无需临时文件
|
2026-05-09 04:06:47 +08:00
|
|
|
|
|
|
|
|
|
|
### 2026-05-09 - 流式播放修复
|
|
|
|
|
|
|
|
|
|
|
|
#### 问题:流式播放没有声音(SSE 解析错误)
|
|
|
|
|
|
**现象**:守护进程 `send --stream` 和 CLI `--stream --play` 均无声音输出,但非流式正常
|
|
|
|
|
|
**原因**(双重):
|
|
|
|
|
|
1. SSE 结构体层级错误:`audio` 在 `choices[0].delta.audio.data`,原代码只在顶层找
|
|
|
|
|
|
2. SSE 行分割问题:HTTP chunk 边界不对齐 `\n`,长 `data:` 行被切碎导致 serde_json 解析失败
|
|
|
|
|
|
**解决方案**:
|
|
|
|
|
|
1. 创建 `SseChunk`/`SseChoice`/`SseDelta`/`SseAudio` 四层嵌套结构,匹配 OpenAI chat.completion.chunk 格式
|
|
|
|
|
|
2. 添加 `line_buf` 行缓冲器:累积跨 chunk 的不完整行,遇到 `\n` 才取出处理
|
|
|
|
|
|
**经验**:
|
|
|
|
|
|
- 对流式 HTTP 响应,永远不要假设 chunk 边界对齐消息边界,必须自己处理行缓冲
|
|
|
|
|
|
- 调试无声音问题时,先分段验证:数据是否到达 → 数据是否正确解码 → sink 是否收到数据
|
|
|
|
|
|
- 用 `--stream`(不带 `--play`)测试 JSON 响应路径 vs 用 curl 看 SSE 原始格式,能快速定位数据流断裂点
|