Files
group_fqcd_jr/docs/17-接口文档易懂说明.md
T
lzf_0626 6516ccb385 feat: 第二版——接口契约对齐 docs/05,修复静默故障与数据库基线
相对第一版 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(含失败关闭反证)。
2026-09-10 15:55:54 +08:00

488 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 接口文档易懂说明
> 本文是《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
```
出现冲突时,不要同时实现两种规则,先按权威源矩阵裁决。