欢迎光临
我们一直在努力

【AI Agent基础 | 第六篇】LLM多厂商接入:区分provider、protocol、base_url、adapter

前言   此文是我近期学习星哥的Agent项目遇到的问题,结合AI整理总结而来,分享给大家。一是为了记录,二是用于日后的回顾,三也是希望能其给他初学者带来一点点帮助。   特别鸣谢 UP主:氪金氪氪氪氪


目录

  • 1. 先说结论
  • 2. 一个常见的 Model 结构
  • 3. provider 是什么?
  • 4. api / protocol 是什么?
  • 5. base_url 是什么?
  • 6. adapter 是什么?
  • 7. 为什么不把阿里模型直接放进 openai-standard?
  • 8. 更推荐的模型表结构
  • 9. 注册协议适配器
  • 10. 常见主流协议有哪些?
  • 11. 最终推荐命名
  • 12. 一句话总结

在接入多个大模型厂商时,经常会遇到这些词:

  • provider
  • api
  • protocol
  • base_url
  • adapter
  • OpenAI-compatible

如果这些概念没分清,代码很容易变成这样:

Model(
id="qwen-plus",
api="openai-standard",
provider="openai-standard",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)

这段配置可能能跑,但读起来很混乱:这到底是 OpenAI,还是阿里百炼?

这篇文章用一个简单的 LLM 项目结构,解释这些概念应该怎么拆。

1. 先说结论

推荐把概念分成四层:

provider/vendor = 谁提供服务,比如 openai / anthropic / dashscope
api/protocol = 接口协议,比如 openai-standard / anthropic-messages
base_url = 服务地址,比如 https://api.openai.com/v1
adapter = 适配器代码,比如 stream_openai_compatible

一句话理解:

provider = 跟谁打交道
api = 用什么协议说话
base_url = 去哪里说话
adapter = 负责翻译这门协议的代码

2. 一个常见的 Model 结构

假设项目里有这样一个模型定义:

from dataclasses import dataclass

@dataclass
class Model:
id: str
name: str
api: str
provider: str
base_url: str
max_tokens: int

这里最容易混的是 api 和 provider。

推荐含义是:

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,
)

这表示:

使用阿里百炼的 qwen-plus 模型
但请求格式复用 OpenAI 兼容协议
请求地址是 DashScope 的 base_url

3. provider 是什么?

provider 更适合表示“厂商”或“配置来源”。

例如:

openai
anthropic
dashscope
deepseek
openrouter
azure

它通常决定:

1. API Key 从哪个环境变量读取
2. 默认 base_url 是什么
3. 日志、错误归属、用量统计属于哪个厂商
4. 后续厂商特有能力如何扩展

例如:

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

这样配置会比较清楚:

$env:OPENAI_API_KEY = "OpenAI 的 Key"
$env:DASHSCOPE_API_KEY = "阿里百炼的 Key"

而不是把阿里百炼的 Key 塞进 OPENAI_API_KEY。

4. api / protocol 是什么?

很多项目不一定叫 protocol,可能叫 api。

它表示“接口协议”,也就是请求和响应长什么样。

例如:

openai-standard
anthropic-messages
gemini-generate-content
bedrock-converse

api 决定调用哪个适配器:

def stream(model: Model, context, options=None):
adapter = get_api_adapter(model.api)
return adapter.stream(model, context, options)

如果:

model.api == "openai-standard"

就走 OpenAI Chat Completions 格式:

POST /chat/completions
Authorization: Bearer xxx
messages: […]
tools: […]
stream: true

如果:

model.api == "anthropic-messages"

就走 Anthropic Messages 格式:

POST /v1/messages
x-api-key: xxx
anthropic-version: 2023-06-01
messages: […]
tools: […]
stream: true

5. base_url 是什么?

base_url 是接口地址的前半截。

它不是 protocol。

简单理解:

protocol = 怎么说话
base_url = 跟谁说话

同一个 OpenAI 协议,可以有不同的 base_url:

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

对应代码可能是:

url = f"{model.base_url.rstrip('/')}/chat/completions"

所以:

Model(
api="openai-standard",
provider="dashscope",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)

最后请求的是:

https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions

6. adapter 是什么?

adapter 是适配器代码。

它负责把项目内部的统一格式,转换成某个外部协议需要的格式;再把外部响应转换回内部格式。

例如项目内部统一调用是:

stream(model, context, options)

对于 OpenAI 协议,适配器可能是:

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 协议,适配器可能是:

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

所以:

adapter = 代码层面的协议翻译器

阿里百炼兼容 OpenAI 接口,因此不需要单独写 stream_dashscope,可以复用:

stream_openai_compatible

7. 为什么不把阿里模型直接放进 openai-standard?

当然可以。

比如:

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,
),
}
}

然后:

$env:OPENAI_API_KEY = "你的阿里百炼 Key"
$env:XINGCLAW_PROVIDER = "openai-standard"
$env:XINGCLAW_MODEL_ID = "qwen-plus"

这样能跑。

但问题是语义混乱:

provider 叫 openai-standard
base_url 却是 dashscope.aliyuncs.com
OPENAI_API_KEY 里装的是阿里百炼 Key

短期学习没问题,长期维护会变麻烦。

比如你以后同时使用 OpenAI 和阿里百炼:

$env:OPENAI_API_KEY = "OpenAI Key"
$env:DASHSCOPE_API_KEY = "DashScope Key"

如果都塞进 OPENAI_API_KEY,就没法干净切换。

8. 更推荐的模型表结构

更清晰的写法是:

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,
),
},
}

这里:

openai 和 dashscope 是不同 provider
但它们都复用 api="openai-standard"

这才是重点。

不是说百炼不兼容 OpenAI,而是:

兼容 OpenAI 协议,不等于它就是 OpenAI provider。

9. 注册协议适配器

可以用一个注册表,把 api 映射到适配器函数:

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]

注册内置协议:

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。

因为这里注册的是:

协议 -> 适配器实现

不是:

厂商 -> 厂商配置

10. 常见主流协议有哪些?

不止两个。

常见的有:

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,有些有原生接口。

所以一个项目早期只有:

openai-standard
anthropic-messages

很正常。

它们能覆盖大量场景,尤其是 OpenAI-compatible 生态。

11. 最终推荐命名

推荐:

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

不推荐:

provider = openai-standard

因为 openai-standard 更像协议,不像厂商。

12. 一句话总结

在 LLM 多厂商接入里,最重要的是把这几个概念拆开:

provider/vendor = 谁提供服务
api/protocol = 请求响应格式
base_url = 服务入口地址
adapter = 负责协议转换的代码

阿里百炼兼容 OpenAI 接口,所以它应该是:

Model(
id="qwen-plus",
provider="dashscope",
api="openai-standard",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)

而不是把它伪装成:

provider="openai-standard"

这样做的好处是:

1. Key 不混乱
2. 日志更清楚
3. 后续扩展更容易
4. 多厂商可以并存
5. 协议复用和厂商配置互不影响


上述内容也同步在我的飞书,欢迎访问

https://my.feishu.cn/wiki/QLauws6lWif1pnkhB8IcAvkhncc?from=from_copylink

如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,你们的支持就是我坚持下去的动力!

赞(0)
未经允许不得转载:171主机测评 » 【AI Agent基础 | 第六篇】LLM多厂商接入:区分provider、protocol、base_url、adapter
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址