数据截至 (上游 commit 101f0313b0dd)
Provider 无关的模型层
30 秒导读: Upsonic 支持 25+ 家模型 provider(OpenAI、Anthropic、Google、Bedrock、Groq、Ollama……),但上层代码从不关心你用哪家。秘密是一个抽象基类
Model定义了统一的三个方法(请 求 / 数 token / 流式),外加一个infer_model()分发表把'anthropic/claude-sonnet-4-5'这样的字符串解析成对应的Model子类实例。本章讲这套字符串 → 真实 API 调用的完整链路。
本章只讲模型侧的抽象。一次完整运行怎么被 24 步管线调度(ModelExecutionStep 何时调用 model.request)是 02-execution-pipeline.md 的内容;这里我们把镜头对准"管线拿到 self.model 之后,那一次调用内部发生了什么"。
1. 这是什么(零基础也能懂)
一句话定义: 模型层是 Upsonic 里"对接大模型 API"的那一层——它把 25+ 家 provider 各不相同的 SDK、请求格式、返回格式,藏在一个统一接口后面。
它要解决的痛点: 每家模型厂商的 API 都长得不一样。
| 差异点 | Anthropic | OpenAI | |
|---|---|---|---|
| 系统提示 | 顶层独立的 system 参数 | 混在 messages 里的一条 role=system | 独立的 system_instruction |
| 工具调用返回 | BetaToolUseBlock | tool_calls[].function | functionCall part |
| 流式事件 | content_block_delta 等 | chat.completion.chunk | GenerateContentResponse 分片 |
如果每个用 到模型的地方都写 if provider == 'anthropic': ... elif ...,代码会立刻失控。模型层的职责就是:在一处把这些差异吸收掉,对外只暴露一套统一的输入/输出。
用起来什么样: 上层永远只给一个字符串,拿回一个统一的 ModelResponse。
# 示意,非源码 —— 体会"字符串进,统一对象出"
from upsonic import infer_model
model = infer_model("anthropic/claude-sonnet-4-5") # 字符串 → AnthropicModel 实例
resp = await model.request(messages, settings, params) # 统一接口
print(resp.parts) # 统一的 ModelResponse,不管底层是哪家
换成 "openai/gpt-4o" 或 "ollama/llama3.1",上面这段一个字都不用改——这就是"provider 无关"。
一句话直觉/类比: 把它当成模型界的电源转换插头。世界各地插座形状不同(各家 API),你的电器(Agent 主逻辑)只认一种插头(Model 接口),转换插头(各 Model 子类)负责适配当地插座。