1. 为什么 smolagents 的工具调用值得单独折腾一遍
smolagents 是 HuggingFace 出的轻量 Agent 框架,核心卖点就一句话:让模型直接写 Python 代码来调用工具,而不是吐一堆 JSON 让你去解析。它内置了ToolCallingAgent和CodeAgent两种执行器,前者走标准的 function calling 协议,后者直接生成可执行代码片段。对于需要在本地快速验证「多工具 Agent 能不能跑通」的开发者来说,这个框架的代码量少、依赖清晰,比动辄要起一堆服务的方案友好得多。
但真正上手时,卡人的往往不是 Agent 逻辑本身,而是模型接入这一层。smolagents 底层用 LiteLLM 做模型路由,你得给每个模型配api_base和api_key;今天试 DeepSeek,明天换 Claude,后天想对比 Qwen,Key 和地址散落在代码、环境变量、配置文件里,改一次错一次。这篇就聚焦这个落地场景:用 TaoToken 统一 Key 和 API 通道,把 smolagents 的工具调用链路一次性跑通,并给出可复制的config.toml与settings.json骨架,最后用一条可执行动作确认工具真的被调用了。
适合谁看:已经在本地装好 Python 环境、想验证多工具 Agent 的开发者;被 LiteLLM 的 provider 前缀和参数搞晕的人;希望把模型配置从代码里抽出来、做成可切换配置的人。下面所有步骤都可以直接跟做,不需要你有 HuggingFace 账号,也不需要额外申请一堆平台的 Key。
2. 前置准备:TaoToken 统一 Key 与 smolagents 环境
2.1 先把 Key 和通道准备好
TaoToken 在这里扮演的角色是「统一入口」:你只需要一个 Key,就能通过同一个 API 通道访问多种模型,不用为每个 provider 单独维护地址和凭证。对 smolagents 这种底层走 LiteLLM 的框架来说,这意味着api_base可以固定下来,切换模型只改model_id。
先去控制台创建一个 API Key,入口在 https://taotoken.net/api-keys 。创建后复制出来,形如sk-开头的一串字符,后面配置里会用到。如果你还没注册,从 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 进官网即可。
注意:Key 只显示一次,建议创建后立刻存进密码管理器或本地
.env,不要直接硬编码进要提交到 Git 的脚本里。
2.2 装依赖,顺手解决那个 ImportError
smolagents 对transformers版本有要求。如果你本地是较老的版本,导入时会撞上这个报错:
ImportError: cannot import name 'define_import_structure' from 'transformers.utils.import_utils'这是transformers内部结构变动导致的,升级到 4.47.1 及以上即可解决。完整安装命令:
pip install -U "transformers>=4.47.1" pip install smolagents pip install litellmlitellm建议显式装一下,虽然 smolagents 会带依赖,但单独装能保证版本较新,provider 前缀识别更稳。装完可以用下面这条命令确认版本:
python -c "import transformers, smolagents, litellm; print(transformers.__version__, smolagents.__version__, litellm.__version__)"输出三个版本号就说明环境 OK。如果smolagents导入报错,八成还是transformers没升到位,回去检查版本。
3. 可复制配置:config.toml 与 settings.json 骨架
3.1 为什么要把配置抽出来
把api_base、api_key、model_id写死在 Python 里,最直接的后果是:换模型要改代码,改完还要小心别把 Key 提交上去。抽成配置文件后,代码只读配置,切换模型就是改一行字符串的事。下面给两套骨架,你可以按习惯选一套,也可以两套都用——config.toml给 Python 侧读,settings.json给需要 JSON 的工具链读。
3.2 config.toml 骨架
# config.toml [taotoken] api_base = "https://taotoken.net/api" api_key = "sk-你的Key" [agent] # 默认使用的模型,切换只改这一行 model_id = "deepseek/deepseek-chat" max_steps = 5 verbosity = 1 [agent.tools] weather = true calculator = true这里api_base固定为 TaoToken 的 API 地址,model_id用 LiteLLM 的 provider 前缀格式。DeepSeek 系列写deepseek/deepseek-chat,Claude 系列写anthropic/claude-3-5-sonnet-20240620这类全称。前缀写错是后面报错排查里最常见的一类问题。
3.3 settings.json 骨架
{ "taotoken": { "api_base": "https://taotoken.net/api", "api_key": "sk-你的Key" }, "agent": { "model_id": "deepseek/deepseek-chat", "max_steps": 5, "verbosity": 1 } }两套配置字段一一对应,读哪个都行。实际项目里我一般把 Key 放环境变量,配置文件里只留占位符,读取时用os.environ覆盖,这样配置文件可以放心进版本库。
3.4 读取配置并构造模型
import os import tomllib from smolagents import LiteLLMModel with open("config.toml", "rb") as f: cfg = tomllib.load(f) api_base = cfg["taotoken"]["api_base"] api_key = os.environ.get("TAOTOKEN_API_KEY", cfg["taotoken"]["api_key"]) model_id = cfg["agent"]["model_id"] model = LiteLLMModel( model_id=model_id, api_base=api_base, api_key=api_key, )tomllib是 Python 3.11 起内置的,低版本用tomli替代即可。注意LiteLLMModel的api_base参数会透传给 LiteLLM,所以这里填 TaoToken 的地址,LiteLLM 就会把请求发到这个统一通道,而不是各 provider 的官方地址。
4. 跑通工具调用:从定义工具到验证链路
4.1 定义一个最小工具
工具用@tool装饰器声明,类型注解和 docstring 会被框架解析成给模型看的工具描述,所以 docstring 别省。
from typing import Optional from smolagents import tool @tool def get_weather(location: str, celsius: Optional[bool] = False) -> str: """查询指定地点未来几天的天气。 Args: location: 地点名称,例如 Paris。 celsius: 是否返回摄氏度,默认华氏度。 """ return f"{location} 未来几天有暴雨,气温低于 -10°C"这个工具故意不真的联网,返回固定字符串,目的是验证「模型有没有正确调用工具」这条链路,而不是验证天气数据准不准。
4.2 组装 ToolCallingAgent 并执行
from smolagents.agents import ToolCallingAgent agent = ToolCallingAgent( tools=[get_weather], model=model, max_steps=cfg["agent"]["max_steps"], ) result = agent.run("What's the weather like in Paris?") print(result)ToolCallingAgent走的是标准 function calling 协议,模型返回工具名和参数,框架执行工具再把结果喂回模型。max_steps控制最多迭代几轮,设太小可能工具还没调完就停了,设 5 对单工具场景足够。
4.3 一条可执行的验证动作
想确认工具真的被调用了,而不是模型自己编了个答案,最直接的办法是在工具里打日志:
@tool def get_weather(location: str, celsius: Optional[bool] = False) -> str: """查询指定地点未来几天的天气。 Args: location: 地点名称,例如 Paris。 celsius: 是否返回摄氏度,默认华氏度。 """ print(f"[TOOL CALLED] location={location}, celsius={celsius}") return f"{location} 未来几天有暴雨,气温低于 -10°C"重新运行agent.run(...),如果终端打印出[TOOL CALLED] location=Paris, celsius=False,说明模型正确解析了问题、选中了工具、传对了参数,整条链路连通。如果没打印,但result里却有天气描述,那基本是模型在幻觉,需要检查model_id前缀和工具描述是否被正确传递。
4.4 换成 CodeAgent 的对比
smolagents 另一个执行器是CodeAgent,它让模型直接写 Python 代码调用工具,适合工具多、需要组合逻辑的场景。切换只需改一行:
from smolagents.agents import CodeAgent agent = CodeAgent(tools=[get_weather], model=model, max_steps=5)CodeAgent对模型的代码能力要求更高,DeepSeek 系列实测能跑,但复杂工具链下偶尔会写出语法错误,max_steps可以适当调大。先用ToolCallingAgent验证链路,再换CodeAgent试复杂场景,是比较稳的顺序。
5. 本篇常见错排查
5.1 ImportError: cannot import name 'define_import_structure'
前面提过,transformers版本过低。执行pip install -U "transformers>=4.47.1"后重启 Python 进程。如果还报错,检查是不是有多个 Python 环境,pip和python指向了不同的解释器,用python -m pip install -U transformers更保险。
5.2 model_id 前缀写错导致 provider 识别失败
LiteLLM 靠provider/model格式路由。DeepSeek 必须写全称deepseek/deepseek-chat,只写deepseek-chat会报找不到 provider。Claude 写anthropic/claude-3-5-sonnet-20240620。报错信息里通常会出现LLM Provider NOT provided或BadRequestError,看到这类提示先回去核对前缀。
5.3 api_base 带了多余路径
TaoToken 的 API 地址是https://taotoken.net/api,不要在后面手动加/v1或/chat/completions,LiteLLM 会自己拼。多写一段路径会导致 404。如果你从别处复制了带/v1的地址,删掉再试。
5.4 工具没被调用,模型直接编答案
两种可能:一是工具 docstring 太模糊,模型没理解什么时候该用;二是ToolCallingAgent的模型不支持 function calling。前者把 docstring 写具体,参数说明补全;后者换支持工具调用的模型,DeepSeek 系列和 Claude 系列都支持。用 4.3 的日志法一测就知道是哪种。
5.5 Key 无效或额度问题
报错AuthenticationError或401,先确认 Key 复制完整、没有多余空格。如果 Key 没问题,去控制台看下额度状态。Key 和通道的管理都在 https://taotoken.net/api-keys ,接入细节和参数说明可以对照 https://taotoken.net/doc 。
6. 把配置固定下来,后续切换只改一行
跑通之后,建议把config.toml里的model_id当成唯一的切换开关。想对比不同模型在同一个工具调用任务上的表现,改这一行、重跑脚本即可,api_base和api_key都不用动。这就是统一 Key 通道在 Agent 开发里最实际的价值:把「换模型」这件事的成本压到最低。
如果你接下来要长期跑编码类 Agent,或者把 smolagents 接进更大的工作流,可以了解下 Coding Plan,入口在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。想先在网页里直接对话验证模型行为,用模型对话页 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 更快。Claude Code 相关的接入配置在 https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,需要的话可以对照着改。
最后留一个我踩过的坑:max_steps设成 1 的时候,ToolCallingAgent有时会在第一轮就返回工具调用请求,但没机会执行第二轮把结果整合成自然语言,输出会是一段半成品。单工具场景设 3 到 5 比较稳,多工具链建议 8 以上。