前言
此文是我近期学习星哥的Agent项目遇到的问题,结合AI整理总结而来,分享给大家。一是为了记录,二是用于日后的回顾,三也是希望能其给他初学者带来一点点帮助。
特别鸣谢 UP主:氪金氪氪氪氪
目录
- [1. 先说结论](#1. 先说结论)
- [2. 一个常见的 Model 结构](#2. 一个常见的 Model 结构)
- [3. provider 是什么?](#3. provider 是什么?)
- [4. api / protocol 是什么?](#4. api / protocol 是什么?)
- [5. base_url 是什么?](#5. base_url 是什么?)
- [6. adapter 是什么?](#6. adapter 是什么?)
- [7. 为什么不把阿里模型直接放进 openai-standard?](#7. 为什么不把阿里模型直接放进 openai-standard?)
- [8. 更推荐的模型表结构](#8. 更推荐的模型表结构)
- [9. 注册协议适配器](#9. 注册协议适配器)
- [10. 常见主流协议有哪些?](#10. 常见主流协议有哪些?)
- [11. 最终推荐命名](#11. 最终推荐命名)
- [12. 一句话总结](#12. 一句话总结)
在接入多个大模型厂商时,经常会遇到这些词:
providerapiprotocolbase_urladapterOpenAI-compatible
如果这些概念没分清,代码很容易变成这样:
python
Model(
id="qwen-plus",
api="openai-standard",
provider="openai-standard",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
这段配置可能能跑,但读起来很混乱:这到底是 OpenAI,还是阿里百炼?
这篇文章用一个简单的 LLM 项目结构,解释这些概念应该怎么拆。
1. 先说结论
推荐把概念分成四层:
text
provider/vendor = 谁提供服务,比如 openai / anthropic / dashscope
api/protocol = 接口协议,比如 openai-standard / anthropic-messages
base_url = 服务地址,比如 https://api.openai.com/v1
adapter = 适配器代码,比如 stream_openai_compatible
一句话理解:
text
provider = 跟谁打交道
api = 用什么协议说话
base_url = 去哪里说话
adapter = 负责翻译这门协议的代码
2. 一个常见的 Model 结构
假设项目里有这样一个模型定义:
python
from dataclasses import dataclass
@dataclass
class Model:
id: str
name: str
api: str
provider: str
base_url: str
max_tokens: int
这里最容易混的是 api 和 provider。
推荐含义是:
python
Model(
id="qwen-plus",
name="Qwen Plus",
api="openai-standard", # 协议:OpenAI Chat Completions
provider="dashscope", # 厂商:阿里百炼
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
max_tokens=8192,
)
这表示:
text
使用阿里百炼的 qwen-plus 模型
但请求格式复用 OpenAI 兼容协议
请求地址是 DashScope 的 base_url
3. provider 是什么?
provider 更适合表示"厂商"或"配置来源"。
例如:
text
openai
anthropic
dashscope
deepseek
openrouter
azure
它通常决定:
text
1. API Key 从哪个环境变量读取
2. 默认 base_url 是什么
3. 日志、错误归属、用量统计属于哪个厂商
4. 后续厂商特有能力如何扩展
例如:
python
import os
def get_env_api_key(provider: str) -> str | None:
if provider == "anthropic":
return os.getenv("ANTHROPIC_API_KEY")
if provider == "openai":
return os.getenv("OPENAI_API_KEY")
if provider == "dashscope":
return os.getenv("DASHSCOPE_API_KEY")
return None
这样配置会比较清楚:
powershell
$env:OPENAI_API_KEY = "OpenAI 的 Key"
$env:DASHSCOPE_API_KEY = "阿里百炼的 Key"
而不是把阿里百炼的 Key 塞进 OPENAI_API_KEY。
4. api / protocol 是什么?
很多项目不一定叫 protocol,可能叫 api。
它表示"接口协议",也就是请求和响应长什么样。
例如:
text
openai-standard
anthropic-messages
gemini-generate-content
bedrock-converse
api 决定调用哪个适配器:
python
def stream(model: Model, context, options=None):
adapter = get_api_adapter(model.api)
return adapter.stream(model, context, options)
如果:
python
model.api == "openai-standard"
就走 OpenAI Chat Completions 格式:
text
POST /chat/completions
Authorization: Bearer xxx
messages: [...]
tools: [...]
stream: true
如果:
python
model.api == "anthropic-messages"
就走 Anthropic Messages 格式:
text
POST /v1/messages
x-api-key: xxx
anthropic-version: 2023-06-01
messages: [...]
tools: [...]
stream: true
5. base_url 是什么?
base_url 是接口地址的前半截。
它不是 protocol。
简单理解:
text
protocol = 怎么说话
base_url = 跟谁说话
同一个 OpenAI 协议,可以有不同的 base_url:
text
OpenAI:
https://api.openai.com/v1/chat/completions
阿里百炼:
https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions
OpenRouter:
https://openrouter.ai/api/v1/chat/completions
DeepSeek:
https://api.deepseek.com/chat/completions
对应代码可能是:
python
url = f"{model.base_url.rstrip('/')}/chat/completions"
所以:
python
Model(
api="openai-standard",
provider="dashscope",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
最后请求的是:
text
https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions
6. adapter 是什么?
adapter 是适配器代码。
它负责把项目内部的统一格式,转换成某个外部协议需要的格式;再把外部响应转换回内部格式。
例如项目内部统一调用是:
python
stream(model, context, options)
对于 OpenAI 协议,适配器可能是:
python
def stream_openai_compatible(model, context, options=None):
api_key = get_env_api_key(model.provider)
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
payload = {
"model": model.id,
"messages": to_openai_messages(context),
"stream": True,
}
url = f"{model.base_url.rstrip('/')}/chat/completions"
# 发请求、解析 SSE、转换成内部 AssistantMessage
对于 Anthropic 协议,适配器可能是:
python
def stream_anthropic(model, context, options=None):
api_key = get_env_api_key(model.provider)
headers = {
"x-api-key": api_key,
"anthropic-version": "2023-06-01",
"Content-Type": "application/json",
}
payload = {
"model": model.id,
"messages": to_anthropic_messages(context),
"max_tokens": model.max_tokens,
"stream": True,
}
url = f"{model.base_url.rstrip('/')}/v1/messages"
# 发请求、解析 event/data、转换成内部 AssistantMessage
所以:
text
adapter = 代码层面的协议翻译器
阿里百炼兼容 OpenAI 接口,因此不需要单独写 stream_dashscope,可以复用:
text
stream_openai_compatible
7. 为什么不把阿里模型直接放进 openai-standard?
当然可以。
比如:
python
MODELS = {
"openai-standard": {
"qwen-plus": Model(
id="qwen-plus",
name="Qwen Plus",
api="openai-standard",
provider="openai-standard",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
max_tokens=8192,
),
}
}
然后:
powershell
$env:OPENAI_API_KEY = "你的阿里百炼 Key"
$env:XINGCLAW_PROVIDER = "openai-standard"
$env:XINGCLAW_MODEL_ID = "qwen-plus"
这样能跑。
但问题是语义混乱:
text
provider 叫 openai-standard
base_url 却是 dashscope.aliyuncs.com
OPENAI_API_KEY 里装的是阿里百炼 Key
短期学习没问题,长期维护会变麻烦。
比如你以后同时使用 OpenAI 和阿里百炼:
powershell
$env:OPENAI_API_KEY = "OpenAI Key"
$env:DASHSCOPE_API_KEY = "DashScope Key"
如果都塞进 OPENAI_API_KEY,就没法干净切换。
8. 更推荐的模型表结构
更清晰的写法是:
python
MODELS = {
"openai": {
"gpt-4o-mini": Model(
id="gpt-4o-mini",
name="GPT-4o mini",
api="openai-standard",
provider="openai",
base_url="https://api.openai.com/v1",
max_tokens=16384,
),
},
"dashscope": {
"qwen-plus": Model(
id="qwen-plus",
name="Qwen Plus",
api="openai-standard",
provider="dashscope",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
max_tokens=8192,
),
},
"anthropic": {
"claude-sonnet-4-5": Model(
id="claude-sonnet-4-5",
name="Claude Sonnet 4.5",
api="anthropic-messages",
provider="anthropic",
base_url="https://api.anthropic.com",
max_tokens=8192,
),
},
}
这里:
text
openai 和 dashscope 是不同 provider
但它们都复用 api="openai-standard"
这才是重点。
不是说百炼不兼容 OpenAI,而是:
text
兼容 OpenAI 协议,不等于它就是 OpenAI provider。
9. 注册协议适配器
可以用一个注册表,把 api 映射到适配器函数:
python
from dataclasses import dataclass
from typing import Callable
@dataclass
class ApiAdapter:
api: str
stream: Callable
_REGISTRY: dict[str, ApiAdapter] = {}
def register_api_adapter(adapter: ApiAdapter) -> None:
_REGISTRY[adapter.api] = adapter
def get_api_adapter(api: str) -> ApiAdapter:
return _REGISTRY[api]
注册内置协议:
python
def register_builtin_api_adapters() -> None:
register_api_adapter(
ApiAdapter(
api="anthropic-messages",
stream=stream_anthropic,
)
)
register_api_adapter(
ApiAdapter(
api="openai-standard",
stream=stream_openai_compatible,
)
)
注意这里更推荐叫 ApiAdapter,而不是 ApiProvider。
因为这里注册的是:
text
协议 -> 适配器实现
不是:
text
厂商 -> 厂商配置
10. 常见主流协议有哪些?
不止两个。
常见的有:
text
1. OpenAI Chat Completions
/v1/chat/completions
很多厂商兼容它,比如阿里百炼、DeepSeek、OpenRouter、Groq 等。
2. OpenAI Responses API
/v1/responses
OpenAI 新一代统一接口。
3. Anthropic Messages API
/v1/messages
Claude 原生接口。
4. Google Gemini API
generateContent / streamGenerateContent
Gemini 原生接口。
5. AWS Bedrock Converse API
AWS Bedrock 的统一对话接口。
6. Azure OpenAI API
接近 OpenAI,但 endpoint、deployment、api-version、鉴权方式有 Azure 特有差异。
7. 本地模型服务
Ollama、vLLM、llama.cpp、TGI 等,有些兼容 OpenAI,有些有原生接口。
所以一个项目早期只有:
text
openai-standard
anthropic-messages
很正常。
它们能覆盖大量场景,尤其是 OpenAI-compatible 生态。
11. 最终推荐命名
推荐:
text
provider:
openai
anthropic
dashscope
deepseek
openrouter
api/protocol:
openai-standard
anthropic-messages
openai-responses
gemini-generate-content
bedrock-converse
adapter:
stream_openai_compatible
stream_anthropic
stream_openai_responses
stream_gemini
不推荐:
text
provider = openai-standard
因为 openai-standard 更像协议,不像厂商。
12. 一句话总结
在 LLM 多厂商接入里,最重要的是把这几个概念拆开:
text
provider/vendor = 谁提供服务
api/protocol = 请求响应格式
base_url = 服务入口地址
adapter = 负责协议转换的代码
阿里百炼兼容 OpenAI 接口,所以它应该是:
python
Model(
id="qwen-plus",
provider="dashscope",
api="openai-standard",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
而不是把它伪装成:
python
provider="openai-standard"
这样做的好处是:
text
1. Key 不混乱
2. 日志更清楚
3. 后续扩展更容易
4. 多厂商可以并存
5. 协议复用和厂商配置互不影响
上述内容也同步在我的飞书,欢迎访问
https://my.feishu.cn/wiki/QLauws6lWif1pnkhB8IcAvkhncc?from=from_copylink
如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,你们的支持就是我坚持下去的动力!