本节摘要:本节精读
src/smolagents/models.py——2102 行,smolagents 最大的单文件。它回答一个工程问题:agent 逻辑(第 2、3 章的 ReAct 循环与代码解释器)如何与具体的 LLM 服务解耦?答案是Model基类定义generate/generate_stream统一接口,ChatMessage统一消息封装,下面再挂 10 个实现类:本地三兄弟(TransformersModel/vLLMModel/MLXModel)、云端六家(InferenceClientModel/LiteLLMModel/OpenAIModel/AzureOpenAIModel/AmazonBedrockModel/LiteLLMRouterModel),中间还有一层ApiModel抽象。换模型=换一个类,agent 代码零改动。读完本节,你能按场景(本地隐私/高吞吐/多云)选对 Model 类。
内容来源:原项目源码
src/smolagents/models.py(2102 行)与examples/agent_from_any_llm.py,精读并套用体系化模板。
⚠️ 注意:
Model是抽象基类,直接实例化后调用generate会抛NotImplementedError;各实现类的重依赖(vllm/mlx_lm/openai/litellm/boto3)都是可选安装,未安装时报ModuleNotFoundError并提示对应的pip install 'smolagents[...]'命令。
阅读完本节,你应当能够:
Model 基类的 generate 统一接口签名与 _prepare_completion_kwargs 的参数优先级。ChatMessage 的五个字段(role/content/tool_calls/raw/token_usage)各自的作用。ApiModel 中间层)。先看这 2102 行的版图:
70-200 ChatMessage 家族:工具调用/消息角色/文本工具调用解析 203-280 流式 delta 家族与 agglomerate_stream_deltas(下节精读) 282-438 消息清洗 get_clean_message_list、工具调用解析、stop 参数探测 441-631 Model 基类:统一接口的核心 633-1136 本地三兄弟:VLLMModel / MLXModel / TransformersModel 1138-1202 ApiModel 中间基类:限流 + 重试的公共底座 1205-2063 云端六家:LiteLLMModel/LiteLLMRouterModel/InferenceClientModel/ OpenAIModel/AzureOpenAIModel/AmazonBedrockModel 2066-2102 MODEL_REGISTRY 反序列化白名单
为什么这个文件最大?因为每接入一家 LLM 供应商,就要处理一套方言:消息角色叫法不同(tool-call vs function)、工具 schema 格式不同、token 用量字段不同、甚至 Bedrock 连消息里的 type 键都要删掉(models.py:2000-2003)。smolagents 把所有方言差异压平到一个文件里,换来的是 agent 侧的绝对简洁——agents.py 里只认 ChatMessage。
models.py:452-596,核心只有三段。先是构造参数:
452 class Model: 484 def __init__( 485 self, 486 flatten_messages_as_text: bool = False, 487 tool_name_key: str = "name", 488 tool_arguments_key: str = "arguments", 489 model_id: str | None = None, 490 **kwargs, 491 ): 492 self.flatten_messages_as_text = flatten_messages_as_text 493 self.tool_name_key = tool_name_key 494 self.tool_arguments_key = tool_arguments_key 495 self.kwargs = kwargs 496 self.model_id: str | None = model_id
flatten_messages_as_text 决定多模态消息是拍平成纯文本(适配不支持图片列表的本地后端)还是保留结构化 content;**kwargs 原样转存,最终会转发给底层 completion 调用——这是"构造时配 temperature/max_tokens"的实现基础。
然后是必须由子类实现的 generate(models.py:553-578,签名节选):
553 def generate( 554 self, 555 messages: list[ChatMessage], 556 stop_sequences: list[str] | None = None, 557 response_format: dict[str, str] | None = None, 558 tools_to_call_from: list[Tool] | None = None, 559 **kwargs, 560 ) -> ChatMessage: ... 578 raise NotImplementedError("This method must be implemented in child classes")
四个参数覆盖 agent 的全部需要:messages 是记忆转出的消息列表;stop_sequences 让 ToolCallingAgent 在 </tool_call> 处截断;response_format 传结构化输出 schema;tools_to_call_from 传工具列表。返回值恒为 ChatMessage。还有 __call__ 直接委托 generate(models.py:580-581),所以 agent 里可以写 model(messages)。
接口的"胶水"是 _prepare_completion_kwargs(models.py:502-551),它把 ChatMessage 清洗成各家 API 要的字典,并实现三级参数优先级:
521 # Parameter priority (highest to lowest): 522 # 1. self.kwargs (model defaults) 523 # 2. Explicitly passed kwargs 524 # 3. Specific parameters (stop_sequences, response_format, etc.) ... 543 completion_kwargs.update(kwargs) 544 # Override with self.kwargs 545 for kwarg_name, kwarg_value in self.kwargs.items(): 546 if kwarg_value is REMOVE_PARAMETER: 547 completion_kwargs.pop(kwarg_name, None) 548 else: 549 completion_kwargs[kwarg_name] = kwarg_value
构造时的 self.kwargs 优先级最高——你在 TransformersModel(max_new_tokens=5000) 里写死的值,任何调用处都覆盖不了;配合哨兵值 REMOVE_PARAMETER(models.py:441-449)还能反向删除某个参数(比如给不支持 tool_choice 的后端拆掉它)。
💡 阶梯要点:统一接口的价值在第 2、3 章已埋下伏笔——
MultiStepAgent.step里只写self.model.generate(...),从不知道背后是 GPU 上的 1.7B 小模型还是云端 80B 大模型。接口收敛在一处,变化被隔离在十个子类里,这是"barebones"哲学在模型层的落地。
models.py:123-137,五个字段(MessageRole 枚举定义在 models.py:111-120,含 USER/ASSISTANT/SYSTEM/TOOL_CALL/TOOL_RESPONSE 五种角色):
123 @dataclass 124 class ChatMessage: 125 role: MessageRole 126 content: str | list[dict[str, Any]] | None = None 127 tool_calls: list[ChatMessageToolCall] | None = None 128 raw: Any | None = None # Stores the raw output from the API 129 token_usage: TokenUsage | None = None
role:五种角色,内部多出的 TOOL_CALL/TOOL_RESPONSE 会在 tool_role_conversions(models.py:282-285)里折算回 assistant/user,因为多数 API 只认三种角色;content:纯文本或多模态块列表([{"type": "image", ...}, {"type": "text", ...}]);tool_calls:结构化工具调用,__post_init__ 里用 _coerce_tool_call 把 OpenAI 的 pydantic 对象、裸 dict 统一拍成内部 dataclass——又一处方言压平;raw:保留 API 原始响应,调试时有用,model_dump_json 序列化时会忽略;token_usage:挂第 2 节精读的 TokenUsage。继承结构与分工一表看全(行号为 __init__ 位置):
| 类 | 继承 | 底层 | 一句话定位 |
|---|---|---|---|
| TransformersModel | Model | transformers + torch | 本地推理,auto 探测 VLM(models.py:906) |
| VLLMModel | Model | vLLM LLM | 高吞吐本地服务,支持结构化输出(models.py:648) |
| MLXModel | Model | mlx-lm | Apple 芯片优化,Mac 本地跑(models.py:791) |
| ApiModel | Model | (抽象) | 云端公共底座:限流+重试(models.py:1161) |
| LiteLLMModel | ApiModel | litellm SDK | 一个接口调 100+ 云模型(models.py:1224) |
| LiteLLMRouterModel | LiteLLMModel | litellm Router | 多模型编组智能路由/负载均衡(models.py:1426) |
| InferenceClientModel | ApiModel | HF InferenceClient | HF Inference Providers,默认后端(models.py:1514) |
| OpenAIModel | ApiModel | openai SDK | OpenAI 及一切兼容 API 服务(models.py:1671) |
| AzureOpenAIModel | OpenAIModel | openai AzureOpenAI | 企业 Azure 部署(models.py:1820) |
| AmazonBedrockModel | ApiModel | boto3 converse | AWS Bedrock,角色全转 user(models.py:1940) |
几个值得点名的细节:
TransformersModel 的 VLM 探测(models.py:950-973):先尝试 AutoModelForImageTextToText 加载,失败且报 "Unrecognized configuration class" 就回退 AutoModelForCausalLM——同一份代码既能跑 Qwen3 文本模型也能跑视觉模型,靠异常类型分支而非配置开关。
ApiModel 的公共底座(models.py:1161-1191):云端模型的 __init__ 统一创建 RateLimiter 与 Retrying(详见下节),generate 里统一走 self._apply_rate_limit() + self.retryer(self.client...)。子类只需实现 create_client 与 generate,限流重试白送。
AmazonBedrockModel 的角色折叠(models.py:1953-1958):Bedrock 只支持 assistant/user 两种角色且不允许对话以 assistant 开头,于是默认把 SYSTEM/TOOL_CALL/TOOL_RESPONSE 全部转成 user,再拍平消息——方言差异的极端案例。
LiteLLMRouterModel:把 gpt-4o-mini 和 bedrock Claude 塞进同一个 model-group-1,routing_strategy: "simple-shuffle" 随机分发,一个逻辑模型组背后是多云负载均衡(完整示例见 examples/multi_llm_agent.py)。
文件末尾的 MODEL_REGISTRY(models.py:2070-2080)是 9 个具体类的白名单,反序列化 from_dict 只允许实例化注册过的类,防止通过恶意序列化数据动态 import 任意代码——安全意识贯穿到模型层。
官方示例 examples/agent_from_any_llm.py(即文档 using_different_models)把这一点演示到极致——改一个字符串,后端全换:
14 available_inferences = ["inference_client", "transformers", "ollama", "litellm", "openai"] 15 chosen_inference = "inference_client" ... 19 if chosen_inference == "inference_client": 20 model = InferenceClientModel(model_id="meta-llama/Llama-3.3-70B-Instruct", provider="nebius") 22 elif chosen_inference == "transformers": 23 model = TransformersModel(model_id="HuggingFaceTB/SmolLM2-1.7B-Instruct", device_map="auto", max_new_tokens=1000) 25 elif chosen_inference == "ollama": 26 model = LiteLLMModel(model_id="ollama_chat/llama3.2", api_base="http://localhost:11434", ...) 37 elif chosen_inference == "openai": 39 model = OpenAIModel(model_id="gpt-4o") ... 55 agent = ToolCallingAgent(tools=[get_weather], model=model, verbosity_level=2) 57 print("ToolCallingAgent:", agent.run("What's the weather like in Paris?")) 59 agent = CodeAgent(tools=[get_weather], model=model, verbosity_level=2, stream_outputs=True)
注意 L55-59:agent 的构造与 run 调用对五种后端完全一致。差异全部被压缩在 model = XxxModel(...) 这一行。这正是第 3 章"代码即 action"能跨模型验证的前提——论文里"少 30% 步"的实验,就是在同一套 CodeAgent 代码下换不同后端模型跑出来的。
| 场景 | 推荐 | 理由 |
|---|---|---|
| 数据隐私/离线/零 API 成本 | TransformersModel | 权重本地加载,数据不出机;代价是速度与显存 |
| 本地高并发/批量任务 | VLLMModel | vLLM 的 PagedAttention 连续批处理,吞吐远超裸 transformers,还支持结构化输出 |
| Mac 笔记本开发调试 | MLXModel | MLX 针对 Apple 芯片统一内存优化,4bit 量化跑 32B 模型 |
| 不想选型/快速起跑 | InferenceClientModel | 一个 HF token 通吃几百个开源模型,默认后端 |
| 多云/避免供应商锁定 | LiteLLMModel | anthropic/claude-...、gpt-4o、ollama_chat/... 统一前缀寻址 |
| 生产高可用/成本分流 | LiteLLMRouterModel | 小任务路由便宜模型,大任务路由旗舰,还能故障转移 |
| 企业合规(Azure/AWS) | AzureOpenAIModel / AmazonBedrockModel | 走企业账号、私有端点与合规审计 |
⚠️ 注意:本地三兄弟对多模态的支持参差——vLLMModel 与 MLXModel 明确
_is_vlm = False(models.py:668、814),图片会被拍平成文本;只有 TransformersModel 探测到 VLM 架构时才保留图像输入。视觉任务要么用云端模型,要么用第 8 章的 vision_web_browser 思路(截图走工具回调)。
generate(messages, stop_sequences, response_format, tools_to_call_from) -> ChatMessage,__call__ 委托 generate;_prepare_completion_kwargs 实现 self.kwargs > 显式 kwargs > 专用参数的三级优先级,REMOVE_PARAMETER 哨兵可删参数。tool_role_conversions 把内部五角色折算回 API 三角色;_coerce_tool_call 兼容各家工具调用对象。create_client + generate。下一节:深入
generate_stream流式输出与 delta 聚合、工具 JSON schema 的自动生成,以及 TokenUsage 统计与 AgentLogger 遥测——把 Model 层的"毛细血管"走完。