Appearance
如何隔离 Provider 差异:Streaming、Adapter 与 Capability
一个 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 事件模型
精确定位
- 文件:
source/host/runner/conversation-state.ts - Symbol:
sanitizeFullStream()→sanitizeStreamResult()→createSandResolvedModelTracker() - 深入索引:Code Atlas:08-routing-provider
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 改变。 |
自测
- Provider A 用
delta_text,Provider B 用另一套 chunk 类型。怎样设计 Adapter 才能让 Runtime 不知道这些 SDK 类型? - 流式响应中途断掉时,哪些内容只能算 partial event,什么时候才可以形成完整 domain output?