实现酒店上下文单酒店收口

This commit is contained in:
andy
2026-07-10 15:31:51 +08:00
parent 7492c21350
commit d69f3975ef
52 changed files with 1183 additions and 188 deletions

View File

@@ -268,8 +268,8 @@ AGENTBUS_SAMPLE_DIR=var/agentbus-samples
AGENTBUS_MAX_FRAME_BYTES=1048576
AGENTBUS_MAX_SAMPLES=100
AGENTBUS_CAPTURE_ENABLED=true
AGENTBUS_DEFAULT_HOTEL_ID=HOTEL-TEST
AGENTBUS_REPLY_MODE=NONE
AUTH_DEV_BOOTSTRAP_DEFAULT_HOTEL_ID=HOTEL-DEV
SOURCE_MESSAGE_DEV_ORIGINAL_READ_ACCESS_KEY=
SOURCE_MESSAGE_ORIGINAL_READ_ACCESS_KEY=
```
@@ -289,7 +289,7 @@ SOURCE_MESSAGE_ORIGINAL_READ_ACCESS_KEY=
| `AGENTBUS_MAX_FRAME_BYTES` | 否 | 单个入站 frame 最大字节数。 |
| `AGENTBUS_MAX_SAMPLES` | 否 | 最多保留的本地样本数。 |
| `AGENTBUS_CAPTURE_ENABLED` | 否 | 是否写入 SourceMessage Inbox。 |
| `AGENTBUS_DEFAULT_HOTEL_ID` | 否 | AgentBus 未提供租户上下文时的默认业务上下文。 |
| `AUTH_DEV_BOOTSTRAP_DEFAULT_HOTEL_ID` | 否 | dev 初始化平台酒店M005 后 AgentBus 捕获运行时从 `platform_hotel` 唯一 `ACTIVE` 酒店解析系统酒店,不再依赖 `AGENTBUS_DEFAULT_HOTEL_ID`。 |
| `AGENTBUS_REPLY_MODE` | 否 | 调试回复模式。真实客户渠道应保持 `NONE`。 |
| `SOURCE_MESSAGE_DEV_ORIGINAL_READ_ACCESS_KEY` | 是 | dev 原文读取接口的临时受控访问 key后续可替换为正式权限体系。 |
| `SOURCE_MESSAGE_TEST_ORIGINAL_READ_ACCESS_KEY` | 是 | test 原文读取接口的临时受控访问 key。 |
@@ -390,6 +390,8 @@ reply_policy.final_only
hotel_id + provider + channel + external_message_id
```
中文说明M005 后 `hotel_id` 由本系统后端解析,不要求 AgentBus payload 携带酒店;单酒店阶段要求 `platform_hotel` 只有一家 `ACTIVE` 酒店。
重复投递时返回已有 Inbox不覆盖原始 payload不创建重复记录。
如果 payload 缺少必要字段或格式不符合预期,也应保存为 `FAILED` Inbox并记录安全错误

View File

