Code Agent 解剖(21):从零扩展——接入新的 LLM Provider

先问一个问题:为什么接入新 provider 这么容易?

如果你去看 core/llm.py,会发现一件有趣的事:代码里已经支持了 OpenAI、DeepSeek、Qwen、Kimi、Zhipu、SiliconFlow、Ollama、vLLM 等十家不同的服务,但整个文件里没有十段风格迥异的适配代码。

秘密在 PROVIDER_PROFILES 这张表里。


结论先说

MyCodeAgent 的 LLM 层用**表驱动(table-driven)**的方式管理 provider。接入一个新 provider 只需要三步:

步骤 做什么
1. 在表里加一行 PROVIDER_PROFILES 里加一个 profile
2. 扩展类型注解 SUPPORTED_PROVIDERS Literal 里加名字
3. 配置环境变量 .env.example 里加新 provider 的变量说明

不需要改调用链,不需要写新类,不需要 if-else 分支。


一、PROVIDER_PROFILES:provider 路由的核心

先看 DeepSeek 的 profile,它是所有 profile 里最典型的:

python 复制代码
# core/llm.py
PROVIDER_PROFILES = {
    "deepseek": {
        "key_envs": ("DEEPSEEK_API_KEY", "LLM_API_KEY"),  # 按优先级查 API key
        "detect_envs": ("DEEPSEEK_API_KEY",),              # 自动探测时检查哪些变量
        "base_url_envs": ("LLM_BASE_URL",),                # 从哪个环境变量读端点
        "base_url": "https://api.deepseek.com",            # 未配置时的默认端点
        "model": "deepseek-chat",                          # 默认模型名
        "url_markers": ("api.deepseek.com",),              # 从 base_url 反推 provider
    },
    ...
}

这五个字段控制了 provider 路由的全部逻辑:

key_envs :查 API key 的优先级列表。框架从左到右依次查环境变量,第一个有值的就用。这让用户既可以用专用的 DEEPSEEK_API_KEY,也可以用通用的 LLM_API_KEY

detect_envs :自动探测时用哪些变量。当用户没有指定 provider,框架会扫描所有 profile 的 detect_envs,看哪家的变量在环境里设置了值,就自动选那家。如果有多家同时命中,会报错要求用户显式指定(避免歧义)。

base_url :默认端点。用户没有配置 LLM_BASE_URL 时用这个值。

url_markers :从用户配置的 base_url 字符串里反推 provider。比如用户设置了 LLM_BASE_URL=https://api.deepseek.com/v1,框架检查这个 URL 包含 "api.deepseek.com",就自动把 provider 识别为 deepseek

model :默认模型。用户没有指定 LLM_MODEL_ID 时用这个值。


二、provider 解析的完整优先级

搞清楚表结构之后,来看框架是怎么确定最终使用哪个 provider 的:

python 复制代码
def _resolve_provider(self, provider, api_key, base_url):
    # 优先级 1:代码里显式传入的 provider 参数
    if provider:
        return self._normalize_provider(provider)
    
    # 优先级 2:环境变量 LLM_PROVIDER
    env_provider = self._get_env("LLM_PROVIDER")
    if env_provider:
        return self._normalize_provider(env_provider)
    
    # 优先级 3:自动探测(扫描 detect_envs,或从 base_url 字符串反推)
    return self._auto_detect_provider(api_key, base_url)

三级优先级,层层兜底。对于用户来说,最常见的配置方式是在 .env 里设置:

bash 复制代码
LLM_PROVIDER=deepseek
DEEPSEEK_API_KEY=sk-xxxxx
LLM_MODEL_ID=deepseek-chat

或者更省事,让框架自动探测:

bash 复制代码
DEEPSEEK_API_KEY=sk-xxxxx   # 只设这一个,框架会自动识别是 DeepSeek

三、接入一个新 provider:以"SomeNewAI"为例

假设市场上出现了一个叫 "SomeNewAI" 的新服务,它的 API 接口 OpenAI 兼容(这是现在几乎所有新模型服务商的标准),端点是 https://api.someneai.com/v1

第一步:在 PROVIDER_PROFILES 里加一行

python 复制代码
# core/llm.py --- PROVIDER_PROFILES 里加入:
"someneai": {
    "key_envs": ("SOMENEAI_API_KEY", "LLM_API_KEY"),
    "detect_envs": ("SOMENEAI_API_KEY",),
    "base_url_envs": ("LLM_BASE_URL",),
    "base_url": "https://api.someneai.com/v1",
    "model": "someneai-pro",
    "url_markers": ("api.someneai.com",),
},

第二步:扩展类型注解

python 复制代码
# core/llm.py --- SUPPORTED_PROVIDERS Literal 里加上新名字
SUPPORTED_PROVIDERS = Literal[
    "openai",
    "deepseek",
    ...
    "someneai",   # 新增
    "auto",
]

第三步:更新 .env.example

bash 复制代码
# .env.example --- 在 LLM 配置区加上新 provider 的说明
# SomeNewAI
# SOMENEAI_API_KEY=your-key-here

完成。现在用户可以这样使用:

bash 复制代码
LLM_PROVIDER=someneai
SOMENEAI_API_KEY=sk-xxxxx
LLM_MODEL_ID=someneai-pro

或者在启动命令里覆盖:

bash 复制代码
uv run python main.py --provider someneai --api-key sk-xxxxx --model someneai-pro

四、如果新 provider 有特殊怪癖怎么办

