7.5 LLM 模块:流式调用的实现

模型: claude-opus-4-6 (anthropic/claude-opus-4-6) 生成日期: 2026-02-17


LLM 模块是 OpenCode 中 Provider 层与 Session 层之间的桥梁——它负责将上层提供的 Agent 配置、消息历史和工具定义,转化为一次完整的 LLM 流式调用。本节将完整解析 LLM.stream() 的实现,理解从参数组装到流式输出的每一个环节。

7.5.1 LLM.stream() 完整实现解析

export namespace LLM {
  export type StreamInput = {
    user: MessageV2.User        // 当前用户消息
    sessionID: string            // 会话 ID
    model: Provider.Model        // 模型元数据
    agent: Agent.Info            // Agent 配置
    system: string[]             // 额外的 system 指令
    abort: AbortSignal           // 终止信号
    messages: ModelMessage[]     // 完整的对话历史
    small?: boolean              // 是否使用小模型配置
    tools: Record<string, Tool>  // 可用工具集
    retries?: number             // 重试次数
  }
  
  export async function stream(input: StreamInput) {
    // ... 完整实现
  }
}

第一步:并行获取依赖资源

使用 Promise.all() 并行加载四个依赖资源,避免顺序等待。isCodex 标志判断当前是否为 Codex 模式(OpenAI 的 OAuth 认证模式),Codex 模式下的行为有特殊处理。

第二步:System Prompt 组装

这部分在第 6 章已详细分析,这里简要回顾:

第三步:Provider 选项合并

选项合并使用了 remeda 库的 pipemergeDeep 函数,构成一个清晰的优先级链:Provider < Model < Agent < Variant

第四步:Plugin 参数钩子

chat.params 钩子允许 Plugin 在最后一刻修改 LLM 调用的参数。oh-my-opencode 使用这个钩子注入 Anthropic 的 effort 等级(如 budget_tokens)。

第五步:工具解析与权限过滤

两层过滤确保 Agent 只能"看到"被允许的工具。

第六步:LiteLLM 代理兼容

衍生解释:LiteLLM

LiteLLM 是一个流行的 LLM API 代理,允许通过统一接口调用多个 Provider。但它有一个限制:如果消息历史中包含工具调用,请求中必须包含至少一个工具定义(即使当前不需要使用工具)。OpenCode 通过添加一个永远不会被调用的 _noop 占位工具来绕过这个限制。

第七步:调用 streamText()

experimental_repairToolCall:工具调用修复

这是一个精巧的容错机制。LLM 有时会生成错误的工具名称(如大小写不对),experimental_repairToolCall 回调在工具调用失败时尝试修复:

  1. 大小写修复:如果工具名的小写版本存在(如 LLM 输出了 Read 但实际工具名是 read),自动修正。

  2. 无效工具兜底:如果无法修复,将工具调用路由到 "invalid" 工具,并将错误信息作为输入传递——这让 LLM 可以"看到"自己的错误并自我纠正。

wrapLanguageModel 中间件

中间件在消息发送到 Provider API 之前,执行 ProviderTransform.message() 对消息进行最终的格式适配——包括空消息过滤、Tool Call ID 规范化、缓存标记等(详见 7.4 节)。

7.5.2 认证体系

OpenCode 支持两种认证模式:

API Key 模式:用户直接提供 API Key,通过环境变量或 opencode auth 命令存储。

OAuth 模式:适用于 GitHub Copilot、Codex 等需要 OAuth 授权的服务。OAuth 流程由 Plugin 系统中的专门 Auth Plugin 处理(如 CopilotAuthPluginCodexAuthPlugin)。

OAuth 模式的检测:

在 Codex 模式下,System Prompt 的传递方式不同——不作为 system 消息发送,而是通过 options.instructions 字段:

7.5.3 请求 Header 管理

Header 的设置因 Provider 而异:

  • OpenCode 自有 Provider:发送项目 ID、会话 ID、请求 ID 等追踪信息。

  • 非 Anthropic 的第三方 Provider:发送 User-Agent 标识。

  • Anthropic:不发送额外 header(Anthropic SDK 自行管理)。

7.5.4 小模型模式

input.smalltrue 时(用于标题生成、摘要生成等辅助任务),系统使用精简的 Provider 选项:

小模型模式的核心思路是降低推理深度——使用最低的 reasoningEffort 或关闭 thinking,以获得更快的响应速度和更低的成本。


本章小结

本章完整分析了 OpenCode 的 Provider 多模型适配层:

  • 7.1 介绍了 Vercel AI SDK 的核心概念及其在 OpenCode 中的集成方式,包括 20+ 个 bundled Provider 和运行时动态安装机制。

  • 7.2 详细分析了 Provider 从多来源注册到最终状态构建的完整流程——环境变量发现、配置文件合并、Auth 存储、Plugin 加载和 Custom Loader。

  • 7.3 解析了模型元数据系统——能力矩阵、价格信息、models.dev 数据源、模糊搜索和模型变体。

  • 7.4 深入了 ProviderTransform 的差异化适配——消息格式标准化、缓存策略、不支持模态的降级处理和 SDK 键映射。

  • 7.5 完整剖析了 LLM.stream() 的实现——从参数组装到流式调用,从工具修复到中间件注入。

Provider 层的设计哲学可以概括为:统一接口屏蔽差异,分层合并提供灵活性,防御性编码确保健壮性。 无论用户选择哪个 Provider 和模型,上层的 Session、Agent、Tool 系统都可以无感知地正常工作。

下一章,我们将进入 MCP(Model Context Protocol)——OpenCode 连接外部工具生态的核心协议。

Last updated