External Skill 能力集成
外部系统不是数据源,是可调用的能力。
职责
| 负责 | 不负责 |
|---|---|
| 注册外部能力(endpoint + 超时) | 外部系统的认证密钥管理 |
| 调用并把结果落成事实 | 重试与熔断 |
| 幂等(同输入不重复请求) | 结果缓存过期 |
| 记录快照时间与原始响应 | 复制对方的数据 |
为什么不是数据同步
| 能力集成(本模块) | 事件接入 / 数据镜像 | |
|---|---|---|
| 数据流向 | 按需问一次 | 持续同步 |
| 是否产生第二真相 | 否 | 是 |
| 审计粒度 | 每次调用一条事实 | 每条变更一条事实 |
| 失败影响 | 单次调用失败 | 管道失败可能静默 |
大多数「ERP 集成」需求其实是能力集成——只是过去没有 agent 这个概念,只能做数据同步。
调用链路
register({ skillId, endpoint, method?, timeoutMs? })
→ invoke(skillId, input, { taskId, actor })
→ 结果落 /tasks/{taskId}/external-calls/{callId}
→ 因果链能追到「谁在何时问了什么」三条规则
| # | 规则 | 实现 |
|---|---|---|
| 1 | 结果落事实 | 每次调用写一条 external-call,带 actor 与输入指纹 |
| 2 | 幂等 | callId = skillId:hash(input),同输入不重复发请求 |
| 3 | 不猜 | 非 2xx 与超时照实记录(ok=false + 原响应) |
接口
| Method | Path | 说明 |
|---|---|---|
| GET | /api/external-skill/skills | 已注册能力(不含 headers) |
| POST | /api/external-skill/:skillId/invoke | 调用(幂等,结果落任务因果链) |
不开放 register:它接受任意 URL 并由服务端发起请求,暴露给登录用户会造成 SSRF。 注册走部署配置或管理员通道。
读的人必须知道
结果带 snapshot: true 与 calledAt——它是某时刻的答案,不是当前值。 要刷新就换 input(幂等键是永久的)。
判据:能力还是事实
外部系统提供的是「能力」还是「事实」?
├─ 能力(我能问你一个问题)→ 本模块
└─ 事实(它的状态我必须知道)→
├─ 它能推变更 → 事件接入
└─ 它只能被我拉 → 只读镜像 + 对账后两种模式受「同一事实只有一个权威」约束:镜像域只接受同步写入,业务写入一律拒绝。
边界
- 认证密钥应在部署侧注入,不进代码
- 只有超时,没有重试与熔断
- 事件接入与数据镜像的整体设计已成型,代码未实现
源码
libs/modules/src/external-skill/
├── interfaces/external-skill.interface.ts skill 定义 + 调用结果
└── services/external-skill.service.ts 注册 / 调用 / 幂等 / 落状态