# 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 ` **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`。