goose/source-notesupstream ↗
goose · version 1.45.0 · Rust 1.94.1

第 3 章:Provider 与模型——把供应商差异压到边界

3.1 是什么:一个 trait、多种协议适配器

goose-provider-types/src/base.rs 定义了跨 provider 的最小协议:

#[async_trait]
pub trait Provider: Send + Sync {
    fn get_name(&self) -> &str;
    async fn stream(
        &self,
        model_config: &ModelConfig,
        system: &str,
        messages: &[Message],
        tools: &[Tool],
    ) -> Result<MessageStream, ProviderError>;
}

MessageStream 的语义是“文本可以是增量,tool call 必须是完整的 Message”。这让上层既能低延迟渲染文本,又不必在 Agent 层重新拼装不完整的工具参数。

3.2 源码怎么做:四层注册与翻译

第一层:统一模型配置

ModelConfig 保存 model name、context limit、temperature、max tokens、toolshim、thinking/reasoning、request params 和 request headers。它还会规范化 thinking effort suffix,并从 canonical model registry 补齐上下文与输出上限。

第二层:Provider trait

除了 stream,trait 还定义 complete(默认收集 stream)、context limit、retry config、模型发现、canonical mapping、OAuth、credential refresh、mode update 和 permission routing。可见 goose 把“调用模型”与“配置/能力询问”放在同一个 provider contract 中,减少上层对具体实现的判断。

第三层:协议格式模块

goose-provider-types/src/formats/ 把 OpenAI chat、OpenAI Responses、Anthropic、Google、Ollama、Snowflake 等消息格式分开。goose-providers 的实现只需要选择正确的 request/response translator,并处理供应商特有的认证、模型发现、错误和流解析。

第四层:ProviderRegistry

providers/init.rs 使用 OnceCell<RwLock<ProviderRegistry>> 延迟初始化 registry,并注册内置、preferred、declarative provider。每个 entry 携带 metadata、constructor、inventory identity、是否支持 refresh、cleanup hook 和 provider type。声明式 provider 通过 JSON 定义 endpoint、环境变量和 model 清单,不必为每个兼容服务重复写 Rust provider。

flowchart LR
  Config[provider name + ModelConfig] --> Registry[ProviderRegistry]
  Registry -->|constructor| Instance[Arc<dyn Provider>]
  Instance -->|stream| Format[provider-specific formatter]
  Format --> Wire[SSE / JSON / ACP / CLI subprocess]
  Wire --> Format
  Format --> MessageStream[MessageStream]
  MessageStream --> Agent[Agent Loop]

3.3 为什么这样做:对齐“能力”而不是“厂商名字”

Provider metadata 不只是显示名,它还描述 config keys、OAuth flow、known models、context limit、reasoning、cache control 和 fast model。Agent 因此可以用能力判断:是否支持自己的 context、是否需要 toolshim、是否走 ActionRequired permission routing,而不是写 if provider == anthropic

同样值得注意的是 manages_own_context():Claude Code、Gemini CLI 这类外部 Agent/CLI provider 可以声明上下文由自己管理,goose 就不会重复做 tool-pair summarization。这是“把状态源的所有权写进接口”的例子。

3.4 失败路径与边界

源码定位