01 Provider 门面模式与 Route 抽象


文档摘要

01 Provider 门面模式与 Route 抽象 本节摘要:OpenCode 支持 20 多家模型厂商,每家的 API 签名、认证、部署方式都不同。如果每接一家写一套分支,代码会变成乱麻。LLM 抽象层的第一层解法是门面模式(Facade)+ Route 抽象:用门面先配置一个 Provider(端点、认证、部署),再用 Route 把「一个部署」抽象成端点+认证+分帧等字段的组合。本节讲清这套设计:门面怎么用、Route 是什么、为什么 Route 让「加一个新部署只要很少代码」。 一、问题:20+ 厂商怎么统一 先看问题的规模。

01 Provider 门面模式与 Route 抽象

本节摘要:OpenCode 支持 20 多家模型厂商,每家的 API 签名、认证、部署方式都不同。如果每接一家写一套分支,代码会变成乱麻。LLM 抽象层的第一层解法是门面模式(Facade)+ Route 抽象:用门面先配置一个 Provider(端点、认证、部署),再用 Route 把「一个部署」抽象成端点+认证+分帧等字段的组合。本节讲清这套设计:门面怎么用、Route 是什么、为什么 Route 让「加一个新部署只要很少代码」。

一、问题:20+ 厂商怎么统一

先看问题的规模。OpenCode 要支持:Anthropic、OpenAI、Google、Amazon Bedrock、Azure、Cloudflare、GitHub Copilot、OpenRouter、xAI、Mistral、Cohere,以及任何 OpenAI 兼容的端点......每家都有自己的:

  • API 路径与请求体格式
  • 认证方式(API Key、OAuth、AWS 签名......)
  • 部署变体(Azure 的 deployment、Bedrock 的 model id、OpenRouter 的路由......)
  • 流式协议

如果用 if 厂商 == "anthropic" ... else if 厂商 == "openai" ...,代码会膨胀到不可维护。OpenCode 的解法分两层:门面(本节)和 Route(本节)+ Protocol/Framing(下一节)。

二、门面模式:先配置,再选模型

门面模式的核心是把「配置一个 Provider」和「选择一个模型」分成两步:

第一步:配置 Provider 某Provider.configure({ 端点: ..., 认证: ..., 部署相关: ... }) ──► 得到一个「配置好的 Provider 门面」 第二步:从门面选模型 配置好的门面.model("某模型id") ──► 得到一个「可调用的模型句柄」

这种「两步走」的好处是:配置一次,可选多个模型。比如你配好了 Anthropic 门面,然后既可以用 claude-sonnet,也可以用 claude-haiku,不用重复配端点和认证。

💡 为什么叫「门面」:门面(Facade)是设计模式名——它把一堆复杂的内部细节(端点、认证、部署)藏在一个简洁的「门面」后面,你只跟门面打交道,不用关心内部。配好门面,后面选模型就是一句的事。

三、Route 抽象:一个部署 = 几个字段的组合

门面之下是 Route 抽象,这是本节真正的硬核。Route 把「一个部署(一个可调用的端点)」抽象成几个字段的组合:

Route = { protocol: 用哪种协议(anthropic-messages / openai-chat / ...), endpoint: 端点(URL、路径), auth: 认证(API Key / OAuth / AWS 签名 / ...), framing: 分帧方式(流怎么切), ...其他传输细节 }

关键洞察是:绝大多数「新部署」,其实是已有字段的重新组合。比如:

  • 「Anthropic 官方端点」= 协议 anthropic-messages + 官方 URL + API Key 认证 + Anthropic 分帧
  • 「某代理的 Anthropic 兼容端点」= 协议 anthropic-messages + 代理 URL + 代理认证 + Anthropic 分帧
  • 「Azure 上的某模型」= 协议 openai-chat + Azure URL + Azure Key + OpenAI 分帧

它们的字段值不同,但结构相同。所以加一个新部署,只需要 Route.make({ ...这几个字段 ... }),几行代码就够——不需要写一整个新模块。

四、Route 的构造:Route.make

Route 通过一个构造函数创建,大致是这样:

// 概念性示例:用一个已有协议 + 新端点 + 新认证,定义一个新部署 const 我的Route = Route.make({ protocol: 某已有协议, // 复用,不重写 endpoint: { url: "https://我的端点", path: "/v1/..." }, auth: { type: "api_key", key: ... }, framing: 某已有分帧, // 复用 });

注意三个「复用」:协议复用、分帧复用、认证复用。你要新的往往只是「端点 URL」和「认证凭据」。这就是为什么「加一个新部署只要很少代码」——你站在已有协议和分帧的肩膀上,只填差异部分。

⚠️ 别误解 Route 是「配置」:Route 不只是配置文件,它是一个真正的抽象——它把「一个部署的所有可变部分」收敛成结构化字段,让新部署变成「字段组合」而非「新代码」。这是抽象的力量。

五、门面与 Route 的关系

把门面和 Route 放一起看:

门面(某Provider.configure) │ 内部组装 ▼ Route(协议 + 端点 + 认证 + 分帧) │ 选模型时 ▼ 可调用的模型句柄(.model(id))

门面是「用户视角的简洁入口」,Route 是「内部的部署抽象」。用户配门面,门面内部建 Route,Route 决定「怎么调这个部署」。这种分层让用户界面简洁(配门面),同时内部足够灵活(Route 可任意组合)。

六、这一节为下一节铺路

本节讲了「怎么配置和组合部署」,但还没讲「请求体怎么构造、流式响应怎么解析」——那是 Protocol 和 Framing 的事。下一节就讲:协议(Protocol)负责构造请求体与解析流,分帧(Framing)管字节流切分,传输(Transport)管 HTTP 细节。三者分离,让支持新协议变得简单。

本节要点回顾

  1. 问题规模:20+ 厂商,各自 API/认证/部署/流式都不同,不能用 if-else 硬扛。
  2. 门面模式:分两步——先 configure 配 Provider(端点/认证/部署),再 .model(id) 选模型;配一次可选多模型。
  3. Route 抽象:把「一个部署」抽象成 protocol + endpoint + auth + framing 等字段组合。
  4. 关键洞察:大多数新部署是已有字段的重新组合,所以加新部署只需 Route.make({...}) 几行。
  5. 三个复用:协议复用、分帧复用、认证复用——新部署往往只需新端点 + 新凭据。
  6. 门面 vs Route:门面是用户视角的简洁入口,Route 是内部的部署抽象,二者分层。

门面和 Route 讲清了「怎么组合部署」,下一节讲部署确定后「请求怎么构造、流怎么解析」——协议、分帧、传输三件套。


发布者: 作者: 灏天文库 转发
评论区 (0)
U