L3 用 smolagents 30 行代码构建第一个 Agent
课程目标:跑通第一个真正能用的 Agent——亲眼看到 LLM 怎么"决定调工具"、怎么"读工具结果"。
一、为什么要先动手?#
L1 和 L2 我们一直在讲理论——Agent 是什么、ReAct 循环怎么转。这节课我们要亲手跑一次。
为什么先讲 smolagents 而不是 LangGraph?因为 smolagents 是 HuggingFace 推出的极简框架,30 行代码就能跑通一个 ReAct Agent,适合第一课建立直觉。LangGraph 更适合复杂生产环境,留到 L5。
学完这节课你会掌握:
@tool装饰器怎么把普通 Python 函数变成 Agent 工具CodeAgent怎么在内部跑 ReAct 循环怎么选模型(免费 HF、本地 Ollama、付费 OpenAI/Anthropic)
怎么读懂 Agent 的运行日志,看它"思考—行动"的全过程
二、用一个比喻理解 smolagents#
还记得 L1 的"私人管家"比喻吗?
OpenAI / Anthropic API 直接调用:你是管家的大脑,但手要自己做(自己写网络请求、解析 JSON、处理错误)
LangGraph:你给管家一套流程图,告诉他"先想、再做、做完再想"
smolagents:你给管家一本工具书(@tool 装饰的函数),管家会自己写 Python 代码来用这些工具
最后这条是关键差异:smolagents 的 CodeAgent 让 LLM 直接生成 Python 代码并执行。比如让 Agent 查天气,它生成的代码可能是:
weather = get_weather(city="北京")
print(weather)
而不是传统 Function Calling 的 JSON:
{"name": "get_weather", "arguments": "{\"city\": \"北京\"}"}
这种"代码即行动"的范式更强大(LLM 能循环、调用多个工具、组合结果),但也更危险(LLM 可能写出 os.system("rm -rf /"))。所以 smolagents 提供沙箱执行环境来隔离风险。
三、环境准备(5 分钟)#
Step 1:安装 smolagents#
pip install 'smolagents[toolkit,litellm]'
toolkit包含默认工具(如 WebSearch)litellm让你能用 OpenAI、Anthropic 等 100+ 模型
Step 2:选择你的模型(三种方案)#
根据你的预算和账号情况三选一:
方案 A:免费 + HuggingFace(推荐新手)#
export HF_TOKEN="hf_xxxxxxxxxxxxxx" # 从 huggingface.co/settings/tokens 获取
注册 HuggingFace 账号 → Settings → Access Tokens → Create new token(选 read 权限即可,免费)。
方案 B:OpenAI / Anthropic(推荐已有 API Key 的)#
export OPENAI_API_KEY="sk-xxxxxxxx"
# 或
export ANTHROPIC_API_KEY="sk-ant-xxxxxxxx"
方案 C:本地 Ollama(推荐想完全离线跑)#
# 1. 安装 Ollama(https://ollama.com)
ollama pull qwen2.5-coder:7b
四、第一个 Agent:算术小助手(30 行代码)#
我们先做一个最简单的 Agent——会做数学题的算术助手。它有两个工具:加法 和 乘法。LLM 会自己决定什么时候该用哪个工具。
完整代码#
from smolagents import tool, CodeAgent, InferenceClientModel
# ============ 1. 定义工具 ============
@tool
def add(a: int, b: int) -> int:
"""返回两个整数的和。
Args:
a: 第一个整数
b: 第二个整数
"""
return a + b
@tool
def multiply(a: int, b: int) -> int:
"""返回两个整数的乘积。
Args:
a: 第一个整数
b: 第二个整数
"""
return a * b
# ============ 2. 准备模型 ============
# 方案 A:免费 HF(默认用 Qwen2.5-Coder-32B-Instruct)
model = InferenceClientModel(token="YOUR_HF_TOKEN")
# 方案 B:用 OpenAI(注释掉 A 取消注释 B)
# from smolagents import LiteLLMModel
# model = LiteLLMModel(model_id="gpt-4o-mini")
# 方案 C:用 Anthropic
# model = LiteLLMModel(model_id="anthropic/claude-3-5-sonnet-latest")
# ============ 3. 创建 Agent 并运行 ============
agent = CodeAgent(tools=[add, multiply], model=model)
# 问一个需要两步推理的问题
result = agent.run("(5 + 3) 乘以 7 等于多少?")
print(f"最终答案: {result}")
# 预期输出:最终答案: 56
这 30 行代码发生了什么?#
我们一行一行看:
① @tool 装饰器(4-15 行)#
@tool
def add(a: int, b: int) -> int:
"""返回两个整数的和。
Args:
a: 第一个整数
b: 第二个整数
"""
return a + b
关键点:
def上方的@tool装饰器把普通函数变成 Agent 可调用的工具函数的 docstring(
"""...""")会被自动提取,作为工具描述喂给 LLM类型注解(
a: int)会自动生成工具的 JSON Schema函数体就是工具的实际逻辑
💡 经验法则:docstring 写得越清楚,LLM 越会用对工具。这是 L4 课要深入讲的"工具设计"。
② 模型选择(17-25 行)#
四种模型类对应四种场景:
| 类 | 用途 | 是否需要额外配置 |
|---|---|---|
InferenceClientModel | HuggingFace Inference API | 需要 HF_TOKEN |
LiteLLMModel | 100+ 商业模型(OpenAI/Anthropic/Ollama 等) | 需要对应服务的 API Key |
TransformersModel | 完全本地运行开源模型 | 需要 GPU |
OpenAIModel | OpenAI 兼容的 API 端点 | 需要 API Key + base_url |
③ CodeAgent(27 行)#
agent = CodeAgent(tools=[add, multiply], model=model)
tools=[...]:告诉 Agent 你有哪些工具可用model=:告诉 Agent 用哪个 LLM 当"大脑"内部会自动构建 ReAct 循环的 system prompt,把工具描述、Few-shot 示例塞进去
④ agent.run(...)(30 行)#
这是触发 Agent 运行的入口。它会:
把你的问题拼进 prompt
让 LLM 生成 Python 代码(ReAct 的 Thought + Action 合并成代码)
在沙箱里执行代码
把执行结果(Observation)喂回 LLM
循环直到 LLM 写出"最终答案"代码
五、亲眼看到 ReAct 循环:开启 stream_outputs#
上面的代码我们只看到最终答案 56。真实跑起来时,Agent 在背后做了什么? 加上 stream_outputs=True 就能看到完整过程:
agent = CodeAgent(tools=[add, multiply], model=model, stream_outputs=True)
result = agent.run("(5 + 3) 乘以 7 等于多少?")
典型输出(简化版):
=== 思考 1 ===
我需要先计算 5 + 3 = 8,然后把结果乘以 7。
=== 代码 1 ===
```python
step1 = add(a=5, b=3)
result = multiply(a=step1, b=7)
print(result)
=== 执行结果 === 56
=== 思考 2 === 代码已经算出结果 56。
=== 最终答案 === 56
**这就是 ReAct 循环的真实样貌**!对比 L2 的伪代码:
- L2 是 "Thought: ... Action: add[5,3] Observation: 8" 这种纯文本格式
- smolagents 是 "Thought: ... Action: <Python 代码> Observation: <执行输出>"
**哪种更好?** 代码形式更强大:LLM 能在一段代码里调用多个工具、组合结果、做分支判断。文本形式的 Thought/Action 只能一步步走。
---
## 六、一个更有意思的例子:旅行规划 Agent
算术太简单了。我们做一个能查"巴黎一日游"的 Agent(来自 [smolagents 官方博客](https://huggingface.co/blog/smolagents)):
```python
import os
from smolagents import tool, CodeAgent, HfApiModel
@tool
def get_travel_duration(start: str, destination: str, mode: str = "driving") -> str:
"""获取两个地点之间的旅行时间。
Args:
start: 出发地
destination: 目的地
mode: 交通方式,可选 'driving' / 'walking' / 'bicycling' / 'transit'
"""
# 这里假装调用 Google Maps API(实际使用需要 googlemaps 库 + API Key)
import googlemaps
gmaps = googlemaps.Client(os.getenv("GMAPS_API_KEY"))
directions = gmaps.directions(start, destination, mode=mode)
if not directions:
return f"未找到从 {start} 到 {destination} 的 {mode} 路线"
return directions[0]["legs"][0]["duration"]["text"]
agent = CodeAgent(
tools=[get_travel_duration],
model=HfApiModel(),
additional_authorized_imports=["datetime"] # 允许 Agent 在代码里 import datetime
)
result = agent.run(
"帮我规划巴黎一日游,安排 3 个景点,并告诉我每个景点之间的交通时间"
)
print(result)
Agent 内部生成的代码可能是:
# 1. 查埃菲尔铁塔到卢浮宫的时间
leg1 = get_travel_duration(start="埃菲尔铁塔", destination="卢浮宫", mode="transit")
print(f"铁塔→卢浮宫: {leg1}")
# 2. 查卢浮宫到巴黎圣母院的时间
leg2 = get_travel_duration(start="卢浮宫", destination="巴黎圣母院", mode="transit")
print(f"卢浮宫→圣母院: {leg2}")
# 3. 汇总
print("建议行程:上午铁塔 → 中午卢浮宫 → 下午圣母院")
对比传统 Function Calling:JSON 形式下 LLM 要分三次调用,每次只返回一个数字。代码形式下 LLM 一次生成所有逻辑,循环、判断、汇总一步到位。
七、调试技巧:怎么看 Agent 到底在想什么#
如果 Agent 答错了或者卡住,怎么排查?smolagents 提供了几个工具:
1. 加详细日志#
agent = CodeAgent(
tools=[add, multiply],
model=model,
stream_outputs=True, # 实时打印每一步
max_steps=10, # 最多 10 步,防止死循环
verbosity_level=2 # 0=安静, 1=正常, 2=详细
)
2. 加一个 final_answer_checks 检查器#
def check_answer(answer, task):
"""Agent 自检:答案是否合理"""
if "error" in answer.lower():
return False
return True
agent = CodeAgent(
tools=[add, multiply],
model=model,
final_answer_checks=[check_answer]
)
3. 看中间执行步骤#
result = agent.run("你的问题")
# 拿到所有中间步骤
print(agent.memory.steps) # 每个 step 是 Thought + Code + Observation
八、安全警示:沙箱为什么重要?#
smolagents 默认使用 E2BSandbox 或 Docker 隔离代码执行。这是 CodeAgent 范式的最大风险点。
举个例子。如果 LLM 在生成的代码里写了:
import os
os.system("rm -rf /")
没有沙箱的话,你的电脑就完了。
生产环境的解法:
用
Docker沙箱跑 Agent 代码用
E2B云沙箱(smolagents 内置支持)关键场景用
ToolCallingAgent(让 LLM 生成 JSON 而不是代码,更安全但功能弱)
经验法则:能在沙箱里跑就别在本地跑。生产环境的 Agent 代码执行必须隔离。
九、和 L2 的连接#
| L2 概念 | L3 对应实现 |
|---|---|
| Thought | LLM 生成 Python 代码前的推理文字 |
| Action | 生成的 Python 代码(可能调用多个工具) |
| Observation | 代码在沙箱执行的输出(print / 返回值) |
| Finish | LLM 把最终答案通过 final_answer() 函数返回 |
你跑完上面的代码后,应该能在脑子里清晰画出:LLM 思考 → 生成代码 → 执行 → 把结果带回 prompt → 再思考 → 再生成代码 → ... → 输出 final_answer。这就是 ReAct 循环的代码形态。
十、动手时间:亲手跑 + 改#
请按顺序完成以下三步:
步骤 1:跑通最小例子(5 分钟)#
把上面"算术小助手"的代码复制到本地 agent_demo.py,配置好 API Key,跑通。预期看到 (5+3)*7=56 的输出。
步骤 2:故意为难它(5 分钟)#
修改问题,让 LLM 必须分多步:
result = agent.run("计算 (12 + 8) * (15 - 7) / (3 + 4) 的值,保留两位小数")
观察它是不是真的生成了多行代码调用了多次工具。
步骤 3:故意给坏工具名(5 分钟)#
把工具名改成中文:
@tool
def 加法(a: int, b: int) -> int:
"""计算两个整数之和。
Args:
a: 第一个整数
b: 第二个整数
"""
return a + b
agent = CodeAgent(tools=[加法], model=model)
result = agent.run("5 加 3 等于多少?")
观察它能不能调用中文函数名。这是个真实问题——很多框架的反射机制对中文函数名支持不好,亲手测一下你能更深刻地理解 smolagents 的工作原理。
📋 步骤 3 的答案(点击展开)
结果:可能成功,可能失败。取决于:
Python 解释器版本(3.10+ 反射机制对 Unicode 标识符支持更好)
LLM 是否能稳定生成中文函数名(中文对模型来说相对少见)
如果失败:把工具函数名改成英文(add),docstring 用中文。这是 L4 课要讲的"工具设计"最佳实践。
十一、小结#
| 知识点 | 一句话回顾 |
|---|---|
| 安装 | pip install 'smolagents[toolkit,litellm]' |
| 工具定义 | @tool + docstring + 类型注解 |
| Agent 类型 | CodeAgent(代码执行)vs ToolCallingAgent(JSON 调用) |
| 模型选择 | InferenceClientModel / LiteLLMModel / TransformersModel |
| 调试 | stream_outputs=True + verbosity_level=2 |
| 安全 | 永远在沙箱里跑 CodeAgent |
十二、Read next#
下一节课 L4 Tool Use 深入:让 Agent 拥有"双手",我们深入 Tool Use 的设计模式:怎么写 docstring 让 LLM 更会用工具、Function Calling 在底层是怎么发生的、怎么让 Agent 同时调用多个工具。
主要参考:
src-002 §Unit 2 smolagents guided tour — 官方文档最佳实践
smolagents 官方博客 — 旅行规划 Agent 的完整示例
十三、Sources#
src-002 — Hugging Face Agents Course · T1 · Course
smolagents 官方文档 · T1 · Guided Tour
smolagents 官方博客 · T1 · Introducing smolagents
💬 有疑问? 试试问我:"CodeAgent 和 ToolCallingAgent 怎么选?"、"@tool 装饰器到底做了什么魔法?"、"为什么沙箱这么重要?"——我会用代码或比喻为你解答。
1. @tool 装饰器在 smolagents 里到底做了什么?
2. CodeAgent 与常见的 Tool Calling Agent 最本质的差别是什么?
3. 为什么 smolagents 强调生产环境必须给 CodeAgent 配沙箱?