模型 Provider / LLM 调用
a3647eb025c7 · 官方 Release
Agent 生成档案
Section titled “Agent 生成档案”- 章节 ID:
04-llm-provider - 章节摘要:从已准备好的模型上下文出发,理解 provider client、system/options/tools/headers、消息兼容转换、native 回退与统一 LLMEvent。
- 教程版本:
v1.18.16 - 源码基线:
a3647eb025c7615159d417dcc49fc39fdaeba65b - 章节元数据:/versions/v1-18-16/data/chapters.json
- 源码映射:/versions/v1-18-16/data/source-map.json
主要源码路径
Section titled “主要源码路径”packages/opencode/src/session/llm.tspackages/opencode/src/session/llm/ai-sdk.tspackages/opencode/src/session/llm/native-runtime.tspackages/opencode/src/provider/provider.tspackages/opencode/src/provider/transform.tspackages/llm/src/protocols/index.ts
本章以 OpenCode 源码版本
v1.18.16(提交a3647eb025c7)为证据基线。我们追踪一条典型 AI SDK 路径:session 已准备好消息、system 内容和工具;当 native runtime 未启用或不支持当前 provider 时,OpenCode 怎样构造请求、调用 provider,再把流还原为统一事件?这条路径由源码证明,但不是一次真实网络请求录屏。
0. 本章学习目标
Section titled “0. 本章学习目标”学完这一章,你应该能够:
- 画出
SessionProcessor -> LLM.stream -> Provider -> native/AI SDK -> LLMEvent的适配地图。 - 区分 provider 元数据、language model client、请求参数与 provider-specific transform。
- 沿源码解释 system、messages、tools、options 与 headers 怎样合成一次模型请求。
- 说明为什么同一份内部消息在发给不同 provider 前仍需要归一化。
- 判断 native runtime 何时回退、工具何时被过滤、异常事件怎样进入统一错误通道。
- 把“稳定内核 + 边界适配器”的方法迁移到自己的多模型 Agent。
1. 一句话讲明白
Section titled “1. 一句话讲明白”OpenCode 的 LLM 层是一座双向翻译桥:向外把统一的 session 消息、system、工具和策略翻译成某个 provider 接受的请求;向内把不同 runtime 的流翻译成统一 LLMEvent。
本章只回答一个问题:
为什么切换 provider 不应该迫使 agent loop 重写一遍?
因为 loop 只依赖 LLM.stream(input): Stream<LLMEvent>;provider SDK 的创建、请求兼容和原始事件差异都被关在 LLM/Provider 边界内。核心合同见 packages/opencode/src/session/llm.ts。
v1.18.16 相比旧基线改变了什么
Section titled “v1.18.16 相比旧基线改变了什么”- native runtime 已成为清晰的 opt-in adapter:先由
LLMNativeRuntime.stream判断支持度,不支持就带原因回退 AI SDK。 - AI SDK 路径仍调用
streamText,再由LLMAISDK.toLLMEvents把fullStream归一化;agent loop 不需要知道当前选中了哪条 runtime。 - provider transform 的职责继续扩大,包含 reasoning variant、provider options、schema 与 token 上限等兼容逻辑,更说明这些差异不应进入 loop。
native runtime 与 AI SDK 回退
packages/opencode/src/session/llm.ts:224-282
runtime 选择发生在 LLM 边界内部。
224 // Runtime seam: native is an opt-in adapter over @opencode-ai/llm. It225 // either returns a ready LLMEvent stream or a concrete fallback reason.226 if (flags.experimentalNativeLlm) {按条件进入分支。227 const native = LLMNativeRuntime.stream({228 model: input.model,选择模型或 provider。229 provider: item,选择模型或 provider。230 auth: info,231 llmClient,232 messages: prepared.messages,233 tools: prepared.tools,234 toolChoice: input.toolChoice,235 temperature: prepared.params.temperature,236 topP: prepared.params.topP,237 topK: prepared.params.topK,238 maxOutputTokens: prepared.params.maxOutputTokens,239 providerOptions: prepared.params.options,选择模型或 provider。240 headers: prepared.headers,241 abort: input.abort,242 })243 if (native.type === "supported") {按条件进入分支。244 yield* Effect.logInfo("llm runtime selected", {Effect 异步工作流。245 "llm.runtime": "native",246 "llm.provider": input.model.providerID,选择模型或 provider。247 "llm.model": input.model.id,248 })249 return {返回给上一层。250 type: "native" as const,251 stream: native.stream,252 }253 }254 yield* Effect.logInfo("llm runtime selected", {Effect 异步工作流。255 "llm.runtime": "ai-sdk",256 "llm.provider": input.model.providerID,选择模型或 provider。257 "llm.model": input.model.id,258 "llm.native_unsupported_reason": native.reason,259 })260 yield* Effect.logInfo("native runtime unavailable; falling back to ai-sdk", {Effect 异步工作流。261 providerID: input.model.providerID,选择模型或 provider。262 modelID: input.model.id,选择模型或 provider。263 "session.id": input.sessionID,264 small: (input.small ?? false).toString(),265 agent: input.agent.name,266 mode: input.agent.mode,267 reason: native.reason,268 })269 }270271 yield* Effect.logInfo("llm runtime selected", {Effect 异步工作流。272 "llm.runtime": "ai-sdk",273 "llm.provider": input.model.providerID,选择模型或 provider。274 "llm.model": input.model.id,275 })276 // Default runtime path: AI SDK owns provider execution and tool dispatch;277 // LLMAISDK.toLLMEvents below normalizes fullStream parts for the processor.278 return {返回给上一层。279 type: "ai-sdk" as const,280 result: streamText({向模型发起请求。281 onError(error) {282 bridge.fork(
两条 runtime 汇入统一事件流
packages/opencode/src/session/llm.ts:368-380
native 直接返回统一流,AI SDK 经过事件适配器。
368 if (result.type === "native") return result.stream按条件进入分支。369370 // Adapter seam: both runtimes expose the same LLMEvent stream. Native371 // already returns one; AI SDK streams are converted here.372 const state = LLMAISDK.adapterState()373 return Stream.fromAsyncIterable(result.result.fullStream, (e) =>返回给上一层。374 e instanceof Error ? e : new Error(String(e)),375 ).pipe(376 Stream.mapEffect((event) => LLMAISDK.toLLMEvents(state, event)),377 Stream.flatMap((events) => Stream.fromIterable(events)),378 )379 }),380 ),
2. 为什么“调用模型”远不止一个 HTTP 请求
Section titled “2. 为什么“调用模型”远不止一个 HTTP 请求”最简聊天请求看起来像:
messages -> provider -> textcoding agent 的真实请求还包含:
- agent prompt、环境 system 与用户自定义 system;
- 当前模型能力、variant、温度、topP、token 上限;
- 几十个带 JSON Schema 和 execute 回调的工具;
- OAuth、API key、base URL、provider options 与 headers;
- text、reasoning、tool call、tool result、usage、finish reason 等流事件。
更麻烦的是,各 provider 对空消息、tool-call id、附件模态、缓存标记和 options namespace 的要求并不一致。若 agent loop 直接理解这些差异,它会迅速变成 provider 条件分支的墙。
3. 先画地图:三种稳定合同夹住变化
Section titled “3. 先画地图:三种稳定合同夹住变化”SessionProcessor | | LLM.StreamInput vLLM.Service | +--> Provider.getLanguage(model) ---> provider SDK / model client | +--> ProviderTransform ------------> messages/options/schema 兼容 | +--> native runtime (支持时) | | | +-----------------------> LLMEvent | +--> AI SDK streamText (默认回退) | v LLMAISDK.toLLMEvents ----------> LLMEvent | v SessionProcessor| 层 | 稳定合同 | 变化被放在哪里 |
|---|---|---|
| session -> LLM | StreamInput | session 如何准备历史,不泄漏给 provider |
| Provider | Model、Info、getLanguage | SDK 包、认证、base URL、model client |
| LLM -> session | LLMEvent | AI SDK/native 原始流事件被适配掉 |
通用 Agent 内核只需要“输入一份模型上下文,返回一条事件流”;OpenCode 的 provider catalog、插件 hooks、native 实验路径、GitLab workflow 与大量兼容规则是产品层。
4. 最小机制:先看 15 行请求桥
Section titled “4. 最小机制:先看 15 行请求桥”1function stream(input: StableLLMInput): Stream<LLMEvent> {定义一段可复用逻辑。2 return scopedStream(async (abort) => {返回给上一层。3 const language = await provider.getLanguage(input.model)选择模型或 provider。4 const system = buildSystem(input)5 const params = mergeModelAgentVariantOptions(input)6 const tools = filterDisabledTools(input)7 const messages = adaptMessages(input.messages, input.model)89 const native = tryNative({ language, system, messages, tools, abort })10 if (native.supported) return native.stream按条件进入分支。1112 const result = streamText({向模型发起请求。13 model: language, system, messages, tools, ...params, abortSignal: abort,选择模型或 provider。14 })15 return mapEvents(result.fullStream, toLLMEvent)返回给上一层。16 })17}
这是从真实实现抽出的教学骨架。OpenCode 还要加入 auth、config、plugin hooks、telemetry、headers、tool-call repair 和 provider-specific options。
5. 读源码前,只分清四个角色
Section titled “5. 读源码前,只分清四个角色”5.1 Provider.Model 是能力与寻址信息,不是客户端
Section titled “5.1 Provider.Model 是能力与寻址信息,不是客户端”Provider.Model 保存 model id、provider id、API package/id/url、能力、上下文限制、价格、options、headers 和 variants。来源:packages/opencode/src/provider/provider.ts。
它回答“这个模型是什么、能做什么、怎样找到 SDK”,但不能直接发请求。
5.2 language model 是可调用客户端
Section titled “5.2 language model 是可调用客户端”Provider.getLanguage(model) 返回 AI SDK 的 LanguageModelV3,并按 providerID/modelID 缓存。它通过 resolveSDK 得到 provider factory,再调用自定义 model loader 或 sdk.languageModel(model.api.id)。来源:packages/opencode/src/provider/provider.ts。
可以把它类比为 Spring 中由配置创建并缓存的 client bean。类比边界是:不同 provider SDK 在运行时动态选择,并不都实现一个由 OpenCode 自己定义的 Java interface。
5.3 ProviderTransform 是兼容层,不是 provider registry
Section titled “5.3 ProviderTransform 是兼容层,不是 provider registry”它负责消息、options、temperature、token 上限和 tool schema 等转换。registry 决定“用谁”,transform 解决“对方接受什么形状”。
5.4 LLMEvent 是 session 层唯一需要懂的输出语言
Section titled “5.4 LLMEvent 是 session 层唯一需要懂的输出语言”AI SDK 的 text-delta、tool-call、finish-step 等事件经 LLMAISDK.toLLMEvents 转成统一事件;native runtime 原生返回同一事件类型。来源:packages/opencode/src/session/llm/ai-sdk.ts、packages/opencode/src/session/llm/native-runtime.ts。
6. 追一条典型源码旅程:从稳定输入到 AI SDK 事件
Section titled “6. 追一条典型源码旅程:从稳定输入到 AI SDK 事件”假设 Agent 核心循环已经准备好:
- 当前 user message;
- 当前 agent 与 model;
- 转成
ModelMessage[]的会话历史; - system 内容数组;
- 当前轮可用 tools。
我们只追 native 未启用或返回 unsupported 后的 AI SDK 分支。
6.1 第一站:StreamInput 划定 session 与 provider 的边界
Section titled “6.1 第一站:StreamInput 划定 session 与 provider 的边界”1export type StreamInput = {定义数据结构约束。2 user: MessageV2.User会话消息片段结构。3 sessionID: string4 parentSessionID?: string5 model: Provider.Model选择模型或 provider。6 agent: Agent.Info7 permission?: Permission.Ruleset8 system: string[]9 messages: ModelMessage[]10 small?: boolean11 tools: Record<string, Tool>12 retries?: number13 toolChoice?: "auto" | "required" | "none"14}
来源:packages/opencode/src/session/llm.ts
注意这里已经是 ModelMessage[],不是数据库原始 parts。message-to-model 的领域转换属于 session/message 层;本章从这个稳定边界开始。
6.2 第二站:并发取得 client、配置、provider 与认证
Section titled “6.2 第二站:并发取得 client、配置、provider 与认证”LLM.run 使用 Effect.all(..., { concurrency: "unbounded" }) 同时获取:
provider.getLanguage(input.model);- 全局 config;
- provider info;
- 当前 provider auth。
来源:packages/opencode/src/session/llm.ts。
这些读取互不依赖,因此并发可以缩短首个 token 前的准备时间。失败仍会进入同一个 Effect 错误通道。
6.3 第三站:Provider 把配置变成 language model
Section titled “6.3 第三站:Provider 把配置变成 language model”resolveSDK 从 provider options 开始,解析 base URL 中的变量,补 API key 与 model headers,再对 { providerID, npm, options } 求 hash 作为 SDK 缓存 key。来源:packages/opencode/src/provider/provider.ts。
若 package 在 bundled provider 表中,直接加载内置 factory;否则通过 Npm.add 获取入口或加载 file:// provider,再查找 create* factory。来源:packages/opencode/src/provider/provider.ts。
随后 getLanguage 再按 ${providerID}/${model.id} 缓存具体 model client。来源:packages/opencode/src/provider/provider.ts。
两级缓存解决不同复用粒度:相同 provider 配置可共享 SDK,同一模型可复用 language model。缓存键与初始化行为由源码证明;性能收益是结构上的设计解释。
6.4 第四站:system prompt 先组合,再允许插件修改
Section titled “6.4 第四站:system prompt 先组合,再允许插件修改”system 的基础顺序是:
agent.prompt(若有)否则 provider system prompt + input.system + last user message 上的 system来源:packages/opencode/src/session/llm.ts。
然后触发 experimental.chat.system.transform。若插件扩展出多个 system 项,且 header 没被改动,代码把剩余项重新合并,维持两段结构以利缓存。来源:packages/opencode/src/session/llm.ts。
OpenAI OAuth 是特殊分支:system 被写入 options.instructions,messages 中不再前置 system message;GitLab workflow 也有独立处理。普通路径则把 system 数组转换成 role: "system" 的 messages。来源:packages/opencode/src/session/llm.ts。
6.5 第五站:options 不是覆盖,而是有顺序的合并
Section titled “6.5 第五站:options 不是覆盖,而是有顺序的合并”普通请求先从 ProviderTransform.options(...) 得到 provider/model 默认值,再依次深合并:
base options <- model.options <- agent.options <- selected variant越靠后的层优先级越高。来源:packages/opencode/src/session/llm.ts。
接着 chat.params hook 可以修改 temperature、topP、topK、maxOutputTokens 与 options;chat.headers hook 单独修改 headers。来源:packages/opencode/src/session/llm.ts。
为什么 parameters 与 headers 分两个 hook?代码没有写设计说明;从接口形状可解释为分别治理模型行为与传输元数据,这是明确标注的设计解释。
6.6 第六站:工具要经过两次筛选与稳定排序
Section titled “6.6 第六站:工具要经过两次筛选与稳定排序”resolveTools 合并 agent/session permission,移除被 disabled 的工具,也尊重 user message 上旧 tools[k] === false 的覆盖。来源:packages/opencode/src/session/llm.ts。
之后工具按名称排序:
1const sortedTools = Object.fromEntries(2 Object.entries(tools).toSorted(([a], [b]) => a.localeCompare(b)),3)
来源:packages/opencode/src/session/llm.ts
这不是决定模型会调用谁,而是稳定工具声明顺序。对 GitHub Copilot 的“历史含 tool call、当前无工具”特殊情况,源码还注入永不应调用的 _noop 兼容工具。来源:packages/opencode/src/session/llm.ts。
6.7 第七站:headers 最后汇合
Section titled “6.7 第七站:headers 最后汇合”OpenCode 自有 provider 会加入 project/session/request/client headers;其他 provider 获得 session affinity、可选 parent session id 与 User-Agent。最后再合并 model headers 和 plugin headers。来源:packages/opencode/src/session/llm.ts。
这里能确认合并顺序:后面的 input.model.headers 与 plugin headers 可覆盖前面的同名字段。是否应该允许覆盖某个具体 header,要结合配置安全策略判断,源码本身只证明当前行为。
6.8 第八站:native 先试,unsupported 就回退
Section titled “6.8 第八站:native 先试,unsupported 就回退”只有 experimentalNativeLlm 开启时才尝试 native runtime。native 当前检查 provider id、SDK package、OAuth 与 API key;不支持时返回带原因的 unsupported,LLM.run 记录原因并继续 AI SDK。来源:packages/opencode/src/session/llm.ts、packages/opencode/src/session/llm/native-runtime.ts。
native 支持时直接返回 Stream<LLMEvent>;否则进入下一站。packages/llm/src/protocols/index.ts 显示 native LLM 包对外组织了 Anthropic、Bedrock、Gemini、OpenAI Chat/Compatible/Responses 等协议模块,但仅凭这个 index 不能推断每个模型此刻都走 native 分支。
6.9 第九站:AI SDK streamText 发出真正请求
Section titled “6.9 第九站:AI SDK streamText 发出真正请求”AI SDK 分支调用 streamText,主要参数包括:
- temperature/topP/topK/providerOptions;
- activeTools/tools/toolChoice;
- maxOutputTokens、abortSignal、maxRetries;
- messages、headers 与 language model;
- telemetry 与 tool-call repair。
来源:packages/opencode/src/session/llm.ts。
关键的一层在 wrapLanguageModel middleware:请求真正发出前,ProviderTransform.message(...) 再处理 prompt。来源:packages/opencode/src/session/llm.ts。
这说明内部 ModelMessage[] 是稳定语义,不保证已经符合每家 provider 的所有格式限制。
6.10 第十站:ProviderTransform 修复“语义相同、格式不兼容”
Section titled “6.10 第十站:ProviderTransform 修复“语义相同、格式不兼容””ProviderTransform.message 的总入口依次处理不支持的附件 part、消息归一化、部分 provider 的缓存标记,以及 providerOptions key 映射。来源:packages/opencode/src/provider/transform.ts。
normalizeMessages 中可见的真实兼容例子包括:
- 清理非法 surrogate 字符;
- Anthropic/Bedrock 过滤空内容;
- Claude tool-call id 字符清理;
- 特定 Anthropic SDK 调整 tool-call 与非 tool 内容顺序。
来源:packages/opencode/src/provider/transform.ts。
不要把这些规则背成永恒标准。它们明显依赖 provider/SDK 行为,升级时必须用当前源码和集成测试重新验证。
6.11 第十一站:原始流被翻译为 LLMEvent
Section titled “6.11 第十一站:原始流被翻译为 LLMEvent”LLM.stream 为本次调用创建 scoped AbortController。AI SDK 路径把 result.fullStream 转成 Effect Stream,再逐个交给 LLMAISDK.toLLMEvents。来源:packages/opencode/src/session/llm.ts。
adapter 维护 step、当前 text/reasoning id 与 tool call id 到名称的映射。典型转换包括:
start-step -> LLMEvent.stepStarttext-delta -> LLMEvent.textDeltatool-call -> LLMEvent.toolCalltool-result -> LLMEvent.toolResultfinish-step -> LLMEvent.stepFinishfinish -> LLMEvent.finisherror -> Effect.fail来源:packages/opencode/src/session/llm/ai-sdk.ts、packages/opencode/src/session/llm/ai-sdk.ts。
到这里,provider 的职责结束;统一事件继续交回 SessionProcessor 落成 message parts。
7. 五个不能忽略的失败与兼容分支
Section titled “7. 五个不能忽略的失败与兼容分支”7.1 provider 或 model 不存在
Section titled “7.1 provider 或 model 不存在”Provider.getModel 在 provider/model 缺失时返回 ModelNotFoundError,并尽量提供 suggestions。来源:packages/opencode/src/provider/provider.ts。resolveSDK 初始化异常则包装成 InitError。来源:packages/opencode/src/provider/provider.ts。
7.2 native unsupported 不是请求失败
Section titled “7.2 native unsupported 不是请求失败”unsupported 只是说明当前请求不走 native;随后会回退 AI SDK。只有 native 已选中后的 stream failure 或 AI SDK error 才是本次模型调用错误。
7.3 模型不支持某种附件
Section titled “7.3 模型不支持某种附件”ProviderTransform.message 先调用 unsupportedParts;它会把不支持的附件变成可读错误文本,提示模型告知用户。来源:packages/opencode/src/provider/transform.ts。这是降级语义,不是让 provider 接收它不支持的二进制。
7.4 tool call 名称错误会尝试修复
Section titled “7.4 tool call 名称错误会尝试修复”AI SDK 分支先尝试把工具名转为 lowercase 并匹配现有工具;仍不匹配时把调用改写给 invalid 工具,并携带原工具名与错误。来源:packages/opencode/src/session/llm.ts。
这让无效工具调用进入可观察的工具失败路径,而不是在 provider 边界静默消失。
7.5 取消必须穿透到请求
Section titled “7.5 取消必须穿透到请求”scoped AbortController 在 stream scope 释放时 abort,并作为 abortSignal 传入 AI SDK;native 工具包装同样转发 abort。来源:packages/opencode/src/session/llm.ts、packages/opencode/src/session/llm/native-runtime.ts。
8. OpenCode 的选择:替代方案与代价
Section titled “8. OpenCode 的选择:替代方案与代价”| 设计问题 | OpenCode 的选择 | 可选做法 | 收益与代价 |
|---|---|---|---|
| loop 看什么输出 | 统一 LLMEvent | loop 直接处理各 SDK event | session 稳定;需要维护 adapter |
| provider client 如何创建 | 元数据驱动、动态 factory、缓存 | 每家 provider 手写固定 client | 扩展灵活;初始化与缓存键复杂 |
| 兼容规则放哪里 | ProviderTransform 边界集中处理 | 污染 message domain model | 内部语义干净;transform 容易膨胀 |
| runtime 如何演进 | native 支持时使用,否则 AI SDK 回退 | 一次性切换实现 | 可渐进迁移;两条路径都要验证 |
| 配置怎样覆盖 | base -> model -> agent -> variant -> plugin | 单层全局 options | 灵活;最终参数来源不再单一 |
表中选择由源码直接证明;收益与代价是设计解释。
9. TypeScript / Effect:只补四个阅读障碍
Section titled “9. TypeScript / Effect:只补四个阅读障碍”9.1 Record<string, Tool>
Section titled “9.1 Record<string, Tool>”这是工具名到 AI SDK Tool 的映射,不是 Java 的 record。可类比 Map<String, Tool>,但运行时是普通对象。
9.2 Layer 与 Context.Service
Section titled “9.2 Layer 与 Context.Service”LLM.Service 声明能力接口,live/defaultLayer 组合 Auth、Config、Provider、Plugin 等依赖。可以类比 DI container 提供的 bean graph;不同点是依赖和错误在 Effect 类型中显式组合。
9.3 discriminated union 的 runtime 选择
Section titled “9.3 discriminated union 的 runtime 选择”native.type === "supported" 后,TypeScript 知道存在 stream;否则存在 reason。它是轻量的 result union,类似 Java sealed result。
9.4 Stream.mapEffect + flatMap
Section titled “9.4 Stream.mapEffect + flatMap”一个 AI SDK event 可能映射为零个或多个 LLMEvent,所以先 effectful 转换为数组,再把数组摊平成事件流。
10. 可以带走的方法
Section titled “10. 可以带走的方法”方法一:在边界两侧定义稳定语言
Section titled “方法一:在边界两侧定义稳定语言”输入用统一 request model,输出用统一 domain event;provider adapter 只负责翻译。
验证问题:新增 provider 时,SessionProcessor 是否完全不用修改?
方法二:把兼容修形集中在最后一公里
Section titled “方法二:把兼容修形集中在最后一公里”领域消息保持统一,发送前才处理 provider 的空消息、id、附件与 options 规则。
验证问题:某 provider 修复 bug 后,你能否只删除一条 transform,而不迁移历史消息?
方法三:让新 runtime 可回退、可观测
Section titled “方法三:让新 runtime 可回退、可观测”先做 capability/status 判断,记录选择与 unsupported reason,再回退成熟路径。
验证问题:native 覆盖不足时,用户得到的是透明回退还是硬失败?日志能否说明选了哪条路?
11. 费曼复述与练习阶梯
Section titled “11. 费曼复述与练习阶梯”11.1 60 秒复述
Section titled “11.1 60 秒复述”不要看上文,补全:
SessionProcessor 给 LLM 的稳定输入叫 ______。Provider 先把 model 元数据变成 ______。system 由 ______ 组合,options 按 ______ 的顺序覆盖。消息发送前经 ______ 修形。native 不支持时回退到 ______,原始流最后被翻译成 ______。
如果你的解释里仍出现“不同 provider 由 agent loop 分支处理”,请回看 packages/opencode/src/session/llm.ts。
11.2 读源码练习
Section titled “11.2 读源码练习”- 入门:列出
StreamInput中与 provider 无关的五个字段。 - 进阶:从
provider.getLanguage追到resolveSDK,画出两级缓存键。 - 辨析:比较
ProviderTransform.message与LLMAISDK.toLLMEvents的方向。 - 失败路径:分别说明 model not found、native unsupported、AI SDK error 最终走向哪里。
11.3 小实现
Section titled “11.3 小实现”为 mini agent 写两个假 provider adapter:一个输出自定义 delta 事件,一个输出 chunk 事件;两者都必须转换成相同 DomainLLMEvent,业务 processor 不允许出现 provider 名称判断。
12. 最后复盘:桥接完成,但模型怎样获得行动能力
Section titled “12. 最后复盘:桥接完成,但模型怎样获得行动能力”本章主链是:
稳定 StreamInput -> provider client + system/options/tools/headers -> provider-specific message transform -> native 或 AI SDK stream -> 统一 LLMEvent你现在应该能回答中心问题:切换 provider 不重写 agent loop,是因为变化被夹在稳定输入与统一事件之间。
下一章还有一个关键缺口:LLM 请求里的 tools 从哪里来?description、schema 与真正的 execute 如何绑定,权限和输出截断又在哪一层发生?接下来进入“Tool 调用系统”。