相对第一版 46fc976 的完整变更。组员迁移对照表见 docs/20。
一、对外契约对齐 docs/05(破坏性,共 4 处,组员需按 docs/20 调整)
1) 配置发布端点改为文档规定的复数资源名:submit→validations、
approve→reviews(需 body decision)、activate→activations、
rollback→rollbacks;第一版这 4 个动词式路径 docs/05 从未定义过。
2) 错误码由 8 个笼统码改为 15 个具体语义码(FORBIDDEN→AGENT_PERMISSION_DENIED、
UNAUTHORIZED→AUTHENTICATION_REQUIRED、CONFLICT→RESOURCE_VERSION_CONFLICT、
RESOURCE_NOT_FOUND→RUN_NOT_FOUND/SESSION_NOT_FOUND 等),
输入类错误状态码 400→422。
3) POST /api/v1/agent-runs 与 GET /api/v1/agent-runs/{run_id} 统一为
{data, meta} 信封(data 内字段名与语义未变)。
4) 错误响应体统一为 {error:{code,message,retryable,field_errors}, meta:{trace_id}},
不再返回 FastAPI 默认的 {"detail": ...}。
二、数据库基线与约束
新增 39 张表的基线迁移(链根)与联合唯一键纠偏(4 张表、删 8 增 4,幂等收敛);
撤下 config_release 的双人复核 CHECK(应用层已允许自审,审核节点保留,
自审如实写入 reviewer_id);记忆 active key 生成列与唯一键;
activate 开始记录 supersedes_release_id 使版本链可追溯。
docs/00 基线未修改,未重命名或删除任何表与字段。
三、修复会静默出错或无报错的缺陷
- 跑完集成测试后平台会静默失去生效配置:清理只删自己创建的版本,却没有恢复被它
顶成 superseded 的原生效版本,且审计一并删除因而完全无痕,表现为所有工具被拒
但没有任何报错。已修清理逻辑并加恢复。
- Worker 单轮异常导致进程退出;记忆抽取调用方的“事务已开始”异常;
召回缓存丢失 degraded 标记;连接时区未生效导致 created_at/updated_at 差 8 小时;
.env 与 os.getenv 密钥来源分裂导致“没有可用的已批准模型端点”。
- 记忆信号识别漏判与跨键误命中;SSE 未带 Accept 的协商行为。
四、功能补齐
记忆链路 P1/P2/P3(抽取、受控词表、召回与缓存、生命周期级联及投影事件)、
fin_* 场内交易只读 ORM 层、agent_intent_config 状态流转并在运行期真正生效、
限流(Redis 固定窗口、故障一律放行)、游标校验、trace_id 中间件、
示例业务 Agent fund_query_demo 与一键端到端验证脚本,以及审计/指纹/迁移状态工具。
五、文档与验证
新增 docs/19(业务 Agent 接入实操)、docs/20(第一版迁移指南)与 docs/evidence 证据;
docs/01/02/06/08/09/17 同步实现现状。
验证结果:ruff 通过、mypy 103 文件无错、unit+contract 447 passed、
integration 29 passed、acceptance_check --production 7 PASS、
demo_agent_e2e 9/9 PASS(含失败关闭反证)。
488 lines
14 KiB
Markdown
488 lines
14 KiB
Markdown
# 接口文档易懂说明
|
||
|
||
> 本文是《05-接口文档.md》的阅读指南,不替代 05 文档。
|
||
> 适用对象:前端、业务 Agent 开发人员、测试人员和 AI 编码助手。
|
||
|
||
## 1. 接口到底是什么
|
||
|
||
接口就是前端或其他系统和平台说话的固定方式。调用接口时,必须明确:
|
||
|
||
```text
|
||
请求地址 + 请求方法 + Header + 请求体
|
||
→ 服务端处理
|
||
→ HTTP 状态码 + 返回数据
|
||
```
|
||
|
||
本系统有三类接口:
|
||
|
||
| 类型 | 地址前缀 | 用途 |
|
||
|---|---|---|
|
||
| 业务运行接口 | `/api/v1` | 提交 Agent、查结果、订阅 SSE |
|
||
| 平台管理接口 | `/api/v1/admin` | 管理配置、模型、Prompt、意图和审计 |
|
||
| 运维接口 | `/internal` | 健康检查和监控 |
|
||
|
||
业务 Agent 一般不需要新增公共 HTTP 接口。新增一个 Agent 通常直接使用统一的
|
||
`/api/v1/agent-runs`。
|
||
|
||
## 2. 先看懂一次完整请求
|
||
|
||
### 2.1 提交 Agent 运行
|
||
|
||
```http
|
||
POST /api/v1/agent-runs
|
||
Authorization: Bearer <JWT>
|
||
Idempotency-Key: advisor-request-202609100001
|
||
Content-Type: application/json
|
||
```
|
||
|
||
```json
|
||
{
|
||
"agent_type": "advisor",
|
||
"session_id": "session-uuid",
|
||
"message": "请查询基金 159511 的行情",
|
||
"metadata": {
|
||
"locale": "zh-CN",
|
||
"client_version": "web-1.0",
|
||
"ui_entry": "advisor-chat"
|
||
}
|
||
}
|
||
```
|
||
|
||
返回 `202`:
|
||
|
||
```json
|
||
{
|
||
"data": {
|
||
"run_id": "run-uuid",
|
||
"trace_id": "trace-uuid",
|
||
"status": "queued",
|
||
"status_url": "/api/v1/agent-runs/run-uuid",
|
||
"events_url": "/api/v1/agent-runs/run-uuid/events"
|
||
},
|
||
"meta": {
|
||
"trace_id": "trace-uuid"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2.2 这几个字段的区别
|
||
|
||
- `agent_type`:要执行哪个业务 Agent;必须已经在 `AgentFactory` 注册;
|
||
- `session_id`:这次对话属于哪个会话;
|
||
- `message`:用户真正说的话;
|
||
- `idempotency-key`:防止网络重试造成重复运行;
|
||
- `run_id`:一次运行的唯一编号;
|
||
- `trace_id`:贯穿日志、审计、事件和模型调用的追踪编号;
|
||
- `status_url`:查询最终结果;
|
||
- `events_url`:订阅实时或最终 SSE 结果。
|
||
|
||
客户端不能通过请求体伪造 `user_id`、角色、客户范围、模型、工具或权限。
|
||
|
||
## 3. 通用 Header
|
||
|
||
### 3.1 JWT
|
||
|
||
除运维接口外,接口都需要:
|
||
|
||
```http
|
||
Authorization: Bearer <access-token>
|
||
```
|
||
|
||
服务端会检查签名、签发方、受众、有效时间和吊销状态,并重新读取用户当前角色和权限。
|
||
不能只相信 JWT 里保存的历史角色。
|
||
|
||
### 3.2 幂等键
|
||
|
||
所有写接口都要按 05 文档要求携带:
|
||
|
||
```http
|
||
Idempotency-Key: unique-request-key
|
||
```
|
||
|
||
同一个用户、同一路径、同一个幂等键:
|
||
|
||
- 请求内容相同:返回第一次请求的结果;
|
||
- 请求内容不同:返回 `409 IDEMPOTENCY_CONFLICT`;
|
||
- 原请求还在运行:返回原来的 `run_id`,不会创建第二次运行。
|
||
|
||
### 3.3 其他 Header
|
||
|
||
```http
|
||
Content-Type: application/json
|
||
Accept: application/json
|
||
If-Match: <etag>
|
||
```
|
||
|
||
`Accept` 非必填。普通接口按 `application/json` 返回;SSE 接口见第 6 节,
|
||
显式声明不接受 `text/event-stream` 时会返回 `406 SSE_NOT_ACCEPTABLE`。
|
||
|
||
`If-Match` 主要用于管理接口更新和状态转换,防止两个人同时覆盖配置。
|
||
|
||
## 4. 成功和失败格式
|
||
|
||
### 4.1 成功格式
|
||
|
||
单个资源:
|
||
|
||
```json
|
||
{
|
||
"data": {},
|
||
"meta": {"trace_id": "trace-uuid"}
|
||
}
|
||
```
|
||
|
||
列表资源:
|
||
|
||
```json
|
||
{
|
||
"data": [],
|
||
"meta": {
|
||
"trace_id": "trace-uuid",
|
||
"next_cursor": "opaque-cursor",
|
||
"has_more": false
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.2 失败格式
|
||
|
||
```json
|
||
{
|
||
"error": {
|
||
"code": "AGENT_PERMISSION_DENIED",
|
||
"message": "当前角色不能使用该 Agent",
|
||
"retryable": false,
|
||
"field_errors": []
|
||
},
|
||
"meta": {"trace_id": "trace-uuid"}
|
||
}
|
||
```
|
||
|
||
错误响应不会返回 SQL、堆栈、模型原文、供应商响应或密钥。
|
||
|
||
## 5. Agent 运行接口
|
||
|
||
### 5.1 创建运行
|
||
|
||
```http
|
||
POST /api/v1/agent-runs
|
||
```
|
||
|
||
用途:把用户消息提交给已注册的 Agent。返回 `202` 只表示“已受理”,不代表业务已经完成。
|
||
|
||
典型状态:
|
||
|
||
```text
|
||
queued → running → succeeded
|
||
→ failed
|
||
queued/running → cancel_requested → cancelled
|
||
```
|
||
|
||
### 5.2 查询运行
|
||
|
||
```http
|
||
GET /api/v1/agent-runs/{run_id}
|
||
```
|
||
|
||
用途:获取最终权威结果。运行未完成时 `result=null`;成功后结果来自数据库持久化消息,不依赖 Worker 内存。
|
||
|
||
```json
|
||
{
|
||
"data": {
|
||
"run_id": "run-uuid",
|
||
"status": "succeeded",
|
||
"result": {
|
||
"reply": "最终回复",
|
||
"intent": "fund_quote",
|
||
"confidence": "0.9200",
|
||
"source_references": [],
|
||
"transfer_required": false,
|
||
"degraded": false
|
||
}
|
||
},
|
||
"meta": {"trace_id": "trace-uuid"}
|
||
}
|
||
```
|
||
|
||
### 5.3 取消运行
|
||
|
||
```http
|
||
POST /api/v1/agent-runs/{run_id}/cancellations
|
||
Idempotency-Key: cancel-request-001
|
||
```
|
||
|
||
```json
|
||
{"reason": "user_cancelled"}
|
||
```
|
||
|
||
返回 `202` 表示已提出取消请求。运行已经成功、已经失败或已进入最终提交阶段时返回
|
||
`409 RUN_NOT_CANCELLABLE`。
|
||
|
||
**重复取消是幂等的**:对同一运行再次调用取消,返回与首次相同的状态与受理时间,不会报错;
|
||
取消成功后原请求会在 `request_idempotency` 中记为 `status=failed` + `error_code=RUN_CANCELLED`,
|
||
表示这次请求已被终止(`RUN_CANCELLED` 不是 HTTP 响应码,客户端不需要处理它)。
|
||
|
||
## 6. SSE 接口怎么用
|
||
|
||
```http
|
||
GET /api/v1/agent-runs/{run_id}/events
|
||
Authorization: Bearer <JWT>
|
||
Accept: text/event-stream
|
||
```
|
||
|
||
`Accept` 是**非必填**头:不携带时服务端按 SSE 返回;显式携带时必须接受
|
||
`text/event-stream`(`text/*`、`*/*` 也算接受),否则返回 `406 SSE_NOT_ACCEPTABLE`。
|
||
运行不存在或不属于当前用户时仍然返回 `404 RUN_NOT_FOUND`——不因为 `Accept` 非法就改变
|
||
可见性判断,避免被用来探测运行是否存在。
|
||
|
||
### 6.1 事件顺序
|
||
|
||
未完成运行:
|
||
|
||
```text
|
||
start → 心跳 → 等待最终提交
|
||
```
|
||
|
||
正常完成:
|
||
|
||
```text
|
||
start → tools(可选)→ delta(一个或多个)→ done
|
||
```
|
||
|
||
已完成运行重连:
|
||
|
||
```text
|
||
start → replace → done
|
||
```
|
||
|
||
错误:
|
||
|
||
```text
|
||
start → error
|
||
```
|
||
|
||
### 6.2 客户端注意事项
|
||
|
||
- SSE 必须携带 JWT;
|
||
- 浏览器原生 `EventSource` 无法设置 Authorization Header,建议使用支持 Header 的 `fetch` 流式实现;
|
||
- `done` 和 `error` 是终止事件;
|
||
- 当前是结果级恢复,不是事件级 `Last-Event-ID` 续传;
|
||
- 断线后重新请求同一个 `run_id`;
|
||
- 使用 `run_id + result_version` 去重;
|
||
- `delta`、`replace` 和 `done` 只会在最终事务提交后发送。
|
||
|
||
## 7. 会话接口
|
||
|
||
### 创建会话
|
||
|
||
```http
|
||
POST /api/v1/conversations
|
||
Idempotency-Key: session-create-001
|
||
```
|
||
|
||
```json
|
||
{"agent_type": "advisor"}
|
||
```
|
||
|
||
### 查询会话
|
||
|
||
```http
|
||
GET /api/v1/conversations/{session_id}
|
||
```
|
||
|
||
### 查询消息
|
||
|
||
```http
|
||
GET /api/v1/conversations/{session_id}/messages?limit=20&cursor=<cursor>
|
||
```
|
||
|
||
### 结束会话
|
||
|
||
```http
|
||
POST /api/v1/conversations/{session_id}/closures
|
||
Idempotency-Key: session-close-001
|
||
```
|
||
|
||
### 转人工
|
||
|
||
```http
|
||
POST /api/v1/conversations/{session_id}/handover-requests
|
||
Idempotency-Key: handover-001
|
||
```
|
||
|
||
Agent 可以发起转人工请求,但不能分配、接单、解决或关闭人工工单。
|
||
|
||
## 8. 记忆和知识引用接口
|
||
|
||
查询自己的记忆:
|
||
|
||
```http
|
||
GET /api/v1/users/me/memory-profile
|
||
```
|
||
|
||
查询授权客户记忆:
|
||
|
||
```http
|
||
GET /api/v1/customers/{customer_id}/memory-profile
|
||
```
|
||
|
||
解析知识引用:
|
||
|
||
```http
|
||
GET /api/v1/knowledge-references/{reference_token}
|
||
```
|
||
|
||
记忆和知识数据都受权限和客户范围限制,不能通过 token 枚举其他客户的数据。
|
||
|
||
## 9. 平台管理接口
|
||
|
||
管理接口只给管理员和配置负责人使用,业务 Agent 不应该在 `handle()` 中调用这些接口。
|
||
|
||
主要分类:
|
||
|
||
| 分类 | 示例 |
|
||
|---|---|
|
||
| 配置发布 | `/api/v1/admin/config-releases` |
|
||
| 平台配置项 | `/api/v1/admin/config-releases/{id}/platform-config-items` |
|
||
| 模型端点 | `/api/v1/admin/model-endpoints` |
|
||
| 模型路由 | `/api/v1/admin/config-releases/{id}/model-routing-rules` |
|
||
| Prompt | `/api/v1/admin/prompt-templates` |
|
||
| Agent 意图 | `/api/v1/admin/agent-intent-configs` |
|
||
| 回复模板 | `/api/v1/admin/reply-templates` |
|
||
| 禁止表达 | `/api/v1/admin/negative-word-rules` |
|
||
| 审计查询 | `/api/v1/admin/audit-records` |
|
||
|
||
配置发布流程:
|
||
|
||
```text
|
||
draft → pending_review → approved → active
|
||
↘ rejected
|
||
active → superseded / rollback
|
||
```
|
||
|
||
管理更新必须使用 `If-Match`;创建、审核、激活、回滚和停用必须使用幂等键。审核节点不可跳过(单管理员部署下创建人可自审,不再要求复核人不同于创建人)。
|
||
|
||
## 10. 运维接口
|
||
|
||
```http
|
||
GET /internal/health/live
|
||
GET /internal/health/ready
|
||
GET /internal/metrics
|
||
```
|
||
|
||
- `live`:进程是否存活;
|
||
- `ready`:服务是否可以接收请求,MySQL/Redis 等依赖异常时可能返回 `503`;
|
||
- `metrics`:监控指标。
|
||
|
||
## 11. 常见错误
|
||
|
||
下表与唯一权威接口文档 `docs/05-接口文档.md` §3.6 完全一致(按 §6.4 的澄清,
|
||
`RUN_CANCELLED` 不是 HTTP 响应错误码,因此单列在表末并标注用途),并由
|
||
`tests/unit/core/test_errors.py` 逐条锁定(实现里每个异常类的码、状态码和可重试标注
|
||
都必须能对回这张表)。错误响应信封见 §3.4:
|
||
|
||
```json
|
||
{
|
||
"error": {
|
||
"code": "IDEMPOTENCY_CONFLICT",
|
||
"message": "幂等键已用于不同请求",
|
||
"retryable": false,
|
||
"field_errors": []
|
||
},
|
||
"meta": {"trace_id": "trace-uuid"}
|
||
}
|
||
```
|
||
|
||
| 状态码 | 错误码 | 说明 | 是否重试 |
|
||
|---:|---|---|---|
|
||
| 400 | `INVALID_CURSOR` | 游标非法、过期或与过滤条件不符 | 否 |
|
||
| 401 | `AUTHENTICATION_REQUIRED` | Token 缺失、无效、过期或已吊销 | 修复 Token 后再试 |
|
||
| 403 | `AGENT_PERMISSION_DENIED` | 角色、入口或数据范围不允许 | 否 |
|
||
| 404 | `SESSION_NOT_FOUND` | 会话不存在,或为防枚举而被隐藏;其他被隐藏资源(消息、工单、客户、管理面资源)复用此码 | 否 |
|
||
| 404 | `SESSION_NOT_ACCESSIBLE` | 当前身份不能访问该会话(含跨用户运行受理) | 否 |
|
||
| 404 | `AGENT_TYPE_NOT_FOUND` | 请求的 `agent_type` 未注册(受理前拒绝,不写库) | 否 |
|
||
| 404 | `RUN_NOT_FOUND` | 运行不存在或不可见;越权与不存在返回同一码,不泄露存在性 | 否 |
|
||
| 406 | `SSE_NOT_ACCEPTABLE` | `Accept` 不接受 `text/event-stream` | 否 |
|
||
| 409 | `IDEMPOTENCY_CONFLICT` | 幂等键复用但请求不同;唯一性约束冲突同样归此码 | 否 |
|
||
| 409 | `RESOURCE_VERSION_CONFLICT` | `If-Match` 版本过期或缺失 | 可以重试 |
|
||
| 409 | `RUN_NOT_CANCELLABLE` | 运行已成功、失败或进入最终提交事务;非法状态转换(审核、停用、关闭会话等)也归此码 | 否 |
|
||
| (仅状态标识) | `RUN_CANCELLED` | 只写在 `request_idempotency`(`status=failed`),表示取消后原请求已终止;不作为 HTTP 响应码返回,重复取消返回同一状态 | — |
|
||
| 409 | `FEEDBACK_ALREADY_EXISTS` | 同一用户对同一消息重复提交且内容不同 | 否 |
|
||
| 422 | `AGENT_INPUT_INVALID` | 已解析请求不满足字段或业务输入约束(含工具参数校验失败) | 否 |
|
||
| 429 | `RATE_LIMITED` | 频率、并发或配额限制 | 可以重试 |
|
||
| 500 | `AGENT_INTERNAL_ERROR` | 未分类内部错误 | 视情况 |
|
||
| 503 | `DEPENDENCY_UNAVAILABLE` | 必需依赖不可用(模型端点、embedding、行情源、工具调用失败),Worker 会重试 | 可以重试 |
|
||
| 504 | `UPSTREAM_TIMEOUT` | 上游超过时间预算(模型、工具、Agent 执行超时) | 可以重试 |
|
||
|
||
注意事项:
|
||
|
||
- 401 与其他错误使用**同一个信封**:`code=AUTHENTICATION_REQUIRED`,并带 `error.retryable`
|
||
与 `error.field_errors`;令牌缺失、非法、过期、吊销以及账号不可用都返回这一种形态,
|
||
调用方只需要一套解析逻辑。认证失败时请求上下文尚未建立,`meta.trace_id` 复用请求头里的
|
||
`X-Trace-ID`,没有则为空字符串(服务端不会凭空生成)。
|
||
- 越权资源统一返回 `404`,不泄露资源是否存在;明确功能无权限时返回 `403`。
|
||
- Worker 落库的 `agent_run.error_code` 是任务层字段,与 HTTP 错误码不同层,
|
||
例如 `RUN_CANCELLED`、`RUN_LEASE_LOST` 出现在该字段中不代表 HTTP 响应码。
|
||
- `RUN_CANCELLED` 只作为 `request_idempotency` 的状态标识(`status=failed`)使用,
|
||
**不是** HTTP 响应错误码;对同一运行重复取消必须幂等返回同一状态。
|
||
|
||
## 12. 业务 Agent 与接口的关系
|
||
|
||
### A 类:Agent 能力扩展
|
||
|
||
新增 Agent、意图或 `handle()` 分支时,继续使用:
|
||
|
||
```text
|
||
POST /api/v1/agent-runs
|
||
```
|
||
|
||
不需要新增 Controller,不允许复制一套 HTTP 运行接口。
|
||
|
||
### B 类:业务 API 扩展
|
||
|
||
只有确实需要独立领域资源,例如模拟委托、风险处置、方案审核时,才新增业务接口。新增时必须:
|
||
|
||
- 使用 `/api/v1` 前缀;
|
||
- 由 Controller 调 Service,不能直接访问 Model;
|
||
- 使用统一 JWT、错误信封、幂等、追踪和审计;
|
||
- 清楚写出 Agent 能做什么、不能做什么;
|
||
- 同时更新 05 文档、OpenAPI、权限、审计和测试。
|
||
|
||
## 13. 前端或业务联调清单
|
||
|
||
```text
|
||
[ ] 使用真实 agent_type,确认已注册
|
||
[ ] Authorization 使用 Bearer JWT
|
||
[ ] 所有写请求有唯一 Idempotency-Key
|
||
[ ] POST 受理后使用 run_id 查询或订阅 SSE
|
||
[ ] SSE 断线后重新请求同一个 run_id
|
||
[ ] 按 data/meta 读取成功信封
|
||
[ ] 按 error/meta 读取错误信封
|
||
[ ] 不把 202 当成业务完成
|
||
[ ] 不把 degraded 行情当成成交结果
|
||
[ ] 不把客户端传入的身份字段当成权限依据
|
||
```
|
||
|
||
## 14. 接口变更规则
|
||
|
||
新增或修改接口必须同时更新:
|
||
|
||
1. `docs/05-接口文档.md`;
|
||
2. OpenAPI 定义;
|
||
3. 权限码、审计类别、幂等和错误码;
|
||
4. 正常、越权、重复提交、故障恢复和安全测试;
|
||
5. 对应业务文档的入口索引。
|
||
|
||
已有接口不能随意删除、改名、改字段类型或改变既有含义。发生破坏性变更时升级到新的主版本路径。
|
||
|
||
## 15. 权威文档关系
|
||
|
||
```text
|
||
接口路径、Header、信封、错误、SSE、幂等 → docs/05-接口文档.md
|
||
Agent 内部执行契约 → docs/01-通用Agent平台开发设计.md
|
||
数据库表和字段 → docs/00-新数据库基线设计.md、docs/02-数据库建表设计.md
|
||
组员开发步骤 → docs/15、docs/16
|
||
```
|
||
|
||
出现冲突时,不要同时实现两种规则,先按权威源矩阵裁决。
|