相对第一版 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(含失败关闭反证)。
14 KiB
接口文档易懂说明
本文是《05-接口文档.md》的阅读指南,不替代 05 文档。
适用对象:前端、业务 Agent 开发人员、测试人员和 AI 编码助手。
1. 接口到底是什么
接口就是前端或其他系统和平台说话的固定方式。调用接口时,必须明确:
请求地址 + 请求方法 + Header + 请求体
→ 服务端处理
→ HTTP 状态码 + 返回数据
本系统有三类接口:
| 类型 | 地址前缀 | 用途 |
|---|---|---|
| 业务运行接口 | /api/v1 |
提交 Agent、查结果、订阅 SSE |
| 平台管理接口 | /api/v1/admin |
管理配置、模型、Prompt、意图和审计 |
| 运维接口 | /internal |
健康检查和监控 |
业务 Agent 一般不需要新增公共 HTTP 接口。新增一个 Agent 通常直接使用统一的
/api/v1/agent-runs。
2. 先看懂一次完整请求
2.1 提交 Agent 运行
POST /api/v1/agent-runs
Authorization: Bearer <JWT>
Idempotency-Key: advisor-request-202609100001
Content-Type: application/json
{
"agent_type": "advisor",
"session_id": "session-uuid",
"message": "请查询基金 159511 的行情",
"metadata": {
"locale": "zh-CN",
"client_version": "web-1.0",
"ui_entry": "advisor-chat"
}
}
返回 202:
{
"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
除运维接口外,接口都需要:
Authorization: Bearer <access-token>
服务端会检查签名、签发方、受众、有效时间和吊销状态,并重新读取用户当前角色和权限。 不能只相信 JWT 里保存的历史角色。
3.2 幂等键
所有写接口都要按 05 文档要求携带:
Idempotency-Key: unique-request-key
同一个用户、同一路径、同一个幂等键:
- 请求内容相同:返回第一次请求的结果;
- 请求内容不同:返回
409 IDEMPOTENCY_CONFLICT; - 原请求还在运行:返回原来的
run_id,不会创建第二次运行。
3.3 其他 Header
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 成功格式
单个资源:
{
"data": {},
"meta": {"trace_id": "trace-uuid"}
}
列表资源:
{
"data": [],
"meta": {
"trace_id": "trace-uuid",
"next_cursor": "opaque-cursor",
"has_more": false
}
}
4.2 失败格式
{
"error": {
"code": "AGENT_PERMISSION_DENIED",
"message": "当前角色不能使用该 Agent",
"retryable": false,
"field_errors": []
},
"meta": {"trace_id": "trace-uuid"}
}
错误响应不会返回 SQL、堆栈、模型原文、供应商响应或密钥。
5. Agent 运行接口
5.1 创建运行
POST /api/v1/agent-runs
用途:把用户消息提交给已注册的 Agent。返回 202 只表示“已受理”,不代表业务已经完成。
典型状态:
queued → running → succeeded
→ failed
queued/running → cancel_requested → cancelled
5.2 查询运行
GET /api/v1/agent-runs/{run_id}
用途:获取最终权威结果。运行未完成时 result=null;成功后结果来自数据库持久化消息,不依赖 Worker 内存。
{
"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 取消运行
POST /api/v1/agent-runs/{run_id}/cancellations
Idempotency-Key: cancel-request-001
{"reason": "user_cancelled"}
返回 202 表示已提出取消请求。运行已经成功、已经失败或已进入最终提交阶段时返回
409 RUN_NOT_CANCELLABLE。
重复取消是幂等的:对同一运行再次调用取消,返回与首次相同的状态与受理时间,不会报错;
取消成功后原请求会在 request_idempotency 中记为 status=failed + error_code=RUN_CANCELLED,
表示这次请求已被终止(RUN_CANCELLED 不是 HTTP 响应码,客户端不需要处理它)。
6. SSE 接口怎么用
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 事件顺序
未完成运行:
start → 心跳 → 等待最终提交
正常完成:
start → tools(可选)→ delta(一个或多个)→ done
已完成运行重连:
start → replace → done
错误:
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. 会话接口
创建会话
POST /api/v1/conversations
Idempotency-Key: session-create-001
{"agent_type": "advisor"}
查询会话
GET /api/v1/conversations/{session_id}
查询消息
GET /api/v1/conversations/{session_id}/messages?limit=20&cursor=<cursor>
结束会话
POST /api/v1/conversations/{session_id}/closures
Idempotency-Key: session-close-001
转人工
POST /api/v1/conversations/{session_id}/handover-requests
Idempotency-Key: handover-001
Agent 可以发起转人工请求,但不能分配、接单、解决或关闭人工工单。
8. 记忆和知识引用接口
查询自己的记忆:
GET /api/v1/users/me/memory-profile
查询授权客户记忆:
GET /api/v1/customers/{customer_id}/memory-profile
解析知识引用:
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 |
配置发布流程:
draft → pending_review → approved → active
↘ rejected
active → superseded / rollback
管理更新必须使用 If-Match;创建、审核、激活、回滚和停用必须使用幂等键。审核节点不可跳过(单管理员部署下创建人可自审,不再要求复核人不同于创建人)。
10. 运维接口
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:
{
"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() 分支时,继续使用:
POST /api/v1/agent-runs
不需要新增 Controller,不允许复制一套 HTTP 运行接口。
B 类:业务 API 扩展
只有确实需要独立领域资源,例如模拟委托、风险处置、方案审核时,才新增业务接口。新增时必须:
- 使用
/api/v1前缀; - 由 Controller 调 Service,不能直接访问 Model;
- 使用统一 JWT、错误信封、幂等、追踪和审计;
- 清楚写出 Agent 能做什么、不能做什么;
- 同时更新 05 文档、OpenAPI、权限、审计和测试。
13. 前端或业务联调清单
[ ] 使用真实 agent_type,确认已注册
[ ] Authorization 使用 Bearer JWT
[ ] 所有写请求有唯一 Idempotency-Key
[ ] POST 受理后使用 run_id 查询或订阅 SSE
[ ] SSE 断线后重新请求同一个 run_id
[ ] 按 data/meta 读取成功信封
[ ] 按 error/meta 读取错误信封
[ ] 不把 202 当成业务完成
[ ] 不把 degraded 行情当成成交结果
[ ] 不把客户端传入的身份字段当成权限依据
14. 接口变更规则
新增或修改接口必须同时更新:
docs/05-接口文档.md;- OpenAPI 定义;
- 权限码、审计类别、幂等和错误码;
- 正常、越权、重复提交、故障恢复和安全测试;
- 对应业务文档的入口索引。
已有接口不能随意删除、改名、改字段类型或改变既有含义。发生破坏性变更时升级到新的主版本路径。
15. 权威文档关系
接口路径、Header、信封、错误、SSE、幂等 → docs/05-接口文档.md
Agent 内部执行契约 → docs/01-通用Agent平台开发设计.md
数据库表和字段 → docs/00-新数据库基线设计.md、docs/02-数据库建表设计.md
组员开发步骤 → docs/15、docs/16
出现冲突时,不要同时实现两种规则,先按权威源矩阵裁决。