统一 SuperAgent 查询接口鉴权契约
This commit is contained in:
393
docs/project/integrations/superagent-api-contract.md
Normal file
393
docs/project/integrations/superagent-api-contract.md
Normal file
@@ -0,0 +1,393 @@
|
||||
# TH Hotel SuperAgent API 对接契约
|
||||
|
||||
## 文档信息
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 文档版本 | 0.1 |
|
||||
| 日期 | 2026-07-08 |
|
||||
| 状态 | 第一版后端已实现接口契约 |
|
||||
| 适用范围 | SuperAgent 调用本系统查询上下文、提交 AI 任务结果 |
|
||||
| 主要读者 | SuperAgent 对接方、后端、测试、运维 |
|
||||
|
||||
## 1. 基础约定
|
||||
|
||||
请求地址先使用占位符:
|
||||
|
||||
```text
|
||||
{TH_HOTEL_API_BASE_URL}
|
||||
```
|
||||
|
||||
上线或联调时由环境提供实际域名,例如 UAT、生产内网域名或 API Gateway 地址。本文所有接口均为 SuperAgent 到本系统的服务到服务调用,不给前端直接调用。
|
||||
|
||||
## 2. 通用 HMAC 鉴权
|
||||
|
||||
查询接口和任务结果通知接口使用同一套 HMAC-SHA256 规则。
|
||||
|
||||
### 2.1 通用 Header
|
||||
|
||||
| Header | 是否必填 | 中文说明 |
|
||||
| --- | --- | --- |
|
||||
| `Content-Type` | 是 | 固定 `application/json` |
|
||||
| `X-TH-Hotel-SuperAgent-Client-Id` | 是 | SuperAgent 调用方客户端 ID |
|
||||
| `X-TH-Hotel-SuperAgent-Timestamp` | 是 | UTC ISO-8601 时间,例如 `2026-07-08T01:30:00Z` |
|
||||
| `X-TH-Hotel-SuperAgent-Nonce` | 是 | 每次请求唯一随机值,用于防重放 |
|
||||
| `X-TH-Hotel-SuperAgent-Signature` | 是 | HMAC 签名,格式 `sha256=<lowercase-hex>` |
|
||||
| `X-TH-Hotel-Request-Id` | 否 | 调用方请求 ID,用于日志串联 |
|
||||
| `X-TH-Hotel-AI-Trace-Id` | 否 | AI 运行链路 ID,查询接口会原样带回 `trace_id` |
|
||||
|
||||
### 2.2 签名串
|
||||
|
||||
签名串使用接口 path,不包含域名、query string 或 fragment。
|
||||
|
||||
```text
|
||||
POST
|
||||
<request_path>
|
||||
<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>
|
||||
```
|
||||
|
||||
### 2.3 服务端校验顺序
|
||||
|
||||
1. 校验请求体大小。
|
||||
2. 校验 HMAC 相关 Header 是否存在。
|
||||
3. 校验 timestamp 是否在允许时间窗口内。
|
||||
4. 计算原始请求体 SHA-256。
|
||||
5. 使用共享 secret 重新计算 HMAC。
|
||||
6. 常量时间比较签名。
|
||||
7. 校验并记录 `client_id + nonce`,防止重放。
|
||||
8. 查询接口校验 `Content-Type` 是否为 `application/json`。
|
||||
9. 鉴权和协议校验通过后再解析业务 JSON。
|
||||
|
||||
## 3. 接口 1:查询订单上下文
|
||||
|
||||
### 3.1 请求
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| Method | `POST` |
|
||||
| URL | `{TH_HOTEL_API_BASE_URL}/api/ai-query/v1/case-context` |
|
||||
| request_path | `/api/ai-query/v1/case-context` |
|
||||
| Content-Type | `application/json` |
|
||||
| 业务动作 | 只读查询,不创建任务、不修改订单、不写 OPERA |
|
||||
|
||||
### 3.2 请求体
|
||||
|
||||
```json
|
||||
{
|
||||
"hotel_id": "HOTEL-TEST",
|
||||
"source_message_id": "1900000000000000001",
|
||||
"source_event_index": 1,
|
||||
"group_code": "GRP-001",
|
||||
"confirmation_number": null,
|
||||
"reservation_no": null,
|
||||
"object_type_hint": "group_block",
|
||||
"target_key_source": "body_current",
|
||||
"body_thread_used_only_as_evidence": false
|
||||
}
|
||||
```
|
||||
|
||||
字段说明:
|
||||
|
||||
| 字段 | 是否必填 | 中文说明 |
|
||||
| --- | --- | --- |
|
||||
| `hotel_id` | 是 | 酒店上下文 ID |
|
||||
| `source_message_id` | 否 | 当前 SourceMessage ID;全局上下文查询时可不传,只作为审计和排查字段 |
|
||||
| `source_event_index` | 否 | 当前 AI 事件序号;全局上下文查询可不传,传入时必须为正整数 |
|
||||
| `group_code` | 条件必填 | Group / Allotment 查询 key |
|
||||
| `confirmation_number` | 条件必填 | FIT Confirmation Number 查询 key |
|
||||
| `reservation_no` | 条件必填 | OPERA reservation no;当前系统无可靠表源,只传该字段时会返回人工复核原因 |
|
||||
| `object_type_hint` | 否 | 调用方推测的对象类型,只作为提示 |
|
||||
| `target_key_source` | 否 | key 来源,例如 `body_current`、`body_thread_evidence` |
|
||||
| `body_thread_used_only_as_evidence` | 否 | 历史线程 key 是否仅作为证据 |
|
||||
|
||||
`group_code`、`confirmation_number`、`reservation_no` 至少一个非空。当前稳定查询能力优先支持 `group_code` 和 `confirmation_number`。
|
||||
|
||||
全局上下文查询只依赖 `hotel_id + 业务 key`;`source_message_id` 和 `source_event_index` 仅用于审计、追踪或排查,不作为查询边界。
|
||||
|
||||
### 3.3 成功响应
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"request_id": "req-001",
|
||||
"trace_id": "trace-001",
|
||||
"data": {
|
||||
"matched_order_records": [],
|
||||
"pending_or_open_tasks": [],
|
||||
"active_workflows": [],
|
||||
"terminated_records": [],
|
||||
"target_object_validation": {
|
||||
"status": "none",
|
||||
"matched_object_id": null,
|
||||
"matched_object_type": null,
|
||||
"can_create_new_booking_task": true,
|
||||
"can_create_update_task": false,
|
||||
"can_create_cancel_task": false,
|
||||
"can_attach_voucher": false,
|
||||
"can_attach_rooming_list": false,
|
||||
"needs_manual_review_reason": null
|
||||
},
|
||||
"key_relationships": {
|
||||
"group_code_and_confirmation_same_object": null,
|
||||
"relationship_evidence": ""
|
||||
}
|
||||
},
|
||||
"warnings": [],
|
||||
"error": null
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 主要数据来源
|
||||
|
||||
| 返回字段 | 来源 |
|
||||
| --- | --- |
|
||||
| `matched_order_records[]` | `workflow_reservation_order` |
|
||||
| `pending_or_open_tasks[]` | `workflow_reservation_task` + `workflow_reservation_ai_transition` |
|
||||
| `terminated_records[]` | 订单 `ENDED` / `LOGIC_DELETED`,任务 `FAILED` / `COMPLETED` |
|
||||
| `active_workflows[]` | 当前无独立 workflow 表,固定空数组 |
|
||||
|
||||
## 4. 接口 2:查询对象详情
|
||||
|
||||
### 4.1 请求
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| Method | `POST` |
|
||||
| URL | `{TH_HOTEL_API_BASE_URL}/api/ai-query/v1/object-detail` |
|
||||
| request_path | `/api/ai-query/v1/object-detail` |
|
||||
| Content-Type | `application/json` |
|
||||
| 业务动作 | 只读查询对象详情,不创建任务、不修改订单、不写 OPERA |
|
||||
|
||||
### 4.2 请求体
|
||||
|
||||
```json
|
||||
{
|
||||
"hotel_id": "HOTEL-TEST",
|
||||
"object_id": "ORDER:1900000000000000100",
|
||||
"object_type": "group_block"
|
||||
}
|
||||
```
|
||||
|
||||
字段说明:
|
||||
|
||||
| 字段 | 是否必填 | 中文说明 |
|
||||
| --- | --- | --- |
|
||||
| `hotel_id` | 是 | 酒店上下文 ID |
|
||||
| `object_id` | 是 | 查询对象 ID,第一版只支持 `ORDER:{order_id}` |
|
||||
| `object_type` | 否 | 调用方对象类型提示,第一版不作为强校验 |
|
||||
|
||||
### 4.3 成功响应
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"request_id": "req-002",
|
||||
"trace_id": "trace-001",
|
||||
"data": {
|
||||
"object_id": "ORDER:1900000000000000100",
|
||||
"object_type": "group_block",
|
||||
"order_id": "1900000000000000100",
|
||||
"order_key_type": "GROUP_CODE",
|
||||
"group_code": "GRP-001",
|
||||
"confirmation_number": null,
|
||||
"reservation_no": null,
|
||||
"block_id": null,
|
||||
"temporary_order_code": "TMP-1900000000000000100",
|
||||
"display_name": "GRP-001",
|
||||
"status": "ACTIVE",
|
||||
"source_message_id": "1900000000000000001",
|
||||
"created_from_task_id": null,
|
||||
"created_at": "2026-07-08T01:00:00",
|
||||
"last_updated_at": "2026-07-08T01:10:00",
|
||||
"arrival_date": null,
|
||||
"departure_date": null,
|
||||
"nights": null,
|
||||
"guest_count": null,
|
||||
"room_items": [],
|
||||
"rate_code": null,
|
||||
"rate_code_price": null,
|
||||
"reservation_type": null,
|
||||
"cancel_status": "not_cancelled",
|
||||
"can_update": true,
|
||||
"can_cancel": true,
|
||||
"hard_validation_warnings": [
|
||||
{
|
||||
"code": "OPERA_PROJECTION_UNAVAILABLE",
|
||||
"message": "当前系统尚未接入 OPERA 对象投影,日期、房型、房价等字段无法确认。"
|
||||
}
|
||||
]
|
||||
},
|
||||
"warnings": [],
|
||||
"error": null
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 接口 3:SuperAgent 通知 AI 任务结果
|
||||
|
||||
### 5.1 请求
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| Method | `POST` |
|
||||
| URL | `{TH_HOTEL_API_BASE_URL}/api/integrations/superagent/task-results` |
|
||||
| request_path | `/api/integrations/superagent/task-results` |
|
||||
| Content-Type | `application/json` |
|
||||
| 业务动作 | 接收 AI 任务结果,写入 AI 过渡层、订单、任务和任务卡 |
|
||||
|
||||
### 5.2 请求体
|
||||
|
||||
```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": []
|
||||
}
|
||||
```
|
||||
|
||||
字段说明:
|
||||
|
||||
| 字段 | 是否必填 | 中文说明 |
|
||||
| --- | --- | --- |
|
||||
| `source_message_id` | 是 | 本系统 SourceMessage ID,一次请求只能有一个 |
|
||||
| `ai_task_results[]` | 是 | AI 拆分出的任务结果列表,必须保留数组顺序 |
|
||||
| `ai_task_results[].source_event_index` | 是 | AI current 事件序号 |
|
||||
| `ai_task_results[].catalog_code` | 是 | Skill 目录代码 |
|
||||
| `ai_task_results[].skill_id` | 是 | Skill 标识 |
|
||||
| `ai_task_results[].result_type` | 是 | `normal_task`、`manual_review`、`informational_message` |
|
||||
| `ai_task_results[].task_type` | 是 | AI 原始任务类型 |
|
||||
| `ai_task_results[].task_subtype` | 否 | 业务动作 subtype |
|
||||
| `ai_task_results[].case_keys` | 否 | 订单关联候选键 |
|
||||
| `ai_task_results[].extracted_fields` | 否 | 业务字段主体 |
|
||||
|
||||
### 5.3 成功响应
|
||||
|
||||
```json
|
||||
{
|
||||
"request_id": "req-003",
|
||||
"source_message_id": "1900000000000000001",
|
||||
"batch_id": "1900000000000000200",
|
||||
"idempotent_replay": false,
|
||||
"accepted_count": 1,
|
||||
"items": [
|
||||
{
|
||||
"source_event_index": 1,
|
||||
"array_index": 1,
|
||||
"order_id": "1900000000000000300",
|
||||
"task_id": "1900000000000000400",
|
||||
"task_status": "PENDING_CONFIRM",
|
||||
"order_status": "TEMPORARY",
|
||||
"system_task_type": "NEW_BOOKING",
|
||||
"task_card_type": "NEW_BOOKING",
|
||||
"warnings": []
|
||||
}
|
||||
],
|
||||
"warnings": []
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 错误响应
|
||||
|
||||
### 6.1 查询接口错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"request_id": "req-001",
|
||||
"trace_id": "trace-001",
|
||||
"data": null,
|
||||
"warnings": [],
|
||||
"error": {
|
||||
"code": "AUTH_SIGNATURE_INVALID",
|
||||
"message": "签名校验失败。",
|
||||
"details": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 任务结果通知接口错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"request_id": null,
|
||||
"error_code": "AUTH_SIGNATURE_INVALID",
|
||||
"message": "签名校验失败。",
|
||||
"details": []
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 常见错误码
|
||||
|
||||
| 错误码 | HTTP 状态 | 中文说明 |
|
||||
| --- | --- | --- |
|
||||
| `AUTH_HEADER_MISSING` | 401 | HMAC 必要 Header 缺失 |
|
||||
| `AUTH_HEADER_INVALID` | 401 | HMAC Header 格式或长度无效 |
|
||||
| `AUTH_TIMESTAMP_INVALID` | 401 | timestamp 格式错误或超出时间窗口 |
|
||||
| `AUTH_SIGNATURE_INVALID` | 401 | 签名不匹配或服务端未配置 secret |
|
||||
| `AUTH_NONCE_REPLAY` | 409 | nonce 已被使用 |
|
||||
| `REQUEST_BODY_TOO_LARGE` | 413 | 请求体超过大小限制 |
|
||||
| `REQUEST_CONTENT_TYPE_UNSUPPORTED` | 415 | `Content-Type` 不是 `application/json` |
|
||||
| `REQUEST_BODY_INVALID` | 400 | JSON 不合法 |
|
||||
| `QUERY_KEY_REQUIRED` | 400 | 查询接口缺少可用业务 key |
|
||||
| `OBJECT_NOT_FOUND` | 404 | 对象详情查询目标不存在 |
|
||||
| `SOURCE_MESSAGE_NOT_FOUND` | 400 | 任务结果通知引用的 SourceMessage 不存在 |
|
||||
|
||||
## 7. HMAC 上线配置
|
||||
|
||||
上线需要配置:
|
||||
|
||||
| 配置项 | 是否必填 | 中文说明 |
|
||||
| --- | --- | --- |
|
||||
| `SUPERAGENT_TASK_RESULT_HMAC_SECRET` | 是 | HMAC 共享密钥;查询接口和任务结果通知接口共用 |
|
||||
| `SUPERAGENT_TASK_RESULT_CLOCK_SKEW_SECONDS` | 否 | 请求时间允许偏移,默认 `300` 秒 |
|
||||
| `SUPERAGENT_TASK_RESULT_NONCE_TTL_SECONDS` | 否 | nonce 防重放保存时间,默认 `600` 秒 |
|
||||
| `SUPERAGENT_TASK_RESULT_MAX_BODY_BYTES` | 否 | 请求体最大字节数,默认 `1048576` |
|
||||
|
||||
上线注意事项:
|
||||
|
||||
- 本系统和 SuperAgent 必须配置同一个 HMAC secret。
|
||||
- secret 只能放在部署平台 Secret 或环境变量中,不能写入仓库、镜像、前端配置或普通文档。
|
||||
- SuperAgent 必须使用原始请求体计算 SHA-256,不能使用格式化后 JSON。
|
||||
- HMAC canonical string 的第二行必须使用 `request_path`,例如 `/api/ai-query/v1/case-context`。
|
||||
- SuperAgent 每次请求必须生成全新的 nonce;同一个 `client_id + nonce` 在 TTL 窗口内不能重复使用。
|
||||
- 调用双方服务器时间必须同步,建议使用 NTP。
|
||||
- 建议所有接口只暴露在 HTTPS 和可信网络边界内。
|
||||
- 轮换 secret 时需要安排双写或短窗口切换;当前第一版后端只支持一个 secret,轮换窗口内需要协调发布顺序。
|
||||
Reference in New Issue
Block a user