2026-09-09 21:55:37 +08:00
|
|
|
|
# MVC+S Agent 平台接口文档
|
|
|
|
|
|
|
|
|
|
|
|
> 版本:v1.0
|
|
|
|
|
|
> 状态:评审稿
|
|
|
|
|
|
> 修订日期:2026-09-09
|
|
|
|
|
|
> 适用对象:前端、后端、测试、运维、业务 Agent 开发人员和编码 Agent
|
|
|
|
|
|
> 数据库约束:允许新增表和字段,禁止修改已有表名及已有字段定义
|
|
|
|
|
|
|
|
|
|
|
|
## 1. 文档目的
|
|
|
|
|
|
|
|
|
|
|
|
本文是 Agent 平台 HTTP 接口的唯一权威规范,定义运行面、会话面、平台管理面、SSE 传输、JWT 鉴权、统一信封、错误映射、幂等、分页、审计及业务扩展规则。
|
|
|
|
|
|
|
|
|
|
|
|
内部 Python 类型、Service Protocol、领域事件字段和数据库 DDL 不在本文重复定义。本文通过权威源矩阵引用已有设计,避免同一契约出现两个定义。
|
|
|
|
|
|
|
|
|
|
|
|
当前业务能力只针对场内基金模拟交易。场外基金运营使用独立业务表和接口,不得写入场内模拟交易表。
|
|
|
|
|
|
|
|
|
|
|
|
## 2. 权威源与边界
|
|
|
|
|
|
|
|
|
|
|
|
### 2.1 权威源矩阵
|
|
|
|
|
|
|
|
|
|
|
|
| 契约项 | 契约编号 | 唯一权威源 | 本文职责 |
|
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
| Agent 请求与上下文 | `AGENT-REQ-001` | 01 §5.2 | 定义 HTTP 投影和映射规则 |
|
|
|
|
|
|
| Agent 结果 | `AGENT-RESULT-001` | 01 §5.3 | 定义对外字段和序列化 |
|
|
|
|
|
|
| Service Protocol | `AGENT-SERVICE-001` | 01 §5.4 | 仅登记依赖方向 |
|
|
|
|
|
|
| Agent 错误体系 | `AGENT-ERROR-001` | 01 §5.5 | 定义 HTTP 状态码映射 |
|
|
|
|
|
|
| 领域事件 | `AGENT-EVENT-001` | 01 §5.4、02 §8.2 | 定义触发接口和触发时机 |
|
|
|
|
|
|
| SSE 事件载荷 | `AGENT-SSE-001` | 01 §12 | 定义 HTTP 传输方式 |
|
|
|
|
|
|
| Agent 执行顺序 | `AGENT-FLOW-001` | 01 §6、03 §3-§5 | 保证接口不绕过执行顺序 |
|
|
|
|
|
|
| 数据库实体 | `DB-BASE-001` | 00、02 | 仅定义资源与实体映射 |
|
|
|
|
|
|
| 请求幂等 | `DB-IDEMPOTENCY-001` | 02 §8.3 | 定义 HTTP 幂等语义 |
|
|
|
|
|
|
| Agent 运行 | `DB-RUN-001` | 02 中的 `agent_run` | 定义运行资源语义 |
|
|
|
|
|
|
| 领域状态机 | `DOMAIN-FLOW-001` | 对应业务流程文档 | 仅登记入口和 Agent 边界 |
|
|
|
|
|
|
|
|
|
|
|
|
### 2.2 冲突裁决
|
|
|
|
|
|
|
|
|
|
|
|
1. 本文的认证、路径、Header、HTTP 信封、分页、错误码映射和版本规则优先于业务接口文档。
|
|
|
|
|
|
2. 业务字段、业务状态机和业务校验以对应业务文档为准,本文不得复制后形成第二权威源。
|
|
|
|
|
|
3. 内部 DTO、Service Protocol、事件字段和数据库字段以矩阵指定文档为准。
|
|
|
|
|
|
4. 同一契约出现两处定义时,非权威定义必须删除并改为引用,不允许两份定义同时进入实现。
|
|
|
|
|
|
5. OpenAPI 是本文 HTTP 契约的机器可读投影。OpenAPI 与本文冲突时先停止发布并修正文档或生成逻辑。
|
|
|
|
|
|
|
|
|
|
|
|
### 2.3 MVC+S 依赖边界
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
Controller -> Application Service -> Domain Service/Repository -> Model
|
|
|
|
|
|
|
|
|
|
|
|
|
`-> View
|
|
|
|
|
|
|
|
|
|
|
|
AgentRunService -> RunRepository/IdempotencyService/RunDispatchPort
|
|
|
|
|
|
Run Worker -> AgentExecutor -> AgentFactory -> BaseAgent
|
|
|
|
|
|
Run Query Service -> RunRepository/ConversationRepository -> JSON/SSE View
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- Controller 不调用 Agent、ORM、Redis、Milvus、Neo4j 或消息中间件。
|
|
|
|
|
|
- Worker 不依赖 HTTP、Controller 或 SSE View。
|
|
|
|
|
|
- BaseAgent 返回传输无关的执行结果,不生成 HTTP 数据帧。
|
|
|
|
|
|
- JSON 查询和 SSE 订阅使用同一个 `RunQueryService` 权威投影。
|
|
|
|
|
|
- HTTP DTO 与内部 Agent DTO 分离,通过显式 Mapper 转换。
|
|
|
|
|
|
- Redis 只承担缓存和实时通知;MySQL 保存权威运行状态和最终结果。
|
|
|
|
|
|
|
|
|
|
|
|
## 3. 通用 HTTP 约定
|
|
|
|
|
|
|
|
|
|
|
|
### 3.1 基础地址与命名
|
|
|
|
|
|
|
|
|
|
|
|
- 业务接口前缀:`/api/v1`。
|
|
|
|
|
|
- 平台管理接口前缀:`/api/v1/admin`。
|
|
|
|
|
|
- 内部运维接口前缀:`/internal`。
|
|
|
|
|
|
- HTTP 路径使用 kebab-case,资源名称使用复数。
|
|
|
|
|
|
- `agent_type` 使用 snake_case,格式为 `[a-z][a-z0-9_]{1,31}`。
|
|
|
|
|
|
- 请求和响应编码统一为 UTF-8。
|
|
|
|
|
|
- JSON 请求使用 `Content-Type: application/json`。
|
|
|
|
|
|
|
|
|
|
|
|
### 3.2 通用请求 Header
|
|
|
|
|
|
|
|
|
|
|
|
| Header | 是否必填 | 规则 |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| `Authorization` | 是 | `Bearer <JWT>`,运维接口除外 |
|
|
|
|
|
|
| `Idempotency-Key` | 写接口按本文要求 | 8-64 个可打印 ASCII 字符 |
|
|
|
|
|
|
| `Content-Type` | 有请求体时是 | `application/json` |
|
|
|
|
|
|
| `Accept` | 否 | 默认 `application/json`;SSE 为 `text/event-stream` |
|
|
|
|
|
|
| `If-Match` | 管理面更新和状态转换是 | 使用服务端上次返回的 ETag |
|
|
|
|
|
|
| `traceparent` | 否 | 符合 W3C Trace Context;非法值由服务端替换 |
|
|
|
|
|
|
|
|
|
|
|
|
所有响应返回 `X-Trace-ID`。客户端不能通过请求正文或 `metadata` 指定用户、角色、客户范围、模型、工具、合规策略或追踪标识。
|
|
|
|
|
|
|
|
|
|
|
|
### 3.3 成功信封
|
|
|
|
|
|
|
|
|
|
|
|
单资源:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"data": {},
|
|
|
|
|
|
"meta": {
|
|
|
|
|
|
"trace_id": "trace-uuid"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
列表资源:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"data": [],
|
|
|
|
|
|
"meta": {
|
|
|
|
|
|
"trace_id": "trace-uuid",
|
|
|
|
|
|
"next_cursor": "opaque-cursor",
|
|
|
|
|
|
"has_more": true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
业务接口不得增加其他顶层字段。SSE、文件下载和运维健康检查不使用业务 JSON 信封。
|
|
|
|
|
|
|
|
|
|
|
|
### 3.4 错误信封
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"error": {
|
|
|
|
|
|
"code": "IDEMPOTENCY_CONFLICT",
|
|
|
|
|
|
"message": "幂等键已用于不同请求",
|
|
|
|
|
|
"retryable": false,
|
|
|
|
|
|
"field_errors": []
|
|
|
|
|
|
},
|
|
|
|
|
|
"meta": {
|
|
|
|
|
|
"trace_id": "trace-uuid"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`field_errors` 元素格式为:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"field": "metadata.ui_entry",
|
|
|
|
|
|
"reason": "格式不正确"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
错误响应禁止包含异常堆栈、SQL、模型原文、供应商响应、内部网络地址和密钥引用。
|
|
|
|
|
|
|
|
|
|
|
|
### 3.5 HTTP 状态码
|
|
|
|
|
|
|
|
|
|
|
|
| 状态码 | 使用场景 |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| `200` | 查询、幂等更新或同步操作成功 |
|
|
|
|
|
|
| `201` | 同步创建资源成功 |
|
|
|
|
|
|
| `202` | 异步任务或取消请求已受理 |
|
|
|
|
|
|
| `204` | 操作成功且无响应体 |
|
|
|
|
|
|
| `304` | 条件查询内容未变化 |
|
|
|
|
|
|
| `400` | JSON、Header 或查询参数无法解析 |
|
|
|
|
|
|
| `401` | JWT 缺失、无效、过期或已吊销 |
|
|
|
|
|
|
| `403` | 角色、权限、适当性或数据范围拒绝 |
|
|
|
|
|
|
| `404` | 资源不存在,或为防止越权枚举而隐藏资源 |
|
|
|
|
|
|
| `409` | 幂等冲突、版本冲突或非法状态转换 |
|
2026-09-11 14:08:55 +08:00
|
|
|
|
| `413` | 请求体或上传文件超过大小限制 |
|
2026-09-09 21:55:37 +08:00
|
|
|
|
| `422` | 已解析请求不满足字段或业务输入约束 |
|
|
|
|
|
|
| `429` | 频率、并发或配额限制 |
|
|
|
|
|
|
| `500` | 未分类内部错误 |
|
|
|
|
|
|
| `502` | 上游模型或工具返回无效响应 |
|
|
|
|
|
|
| `503` | 必需依赖不可用 |
|
|
|
|
|
|
| `504` | 上游调用超时且无法降级 |
|
|
|
|
|
|
|
|
|
|
|
|
### 3.6 核心错误码
|
|
|
|
|
|
|
|
|
|
|
|
| 错误码 | HTTP | 是否可重试 | 说明 |
|
|
|
|
|
|
|---|---:|---|---|
|
|
|
|
|
|
| `AGENT_INPUT_INVALID` | 422 | 否 | 请求内容不满足 Agent 输入约束 |
|
|
|
|
|
|
| `AGENT_PERMISSION_DENIED` | 403 | 否 | 角色、入口或数据范围不允许 |
|
|
|
|
|
|
| `AUTHENTICATION_REQUIRED` | 401 | 否 | Token 缺失或无效 |
|
|
|
|
|
|
| `SESSION_NOT_FOUND` | 404 | 否 | 会话不存在或已被资源隐藏 |
|
|
|
|
|
|
| `SESSION_NOT_ACCESSIBLE` | 404 | 否 | 当前身份不能访问会话 |
|
|
|
|
|
|
| `AGENT_TYPE_NOT_FOUND` | 404 | 否 | Agent 未注册 |
|
|
|
|
|
|
| `IDEMPOTENCY_CONFLICT` | 409 | 否 | 同一幂等键对应不同正文 |
|
|
|
|
|
|
| `RESOURCE_VERSION_CONFLICT` | 409 | 是 | `If-Match` 版本过期 |
|
|
|
|
|
|
| `RUN_NOT_FOUND` | 404 | 否 | 运行不存在或不可见 |
|
|
|
|
|
|
| `RUN_NOT_CANCELLABLE` | 409 | 否 | 运行已进入不可取消阶段 |
|
|
|
|
|
|
| `RUN_CANCELLED` | 409 | 否 | 运行已取消 |
|
|
|
|
|
|
| `INVALID_CURSOR` | 400 | 否 | 游标非法、过期或与过滤条件不符 |
|
|
|
|
|
|
| `RATE_LIMITED` | 429 | 是 | 频率、并发或配额限制 |
|
|
|
|
|
|
| `DEPENDENCY_UNAVAILABLE` | 503 | 是 | 必需依赖不可用 |
|
|
|
|
|
|
| `UPSTREAM_TIMEOUT` | 504 | 是 | 上游超过时间预算 |
|
|
|
|
|
|
| `AGENT_INTERNAL_ERROR` | 500 | 视情况 | 未分类内部错误 |
|
2026-09-12 15:35:12 +08:00
|
|
|
|
| `ACCOUNT_NOT_FOUND` | 404 | 否 | 账户不存在或状态非"正常" |
|
|
|
|
|
|
| `INSUFFICIENT_FUNDS` | 422 | 否 | 账户可用资金不足以扣减本次买入金额+费用 |
|
|
|
|
|
|
| `INSUFFICIENT_HOLDING` | 422 | 否 | 可用持仓不足以卖出本次数量 |
|
|
|
|
|
|
| `PRODUCT_NOT_TRADABLE` | 422 | 否 | 产品未上市或不在交易时段 |
|
|
|
|
|
|
| `FUND_QUOTE_UNAVAILABLE` | 503 | 是 | 行情快照缺失或过期(持仓比例上限校验依赖) |
|
|
|
|
|
|
| `HOLDING_RATIO_EXCEEDED` | 422 | 否 | 买入后超过产品持仓比例上限 |
|
|
|
|
|
|
| `SUITABILITY_MISMATCH` | 422 | 否 | 客户适当性等级与产品风险等级不兼容 |
|
|
|
|
|
|
| `ORDER_NOT_CANCELLABLE` | 409 | 否 | 委托已进入不可撤单阶段(首版直接成交后不可撤) |
|
|
|
|
|
|
| `ORDER_NOT_FOUND` | 404 | 否 | 委托不存在或不属于当前客户 |
|
2026-09-09 21:55:37 +08:00
|
|
|
|
|
|
|
|
|
|
### 3.7 数据格式
|
|
|
|
|
|
|
|
|
|
|
|
- 时间统一使用 UTC 的 RFC 3339,例如 `2026-09-09T08:30:00.000000Z`。
|
|
|
|
|
|
- 金额、价格、数量和置信度使用十进制字符串,禁止使用 JSON 浮点数。
|
|
|
|
|
|
- 数据库 `BIGINT` 对外序列化为字符串,避免前端整数精度丢失。
|
|
|
|
|
|
- 布尔值使用 `true/false`,不得使用 `0/1`。
|
|
|
|
|
|
- 空集合返回 `[]`,空对象返回 `{}`,无值返回 `null`。
|
|
|
|
|
|
- 未特别说明的文本字段去除首尾空白,禁止不可见控制字符。
|
|
|
|
|
|
|
|
|
|
|
|
### 3.8 游标分页
|
|
|
|
|
|
|
|
|
|
|
|
列表接口统一使用 `cursor` 和 `limit`。`limit` 默认 20、最小 1、最大 100。游标是不透明字符串,绑定用户、查询条件、排序字段和方向,客户端不得解析或修改。
|
|
|
|
|
|
|
|
|
|
|
|
默认排序为 `created_at DESC, id DESC`。默认不返回总条数;确需精确统计时使用对应领域的独立统计接口。
|
|
|
|
|
|
|
|
|
|
|
|
### 3.9 接口版本
|
|
|
|
|
|
|
|
|
|
|
|
SSE 实现补充:运行未完成时建立的连接,在观察到最终事务提交后分块输出 delta;
|
|
|
|
|
|
已完成运行的新连接使用 replace 返回完整结果。两者都是结果级恢复,不提供事件游标续传。
|
|
|
|
|
|
块长度通过 .env 的 SSE_CHUNK_CHARACTERS 控制,按 Unicode 字符切分,空结果也输出一个 delta。
|
|
|
|
|
|
|
|
|
|
|
|
- 兼容变更:新增可选字段、新增接口、新增可识别的错误码。
|
|
|
|
|
|
- 破坏性变更:删除或重命名字段、改变字段类型或含义、把可选字段改为必填、改变既有状态语义。
|
|
|
|
|
|
- 破坏性变更必须升级主路径,例如 `/api/v2`。
|
|
|
|
|
|
- 客户端必须对未知枚举值提供兜底行为。
|
|
|
|
|
|
- 废弃接口返回 `Deprecation`、`Sunset` 和替代接口链接。
|
|
|
|
|
|
- 废弃期不得少于两个发布周期且不得少于 90 天。
|
|
|
|
|
|
|
|
|
|
|
|
## 4. JWT 鉴权与授权
|
|
|
|
|
|
|
|
|
|
|
|
### 4.1 Token 要求
|
|
|
|
|
|
|
|
|
|
|
|
```http
|
|
|
|
|
|
Authorization: Bearer <access-token>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
JWT 至少包含 `sub`、`jti`、`iss`、`aud`、`iat`、`nbf` 和 `exp`。服务端必须验证签名算法白名单、签发方、受众、时间窗口和吊销状态。
|
|
|
|
|
|
|
|
|
|
|
|
JWT 只证明身份和基础授权范围。服务端每次请求重新加载有效用户状态、角色、权限、客户归属和数据范围;不得完全信任 Token 中的历史角色信息。
|
|
|
|
|
|
|
|
|
|
|
|
### 4.2 权限处理
|
|
|
|
|
|
|
|
|
|
|
|
实现补充(2026-09-09):统一 REST 入口在服务端设置 `portal=api`;Agent 声明须显式允许
|
|
|
|
|
|
此入口,metadata、Header 和 JWT 中的 portal/roles 均不作为授权依据。用户状态须为基线的
|
|
|
|
|
|
`正常`,角色状态为 `active`,角色分配在有效期内。每次请求直读 RBAC,撤权立即生效;
|
|
|
|
|
|
暂不缓存授权数据。运行同时要求 `agent:run` 权限及 AgentDefinition 的角色、入口交集。
|
|
|
|
|
|
权限范围按 permission_code 分别保存,禁止把另一权限的 `all` 扩散到全部资源。
|
|
|
|
|
|
管理面需 admin/super_admin 角色及第 19 节对应操作权限;拒绝追加审计。
|
|
|
|
|
|
|
|
|
|
|
|
- 客户只能访问自己的运行、会话、消息、反馈、转人工请求和安全记忆投影。
|
|
|
|
|
|
- 客服只能访问进入客服队列或分配给自己的会话和工单。
|
|
|
|
|
|
- 投顾只能访问 `sys_customer_assignment` 中归属自己的客户。
|
|
|
|
|
|
- 风控人员只能访问其角色和数据范围允许的风险记录。
|
|
|
|
|
|
- 普通管理员默认无权查看未脱敏会话正文和敏感审计详情。
|
|
|
|
|
|
- 越权资源统一返回 `404`,避免泄露资源是否存在;明确功能无权限时返回 `403`。
|
|
|
|
|
|
|
|
|
|
|
|
### 4.3 SSE 鉴权
|
|
|
|
|
|
|
|
|
|
|
|
SSE 订阅必须携带 Bearer Token。浏览器客户端使用支持自定义 Header 的 `fetch` 流式实现或合规 SSE 客户端,不使用无法设置 `Authorization` Header 的原生 `EventSource`。
|
|
|
|
|
|
|
|
|
|
|
|
连接建立时验证 JWT 和资源权限。Token 在连接期间过期不强制截断已经通过鉴权的短连接,但重连必须重新鉴权。
|
|
|
|
|
|
|
|
|
|
|
|
## 5. 幂等与并发
|
|
|
|
|
|
|
|
|
|
|
|
### 5.1 幂等范围
|
|
|
|
|
|
|
|
|
|
|
|
必须携带 `Idempotency-Key` 的接口包括:
|
|
|
|
|
|
|
|
|
|
|
|
- 创建 Agent 运行。
|
|
|
|
|
|
- 取消 Agent 运行。
|
|
|
|
|
|
- 创建或关闭会话。
|
|
|
|
|
|
- 创建转人工请求。
|
|
|
|
|
|
- 业务写接口。
|
|
|
|
|
|
- 配置审核、激活、回滚、停用和归档。
|
|
|
|
|
|
|
|
|
|
|
|
通用幂等范围为 `user_id + HTTP method + normalized_path + idempotency_key`。Agent 运行同时校验 `agent_type`。请求哈希使用规范化后的路径、查询参数和 JSON 正文计算,不包含 Authorization、trace Header 和传输时间。
|
|
|
|
|
|
|
|
|
|
|
|
### 5.2 重复请求
|
|
|
|
|
|
|
|
|
|
|
|
- 同一范围、同一键、同一哈希:返回原资源和当前状态。
|
|
|
|
|
|
- 同一范围、同一键、不同哈希:返回 `409 IDEMPOTENCY_CONFLICT`。
|
|
|
|
|
|
- 原异步任务仍在执行:返回原 `run_id`,不得创建第二个任务。
|
|
|
|
|
|
- Worker 租约超时:接管原 `run_id`,不得生成新的运行记录。
|
|
|
|
|
|
- 已完成请求:返回原结果资源地址。
|
|
|
|
|
|
|
|
|
|
|
|
### 5.3 乐观并发
|
|
|
|
|
|
|
|
|
|
|
|
管理面草稿更新和状态转换必须携带 `If-Match`。ETag 是服务端根据资源版本生成的不透明值。版本不一致返回 `409 RESOURCE_VERSION_CONFLICT`。
|
|
|
|
|
|
|
|
|
|
|
|
已审核、已激活、已停用或已归档的不可变版本不得原地编辑。
|
|
|
|
|
|
|
|
|
|
|
|
## 6. Agent 运行接口
|
|
|
|
|
|
|
|
|
|
|
|
### 6.1 运行状态
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
queued -> running -> succeeded
|
|
|
|
|
|
-> failed
|
|
|
|
|
|
queued/running -> cancel_requested -> cancelled
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- `queued`:受理事务已提交,等待 Worker。
|
|
|
|
|
|
- `running`:Worker 持有有效租约并正在执行。
|
|
|
|
|
|
- `cancel_requested`:已收到取消请求,等待 Worker 到达安全停止点。
|
|
|
|
|
|
- `succeeded`:最终消息、审计、幂等完成状态和 Outbox 已原子提交。
|
|
|
|
|
|
- `failed`:不可恢复错误及审计已经提交。
|
|
|
|
|
|
- `cancelled`:在最终事务开始前成功取消。
|
|
|
|
|
|
|
|
|
|
|
|
### 6.2 创建运行
|
|
|
|
|
|
|
|
|
|
|
|
```http
|
|
|
|
|
|
POST /api/v1/agent-runs
|
|
|
|
|
|
Authorization: Bearer <token>
|
|
|
|
|
|
Idempotency-Key: <key>
|
|
|
|
|
|
Content-Type: application/json
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
请求:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"agent_type": "customer_service",
|
|
|
|
|
|
"session_id": "session-uuid",
|
|
|
|
|
|
"message": "场内基金卖出后资金什么时候可用?",
|
|
|
|
|
|
"end_session": false,
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"locale": "zh-CN",
|
|
|
|
|
|
"client_version": "1.0.0",
|
|
|
|
|
|
"ui_entry": "customer_chat"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`message` 长度 1-8000,不能只包含空白。`metadata` 只允许 `locale`、`client_version` 和 `ui_entry`,当前 `locale` 只允许 `zh-CN`。会话必须属于当前用户或在员工数据范围内,Agent 必须已注册且允许当前角色和入口。
|
|
|
|
|
|
|
|
|
|
|
|
返回 `202 Accepted`:
|
|
|
|
|
|
|
|
|
|
|
|
```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",
|
|
|
|
|
|
"created_at": "2026-09-09T08:30:00.000000Z"
|
|
|
|
|
|
},
|
|
|
|
|
|
"meta": {
|
|
|
|
|
|
"trace_id": "trace-uuid"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
受理事务必须原子保存用户消息、`request_idempotency`、`agent_run` 和 `agent.run_requested` Outbox。事务失败时不得返回 `run_id`。
|
|
|
|
|
|
|
|
|
|
|
|
主要错误:`AGENT_INPUT_INVALID`、`AGENT_PERMISSION_DENIED`、`SESSION_NOT_FOUND`、`SESSION_NOT_ACCESSIBLE`、`AGENT_TYPE_NOT_FOUND`、`IDEMPOTENCY_CONFLICT`、`RATE_LIMITED`。
|
|
|
|
|
|
|
|
|
|
|
|
### 6.3 查询运行
|
|
|
|
|
|
|
|
|
|
|
|
```http
|
|
|
|
|
|
GET /api/v1/agent-runs/{run_id}
|
|
|
|
|
|
Authorization: Bearer <token>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
返回 `200 OK`:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"run_id": "run-uuid",
|
|
|
|
|
|
"trace_id": "trace-uuid",
|
|
|
|
|
|
"session_id": "session-uuid",
|
|
|
|
|
|
"agent_type": "customer_service",
|
|
|
|
|
|
"status": "succeeded",
|
|
|
|
|
|
"result_version": 1,
|
|
|
|
|
|
"result": {
|
|
|
|
|
|
"message_id": "12345",
|
|
|
|
|
|
"reply": "最终安全回复",
|
|
|
|
|
|
"intent": "faq",
|
|
|
|
|
|
"confidence": "0.9400",
|
|
|
|
|
|
"source_references": [],
|
|
|
|
|
|
"suggestions": [],
|
|
|
|
|
|
"transfer_required": false,
|
|
|
|
|
|
"transfer_reason": null,
|
|
|
|
|
|
"degraded": false,
|
|
|
|
|
|
"degradation_reason": null
|
|
|
|
|
|
},
|
|
|
|
|
|
"error": null,
|
|
|
|
|
|
"created_at": "2026-09-09T08:30:00.000000Z",
|
|
|
|
|
|
"started_at": "2026-09-09T08:30:01.000000Z",
|
|
|
|
|
|
"completed_at": "2026-09-09T08:30:04.000000Z"
|
|
|
|
|
|
},
|
|
|
|
|
|
"meta": {
|
|
|
|
|
|
"trace_id": "trace-uuid"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- 未完成时 `result` 和 `error` 均为 `null`。
|
|
|
|
|
|
- 成功时 `result` 来自已持久化助手消息,不能从 Worker 内存拼装。
|
|
|
|
|
|
- 失败时 `result` 为 `null`,`error` 只包含安全错误码、消息和可重试标志。
|
|
|
|
|
|
- 取消时不创建助手成功消息。
|
|
|
|
|
|
- `result_version` 用于客户端重连去重,MVP 成功结果固定为 1。
|
|
|
|
|
|
|
|
|
|
|
|
主要错误:`RUN_NOT_FOUND`、`AGENT_PERMISSION_DENIED`。
|
|
|
|
|
|
|
|
|
|
|
|
### 6.4 订阅运行结果
|
|
|
|
|
|
|
|
|
|
|
|
```http
|
|
|
|
|
|
GET /api/v1/agent-runs/{run_id}/events
|
|
|
|
|
|
Authorization: Bearer <token>
|
|
|
|
|
|
Accept: text/event-stream
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
成功响应 Header:
|
|
|
|
|
|
|
|
|
|
|
|
```http
|
|
|
|
|
|
Content-Type: text/event-stream; charset=utf-8
|
|
|
|
|
|
Cache-Control: no-cache, no-transform
|
|
|
|
|
|
X-Accel-Buffering: no
|
|
|
|
|
|
X-Trace-ID: trace-uuid
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
传输顺序:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
运行未完成:start -> 注释心跳 -> 等待
|
|
|
|
|
|
正常完成:start -> tools(可选) -> delta(一个或多个) -> done -> 关闭
|
|
|
|
|
|
安全替换:start -> replace -> done -> 关闭
|
|
|
|
|
|
不可恢复:start -> error -> 关闭
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
帧示例:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
event: start
|
|
|
|
|
|
data: {"trace_id":"trace-uuid","session_id":"session-uuid"}
|
|
|
|
|
|
|
|
|
|
|
|
: keepalive
|
|
|
|
|
|
|
|
|
|
|
|
event: delta
|
|
|
|
|
|
data: {"trace_id":"trace-uuid","text":"安全文本片段"}
|
|
|
|
|
|
|
|
|
|
|
|
event: done
|
|
|
|
|
|
data: {"trace_id":"trace-uuid","intent":"faq","confidence":"0.9400","sources":[],"suggestions":[]}
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
事件名称和业务载荷以 `AGENT-SSE-001` 为准。注释心跳不是业务事件。`tools`、`delta`、`replace` 和 `done` 只能在最终持久化事务成功后发送。
|
|
|
|
|
|
|
|
|
|
|
|
MVP 采用结果级恢复,不实现事件级游标续传。`Last-Event-ID` 可以被忽略。重连时服务端根据 `agent_run` 和 `conversation_message` 重新发送完整最终结果,客户端按 `run_id + result_version` 去重。
|
|
|
|
|
|
|
|
|
|
|
|
若错误发生在响应 Header 发送前,返回统一 JSON 错误;发送后使用 `error` 事件并关闭连接。`done` 和 `error` 都是终止事件,之后不得继续发送数据。
|
|
|
|
|
|
|
|
|
|
|
|
主要错误:`RUN_NOT_FOUND`、`AGENT_PERMISSION_DENIED`、`SSE_NOT_ACCEPTABLE`。
|
|
|
|
|
|
|
|
|
|
|
|
### 6.5 取消运行
|
|
|
|
|
|
|
|
|
|
|
|
```http
|
|
|
|
|
|
POST /api/v1/agent-runs/{run_id}/cancellations
|
|
|
|
|
|
Authorization: Bearer <token>
|
|
|
|
|
|
Idempotency-Key: <key>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
请求:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"reason": "user_cancelled"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
返回 `202 Accepted`:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"run_id": "run-uuid",
|
|
|
|
|
|
"status": "cancel_requested",
|
|
|
|
|
|
"cancel_requested_at": "2026-09-09T08:30:02.000000Z"
|
|
|
|
|
|
},
|
|
|
|
|
|
"meta": {
|
|
|
|
|
|
"trace_id": "trace-uuid"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
重复取消返回同一状态。运行已成功、失败或进入最终提交事务时返回 `409 RUN_NOT_CANCELLABLE`。取消成功后,`request_idempotency` 使用 `failed + RUN_CANCELLED` 表示原请求已终止,不扩展其既有状态枚举。
|
|
|
|
|
|
|
|
|
|
|
|
## 7. 会话、消息、反馈与转人工接口
|
|
|
|
|
|
|
|
|
|
|
|
### 7.1 创建会话
|
|
|
|
|
|
|
|
|
|
|
|
```http
|
|
|
|
|
|
POST /api/v1/conversations
|
|
|
|
|
|
Authorization: Bearer <token>
|
|
|
|
|
|
Idempotency-Key: <key>
|
|
|
|
|
|
Content-Type: application/json
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
请求:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"agent_type": "customer_service"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
返回 `201 Created`:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"session_id": "session-uuid",
|
|
|
|
|
|
"agent_type": "customer_service",
|
|
|
|
|
|
"portal": "customer_chat",
|
|
|
|
|
|
"status": "active",
|
|
|
|
|
|
"clarification_round": 0,
|
|
|
|
|
|
"created_at": "2026-09-09T08:20:00.000000Z"
|
|
|
|
|
|
},
|
|
|
|
|
|
"meta": {"trace_id": "trace-uuid"}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
服务端根据请求入口确定 `portal`,通过 AgentFactory 校验 Agent、角色和入口。客户端不得提交 `user_id`、`portal` 或澄清轮次。
|
|
|
|
|
|
|
|
|
|
|
|
### 7.2 查询会话
|
|
|
|
|
|
|
|
|
|
|
|
```http
|
|
|
|
|
|
GET /api/v1/conversations/{session_id}
|
|
|
|
|
|
Authorization: Bearer <token>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
返回会话的 `session_id`、`agent_type`、`portal`、`status`、`clarification_round`、`message_count`、`last_active_at`、`started_at` 和 `ended_at`。权限不满足时按资源隐藏规则返回 `404`。
|
|
|
|
|
|
|
|
|
|
|
|
### 7.3 查询会话消息
|
|
|
|
|
|
|
|
|
|
|
|
```http
|
|
|
|
|
|
GET /api/v1/conversations/{session_id}/messages?limit=20&cursor=<cursor>
|
|
|
|
|
|
Authorization: Bearer <token>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
返回 `conversation_message` 的对客投影:`message_id`、`role`、`content`、`intent`、`confidence`、`source_references`、`created_at` 和脱敏后的 `tool_calls`。不返回内部 Prompt、模型原文、权限判断详情、未脱敏工具参数或其他客户数据。
|
|
|
|
|
|
|
|
|
|
|
|
用户消息的 `intent`、`confidence` 和 `source_references` 按现有流程可以为空;助手消息必须来自已经完成合规和持久化的结果。
|
|
|
|
|
|
|
|
|
|
|
|
### 7.4 结束会话
|
|
|
|
|
|
|
|
|
|
|
|
```http
|
|
|
|
|
|
POST /api/v1/conversations/{session_id}/closures
|
|
|
|
|
|
Authorization: Bearer <token>
|
|
|
|
|
|
Idempotency-Key: <key>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
请求体可为空。服务端条件更新会话状态为 `ended`,触发记忆提取事件的判定由公共底座完成。已结束会话重复调用返回当前会话状态;已转人工会话不得通过此接口绕过客服状态机。
|
|
|
|
|
|
|
|
|
|
|
|
### 7.5 创建用户反馈
|
|
|
|
|
|
|
|
|
|
|
|
```http
|
|
|
|
|
|
POST /api/v1/conversation-messages/{message_id}/feedback
|
|
|
|
|
|
Authorization: Bearer <token>
|
|
|
|
|
|
Idempotency-Key: <key>
|
|
|
|
|
|
Content-Type: application/json
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
请求:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"rating": -1,
|
|
|
|
|
|
"feedback_type": "inaccurate",
|
|
|
|
|
|
"feedback_content": "回答没有说明到账条件"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`rating` 只能为 `1` 或 `-1`,正文最多 1000 字符。消息必须属于当前用户;同一用户对同一消息只能有一条有效反馈,重复提交相同内容返回原反馈,内容不同返回 `409 FEEDBACK_ALREADY_EXISTS`。
|
|
|
|
|
|
|
|
|
|
|
|
### 7.6 用户申请转人工
|
|
|
|
|
|
|
|
|
|
|
|
```http
|
|
|
|
|
|
POST /api/v1/conversations/{session_id}/handover-requests
|
|
|
|
|
|
Authorization: Bearer <token>
|
|
|
|
|
|
Idempotency-Key: <key>
|
|
|
|
|
|
Content-Type: application/json
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
请求:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"reason_code": "user_requested",
|
|
|
|
|
|
"reason_detail": "希望人工解释"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
客户端只能提交 `reason_code=user_requested` 和有限长度的补充说明,不能提交 `priority`、`assigned_to`、置信度、会话摘要或来源引用。返回 `202 Accepted`:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"handover_id": "ticket-uuid",
|
|
|
|
|
|
"session_id": "session-uuid",
|
|
|
|
|
|
"status": "pending",
|
|
|
|
|
|
"created_at": "2026-09-09T08:35:00.000000Z"
|
|
|
|
|
|
},
|
|
|
|
|
|
"meta": {"trace_id": "trace-uuid"}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
该接口只负责创建通用转人工请求。客服工单分配、接单、解决和关闭由客服领域 Service 处理,不允许 Controller 直接写 `svc_handover_ticket`。
|
|
|
|
|
|
|
|
|
|
|
|
### 7.7 查询转人工请求
|
|
|
|
|
|
|
|
|
|
|
|
```http
|
|
|
|
|
|
GET /api/v1/handover-requests/{handover_id}
|
|
|
|
|
|
Authorization: Bearer <token>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
客户只能查看自己的 `status`、创建时间和安全提示;客服坐席可按权限查看队列和分配信息。工单状态 `pending -> assigned -> processing -> resolved -> closed`,取消规则和字段以 02 §7.2 及客服业务文档为准。
|
|
|
|
|
|
|
|
|
|
|
|
## 8. 记忆与知识引用接口
|
|
|
|
|
|
|
|
|
|
|
|
### 8.1 查询记忆画像
|
|
|
|
|
|
|
|
|
|
|
|
```http
|
|
|
|
|
|
GET /api/v1/users/me/memory-profile
|
|
|
|
|
|
GET /api/v1/customers/{customer_id}/memory-profile
|
|
|
|
|
|
Authorization: Bearer <token>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
客户只能查询自己的画像;投顾、客服或授权员工必须通过客户归属和数据范围校验。返回经过字段策略过滤的当前画像、有效期、来源摘要和版本,不返回模型原始推理、Prompt、其他客户信息或未解决冲突的内部详情。
|
|
|
|
|
|
|
|
|
|
|
|
记忆提取没有客户端写接口。`memory.extraction_requested` 由 `complete_run()` 与最终结果在同一事务写入 Outbox,再由 Worker 调用内部 `MemoryService`。更正、遗忘和监管删除属于独立隐私流程,本接口不临时复用 `memory_conflict`。
|
|
|
|
|
|
|
2026-09-12 11:15:24 +08:00
|
|
|
|
### 8.2 解析知识引用
|
2026-09-09 21:55:37 +08:00
|
|
|
|
|
|
|
|
|
|
```http
|
|
|
|
|
|
GET /api/v1/knowledge-references/{reference_token}
|
|
|
|
|
|
Authorization: Bearer <token>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`reference_token` 为服务端签发的短期不透明令牌,绑定用户、知识版本和过期时间。返回允许展示的标题、版本、生效日期、来源机构和摘要;客户端不能通过修改令牌枚举 `doc_id` 或读取草稿知识。
|
|
|
|
|
|
|
|
|
|
|
|
知识上传、审核、发布、失效和 Milvus 同步由知识业务文档定义。Agent 只能使用工具返回的 `SourceReference`,不得自行构造引用。
|
|
|
|
|
|
|
2026-09-11 14:37:20 +08:00
|
|
|
|
### 8.3 知识库文档管理
|
|
|
|
|
|
|
|
|
|
|
|
```http
|
|
|
|
|
|
POST /api/v1/knowledge/upload
|
|
|
|
|
|
GET /api/v1/knowledge/list
|
|
|
|
|
|
DELETE /api/v1/knowledge/{knowledge_id}
|
|
|
|
|
|
Authorization: Bearer <token>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
需求来源:老师《需求文档-修改版》Phase 1 验收第 7 条「知识库管理接口可正常上传/查询/删除文档」与 F1.2。与 §8.2 的 `/knowledge-references/{reference_token}`(引用解析,K001)是**两个不同的资源面**:本节是**管理面文档生命周期**,凭 `knowledge:manage` 操作,不解析令牌、不按客户归属过滤;K001 是**读侧引用解析**,凭 `knowledge:reference:read`,返回可用展示的引用片段。两者不复用同一 prefix 语义。
|
|
|
|
|
|
|
|
|
|
|
|
三个端点均需 `knowledge:manage`(现只授给管理员角色)。`created_by` 取自认证上下文,**调用方不能指定**。三个端点共用 router 级限流;底座没有匿名路径,认证先于限流。
|
|
|
|
|
|
|
|
|
|
|
|
**上传**
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"filename": "理财产品销售管理办法.md",
|
|
|
|
|
|
"content_base64": "<文档字节的标准 base64>",
|
|
|
|
|
|
"knowledge_type": "policy"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- `filename` 只能是文件名本身(不得含路径与分隔符)、长度 ≤ 256。
|
|
|
|
|
|
- `knowledge_type` 取 `faq` / `product` / `policy` 之一,**由服务端映射集合**,调用方不得指定集合名;非法取值返回 `422`。
|
|
|
|
|
|
- `content_base64` 严格解码;非法 base64 返回 `422 AGENT_INPUT_INVALID`。
|
|
|
|
|
|
- 老师原文写 `multipart/form-data`,**一期实现为 JSON**(理由见下)。
|
|
|
|
|
|
|
|
|
|
|
|
成功 `201` 返回本次产生的知识行(一份文档切多块即为多行):
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"knowledge_ids": [109, 110],
|
|
|
|
|
|
"filename": "理财产品销售管理办法.md",
|
|
|
|
|
|
"knowledge_type": "policy",
|
|
|
|
|
|
"created_by": "9003",
|
|
|
|
|
|
"chunk_count": 2
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**查询列表**
|
|
|
|
|
|
|
|
|
|
|
|
```http
|
|
|
|
|
|
GET /api/v1/knowledge/list?limit=20&offset=0&knowledge_type=policy
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`limit` 默认 20、上限 100;`offset` ≥ 0;`knowledge_type` 可选。默认只列**未过期**行。响应只给 `content_preview`(前 200 字符)与 `content_length`,**不返回整篇 `content_text`** —— 正文是检索侧素材,管理面列表不得变成"直读知识正文"的旁路:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"items": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"knowledge_id": 109,
|
|
|
|
|
|
"knowledge_type": "policy",
|
|
|
|
|
|
"title": "### 第一条 目的…",
|
|
|
|
|
|
"source_file": "理财产品销售管理办法.md",
|
|
|
|
|
|
"collection": "fin_policy_collection",
|
|
|
|
|
|
"version": "v1",
|
|
|
|
|
|
"status": "active",
|
|
|
|
|
|
"review_status": "published",
|
|
|
|
|
|
"created_by": 9003,
|
|
|
|
|
|
"tags": {"chunk_index": 2, "heading_path": ["理财产品销售管理办法"]},
|
|
|
|
|
|
"content_preview": "### 第一条 目的…",
|
|
|
|
|
|
"content_length": 457
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
"count": 1
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**删除**
|
|
|
|
|
|
|
|
|
|
|
|
```http
|
|
|
|
|
|
DELETE /api/v1/knowledge/{knowledge_id}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
删除**不改物理行**,而是标记 `fin_knowledge_meta.status='expired'` 并**在同一事务**投递
|
|
|
|
|
|
`knowledge.vector_delete_requested`(`aggregate_id` 与 payload 均为 `str(knowledge_id)`),
|
|
|
|
|
|
由知识向量 Worker 消费后删除 Milvus 向量;本地文件随后尽力归档,**归档失败不回滚已提交的删除**(只记日志)。
|
|
|
|
|
|
|
|
|
|
|
|
成功 `200`:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"knowledge_id": 109,
|
|
|
|
|
|
"status": "expired",
|
|
|
|
|
|
"vector_delete_event": "knowledge.vector_delete_requested"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
不存在的 `knowledge_id` 返回 `404`,**不静默成功**。
|
|
|
|
|
|
|
|
|
|
|
|
**两处口径说明(避免误读)**
|
|
|
|
|
|
|
|
|
|
|
|
1. **请求体形状**:老师原文为 `multipart/form-data`,一期用 JSON + `content_base64`。
|
|
|
|
|
|
理由是底座其余写接口(admin 配置面、conversations、agent-runs)**全是 JSON**,错误信封(§3.4)
|
|
|
|
|
|
与 `ValidationAgentError` 的 422 口径都建立在 JSON body 上;一期单独引 multipart 等于新开一条
|
|
|
|
|
|
无测试覆盖的上传失败路径。后续接前端表单时新增一个 multipart 端点复用同一 Service 即可
|
|
|
|
|
|
(Service 只吃 `filename` + `content: bytes`,与传输形状无关)。
|
|
|
|
|
|
2. **删除的 404 错误码**:现实现复用 `SESSION_NOT_FOUND`(底座通用资源不存在码),语义上偏会话面;
|
|
|
|
|
|
若后续要求精确语义,应新增 `KNOWLEDGE_NOT_FOUND`。**本文按现状登记**,不做美化。
|
|
|
|
|
|
|
|
|
|
|
|
### 8.4 公共只读工具索引(Agent 面)
|
|
|
|
|
|
|
|
|
|
|
|
工具**不是 HTTP 接口**,但它们决定 Agent 能读什么,且必须由 `AgentFactory` 注入的
|
|
|
|
|
|
`ToolExecutor` 统一执行(鉴权、数据范围、审计不得绕过)。登记口径:
|
|
|
|
|
|
工具名 / 必需权限 / 只读 / 越权行为。
|
|
|
|
|
|
|
|
|
|
|
|
| 工具名 | 必需权限 | 只读 | 越权与失败行为 |
|
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
| `check_suitability` | `suitability:read` | 是 | 除管理员外不得查他人风险测评;测评过期/缺失一律拒绝(失败关闭) |
|
2026-09-14 20:36:00 +08:00
|
|
|
|
| `query_fund_quote` | `fund:quote:read` | 是 | 只读行情,不得改写为成交、委托或持仓语义。超时 **15s**(`EastmoneyAdapterFactory` 单代码最坏预算 8.2s × 1.8) |
|
|
|
|
|
|
| `search_knowledge` | `knowledge:reference:read` | 是 | **知识检索的正式工具名**。集合名由服务端按意图映射,调用方不得指定;维度不符失败关闭。超时 **10s** |
|
|
|
|
|
|
| `query_knowledge` | `knowledge:query` | 是 | ⚠️ **`search_knowledge` 的别名**(`bootstrap.py:321-328`,同一 handler,为兼容一期发布配置与旧客户端保留),允许角色仅 `visitor`/`customer` |
|
2026-09-11 14:37:20 +08:00
|
|
|
|
| `query_customer_profile` | `memory:read:self`(查他人为 `memory:read:customer`) | 是 | 越范围按"不存在"处理且不泄露存在性;无当前画像**抛错**而不返回空画像 |
|
|
|
|
|
|
|
2026-09-14 20:36:00 +08:00
|
|
|
|
> ⚠️ **本表此前只列 4 个、且把 `query_knowledge` 当主名(2026-09-14 修正)**。
|
|
|
|
|
|
> 两者**都要发布**:知识类意图缺 `search_knowledge`,登录客户与投顾那侧缺权限;
|
|
|
|
|
|
> 缺 `query_knowledge`,**访客令牌只有 `knowledge:query`**、一问即失败。
|
|
|
|
|
|
>
|
|
|
|
|
|
> 其余业务线工具按各自 Agent 白名单注册(**不在本公共表内**,权限码也不同):
|
|
|
|
|
|
> 风控 3 个(`search_risk_alerts`/`get_risk_overview`/`get_alert_evidence`,权限 `risk:alert:read`)、
|
|
|
|
|
|
> 投顾 6 个(`investment-goal:*` 等)、NL2SQL 1 个(`query_financial_data`,权限 `financial:nl2sql:read`)、
|
|
|
|
|
|
> 探针 1 个(权限 `probe:read`)。全部注册点在 `app/service/agent/bootstrap.py::get_agent_factory()`。
|
|
|
|
|
|
|
2026-09-11 14:37:20 +08:00
|
|
|
|
**工具的白名单是两段式**:代码里的 `allowed_tools` 是**上限**,实际可用范围还要与
|
|
|
|
|
|
当前 `active` 的 `config_release` 中 `namespace=agent_tools`、`config_key=<agent_type>:<intent>`
|
|
|
|
|
|
发布的工具白名单**取交集**;**缺发布配置时交集为空、工具失败关闭**。
|
|
|
|
|
|
|
|
|
|
|
|
`query_customer_profile` 的返回值遵循 §8.1 的字段策略:只投影画像属性白名单
|
|
|
|
|
|
(`investor_type` / `investment_horizon` / `trading_frequency` / `preferred_asset_class` /
|
|
|
|
|
|
`risk_tags` / `customer_tier` / `behavior_score` / `total_asset` /
|
|
|
|
|
|
`assessment_valid_until` / `assessment_expired`),
|
|
|
|
|
|
**不返回 `real_name`、`birth_date`、`mobile_masked`、`trade_account` 等 PII**;
|
|
|
|
|
|
`assessment_expired` 按**当前时间**重算,不采信快照里的历史布尔值。
|
|
|
|
|
|
|
2026-09-12 11:15:24 +08:00
|
|
|
|
### 8.5 客服画像候选(Phase 2)
|
|
|
|
|
|
|
|
|
|
|
|
> 编号说明:本节为客服二期新增,**不占用 §8.1–§8.4 既有号段**,以避免破坏 `AGENTS.md`、`docs/09`、`docs/14` 对「§8.3 知识库管理三端点」「§8.4 公共只读工具索引」的既有引用。
|
|
|
|
|
|
|
|
|
|
|
|
客服 Agent 不读取或直接修改正式画像。已登录用户明确陈述长期偏好、约束或目标时,系统
|
|
|
|
|
|
异步生成 `memory_unit.status='candidate'` 候选;访客不会生成候选。候选不进入客服召回,
|
|
|
|
|
|
必须经过用户确认和管理员审核后才能晋升为 `active`。
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
GET /api/v1/users/me/memory-candidates
|
|
|
|
|
|
POST /api/v1/users/me/memory-candidates/{candidate_id}/decisions
|
|
|
|
|
|
GET /api/v1/admin/customer-profile-candidates
|
|
|
|
|
|
POST /api/v1/admin/customer-profile-candidates/{candidate_id}/reviews
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
用户确认请求体为 `{ "decision": "confirmed" | "rejected" }`,需要
|
|
|
|
|
|
`memory:candidate:confirm`;确认只将状态改为 `verified`。管理员审核请求体复用
|
|
|
|
|
|
`ReviewPayload`,需要管理员角色和 `memory:candidate:review`;`approved` 会在事务内
|
|
|
|
|
|
处理同键旧记忆冲突并将候选改为 `active`,`rejected` 将其改为 `rejected`。接口只返回
|
|
|
|
|
|
结构化候选值,不返回对话证据摘录、密码、验证码或其他原始敏感内容。
|
|
|
|
|
|
|
2026-09-09 21:55:37 +08:00
|
|
|
|
## 9. 平台管理面接口
|
|
|
|
|
|
|
|
|
|
|
|
管理面只操作草稿、审核、激活、停用、回滚和归档流程,不提供绕过版本控制的通用 CRUD。所有更新和状态转换都需要 `If-Match`;创建、审核、激活、回滚和停用需要 `Idempotency-Key`。
|
|
|
|
|
|
|
|
|
|
|
|
### 9.1 配置发布
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
POST /api/v1/admin/config-releases
|
|
|
|
|
|
GET /api/v1/admin/config-releases
|
|
|
|
|
|
GET /api/v1/admin/config-releases/{release_id}
|
|
|
|
|
|
POST /api/v1/admin/config-releases/{release_id}/validations
|
|
|
|
|
|
POST /api/v1/admin/config-releases/{release_id}/reviews
|
|
|
|
|
|
POST /api/v1/admin/config-releases/{release_id}/activations
|
|
|
|
|
|
POST /api/v1/admin/config-releases/{release_id}/rollbacks
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
创建草稿请求至少包含 `release_no`、`title`、`change_summary`。验证接口执行 Schema、权限上限、引用完整性、路由备用端点、Prompt、模板和意图配置校验;未通过验证不得审核或激活。
|
|
|
|
|
|
|
|
|
|
|
|
审核请求:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"decision": "approved",
|
2026-09-10 15:55:54 +08:00
|
|
|
|
"comment": "已完成审核"
|
2026-09-09 21:55:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-09-10 15:55:54 +08:00
|
|
|
|
创建人可以审核自己创建的版本(允许自审,不要求审核人不同于创建人);审核这一状态机节点不可跳过,未提交审核的草稿不能直接审核或激活。自审时 `reviewer_id` 留空——库约束 `chk_config_release_separation`(`reviewer_id IS NULL OR reviewer_id <> created_by`)属于数据库基线,不能把创建人写进 `reviewer_id`;自审人身份由 `interaction_audit` 的审核记录承担。他人复核仍照常写入 `reviewer_id`。激活必须是审核通过的完整批次,且原子切换当前生效版本。回滚不修改历史记录,而是创建新的发布记录并填写 `rollback_of_release_id`。
|
2026-09-09 21:55:37 +08:00
|
|
|
|
|
|
|
|
|
|
### 9.2 平台配置项
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
POST /api/v1/admin/config-releases/{release_id}/platform-config-items
|
|
|
|
|
|
GET /api/v1/admin/config-releases/{release_id}/platform-config-items
|
|
|
|
|
|
PUT /api/v1/admin/config-releases/{release_id}/platform-config-items/{item_id}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
配置项必须按 `namespace + item_key` 唯一。`value_json` 必须通过对应 Pydantic Schema 校验,不接受任意权限扩大、模型端点、工具白名单或客户范围参数。
|
|
|
|
|
|
|
|
|
|
|
|
### 9.3 模型端点
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
POST /api/v1/admin/model-endpoints
|
|
|
|
|
|
GET /api/v1/admin/model-endpoints
|
|
|
|
|
|
GET /api/v1/admin/model-endpoints/{endpoint_id}
|
|
|
|
|
|
PUT /api/v1/admin/model-endpoints/{endpoint_id}
|
|
|
|
|
|
POST /api/v1/admin/model-endpoints/{endpoint_id}/reviews
|
|
|
|
|
|
POST /api/v1/admin/model-endpoints/{endpoint_id}/activations
|
|
|
|
|
|
POST /api/v1/admin/model-endpoints/{endpoint_id}/disablements
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
请求只允许提交 `endpoint_code`、供应商标识、能力、敏感级别、超时、成本和 `secret_ref`。不得提交或返回 API 明文密钥。健康检查失败可以熔断流量,但不能自动把数据库配置改成 `active`。
|
|
|
|
|
|
|
|
|
|
|
|
### 9.4 模型路由
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
POST /api/v1/admin/config-releases/{release_id}/model-routing-rules
|
|
|
|
|
|
GET /api/v1/admin/config-releases/{release_id}/model-routing-rules
|
|
|
|
|
|
PUT /api/v1/admin/config-releases/{release_id}/model-routing-rules/{rule_id}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
路由规则请求使用:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"rule_code": "customer_service_answer",
|
|
|
|
|
|
"agent_type": "customer_service",
|
|
|
|
|
|
"task_type": "answer_generation",
|
|
|
|
|
|
"model_policy": "balanced",
|
|
|
|
|
|
"primary_endpoint_id": "1001",
|
|
|
|
|
|
"fallbacks": [
|
|
|
|
|
|
{"endpoint_id": "1002", "fallback_order": 1},
|
|
|
|
|
|
{"endpoint_id": "1003", "fallback_order": 2}
|
|
|
|
|
|
],
|
|
|
|
|
|
"max_attempts": 3,
|
|
|
|
|
|
"latency_budget_ms": 15000
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
HTTP 不接受或返回旧的 `fallback_endpoint_ids` JSON 字段。备用链以 `model_routing_fallback` 为权威关系表,服务端校验主端点不在备用链、顺序不重复、端点存在且总尝试数不超过 3。
|
|
|
|
|
|
|
|
|
|
|
|
### 9.5 Prompt、意图和回复模板
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
POST /api/v1/admin/prompt-templates
|
|
|
|
|
|
GET /api/v1/admin/prompt-templates
|
|
|
|
|
|
GET /api/v1/admin/prompt-templates/{prompt_id}
|
|
|
|
|
|
|
|
|
|
|
|
POST /api/v1/admin/agent-intent-configs
|
|
|
|
|
|
GET /api/v1/admin/agent-intent-configs
|
|
|
|
|
|
PUT /api/v1/admin/agent-intent-configs/{config_id}
|
|
|
|
|
|
|
|
|
|
|
|
POST /api/v1/admin/reply-templates
|
|
|
|
|
|
GET /api/v1/admin/reply-templates
|
|
|
|
|
|
PUT /api/v1/admin/reply-templates/{template_id}
|
|
|
|
|
|
|
|
|
|
|
|
POST /api/v1/admin/negative-word-rules
|
|
|
|
|
|
GET /api/v1/admin/negative-word-rules
|
|
|
|
|
|
PUT /api/v1/admin/negative-word-rules/{rule_id}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
这些资源使用各自的草稿、审核、激活和归档状态。由于现有表没有统一 `release_id` 外键,接口不得声称它们与 `config_release` 自动原子绑定;发布服务必须在激活前校验引用版本一致。
|
|
|
|
|
|
|
|
|
|
|
|
模板和意图配置的同一业务组合最多一个 `active` 版本,数据库生成列唯一约束是最终保障。已激活版本不可原地编辑。
|
|
|
|
|
|
|
|
|
|
|
|
### 9.6 审计查询
|
|
|
|
|
|
|
|
|
|
|
|
```http
|
|
|
|
|
|
GET /api/v1/admin/audit-records
|
|
|
|
|
|
Authorization: Bearer <token>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
支持 `trace_id`、`run_id`、用户、Agent 类型、事件类型、结果、时间范围和游标过滤。接口只读,不提供修改和删除。`audit:read-sensitive` 才能查看未脱敏详情。
|
|
|
|
|
|
|
2026-09-11 16:11:30 +08:00
|
|
|
|
### 9.7 客服转人工队列(只读)
|
|
|
|
|
|
|
|
|
|
|
|
```http
|
|
|
|
|
|
GET /api/v1/admin/customer-service/handover-tickets?limit=20&cursor={ticket_id}
|
|
|
|
|
|
GET /api/v1/admin/customer-service/handover-tickets/{ticket_no}
|
|
|
|
|
|
Authorization: Bearer <token>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
两个接口均要求 `admin` 或 `super_admin` 角色和 `handover:read` 权限。列表仅返回工单号、
|
|
|
|
|
|
会话标识、来源 Agent、优先级、转接原因、状态和时间;详情才追加二次脱敏后的转接原因、
|
|
|
|
|
|
会话摘要、意图置信度和受控知识来源。接口不得返回客户标识、原始会话正文、账户数据、
|
|
|
|
|
|
联系方式、工单分配信息或处理结论。当前仅支持查看,不支持接单、分配、处理、解决或关闭。
|
|
|
|
|
|
|
2026-09-09 21:55:37 +08:00
|
|
|
|
## 10. SSE 与领域事件映射
|
|
|
|
|
|
|
|
|
|
|
|
### 10.1 SSE 事件
|
|
|
|
|
|
|
|
|
|
|
|
事件名称和载荷以 `AGENT-SSE-001` 为准:`start`、`tools`、`delta`、`replace`、`done`、`error`。本文只定义传输和发送时机,不重新定义字段。
|
|
|
|
|
|
|
|
|
|
|
|
### 10.2 Outbox 事件
|
|
|
|
|
|
|
|
|
|
|
|
| 事件 | 产生位置 | 消费者 |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| `agent.run_requested` | 创建运行受理事务 | Agent Worker |
|
|
|
|
|
|
| `agent.run_cancel_requested` | 取消状态事务 | Agent Worker |
|
|
|
|
|
|
| `conversation.completed` | `complete_run()` 最终事务 | 会话投影、通知 |
|
2026-09-11 16:11:30 +08:00
|
|
|
|
| `conversation.transfer_requested` | `complete_run()` 或客户转人工申请事务 | 客服转人工消费者;写入 `handover.queue_ready` 审计,不改变工单 `pending` 状态 |
|
2026-09-09 21:55:37 +08:00
|
|
|
|
| `memory.extraction_requested` | `complete_run()` 最终事务 | 记忆提取 Worker |
|
|
|
|
|
|
| `agent.run_failed` | 失败状态事务 | 监控和告警 |
|
|
|
|
|
|
| `config.release_activated` | 配置激活事务 | 缓存失效、实例刷新 |
|
|
|
|
|
|
| `config.release_rolled_back` | 回滚激活事务 | 缓存失效、监控 |
|
|
|
|
|
|
| `model.endpoint_disabled` | 端点停用事务 | 模型路由缓存 |
|
|
|
|
|
|
|
|
|
|
|
|
Outbox 消费者按 `event_id` 幂等。失败事件保留并重试,超过阈值进入死信状态并告警;不能删除未完成事件。
|
|
|
|
|
|
|
|
|
|
|
|
## 11. 审计分界
|
|
|
|
|
|
|
|
|
|
|
|
### 11.1 必须写 `interaction_audit`
|
|
|
|
|
|
|
|
|
|
|
|
1. 委托、成交、资金、持仓和风险预警处置等受监管业务状态变化。
|
|
|
|
|
|
2. 配置发布、审核、激活、停用和回滚。
|
|
|
|
|
|
3. 越权、适当性、跨客户和其他权限决策拒绝。
|
|
|
|
|
|
4. 工单全生命周期、方案审核和人工处置。
|
|
|
|
|
|
5. Agent 运行的输入摘要、结果、模型/Prompt 版本、合规动作、降级原因和耗时。
|
|
|
|
|
|
|
|
|
|
|
|
`request_idempotency` 的完成状态随 `complete_run()` 与业务结果同一事务写审计。
|
|
|
|
|
|
|
|
|
|
|
|
### 11.2 只写运行日志或指标
|
|
|
|
|
|
|
|
|
|
|
|
心跳和租约续期、幂等抢占重试、缓存读写与失效、SSE 游标与断流、健康检查和普通限流拒绝只写运行日志或指标。若限流同时判定为攻击、越权或安全策略命中,则另写安全审计。
|
|
|
|
|
|
|
|
|
|
|
|
## 12. 业务接口索引与 Agent 边界
|
|
|
|
|
|
|
|
|
|
|
|
公共接口文档只登记入口、归属和 Agent 禁止边界,不预先定义领域载荷和状态机。
|
|
|
|
|
|
|
|
|
|
|
|
| 业务域 | 接口入口 | 归属文档 | Agent 边界 |
|
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
| 客服工单 | `/api/v1/customer-service/handover-tickets/**` | 客服业务文档 | 可生成摘要和转人工请求,不分配、接单、解决或关闭工单 |
|
2026-09-14 00:17:46 +08:00
|
|
|
|
| 投顾方案 | `/api/v1/advisor/**`(编号见 §19 的 `AD` 段与 `A041`–`A046`) | 本文 §19 | 只生成分析草案,不代替投顾审核发布 |
|
2026-09-09 21:55:37 +08:00
|
|
|
|
| 场内模拟交易 | `/api/v1/sim-orders/**` | 交易业务文档 | 只读查询,不创建、确认或撤销委托 |
|
2026-09-11 14:08:55 +08:00
|
|
|
|
| 风控扫描 | `/api/v1/risk/**` | 风控业务文档 | 可解释规则结果,不启动人工处置 |
|
|
|
|
|
|
| 风险预警 | `/api/v1/risk/**` | 风控业务文档 | 只读分析,不确认、升级或关闭预警 |
|
2026-09-14 20:36:00 +08:00
|
|
|
|
| 场外基金运营 | `/api/v1/offsite-fund/**`、`/api`(operation_router) | `docs/28`、场外工作流文档 | **已落地**(2026-09-14 更正:原写"不属于当前系统"已过期)。**独立业务表,不读写场内交易表**;沿用旧式信封 `{code,message,data}` 与 `page`/`page_size` 分页 |
|
2026-09-09 21:55:37 +08:00
|
|
|
|
|
2026-09-11 14:08:55 +08:00
|
|
|
|
风控模块落地时把扫描、预警、证据、通知和日报收在同一个 Controller 下,入口为
|
|
|
|
|
|
`/api/v1/risk/**`(早期规划写作 `/risk-scans/**`、`/risk-alerts/**`,以本节的实际入口为准)。
|
|
|
|
|
|
具体端点清单、权限与字段映射由风控业务文档登记,见第 19 节末尾。
|
|
|
|
|
|
|
2026-09-09 21:55:37 +08:00
|
|
|
|
B 类业务写接口必须复用 JWT、响应信封、错误码、幂等、统一 `RequestContext`、事务和审计规则。Controller 只路由、校验、映射和调用 Service;禁止直接访问 Model。
|
|
|
|
|
|
|
|
|
|
|
|
## 13. A 类和 B 类扩展
|
|
|
|
|
|
|
|
|
|
|
|
### 13.1 A 类:Agent 能力扩展
|
|
|
|
|
|
|
|
|
|
|
|
组员新增 Agent、意图或 `handle()` 分支时,继续使用 `/api/v1/agent-runs`,不新增 Controller。允许修改 Agent 实现、启动注册和测试数据;不得复制或覆盖 BaseAgent 的鉴权、记忆、模型、工具、合规、审计、SSE 和事件逻辑。
|
|
|
|
|
|
|
|
|
|
|
|
### 13.2 B 类:业务 API 扩展
|
|
|
|
|
|
|
|
|
|
|
|
新增模拟下单、方案审核或预警处置等领域接口时,新增该业务域的 Controller、Application Service、DTO、Repository 和测试。B 类接口必须登记:资源路径、权限码、幂等范围、事务边界、审计类别、归属文档和 Agent 只读边界。
|
|
|
|
|
|
|
|
|
|
|
|
### 13.3 组员 Agent 交付清单
|
|
|
|
|
|
|
|
|
|
|
|
1. `<agent_type>_agent.py`:继承 `BaseAgent`,只实现 `handle()`。
|
|
|
|
|
|
2. `AgentDefinition`:角色、入口、意图、工具、合规策略、模型策略和记忆视图。
|
|
|
|
|
|
3. 启动注册新增一行,不修改工厂实现。
|
|
|
|
|
|
4. 正常、低置信、工具失败、模型失败和越权用例。
|
|
|
|
|
|
5. 每个来源引用必须来自工具或知识服务返回值。
|
|
|
|
|
|
6. 不覆盖 `run_stream()`、执行模板、记忆沉淀或事件广播方法。
|
|
|
|
|
|
|
|
|
|
|
|
## 14. 内部契约索引
|
|
|
|
|
|
|
|
|
|
|
|
本文不复制以下签名。实现和测试必须引用指定权威源:
|
|
|
|
|
|
|
|
|
|
|
|
| 契约 | 权威源 |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| `AgentRequest`、`RequestContext`、配置 DTO | 01 §5.2,`AGENT-REQ-001` |
|
|
|
|
|
|
| `IntentResult`、`SourceReference`、`AgentResult` | 01 §5.3,`AGENT-RESULT-001` |
|
|
|
|
|
|
| `ModelGateway`、`MemoryService`、`ComplianceService`、`AuditService` | 01 §5.4,`AGENT-SERVICE-001` |
|
|
|
|
|
|
| `AgentError` 分类 | 01 §5.5,`AGENT-ERROR-001` |
|
|
|
|
|
|
| `BaseAgent` 执行顺序和 `complete_run()` | 01 §6,`AGENT-FLOW-001` |
|
|
|
|
|
|
| `DomainEvent` 和 Outbox | 01 §5.3/§6.2、02 §8.2,`AGENT-EVENT-001` |
|
|
|
|
|
|
| SSE 事件载荷 | 01 §12,`AGENT-SSE-001` |
|
|
|
|
|
|
| 表和字段 | 00、02,`DB-BASE-001` |
|
|
|
|
|
|
|
|
|
|
|
|
HTTP 层使用 DTO Mapper 投影内部对象。Controller 和 View 不得直接返回 ORM Model 或内部异常对象。
|
|
|
|
|
|
|
|
|
|
|
|
## 15. 运维与安全接口
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
GET /internal/health/live
|
|
|
|
|
|
GET /internal/health/ready
|
|
|
|
|
|
GET /internal/metrics
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
这些接口不使用业务 JSON 信封,不暴露数据库地址、模型密钥、Token、完整客户资料或异常堆栈,只允许内网和监控系统访问。JWT 签发、刷新、注销由统一身份认证模块负责,Agent 平台不重复实现。
|
|
|
|
|
|
|
2026-09-11 20:43:21 +08:00
|
|
|
|
> **实现现状(2026-09-11 更新)**:上面这句原本是"平台不做签发"的依据,实际落地时确认了
|
|
|
|
|
|
> 平台**必须**有一个登录入口 —— 否则客户 / 员工 / 管理员三种身份无法区分(各 Agent 的
|
|
|
|
|
|
> `allowed_roles` 早就分开了,缺的只是"怎么证明你是谁")。因此平台现在提供
|
|
|
|
|
|
> **`POST /api/v1/auth/tokens`**(账号密码换访问令牌,见 §19 的 A034),
|
|
|
|
|
|
> 这是本文档 §11 那句的**唯一例外**。
|
|
|
|
|
|
>
|
|
|
|
|
|
> 边界仍然守住:平台**只做登录**,**刷新与注销仍归统一身份认证模块**
|
|
|
|
|
|
> (`app/core/security.py` 已留好 `RevocationStore` 协议,接上 Redis 即可)。
|
|
|
|
|
|
> 令牌里只放 `sub`,角色 / 权限 / 数据范围一律由 `IdentityService` 每次请求查库解析,
|
|
|
|
|
|
> 所以权限变更立即生效,不受令牌有效期影响。
|
|
|
|
|
|
|
2026-09-09 21:55:37 +08:00
|
|
|
|
## 16. 验收与契约测试
|
|
|
|
|
|
|
|
|
|
|
|
### 16.1 HTTP 通用测试
|
|
|
|
|
|
|
|
|
|
|
|
- 所有 JSON 接口使用统一成功或错误信封。
|
|
|
|
|
|
- JWT 缺失、过期、签发方错误、受众错误和吊销均被拒绝。
|
|
|
|
|
|
- 同键同正文返回原资源,同键不同正文返回 `409`。
|
|
|
|
|
|
- 跨客户查询返回 `404`,且响应不泄露资源是否存在。
|
|
|
|
|
|
- 分页游标绑定过滤条件,非法游标返回 `400 INVALID_CURSOR`。
|
|
|
|
|
|
- Controller 不直接导入 ORM、数据库客户端、Agent 实现或消息客户端。
|
|
|
|
|
|
|
|
|
|
|
|
### 16.2 Agent 运行测试
|
|
|
|
|
|
|
|
|
|
|
|
- 受理事务同时创建用户消息、幂等记录、`agent_run` 和 `agent.run_requested` Outbox。
|
|
|
|
|
|
- Worker 租约过期后使用同一 `run_id` 安全接管。
|
|
|
|
|
|
- 最终消息、审计、幂等完成状态和领域事件在同一事务提交。
|
|
|
|
|
|
- `memory.extraction_requested` 不得由请求线程在提交后直接调用。
|
|
|
|
|
|
- SSE 仅在最终事务成功后发送 `tools`、`delta`、`replace` 和 `done`。
|
|
|
|
|
|
- 断流重连可以根据 `run_id` 获取完整结果;MVP 不声称支持事件级续传。
|
|
|
|
|
|
|
|
|
|
|
|
### 16.3 管理面测试
|
|
|
|
|
|
|
2026-09-10 15:55:54 +08:00
|
|
|
|
- 创建人可以审核自己创建的配置;审核状态机不可跳过。
|
2026-09-09 21:55:37 +08:00
|
|
|
|
- 未通过校验、审核或版本检查的配置不能激活。
|
|
|
|
|
|
- 已激活版本不可原地编辑。
|
|
|
|
|
|
- 模板和意图组合最多一个 `active` 版本。
|
|
|
|
|
|
- 备用端点必须存在、顺序唯一,且主端点不能出现在备用链。
|
|
|
|
|
|
- 激活、回滚、停用和权限拒绝都写审计。
|
|
|
|
|
|
|
|
|
|
|
|
### 16.4 静态检查
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
检查 OpenAPI 与本文路径、方法和必填 Header 一致
|
|
|
|
|
|
检查每个接口都有权限码、幂等说明、错误码和审计分类
|
|
|
|
|
|
检查 Controller 不直接访问 Model
|
|
|
|
|
|
检查 Agent 子类未覆盖公共执行模板
|
|
|
|
|
|
检查契约编号唯一且引用存在
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 17. 实施顺序
|
|
|
|
|
|
|
|
|
|
|
|
1. 创建 `agent_run` 表及 Alembic 迁移,核对 00 基线和 02 新表,不修改已有表名和已有字段定义。
|
|
|
|
|
|
2. 实现 HTTP DTO、统一信封、JWT 依赖、错误映射、Trace 中间件和游标工具。
|
|
|
|
|
|
3. 实现 `AgentRunApplicationService`、幂等、运行租约和 `RunDispatchPort`。
|
|
|
|
|
|
4. 将 Agent 执行从 HTTP/SSE 中解耦为 Worker 可调用的 `AgentExecutor`。
|
|
|
|
|
|
5. 实现 JSON 状态查询和结果级恢复 SSE。
|
|
|
|
|
|
6. 实现会话、消息、反馈和公共转人工入口。
|
|
|
|
|
|
7. 实现管理面配置、模型路由、Prompt、意图、模板和禁止表达接口。
|
|
|
|
|
|
8. 生成 OpenAPI 并执行契约测试、安全测试、故障恢复和迁移演练。
|
|
|
|
|
|
|
|
|
|
|
|
## 18. 完成标准
|
|
|
|
|
|
|
|
|
|
|
|
- 所有公共 HTTP 接口具有稳定路径、请求、响应、权限、错误、幂等和审计定义。
|
|
|
|
|
|
- `run_id` 可在服务重启后查询、恢复和获取完整结果。
|
|
|
|
|
|
- JSON 和 SSE 不共享传输实现,但共享运行查询投影和权限检查。
|
|
|
|
|
|
- A 类 Agent 不需要修改公共 Controller、Factory、BaseAgent 或 SSE View。
|
|
|
|
|
|
- B 类业务接口遵守统一 MVC+S 骨架和 Agent 只读边界。
|
|
|
|
|
|
- 内部契约和数据库结构没有在本文形成第二权威定义。
|
2026-09-14 20:36:00 +08:00
|
|
|
|
- 数据库迁移不修改已有表名和已有字段定义(当前现库 **90 张表 = 89 张业务表 + `alembic_version`**,
|
|
|
|
|
|
分域为**场内 51 + 场外/推广 17 + 投顾 21**。
|
|
|
|
|
|
本行原写"49 张"→ 2026-09-10 更正为"52 张"→ **2026-09-14 按 `AGENTS.md` 口径更正为 89 张业务表**。
|
|
|
|
|
|
表的**数量**会随新增表变化,因此这里只保留"不修改已有表名与字段定义"这一硬约束;
|
|
|
|
|
|
数量口径以 `python tools/audit_schema.py` 的实测输出为准)。
|
2026-09-09 21:55:37 +08:00
|
|
|
|
|
|
|
|
|
|
## 19. 接口总目录
|
|
|
|
|
|
|
|
|
|
|
|
下表是 v1 接口的实现清单。除特别标注外,成功响应均使用第 3.3 节信封,错误响应均使用第 3.4 节信封。
|
|
|
|
|
|
|
|
|
|
|
|
| 编号 | 方法与路径 | 权限 | 幂等 | 成功状态 | 审计 |
|
|
|
|
|
|
|---|---|---|---|---|---|
|
|
|
|
|
|
| R001 | `POST /api/v1/agent-runs` | `agent:run` | 必须 | `202` | Agent 运行 |
|
|
|
|
|
|
| R002 | `GET /api/v1/agent-runs/{run_id}` | 资源所有者/数据范围 | 否 | `200` | 否 |
|
|
|
|
|
|
| R003 | `GET /api/v1/agent-runs/{run_id}/events` | 资源所有者/数据范围 | 否 | SSE | 否 |
|
|
|
|
|
|
| R004 | `POST /api/v1/agent-runs/{run_id}/cancellations` | `agent:cancel` | 必须 | `202` | 取消操作 |
|
|
|
|
|
|
| C001 | `POST /api/v1/conversations` | `conversation:create` | 必须 | `201` | 会话创建 |
|
|
|
|
|
|
| C002 | `GET /api/v1/conversations/{session_id}` | 会话所有者/数据范围 | 否 | `200` | 否 |
|
|
|
|
|
|
| C003 | `GET /api/v1/conversations/{session_id}/messages` | 会话所有者/数据范围 | 否 | `200` | 否 |
|
|
|
|
|
|
| C004 | `POST /api/v1/conversations/{session_id}/closures` | `conversation:close` | 必须 | `200` | 会话结束 |
|
|
|
|
|
|
| C005 | `POST /api/v1/conversations/{session_id}/handover-requests` | `handover:create` | 必须 | `202` | 转人工请求 |
|
|
|
|
|
|
| C006 | `GET /api/v1/handover-requests/{handover_id}` | 客户/客服数据范围 | 否 | `200` | 否 |
|
|
|
|
|
|
| C007 | `POST /api/v1/conversation-messages/{message_id}/feedback` | `conversation:feedback` | 必须 | `201` | 反馈创建 |
|
|
|
|
|
|
| M001 | `GET /api/v1/users/me/memory-profile` | `memory:read:self` | 否 | `200` | 敏感访问 |
|
|
|
|
|
|
| M002 | `GET /api/v1/customers/{customer_id}/memory-profile` | `memory:read:customer` | 否 | `200` | 敏感访问 |
|
2026-09-11 17:47:06 +08:00
|
|
|
|
| M003 | `GET /api/v1/users/me/memory-candidates` | `memory:read:self` | 否 | `200` | 候选查询 |
|
|
|
|
|
|
| M004 | `POST /api/v1/users/me/memory-candidates/{candidate_id}/decisions` | `memory:candidate:confirm` | 必须 | `200` | 用户确认/拒绝 |
|
2026-09-12 11:53:35 +08:00
|
|
|
|
| A039 | `GET /api/v1/admin/customer-profile-candidates` | `memory:candidate:review` | 否 | `200` | 候选审核列表 |
|
|
|
|
|
|
| A040 | `POST /api/v1/admin/customer-profile-candidates/{candidate_id}/reviews` | `memory:candidate:review` | 必须 | `200` | 候选审核 |
|
2026-09-14 00:17:46 +08:00
|
|
|
|
| A041 | `POST /api/v1/admin/advisor/asset-allocation-backtests` | `asset-allocation:backtest`(+`admin`) | 必须 | `201` | 配置回测 |
|
|
|
|
|
|
| A042 | `GET /api/v1/admin/advisor/profile-tags` | `profile-governance:read`(+`admin`) | 否 | `200` | 敏感访问 |
|
|
|
|
|
|
| A043 | `GET /api/v1/admin/advisor/profile-drift-reviews` | `profile-governance:read`(+`admin`) | 否 | `200` | 敏感访问 |
|
|
|
|
|
|
| A044 | `POST /api/v1/admin/advisor/profile-drift-reviews/{review_id}/reviews` | `profile-governance:review`(+`admin`) | 必须 | `200` | 画像漂移复核 |
|
|
|
|
|
|
| A045 | `POST /api/v1/admin/advisor/recommendations/{content_id}/reviews` | `product-recommendation:review`(+`admin`) | 必须 | `200` | 推荐方案审核 |
|
|
|
|
|
|
| A046 | `POST /api/v1/admin/advisor/recommendations/{content_id}/publications` | `product-recommendation:publish`(+`admin`) | 必须 | `200` | 推荐方案发布 |
|
|
|
|
|
|
| A047 | `GET /api/v1/admin/advisor/pending-contents` | `product-recommendation:review`(+`admin`) | 否 | `200` | 否 |
|
2026-09-14 01:19:22 +08:00
|
|
|
|
| A048 | `GET /api/v1/admin/config-releases/{release_id}/platform-config-items/{item_id}` | `config:read`(+`admin`) | 否 | `200` | 否 |
|
|
|
|
|
|
| A049 | `GET /api/v1/admin/config-releases/{release_id}/model-routing-rules/{rule_id}` | `config:read`(+`admin`) | 否 | `200` | 否 |
|
2026-09-14 00:17:46 +08:00
|
|
|
|
| AD001 | `POST /api/v1/advisor/investment-goals` | `investment-goal:write:self` / `:customer` | 必须 | `201` | 投资目标创建 |
|
|
|
|
|
|
| AD002 | `GET /api/v1/advisor/investment-goals/current` | `investment-goal:read:self` | 否 | `200` | 否 |
|
|
|
|
|
|
| AD003 | `GET /api/v1/advisor/customers/{customer_id}/investment-goals/current` | `investment-goal:read:self` / `:customer` | 否 | `200` | 否 |
|
|
|
|
|
|
| AD004 | `POST /api/v1/advisor/investment-goals/{goal_no}/confirmations` | `investment-goal:confirm:self` / `:customer` | 必须 | `200` | 目标确认 |
|
|
|
|
|
|
| AD005 | `GET /api/v1/advisor/investment-goals/{goal_no}/goal-book` | `investment-goal:read:self` / `:customer` | 否 | `200` | 否 |
|
|
|
|
|
|
| AD006 | `POST /api/v1/advisor/investment-goals/{goal_no}/goal-book/reviews` | `investment-goal:review`(+`admin`) | 必须 | `200` | 方案书审核 |
|
|
|
|
|
|
| AD007 | `POST /api/v1/advisor/investment-goals/{goal_no}/goal-book/publications` | `investment-goal:publish`(+`admin`) | 必须 | `200` | 方案书发布 |
|
|
|
|
|
|
| AD008 | `POST /api/v1/advisor/portfolio-analysis` | `portfolio-analysis:read:self` | 否 | `200` | 否 |
|
|
|
|
|
|
| AD009 | `POST /api/v1/advisor/asset-allocation` | `asset-allocation:generate:self` | 否 | `200` | 否 |
|
|
|
|
|
|
| AD010 | `POST /api/v1/advisor/recommendations` | `product-recommendation:generate:self` | 必须 | `200` | 否 |
|
|
|
|
|
|
| AD011 | `GET /api/v1/advisor/recommendations/published` | `product-recommendation:read:self` | 否 | `200` | 否 |
|
2026-09-09 21:55:37 +08:00
|
|
|
|
| K001 | `GET /api/v1/knowledge-references/{reference_token}` | `knowledge:reference:read` | 否 | `200` | 否 |
|
2026-09-11 14:37:20 +08:00
|
|
|
|
| K002 | `POST /api/v1/knowledge/upload` | `knowledge:manage` | 否 | `201` | 知识文档变更 |
|
|
|
|
|
|
| K003 | `GET /api/v1/knowledge/list` | `knowledge:manage` | 否 | `200` | 否 |
|
|
|
|
|
|
| K004 | `DELETE /api/v1/knowledge/{knowledge_id}` | `knowledge:manage` | 否 | `200` | 知识文档失效 |
|
2026-09-09 21:55:37 +08:00
|
|
|
|
| A001 | `POST /api/v1/admin/config-releases` | `config:write` | 必须 | `201` | 配置草稿 |
|
|
|
|
|
|
| A002 | `GET /api/v1/admin/config-releases` | `config:read` | 否 | `200` | 否 |
|
|
|
|
|
|
| A003 | `GET /api/v1/admin/config-releases/{release_id}` | `config:read` | 否 | `200` | 否 |
|
|
|
|
|
|
| A004 | `POST /api/v1/admin/config-releases/{release_id}/validations` | `config:write` | 必须 | `200` | 配置校验 |
|
|
|
|
|
|
| A005 | `POST /api/v1/admin/config-releases/{release_id}/reviews` | `config:review` | 必须 | `200` | 配置审核 |
|
|
|
|
|
|
| A006 | `POST /api/v1/admin/config-releases/{release_id}/activations` | `config:activate` | 必须 | `200` | 配置激活 |
|
|
|
|
|
|
| A007 | `POST /api/v1/admin/config-releases/{release_id}/rollbacks` | `config:activate` | 必须 | `201` | 配置回滚 |
|
|
|
|
|
|
| A008 | `POST /api/v1/admin/config-releases/{release_id}/platform-config-items` | `config:write` | 必须 | `201` | 配置变更 |
|
|
|
|
|
|
| A009 | `GET /api/v1/admin/config-releases/{release_id}/platform-config-items` | `config:read` | 否 | `200` | 否 |
|
|
|
|
|
|
| A010 | `PUT /api/v1/admin/config-releases/{release_id}/platform-config-items/{item_id}` | `config:write` | 必须 | `200` | 配置变更 |
|
|
|
|
|
|
| A011 | `POST /api/v1/admin/model-endpoints` | `model-endpoint:manage` | 必须 | `201` | 端点创建 |
|
|
|
|
|
|
| A012 | `GET /api/v1/admin/model-endpoints` | `config:read` | 否 | `200` | 否 |
|
|
|
|
|
|
| A013 | `GET /api/v1/admin/model-endpoints/{endpoint_id}` | `config:read` | 否 | `200` | 否 |
|
|
|
|
|
|
| A014 | `PUT /api/v1/admin/model-endpoints/{endpoint_id}` | `model-endpoint:manage` | 必须 | `200` | 端点变更 |
|
|
|
|
|
|
| A015 | `POST /api/v1/admin/model-endpoints/{endpoint_id}/reviews` | `config:review` | 必须 | `200` | 端点审核 |
|
|
|
|
|
|
| A016 | `POST /api/v1/admin/model-endpoints/{endpoint_id}/activations` | `model-endpoint:manage` | 必须 | `200` | 端点激活 |
|
|
|
|
|
|
| A017 | `POST /api/v1/admin/model-endpoints/{endpoint_id}/disablements` | `model-endpoint:manage` | 必须 | `200` | 端点停用 |
|
|
|
|
|
|
| A018 | `POST /api/v1/admin/config-releases/{release_id}/model-routing-rules` | `config:write` | 必须 | `201` | 路由变更 |
|
|
|
|
|
|
| A019 | `GET /api/v1/admin/config-releases/{release_id}/model-routing-rules` | `config:read` | 否 | `200` | 否 |
|
|
|
|
|
|
| A020 | `PUT /api/v1/admin/config-releases/{release_id}/model-routing-rules/{rule_id}` | `config:write` | 必须 | `200` | 路由变更 |
|
|
|
|
|
|
| A021 | `POST /api/v1/admin/prompt-templates` | `config:write` | 必须 | `201` | Prompt 变更 |
|
|
|
|
|
|
| A022 | `GET /api/v1/admin/prompt-templates` | `config:read` | 否 | `200` | 否 |
|
|
|
|
|
|
| A023 | `GET /api/v1/admin/prompt-templates/{prompt_id}` | `config:read` | 否 | `200` | 否 |
|
|
|
|
|
|
| A024 | `POST /api/v1/admin/agent-intent-configs` | `config:write` | 必须 | `201` | 意图配置 |
|
|
|
|
|
|
| A025 | `GET /api/v1/admin/agent-intent-configs` | `config:read` | 否 | `200` | 否 |
|
|
|
|
|
|
| A026 | `PUT /api/v1/admin/agent-intent-configs/{config_id}` | `config:write` | 必须 | `200` | 意图配置 |
|
|
|
|
|
|
| A027 | `POST /api/v1/admin/reply-templates` | `config:write` | 必须 | `201` | 回复模板 |
|
|
|
|
|
|
| A028 | `GET /api/v1/admin/reply-templates` | `config:read` | 否 | `200` | 否 |
|
|
|
|
|
|
| A029 | `PUT /api/v1/admin/reply-templates/{template_id}` | `config:write` | 必须 | `200` | 回复模板 |
|
|
|
|
|
|
| A030 | `POST /api/v1/admin/negative-word-rules` | `config:write` | 必须 | `201` | 禁止表达 |
|
|
|
|
|
|
| A031 | `GET /api/v1/admin/negative-word-rules` | `config:read` | 否 | `200` | 否 |
|
|
|
|
|
|
| A032 | `PUT /api/v1/admin/negative-word-rules/{rule_id}` | `config:write` | 必须 | `200` | 禁止表达 |
|
|
|
|
|
|
| A033 | `GET /api/v1/admin/audit-records` | `audit:read` | 否 | `200` | 否 |
|
2026-09-11 20:43:21 +08:00
|
|
|
|
| A034 | `POST /api/v1/auth/tokens` | 公开(登录前无身份) | 否 | `200` | 登录成功/失败 |
|
2026-09-11 21:22:56 +08:00
|
|
|
|
| A035 | `GET /api/v1/admin/roles` | `audit:read` | 否 | `200` | 否 |
|
|
|
|
|
|
| A036 | `GET /api/v1/admin/roles/{role_code}` | `audit:read` | 否 | `200` | 否 |
|
|
|
|
|
|
| A037 | `GET /api/v1/admin/roles/{role_code}/permissions` | `audit:read` | 否 | `200` | 否 |
|
|
|
|
|
|
| A038 | `GET /api/v1/admin/users/{user_id}/roles` | `audit:read` | 否 | `200` | 否 |
|
2026-09-09 21:55:37 +08:00
|
|
|
|
| O001 | `GET /internal/health/live` | 内网 | 否 | `200` | 否 |
|
|
|
|
|
|
| O002 | `GET /internal/health/ready` | 内网 | 否 | `200/503` | 否 |
|
|
|
|
|
|
| O003 | `GET /internal/metrics` | 监控系统 | 否 | `200` | 否 |
|
2026-09-12 15:35:12 +08:00
|
|
|
|
| T001 | `GET /api/v1/users/me/account/dashboard` | `account:read:self`(已登录) | 否 | `200` | 账户看板(汇总账户/资金/持仓/盈亏) |
|
|
|
|
|
|
| T002 | `POST /api/v1/users/me/orders` | `trade:order:create`(已登录) | 必须 | `201` | 委托提交(市价立即全额成交) |
|
|
|
|
|
|
| T003 | `GET /api/v1/users/me/orders` | `trade:order:read`(已登录) | 否 | `200` | 委托列表(按 id 倒序游标分页) |
|
|
|
|
|
|
| T004 | `GET /api/v1/users/me/orders/{order_no}` | `trade:order:read`(资源所有者) | 否 | `200` | 委托详情 |
|
|
|
|
|
|
| T005 | `POST /api/v1/users/me/orders/{order_no}/cancellations` | `trade:order:cancel`(资源所有者) | 必须 | `200` | 撤单(首版仅"已接受/已部分成交"可撤) |
|
|
|
|
|
|
| T006 | `GET /api/v1/users/me/holdings` | `holding:read:self`(已登录) | 否 | `200` | 持仓列表(含市值/盈亏/当日盈亏) |
|
|
|
|
|
|
| T007 | `GET /api/v1/users/me/transactions` | `trade:txn:read`(已登录) | 否 | `200` | 成交记录列表 |
|
|
|
|
|
|
| T008 | `GET /api/v1/users/me/transactions/{txn_no}` | `trade:txn:read`(资源所有者) | 否 | `200` | 成交详情 |
|
|
|
|
|
|
| T009 | `GET /api/v1/users/me/cash-ledger` | `account:read:self`(已登录) | 否 | `200` | 资金账本(按 id 倒序游标分页) |
|
2026-09-13 22:57:48 +08:00
|
|
|
|
| P001 | `GET /api/v1/products` | 仅要求有效令牌(访客令牌即可,不校验权限码) | 否 | `200` | 否 |
|
2026-09-13 23:38:13 +08:00
|
|
|
|
| P002 | `GET /api/v1/products/{product_code}/nav-history` | 同上;`days` 取 1–365,默认 90 | 否 | `200` | 否 |
|
2026-09-12 15:35:12 +08:00
|
|
|
|
|
2026-09-14 20:36:00 +08:00
|
|
|
|
### 19.1 补充接口段(2026-09-14 补登)
|
|
|
|
|
|
|
|
|
|
|
|
⚠️ **以下几段此前完全未登记在 §19**,但**已由 `app/main.py` 实际挂载**(`include_router` 共 21 个)。
|
|
|
|
|
|
使用者按本文档找场外/访客令牌/入驻接口时**会找不到**,故在此补登。各段的**请求/响应字段级契约**
|
|
|
|
|
|
以对应 Controller 与 Service 为准(本节只登记路由与鉴权口径):
|
|
|
|
|
|
|
|
|
|
|
|
| 段 | 前缀 | 挂载来源 | 权限口径 |
|
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
| **场外基金** | `/api/v1/offsite-fund` | `offsite_fund.py` 的 `router` | **any-of(非空交集)**语义,**不走** `AuthorizationService.require`:四种组合 `("offsite:read","offsite:write")`、`("offsite:write",)`、`("offsite:write","offsite:confirm")`、`("offsite:notify","offsite:write")` |
|
|
|
|
|
|
| **场外运营** | `/api`(同一文件的 `operation_router`) | `offsite_fund.py` 的 `operation_router` | 同上 |
|
|
|
|
|
|
| **访客令牌** | `/api/v1/visitor-tokens` | `visitor_tokens.py` | 签发访客令牌(访客页面用) |
|
|
|
|
|
|
| **客户入驻** | `/api/v1/onboarding` | `onboarding.py` | 注册/开户流程 |
|
|
|
|
|
|
| **推广材料** | `/api/v1/promotion-materials` | `promotion_material.py` | 权限码 `promotion:read`/`write`/`review`/`deliver`(9047 段同批) |
|
|
|
|
|
|
| **账户与交易** | `/api/v1/users/me` | `trading.py` 的 `router` | 见上表 T001–T009 |
|
|
|
|
|
|
|
|
|
|
|
|
> **场外契约有两条与主站不同,容易踩**(详见 `docs/28` 与 SRS):
|
|
|
|
|
|
> 1. **场外全域沿用旧式信封 `{code,message,data}`(成功 = `code === 0`)**,
|
|
|
|
|
|
> **不是** §3.3 的新信封;
|
|
|
|
|
|
> 2. 场外**用 `page`/`page_size` 分页**,**不是** cursor 分页。
|
|
|
|
|
|
>
|
|
|
|
|
|
> **风控段的编号是 `RK001–RK015`**(见 `common/api-client.js`),
|
|
|
|
|
|
> 其中 **`RK013–RK015` 在 `api-client.js` 内已自述"未登记进本文 §19"**——属已知缺口,待补。
|
|
|
|
|
|
> **§19 的 A 段编号当前到 `A049`**(`api-client.js` 亦为 A001–A049),后续新增请顺延。
|
|
|
|
|
|
|
2026-09-12 15:35:12 +08:00
|
|
|
|
> **T001 – T009 的四点说明**:
|
|
|
|
|
|
>
|
|
|
|
|
|
> - **首版只支持 `price_type="market"` 市价委托**(`docs/00` §6.6 定义),系统**立即全额成交**,
|
|
|
|
|
|
> 委托状态直接落到 `已成交`;因此 T005 撤单首版对任何在场委托都返回
|
|
|
|
|
|
> `ORDER_NOT_CANCELLABLE`(409),保留接口作为后续限价/部分成交开启的入口。
|
|
|
|
|
|
> - **价格来源**仅复用底座 `FundQuoteService` 的公共行情快照
|
|
|
|
|
|
> (`fin_market_price` 最新交易日,quote_source = `eastmoney_demo_seed`);
|
|
|
|
|
|
> Service 层**不**做行情二次封装,从而满足 AGENTS.md 第 2 条 Agent/Service 不直接命中行情 API。
|
|
|
|
|
|
> - **数据库零变更**:T 段所用的 10 张 `fin_*` 表均由 `docs/00` 定义;本批 PR **不**重命名/删除/修改列类型
|
|
|
|
|
|
> 与可空性,与 AGENTS.md 第 1 条一致。
|
|
|
|
|
|
> - **持仓比例上限**在 T002 买入路径强制校验
|
|
|
|
|
|
> `(当前持仓 + 本次拟成交)/ total_fund_shares * 100 <= single_investor_max_holding_ratio`;
|
|
|
|
|
|
> `fin_market_price` 缺失或过期则拒绝买入(docs/00 §6.6 红线)。
|
2026-09-09 21:55:37 +08:00
|
|
|
|
|
2026-09-11 21:22:56 +08:00
|
|
|
|
> **A034 – A038 的两点说明**:
|
|
|
|
|
|
>
|
|
|
|
|
|
> - `POST /api/v1/auth/tokens` 是平台内**唯一的登录入口**(§11 已注明这是"平台不重复实现
|
|
|
|
|
|
> 签发"的唯一例外;**刷新与注销仍归统一身份认证模块**)。
|
|
|
|
|
|
> - A035 – A038 是 RBAC 的**只读**查询,供管理员回答"谁能访问什么""这个人为什么 403"。
|
|
|
|
|
|
> 它们复用 `audit:read` 而**不新增** `rbac:read`:这份清单本身就是审计材料,且复用是
|
|
|
|
|
|
> 零数据改动、立刻可用(新增权限码得先改 `sys_permission`,而它目前由
|
|
|
|
|
|
> `seed_test_rbac.py` 以 DELETE 重建语义管理)。
|
|
|
|
|
|
> **权限变更(提权 / 降权)尚无接口** —— 那条写路径必须带三条红线
|
|
|
|
|
|
> (审计留痕、禁止自我提权、保护内置角色),需要单独评审,不是遗漏。
|
|
|
|
|
|
|
2026-09-09 21:55:37 +08:00
|
|
|
|
业务域接口 `/customer-service/handover-tickets/**`、`/advisory-plans/**`、`/sim-orders/**`、`/risk-scans/**` 和 `/risk-alerts/**` 的具体方法、请求体、领域状态机和错误码分别由对应业务文档登记;它们仍必须遵守本文第 3-5、11 和 12 节。
|
|
|
|
|
|
|
2026-09-14 01:19:22 +08:00
|
|
|
|
> **A048 / A049 为什么必须存在**:`platform-config-items` 与 `model-routing-rules`
|
|
|
|
|
|
> 的**更新端点要求 `If-Match`**,校验的是该行内容的 digest;而这两个资源此前**没有详情端点**,
|
|
|
|
|
|
> 列表的 `meta` 也不带 etag —— 客户端**无从取得当前 digest**,首次编辑必然
|
|
|
|
|
|
> `409 RESOURCE_VERSION_CONFLICT`。乐观并发在"读不到版本"的前提下等于死锁。
|
|
|
|
|
|
> 补上详情端点后,客户端 GET 详情(响应头 `ETag` + `meta.etag`)再 PUT 即可。
|
|
|
|
|
|
>
|
|
|
|
|
|
> ⚠️ 判据:**凡接受 `If-Match` 的资源,必须同时提供能返回该 etag 的读取路径** ——
|
|
|
|
|
|
> 这是本次由前端等价测试(照接口逐个调用核对)才暴露出来的,纯看代码不容易发现。
|
|
|
|
|
|
|
|
|
|
|
|
> **AD 段(投顾自用)与 A041–A047(投顾治理)的六点说明**:
|
2026-09-14 00:17:46 +08:00
|
|
|
|
>
|
|
|
|
|
|
> 这批端点原先**只存在于代码中**,`§19` 一条都没登记(2026-09-13 补登)。当时 §12 写的
|
|
|
|
|
|
> 入口是 `/api/v1/advisory-plans/**`,与实际路径 `/api/v1/advisor/**` **不符**,
|
|
|
|
|
|
> 也已一并修正。门禁脚本 `check_docs_endpoint_ids.py` 只校验 §19 **内部**编号唯一性,
|
|
|
|
|
|
> **查不出"代码里有端点、文档里没登记"**这类缺口 —— 新增端点时请按 §20 主动登记。
|
|
|
|
|
|
>
|
|
|
|
|
|
> - **为什么新开 `AD` 号段**:这批端点落在 `/api/v1/advisor/**`,与 A 段的
|
|
|
|
|
|
> `/api/v1/admin/**` 是两个不同的权限面(A 段是管理面,AD 段是投顾自用)。
|
|
|
|
|
|
> 混在一个号段里,"这条到底是投顾能调还是只有管理员能调"就得逐条去读权限列。
|
|
|
|
|
|
> - **`investment-goal` 的两套权限码**:`investment-goal:<action>:self`(本人)与
|
|
|
|
|
|
> `investment-goal:<action>:customer`(名下客户),由
|
|
|
|
|
|
> `InvestmentGoalService._assert_customer_access` 按 `customer_id` 是否等于
|
|
|
|
|
|
> `context.user_id` 选择。`:customer` 那一支还要求数据范围是 `all`,或者
|
|
|
|
|
|
> `own_customers` 且该客户确实在 `sys_customer_assignment` 里 —— 否则返回
|
|
|
|
|
|
> `404 客户不可访问`(**注意是 404 不是 403**:不向调用方泄露"该客户存在但你没权看")。
|
|
|
|
|
|
> - **AD006 / AD007 虽在投顾路径下,却要求 `admin`**:方案书的审核与发布是**管理员的
|
|
|
|
|
|
> 复核动作**(`review_book` / `publish_book` 都带 `admin=True`),投顾本人发不出来。
|
|
|
|
|
|
> 这是有意的复核环节,不是遗漏。
|
|
|
|
|
|
> - **`enforce_advisor_rollout` 是额外前置**:AD 段每一条都挂了投顾灰度开关,
|
|
|
|
|
|
> 未放行时在鉴权之后、业务逻辑之前就被拦下。
|
|
|
|
|
|
> - **幂等**:AD001 / AD004 / AD006 / AD007 / AD010 与 A041 / A044 / A045 / A046 接受
|
|
|
|
|
|
> `Idempotency-Key`;**AD008 / AD009(组合分析、资产配置)没有幂等头** ——
|
|
|
|
|
|
> 它们是纯分析入口,不落业务单据。
|
|
|
|
|
|
> - **失败口径**:目标或方案书不存在 → `404`;状态不允许(重复确认、未审核就发布)
|
|
|
|
|
|
> → `409`。⚠️ 409 目前一律复用 `RUN_NOT_CANCELLABLE` 这个码(见 §3.6),
|
|
|
|
|
|
> 所以"投资目标不能确认"会报出字面像"运行不可取消"的码,属于已知的文档缺口。
|
|
|
|
|
|
|
2026-09-13 22:57:48 +08:00
|
|
|
|
> **P001(公开产品列表)的四点说明**:
|
|
|
|
|
|
>
|
|
|
|
|
|
> - **鉴权口径:要求令牌但不校验权限码。** 访客令牌的角色是 `visitor`、**不带任何权限**
|
|
|
|
|
|
> (见 §4.2 与 `app/core/security.py`),因此这里不能用权限码把关,否则访客永远 401。
|
|
|
|
|
|
> 这与 `/api/v1/agent-runs`、`/api/v1/conversations` 面向访客的做法一致 ——
|
|
|
|
|
|
> 产品信息本身是公开信息,要求令牌只为复用统一入口、限流与追踪,不是为了授权。
|
|
|
|
|
|
> 数据面只暴露 `fin_product`(`status='上市'`)与 `fin_market_price` 的最新一行,
|
|
|
|
|
|
> **不含任何账户、持仓或客户字段**。
|
|
|
|
|
|
> - **载荷**:`data.products[]` + `data.count`。产品字段与 `fin_product` 同名
|
|
|
|
|
|
> (`product_code`/`product_name`/`exchange_code`/`product_category`/`risk_level`/
|
|
|
|
|
|
> `fund_manager`/`current_nav`/`current_nav_at`/`lot_size`/`price_tick`/`min_amount`/
|
|
|
|
|
|
> `management_fee_rate`/`custodian_fee_rate`/`status`),
|
|
|
|
|
|
> 另加 `latest_close`/`latest_trade_date`/`quote_source`(来自 `fin_market_price`)
|
|
|
|
|
|
> 与 `change_pct`。金额与费率一律为**字符串**(与既有接口口径一致)。
|
|
|
|
|
|
> - **`change_pct` 可能为 `null`,调用方必须显示"暂无"而不得当成 `0`。**
|
|
|
|
|
|
> 当日涨跌幅需要**两个交易日**的收盘价,而行情可能只同步过一天。
|
|
|
|
|
|
> 把 `null` 读成 `0` 等于对客户说"今天平盘",那是编出来的结论。
|
2026-09-13 23:38:13 +08:00
|
|
|
|
> - **首页推荐位、产品列表页、产品详情页共用本端点**;产品详情页的净值走势图另走 P002。
|
|
|
|
|
|
> 此前这三页读的是前端手写的 `app/static/portal/common/mock-data.js`
|
|
|
|
|
|
> (8 只演示数据,其中 6 只不在 `fin_product` 里),该文件已随本次接入删除。
|
|
|
|
|
|
>
|
|
|
|
|
|
> **P002(历史净值序列)的三点说明**:
|
|
|
|
|
|
>
|
|
|
|
|
|
> - **用途**:产品详情页的净值走势图。数据来自 `fin_nav_history`,
|
|
|
|
|
|
> 由 `tools/sync_nav_history.py` 从东方财富历史净值接口
|
|
|
|
|
|
> (`api.fund.eastmoney.com/f10/lsjz`)同步 —— 注意该域名与**行情**域名不同:
|
|
|
|
|
|
> 东财的 `push2` / `push2his` 在本环境连接被拒,所以历史走势走净值这条路。
|
|
|
|
|
|
> - **表为空时返回 `count=0` 与空数组,不是错误**:调用方据此显示"尚未接入",
|
|
|
|
|
|
> 而**不得回退到编造的曲线**。此前详情页那条线是演示数据里 12 个假点位,
|
|
|
|
|
|
> 走势图最容易被当成真实业绩。
|
|
|
|
|
|
> - **不校验基金归属**:`fin_product` 里有非南方基金的产品(510300 是华泰柏瑞的),
|
|
|
|
|
|
> 所以这里不套 `hq.py` 的南方基金白名单,只按 `product_code` 取数。
|
|
|
|
|
|
> 产品不存在或未上市 → `404`。
|
2026-09-13 22:57:48 +08:00
|
|
|
|
|
2026-09-09 21:55:37 +08:00
|
|
|
|
## 20. 变更流程
|
|
|
|
|
|
|
|
|
|
|
|
任何新增或修改接口必须同时更新:
|
|
|
|
|
|
|
|
|
|
|
|
1. 本文对应章节和第 19 节目录。
|
|
|
|
|
|
2. OpenAPI 机器可读定义。
|
|
|
|
|
|
3. 权限码、审计类别、幂等说明和错误码清单。
|
|
|
|
|
|
4. 请求、响应、越权、重复提交、故障恢复和安全测试。
|
|
|
|
|
|
5. 对应业务文档的入口索引;业务载荷不得复制到公共文档。
|
|
|
|
|
|
|
|
|
|
|
|
涉及内部契约时,先修改权威源文档,再更新本文索引。禁止只修改 OpenAPI 或只修改某个业务文档造成第二权威源。
|