从这篇文章开始将逐个学习 smolagent 官网教程demo,逐步搭建适合自己项目的LLM代理。
官方文档查看
就像之前那篇文章提到的,由于这个库非常年轻,官网的文档维护并不完善,特别是tutrial方面的文档。你会在不同官网上找到很多份文档,通常源于下面两个链接:
-
【链接一】smolagents官网
Guides
->Docs
页面:https://smolagents.org/docs/;
-
【链接二】huggingface官网
Docs
找到 smolagents 打开的页面:https://huggingface.co/docs/smolagents/v1.9.2/en/index;
按照正常人的思维肯定是去第一个页面找,但实际上完整且版本对应的doc是第二个链接。在第二个链接左侧选择 v1.9.2
也就是我们之前安装的版本:
接下来就会按照我自己的学习步骤逐步演示官方demo并在必要的位置配上注释。
Building your agent
官网文档中的这一章节介绍了六种最简单的调用方式,这篇博客重点介绍以下几种调用方式:
- Huggingface 在线调用;
- Huggingface 本地调用;
- Ollama 本地调用;
剩下三种在线调用方式我会在申请到对应的Key之后补充进来;
HF Inference API
这个demo是官网的第一个demo,前面的 HF 其实就是 huggingface 的缩写,根据官网介绍,这个API是不需要的Token的,但是对访问频率有限制。从这里开始就已经有了坑。
根据官网的描述如果你想要提升访问频率或者使用模型需要升级到Pro账户,然后通过设置环境变量 HF_TOKEN
来使用这个demo,但在 没有升级到 Pro 账户 的前提下即便设置了环境变量也是无法运行这个demo,原因是demo中使用的是 meta-llama/Llama-3.3-70B-Instruct
这个模型是收费的,直接复制并运行后会有如下报错,其本质仍然是你没有访问模型的权限:
按照下面的方式改写官网demo就可以白嫖在线模型,在 model
中如果不指定模型名,则会自动分配免费模型,通常情况下国内会分配通义千问,比如我这里运行后分配的模型就是 Qwen/Qwen2.5-Coder-32B-Instruct
:
from smolagents import CodeAgent, HfApiModel
# model_id = "meta-llama/Llama-3.3-70B-Instruct" # 不使用指定模型,因为这个需要Pro账户
# model = HfApiModel(model_id=model_id, token="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") # You can choose to not pass any model_id to HfApiModel to use a default free model
model = HfApiModel(token="上一篇文章中让你记下来的Token码")
agent = CodeAgent(tools=[], model=model, add_base_tools=True)
agent.run(
"Could you give me the 118th number in the Fibonacci sequence?",
)
运行后应该能看到以下画面:
现在来解释上面的输出是什么意思。
StepN
:我们正常使用LLM模型时会根据回答内容与模型进行交互。smolagent默认会对模型尝试进行 6轮 对话,如果超过六轮则自动结束,这里的StepN其实就是对话轮次,上面的demo能看出在第二轮对话后Qwen模型给的结果就可以正常运行了。Code execution failed at line 'fib_seq = fibonacci_sequence()' due to: InterpreterError: Yield is not supported.
:这个是Agent在与模型第一轮对话后得到的代码执行时报的错,Agent会将执行错误返回给模型让模型进行二次生成,非常类似于我们人的行为,因为第一轮生成的代码不可用所以触发了第二轮对话。Out - Final answer: 2046711111473984623691759
:这个是Agent与模型第二轮对话生成代码得到的值,如果代码运行没有报错则不会触发下一轮对话,如果仍然报错则会继续对话直到6轮对话机会耗尽。
Local Transformers Model
在官网的第二个demo中展示了如何使用本地的Transformers模型,这里需要额外安装一个库:
$ pip install smolagents[transformers]
直接运行官网demo会出现报错 Cannot access gated repo for url https://huggingface.co/meta-llama/Llama-3.2-3B-Instruct/resolve/main/config.json.
本质上还是由于没有Pro账户的权限。此处我提供了两种方式来调用基于 Transformers 库的本地模型调用:
自动在线下载
和上面一样如果在代码中不指定模型名:
from smolagents import CodeAgent, TransformersModel
# model_id = "meta-llama/Llama-3.2-3B-Instruct" # 不指定模型名
model = TransformersModel()
agent = CodeAgent(tools=[], model=model, add_base_tools=True)
agent.run(
"Could you give me the 118th number in the Fibonacci sequence?",
)
上面的代码会使用默认模型名与配置,在我电脑上运行后会在线拉取 HuggingFaceTB/SmolLM2-1.7B-Instruct
模型,整个下载过程会比较耗时,下载完成后就是本地运行了,在我的M2 Macbook Pro上能够很明显体会到没有在线模型反应快,一轮问答基本会在30~60s左右,而且很容易出错也容易出现答非所问的情况:
手动离线下载
很显然上面自动分配的模型在智力水平上令人堪忧,但最重要的是不能指定问答模型,这里以Qwen为例教你如何手动下载模型然后指定Agent使用本地路径的模型。
首先因为这个demo使用的是 huggingface 自己的 transformers 框架,那么就需要在其官网(https://huggingface.co/models)上查找支持的模型,为了演示方便这里使用一个小参数量的模型 Qwen2.5-0.5B-Instruct
:
关于 huggingface 模型下载网上已经有了很多教程就不再赘述,这里只介绍下最适合初学者或者最简单的直观的下载方法,不用授权Token或者验证什么密钥,直接点击界面里的下载按钮即可:
【重点】:搜索得到的模型页面理论上要全部下载,但实际上这个页面就是基于 git-lfs
的一个仓库,因此像 .gitattributs
、 README.md
、LICENSE
这类的文件可以忽略。
将下载好的所有文件放在一个你指定的文件夹中,比如我的文件结构如下:
(base) ~/Desktop/LLM $ tree -L 3
.
├── __pycache__
│ ├── smolagent.cpython-310.pyc
│ └── tello_driver.cpython-310.pyc
├── deepsort.py
├── models
│ └── Qwen
│ ├── config.json
│ ├── generation_config.json
│ ├── merges.txt
│ ├── model.safetensors
│ ├── tokenizer.json
│ ├── tokenizer_config.json
│ └── vocab.json
├── smolagent.py
└── yolov8n.pt
【注意】:模型与文件夹之间应该是一一对应的关系,想要下载几个模型就应该创建几个文件夹;
这个时候就可以将官网demo中的内容进行修改:
from smolagents import CodeAgent, TransformersModel
model_foler_path = "./models/Qwen" # 本地存放模型的文件夹
model = TransformersModel(model_id=model_foler_path) # 将文件夹传给模型
agent = CodeAgent(tools=[], model=model, add_base_tools=True)
agent.run(
"Could you give me the 118th number in the Fibonacci sequence?",
)
运行后输出如下,可以看到Qwen尽管在第一轮运行的输出结果存在格式错误,但第二轮的输出却是语法合规的:
此处有一个细节值得注意,尽管模型在第二轮输出了语法正确的代码,但Agent仍然在继续询问,造成这种情况主要是这个原因:本地模型太小以至于无法按照Agent规定的格式输出,因为Agent会给模型一个很长的prompt,如果模型参数太小可能会丢弃部分规则而直接运行;
从第一轮对话的返回值中可以看出Agent根据正则表达式从LLM的回答中截取以markdown
格式的py
字段有效内容,Agent其实希望的是模型输出一个函数,然后自己去调用这个函数,但模型直接给了 for
循环并自己运行了,导致 Agent 运行后找不到这个函数因此 Out: None
;
OpenAI or Anthoripc API
官网的这个demo是通过api_key来在线访问其他接口的模型,这个demo与第一个 HF Interface API
demo的差异在于允许你访问其他厂商的服务器,这样就可以绑定自己的API接口。
这个demo我后续会进行补充。
Ollama
ollama是当前很受欢迎的本地模型管理框架,也是我个人一直使用的工具,个人感觉虽然ollama平台的模型总量和粒度没有huggingface的多,但其胜在简单、快速部署本地模型,对于新手而言最友好的一点是 下载模型支持断点续传,即你在下载模型是 Ctrl+C
然后再恢复仍然可以继续下载,至于Ollama本地模型使用我会另开一个专栏,这里假设你已经安装好ollama并安装好 deepseek-coder-v2:latest
模型:
【注意】:当你使用ollama命令下载模型后名字可能并不是以 latest
结尾,大多数情况下是 :16B
之类的后缀,但在使用时必须用 ollama list
输出结果的模型名,否则会报错找不到模型。通常情况下在ollama对指定模型进行一次更新后就会把名字后缀改成latest
(LLM) ~/Desktop/LLM $ ollama list
NAME ID SIZE MODIFIED
deepseek-coder-v2:latest 63fb193b3a9b 8.9 GB 4 days ago
【可选】在完成ollama本地模型安装后可以用下面这个命令测试下模型能否返回值,然后再继续:
(LLM) ~/Desktop/LLM $ curl http://localhost:11434/api/generate -d '{
"model": "deepseek-coder-v2:latest",
"prompt": "Hello, world!",
"stream": false
}'
如果运行后模型给你输出了类似于下面这种则说明至少ollama端是能够正常运行的:
{"model":"deepseek-coder-v2","created_at":"2025-02-20T05:42:56.427884Z","response":" Hi there! How can I assist you today?","done":true,"done_reason":"stop","context":[100000,5726,25,37727,11,1843,0,185,185,77398,25,11598,745,0,1724,481,304,4750,340,3571,30],"total_duration":577970458,"load_duration":28564042,"prompt_eval_count":12,"prompt_eval_duration":304000000,"eval_count":11,"eval_duration":243000000}
对官网demo的修改如下:
from smolagents import CodeAgent, LiteLLMModel
model = LiteLLMModel(
model_id="ollama_chat/deepseek-coder-v2:latest",
api_base="http://localhost:11434",
num_ctx=8192,
)
agent = CodeAgent(tools=[], model=model, add_base_tools=True)
agent.run(
"Could you give me the 118th number in the Fibonacci sequence?",
)
因为调用的是本地模型,所以官网示例中的 api_key
就没有必要了,可以直接删除。
【重点】:上面代码中 model_id
必须要写明前缀:ollama_chat
或 ollama
,否则会出现 litellm.BadRequestError: LLM Provider NOT provided.
报错,这表明Agent不知道你想要调用哪个框架,因为不同的框架API调用机制是不同的:
运行后显示,可以看到在经过4轮对话后 deepseek-coder 模型给出了Agent认为满意的答案并运行了:
Azure OpenAI
这个demo是调用微软Azure云的API,后续我申请完后会回来补充;
mlx-ml
这个demo是使用mlx调用模型,但因为对MacOS的版本要求在 13.5 以上,我后续会找一个Linux来补充这部分demo;
CodeAgent and ToolCallingAgent
CodeAgent
CodeAgent
是smolagents库默认的代理模式,其主要作用就是一步一步执行 python
代码,然后根据返回与模型进行多轮对话,上面提到的所有例子都是使用的CodeAgent
模式。
此时,如果你已经从事了码农个行业就一定会警觉:如何保证函数安全,因为大语言模型天然具有发散性,而Agent又无法判断生成的代码是否安全,比如LLM模型生成了一个函数需要执行bash命令 rm -rf *
,而Agent只负责执行并将结果返回给模型,那么Agent自动执行这段代码的后果是灾难性的,为了防止这种情况发生 smolagent 也提供了对应的机制,可以通过参数 additional_authorized_imports
指定授权的库有哪些,这个事例只允许使用 requests
和 bs4
这两个库:
官网在文档中着重强调了这句话 The LLM can generate arbitrary code that will then be executed: do not add any unsafe imports!,尽管你可以通过 additional_authorized_imports
允许Agent导入库,但也要确保导入的这些库是安全的,如果你导入的这些库本身都不安全比如有一个函数会执行 rm -rf *
,那么即便设置了该变量也有可能调用不安全函数;
【注意】:这里官网又挖坑了,其demo在设置模型的时候没有提供token,所以运行后会报错。
from smolagents import HfApiModel, CodeAgent
model = HfApiModel(token="你在huggingface网页创建的token") # 在初始化模型时添加你的token
agent = CodeAgent(tools=[], model=model, additional_authorized_imports=['requests', 'bs4'])
agent.run("Could you get me the title of the page at url 'https://huggingface.co/blog'?")
当然,如果你不想每次都手动填写token,也可以通过设置环境变量来自动填充token,这种方式在一个模型的情况下比较方便,但当模型一多起来就容易出现调用混乱的情况:
$ export HF_TOKEN="你在huggingface网页创建的token"
如果你在bash中执行了上面的命令,那么就可以直接调用官网demo,但要注意只限于使用 HfApiModel
这个类的模型对象
from smolagents import HfApiModel, CodeAgent
model = HfApiModel() # export环境变量后可以直接调用,不用额外填写token
agent = CodeAgent(tools=[], model=model, additional_authorized_imports=['requests', 'bs4'])
agent.run("Could you get me the title of the page at url 'https://huggingface.co/blog'?")
运行输出如下:
ToolCallingAgent
在如何使用 ToolCallingAgent
时官网文档就存在巨大疏忽了,因为既没有提供如何创建 Tool
的实例示范,也没有把代码写完整,甚至demo中的 tools=[]
字段还是给了一个空列表,等于没有任何参考价值:
from smolagents import ToolCallingAgent
agent = ToolCallingAgent(tools=[], model=model)
agent.run("Could you get me the title of the page at url 'https://huggingface.co/blog'?")
实际上如何定义tool与使用被放在了下一小节中,这里为了给下一小节进行铺垫提前介绍下如何创建 tool。先以官方提供的两个通用 tool 为例 DuckDuckGoSearchTool
和 VisitWebpageTool
演示:
from smolagents import ToolCallingAgent, HfApiModel
from smolagents import DuckDuckGoSearchTool, VisitWebpageTool
model = HfApiModel()
agent = ToolCallingAgent(tools=[DuckDuckGoSearchTool(), VisitWebpageTool()], model=model)
result = agent.run("Could you get me the title of the page at url 'https://huggingface.co/blog'?")
print(result)
运行后效果如下,可以看见Agent通过使用tool对网站 https://huggingface.co/blog
进行了访问并提供了最终结果为 Hugging Face - Blog
:
很显然官方提供的有限的Tool是不满足真实需求的,下面这个事例将展示如何使用自定义tool并传递给agent,和正常编码一样有两种方式:函数修饰器、继承类;
- 使用关键字
@tool
修饰函数
from smolagents import ToolCallingAgent, HfApiModel
from smolagents import tool
from smolagents.tools import Tool
from smolagents import tool
import subprocess
@tool
def get_url_contant(url: str) -> str:
"""
This is a tool to download url link
Args:
url: The target link of webpage
"""
return subprocess.run(['curl', '-X', 'GET', url])
# 初始化 Agent,添加 Tool
model = HfApiModel()
agent = ToolCallingAgent(tools=[get_url_contant], model=model)
# 运行 Agent 获取网页标题
result = agent.run("Could you get me the title of the page at url 'https://huggingface.co/blog'?")
print(result)
上面的代码定义了一个函数 get_url_contant
使用 curl
命令获取指定 url 链接中的内容,并用 @tool
对其进行修饰标识其是一个 smolagents.tool
。
- 继承类:
from smolagents import ToolCallingAgent, HfApiModel
from smolagents.tools import Tool
import subprocess
class GetURLContantTool(Tool):
name="get_url_contant_tool"
description="This is a tool to download url link"
inputs={
"url":{
"type" : "string",
"description": "Target url link of webpage"
}
}
output_type="string"
def forward(self, url: str) -> str:
return subprocess.run(['curl', '-X', 'GET', url])
# 初始化 Agent,添加 Tool
model = HfApiModel()
agent = ToolCallingAgent(tools=[GetURLContantTool()], model=model)
# 运行 Agent 获取网页标题
result = agent.run("Could you get me the title of the page at url 'https://huggingface.co/blog'?")
print(result)
在使用继承类的方式实现时需要注意以下几点:
name
、description
、inputs
、output_type
这些字段必须提供;forward
函数必须实现;
到达这里你可能又有疑问如果我想要多个工具类或者一个工具类提供多个功能又应该怎么做?
- 多个工具类:
from smolagents import ToolCallingAgent, HfApiModel
from smolagents.tools import Tool
import subprocess
import re
from bs4 import BeautifulSoup
class GetURLContantTool(Tool):
name = "get_url_content_tool"
description = "Downloads the content of a given URL."
inputs = {"url": {"type": "string", "description": "Target URL link of the webpage"}}
output_type = "string"
def forward(self, url: str) -> str:
result = subprocess.run(['curl', '-s', '--max-time', '5', '-X', 'GET', url], capture_output=True, text=True)
html = result.stdout
soup = BeautifulSoup(html, "html.parser")
text = soup.get_text() # 提取纯文本
return text[:5000] # 限制长度,避免 token 过多
class ExtractTitleTool(Tool):
name = "extract_title_tool"
description = "Extracts the title from a webpage's HTML content."
inputs = {"html_content": {"type": "string", "description": "The HTML content of a webpage"}}
output_type = "string"
def forward(self, html_content: str) -> str:
match = re.search(r"<title>(.*?)</title>", html_content, re.IGNORECASE)
return match.group(1) if match else "Title not found"
model = HfApiModel() # 限制 Hugging Face API 生成的 token
agent = ToolCallingAgent(tools=[GetURLContantTool(), ExtractTitleTool()], model=model)
# 运行
html_result = agent.run("Get the content of the page at url 'https://huggingface.co/blog'")
title_result = agent.run(f"Extract the title from this HTML content: {html_result}")
print("Title of the page:", title_result)
- 一个工具类实现多个功能
【注】:由于1.9.2版本API变更,我后续测试通后会在这里补全。
Tools
官网在这一小节才开始介绍Tool,但总体上来说也比较笼统,我会在必要的部分进行补充说明。
Default toolbox
smolagent 提供了一些默认的工具,可以在不创建模型的情况下直接使用,但要注意这些工具只能在 联网 的状态下调用:
from smolagents import DuckDuckGoSearchTool
search_tool = DuckDuckGoSearchTool()
print(search_tool("Who's the current president of Russia?"))
Create a new tool
官网在这里使用了 huggingface_hub
的另一个功能函数 list_models
,这个函数用来列出你的账户当前可以使用的模型列表;
from huggingface_hub import list_models
task = "text-classification"
most_downloaded_model = next(iter(list_models(filter=task, sort="downloads", direction=-1)))
print(most_downloaded_model.id) # 打印在文本分类中下载量最多的模型
在运行时可能会出现 SSH
相关的报错,多运行两次就可以了
(LLM) ~/Desktop/LLM $ python smolagent.py
cross-encoder/ms-marco-MiniLM-L-6-v2
对函数进行 @tool
修饰,这部分和上面展示的例子基本一致,只需要在函数前加上标志符即可:
from smolagents import tool
@tool
def model_download_tool(task: str) -> str:
"""
This is a tool that returns the most downloaded model of a given task on the Hugging Face Hub.
It returns the name of the checkpoint.
Args:
task: The task for which to get the download count.
"""
most_downloaded_model = next(iter(list_models(filter=task, sort="downloads", direction=-1)))
return most_downloaded_model.id
对类进行继承 Tool
,这部分也和上面的例子基本一致,但要注意需要显示指明 name
、description
、inputs
、output_type
这些字段:
from smolagents import Tool
class ModelDownloadTool(Tool):
name = "model_download_tool"
description = "This is a tool that returns the most downloaded model of a given task on the Hugging Face Hub. It returns the name of the checkpoint."
inputs = {"task": {"type": "string", "description": "The task for which to get the download count."}}
output_type = "string"
def forward(self, task: str) -> str:
most_downloaded_model = next(iter(list_models(filter=task, sort="downloads", direction=-1)))
return most_downloaded_model.id
Multi-agents
smolagent引入了微软的多代理的框架,在未来业务中必然会出现多代理的情况,如:
- 一个总代理专门负责处理用户输出的自然语言并解析成功能函数的调用列表;
- 多个工具代理负责调用不同模型,如一个代理调用文本生成图像模型、一个代理调用目标检测与识别模型;
【注意】:旧版本上可能会存在 ManagedAgent
的对象,该对象在 1.9.2
版本上已经弃用;
from smolagents import CodeAgent, HfApiModel, DuckDuckGoSearchTool
model = HfApiModel()
web_agent = CodeAgent(
tools=[DuckDuckGoSearchTool()],
model=model,
name="web_search",
description="Runs web searches for you. Give it your query as an argument."
)
manager_agent = CodeAgent(
tools=[], model=model, managed_agents=[web_agent]
)
manager_agent.run("Who is the CEO of Hugging Face?")
这个官网案例是能够直接运行的,但实际上并没有体现多Agent的调用过程,因为案例中只使用了一个Agent: