2. 环境搭建与第一个 Agent
这是什么:工具调用(function calling) = 模型不直接执行代码,而是「点名」要你调哪个函数、传什么参数。
为什么用:只靠模型自己回答,它既查不了资料也动不了你的文件;给它工具,模型才算有了手。
如何用:装好 SDK 后,用三段代码把「挂工具 → 执行 → 回传结果」串起来,做出会查天气的 Agent。
2.1 环境准备
按 虚拟环境 venv 建环境,装 LLM 教程用过的同一个 SDK:
mkdir agent-lab && cd agent-lab
python -m venv .venv
.venv\Scripts\Activate.ps1 # (macOS/Linux: source .venv/bin/activate)
pip install openai
Key 仍从环境变量读,沿用 LLM 教程 的约定:
$env:LLM_API_KEY = "你的key"
$env:LLM_BASE_URL = "https://api.openai.com/v1"
$env:CHAT_MODEL = "gpt-4o-mini"
前提:模型必须支持 function calling(工具调用)。不支持的模型看不到
tools参数,工具会被静默忽略。
2.2 工具调用是怎么发生的
模型不会真的执行你的函数——它只会说「我要调这个函数,参数是这些」。整个过程分三步:
你 ──▶ 模型:给你一个工具清单
模型 ──▶ 你:我要调 get_weather(city="北京")
你 ──▶ 模型:工具执行完了,结果是「晴,26℃」
模型 ──▶ 你:最终回答(基于结果)
关键点:第 3 步是模型自己决定要不要调、调哪个。你只负责「把工具挂上」和「把结果送回去」。
2.3 第一个 Agent
新建 first_agent.py:
import json
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["LLM_API_KEY"],
base_url=os.environ.get("LLM_BASE_URL", "https://api.openai.com/v1"),
)
CHAT_MODEL = os.environ.get("CHAT_MODEL", "gpt-4o-mini")
# ① 真正干活的函数。真实场景这里会请求天气 API
def get_weather(city: str) -> str:
fake = {"北京": "晴,26℃", "上海": "多云,29℃", "广州": "雷阵雨,31℃"}
return fake.get(city, f"没查到 {city} 的天气")
# ② 用 JSON Schema 告诉模型:这个工具叫什么、干什么、要什么参数
TOOLS = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的当前天气。城市名用中文。",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名,如 '北京'"}
},
"required": ["city"]
}
}
}]
messages = [{"role": "user", "content": "北京现在天气怎么样?"}]
# ③ 第一次请求:把工具清单给模型
resp = client.chat.completions.create(
model=CHAT_MODEL,
messages=messages,
tools=TOOLS,
)
msg = resp.choices[0].message
# ④ 模型要调工具了
if msg.tool_calls:
call = msg.tool_calls[0]
args = json.loads(call.function.arguments)
result = get_weather(**args)
# ⑤ 把「模型的决定」和「工具的结果」都写回上下文
messages.append(msg)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": result,
})
# ⑥ 再问一次,这次它会拿结果回答
resp = client.chat.completions.create(
model=CHAT_MODEL,
messages=messages,
tools=TOOLS,
)
print(resp.choices[0].message.content)
运行:
python first_agent.py
你会看到(示意):
北京现在天气晴,26℃,适合外出。
| 代码 | 作用 |
|---|---|
get_weather() |
真正干活的地方,你随便换 |
TOOLS |
工具说明书,模型靠它决定调不调 |
msg.tool_calls |
模型要调工具时才有;不需要工具时是 None |
json.loads(call.function.arguments) |
参数是 JSON 字符串,要解出来 |
role: "tool" + tool_call_id |
工具结果的回传格式,id 必须对上 |
| 第二次请求 | 把结果送回去,模型才会给出最终答案 |
2.4 工具结果要写成给人看的句子
工具返回值是给模型看的,模型读不懂 {'code': 0, 'data': {...}} 这种结构。直接返回自然语言,它才知道怎么复述:
def get_weather(city: str) -> str:
return f"{city}:晴,26℃,东南风 2 级" # ✅ 模型能直接用
# return {"city": city, "temp": 26} # ❌ 模型得自己猜怎么转述
2.5 常见翻车现场
| 报错 / 现象 | 原因 | 修法 |
|---|---|---|
msg.tool_calls 一直是 None |
模型不支持 function calling | 换个支持工具调用的模型 |
| 模型调了工具但没收到结果 | 漏了 role: "tool" 那条消息 |
严格按 ⑤ 写回 |
| 报 tool_call_id 不匹配 | 手工拼的 id 对不上 | 用 call.id 原样回传 |
| 工具参数传错类型 | Schema 里没写清类型 | 给每个参数写 type 和 description |
| 模型调了个不存在的函数 | 工具清单里有重名或描述不清 | 检查 TOOLS 里的 name |
2.6 动手改
- 给
TOOLS再加一个get_time(city)工具(返回当前时间),问一句「北京几点 北京天气怎么样」,看它会不会两个都调 - 把
get_weather的返回值改成字典,看模型回答会不会变得别扭 - 把
TOOLS里的description删掉再跑一次,感受「工具说明写不清」的后果
2.7 小结
- 模型不执行函数,它只要求你执行——工具调用是「模型说、你来干」
- 工具挂上
tools,模型自己决定调不调、调哪个 - 执行完必须把 assistant 消息和
role: "tool"消息都写回上下文 - 工具的
description是写给模型看的说明书,比代码本身更重要 - 工具返回值用自然语言,模型更好消化
下一章:Agent 的循环,让它自己转起来干多步的活 🐸