# Python 后端 API 文档 > Base URL: `http://localhost:3001` > > 所有来自 **java-mock** 的请求须附加内部服务签名 Header(见[内部鉴权协议](#内部鉴权协议))。 > > `/api/chat_callback` 由**火山引擎 RTC 平台**直接回调,走独立 API Key 鉴权。 --- ## 目录 - [接口总览](#接口总览) - [一、场景接口](#一场景接口) - [1.1 获取场景列表](#11-获取场景列表) - [二、RTC 代理接口](#二rtc-代理接口) - [2.1 开始 / 停止语音对话](#21-开始--停止语音对话) - [三、会话历史接口](#三会话历史接口) - [3.1 写入历史上下文](#31-写入历史上下文) - [四、LLM 回调接口](#四llm-回调接口) - [4.1 自定义 LLM 回调(SSE)](#41-自定义-llm-回调sse) - [五、调试接口](#五调试接口) - [5.1 调试聊天](#51-调试聊天) - [5.2 调试 RAG 检索](#52-调试-rag-检索) - [内部鉴权协议](#内部鉴权协议) - [通用错误结构](#通用错误结构) - [环境变量](#环境变量) --- ## 接口总览 | 方法 | 路径 | 调用方 | 鉴权方式 | 说明 | |---|---|---|---|---| | POST | `/getScenes` | java-mock | 内部签名 | 获取场景列表 & RTC 配置 | | POST | `/proxy` | java-mock | 内部签名 | 转发 StartVoiceChat / StopVoiceChat 到火山引擎 | | POST | `/api/session/history` | java-mock | 内部签名 | 写入房间历史上下文 | | POST | `/api/chat_callback` | 火山引擎 RTC 平台 | API Key | 自定义 LLM 回调,返回 SSE 流 | | POST | `/debug/chat` | 开发调试 | 无 | 直接测试 LLM 对话 | | GET | `/debug/rag` | 开发调试 | 无 | 测试 RAG 知识库检索 | --- ## 一、场景接口 ### 1.1 获取场景列表 ``` POST /getScenes ``` **鉴权:** 内部签名(java-mock 调用) **请求体:** `{}` 或空 **成功响应 200:** ```json { "ResponseMetadata": { "Action": "getScenes" }, "Result": { "scenes": [ { "scene": { "id": "Custom", "botName": "BotUser001", "isInterruptMode": true, "isVision": false, "isScreenMode": false, "isAvatarScene": false, "avatarBgUrl": null }, "rtc": { "AppId": "6xxxxxxx", "RoomId": "550e8400-e29b-41d4-a716-446655440000", "UserId": "user-xyz", "Token": "AQBhMGI3Zm...", "TaskId": "task-001" } } ] } } ``` **场景字段说明:** | 字段 | 类型 | 说明 | |---|---|---| | id | string | 场景唯一标识,后续接口的 `SceneID` 取此值 | | botName | string | AI Bot 在 RTC 房间中的 UserId | | isInterruptMode | boolean | 是否开启打断模式(InterruptMode === 0) | | isVision | boolean | 是否开启视觉能力 | | isScreenMode | boolean | 是否为屏幕共享模式 | | isAvatarScene | boolean | 是否为数字人场景 | | avatarBgUrl | string\|null | 数字人背景图 URL | **RTC 字段说明:** | 字段 | 类型 | 说明 | |---|---|---| | AppId | string | 火山引擎 RTC AppId | | RoomId | string | 本次会话分配的房间 ID(UUID) | | UserId | string | 用户在 RTC 房间中的 UserId | | Token | string | RTC 入房 Token | | TaskId | string | 语音任务 ID,StopVoiceChat 时需要 | > **副作用:** 该接口会将 `RoomId / UserId / TaskId` 写入服务端 Session,供后续 `/proxy?Action=StartVoiceChat` 自动取用,无需客户端传递。 **失败响应:** ```json { "ResponseMetadata": { "Action": "getScenes", "Error": { "Code": -1, "Message": "错误描述" } } } ``` --- ## 二、RTC 代理接口 ### 2.1 开始 / 停止语音对话 ``` POST /proxy?Action=&Version= ``` **鉴权:** 内部签名(java-mock 调用) **Query 参数:** | 字段 | 必填 | 默认值 | 说明 | |---|---|---|---| | Action | ✅ | — | `StartVoiceChat` 或 `StopVoiceChat` | | Version | ❌ | 配置文件值 | 火山引擎 OpenAPI 版本,如 `2024-12-01` | **请求体(JSON):** | 字段 | 类型 | 必填 | 说明 | |---|---|---|---| | SceneID | string | ✅ | 场景 ID(从 `getScenes` 的 `scene.id` 获取) | --- #### StartVoiceChat **请求示例:** ```json { "SceneID": "Custom" } ``` **内部处理逻辑:** 1. 从 Session 取回 `getScenes` 时生成的 `RoomId / UserId / TaskId` 2. 将 `room_id` 附加到 LLM 回调 URL(`?room_id=xxx`),使 `/api/chat_callback` 能关联历史上下文 3. 带 SigV4 签名转发到火山引擎 `https://rtc.volcengineapi.com` **成功响应 200:** ```json { "ResponseMetadata": { "RequestId": "2024xxxxxxxxxx", "Action": "StartVoiceChat", "Version": "2024-12-01", "Service": "rtc" }, "Result": { "Message": "success" } } ``` --- #### StopVoiceChat **请求示例:** ```json { "SceneID": "Custom" } ``` **内部处理逻辑:** 1. 从 Session 取回 `RoomId / TaskId` 2. 清除该房间的历史上下文缓存(`room_history`) 3. 带 SigV4 签名转发到火山引擎 **成功响应 200:** ```json { "ResponseMetadata": { "RequestId": "2024xxxxxxxxxx", "Action": "StopVoiceChat", "Version": "2024-12-01", "Service": "rtc" }, "Result": { "Message": "success" } } ``` **通用失败响应:** ```json { "ResponseMetadata": { "Action": "StartVoiceChat", "Error": { "Code": "InvalidParameter", "Message": "SceneID 不能为空,SceneID 用于指定场景配置" } } } ``` --- ## 三、会话历史接口 ### 3.1 写入历史上下文 > 在 `StartVoiceChat` 之前调用,将历史对话注入该房间的上下文缓存。后续 `/api/chat_callback` 会自动将其 prepend 到每次 LLM 请求的 messages 前。 ``` POST /api/session/history ``` **鉴权:** 内部签名(java-mock 调用) **请求体(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": "今天出勤率 90%" } ] } ``` **成功响应 200:** ```json { "code": 200 } ``` **失败响应 401:** ```json { "code": 401, "message": "鉴权失败" } ``` --- ## 四、LLM 回调接口 ### 4.1 自定义 LLM 回调(SSE) > 由**火山引擎 RTC 平台**在用户发言后自动回调。返回 OpenAI 兼容格式的 SSE 流。 ``` POST /api/chat_callback?room_id= ``` **鉴权:** `Authorization: Bearer `(Header 或 Query 参数) **Query 参数:** | 字段 | 必填 | 说明 | |---|---|---| | room_id | ❌ | 房间 ID。传入时自动从缓存取历史上下文并 prepend 到 messages | **请求体(JSON):** | 字段 | 类型 | 必填 | 说明 | |---|---|---|---| | messages | array | ✅ | 对话消息列表,最后一条必须是 `user` 角色 | | temperature | float | ❌ | 采样温度 | | max_tokens | int | ❌ | 最大生成 token 数 | | top_p | float | ❌ | Top-P 采样 | `messages` 中每条消息: | 字段 | 类型 | 说明 | |---|---|---| | role | string | `"user"` / `"assistant"` / `"system"` | | content | string | 消息文本 | **请求示例:** ```json { "messages": [ { "role": "user", "content": "今天办公室出勤情况咋样" } ], "temperature": 0.7, "max_tokens": 1024 } ``` **处理逻辑:** 1. 验证 API Key 2. 过滤掉 `content === "欢迎语"` 的触发词消息(RTC 平台自动发送,非真实用户输入) 3. 若有 `room_id`,从缓存取历史并 prepend 到 messages 前 4. 调用本地 LLM 服务(工具调用 / RAG 按需触发) 5. 以 SSE 流返回结果 **成功响应 200(text/event-stream):** ``` data: {"id":"chatcmpl-xxx","object":"chat.completion.chunk","choices":[{"delta":{"content":"今"},...}]} data: {"id":"chatcmpl-xxx","object":"chat.completion.chunk","choices":[{"delta":{"content":"天"},...}]} data: [DONE] ``` **失败响应(SSE 格式,HTTP 状态码对应):** ``` data: {"error":{"code":"AuthenticationError","message":"API Key 无效"}} data: [DONE] ``` | HTTP 状态码 | code | 触发场景 | |---|---|---| | 401 | `AuthenticationError` | API Key 无效 | | 400 | `BadRequest` | messages 为空 / 最后一条不是 user | | 500 | `InternalError` | LLM 初始化失败 / 请求解析失败 | --- ## 五、调试接口 > 仅供本地开发使用,无鉴权。 ### 5.1 调试聊天 ``` POST /debug/chat ``` 直接发消息给 LLM,响应为纯文本流(非 SSE)。完成后在服务端终端输出可复用的 `history` JSON 结构。 **请求体(JSON):** | 字段 | 类型 | 必填 | 说明 | |---|---|---|---| | history | array | ❌ | 历史消息列表,格式同 `messages`,默认空 | | question | string | ✅ | 本次用户提问 | **请求示例:** ```json { "history": [ { "role": "assistant", "content": "你好,有什么可以帮你?" } ], "question": "今天办公室有多少人打卡?" } ``` **成功响应 200(text/plain 流式):** ``` 今天办公室一共九人,目前出勤率为 100%。 ``` --- ### 5.2 调试 RAG 检索 ``` GET /debug/rag?query= ``` 测试知识库检索,返回检索到的原始上下文内容。 **Query 参数:** | 字段 | 必填 | 说明 | |---|---|---| | query | ✅ | 检索问题 | **成功响应 200:** ```json { "query": "今天出勤情况", "retrieved_context": "#### 今天出勤数据\n办公室共 9 人...", "length": 128, "status": "success" } ``` 无结果时 `status` 为 `"no_results_or_error"`,`retrieved_context` 为 `null`。 --- ## 内部鉴权协议 > `/getScenes`、`/proxy`、`/api/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 编码的用户显示名 | | `X-User-Sex` | 性别 | | `X-User-Is-Driver` | `"true"` / `"false"` | | `X-User-Dept-Id` | 部门 ID | | `X-User-Dept-Name` | URL 编码的部门名称 | | `X-User-Role-List` | URL 编码的角色列表 JSON | **签名算法:** ``` message = "java-gateway:{userId}:{毫秒时间戳}" signature = HMAC-SHA256(INTERNAL_SERVICE_SECRET, message) // hex ``` **Python 接收方验证逻辑(`security/internal_auth.py`):** 1. `INTERNAL_SERVICE_SECRET` 未配置 → 直接放行(开发环境兼容) 2. 校验 4 个必要字段是否存在,`X-Internal-Service` 必须为 `java-gateway` 3. 时间窗口:`abs(now_ms - timestamp) ≤ 5分钟`(防重放) 4. 重新计算 HMAC,用 `hmac.compare_digest` 常量时间比较(防时序攻击) --- ## 通用错误结构 **内部接口(getScenes / proxy / session 系列)** 返回火山引擎风格: ```json { "ResponseMetadata": { "Action": "xxx", "Error": { "Code": "InvalidParameter", "Message": "SceneID 不能为空" } } } ``` **chat_callback** 返回 SSE 格式错误: ``` data: {"error":{"code":"BadRequest","message":"messages 不能为空"}} data: [DONE] ``` --- ## 环境变量 | 变量名 | 必填 | 说明 | |---|---|---| | `INTERNAL_SERVICE_SECRET` | ✅ | 内部服务签名密钥,与 java-mock 侧保持一致 | | `CUSTOM_LLM_API_KEY` | ✅ | `chat_callback` 接口的鉴权 Key,配置到火山引擎 RTC 场景中 | | `OPENAI_API_KEY` | ✅(或同类) | 接入 LLM 所需的 API Key | | `OPENAI_BASE_URL` | ❌ | 自定义 LLM Base URL(接入本地模型时使用) | | `LLM_MODEL` | ❌ | 指定模型名称 | | `RAG_*` | ❌ | 知识库相关配置(详见 `utils/env.py`) |