Files
ai-customer-service/docs/plans/2026-05-06-newapi-sub2api-adapter-design.md
2026-05-06 10:45:51 +08:00

11 KiB
Raw Blame History

NewAPI / Sub2API 适配增强设计

日期2026-05-06
状态:设计稿
适用项目:projects/ai-customer-service
设计边界:最小接入层、内置适配器、入站 + 异步全事件流回写、Sub2API 优先、准可靠投递


1. 目标与边界

本设计解决的问题不是“把 ai-customer-service 做成另一个 NewAPI/Sub2API”而是让它能够稳定挂接在 NewAPI/Sub2API 后面,作为客服能力子系统运行。当前代码已经具备 webhook、会话、意图、转人工、工单、审计、去重、PostgreSQL 落库、Gate B/Gate C 脚本化验证等底座,缺的是把外部平台原生消息接进来、再把内部处理结果以平台可消费的事件流回推出去的适配层。

第一版范围严格限制为:

  1. Sub2API 优先NewAPI 保持同构兼容位,不追求双平台一次做满。
  2. 内置适配器,不新增外部 shim 作为主路径。
  3. 入站适配:把平台原生消息转换为 UnifiedMessage 并进入现有主链。
  4. 出站回写:把内部处理结果、工单、错误、回调状态转成异步事件回推给上游平台。
  5. 准可靠投递:事件持久化、重试、死信/补偿到位,但不追求复杂的跨系统 exactly-once。

明确不做的内容:

  1. 完整平台级管理后台
  2. 知识库共享 API 的全量产品化
  3. NewAPI/Sub2API 全量管理协议一比一兼容
  4. 任意平台原生结构透传

结论是:第一版目标是“可稳定接入和可观测回推”,不是“完整兼容替代”。


2. 总体架构

推荐架构是在现有 HTTP 入口和对话主链之间插入一个平台适配层Platform Adapter Layer,并在主链处理完成后插入一个事件出站层Event Outbox + Delivery Layer。这样可以保持当前客服核心逻辑不被平台协议污染,同时把平台差异收口在边缘。

逻辑结构如下:

Sub2API / NewAPI
   -> Platform Ingress Handler
   -> Adapter Registry
   -> Platform Adapter (normalize)
   -> UnifiedMessage
   -> dialog / intent / handoff / ticket / audit / dedup
   -> Internal Domain Events
   -> Event Outbox
   -> Delivery Worker
   -> Platform Callback Endpoint

核心原则:

  1. 核心主链不感知平台细节
    dialog.Service 继续只消费 UnifiedMessage,不直接理解 Sub2API/NewAPI 原生字段。

  2. 适配逻辑边缘化
    平台差异集中在 adapter 目录中,用接口抽象隔离。

  3. 事件先落库再投递
    所有异步回调事件进入 outbox 后再由 worker 重试发送,避免平台短时不可用导致结果丢失。

  4. 同步 HTTP 只做最小确认
    入站请求同步返回“收到并入链”的最小响应,不在主请求路径里等待整条回调链路完成。

这样做的收益是:现有 webhook 主链、Gate B/Gate C 验证、鉴权、工单状态机都可以复用,不需要重写核心业务。


3. 入站适配设计

第一版入站适配增加一个新的入口族,而不是强行把平台原生大包塞进现有 UnifiedMessage handler。建议新增

POST /api/v1/customer-service/platforms/{platform}/webhook
POST /api/v1/customer-service/platforms/{platform}/webhook/{channel}

