Skip to content
预计 1 分钟右侧目录可快速定位

如何隔离 Provider 差异:Streaming、Adapter 与 Capability ​

前置:Structured Output、Schema Validation 与 Tool Calling

一个 Streaming Tool Call 为什么不能“边到边执行” ​

text
text.delta("我先读取文件")
tool.arguments.delta('{"path":"src/')
tool.arguments.delta('auth.py"}')
tool.completed

前两个 argument delta 只是半成品,Runtime 不能提前执行 Tool;只有 Adapter 组装并校验出完整 ToolCall 后,才能交给 ToolExecutor。Streaming 事件和最终 committed item 必须分开。

Streaming Model Events ​

Streaming 把完整模型响应拆成事件;调用方需要区分 text delta、tool argument delta、completed、error、cancelled,并保证最终 normalized response 与事件流一致。

Streaming 的价值是降低用户可感知等待并暴露增量事件,但引入了半完成输出、断线、取消、乱序和聚合状态。必须区分 delta event 与最终 committed item。

🔍 源码对照:Provider delta 先规范化,再进入 Runtime 事件模型 ​

精确定位

ts
async function* sanitizeFullStream(stream) {
  let error
  for await (const part of stream) {
    if (part.type === "error") {
      error = normalizeError(part.error)
      continue
    }
    yield part.type === "finish"
      ? { ...part, usage: sanitizeUsage(part.usage) }
      : part
  }
  if (error) throw error
}

对照理解

  • Provider stream 中间事件不是最终 Runtime State;源码先规范化 error/usage,再向上暴露稳定事件。
  • 这对应“delta 是中间事件,最终 Item/Response 经 domain validation 后才进入 Runtime”的原则。
  • createSandResolvedModelTracker() 还跟踪 requested model 与 resolved model,避免 routing/usage 记录失真。

Provider Abstraction ​

Provider abstraction 的责任是稳定内部 ModelRequest / ModelResponse / ModelEvent / ModelError 语义,把 SDK 类型、字段命名和错误格式限制在 Adapter 内。它不应该通过“取最小公约数”假装所有 Provider 完全相同;能力差异交给单独的 capability profile 表达。

Capability Detection ​

Capability Detection 回答“当前模型/端点实际支持什么”,例如 strict structured output、parallel tool calls、streaming、tool search。Runtime 应据此选择策略;不支持时要显式降级或拒绝,而不是静默假设能力存在。

Failure Modes ​

Provider SDK 类型穿透 Domain 层 ​

症状: 业务代码、Planner、Store 直接依赖某 provider 的 request/response/error 类型。

为什么危险: SDK 升级或切换 provider 会扩散成全项目修改,Fake Model 和 contract test 也难以替换。

处理: SDK 类型只存在 Adapter;内部使用 ModelRequest/Response/Event/Error 与 capability contract。

缓存的 Capability 与真实模型版本漂移 ​

症状: 升级 model/provider 后仍复用旧 capability profile,继续假定支持或不支持某能力。

为什么危险: Runtime 会选择错误 tool/stream/schema 策略,问题只在特定模型版本出现。

处理: Capability 与 provider/model/version 绑定并进入 RunManifest;版本变化时失效缓存并跑 contract test。

如何验证 ​

Case断言
Provider SDK 类型穿透 Domain 层替换成 Fake/第二 Provider Adapter,断言 Runtime/Planner/Store 测试无需修改。
缓存的 Capability 与真实模型版本漂移切换到 capability 不同的 Fake Adapter,断言策略分支随 profile 改变。

自测 ​

  1. Provider A 用 delta_text,Provider B 用另一套 chunk 类型。怎样设计 Adapter 才能让 Runtime 不知道这些 SDK 类型?
  2. 流式响应中途断掉时,哪些内容只能算 partial event,什么时候才可以形成完整 domain output?

Sources ​

AI Engineering · Agent · Harness · Backend Systems