509 lines
12 KiB
Markdown
509 lines
12 KiB
Markdown
# 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=<action>&Version=<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=<room_id>
|
||
```
|
||
|
||
**鉴权:** `Authorization: Bearer <CUSTOM_LLM_API_KEY>`(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 参数:**
|
||
|
||
| 字段 | 必填 | 说明 |
|
||
|---|---|---|
|
||
| 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`) |
|