OpenAI 兼容接口是现在的主流,但不同服务商在实现上多少有些差异。core/llm.py 里已经有一些针对特定服务的"怪癖处理":

python 复制代码
# core/llm.py --- 构建请求时的怪癖处理
def _build_request(self, messages, tools, ...):
    # 有些 provider 不支持 temperature=0,会报错
    if self.provider in ("zhipu",):
        temperature = max(temperature, 0.01)

    # 有些 provider 不支持多个 system message,需要合并
    if self.provider in ("kimi", "moonshot"):
        messages = self._merge_system_messages(messages)
    
    # 有些 provider 不支持 tool_choice="auto"
    if self.provider in ("some_provider",):
        request.pop("tool_choice", None)

如果新 provider 也有类似的怪癖,在 _build_request() 里加一个针对它的条件分支即可。

框架把"路由"(去哪家?用什么 key?什么端点?)和"怪癖处理"(请求格式要不要调整?)分开了:路由在表里,怪癖在 _build_request() 里。


五、本地模型:Ollama 的接入方式

对于完全本地运行的模型(比如 Ollama),接入方式一样,只是 profile 里的 base_url 指向本地:

python 复制代码
"ollama": {
    "key_envs": ("OLLAMA_API_KEY", "LLM_API_KEY"),
    "detect_envs": ("OLLAMA_API_KEY", "OLLAMA_HOST"),
    "base_url_envs": ("OLLAMA_HOST", "LLM_BASE_URL"),
    "base_url": "http://localhost:11434/v1",
    "model": "llama3.2",
    "default_key": "ollama",   # API key 默认值(Ollama 不验证 key,随便填)
    "url_markers": ("ollama",),
},

用户只需要:

bash 复制代码
# 先启动 Ollama 服务
ollama serve

# 然后在 .env 里配置
LLM_PROVIDER=ollama
LLM_MODEL_ID=llama3.2
# OLLAMA_API_KEY 可以不填,框架会用 "ollama" 作为默认值

设计亮点

1. 表驱动,不是继承

每家 provider 的差异只在数据层(那张表),而不是代码层(一个 class per provider)。新增 provider 是加数据行,不是加代码类。这让维护成本非常低------所有 provider 的路由逻辑集中在一处,一眼能看出区别。

2. 三级 API key 查找

key_envs 的列表设计允许"专用 key 优先,通用 key 兜底"。DEEPSEEK_API_KEYLLM_API_KEY 优先级高,这让有多家 provider 的用户可以为每家分别配密钥,而不是每次切换 provider 都要改 LLM_API_KEY

3. 自动探测省去显式声明

扫描 detect_envs 来自动识别 provider,让用户只需要设置 API key 而不用同时设置 LLM_PROVIDER。对新手友好,减少了必填配置项的数量。


小结

设计选择 方案 工程价值
provider 路由 表驱动(PROVIDER_PROFILES) 加 provider 加数据行,不改代码逻辑
API key 查找 多级 key_envs 专用 key 优先,通用 key 兜底
provider 识别 三级优先级(参数 → 环境变量 → 自动探测) 灵活,最少配置可用
怪癖处理 _build_request() 里按 provider 分支 路由逻辑与怪癖逻辑分离,各自演化

下一篇讲 Skill------用 Markdown 定义一段可复用的"专家行为",这是 MyCodeAgent 里最轻量的扩展方式。


关于本系列的源码

本系列所有分析均基于开源项目 MyCodeAgent

源码里已经按照本系列文章的讲解顺序,在关键位置加入了配套注释------读文章时可以对照代码,也可以直接克隆下来自己跑、改、扩展,基于它开发你自己的 agent。

bash 复制代码
git clone https://github.com/chendongqi/MyCodeAgent
cd MyCodeAgent
cp .env.example .env   # 填入你的 LLM API key
uv sync
uv run python main.py

欢迎访问 PrimeSkills ------ 一个精心策划的 AI Agent 与技能市场,所有内容均经过真实企业级工作流验证。没有噱头,只有真正有效的东西。

更多实用知识和有趣产品,欢迎访问我的个人主页

相关推荐
那个松鼠很眼熟w1 小时前
4.Spring-Ai入门案例
java·人工智能·spring
巫山老妖1 小时前
讲不清楚,是因为你还没想清楚
人工智能·程序员
职场的momo1 小时前
字节生活服务海量内推,挑战亿级订单与AI交易中台
人工智能·程序人生·面试·职场和发展·跳槽·生活·业界资讯
Dawson Zhu1 小时前
知识图谱与 Palantir Ontology:同一套「实体—关系」表象下,两种截然不同的工程范式
人工智能·语言模型·架构·aigc·agi
YOLO数据集集合1 小时前
无人机高分辨率多树种单木分割数据集 | 单木分割 无人机林业 树种识别 实例分割 点云 遥感数据集 深度学习 精准林业9047期
人工智能·深度学习·数据集·无人机·遥感数据集·点云数据集·树木分割
科技看点1 小时前
小机身里的Windows+AI,Windows原生OpenClaw离线AI盒子实测
人工智能
GEO实战经验分享1 小时前
跨平台GEO度量框架:从多源数据整合到效果评估的系统化指南
人工智能
2601_966863192 小时前
扩音器哪个牌子性价比高?哪个牌子适合教师?开学季扩音器推荐
人工智能
Capricorn19882 小时前
日志级幻觉排障:GPT-6 Astra 迈入 AGI 时代,知芽 Notebook Skill 如何以引用校验与零命中诚实弃权解决长文失忆
论文阅读·人工智能·笔记·gpt·agi