495 lines
13 KiB
Markdown
495 lines
13 KiB
Markdown
# Python 后端 API 文档
|
||
|
||
> Base URL: `http://localhost:3001/v1`
|
||
>
|
||
> 所有路径均挂载在 `/v1` 前缀下,完整路径如 `POST http://localhost:3001/v1/getScenes`。
|
||
>
|
||
> 来自 **java-mock** 的请求须附加 [内部签名 Header](#内部鉴权协议)。
|
||
>
|
||
> `/chat_callback` 由**火山引擎 RTC 平台**直接回调,走独立 API Key 鉴权。
|
||
|
||
---
|
||
|
||
## 接口总览
|
||
|
||
| 方法 | 路径 | 调用方 | 鉴权 | 说明 |
|
||
|------|------|--------|------|------|
|
||
| POST | `/getScenes` | java-mock | 内部签名 | 获取场景列表 & RTC 入房参数 |
|
||
| POST | `/proxy?Action=xxx` | java-mock | 内部签名 | 开始/停止语音对话 |
|
||
| POST | `/session/history` | java-mock | 内部签名 | 写入房间历史上下文 |
|
||
| POST | `/chat_callback` | 火山引擎 RTC | Bearer Token | 自定义 LLM 回调(SSE 流式) |
|
||
| POST | `/debug/chat` | 开发调试 | 无 | 调试 LLM 对话 |
|
||
| GET | `/debug/rag` | 开发调试 | 无 | 调试 RAG 检索 |
|
||
|
||
### 调用时序
|
||
|
||
```
|
||
前端 → java-mock → Python 后端
|
||
|
||
1. POST /getScenes ← 获取场景 + RTC 入房参数
|
||
2. POST /session/history ← (可选) 注入历史上下文
|
||
3. POST /proxy?Action=StartVoiceChat ← 启动语音对话
|
||
4. 火山引擎 RTC → POST /chat_callback ← 平台回调(自动,非 java-mock)
|
||
5. POST /proxy?Action=StopVoiceChat ← 结束语音对话
|
||
```
|
||
|
||
---
|
||
|
||
## 一、获取场景列表
|
||
|
||
```
|
||
POST /v1/getScenes
|
||
```
|
||
|
||
返回所有已配置场景的信息和对应的 RTC 入房参数。每次调用会**重新生成** RoomId/UserId/Token/TaskId 并写入服务端 Session,供后续 `StartVoiceChat` 自动取用。
|
||
|
||
### 请求
|
||
|
||
**Headers(内部签名):**
|
||
|
||
| Header | 必填 | 说明 |
|
||
|--------|------|------|
|
||
| `X-Internal-Service` | ✅ | 固定 `java-gateway` |
|
||
| `X-Internal-User-Id` | ✅ | 当前登录用户 ID |
|
||
| `X-Internal-Timestamp` | ✅ | 毫秒时间戳(字符串) |
|
||
| `X-Internal-Signature` | ✅ | HMAC-SHA256 签名(hex),算法见[内部鉴权协议](#内部鉴权协议) |
|
||
|
||
**Body:** 无(`{}` 或空均可)
|
||
|
||
### 成功响应 `200`
|
||
|
||
```json
|
||
{
|
||
"ResponseMetadata": {
|
||
"Action": "getScenes"
|
||
},
|
||
"Result": {
|
||
"scenes": [
|
||
{
|
||
"scene": {
|
||
"id": "Custom",
|
||
"name": "小块",
|
||
"icon": "https://lf3-rtc-demo.volccdn.com/obj/rtc-aigc-assets/DoubaoAvatar.png",
|
||
"botName": "agent-user-001",
|
||
"isInterruptMode": true,
|
||
"isVision": false,
|
||
"isScreenMode": false,
|
||
"isAvatarScene": false,
|
||
"avatarBgUrl": ""
|
||
},
|
||
"rtc": {
|
||
"AppId": "6xxxxxxx",
|
||
"RoomId": "550e8400-e29b-41d4-a716-446655440000",
|
||
"UserId": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
|
||
"Token": "001xxxxxxAQBhMGI3Zm...",
|
||
"TaskId": "e5f6a7b8-1234-5678-9abc-def012345678"
|
||
}
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
**`Result.scenes[*].scene` 字段说明:**
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `id` | string | 场景唯一标识,后续 `/proxy` 接口的 `SceneID` 取此值 |
|
||
| `name` | string | 场景显示名称 |
|
||
| `icon` | string | 场景头像 URL |
|
||
| `botName` | string | AI Bot 在 RTC 房间中的 UserId |
|
||
| `isInterruptMode` | boolean | 是否开启用户打断模式 |
|
||
| `isVision` | boolean | 是否开启视觉理解能力 |
|
||
| `isScreenMode` | boolean | 是否为屏幕共享模式(视觉输入来自屏幕流) |
|
||
| `isAvatarScene` | boolean | 是否为数字人场景 |
|
||
| `avatarBgUrl` | string\|null | 数字人背景图 URL,非数字人场景为空 |
|
||
|
||
**`Result.scenes[*].rtc` 字段说明:**
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `AppId` | string | 火山引擎 RTC AppId |
|
||
| `RoomId` | string | 本次生成的房间 ID(UUID),前端入房和后续接口都需要 |
|
||
| `UserId` | string | 本次生成的用户 ID(UUID),前端入房使用 |
|
||
| `Token` | string | RTC 入房 Token,24 小时有效 |
|
||
| `TaskId` | string | 语音任务 ID,`StopVoiceChat` 时需要 |
|
||
|
||
> **注意:** 每次调用 `getScenes` 都会重新生成 `RoomId`/`UserId`/`Token`/`TaskId`。必须在 `getScenes` 之后、`StartVoiceChat` 之前使用同一套参数。
|
||
|
||
### 失败响应
|
||
|
||
**鉴权失败 `401`:**
|
||
|
||
```json
|
||
{
|
||
"code": 401,
|
||
"message": "鉴权失败"
|
||
}
|
||
```
|
||
|
||
**配置错误(缺少环境变量等):**
|
||
|
||
```json
|
||
{
|
||
"ResponseMetadata": {
|
||
"Action": "getScenes",
|
||
"Error": {
|
||
"Code": -1,
|
||
"Message": "Custom 场景缺少以下环境变量: CUSTOM_ACCESS_KEY_ID, CUSTOM_SECRET_KEY"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 二、开始/停止语音对话
|
||
|
||
```
|
||
POST /v1/proxy?Action={Action}&Version={Version}
|
||
```
|
||
|
||
带 SigV4 签名转发到火山引擎 RTC OpenAPI。内部会自动从 Session 取回 `getScenes` 分配的房间参数。
|
||
|
||
### 请求
|
||
|
||
**Headers(内部签名):** 同 [getScenes](#请求)
|
||
|
||
**Query 参数:**
|
||
|
||
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
||
|------|------|------|--------|------|
|
||
| `Action` | string | ✅ | — | `StartVoiceChat` 或 `StopVoiceChat` |
|
||
| `Version` | string | ❌ | 环境变量 `RTC_OPENAPI_VERSION`,兜底 `2025-06-01` | 火山引擎 OpenAPI 版本 |
|
||
|
||
**Body(JSON):**
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `SceneID` | string | ✅ | 场景 ID,取 `getScenes` 返回的 `scene.id`(当前固定为 `"Custom"`) |
|
||
|
||
**请求示例:**
|
||
|
||
```json
|
||
{
|
||
"SceneID": "Custom"
|
||
}
|
||
```
|
||
|
||
### StartVoiceChat 处理逻辑
|
||
|
||
1. 从 Session 取回 `getScenes` 时生成的 `RoomId`、`UserId`、`TaskId`
|
||
2. 将 `room_id` 追加到 LLM 回调 URL(`?room_id={RoomId}`),使 `/chat_callback` 能关联历史上下文
|
||
3. 使用 AK/SK 对请求做 SigV4 签名
|
||
4. 转发到 `https://rtc.volcengineapi.com?Action=StartVoiceChat`
|
||
|
||
### StartVoiceChat 成功响应 `200`
|
||
|
||
```json
|
||
{
|
||
"ResponseMetadata": {
|
||
"RequestId": "2025070100000000000000000000abcd",
|
||
"Action": "StartVoiceChat",
|
||
"Version": "2025-06-01",
|
||
"Service": "rtc"
|
||
},
|
||
"Result": {
|
||
"Message": "success"
|
||
}
|
||
}
|
||
```
|
||
|
||
### StopVoiceChat 处理逻辑
|
||
|
||
1. 从 Session 取回 `RoomId`、`TaskId`
|
||
2. **清除该房间的历史上下文缓存**(`session/history` 写入的数据)
|
||
3. 使用 AK/SK 签名后转发到火山引擎
|
||
|
||
### StopVoiceChat 成功响应 `200`
|
||
|
||
```json
|
||
{
|
||
"ResponseMetadata": {
|
||
"RequestId": "2025070100000000000000000000efgh",
|
||
"Action": "StopVoiceChat",
|
||
"Version": "2025-06-01",
|
||
"Service": "rtc"
|
||
},
|
||
"Result": {
|
||
"Message": "success"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 失败响应
|
||
|
||
**鉴权失败 `401`:**
|
||
|
||
```json
|
||
{
|
||
"code": 401,
|
||
"message": "鉴权失败"
|
||
}
|
||
```
|
||
|
||
**参数/配置错误(HTTP 200,但包含 Error):**
|
||
|
||
```json
|
||
{
|
||
"ResponseMetadata": {
|
||
"Action": "StartVoiceChat",
|
||
"Error": {
|
||
"Code": -1,
|
||
"Message": "SceneID 不能为空,SceneID 用于指定场景配置"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
| 错误场景 | Error.Message |
|
||
|----------|---------------|
|
||
| Action 为空 | `Action 不能为空` |
|
||
| SceneID 为空 | `SceneID 不能为空,SceneID 用于指定场景配置` |
|
||
| 场景不存在 | `{SceneID} 不存在,请先配置对应场景。` |
|
||
| AK/SK 缺失 | `Custom 场景的 AccountConfig.accessKeyId 不能为空` |
|
||
| 火山引擎接口报错 | 透传火山引擎原始错误信息 |
|
||
|
||
---
|
||
|
||
## 三、写入历史上下文
|
||
|
||
```
|
||
POST /v1/session/history
|
||
```
|
||
|
||
在 `StartVoiceChat` **之前**调用,将上一次的对话历史注入该房间的上下文缓存。后续火山引擎 RTC 回调 `/chat_callback` 时,会自动将这些历史消息 prepend 到每次 LLM 请求的 messages 前面,实现跨会话的上下文延续。
|
||
|
||
### 请求
|
||
|
||
**Headers(内部签名):** 同 [getScenes](#请求)
|
||
|
||
**Body(JSON):**
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `room_id` | string | ✅ | RTC 房间 ID,取 `getScenes` 返回的 `rtc.RoomId` |
|
||
| `messages` | array | ✅ | 历史消息列表,按时间正序排列 |
|
||
|
||
**`messages[*]` 字段说明:**
|
||
|
||
| 字段 | 类型 | 必填 | 可选值 | 说明 |
|
||
|------|------|------|--------|------|
|
||
| `role` | string | ✅ | `"user"` \| `"assistant"` | 消息发送方角色 |
|
||
| `content` | string | ✅ | — | 消息文本内容 |
|
||
|
||
**请求示例:**
|
||
|
||
```json
|
||
{
|
||
"room_id": "550e8400-e29b-41d4-a716-446655440000",
|
||
"messages": [
|
||
{ "role": "assistant", "content": "你好,我是小块,有什么需要帮忙的吗?" },
|
||
{ "role": "user", "content": "今天出勤情况咋样" },
|
||
{ "role": "assistant", "content": "今天全部门出勤率百分之九十五,共二十三人到岗。" }
|
||
]
|
||
}
|
||
```
|
||
|
||
### 成功响应 `200`
|
||
|
||
```json
|
||
{
|
||
"code": 200
|
||
}
|
||
```
|
||
|
||
### 失败响应
|
||
|
||
**鉴权失败 `401`:**
|
||
|
||
```json
|
||
{
|
||
"code": 401,
|
||
"message": "鉴权失败"
|
||
}
|
||
```
|
||
|
||
**Body 校验失败 `422`(FastAPI 自动校验):**
|
||
|
||
```json
|
||
{
|
||
"detail": [
|
||
{
|
||
"type": "missing",
|
||
"loc": ["body", "room_id"],
|
||
"msg": "Field required"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
> **注意:**
|
||
> - 每次调用会**覆盖**该 `room_id` 下已有的历史,不是追加
|
||
> - `StopVoiceChat` 时会自动清除该房间的历史缓存
|
||
> - 如果不需要上下文延续(新对话),可以跳过此接口
|
||
|
||
---
|
||
|
||
## 四、自定义 LLM 回调(SSE)
|
||
|
||
> 此接口由**火山引擎 RTC 平台**自动回调,不经过 java-mock。
|
||
|
||
```
|
||
POST /v1/chat_callback?room_id={room_id}
|
||
```
|
||
|
||
**鉴权:** `Authorization: Bearer <CUSTOM_LLM_API_KEY>`
|
||
|
||
**Query 参数:**
|
||
|
||
| 参数 | 必填 | 说明 |
|
||
|------|------|------|
|
||
| `room_id` | ❌ | 房间 ID,由 `StartVoiceChat` 自动追加到回调 URL 中 |
|
||
|
||
**Body(JSON):**
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `messages` | array | ✅ | 对话消息列表,最后一条必须是 `user` 角色 |
|
||
| `temperature` | float | ❌ | 采样温度 |
|
||
| `max_tokens` | int | ❌ | 最大生成 token 数 |
|
||
| `top_p` | float | ❌ | Top-P 采样 |
|
||
|
||
**成功响应 `200`(`text/event-stream`):**
|
||
|
||
```
|
||
data: {"id":"chatcmpl-xxx","choices":[{"delta":{"content":"今"}}]}
|
||
|
||
data: {"id":"chatcmpl-xxx","choices":[{"delta":{"content":"天"}}]}
|
||
|
||
data: [DONE]
|
||
```
|
||
|
||
**失败响应:**
|
||
|
||
| HTTP 状态码 | Error.Code | 触发场景 |
|
||
|-------------|------------|----------|
|
||
| 401 | `AuthenticationError` | API Key 无效 |
|
||
| 400 | `BadRequest` | messages 为空 / 最后一条不是 user |
|
||
| 500 | `InternalError` | LLM 初始化失败 / 请求解析失败 |
|
||
|
||
---
|
||
|
||
## 五、调试接口
|
||
|
||
> 仅供本地开发,无鉴权。
|
||
|
||
### 5.1 调试聊天
|
||
|
||
```
|
||
POST /v1/debug/chat
|
||
```
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `history` | array | ❌ | 历史消息列表(`role` + `content`),默认空 |
|
||
| `question` | string | ✅ | 本次用户提问 |
|
||
|
||
**响应:** `200 text/plain` 流式文本
|
||
|
||
### 5.2 调试 RAG 检索
|
||
|
||
```
|
||
GET /v1/debug/rag?query={query}
|
||
```
|
||
|
||
| 参数 | 必填 | 说明 |
|
||
|------|------|------|
|
||
| `query` | ✅ | 检索问题 |
|
||
|
||
**响应 `200`:**
|
||
|
||
```json
|
||
{
|
||
"query": "今天出勤情况",
|
||
"retrieved_context": "检索到的知识文本...",
|
||
"length": 128,
|
||
"status": "success"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 内部鉴权协议
|
||
|
||
> `/getScenes`、`/proxy`、`/session/history` 均启用此鉴权。
|
||
|
||
**java-mock 发送方**在请求头附加:
|
||
|
||
| Header | 必填 | 说明 |
|
||
|--------|------|------|
|
||
| `X-Internal-Service` | ✅ | 固定值 `java-gateway` |
|
||
| `X-Internal-User-Id` | ✅ | 当前登录用户 ID |
|
||
| `X-Internal-Timestamp` | ✅ | 毫秒级 Unix 时间戳(字符串) |
|
||
| `X-Internal-Signature` | ✅ | HMAC-SHA256 签名(hex) |
|
||
| `X-User-Name` | ❌ | URL 编码的用户显示名(透传,Python 侧暂未使用) |
|
||
| `X-User-Sex` | ❌ | 性别 |
|
||
| `X-User-Dept-Name` | ❌ | URL 编码的部门名称 |
|
||
|
||
**签名算法:**
|
||
|
||
```
|
||
message = "java-gateway:{userId}:{毫秒时间戳}"
|
||
signature = HMAC-SHA256(INTERNAL_SERVICE_SECRET, message) → hex 编码
|
||
```
|
||
|
||
**Python 接收方验证逻辑:**
|
||
|
||
1. `INTERNAL_SERVICE_SECRET` 未配置 → **直接放行**(开发环境兼容)
|
||
2. 校验 `X-Internal-Service` 必须为 `java-gateway`,且 4 个必要 Header 非空
|
||
3. 时间窗口校验:`|当前时间 - timestamp| ≤ 5 分钟`(防重放)
|
||
4. 重新计算 HMAC,用 `hmac.compare_digest` 常量时间比较(防时序攻击)
|
||
|
||
---
|
||
|
||
## 通用错误结构
|
||
|
||
**内部接口(getScenes / proxy / session/history)** 返回火山引擎风格:
|
||
|
||
```json
|
||
{
|
||
"ResponseMetadata": {
|
||
"Action": "操作名",
|
||
"Error": {
|
||
"Code": -1,
|
||
"Message": "错误描述"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**chat_callback** 返回 JSON 格式错误(非 SSE):
|
||
|
||
```json
|
||
{
|
||
"Error": {
|
||
"Code": "BadRequest",
|
||
"Message": "messages 不能为空"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 环境变量
|
||
|
||
| 变量名 | 必填 | 说明 |
|
||
|--------|------|------|
|
||
| `INTERNAL_SERVICE_SECRET` | ✅ | 内部服务签名密钥,需与 java-mock 侧一致 |
|
||
| `CUSTOM_LLM_API_KEY` | ❌ | `/chat_callback` 的 Bearer Token,留空则跳过鉴权 |
|
||
| `LOCAL_LLM_API_KEY` | ✅ | 方舟 LLM API Key |
|
||
| `LOCAL_LLM_MODEL` | ✅ | 方舟端点 ID |
|
||
| `LOCAL_LLM_BASE_URL` | ✅ | 方舟 API 地址 |
|
||
|
||
完整变量列表见 `.env.example`。
|