docs: 添加完整 API 文档 — Gateway 统一文档 + 后端服务文档

新增 docs/api/gateway-api.md:面向客户端开发的网关 API 统一文档,覆盖全部 16 个模块。
新增 docs/api/backend-services/:后端服务详细文档 (ai-core, memory-service, voice-service, iot-debug, tool-engine)。
更新 .gitignore:docs/api/ 例外允许推送,其他 docs/ 内容仍忽略。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 12:39:55 +08:00
parent 9c29459fb6
commit 70f8b30d04
7 changed files with 2343 additions and 2 deletions
+3 -2
View File
@@ -44,8 +44,9 @@ models.json
platform_configs.json
.claude/
# ========== 文档 (项目规范:docs/ 不纳入版本管理) ==========
docs/
# ========== 文档 (项目规范:docs/ 不纳入版本管理docs/api/ 为例外) ==========
docs/*
!docs/api/
# ========== 调试临时文件 (项目规范:debug/cache/ 为临时脚本目录) ==========
debug/cache/
+260
View File
@@ -0,0 +1,260 @@
# AI-Core Service API
**Base URL:** `http://<host>:8091` | **Auth:** 无(/internal 路由除外)
AI-Core 是 LLM 编排引擎,负责对话处理、意图分析、子会话调度、结果合成。所有对话和记忆端点通过 **Server-Sent Events (SSE)****JSON** 返回。
---
## 目录
1. [POST /api/v1/chat — 对话](#1-post-apiv1chat)
2. [GET /api/v1/memory/search — 记忆搜索](#2-get-apiv1memorysearch)
3. [GET /api/v1/memory — 记忆列表](#3-get-apiv1memory)
4. [POST /api/v1/memory — 添加记忆](#4-post-apiv1memory)
5. [DELETE /api/v1/memory — 删除记忆](#5-delete-apiv1memory)
6. [POST /api/v1/internal/presence — 在线状态](#6-post-apiv1internalpresence)
7. [GET /api/v1/health — 健康检查](#7-get-apiv1health)
8. [Model Selector 路由说明](#8-model-selector-路由说明)
---
## 1. POST /api/v1/chat
**类型:** SSE 流式 | **Content-Type:** `application/json``text/event-stream`
### 请求体 (JSON)
| JSON 字段 | Go 类型 | 必填 | 说明 |
|-----------|---------|------|------|
| `user_id` | string | 是 | 用户 ID |
| `session_id` | string | 是 | 会话 ID |
| `message` | string | 是 | 用户消息文本 |
| `images` | []string | 否 | base64 Data URL 数组 |
| `mode` | string | 否 | 默认 `"text"` |
| `nickname` | string | 否 | 用户昵称 |
### SSE 事件
**delta** — 逐 token 发送
```json
{ "delta": "token 文本", "message_id": "msg-1717000000000000000" }
```
**segments** — 断句事件(生成完成后)
```json
{
"message_id": "string",
"mode": "string",
"segments": [
{ "index": 0, "text": "第一句话" },
{ "index": 1, "text": "第二句话" }
]
}
```
**review** — 审查后结构化消息(action/chat 分离)
```json
{
"message_id": "string",
"review_messages": [
{ "type": "action", "content": "打开灯光", "delay_ms": 0 },
{ "type": "chat", "content": "好的,已为你打开客厅的灯光。", "delay_ms": 200 }
]
}
```
**done** — 流结束
```json
{ "message_id": "string", "mode": "string", "done": true }
```
**error** — 错误
```json
{ "delta": "", "error": "错误描述" }
```
终端标记: `data: [DONE]\n\n`
### HTTP 状态码
| 状态码 | 含义 |
|--------|------|
| 200 | 流开始 |
| 400 | JSON 解析失败 |
| 405 | 非 POST 请求 |
| 500 | 服务端不支持流式 |
---
## 2. GET /api/v1/memory/search
### Query 参数
| 参数 | 必填 | 说明 |
|------|------|------|
| `user_id` | 是 | 用户 ID |
| `q` | 是 | 搜索关键词 |
### 响应 200
```json
{
"user_id": "string",
"query": "搜索词",
"memories": [ MemoryEntry, ... ],
"total": 5
}
```
### 错误 (200 + error 字段)
```json
{
"user_id": "string",
"query": "string",
"memories": [],
"error": "错误描述",
"errorType": "memory_store_unavailable|retrieve_failed",
"hint": "解决方案 (仅 memory_store_unavailable)"
}
```
---
## 3. GET /api/v1/memory — 记忆列表
`?user_id=xxx` 最大返回 50 条。
```json
{
"user_id": "string",
"memories": [ MemoryEntry, ... ],
"total": 3
}
```
---
## 4. POST /api/v1/memory — 添加记忆
```json
// 请求
{
"user_id": "string (必填)",
"content": "string (必填)",
"category": "string (默认 other)",
"priority": 1
}
// 响应 201
{
"status": "saved",
"memory": MemoryEntry
}
```
### 错误
| 状态码 | errorType | 说明 |
|--------|-----------|------|
| 400 | — | 缺少 user_id 或 content |
| 500 | `save_failed` | 保存失败 |
| 503 | `memory_store_unavailable` | 存储未初始化 |
---
## 5. DELETE /api/v1/memory — 删除记忆
`?id=<memory_id>`
```json
// 响应 200
{ "status": "deleted", "memory_id": "string" }
```
### 错误
| 状态码 | errorType |
|--------|-----------|
| 400 | 缺少 id 参数 |
| 500 | `delete_failed` |
| 503 | `memory_store_unavailable` |
---
## 6. POST /api/v1/internal/presence
**Auth:** `X-Internal-Token` header。Gateway 内部调用的用户上线/下线通知。
```json
// 请求
{
"user_id": "string (必填)",
"status": "online|offline (必填)",
"session_id": "string (必填)"
}
// 响应 200
{ "status": "ok" }
```
| 状态码 | 含义 |
|--------|------|
| 200 | 成功 |
| 400 | JSON 无效 |
| 401 | Token 无效 |
---
## 7. GET /api/v1/health
```json
{
"status": "ok",
"service": "ai-core",
"model": "gpt-4o"
}
```
---
## 8. Model Selector 路由说明
AI-Core 使用基于用途的模型路由。模型配置从 `models.json` 加载,回退到 `.env` 环境变量。
### Purpose 常量
| Purpose | 常量 | 用途 |
|---------|------|------|
| `chat` | `PurposeChat` | 通用对话 |
| `deep_thinking` | `PurposeDeepThinking` | 后台深度思考 |
| `intent_analysis` | `PurposeIntentAnalysis` | 意图分析 |
| `tool_calling` | `PurposeToolCalling` | 工具调用 |
| `memory_extraction` | `PurposeMemoryExtraction` | 记忆提取 |
### 路由策略
1. `models.json` 存在 → 按 `routing.<purpose>.fallback_chain` 顺序尝试
2. `models.json` 不存在 → 回退到 `.env``LLM_API_URL`/`LLM_API_KEY`/`LLM_MODEL`
3. 全部失败 → 返回 `DefaultAdapter()` 使用 fallback model
### MemoryEntry 结构
| JSON 字段 | Go 类型 | 说明 |
|-----------|---------|------|
| `id` | string | UUID |
| `user_id` | string | 所属用户 |
| `content` | string | 完整内容 |
| `summary` | string | 摘要 |
| `category` | string | `user_preference`, `personal_info`, `conversation`, `knowledge`, `event`, `task`, `relationship` |
| `priority` | int | 0=Temp, 1=Normal, 2=Important, 3=Core |
| `importance` | int | 1-10 |
| `keywords` | []string | 关键词标签 |
| `session_id` | string | 来源会话 |
| `source` | string | `conversation`, `thinking`, `manual` |
| `access_count` | int | 访问次数 |
| `last_access` | time | 最近访问 |
| `created_at` | time | 创建时间 |
| `updated_at` | time | 更新时间 |
| `expires_at` | *time | 临时记忆过期(可空) |
+178
View File
@@ -0,0 +1,178 @@
# IoT Debug Service API
**Base URL:** `http://<host>:8083` | **Auth:**
模拟 IoT 设备的调试服务,预置 8 个设备用于开发和测试。所有状态存储在内存中,重启后重置。
---
## 目录
1. [GET /api/v1/health](#1-get-apiv1health)
2. [GET /api/v1/devices](#2-get-apiv1devices)
3. [GET /api/v1/devices/:id](#3-get-apiv1devicesid)
4. [GET /api/v1/devices/:id/history](#4-get-apiv1devicesidhistory)
5. [POST /api/v1/devices/:id/toggle](#5-post-apiv1devicesidtoggle)
6. [POST /api/v1/devices/:id/set](#6-post-apiv1devicesidset)
---
## 预置设备
| ID | 名称 | 类型 | 默认状态 |
|----|------|------|----------|
| `light-livingroom` | 客厅灯 | light | off, brightness=0 |
| `light-bedroom` | 卧室灯 | light | off, brightness=0 |
| `ac-livingroom` | 客厅空调 | ac | off, temperature=26 |
| `ac-bedroom` | 卧室空调 | ac | off, temperature=26 |
| `curtain-livingroom` | 客厅窗帘 | curtain | closed, position=0 |
| `sensor-temperature` | 温度传感器 | sensor | value=25.5, unit=celsius |
| `sensor-humidity` | 湿度传感器 | sensor | value=60.0, unit=percent |
| `lock-door` | 智能门锁 | lock | locked |
---
## 数据模型
### Device
| 字段 | 类型 | 适用类型 | 说明 |
|------|------|----------|------|
| `id` | string | 全部 | 设备 ID |
| `name` | string | 全部 | 设备名称 |
| `type` | string | 全部 | `light`, `ac`, `curtain`, `sensor`, `lock` |
| `status` | string | 全部 | `on`/`off`, `open`/`closed`, `locked`/`unlocked` |
| `brightness` | int | light | 0-100 |
| `color` | string | light | 颜色标识 |
| `temperature` | float64 | ac | 设定温度 |
| `mode` | string | ac | `cool`, `heat`, `auto` |
| `position` | int | curtain | 0-100 |
| `value` | float64 | sensor | 传感器读数 |
| `unit` | string | sensor | `celsius`, `percent` |
| `battery` | int | lock | 电池百分比 |
| `last_updated` | string | 全部 | RFC3339 时间戳 |
### HistoryEntry
| 字段 | 类型 | 说明 |
|------|------|------|
| `timestamp` | string | RFC3339 |
| `field` | string | 变更字段名 |
| `old_value` | string | 旧值 |
| `new_value` | string | 新值 |
---
## 1. GET /api/v1/health
```json
{ "status": "ok", "service": "iot-debug-service" }
```
---
## 2. GET /api/v1/devices — 设备列表
`history` 字段在列表中始终为 null。
```json
{
"devices": [ Device, ... ],
"total": 8
}
```
---
## 3. GET /api/v1/devices/:id — 设备详情
含最近 10 条 `history`
```json
{
"device": {
"id": "light-livingroom",
"name": "客厅灯",
"type": "light",
"status": "off",
"brightness": 0,
"color": "warm_white",
"last_updated": "2024-01-01T12:00:00Z",
"history": [
{ "timestamp": "2024-01-01T12:00:00Z", "field": "status", "old_value": "on", "new_value": "off" }
]
}
}
```
**404:** `{"error":"设备 {id} 不存在"}`
---
## 4. GET /api/v1/devices/:id/history — 设备历史
```json
{
"device_id": "string",
"history": [ HistoryEntry, ... ]
}
```
---
## 5. POST /api/v1/devices/:id/toggle — 切换状态
**请求体:** 空。
```json
// 响应 200
{ "device": Device, "action": "toggled" }
```
### 行为
| 设备类型 | 行为 |
|----------|------|
| light | on ↔ off。off 时 brightness=0on 时 brightness=80 |
| ac | on ↔ off |
| curtain | closed (position=0) ↔ open (position=100) |
| lock | locked ↔ unlocked |
| sensor | **不支持**,返回错误 |
---
## 6. POST /api/v1/devices/:id/set — 设置属性
```json
// 请求
{ "field": "brightness", "value": 80 }
```
### 有效 field/value 组合
| field | value | 适用设备 | 说明 |
|-------|-------|----------|------|
| `status` / `power` | `"on"`/`"off"`/`"open"`/`"closed"`/`"locked"`/`"unlocked"` | 除 sensor | 中文 `"开"`/`"关"` 也支持 |
| `status` / `power` | `true`/`false` | 除 sensor | true→on, false→off |
| `status` / `power` | 数字 | 除 sensor | 0→off, 非0→on |
| `temperature` | number | ac | — |
| `brightness` | int 0-100 | light | — |
| `position` | int 0-100 | curtain | — |
| `mode` | `"cool"`/`"heat"`/`"auto"` | ac | — |
| `color` | string | light | 如 `"warm_white"`, `"colorful"` |
```json
// 响应 200
{ "device": Device, "action": "set_brightness" }
```
### 错误
| 状态码 | 错误体 |
|--------|--------|
| 400 | `{"error":"设备 {id} 不存在"}` |
| 400 | `{"error":"无效的状态值: ..."}` |
| 400 | `{"error":"temperature 需要数字值"}` |
| 400 | `{"error":"设备 {name} (类型 {type}) 不支持温度调节"}` |
| 400 | `{"error":"不支持的属性: {field}"}` |
| 400 | `{"error":"请求格式错误"}` |
+298
View File
@@ -0,0 +1,298 @@
# Memory-Service API
**Base URL:** `http://<host>:8094` | **Auth:** 无(按 `user_id` 隔离数据)
记忆服务负责长期记忆的存储、检索、合并、衰减以及后台思考日志记录。
---
## 目录
1. [数据模型](#数据模型)
2. [POST /api/v1/memories — 创建记忆](#1-post-apiv1memories)
3. [GET /api/v1/memories — 记忆列表](#2-get-apiv1memories)
4. [GET /api/v1/memories/:id — 获取记忆](#3-get-apiv1memoriesid)
5. [PUT /api/v1/memories/:id — 更新记忆](#4-put-apiv1memoriesid)
6. [DELETE /api/v1/memories/:id — 删除记忆](#5-delete-apiv1memoriesid)
7. [POST /api/v1/memories/query — 搜索记忆](#6-post-apiv1memoriesquery)
8. [POST /api/v1/memories/consolidate — 合并记忆](#7-post-apiv1memoriesconsolidate)
9. [POST /api/v1/memories/decay — 记忆衰减](#8-post-apiv1memoriesdecay)
10. [GET /api/v1/memories/categories — 分类统计](#9-get-apiv1memoriescategories)
11. [POST /api/v1/thinking — 记录思考](#10-post-apiv1thinking)
12. [GET /api/v1/thinking — 思考列表](#11-get-apiv1thinking)
13. [GET /api/v1/thinking/:id — 思考详情](#12-get-apiv1thinkingid)
14. [GET /api/v1/thinking/stats — 思考统计](#13-get-apiv1thinkingstats)
15. [GET /api/v1/health — 健康检查](#14-get-apiv1health)
---
## 数据模型
### MemoryEntry
| JSON 字段 | Go 类型 | 说明 |
|-----------|---------|------|
| `id` | string | UUID 主键 |
| `user_id` | string | 所属用户 (max 64 chars) |
| `content` | string | 完整记忆文本 |
| `summary` | string | 简短摘要 |
| `category` | string | 7 种: `user_preference`, `personal_info`, `conversation`, `knowledge`, `event`, `task`, `relationship` |
| `priority` | int | 0=Temp, 1=Normal, 2=Important, 3=Core (写入时 clamp [0,10]) |
| `importance` | int | 1-10 (默认 5) |
| `keywords` | []string | 关键词标签 |
| `session_id` | string | 来源会话 ID |
| `source` | string | `conversation`, `manual`, `merged`, `consolidated` |
| `access_count` | int | 访问次数 |
| `last_access` | string | 最近访问时间 (RFC3339) |
| `created_at` | string | 创建时间 |
| `updated_at` | string | 更新时间 |
| `expires_at` | string/null | 临时记忆过期时间 (omitempty) |
### ThinkingLog
| JSON 字段 | Go 类型 | 说明 |
|-----------|---------|------|
| `id` | string | UUID |
| `user_id` | string | 所属用户 |
| `content` | string | 思考日志全文 |
| `tool_calls` | string | JSON 数组 (字符串存储) |
| `tool_call_count` | int | 工具调用次数 |
| `content_length` | int | 内容字符长度 |
| `created_at` | string | 创建时间 (RFC3339) |
### ThinkingStats
| JSON 字段 | Go 类型 | 说明 |
|-----------|---------|------|
| `total_logs` | int | 思考日志总数 |
| `total_tool_calls` | int | 工具调用总次数 |
| `avg_content_length` | float64 | 平均内容长度 |
| `latest_at` | string | 最近日志时间 |
---
## 1. POST /api/v1/memories — 创建记忆
创建时自动去重:与已有记忆的 Jaccard/双字母组相似度 >= 0.75 时,合并内容到已有条目而非新建。
```json
// 请求
{
"user_id": "string (必填)",
"content": "string (必填)",
"summary": "string",
"category": "string (默认 knowledge, 七选一)",
"priority": 1,
"importance": 5,
"keywords": ["tag1", "tag2"],
"session_id": "string",
"source": "string (默认 manual)"
}
// 响应 201
{ "status": "saved", "memory": MemoryEntry }
```
### 错误
| 状态码 | 错误 |
|--------|------|
| 400 | `缺少 user_id 或 content` |
| 400 | `请求格式错误: ...` |
| 405 | `method not allowed` |
| 500 | 数据库错误 |
---
## 2. GET /api/v1/memories — 记忆列表
### Query 参数
| 参数 | 必填 | 默认 | 说明 |
|------|------|------|------|
| `user_id` | 是 | — | 用户 ID |
| `category` | 否 | 全部 | 分类过滤 |
| `min_importance` | 否 | 0 | 最低重要度 |
| `limit` | 否 | 50 | 最大条数 |
### 响应 200
```json
{ "user_id": "string", "memories": [ MemoryEntry, ... ], "total": 10 }
```
---
## 3. GET /api/v1/memories/:id — 获取记忆
访问后异步递增 `access_count`
```json
// 响应 200
{ "memory": MemoryEntry }
// 错误 404
{ "error": "记忆不存在" }
```
---
## 4. PUT /api/v1/memories/:id — 更新记忆
Body 所有字段可选 (partial update)。`user_id``session_id` 不可修改。
```json
// 请求
{
"content": "新内容",
"summary": "新摘要",
"category": "knowledge",
"priority": 2,
"importance": 8,
"keywords": ["新标签"],
"source": "manual"
}
// 响应 200
{ "status": "updated", "memory_id": "uuid" }
```
---
## 5. DELETE /api/v1/memories/:id — 删除记忆
不检查存在性,始终返回成功。
```json
{ "status": "deleted", "memory_id": "uuid" }
```
---
## 6. POST /api/v1/memories/query — 搜索记忆
内部使用 SQL ILIKE + 应用层子串匹配在 content/summary/keywords 中搜索,按 importance 降序排序并去重。
```json
// 请求
{
"user_id": "string (必填)",
"query_text": "关键词",
"category": "knowledge",
"min_importance": 3,
"limit": 10
}
// 响应 200
{ "user_id": "string", "query": "关键词", "memories": [ MemoryEntry ], "total": 3 }
```
---
## 7. POST /api/v1/memories/consolidate — 合并记忆
查找 Jaccard 相似度 >= 0.75 的记忆对,合并关键词、增加 importance、删除重复条目。
```json
// 请求
{ "user_id": "string (必填)" }
// 响应 200
{ "status": "consolidated", "user_id": "string", "merged": 3, "message": "记忆整理完成" }
```
---
## 8. POST /api/v1/memories/decay — 记忆衰减
两阶段操作:
1. **降级:** access_count < 3、最近 30 天未访问、importance < 3、priority > 0、非 personal_info/user_preference → priority -1
2. **删除:** priority = 0、access_count = 0、最近 14 天未访问
```json
// 请求
{ "user_id": "string (必填)" }
// 响应 200
{ "status": "decayed", "user_id": "string", "decayed": 5, "deleted": 2, "message": "记忆衰减完成" }
```
---
## 9. GET /api/v1/memories/categories — 分类统计
`?user_id=xxx`
```json
// 响应 200
{
"user_id": "string",
"categories": {
"knowledge": 12,
"user_preference": 3,
"conversation": 8
}
}
```
---
## 10. POST /api/v1/thinking — 记录思考
```json
// 请求
{
"user_id": "string (默认 admin)",
"content": "string (必填)",
"tool_calls": "string (默认 [])",
"tool_call_count": 0,
"content_length": 0
}
// 响应 201
{ "status": "saved", "thinking": ThinkingLog }
```
---
## 11. GET /api/v1/thinking — 思考列表
`?user_id=xxx&limit=20&offset=0`
```json
// 响应 200
{ "logs": [ ThinkingLog ], "total": 5 }
```
---
## 12. GET /api/v1/thinking/:id — 思考详情
```json
// 响应 200
{ "thinking": ThinkingLog }
// 错误 404
{ "error": "思考日志不存在" }
```
---
## 13. GET /api/v1/thinking/stats — 思考统计
`?user_id=xxx` (可选,缺省返回全部用户)
```json
// 响应 200
{ "total_logs": 100, "total_tool_calls": 250, "avg_content_length": 512.3, "latest_at": "2024-01-01T12:00:00Z" }
```
---
## 14. GET /api/v1/health — 健康检查
```json
{ "status": "ok", "service": "memory-service" }
// 或数据库不可用时
{ "status": "degraded", "service": "memory-service" }
```
+297
View File
@@ -0,0 +1,297 @@
# Tool-Engine API
**Base URL:** `http://<host>:8092` | **Auth:**
工具引擎注册了 13 个内置工具,支持单次和批量执行,可选数据库持久化的调用日志。
---
## 目录
1. [GET /api/v1/health](#1-get-apiv1health)
2. [GET /api/v1/tools](#2-get-apiv1tools)
3. [GET /api/v1/tools/:name](#3-get-apiv1toolsname)
4. [POST /api/v1/tools/:name/execute](#4-post-apiv1toolsnameexecute)
5. [POST /api/v1/tools/execute — 批量执行](#5-post-apiv1toolsexecute)
6. [GET /api/v1/tools/calls](#6-get-apiv1toolscalls)
7. [GET /api/v1/tools/calls/stats](#7-get-apiv1toolscallsstats)
8. [已注册工具参考](#8-已注册工具参考)
---
## 数据模型
### ToolDefinition
| 字段 | 类型 | 说明 |
|------|------|------|
| `name` | string | 工具标识 |
| `description` | string | 功能描述 |
| `parameters` | object | JSON Schema 参数定义 |
### CallLogRecord
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | int | 数据库自增 ID |
| `call_id` | string | UUID v4 |
| `tool_name` | string | 工具名 |
| `arguments` | json.RawMessage | 调用参数 |
| `output` | string | 输出文本 |
| `error` | string | 错误信息 |
| `success` | bool | 是否成功 |
| `duration_ms` | int | 执行耗时 |
| `user_id` | string | 调用用户 |
| `session_id` | string | 调用会话 |
| `created_at` | string | 时间戳 |
### ToolCallCount
| 字段 | 类型 | 说明 |
|------|------|------|
| `tool_name` | string | 工具名 |
| `count` | int | 调用总数 |
| `success_count` | int | 成功次数 |
| `fail_count` | int | 失败次数 |
| `avg_duration_ms` | float64 | 平均耗时 |
---
## 1. GET /api/v1/health
```json
{ "status": "ok", "service": "tool-engine" }
```
---
## 2. GET /api/v1/tools — 工具列表
```json
{
"tools": [ ToolDefinition, ... ],
"total": 13
}
```
---
## 3. GET /api/v1/tools/:name — 工具详情
```json
{ "name": "calculator", "description": "...", "parameters": { ... } }
```
**404:** `{"error":"工具 {name} 不存在"}`
---
## 4. POST /api/v1/tools/:name/execute — 执行工具
### 请求
```json
{ "arguments": { "expression": "3 + 4 * 2" } }
```
### Query 参数 (可选,用于日志)
| 参数 | 说明 |
|------|------|
| `user_id` | 调用用户 |
| `session_id` | 调用会话 |
### 响应 200
```json
{
"id": "",
"output": "11",
"error": ""
}
```
- 成功: `output` 有值,`error` 为空字符串
- 失败: `output` 可能为空,`error` 有值 —— **仍然返回 HTTP 200**,调用方需检查 `error` 字段
**500:** `{"error":"执行工具 {name} 失败: ..."}` — 工具注册表未找到(非参数错误)
---
## 5. POST /api/v1/tools/execute — 批量执行
```json
// 请求
{
"calls": [
{ "id": "call-1", "name": "calculator", "arguments": { "expression": "3+4" } },
{ "id": "call-2", "name": "datetime", "arguments": { "action": "now" } }
]
}
// 响应 200
{
"results": [
{ "id": "call-1", "output": "7", "error": "" },
{ "id": "call-2", "output": "2024-01-01 12:00:00", "error": "" }
]
}
```
| 状态码 | 错误 |
|--------|------|
| 400 | `{"error":"calls 不能为空"}` |
| 400 | `{"error":"请求体格式错误: ..."}` |
---
## 6. GET /api/v1/tools/calls — 调用日志
需要 `DB_URL` 环境变量。未配置数据库时返回空结果。
### Query 参数
| 参数 | 默认 | 说明 |
|------|------|------|
| `tool_name` | 全部 | 按工具名过滤 |
| `page` | 1 | 页码 |
| `limit` | 20 (max 100) | 每页条数 |
### 响应 200
```json
{
"calls": [ CallLogRecord, ... ],
"total": 150,
"page": 1,
"limit": 20,
"total_pages": 8
}
```
---
## 7. GET /api/v1/tools/calls/stats — 调用统计
```json
{
"total_calls": 150,
"success_count": 140,
"fail_count": 10,
"success_rate": 93.33,
"avg_duration_ms": 45.2,
"by_tool": [
{ "tool_name": "calculator", "count": 80, "success_count": 78, "fail_count": 2, "avg_duration_ms": 12.3 }
]
}
```
未配置数据库时全返回 0。
---
## 8. 已注册工具参考
### calculator — 数学计算
| 参数 | 必填 | 说明 |
|------|------|------|
| `expression` | 是 | 数学表达式 (如 `3 + 4 * 2`) |
### datetime — 日期时间
| 参数 | 必填 | 说明 |
|------|------|------|
| `action` | 是 | `now`, `format`, `add`, `diff`, `timezone_list` |
| `format` | 否 | 时间格式 (action=format 时) |
| `timezone` | 否 | 时区 |
| `date` | 否 | 日期 |
| `duration` | 否 | 时长 (action=add 时) |
| `date2` | 否 | 第二个日期 (action=diff 时) |
### text — 文本处理
| 参数 | 必填 | 说明 |
|------|------|------|
| `action` | 是 | `count`, `summarize`, `translate`, `extract` |
| `text` | 是 | 文本内容 |
| `target_lang` | 否 | 目标语言 (translate) |
| `pattern` | 否 | 提取模式 (extract) |
### crypto — 加密编码
| 参数 | 必填 | 说明 |
|------|------|------|
| `action` | 是 | `hash`, `base64_encode`, `base64_decode`, `url_encode`, `url_decode` |
| `input` | 是 | 输入文本 |
| `algorithm` | 否 | hash 算法: `md5`, `sha1`, `sha256`, `sha512` (默认 sha256) |
### random — 随机生成
| 参数 | 必填 | 说明 |
|------|------|------|
| `action` | 是 | `number`, `uuid`, `password`, `pick`, `shuffle` |
| `min` | 否 | 最小值 (number) |
| `max` | 否 | 最大值 (number) |
| `length` | 否 | 长度 (password) |
| `items` | 否 | 选项数组 (pick/shuffle) |
| `count` | 否 | 选取数量 (pick) |
### markdown — Markdown 处理
| 参数 | 必填 | 说明 |
|------|------|------|
| `action` | 是 | `to_html`, `to_text`, `extract_links`, `extract_code`, `table_of_contents` |
| `markdown` | 是 | Markdown 原文 |
### json_ops — JSON 操作
| 参数 | 必填 | 说明 |
|------|------|------|
| `action` | 是 | `parse`, `query`, `validate` |
| `json_string` | 是 | JSON 字符串 |
| `path` | 否 | 查询路径 (query) |
### file_ops — 文件操作
| 参数 | 必填 | 说明 |
|------|------|------|
| `action` | 是 | `read`, `write`, `list`, `exists`, `delete` |
| `path` | 是 | 文件路径 |
| `content` | 否 | 写入内容 (write) |
### http_request — HTTP 请求
| 参数 | 必填 | 默认 | 说明 |
|------|------|------|------|
| `url` | 是 | — | 请求 URL |
| `method` | 否 | GET | HTTP 方法 |
| `headers` | 否 | {} | 请求头 |
| `body` | 否 | — | 请求体 |
| `timeout` | 否 | 30s | 超时秒数 |
### web_search — 网页搜索
| 参数 | 必填 | 说明 |
|------|------|------|
| `query` | 是 | 搜索关键词 |
### web_fetch — 网页抓取
| 参数 | 必填 | 说明 |
|------|------|------|
| `url` | 是 | 目标 URL |
### iot_query — IoT 设备查询
| 参数 | 必填 | 说明 |
|------|------|------|
| `device_id` | 否 | 设备 ID (缺省返回全部) |
### iot_control — IoT 设备控制
| 参数 | 必填 | 说明 |
|------|------|------|
| `device_id` | 是 | 设备 ID (别名 `entity_id`) |
| `action` | 是 | `toggle`, `turn_on`, `turn_off`, `set_temperature`, `set_brightness`, `set_position`, `set_mode`, `set_color` (支持中文名) |
| `value` | 否 | 值 (set_* 动作时需要) |
+231
View File
@@ -0,0 +1,231 @@
# Voice-Service API
**Base URL:** `http://<host>:8093` | **Auth:**
语音服务封装两层引擎:
- **STT (语音转文字):** DashScope `qwen3-asr-flash-realtime` (主) + 本地 Whisper (备)
- **TTS (文字转语音):** edge-tts (主) + espeak-ng (备)
---
## 目录
1. [POST /api/v1/transcribe — 语音转文字](#1-post-apiv1transcribe)
2. [POST /api/v1/tts/synthesize — 文字转语音](#2-post-apiv1ttssynthesize)
3. [GET /api/v1/tts/voices — 发音人列表](#3-get-apiv1ttsvoices)
4. [GET /api/v1/health — 健康检查](#4-get-apiv1health)
5. [GET /api/v1/status — 服务状态](#5-get-apiv1status)
6. [GET /api/v1/tts/status — TTS 状态](#6-get-apiv1ttsstatus)
7. [WebSocket GET /api/v1/stt/stream — 流式 STT](#7-websocket-get-apiv1sttstream)
---
## 1. POST /api/v1/transcribe — 语音转文字
**Content-Type:** `multipart/form-data` | **Max body:** 10 MB
### 表单字段
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `audio` | file | 是 | 音频文件。格式从扩展名推断:wav/mp3/ogg/flac/m4a |
| `language` | string | 否 | 默认 `"zh"`。可选: `zh`, `en`, `ja`, `ko`, `auto` |
### 响应 200
```json
{
"success": true,
"text": "转录结果文本",
"language": "zh",
"duration_ms": 1234
}
```
### 错误
| 状态码 | 错误体 |
|--------|--------|
| 400 | `{"error":"文件过大或解析失败,最大支持 10MB"}` |
| 400 | `{"error":"缺少 audio 文件字段"}` |
| 400 | `{"error":"音频文件为空"}` |
| 400 | `{"error":"不支持的音频格式: <ext>,支持的格式: WAV, MP3, OGG, FLAC, M4A"}` |
| 405 | `{"error":"method not allowed"}` |
| 500 | `{"error":"读取音频文件失败"}` |
| 500 | `{"success":false,"error":"<engine error>"}` |
---
## 2. POST /api/v1/tts/synthesize — 文字转语音
**Content-Type:** `application/json`
### 请求
```json
{
"text": "你好世界 (必填)",
"voice": "zh-CN-XiaoxiaoNeural (默认)",
"rate": "+0% (默认,如 +20%/-20%)"
}
```
### 响应 200 — 原始音频流
- **Content-Type:** `audio/mpeg` (edge-tts) 或 `audio/wav` (espeak-ng/fallback)
- **Content-Disposition:** `inline; filename=synthesized.mp3`
引擎回退链: edge-tts (mp3) → espeak-ng (wav) → silent WAV
### 错误
| 状态码 | 错误体 |
|--------|--------|
| 400 | `{"error":"请求体解析失败: ..."}` |
| 400 | `{"error":"text 字段不能为空"}` |
| 405 | `{"error":"method not allowed"}` |
| 500 | `{"error":"TTS 合成失败: ..."}` |
---
## 3. GET /api/v1/tts/voices — 发音人列表
```json
// 响应 200
{
"voices": [
{ "name": "zh-CN-XiaoxiaoNeural", "display_name": "晓晓 (女声)", "gender": "Female", "locale": "zh-CN" },
{ "name": "zh-CN-YunxiNeural", "display_name": "云希 (男声)", "gender": "Male", "locale": "zh-CN" },
{ "name": "zh-CN-XiaoyiNeural", "display_name": "晓伊 (女声)", "gender": "Female", "locale": "zh-CN" }
],
"count": 3
}
```
---
## 4. GET /api/v1/health — 健康检查
```json
{
"status": "ok",
"service": "voice-service",
"stt": {
"available": true,
"primary": "dashscope",
"dashscope": { "available": true, "model": "qwen3-asr-flash-realtime", "provider": "dashscope" },
"whisper": {
"available": true,
"binary_available": true,
"model_loaded": true,
"model_name": "ggml-small.bin"
},
"default_language": "zh",
"supported_languages": ["zh","en","ja","ko","auto"]
},
"tts": {
"available": true,
"edge_tts": true,
"espeak_ng": false,
"engine": "edge-tts",
"default_voice": "zh-CN-XiaoxiaoNeural",
"builtin_voices": 3
}
}
```
### 状态字段说明
| 字段 | 说明 |
|------|------|
| `stt.available` | DashScope 或 Whisper 至少一个可用 |
| `stt.dashscope.available` | DashScope API Key 已配置 |
| `stt.whisper.available` | Whisper 二进制 + 模型文件均存在 |
| `tts.available` | 至少一个 TTS 引擎可用 |
| `tts.engine` | 当前激活引擎: `edge-tts`, `espeak-ng`, `fallback (silent WAV)`, `none` |
---
## 5. GET /api/v1/status — 服务状态
`/health` 但无顶层 `status` 字段:
```json
{
"service": "voice-service",
"stt": { ... }, // 同 health.stt
"tts": { ... } // 同 health.tts
}
```
---
## 6. GET /api/v1/tts/status — TTS 单独状态
```json
{
"service": "voice-service",
"tts": {
"available": true,
"edge_tts": true,
"espeak_ng": false,
"engine": "edge-tts",
"default_voice": "zh-CN-XiaoxiaoNeural",
"builtin_voices": 3
}
}
```
---
## 7. WebSocket GET /api/v1/stt/stream — 流式 STT
**Query 参数:** `?language=zh&format=pcm` (language 默认 zh, format 默认 pcm)
**Read deadline:** 300s
### 客户端 → 服务端
**Binary 帧:** 原始 PCM 音频 (16-bit LE, 16000Hz, mono)。每帧通过 `input_audio_buffer.append` 转发到 DashScope。
**JSON 控制帧:**
```json
{ "action": "stop" }
// 请求结束会话。服务端返回 done 后关闭。
{ "language": "en" }
// 动态切换识别语言。
```
### 服务端 → 客户端 (JSON 文本帧)
**result** — 识别结果
```json
{
"type": "result",
"text": "识别文本片段",
"isFinal": true
}
```
| 字段 | 说明 |
|------|------|
| `isFinal: true` | VAD 端点检测到的完整句子 |
| `isFinal: false` | 中间增量 (delta) |
**error**
```json
{ "type": "error", "error": "错误描述" }
```
**done** — 响应 stop
```json
{ "type": "done", "action": "stop" }
```
### 连接生命周期
1. HTTP 升级请求 → 验证 STT 引擎可用性 (不可用返回 503)
2. 建立 DashScope realtime 会话 (`session.created``session.update``session.updated`)
3. 客户端发送 binary PCM 帧 → 服务端 base64 编码后 `input_audio_buffer.append`
4. DashScope VAD 自动检测 → `conversation.item.input_audio_transcription.completed` → 转发 result
5. 客户端发送 `{"action":"stop"}` → 服务端 `session.finish` → 关闭连接
File diff suppressed because it is too large Load Diff