@@ -4,9 +4,9 @@
| 项目 | 内容 |
| --- | --- |
| 文档版本 | 0.4 |
| 文档版本 | 0.5 |
| 日期 | 2026-07-10 |
| 状态 | 已增加 S000/S999 特殊入口结果处理 |
| 状态 | 已增加 S000/S999 特殊入口结果处理,并完成单酒店阶段 hotel_id 后端解析收口 |
| 适用范围 | SuperAgent 调用本系统查询上下文、查询邮件会话、提交 AI 任务结果 |
| 主要读者 | SuperAgent 对接方、后端、测试、运维 |
@@ -17,7 +17,7 @@
| 参数 | 当前联调值 | 中文说明 |
| --- | --- | --- |
| `TH_HOTEL_API_BASE_URL` | `http://8.138.234.141:18087` | 本系统后端基础地址;本地联调用 8080部署环境改为实际网关或服务地址。 |
| `HOTEL_ID` | `HOTEL-DEV` | 当前 dev profile 下 AgentBus 入库默认酒店 ID查询接口和 JSON 任务结果请求体应传入 `hotel_id`S000/S999 文本结果使用本系统默认酒店。 |
| `SYSTEM_HOTEL` | 后端平台酒店表唯一 `ACTIVE` 酒店 | SuperAgent 不需要配置或传入 `hotel_id`;单酒店阶段由 TH Hotel 后端从 `platform_hotel` 解析。 |
| `SUPERAGENT_CLIENT_ID` | `superagent-debug` | SuperAgent 调用方 ID对应 Header `X-TH-Hotel-SuperAgent-Client-Id`。 |
| `SUPERAGENT_HMAC_SECRET` | `th-hotel-superagent-debug-20260709-change-before-prod` | dev/test 联调临时 HMAC 密钥;生产上线前必须更换为新的高强度随机密钥。 |
@@ -99,11 +99,11 @@ X-TH-Hotel-SuperAgent-Signature: sha256=<lowercase-hex-signature>
| 外部来源消息 ID | AgentBus 邮件 payload 中的 `source.external_message_id`SuperAgent / Main Agent 在最终 JSON 中原样带回为 `source_message_id` | SuperAgent 任务结果通知接口入参和响应回显 |
| 内部 SourceMessage Inbox ID | `platform_source_message_inbox.id`,本系统数据库内部主键 | `workflow_*` 表的 `source_message_id` 外键、前端和运维排查 |
SuperAgent 不应知道或依赖内部 SourceMessage Inbox ID。任务结果通知接口收到外部 `source_message_id` 后,后端使用 `hotel_id + provider + channel + external_message_id` 反查内部 Inbox 记录,用内部 ID 写入业务表。
SuperAgent 不应知道或依赖内部 SourceMessage Inbox ID。任务结果通知接口收到外部 `source_message_id` 后,后端先解析系统酒店,再使用 `hotel_id + provider + channel + external_message_id` 反查内部 Inbox 记录,最后用内部 ID 写入业务表。
查询接口 1、2 在 SuperAgent 查询阶段不依赖当前邮件是否已经入库。若请求体兼容旧契约传入 `source_message_id``source_event_index`,第一版后端会接收但忽略,不校验它们的格式,也不把它们作为查询边界。
查询接口 3、4 面向已经入库的邮件会话:`external_conversation_id``hotel_id + source_provider + source_channel + external_conversation_id` 查询;`source_message_id` 表示外部来源消息 ID可作为锚点反查该邮件所属会话。
查询接口 3、4 面向已经入库的邮件会话:缺省酒店由后端解析,`external_conversation_id` 最终仍`hotel_id + source_provider + source_channel + external_conversation_id` 查询;`source_message_id` 表示外部来源消息 ID可作为锚点反查该邮件所属会话。
## 4. 接口 1查询订单上下文
@@ -121,7 +121,6 @@ SuperAgent 不应知道或依赖内部 SourceMessage Inbox ID。任务结果通
```json
{
"hotel_id": "<HOTEL_ID>",
"group_code": "GRP-001",
"confirmation_number": null,
"reservation_no": null,
@@ -135,7 +134,7 @@ SuperAgent 不应知道或依赖内部 SourceMessage Inbox ID。任务结果通
| 字段 | 是否必填 | 中文说明 |
| --- | --- | --- |
| `hotel_id` | | 酒店上下文 ID |
| `hotel_id` | | 酒店上下文 IDSuperAgent 默认不传,后端按平台酒店表唯一 `ACTIVE` 酒店解析。若兼容旧契约传入,单酒店阶段必须与系统酒店一致。 |
| `group_code` | 条件必填 | Group / Allotment 查询 key |
| `confirmation_number` | 条件必填 | FIT Confirmation Number 查询 key |
| `reservation_no` | 条件必填 | OPERA reservation no当前系统无可靠表源只传该字段时会返回人工复核原因 |
@@ -145,7 +144,7 @@ SuperAgent 不应知道或依赖内部 SourceMessage Inbox ID。任务结果通
`group_code``confirmation_number``reservation_no` 至少一个非空。当前稳定查询能力优先支持 `group_code``confirmation_number`
全局上下文查询只依赖 `hotel_id + 业务 key``source_message_id``source_event_index` 不作为查询边界,传入时也不会影响查询结果。
全局上下文查询最终依赖“后端解析出的酒店 ID + 业务 key`source_message_id``source_event_index` 不作为查询边界,传入时也不会影响查询结果。
### 4.3 成功响应
@@ -205,7 +204,6 @@ SuperAgent 不应知道或依赖内部 SourceMessage Inbox ID。任务结果通
```json
{
"hotel_id": "<HOTEL_ID>",
"object_id": "ORDER:1900000000000000100",
"object_type": "group_block"
}
@@ -215,7 +213,7 @@ SuperAgent 不应知道或依赖内部 SourceMessage Inbox ID。任务结果通
| 字段 | 是否必填 | 中文说明 |
| --- | --- | --- |
| `hotel_id` | | 酒店上下文 ID |
| `hotel_id` | | 酒店上下文 IDSuperAgent 默认不传,后端解析系统酒店。 |
| `object_id` | 是 | 查询对象 ID第一版只支持 `ORDER:{order_id}` |
| `object_type` | 否 | 调用方对象类型提示,第一版不作为强校验 |
@@ -285,7 +283,6 @@ SuperAgent 不应知道或依赖内部 SourceMessage Inbox ID。任务结果通
```json
{
"hotel_id": "<HOTEL_ID>",
"source_provider": "AGENTBUS",
"source_channel": "EMAIL",
"external_conversation_id": "thread-20260708-0001"
@@ -296,7 +293,6 @@ SuperAgent 不应知道或依赖内部 SourceMessage Inbox ID。任务结果通
```json
{
"hotel_id": "<HOTEL_ID>",
"source_provider": "AGENTBUS",
"source_channel": "EMAIL",
"source_message_id": "mail-20260708-0001"
@@ -307,7 +303,7 @@ SuperAgent 不应知道或依赖内部 SourceMessage Inbox ID。任务结果通
| 字段 | 是否必填 | 中文说明 |
| --- | --- | --- |
| `hotel_id` | | 酒店上下文 ID |
| `hotel_id` | | 酒店上下文 IDSuperAgent 默认不传,后端解析系统酒店。 |
| `source_provider` | 否 | 来源提供方,按会话 ID 查询和按 `source_message_id` 反查时都参与隔离,缺省为 `AGENTBUS` |
| `source_channel` | 否 | 来源渠道,按会话 ID 查询和按 `source_message_id` 反查时都参与隔离,缺省为 `EMAIL` |
| `external_conversation_id` | 条件必填 | 外部邮件会话 ID对应 AgentBus `source.external_conversation_id` |
@@ -383,7 +379,6 @@ SuperAgent 不应知道或依赖内部 SourceMessage Inbox ID。任务结果通
```json
{
"hotel_id": "<HOTEL_ID>",
"source_provider": "AGENTBUS",
"source_channel": "EMAIL",
"source_message_id": "mail-20260708-0001"
@@ -444,7 +439,6 @@ SuperAgent 不应知道或依赖内部 SourceMessage Inbox ID。任务结果通
```json
{
"hotel_id": "<HOTEL_ID>",
"source_message_id": "mail-20260708-0001",
"ai_task_results": [
{
@@ -479,7 +473,7 @@ SuperAgent 不应知道或依赖内部 SourceMessage Inbox ID。任务结果通
| 字段 | 是否必填 | 中文说明 |
| --- | --- | --- |
| `hotel_id` | | 酒店上下文 ID用于反查 SourceMessage Inbox 幂等键 |
| `hotel_id` | | 酒店上下文 IDSuperAgent 默认不传,后端解析系统酒店后用于反查 SourceMessage Inbox 幂等键。若兼容旧契约传入,单酒店阶段必须与系统酒店一致。 |
| `source_message_id` | 是 | SuperAgent / Main Agent 原样带回的外部来源消息 ID对应 AgentBus `source.external_message_id`,一次请求只能有一个 |
| `source_provider` | 否 | 来源提供方,第一版缺省为 `AGENTBUS` |
| `source_channel` | 否 | 来源渠道,第一版缺省为 `EMAIL` |
@@ -493,7 +487,7 @@ SuperAgent 不应知道或依赖内部 SourceMessage Inbox ID。任务结果通
| `ai_task_results[].case_keys` | 否 | 订单关联候选键 |
| `ai_task_results[].extracted_fields` | 否 | 业务字段主体 |
正式联调时,后端通过 `hotel_id + source_provider(默认 AGENTBUS) + source_channel(默认 EMAIL) + source_message_id` 查找 `platform_source_message_inbox.external_message_id`。如果没有找到,返回 `SOURCE_MESSAGE_NOT_FOUND`。本地旧夹具允许在缺少 `hotel_id` 时使用内部数字 SourceMessage ID但该兼容路径不作为 SuperAgent 正式契约。
正式联调时,SuperAgent 不需要传 `hotel_id`。后端通过系统酒店 `hotel_id + source_provider(默认 AGENTBUS) + source_channel(默认 EMAIL) + source_message_id` 查找 `platform_source_message_inbox.external_message_id`。如果没有找到,返回 `SOURCE_MESSAGE_NOT_FOUND`。本地旧夹具允许在缺少 `hotel_id` 时使用内部数字 SourceMessage ID但该兼容路径不作为 SuperAgent 正式契约。
`informational_message` 结构化任务仅用于历史兼容。新入口如果是纯信息类邮件或无法形成业务素材包,不要提交空数组,也不要生成 `informational_message`;应使用下面的 S000/S999 文本请求体。
@@ -519,7 +513,7 @@ S999,mail-20260708-0001
| `S999` | 入口阶段无法形成业务素材包,不需要进入业务执行。 |
| `mail-20260708-0001` | 外部来源消息 ID对应 SourceMessage Inbox 的 `external_message_id`。 |
第一版 S000/S999 不在 body 里传 `hotel_id`,后端使用系统默认酒店 `AGENTBUS_DEFAULT_HOTEL_ID` 查询 SourceMessage Inbox。命中后创建 `SOURCE_MESSAGE_ONLY` 只读特殊任务:任务列表可见,订单列表不可见,不允许编辑、确认、转换订单、执行 OPERA 或重试 OPERA也不参与同订单任务执行顺序阻塞。
第一版 S000/S999 不在 body 里传 `hotel_id`,后端使用平台酒店表唯一 `ACTIVE` 酒店查询 SourceMessage Inbox。命中后创建 `SOURCE_MESSAGE_ONLY` 只读特殊任务:任务列表可见,订单列表不可见,不允许编辑、确认、转换订单、执行 OPERA 或重试 OPERA也不参与同订单任务执行顺序阻塞。
### 8.4 成功响应
@@ -621,7 +615,9 @@ S000/S999 成功响应示例:
| `MESSAGE_CONVERSATION_QUERY_KEY_REQUIRED` | 400 | 会话查询缺少 `external_conversation_id``source_message_id` |
| `OBJECT_NOT_FOUND` | 404 | 对象详情查询目标不存在 |
| `MESSAGE_CONVERSATION_NOT_FOUND` | 404 | 外部邮件会话尚未写入 SourceMessage Inbox |
| `HOTEL_ID_REQUIRED` | 400 | 查询接口和 JSON 任务结果缺少必填 `hotel_id`S000/S999 使用系统默认酒店 |
| `SYSTEM_HOTEL_NOT_CONFIGURED` | 409 | 平台酒店表没有可用 `ACTIVE` 酒店 |
| `SYSTEM_HOTEL_AMBIGUOUS` | 409 | 单酒店阶段平台酒店表存在多家 `ACTIVE` 酒店 |
| `HOTEL_ACCESS_DENIED` | 403 | 显式传入的 `hotel_id` 与系统酒店或当前用户授权酒店不一致 |
| `SOURCE_MESSAGE_NOT_FOUND` | 404 | 任务结果通知或会话锚点引用的外部来源消息尚未写入 SourceMessage Inbox |
## 10. HMAC 上线配置

View File

@@ -32,10 +32,11 @@
| 用例 ID | 场景 | 输入要点 | 期望结果 |
| --- | --- | --- | --- |
| MCP-T01-001 | 按 group code 查询 | `hotel_id` + `group_code` | 返回成功 envelope |
| MCP-T01-002 | 按 confirmation 查询 | `hotel_id` + `confirmation_number` | 返回成功 envelope |
| MCP-T01-003 | 缺少查询 key | 只有 `hotel_id` | 返回 `QUERY_KEY_REQUIRED` |
| MCP-T01-004 | 缺 hotel id | 不传 `hotel_id` | 返回 `HOTEL_ID_REQUIRED` |
| MCP-T01-001 | 按 group code 查询 | `group_code``hotel_id` 可选 | 返回成功 envelope |
| MCP-T01-002 | 按 confirmation 查询 | `confirmation_number``hotel_id` 可选 | 返回成功 envelope |
| MCP-T01-003 | 缺少查询 key | 不传 `group_code``confirmation_number` | 返回 `QUERY_KEY_REQUIRED` |
| MCP-T01-004 | 缺 hotel id | 不传 `hotel_id`,传 `group_code` | 后端按系统唯一 ACTIVE 酒店查询并返回成功 envelope |
| MCP-T01-005 | hotel id 不一致 | 传非系统酒店 `hotel_id` | 返回 `HOTEL_ACCESS_DENIED``HOTEL_ID_MISMATCH` |
## 5. th_hotel_query_object_detail
@@ -87,8 +88,9 @@
| MCP-T05-002 | 提交 manual review | `result_type=manual_review` | 写入人工复核任务 |
| MCP-T05-003 | 提交 informational message | `result_type=informational_message` | 写入提示类信息 |
| MCP-T05-004 | source message 不存在 | 不存在的外部 `source_message_id` | 返回 `SOURCE_MESSAGE_NOT_FOUND` |
| MCP-T05-005 | 缺 hotel id | 不传 `hotel_id` | 返回 `HOTEL_ID_REQUIRED` |
| MCP-T05-005 | 缺 hotel id | 不传 `hotel_id`source message 属于系统酒店 | 后端按系统唯一 ACTIVE 酒店写入成功 |
| MCP-T05-006 | 重复提交同一幂等任务 | 使用相同幂等信息 | 不重复创建业务任务 |
| MCP-T05-007 | hotel id 不一致 | 显式传非系统酒店 `hotel_id` | 返回 `HOTEL_ID_MISMATCH` |
写入验证:

View File

@@ -17,6 +17,7 @@
| 字段 | 默认值 | 中文说明 |
| --- | --- | --- |
| `hotel_id` | 后端解析 | SuperAgent 默认不传;单酒店阶段由 TH Hotel 后端从 `platform_hotel` 唯一 `ACTIVE` 酒店解析,兼容旧调用传入时必须与系统酒店一致。 |
| `source_provider` | `AGENTBUS` | 来源提供方 |
| `source_channel` | `EMAIL` | 来源渠道 |
@@ -65,8 +66,8 @@
"additionalProperties": false,
"properties": {
"hotel_id": {
"type": "string",
"description": "酒店上下文 ID"
"type": ["string", "null"],
"description": "可选酒店上下文 ID;默认由 TH Hotel 后端解析"
},
"group_code": {
"type": ["string", "null"],
@@ -93,7 +94,7 @@
"description": "历史线程 key 是否仅作为证据"
}
},
"required": ["hotel_id"]
"required": []
}
```
@@ -135,8 +136,8 @@ POST /api/ai-query/v1/case-context
"additionalProperties": false,
"properties": {
"hotel_id": {
"type": "string",
"description": "酒店上下文 ID"
"type": ["string", "null"],
"description": "可选酒店上下文 ID;默认由 TH Hotel 后端解析"
},
"object_id": {
"type": "string",
@@ -147,7 +148,7 @@ POST /api/ai-query/v1/case-context
"description": "调用方对象类型提示"
}
},
"required": ["hotel_id", "object_id"]
"required": ["object_id"]
}
```
@@ -193,8 +194,8 @@ SuperAgent 提交任务结果时的外部 `source_message_id`。
"additionalProperties": false,
"properties": {
"hotel_id": {
"type": "string",
"description": "酒店上下文 ID"
"type": ["string", "null"],
"description": "可选酒店上下文 ID;默认由 TH Hotel 后端解析"
},
"source_provider": {
"type": ["string", "null"],
@@ -213,7 +214,7 @@ SuperAgent 提交任务结果时的外部 `source_message_id`。
"description": "外部来源消息 ID可作为锚点反查会话"
}
},
"required": ["hotel_id"]
"required": []
}
```
@@ -221,7 +222,7 @@ SuperAgent 提交任务结果时的外部 `source_message_id`。
- `external_conversation_id``source_message_id` 至少一个非空。
- 两者同时传入时,后端按 `external_conversation_id` 查询为准。
- 查询`hotel_id + source_provider + source_channel + external_conversation_id` 隔离。
- 查询最终按“后端解析出的酒店 ID + source_provider + source_channel + external_conversation_id隔离。
### 5.4 输出
@@ -282,8 +283,8 @@ POST /api/ai-query/v1/message-conversation/tasks
"additionalProperties": false,
"properties": {
"hotel_id": {
"type": "string",
"description": "酒店上下文 ID"
"type": ["string", "null"],
"description": "可选酒店上下文 ID;默认由 TH Hotel 后端解析"
},
"source_provider": {
"type": ["string", "null"],
@@ -302,7 +303,7 @@ POST /api/ai-query/v1/message-conversation/tasks
"description": "外部来源消息 ID可作为锚点反查会话"
}
},
"required": ["hotel_id"]
"required": []
}
```
@@ -334,14 +335,14 @@ POST /api/ai-query/v1/message-conversation/messages
### 7.2 何时使用
- SuperAgent 已完成当前邮件的最终任务拆分。
- 已确认 `hotel_id`外部 `source_message_id` 来自 AgentBus payload。
- 已确认外部 `source_message_id` 来自 AgentBus payload`hotel_id` 由 TH Hotel 后端解析
- 需要把 AI 任务结果交给 TH Hotel 后端进入人工确认流程。
### 7.3 不应使用
- 不应在试探、草稿、未完成推理阶段调用。
- 不应把历史邮件中的外部来源消息 ID 当作当前邮件 ID 提交。
- 不应在缺少 `hotel_id``source_message_id` 时调用。
- 不应在缺少 `source_message_id` 时调用`hotel_id` 不需要 SuperAgent 提供
### 7.4 输入 Schema
@@ -351,8 +352,8 @@ POST /api/ai-query/v1/message-conversation/messages
"additionalProperties": false,
"properties": {
"hotel_id": {
"type": "string",
"description": "酒店上下文 ID"
"type": ["string", "null"],
"description": "可选酒店上下文 ID;默认由 TH Hotel 后端解析"
},
"source_provider": {
"type": ["string", "null"],
@@ -381,7 +382,7 @@ POST /api/ai-query/v1/message-conversation/messages
}
}
},
"required": ["hotel_id", "source_message_id", "ai_task_results"]
"required": ["source_message_id", "ai_task_results"]
}
```