第 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 失败路径与边界
- Provider 错误被分成 refusal、network、credits exhausted、generic execution 等类型,Agent 决定是终止、提示重发还是继续。
complete由collect_stream兜底,保证没有实现同步完成接口的 provider 也能提供快路径。- canonical registry 只用于能力筛选和推荐,不强迫所有未知模型都必须出现在静态表中;未知模型会保留,避免供应商新增模型后直接不可用。
- request headers 和 request params 分开存储:前者不进入请求 body,后者可以携带 provider-specific 参数。
源码定位
crates/goose-provider-types/src/base.rs:Provider、MessageStream、ProviderMetadata。crates/goose-provider-types/src/model.rs:ModelConfig。crates/goose-provider-types/src/formats/:协议格式翻译。crates/goose/src/providers/init.rs、provider_registry.rs:注册、构造和清理。crates/goose-providers/src/openai.rs、anthropic.rs、openai_compatible.rs:具体流式实现。