3.1 引言:LLM是智能体的大脑
在上一篇中,我们搭建了智能体的整体架构骨架——感知、记忆、规划、行动四大模块协同运转。但骨架本身不会思考,真正让智能体"活"起来的,是驱动这一切的核心引擎:大语言模型(LLM)。
如果把智能体比作一个人,那么:
- 感知系统是眼睛和耳朵
- 记忆系统是海马体
- 工具调用是双手
- LLM则是大脑皮层——负责理解、推理、决策和生成
一个智能体的能力上限,几乎完全取决于它背后LLM的能力。选错模型,就像让一辆F1赛车装上了家用轿车的引擎——架构再优雅,也跑不出速度。反之,选对模型,很多看似复杂的任务可以迎刃而解。
具体来说,LLM在智能体中承担四项核心职责:理解(读懂用户的自然语言意图)、推理(把模糊需求拆解成可执行步骤)、决策(判断该调用哪个工具、传什么参数)、生成(把结果组织成人类可读的回复)。这四项能力直接决定了智能体的智商天花板。如果LLM推理弱,它可能把"帮我订明天去北京的机票"理解成"查一下北京的天气";如果LLM函数调用不稳,它可能给订票工具传错日期格式。所以——选对LLM,等于给你的智能体装对了大脑。
💡 小贴士:什么是LLM?
LLM(Large Language Model,大语言模型)是一种用海量文本训练出来的AI模型。你可以把它理解成一个"读过整个互联网的超级大脑"。它不是真的懂人类语言,而是通过统计规律学会了"给定上文,下一个词最可能是什么"。GPT系列、Claude系列、Qwen系列都属于LLM。
本章将带你完成两件事:第一,建立一套科学的模型选型框架,让你在面对眼花缭乱的模型列表时不再迷茫;第二,动手实现多模型API集成与智能路由,为后续章节的智能体开发打下地基。
💡 本章代码均可独立运行,建议边读边在本地敲一遍。所有示例使用Python 3.10+。
3.2 商业API模型对比(2026年最新)
商业API模型是大多数智能体项目的首选——无需自建GPU集群,按量付费,开箱即用。2026年,三大厂商的旗舰模型已进入"五美元时代":每百万输入token的成本约5美元,相比2024年下降了80%以上。
💡 小贴士:什么是token?
Token是模型处理文本的最小单位。对英文,大约1个token ≈ 0.75个单词(4个字符);对中文,1个汉字大约消耗1-2个token。模型按处理的token数量计费——你输入的叫"输入token"(prompt tokens),模型输出的叫"输出token"(completion tokens)。举个例子:让模型总结一篇3000字的文章,输入约6000 token,输出约1000 token。
3.2.1 OpenAI GPT-5.x 系列
OpenAI在2025年底发布了GPT-5架构,2026年中又推出了迭代版本GPT-5.5。当前产品线覆盖从旗舰到轻量的完整光谱:
选模型就像买车——有人要跑赛道(旗舰GPT-5.5),有人要日常通勤(GPT-5.4),有人要省钱跑量(mini/nano)。你不需要每辆车都买最贵的,关键是匹配用途。下面的表格就是各款"车型"的参数表,先看价格和"排量"(能力),再决定买哪辆。
| 模型 |
输入价格($/M tokens) |
输出价格($/M tokens) |
上下文窗口 |
多模态 |
函数调用 |
| GPT-5.5(旗舰) |
$5.00 |
$30.00 |
400K |
✅ 文本+图像+音频 |
✅ |
| GPT-5.4 |
$2.50 |
$15.00 |
400K |
✅ 文本+图像 |
✅ |
| GPT-5.4-mini |
$0.75 |
$4.50 |
256K |
✅ 文本+图像 |
✅ |
| GPT-5.4-nano |
$0.15 |
$0.90 |
128K |
❌ 仅文本 |
✅ |
💡 小贴士:什么是"上下文窗口"?
上下文窗口(Context Window)是模型一次能"看到"的最大文本量,单位是token。比如400K上下文,意味着模型一次能处理约40万token(相当于一本中等厚度的书)。窗口越大,能塞进去的对话历史、参考文档就越多;但代价是价格更高、速度更慢。不要盲目追求最大窗口,够用就行。
GPT-5.5在推理基准测试MMLU-Pro上达到89.2分,是目前公开API中综合能力最强的模型之一。GPT-5.4-mini则是性价比之王——能力达到旗舰的85%,价格仅为1/7。
3.2.2 Anthropic Claude 4.x 系列
Anthropic的Claude 4系列以"安全+长文"著称,在代码生成和超长文档理解方面有独特优势:
同样用买车来打比方:Claude Opus 4就像豪华越野车——贵,但在崎岖山路(复杂代码、长文档)上特别稳;Sonnet 4是中端轿车,性价比均衡;Haiku 4是经济型小车,便宜够用。
| 模型 |
输入价格($/M tokens) |
输出价格($/M tokens) |
上下文窗口 |
多模态 |
函数调用 |
| Claude Opus 4 |
$15.00 |
$75.00 |
500K |
✅ 文本+图像 |
✅ |
| Claude Sonnet 4 |
$3.00 |
$15.00 |
500K |
✅ 文本+图像 |
✅ |
| Claude Haiku 4 |
$0.80 |
$4.00 |
256K |
✅ 文本+图像 |
✅ |
Claude Opus 4是当前上下文窗口最长的旗舰模型(500K tokens),适合处理整本书级别的长文档。Claude Haiku 4定位与GPT-5.4-mini直接竞争,价格接近但上下文更长。
⚠️ Claude系列的价格偏高,但在复杂代码重构、严格格式输出等场景下,Claude的"一次成功率"往往更高,综合成本反而更低。不要只看单价,要看单位任务的完成成本。 举个例子:一个任务用便宜模型可能要重试3次才成功,而Claude一次就过——算总账,Claude反而更省钱。
3.2.3 Google Gemini 2.5 系列
Google的Gemini 2.5系列在2026年终于补齐了函数调用和Agent能力的短板,成为有力的第三方选择:
| 模型 |
输入价格($/M tokens) |
输出价格($/M tokens) |
上下文窗口 |
多模态 |
函数调用 |
| Gemini 2.5 Pro |
$1.25 |
$10.00 |
2M |
✅ 文本+图像+音频+视频 |
✅ |
| Gemini 2.5 Flash |
$0.15 |
$0.60 |
1M |
✅ 文本+图像+音频 |
✅ |
Gemini 2.5 Pro的2M tokens上下文窗口是当前所有商业模型中最长的,适合需要同时处理海量参考文档的智能体——2M token相当于约200万字符,能一次性塞进十几本书或一整个代码仓库。Flash版本价格极低,适合高并发场景。
3.2.4 综合能力对比
把三家旗舰放到一张雷达图上对比会更直观(以下为综合评测分数,满分10):
| 维度 |
GPT-5.5 |
Claude Opus 4 |
Gemini 2.5 Pro |
| 推理能力 |
9.5 |
9.2 |
8.8 |
| 代码生成 |
9.3 |
9.6 |
8.5 |
| 多模态理解 |
9.0 |
8.0 |
9.5 |
| 函数调用稳定性 |
9.4 |
8.8 |
8.7 |
| 长文档处理 |
8.5 |
9.5 |
9.8 |
| 性价比 |
8.0 |
6.5 |
8.8 |
| 中文能力 |
9.0 |
8.5 |
8.8 |
选型建议速查:
- 通用智能体 → GPT-5.4(平衡能力与成本)
- 代码智能体 → Claude Sonnet 4(代码质量最佳)
- 超长文档分析 → Gemini 2.5 Pro(2M上下文无敌手)
- 高并发低成本 → GPT-5.4-nano 或 Gemini 2.5 Flash
- 中文场景 → GPT-5.5 或国产开源模型(见下节)
💡 小贴士:为什么没有"万能模型"?
模型能力是多方权衡的结果。推理强通常意味着参数大、价格高;上下文长通常意味着注意力分散、速度变慢。所以现实中,最聪明的做法不是"一个模型打天下",而是根据任务多模型混用——这正是后文"智能路由"要解决的问题。
3.3 开源模型生态
商业API虽好,但有三个硬伤:数据隐私、长期成本、供应商锁定。当你的智能体需要处理企业内部数据,或者调用量达到每月数亿token时,开源模型就成为了必选项。
💡 小贴士:开源 vs 商业API怎么选?
简单的判断标准:如果你的智能体调用量每月低于5000万token,或者要处理敏感数据(医疗、金融、内部代码),优先考虑开源模型本地部署;否则商业API更省心。开源模型的好处是数据不出企业,长期成本可控;坏处是要自己管GPU、运维、模型更新。
3.3.1 主流开源模型一览
2026年,开源模型已能覆盖商业模型80%以上的能力,以下三大家族最为活跃:
| 模型家族 |
旗舰型号 |
参数规模 |
推理能力 |
中文能力 |
许可证 |
| Meta Llama 3 |
Llama 3.1-405B |
405B |
⭐⭐⭐⭐⭐ |
⭐⭐⭐ |
Llama Community License |
| 阿里 Qwen |
Qwen3-235B |
235B |
⭐⭐⭐⭐⭐ |
⭐⭐⭐⭐⭐ |
Apache 2.0 |
| DeepSeek |
DeepSeek-V3 |
671B (MoE, 37B激活) |
⭐⭐⭐⭐⭐ |
⭐⭐⭐⭐ |
MIT |
💡 小贴士:什么是"参数规模"和MoE?
参数(Parameters)是模型内部的"可调旋钮",数量越多通常能力越强,但也更耗资源。比如235B就是2350亿个参数。MoE(Mixture of Experts,混合专家)是一种特殊架构:模型总参数很大,但每次推理只激活其中一小部分"专家"——DeepSeek-V3有671B总参数,但每次只激活37B,所以又强又省钱。
Meta Llama 3系列是全球生态最完善的开源模型,社区适配的工具链最丰富。但其中文能力相对偏弱,在纯中文场景下不如国产模型。
阿里Qwen3系列是目前中文能力最强的开源模型,Qwen3-235B在C-Eval中文评测中得分87.3,超越所有商业模型。Apache 2.0许可证意味着商用完全无限制,企业可以放心用。
DeepSeek-V3采用MoE(混合专家)架构,以671B总参数、37B激活参数实现了接近旗舰的效果,但推理成本极低。在数学和代码任务上表现尤为突出,被誉为"开源界的性价比之王"。
💡 小贴士:开源模型怎么挑?
记住三个匹配原则:①看你的语言——做中文产品优先Qwen3,做英文/全球产品优先Llama 3;②看你的硬件——单卡选7B-14B,四卡可选70B,DeepSeek-V3这种671B级别得上云;③看你的合规要求——Apache 2.0和MIT最宽松,商用零顾虑;Llama Community License对超大规模商用有限制条款,法务要过一遍。
3.3.2 本地部署方案
开源模型的部署主要有两条路线:
路线一:Ollama(开发/原型阶段)
Ollama是一键式本地模型管理工具,适合开发阶段快速验证。它把复杂的模型下载、量化、加载流程都封装好了,你只需要一条命令就能跑起来:
1
2
3
4
5
6
7
8
|
# 安装 Ollama(macOS/Linux)
curl -fsSL https://ollama.com/install.sh | sh
# 拉取并运行 Qwen3 模型(8B 版本适合本地开发)
ollama run qwen3:8b
# 拉取 Llama 3.1
ollama run llama3.1:8b
|
Ollama自带兼容OpenAI API格式的HTTP服务,端口默认11434,你的智能体代码几乎不用改:
这段代码做了什么:用Python的requests库向本地Ollama服务发一个聊天请求,模拟OpenAI的API格式,然后打印模型回复。你可以把它当作"本地版的OpenAI"来用。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
# ollama_call.py —— 通过 Ollama 调用本地开源模型
import requests
# Ollama 兼容 OpenAI API 格式,所以请求结构跟调用 OpenAI 一模一样
response = requests.post(
"http://localhost:11434/v1/chat/completions", # Ollama 默认服务地址
json={
"model": "qwen3:8b", # 指定要调用的本地模型
"messages": [{"role": "user", "content": "你好,请介绍一下你自己"}],
},
)
# 从响应 JSON 中取出模型回复文本
print(response.json()["choices"][0]["message"]["content"])
|
路线二:vLLM(生产环境)
vLLM是高性能推理引擎,支持PagedAttention和连续批处理,吞吐量是原生Transformers的10-20倍,适合生产部署:
1
2
3
4
5
6
7
8
|
# 安装 vLLM
pip install vllm
# 启动 Qwen3 服务,兼容 OpenAI API 格式
python -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen3-235B-A22B \
--tensor-parallel-size 4 \
--port 8000
|
vLLM同样暴露OpenAI兼容API,因此你的智能体代码可以在商业API和自建vLLM之间无缝切换——这是统一API格式带来的巨大好处,后文我们会利用这一点。
🎯 实践经验:7B-14B参数的模型在单张A100/H100上即可流畅运行,适合中小团队。70B以上模型需要4-8张GPU做张量并行,建议使用云端GPU服务(如阿里云PAI、AWS Bedrock)按需启动。
3.4 模型选型维度框架
面对数十个模型,如何系统化决策?我总结为五个维度打分法:
维度一:能力
这是最直观的维度,但也是最容易被"跑分"误导的。不要只看MMLU这类学术基准,要关注与你任务直接相关的能力:
- 推理能力:多步推理、数学证明、逻辑链条
- 编码能力:代码生成、Bug修复、重构
- 多模态能力:图像理解、文档OCR、语音识别
建议用你自己的真实任务做A/B测试,取20个典型case,看各模型的"一次通过率"。跑分高不代表你的任务上表现好,实测才是硬道理。
💡 小贴士:怎么做A/B测试?
准备20个你业务里的真实问题(覆盖简单/中等/困难各1/3),让每个候选模型各跑一遍,人工打分(0-5分)。统计三个指标:平均分、一次通过率(不需要重试就算对的占比)、单次成本。综合这三个指标排序,选出赢家。别迷信跑分榜——你的任务才是唯一的标准答案。
维度二:成本
成本计算公式:
1
|
单次任务成本 = 输入tokens × 输入单价 + 输出tokens × 输出单价
|
注意输出价格通常是输入的3-6倍。一个常见误区是只看输入价格——如果你的智能体生成大量文本(如写报告),输出价格才是大头。
成本阶梯策略(推荐):
| 任务类型 |
推荐模型 |
单次成本估算 |
| 简单问答/分类 |
GPT-5.4-nano |
~$0.0002 |
| 日常对话/摘要 |
GPT-5.4-mini |
~$0.001 |
| 复杂推理/编码 |
GPT-5.4 |
~$0.005 |
| 极限挑战任务 |
GPT-5.5 / Claude Opus 4 |
~$0.02 |
💡 小贴士:怎么估算单次成本?
假设一个任务输入5000 token、输出1000 token,用GPT-5.4-mini(输入$0.75/M、输出$4.50/M):
成本 = 5000/1,000,000 × 0.75 + 1000/1,000,000 × 4.5 = 0.00375 + 0.0045 ≈ $0.008。每天调用1万次,约$80/天,月成本约$2400。看到这个数你就明白为什么需要智能路由了。
维度三:延迟
对于交互式智能体,延迟直接决定用户体验。关注两个指标:
- TTFT(Time To First Token):首token延迟,影响"响应感"——用户点发送后多久看到第一个字
- 吞吐(Tokens/second):生成速度,影响"完整度等待"——用户要等多久才看到完整回复
💡 小贴士:为什么TTFT比吞吐更重要?
用户对"开始响应"的耐心远低于"完整响应"。如果500ms内开始吐字,用户觉得"很快";如果3秒才开始,即使后面吐字飞快,用户也觉得"卡"。这就是为什么流式输出(streaming)几乎成了智能体的标配——先让字一个一个蹦出来,体感速度就上去了。
实测数据(2026年6月,美国东部区域):
| 模型 |
TTFT(ms) |
吞吐(tok/s) |
| GPT-5.4-mini |
280 |
85 |
| GPT-5.5 |
450 |
55 |
| Claude Haiku 4 |
320 |
90 |
| Gemini 2.5 Flash |
200 |
120 |
| 本地 Qwen3-8B(A100) |
80 |
150 |
经验法则:对话场景TTFT应<500ms,后台批处理可以忽略延迟。
💡 小贴士:怎么降低延迟?
三个手段:①用流式输出(streaming),让第一个字尽快蹦出来,体感延迟立刻减半;②简单任务用小模型,nano的TTFT只有旗舰的一半;③把不依赖模型结果的预渲染做了(比如先显示"正在思考…“动画),用感知管理弥补真实延迟。记住了:用户等的是"开始响应”,不是"完整响应"。
维度四:上下文长度
上下文长度决定了智能体能"记住"多少信息。但这不是越大越好——长上下文会导致注意力衰减(模型对中间位置的信息敏感度下降,学术上叫"Lost in the Middle"问题)和成本上升。
- 短对话/简单工具调用:128K足够
- RAG场景:128K-256K,把检索结果塞进去
- 整文档分析:500K+,考虑Gemini 2.5 Pro
维度五:函数调用支持
函数调用是智能体的命脉——智能体之所以能"动手做事",靠的就是让LLM输出结构化的函数调用指令。评估时要看:
💡 小贴士:什么是"函数调用"?
普通对话中,模型只输出文本。但智能体需要"动手"——查数据库、发邮件、调API。函数调用(Function Calling)让模型按JSON格式输出"我要调用哪个函数、参数是什么",你的代码再据此真正执行。这就把LLM从一个"会聊天的鹦鹉"变成了"会干活的助手"。
- 是否原生支持function calling
- 多函数并行调用能力
- 参数格式准确率(JSON Schema合规率)
- 幻觉函数率(调用不存在的函数)
2026年主流模型都已原生支持,但准确率差异仍然显著:GPT-5.5在复杂多函数场景下准确率97%,而部分开源模型只有85%左右。
💡 小贴士:函数调用准确率为什么重要?
假设你的智能体要调用三个工具——查库存、算运费、生成订单。如果模型只正确调用了一个、漏了另外两个,或者参数格式不对(传了字符串但工具要数字),整个流程就断了。准确率从97%掉到85%,意味着失败率从3%涨到15%——在每天10万次调用的系统里,这是3000次 vs 15000次失败的差别,重试成本和用户投诉都是天壤之别。
3.5 API集成实战
理论讲完,开始写代码。我们将实现一个统一的多模型调用封装,支持OpenAI、Anthropic和本地Ollama三种后端无缝切换。
3.5.1 环境准备
这段代码做了什么:安装三个Python包——openai是OpenAI官方SDK,anthropic是Anthropic官方SDK,python-dotenv用来从.env文件读取环境变量(避免把密钥硬编码进代码)。
1
2
|
# 安装依赖
pip install openai anthropic python-dotenv
|
创建 .env 文件管理密钥(千万不要把密钥写进代码再提交到Git,这是新手最常犯的安全错误):
1
2
3
4
|
# .env
OPENAI_API_KEY=sk-你的密钥
ANTHROPIC_API_KEY=sk-ant-你的密钥
OLLAMA_BASE_URL=http://localhost:11434
|
3.5.2 OpenAI SDK 调用示例
这段代码做了什么:用OpenAI官方SDK调用GPT-5.4-mini,发一个问题,打印回复。它演示了最基础的"构造消息列表→调用API→取回复"三步流程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
# openai_call.py —— OpenAI GPT-5.x 调用示例
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv() # 从 .env 文件加载环境变量
# 初始化客户端,会自动读取 OPENAI_API_KEY 环境变量
client = OpenAI()
def chat_with_gpt(
messages: list[dict],
model: str = "gpt-5.4-mini",
temperature: float = 0.7,
tools: list[dict] | None = None,
) -> str:
"""调用 OpenAI 模型进行对话
参数:
messages: 消息列表,格式同 OpenAI API
model: 模型名称,如 gpt-5.5 / gpt-5.4 / gpt-5.4-mini
temperature: 采样温度(0最确定,1最随机)
tools: 函数调用工具定义
返回:
模型回复的文本内容
"""
response = client.chat.completions.create(
model=model, # 指定模型
messages=messages, # 对话历史
temperature=temperature, # 控制随机性
tools=tools, # 可选:传入函数定义
)
# response.choices[0].message.content 就是模型的文本回复
return response.choices[0].message.content
# 测试调用
if __name__ == "__main__":
messages = [
{"role": "system", "content": "你是一个专业的Python开发助手"},
{"role": "user", "content": "用一行代码实现列表去重并保持顺序"},
]
reply = chat_with_gpt(messages, model="gpt-5.4-mini")
print(f"GPT 回复: {reply}")
|
💡 小贴士:消息列表的role是什么意思?
OpenAI的对话由消息列表组成,每条消息有个role:system是系统提示词(给模型定人设),user是用户输入,assistant是模型之前的回复,tool是函数返回结果。多轮对话就是把这一串消息按时间顺序传给模型,它就能"记住"上下文。
3.5.3 Anthropic SDK 调用示例
Anthropic的API结构与OpenAI略有不同,system消息需要单独传参(不能塞进messages里):
这段代码做了什么:用Anthropic官方SDK调用Claude Sonnet 4。注意它和OpenAI的两个关键区别——system是顶层参数而非消息列表中的一条;返回结构是response.content[0].text而非response.choices[0].message.content。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
# anthropic_call.py —— Claude 4.x 调用示例
from dotenv import load_dotenv
from anthropic import Anthropic
load_dotenv()
# 自动读取 ANTHROPIC_API_KEY 环境变量
client = Anthropic()
def chat_with_claude(
user_message: str,
system_prompt: str = "你是一个专业的开发助手",
model: str = "claude-sonnet-4-20250514",
max_tokens: int = 4096,
) -> str:
"""调用 Claude 模型进行对话
参数:
user_message: 用户消息
system_prompt: 系统提示词(Claude 单独传参,不放 messages 里)
model: 模型名称
max_tokens: 最大输出长度(Claude 必填)
返回:
模型回复的文本内容
"""
response = client.messages.create(
model=model,
max_tokens=max_tokens,
system=system_prompt, # 注意:system 是顶层参数,不是 messages 里的一条
messages=[{"role": "user", "content": user_message}],
)
# Claude 的返回结构是 content 列表,取第一块文本
return response.content[0].text
# 测试调用
if __name__ == "__main__":
reply = chat_with_claude(
user_message="解释Python的GIL是什么,如何绕过它",
model="claude-sonnet-4-20250514",
)
print(f"Claude 回复: {reply}")
|
⚠️ 坑点提醒:Claude的max_tokens是必填参数,不传会报错;而OpenAI是可选的。这种API差异正是后文要做"统一封装"的原因——让上层代码不用记每家的怪癖。
3.5.4 统一接口封装
每家API的调用方式都不同:OpenAI把system塞进messages,Claude把system放顶层;返回结构一个叫choices[0].message.content,一个叫content[0].text;连token计费字段名都不一样(prompt_tokens vs input_tokens)。这在多模型切换时非常痛苦。
我们来封装一个统一接口,让上层代码完全不用关心底层用的是哪个模型——只管传一个model字符串,剩下的交给封装层处理:
这段代码做了什么:定义一个UnifiedLLM类,对外只暴露一个chat()方法。它内部根据模型名前缀(gpt-/claude-/其他)自动路由到对应SDK,并把三家的返回结构统一成LLMResponse对象(包含回复文本、token用量、估算成本)。此外还内置了一张定价表,每次调用都能算出花了多少钱。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
|
# llm_router.py —— 统一多模型调用封装
import os
from dataclasses import dataclass
from dotenv import load_dotenv
from openai import OpenAI
from anthropic import Anthropic
load_dotenv()
@dataclass
class LLMResponse:
"""统一的模型响应结构"""
content: str # 回复文本
model: str # 实际使用的模型
input_tokens: int # 输入token数
output_tokens: int # 输出token数
cost_usd: float # 本次调用估算成本
# 各模型的定价表($/M tokens):(输入单价, 输出单价)
PRICING = {
"gpt-5.5": (5.00, 30.00),
"gpt-5.4": (2.50, 15.00),
"gpt-5.4-mini": (0.75, 4.50),
"gpt-5.4-nano": (0.15, 0.90),
"claude-opus-4": (15.00, 75.00),
"claude-sonnet-4": (3.00, 15.00),
"claude-haiku-4": (0.80, 4.00),
"qwen3:8b": (0.0, 0.0), # 本地部署,成本为0
}
def _normalize_model_name(model: str) -> str:
"""规范模型名,便于查价。
Anthropic 的模型名带日期后缀(如 claude-sonnet-4-20250514),
但 PRICING 表里只存了 claude-sonnet-4,所以查价前要去掉后缀。
"""
if "-20" in model: # 形如 -20250514 的日期后缀
return model.rsplit("-20", 1)[0]
return model
class UnifiedLLM:
"""统一大模型调用接口,支持 OpenAI / Anthropic / Ollama"""
def __init__(self):
self.openai_client = OpenAI()
self.anthropic_client = Anthropic()
# Ollama 复用 OpenAI SDK,只是把地址指向本地服务
self.ollama_client = OpenAI(
base_url=os.getenv("OLLAMA_BASE_URL", "http://localhost:11434") + "/v1",
api_key="ollama", # Ollama 不校验密钥,随便填
)
def chat(
self,
messages: list[dict],
model: str = "gpt-5.4-mini",
system: str | None = None,
temperature: float = 0.7,
max_tokens: int = 4096,
) -> LLMResponse:
"""统一对话接口
参数:
messages: 消息列表(不含system)
model: 模型名称,自动路由到对应SDK
system: 系统提示词(可选)
temperature: 采样温度
max_tokens: 最大输出token数
返回:
LLMResponse 对象
"""
if model.startswith("gpt-"):
return self._call_openai(messages, model, system, temperature, max_tokens)
elif model.startswith("claude-"):
return self._call_anthropic(messages, model, system, temperature, max_tokens)
else:
# 默认走 Ollama(本地开源模型)
return self._call_ollama(messages, model, system, temperature, max_tokens)
def _call_openai(self, messages, model, system, temperature, max_tokens) -> LLMResponse:
"""调用 OpenAI 系列"""
full_messages = []
if system:
full_messages.append({"role": "system", "content": system})
full_messages.extend(messages)
resp = self.openai_client.chat.completions.create(
model=model,
messages=full_messages,
temperature=temperature,
max_tokens=max_tokens,
)
usage = resp.usage
cost = self._calc_cost(model, usage.prompt_tokens, usage.completion_tokens)
return LLMResponse(
content=resp.choices[0].message.content,
model=model,
input_tokens=usage.prompt_tokens,
output_tokens=usage.completion_tokens,
cost_usd=cost,
)
def _call_anthropic(self, messages, model, system, temperature, max_tokens) -> LLMResponse:
"""调用 Claude 系列"""
resp = self.anthropic_client.messages.create(
model=model,
max_tokens=max_tokens,
temperature=temperature,
system=system or "", # Claude 的 system 是顶层参数
messages=messages,
)
usage = resp.usage
# 注意:Claude 的 token 字段叫 input_tokens/output_tokens
cost = self._calc_cost(model, usage.input_tokens, usage.output_tokens)
return LLMResponse(
content=resp.content[0].text,
model=model,
input_tokens=usage.input_tokens,
output_tokens=usage.output_tokens,
cost_usd=cost,
)
def _call_ollama(self, messages, model, system, temperature, max_tokens) -> LLMResponse:
"""调用本地 Ollama 服务(OpenAI兼容格式)"""
full_messages = []
if system:
full_messages.append({"role": "system", "content": system})
full_messages.extend(messages)
resp = self.ollama_client.chat.completions.create(
model=model,
messages=full_messages,
temperature=temperature,
max_tokens=max_tokens,
)
usage = resp.usage
return LLMResponse(
content=resp.choices[0].message.content,
model=model,
input_tokens=usage.prompt_tokens if usage else 0,
output_tokens=usage.completion_tokens if usage else 0,
cost_usd=0.0, # 本地部署无API成本
)
@staticmethod
def _calc_cost(model: str, input_tok: int, output_tok: int) -> float:
"""根据定价表计算调用成本(美元)"""
normalized = _normalize_model_name(model) # 去掉日期后缀再查价
pricing = PRICING.get(normalized, (0, 0))
return (input_tok / 1_000_000 * pricing[0]) + (output_tok / 1_000_000 * pricing[1])
# 使用示例
if __name__ == "__main__":
llm = UnifiedLLM()
# 用同一个接口调用不同模型——上层代码完全一样,只改 model 字符串
question = "用Python实现快速排序,并解释时间复杂度"
# 调用 GPT
r1 = llm.chat(
[{"role": "user", "content": question}],
model="gpt-5.4-mini",
system="你是一个资深算法工程师",
)
print(f"[GPT-5.4-mini] 成本: ${r1.cost_usd:.4f}")
print(f"回复: {r1.content[:100]}...\n")
# 调用 Claude
r2 = llm.chat(
[{"role": "user", "content": question}],
model="claude-sonnet-4-20250514",
system="你是一个资深算法工程师",
)
print(f"[Claude Sonnet 4] 成本: ${r2.cost_usd:.4f}")
print(f"回复: {r2.content[:100]}...\n")
# 调用本地 Qwen
r3 = llm.chat(
[{"role": "user", "content": question}],
model="qwen3:8b",
system="你是一个资深算法工程师",
)
print(f"[Qwen3-8B 本地] 成本: ${r3.cost_usd:.4f}")
print(f"回复: {r3.content[:100]}...")
|
这个封装的设计要点:
- 统一入口
chat() 方法,上层代码只管传 model 字符串
- 自动路由:根据模型名前缀自动分发到对应SDK
- 成本透明:每次调用返回估算成本,便于成本监控
- Ollama复用OpenAI SDK:本地模型零代码切换
- 模型名规范化:
_normalize_model_name处理Claude的日期后缀,保证定价表能正确命中
3.6 模型路由与降级策略
3.6.0 为什么需要多模型路由?
先讲一个真实场景:你做了一个客服智能体,每天处理10万次对话。如果全部用GPT-5.5旗舰,每天光API费就要$2000+;但如果全部用GPT-5.4-nano,遇到"帮我对比这两份合同的法律风险"这种复杂问题,nano根本答不好,用户投诉率飙升。
单一模型要么贵死,要么菜死。出路是:让简单的"你好/查订单状态"走nano(几乎免费),让复杂的"合同分析/代码生成"走旗舰(贵但量少)。这就是智能路由——根据任务复杂度动态分配模型。
这背后还有一个朴素的经济原理——二八定律:80%的请求是简单任务,20%是复杂任务。用小模型扛住80%的量,用大模型精耕20%的难活,整体成本能降60-80%,而质量几乎不损失。再加上降级链(一个模型挂了自动切备用),系统还顺带获得了高可用性。一箭双雕。
3.6.1 路由策略设计
一个实用的三级路由策略:
| 层级 |
触发条件 |
模型 |
用途 |
| L1 轻量 |
简单问答、意图分类、关键词提取 |
GPT-5.4-nano |
80%流量 |
| L2 标准 |
多轮对话、RAG问答、工具调用 |
GPT-5.4-mini |
15%流量 |
| L3 旗舰 |
复杂推理、代码生成、多步规划 |
GPT-5.5 / Claude Sonnet 4 |
5%流量 |
如何判断任务复杂度?有两种方式:
- 规则路由:根据任务类型标签直接映射,简单高效。比如看到"你好"就判简单,看到"架构设计"就判复杂。
- LLM路由:用一个nano级小模型先判断复杂度,再分发给对应模型。更灵活,但会多一次调用、增加延迟和成本。
💡 小贴士:规则路由 vs LLM路由怎么选?
早期先用规则路由——简单、可控、零额外成本。等流量上来、任务类型复杂化后,再上LLM路由。两者也可以混用:先用规则快速筛掉明显的简单/复杂任务,剩下的模糊地带交给小模型判断。
3.6.2 降级链设计
当API出现限流(rate limit)或超时时,需要自动降级到备选模型,保证服务可用性。降级链的设计原则是:每一级都比上一级更便宜、更稳定,但能力略弱,最末端兜底一个本地模型或默认回复,确保"再差也有响应"。
1
|
GPT-5.5 → GPT-5.4 → GPT-5.4-mini → Qwen3(本地)→ 返回缓存/默认回复
|
💡 小贴士:为什么降级链末端要放本地模型?
商业API可能整个区域宕机(2024年OpenAI就发生过全球级故障)。这时如果你的降级链全是商业模型,会一窝端全挂。末端放一个本地Ollama模型,即使断网也能兜底,保证服务"降级但不中断"。
3.6.3 智能路由器实现
这段代码做了什么:实现一个SmartRouter类,它的工作流是"判断复杂度→选主模型→调用→失败则沿降级链依次重试→全失败返回兜底回复"。它还内置了累计成本统计,方便你随时看"今天烧了多少钱"。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
|
# smart_router.py —— 智能路由与降级
import time
import logging
from llm_router import UnifiedLLM, LLMResponse
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# 任务复杂度到模型的映射
COMPLEXITY_MODEL_MAP = {
"simple": "gpt-5.4-nano", # 简单任务
"standard": "gpt-5.4-mini", # 标准任务
"complex": "gpt-5.4", # 复杂任务
"extreme": "gpt-5.5", # 极限任务
}
# 每个主模型的降级链:主模型挂了就依次试这些备选
FALLBACK_CHAINS = {
"gpt-5.5": ["gpt-5.4", "gpt-5.4-mini", "qwen3:8b"],
"gpt-5.4": ["gpt-5.4-mini", "qwen3:8b"],
"gpt-5.4-mini": ["gpt-5.4-nano", "qwen3:8b"],
"gpt-5.4-nano": ["qwen3:8b"],
"claude-sonnet-4-20250514": ["gpt-5.4-mini", "qwen3:8b"],
}
# 关键词 → 复杂度等级的简单规则路由
COMPLEXITY_KEYWORDS = {
"extreme": ["架构设计", "系统设计", "重构整个", "安全审计", "性能优化方案"],
"complex": ["编写代码", "调试", "推理", "分析报告", "多步骤"],
"standard": ["翻译", "总结", "摘要", "问答", "分类"],
"simple": ["你好", "谢谢", "再见", "是或否"],
}
class SmartRouter:
"""智能模型路由器:自动选择模型 + 故障降级"""
def __init__(self):
self.llm = UnifiedLLM()
self.total_cost = 0.0 # 累计成本(美元)
self.call_count = 0 # 累计调用次数
def classify_complexity(self, user_input: str) -> str:
"""根据关键词判断任务复杂度(规则路由)
在生产中可以用一个小模型替代关键词匹配,
让模型自己判断复杂度,效果更好但会增加一次调用。
"""
for level, keywords in COMPLEXITY_KEYWORDS.items():
if any(kw in user_input for kw in keywords):
return level
return "standard" # 默认标准
def route(self, user_input: str, system: str = "") -> LLMResponse:
"""智能路由主入口
流程:1.判断复杂度 → 2.选模型 → 3.调用 → 4.失败则降级
"""
# 第一步:判断复杂度
complexity = self.classify_complexity(user_input)
primary_model = COMPLEXITY_MODEL_MAP[complexity]
logger.info(f"任务复杂度: {complexity} → 主模型: {primary_model}")
# 第二步:构建降级链(主模型 + 备选列表)
fallback_models = FALLBACK_CHAINS.get(primary_model, ["qwen3:8b"])
models_to_try = [primary_model] + fallback_models
# 第三步:依次尝试调用,成功即返回
messages = [{"role": "user", "content": user_input}]
last_error = None
for model in models_to_try:
try:
resp = self.llm.chat(
messages=messages,
model=model,
system=system,
)
self.total_cost += resp.cost_usd
self.call_count += 1 # 记录一次成功调用
logger.info(
f"✅ 调用成功 [{model}] "
f"成本=${resp.cost_usd:.4f} 累计=${self.total_cost:.4f}"
)
return resp
except Exception as e:
logger.warning(f"❌ [{model}] 调用失败: {e},尝试降级...")
last_error = e
time.sleep(0.5) # 降级前短暂等待,给上游喘口气
# 所有模型都失败,返回兜底回复
logger.error(f"所有模型均不可用,最后错误: {last_error}")
return LLMResponse(
content="抱歉,服务暂时不可用,请稍后重试。",
model="fallback",
input_tokens=0,
output_tokens=0,
cost_usd=0.0,
)
def get_cost_report(self) -> dict:
"""获取成本报告"""
return {
"total_cost_usd": round(self.total_cost, 4),
"avg_cost_per_call": round(
self.total_cost / max(self.call_count, 1), 4
),
}
# 使用示例
if __name__ == "__main__":
router = SmartRouter()
test_cases = [
"你好,今天天气怎么样?", # → simple → nano
"请帮我总结这篇文章的要点", # → standard → mini
"帮我编写一个Python并发爬虫,要求支持重试", # → complex → 5.4
"请设计一个支持百万并发的微服务架构方案", # → extreme → 5.5
]
for query in test_cases:
print(f"\n{'='*60}")
print(f"用户输入: {query}")
resp = router.route(query, system="你是一个专业的技术助手")
print(f"使用模型: {resp.model}")
print(f"本次成本: ${resp.cost_usd:.4f}")
print(f"回复预览: {resp.content[:80]}...")
print(f"\n{'='*60}")
print(f"总成本: ${router.total_cost:.4f}")
print(f"成本报告: {router.get_cost_report()}")
|
运行这个路由器,你会看到不同复杂度的任务自动分配到不同级别的模型。简单问题用nano处理,成本几乎为零;复杂问题才动用旗舰模型。这就是成本优化的核心——好钢用在刀刃上。
💡 进阶提示:在生产环境中,还可以加入以下增强:
- 缓存层:相同请求直接返回缓存结果(Redis),可节省30%+成本。比如"今天天气怎么样"这类高频问题,第一次算完存起来,后续命中缓存直接返回。
- 批量处理:非实时任务用Batch API(OpenAI/Anthropic均支持),价格减半。适合夜间跑的数据标注、报告生成。
- Token预算:为每个用户/会话设置token预算上限,防止成本失控。比如免费用户每天限1000 token,超出则降级到nano。
- A/B测试:同时用两个模型处理同一请求,对比效果后动态调整路由权重。让数据告诉你哪个模型在你的任务上更值。
3.7 小结
本章我们从"LLM是智能体的大脑"这一核心认知出发,系统梳理了2026年大模型选型的完整知识体系:
-
商业模型对比:GPT-5.x系列综合能力最强、生态最成熟;Claude 4.x系列在代码和长文档场景有独特优势;Gemini 2.5系列以超长上下文和低价取胜。没有"最好"的模型,只有"最适合"的模型。
-
开源模型生态:Qwen3系列中文最强、Llama 3生态最完善、DeepSeek-V3性价比最高。用Ollama做原型验证,用vLLM做生产部署。
-
五维选型框架:能力、成本、延迟、上下文、函数调用——五个维度交叉评分,拒绝"跑分党"和"唯价格论"。
-
统一接口封装:用UnifiedLLM类屏蔽各家API差异,上层代码只需指定模型名,实现多模型无缝切换。配套的_normalize_model_name解决了Claude模型名带日期后缀导致定价查不到的坑。
-
智能路由与降级:简单任务用小模型、复杂任务用大模型,配合降级链保证高可用。这一套机制能让你的智能体成本降低60-80%。记住二八定律——80%的流量用最便宜的模型扛住。
记住一个原则:模型选型不是一次性决策,而是持续优化过程。模型在迭代,价格在下降,你的路由策略也要跟着调整。建议每月做一次成本-质量回顾,动态更新你的路由表。
3.8 下章预告:提示工程进阶
选好了模型只是第一步。同一个模型,不同的提示词可以让输出质量天差地别。第4章我们将深入提示工程进阶:
- 结构化提示词模板设计
- Few-Shot与Chain-of-Thought的工程化应用
- ReAct(Reasoning + Acting)模式详解
- 多轮对话中的提示词管理策略
- 提示词版本管理与A/B测试
如果说模型是引擎,那提示词就是方向盘和油门。下一章,我们学会如何精准操控这辆智能体赛车。🚗💨