其中 {platform} 第一版支持:

  1. sub2api
  2. newapi(保留同构位,可先实现最小 profile

当前状态补充:

  • sub2api 已完成第一版最小接入、outbox、callback worker、dead letter 和 E2E 验证
  • newapi 当前仅保留同构 adapter profile占位返回 501 profile not implemented

新增接口:

type PlatformAdapter interface {
    Platform() string
    ParseInbound(*http.Request, []byte, IngressContext) (*message.UnifiedMessage, *PlatformInboundMeta, error)
    BuildIngressAck(*dialog.Result, *PlatformInboundMeta) any
}

设计要点:

  1. 平台原生请求体不再直接喂给现有 webhook handler
    先在 adapter 里裁剪、校验、映射,再构造 UnifiedMessage

  2. 保留平台元数据 PlatformInboundMeta 记录:

    • platform
    • tenant / app / upstream endpoint
    • raw event id
    • callback target
    • callback auth profile
    • source user/session ids
  3. 统一进入现有主链 Adapter 输出只允许是干净的 UnifiedMessage,这样 dialog.Service、dedup、ticket、audit 无需大改。

  4. 同步确认最小化 入站 HTTP 响应只表达:

    • accepted
    • event_id
    • session_id(如果已生成) 不承担完整业务结果回写职责。

Sub2API 优先意味着第一版先针对 tksea 场景定义一个明确的 inbound profile而不是试图抽象所有平台差异。


4. 出站全事件流设计

你明确要求第一版不是只回最终结果,而是做全事件流异步回调。这意味着需要在内部定义一个稳定的事件模型,而不是拿日志拼 webhook。

建议的事件类型:

  1. message.received
  2. message.rejected
  3. message.deduplicated
  4. message.processing
  5. intent.resolved
  6. handoff.triggered
  7. ticket.created
  8. ticket.assigned
  9. ticket.resolved
  10. ticket.closed
  11. reply.generated
  12. callback.delivered
  13. callback.failed

事件统一结构建议:

{
  "event_id": "uuid",
  "event_type": "reply.generated",
  "platform": "sub2api",
  "occurred_at": "2026-05-06T12:00:00Z",
  "session_id": "uuid",
  "ticket_id": "uuid",
  "source_message_id": "platform-msg-id",
  "attempt": 1,
  "payload": {}
}

关键设计点:

  1. 事件类型稳定、字段尽量固定
  2. 事件 payload 面向平台消费,而不是内部 debug
  3. 每条事件必须有 event_id 供下游幂等
  4. reply / handoff / ticket 是关键事件,必须可补偿重放

这样第一版虽然不是完整平台集成,但已经具备后续扩展到状态同步、工单联动和运营侧诊断的事件基础。


5. 准可靠投递设计

你选择的是“准可靠投递”,这决定了我们不能把异步回调只做成 best-effort。推荐实现是Outbox + Delivery Worker + Retry Policy + Dead Letter

新增持久化表建议:

  1. cs_platform_callbacks
    • 配置每个 platform target 的回调地址、签名方式、启停状态
  2. cs_platform_event_outbox
    • 存放待投递事件
  3. cs_platform_event_delivery_attempts
    • 存放每次尝试结果
  4. cs_platform_event_dead_letters
    • 存放超出重试上限的事件

投递策略:

  1. 业务主链中先生成事件并落 outbox
  2. 后台 worker 轮询领取事件
  3. 成功后标记 delivered
  4. 失败后指数退避重试
  5. 达到上限后进入 dead letter
  6. 提供人工或脚本重放入口

推荐默认策略:

  1. 首次立即投递
  2. 之后 10s / 30s / 60s / 5m / 15m
  3. 最多 5 次
  4. 超过进入 dead letter

这不是严格 exactly-once但对第一版已经足够现实

  • 上游通过 event_id 幂等
  • 我们保证“不轻易丢”
  • 重试/死信让失败可追踪可恢复

6. 配置与安全设计

适配层要想落地,配置必须从“单 webhook secret”提升为“平台适配配置”。建议新增

AI_CS_PLATFORM_ADAPTERS_ENABLED=true
AI_CS_PLATFORM_SUB2API_ENABLED=true
AI_CS_PLATFORM_SUB2API_INGRESS_SECRET=...
AI_CS_PLATFORM_SUB2API_CALLBACK_BASE_URL=...
AI_CS_PLATFORM_SUB2API_CALLBACK_SECRET=...
AI_CS_PLATFORM_SUB2API_CALLBACK_TIMEOUT_MS=3000
AI_CS_PLATFORM_SUB2API_CALLBACK_MAX_RETRIES=5
AI_CS_PLATFORM_NEWAPI_ENABLED=false

安全要求:

  1. 入站鉴权
    平台入口不能复用当前通用 webhook 约束的最小集合就草率上线,必须明确平台级 secret/profile。

  2. 出站签名 回调给 Sub2API/NewAPI 的事件也要带时间戳与签名,避免被伪造。

  3. 最小字段原则 只回推平台真正需要的字段,不把完整上下文、敏感用户数据默认外发。

  4. 审计闭环 所有 callback 失败、重试、死信、重放都进入 audit 或独立 delivery attempts 表。

安全上最重要的一条是:

平台适配层必须是“显式启用、显式配置、显式审计”的能力,不允许默认裸开。


7. 测试与门禁设计

第一版适配增强必须新增独立测试层,而不能只靠现有 webhook 测试顺带覆盖。

建议测试分层:

  1. Unit

    • 平台原生 payload -> UnifiedMessage 映射
    • callback payload 组装
    • 签名算法
    • 重试策略
  2. Integration

    • 平台入站请求 -> 主链处理 -> outbox 落库
    • outbox -> callback mock server
    • 失败重试 -> dead letter
  3. E2E

    • Sub2API mock 发原生消息
    • ai-customer-service 创建 session / ticket / audit
    • callback mock 收到全事件流

第一版阻断门禁建议至少包含:

  1. sub2api 最小接入 happy path
  2. message_id 去重 path
  3. 未知字段/非法签名 path
  4. callback 5xx 重试 path
  5. callback 最终 dead letter path
  6. 回滚后 callback 恢复 path

这里要特别强调:

当前 tech/TEST_DESIGN.md 里 NewAPI/Sub2API 适配验证还是待实现项,第一版增强后必须把它提升为真正可执行的合同测试和联调测试,而不是继续停留在文档层。


8. 分阶段实施建议

为了不把当前 Phase 1 拖爆,建议按 3 个 implementation batch 执行:

Batch 1Sub2API 入站最小适配

  1. 新增 /platforms/sub2api/webhook
  2. 新增 adapter 接口和 sub2api profile
  3. 原生 payload -> UnifiedMessage
  4. 复用现有主链
  5. 单测 + 集成测试

Batch 2事件 outbox 与异步回调

  1. 设计事件模型
  2. 新增 outbox 表
  3. 新增 worker
  4. 新增 callback 签名与投递
  5. 失败重试 + dead letter

Batch 3NewAPI profile 与运维可观测

  1. 新增 newapi adapter profile
  2. 新增 delivery metrics / dashboard
  3. 新增重放工具与 runbook
  4. 补 Gate B / Gate C 适配层联调门禁

这个顺序的理由很简单:

  1. 先把 Sub2API 场景跑通
  2. 再把异步事件流做稳
  3. 最后复用同一套抽象支持 NewAPI

9. 最终建议

我推荐按这份设计推进,因为它满足四个约束:

  1. 符合项目规划:确实开始支持 NewAPI/Sub2API
  2. 不破坏当前主链:平台差异不侵入核心客服逻辑
  3. 可先解决 tksea / Sub2API 的真实问题:不是空转设计
  4. 可灰度实施Batch 1 完成就能先验证最小接入

最终建议一句话概括:

把 NewAPI/Sub2API 支持做成“内置适配器 + 事件 outbox”的最小集成层而不是把 ai-customer-service 重做成另一个平台。

下一步如果继续,最合理的是直接基于这份设计拆 implementation plan而不是直接开写代码。