实现 M002 订单任务入站与队列规则
This commit is contained in:
@@ -0,0 +1,378 @@
|
||||
# M002 SuperAgent Task Result API Contract
|
||||
|
||||
## 文档信息
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 文档版本 | 0.1 |
|
||||
| 日期 | 2026-07-07 |
|
||||
| 状态 | 后端接口契约草稿 |
|
||||
| 适用范围 | SuperAgent / Main Agent 调用本系统提交 AI 任务结果 |
|
||||
| 主要读者 | 后端、SuperAgent 对接方、测试、后续协作 agent |
|
||||
|
||||
## 1. 文档定位
|
||||
|
||||
本文定义 SuperAgent / Main Agent 向本系统提交 `ai_task_results[]` 的第一版后端接口契约。
|
||||
|
||||
本文承接 `M002-order-task-workflow-v2.md`,只定义本系统入站接口、鉴权、幂等、请求响应和技术校验边界,不定义 SuperAgent 内部 prompt、Skill 实现、OPERA 真实接口或前端展示细节。
|
||||
|
||||
## 2. 接口概览
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| Method | `POST` |
|
||||
| Path | `/api/integrations/superagent/task-results` |
|
||||
| Content-Type | `application/json` |
|
||||
| 响应格式 | `application/json` |
|
||||
| 一次请求范围 | 只能包含一个 `source_message_id` |
|
||||
| 业务动作 | 接收 AI 结果、写入 AI 过渡层、生成订单 / 任务 / 任务卡 |
|
||||
| 鉴权方式 | HMAC-SHA256 签名 |
|
||||
|
||||
中文说明:
|
||||
|
||||
- 该接口是服务到服务的入站接口,不给前端直接调用。
|
||||
- SuperAgent 不直连数据库,只能通过本接口提交任务结果。
|
||||
- 本接口只做技术校验和系统接收,不替代用户确认和 OPERA 模拟操作。
|
||||
|
||||
## 3. 鉴权方案
|
||||
|
||||
第一版使用 HMAC-SHA256 签名,做到简单、可实现、可防重放。
|
||||
|
||||
### 3.1 环境变量
|
||||
|
||||
| 环境变量 | 是否必填 | 中文说明 |
|
||||
| --- | --- | --- |
|
||||
| `SUPERAGENT_TASK_RESULT_HMAC_SECRET` | 是 | SuperAgent 任务结果入站签名密钥,只能通过 Secret 注入 |
|
||||
| `SUPERAGENT_TASK_RESULT_CLOCK_SKEW_SECONDS` | 否 | 请求时间允许偏移,默认 `300` 秒 |
|
||||
| `SUPERAGENT_TASK_RESULT_NONCE_TTL_SECONDS` | 否 | Nonce 去重窗口,默认 `600` 秒 |
|
||||
| `SUPERAGENT_TASK_RESULT_MAX_BODY_BYTES` | 否 | 请求体最大字节数,默认 `1048576` |
|
||||
|
||||
如果 `SUPERAGENT_TASK_RESULT_HMAC_SECRET` 为空,生产环境应拒绝接口调用。
|
||||
|
||||
### 3.2 请求 Header
|
||||
|
||||
| Header | 是否必填 | 中文说明 |
|
||||
| --- | --- | --- |
|
||||
| `X-TH-Hotel-SuperAgent-Client-Id` | 是 | 调用方客户端 ID,用于区分不同 SuperAgent 调用方 |
|
||||
| `X-TH-Hotel-SuperAgent-Timestamp` | 是 | UTC 时间,ISO-8601 格式,例如 `2026-07-07T08:30:00Z` |
|
||||
| `X-TH-Hotel-SuperAgent-Nonce` | 是 | 每次请求唯一随机值,用于防重放 |
|
||||
| `X-TH-Hotel-SuperAgent-Signature` | 是 | HMAC 签名,格式 `sha256=<lowercase-hex>` |
|
||||
| `X-TH-Hotel-Request-Id` | 否 | 调用方请求 ID,用于排查和日志串联 |
|
||||
|
||||
### 3.3 签名串
|
||||
|
||||
签名使用原始请求体字节计算 SHA-256,再参与 HMAC。
|
||||
|
||||
规范签名串:
|
||||
|
||||
```text
|
||||
POST
|
||||
/api/integrations/superagent/task-results
|
||||
<X-TH-Hotel-SuperAgent-Timestamp>
|
||||
<X-TH-Hotel-SuperAgent-Nonce>
|
||||
<X-TH-Hotel-SuperAgent-Client-Id>
|
||||
<lowercase-hex-sha256-of-raw-body>
|
||||
```
|
||||
|
||||
签名算法:
|
||||
|
||||
```text
|
||||
signature = HMAC_SHA256(SUPERAGENT_TASK_RESULT_HMAC_SECRET, canonical_string)
|
||||
```
|
||||
|
||||
Header 写法:
|
||||
|
||||
```text
|
||||
X-TH-Hotel-SuperAgent-Signature: sha256=<lowercase-hex-signature>
|
||||
```
|
||||
|
||||
### 3.4 服务端校验
|
||||
|
||||
服务端必须按顺序完成以下校验:
|
||||
|
||||
1. 校验必要 Header 是否存在。
|
||||
2. 校验 timestamp 可解析且在允许时间窗口内。
|
||||
3. 校验同一个 `client_id + nonce` 在 TTL 窗口内没有被使用过。
|
||||
4. 计算原始请求体 SHA-256。
|
||||
5. 使用 HMAC secret 重新计算签名。
|
||||
6. 使用常量时间比较签名。
|
||||
7. 鉴权通过后再解析 JSON。
|
||||
|
||||
日志和错误响应不得输出 secret、签名原文、完整请求体、邮件正文、附件 URL 或个人敏感信息。
|
||||
|
||||
## 4. 请求体
|
||||
|
||||
请求体沿用 AI 导入文档定义的聚合结构。
|
||||
|
||||
```json
|
||||
{
|
||||
"source_message_id": "1900000000000000001",
|
||||
"ai_task_results": [
|
||||
{
|
||||
"source_event_index": 1,
|
||||
"catalog_code": "S01",
|
||||
"skill_id": "S01_new_booking_skill",
|
||||
"result_type": "normal_task",
|
||||
"task_type": "New Booking",
|
||||
"task_subtype": "new_fit_reservation",
|
||||
"current_or_history": "current",
|
||||
"case_keys": {
|
||||
"group_code": null,
|
||||
"confirmation_number": null
|
||||
},
|
||||
"visible_reason": "邮件正文包含新建预订请求。",
|
||||
"relevant_message_excerpt": "Please create a new booking...",
|
||||
"attachments": [],
|
||||
"file_references": [],
|
||||
"context_used": {},
|
||||
"extracted_fields": {},
|
||||
"manual_review": null,
|
||||
"informational_message": null,
|
||||
"additional_operations": [],
|
||||
"idempotency_key": null
|
||||
}
|
||||
],
|
||||
"extraction_warnings": []
|
||||
}
|
||||
```
|
||||
|
||||
### 4.1 顶层字段
|
||||
|
||||
| 字段 | 是否必填 | 中文说明 |
|
||||
| --- | --- | --- |
|
||||
| `source_message_id` | 是 | 关联本系统 SourceMessage ID;一次请求只能有一个 |
|
||||
| `ai_task_results[]` | 是 | AI 拆分出的任务结果列表,必须保留数组顺序 |
|
||||
| `extraction_warnings[]` | 否 | 抽取警告;不直接等同于业务任务 |
|
||||
|
||||
第一版要求 `ai_task_results[]` 至少包含一条记录。没有业务动作时应由 AI 输出 `Message Notification` 或 `Fallback/manual_review`,而不是提交空数组。
|
||||
|
||||
### 4.2 `ai_task_results[]` 字段
|
||||
|
||||
| 字段 | 是否必填 | 中文说明 |
|
||||
| --- | --- | --- |
|
||||
| `source_event_index` | 是 | AI current 事件序号,建议从 1 开始 |
|
||||
| `catalog_code` | 是 | Skill 目录代码,例如 S01、S02 |
|
||||
| `skill_id` | 是 | Skill 标识 |
|
||||
| `result_type` | 是 | 只接受 `normal_task`、`manual_review`、`informational_message` |
|
||||
| `task_type` | 是 | AI 原始任务类型 |
|
||||
| `task_subtype` | 否 | 业务动作 subtype;有则用于任务卡路由 |
|
||||
| `current_or_history` | 否 | 当前或历史标识 |
|
||||
| `case_keys` | 否 | 订单关联候选键 |
|
||||
| `visible_reason` | 否 | 给用户看的生成原因 |
|
||||
| `relevant_message_excerpt` | 否 | 相关邮件片段,注意不要超长 |
|
||||
| `attachments` / `file_references` | 否 | 附件和文件引用 |
|
||||
| `context_used` | 否 | AI 使用的上下文 |
|
||||
| `extracted_fields` | 否 | 业务字段主体 |
|
||||
| `manual_review` | 条件必填 | `result_type=manual_review` 时应提供 |
|
||||
| `informational_message` | 条件必填 | `result_type=informational_message` 时应提供 |
|
||||
| `additional_operations` | 否 | 附加动作建议 |
|
||||
| `idempotency_key` | 否 | 可忽略;本系统第一版自行生成幂等键 |
|
||||
|
||||
## 5. 技术校验边界
|
||||
|
||||
本接口只做技术校验,不做业务合法性判断。
|
||||
|
||||
### 5.1 必须校验
|
||||
|
||||
- 鉴权签名合法。
|
||||
- 请求体大小不超过限制。
|
||||
- JSON 可解析。
|
||||
- 顶层只有一个 `source_message_id`。
|
||||
- `source_message_id` 对应的 SourceMessage 存在。
|
||||
- `ai_task_results[]` 是非空数组。
|
||||
- `result_type` 属于允许值。
|
||||
- `task_type` 属于当前系统可识别的稳定值或可进入 Fallback 处理。
|
||||
- 同一个请求内 `source_event_index` 和数组顺序可保存。
|
||||
- 关键字符串长度不超过数据库限制。
|
||||
|
||||
### 5.2 不在本接口判断
|
||||
|
||||
- 不判断 AI 任务类型是否业务正确。
|
||||
- 不判断房型、价格、日期、Rate Code 是否合理。
|
||||
- 不判断 Cancel Booking 前是否已经有 New Booking。
|
||||
- 不因为字段缺失自动改成 Fallback。
|
||||
- 不直接执行 OPERA 模拟。
|
||||
- 不直接把 AI 原始值写入 OPERA 参数。
|
||||
|
||||
## 6. 幂等设计
|
||||
|
||||
`idempotency_key` 第一版由系统生成,不依赖 SuperAgent 传值。
|
||||
|
||||
### 6.1 请求哈希
|
||||
|
||||
系统必须保存原始请求体的 SHA-256:
|
||||
|
||||
```text
|
||||
request_payload_sha256 = sha256(raw_request_body)
|
||||
```
|
||||
|
||||
### 6.2 批次幂等键
|
||||
|
||||
批次幂等键建议:
|
||||
|
||||
```text
|
||||
batch_idempotency_key =
|
||||
sha256(
|
||||
"superagent-task-result-batch:v1"
|
||||
+ "|" + source_message_id
|
||||
+ "|" + request_payload_sha256
|
||||
)
|
||||
```
|
||||
|
||||
作用:
|
||||
|
||||
- 相同请求体重复提交时识别为幂等重放。
|
||||
- 同一个 `source_message_id` 如果提交了不同请求体,不会被误认为同一个批次。
|
||||
|
||||
### 6.3 item 幂等键
|
||||
|
||||
每条 `ai_task_results[]` 的幂等键建议:
|
||||
|
||||
```text
|
||||
item_idempotency_key =
|
||||
sha256(
|
||||
"superagent-task-result-item:v1"
|
||||
+ "|" + source_message_id
|
||||
+ "|" + source_event_index
|
||||
+ "|" + array_index
|
||||
+ "|" + catalog_code
|
||||
+ "|" + skill_id
|
||||
+ "|" + result_type
|
||||
+ "|" + task_type
|
||||
+ "|" + task_subtype
|
||||
+ "|" + item_payload_sha256
|
||||
)
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `array_index` 按 AI 返回列表顺序保存,建议从 1 开始。
|
||||
- `item_payload_sha256` 是单个 item 规范 JSON 或原始片段的 SHA-256。
|
||||
- 数据库应对 `hotel_id + item_idempotency_key` 建唯一约束。
|
||||
|
||||
### 6.4 重复提交处理
|
||||
|
||||
| 场景 | 处理方式 |
|
||||
| --- | --- |
|
||||
| 完全相同请求体重复提交 | 返回已有 batch 和 item,不重复创建任务 |
|
||||
| 同一 `source_message_id` 不同请求体 | 作为潜在冲突处理,第一版建议拒绝并返回 `IDEMPOTENCY_CONFLICT`,除非后续明确支持重新抽取版本 |
|
||||
| 同一请求内 item 幂等键重复 | 拒绝请求,返回 `DUPLICATE_TASK_RESULT_ITEM` |
|
||||
|
||||
## 7. 系统映射规则
|
||||
|
||||
接口接收后,应将 AI 字段映射到系统字段。
|
||||
|
||||
| AI 字段 | 系统字段 | 中文说明 |
|
||||
| --- | --- | --- |
|
||||
| `task_type` | `ai_task_type` | 保留 AI 原始任务类型 |
|
||||
| `result_type` | `result_type` | 保留 AI 结果类型 |
|
||||
| `task_type + result_type` | `system_task_type` | 映射为系统主任务类型 |
|
||||
| `task_type + task_subtype` | `task_card_type` | 映射为任务卡类型 |
|
||||
| `source_event_index + array_index` | `execution_order` | 生成同订单任务顺序 |
|
||||
| 完整 item JSON | `ai_payload_json` | 保存 AI 原始 payload |
|
||||
| `case_keys` | `case_keys_json` | 保存订单候选键 |
|
||||
| `extracted_fields` | `extracted_fields_json` | 保存业务字段 |
|
||||
| `manual_review` | `manual_review_json` | 保存人工复核结构 |
|
||||
| `informational_message` | `informational_message_json` | 保存信息提醒 |
|
||||
|
||||
系统主任务类型映射以 `M002-order-task-workflow-v2.md` 为准。
|
||||
|
||||
## 8. 响应体
|
||||
|
||||
### 8.1 创建成功
|
||||
|
||||
首次成功创建时返回 `201 Created`。
|
||||
|
||||
```json
|
||||
{
|
||||
"request_id": "req-20260707-0001",
|
||||
"source_message_id": "1900000000000000001",
|
||||
"batch_id": "1900000000000001001",
|
||||
"idempotent_replay": false,
|
||||
"accepted_count": 1,
|
||||
"items": [
|
||||
{
|
||||
"source_event_index": 1,
|
||||
"array_index": 1,
|
||||
"ai_transition_id": "1900000000000002001",
|
||||
"order_id": "1900000000000003001",
|
||||
"task_id": "1900000000000004001",
|
||||
"system_task_type": "NEW_BOOKING",
|
||||
"task_card_type": "NEW_BOOKING",
|
||||
"task_status": "PENDING_CONFIRM",
|
||||
"order_status": "TEMPORARY",
|
||||
"execution_order": 1
|
||||
}
|
||||
],
|
||||
"warnings": []
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 幂等重放
|
||||
|
||||
相同请求体重复提交时返回 `200 OK`。
|
||||
|
||||
```json
|
||||
{
|
||||
"request_id": "req-20260707-0002",
|
||||
"source_message_id": "1900000000000000001",
|
||||
"batch_id": "1900000000000001001",
|
||||
"idempotent_replay": true,
|
||||
"accepted_count": 1,
|
||||
"items": [],
|
||||
"warnings": [
|
||||
{
|
||||
"code": "IDEMPOTENT_REPLAY",
|
||||
"message": "相同请求已经处理,本次未重复创建任务。"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 错误响应
|
||||
|
||||
错误响应统一结构:
|
||||
|
||||
```json
|
||||
{
|
||||
"request_id": "req-20260707-0003",
|
||||
"error_code": "AUTH_SIGNATURE_INVALID",
|
||||
"message": "签名校验失败。",
|
||||
"details": []
|
||||
}
|
||||
```
|
||||
|
||||
常见错误码:
|
||||
|
||||
| HTTP 状态 | error_code | 中文说明 |
|
||||
| --- | --- | --- |
|
||||
| 401 | `AUTH_HEADER_MISSING` | 鉴权 Header 缺失 |
|
||||
| 401 | `AUTH_TIMESTAMP_INVALID` | 请求时间无效或超出窗口 |
|
||||
| 401 | `AUTH_SIGNATURE_INVALID` | 签名不匹配 |
|
||||
| 409 | `AUTH_NONCE_REPLAY` | Nonce 重放 |
|
||||
| 413 | `REQUEST_BODY_TOO_LARGE` | 请求体过大 |
|
||||
| 400 | `INVALID_JSON` | JSON 不可解析 |
|
||||
| 400 | `SOURCE_MESSAGE_REQUIRED` | `source_message_id` 缺失 |
|
||||
| 404 | `SOURCE_MESSAGE_NOT_FOUND` | SourceMessage 不存在 |
|
||||
| 400 | `TASK_RESULTS_EMPTY` | `ai_task_results[]` 为空 |
|
||||
| 400 | `TASK_RESULT_UNSUPPORTED_TYPE` | `result_type` 或 `task_type` 不可识别 |
|
||||
| 400 | `DUPLICATE_TASK_RESULT_ITEM` | 同一请求内 item 重复 |
|
||||
| 409 | `IDEMPOTENCY_CONFLICT` | 同一 SourceMessage 出现不同请求体重复提交 |
|
||||
| 500 | `INTERNAL_ERROR` | 系统内部错误 |
|
||||
|
||||
错误响应不得返回原始请求体、邮件正文、附件 URL、Token、签名 secret 或完整个人敏感信息。
|
||||
|
||||
## 10. 验收标准
|
||||
|
||||
第一版接口实现完成时至少满足:
|
||||
|
||||
- 缺失 HMAC Header 时返回 `401`。
|
||||
- timestamp 超出窗口时返回 `401`。
|
||||
- nonce 重放时返回 `409`。
|
||||
- 签名错误时返回 `401`。
|
||||
- 一个请求只能包含一个 `source_message_id`。
|
||||
- `source_message_id` 不存在时返回 `404`。
|
||||
- 相同请求重复提交不会重复创建任务。
|
||||
- 同一 `source_message_id` 不同请求体重复提交返回 `409`。
|
||||
- 成功接收后能保存 AI 过渡层记录,并返回 batch、task 和 order 标识。
|
||||
- 日志和响应不暴露 secret、签名原文、完整邮件正文或附件 URL。
|
||||
Reference in New Issue
Block a user