从 0 搭一个能调用工具的 Agent:原理、代码与踩坑总结
引言
大模型(LLM)虽然聪明,但它们存在“幻觉”且知识往往停留在训练数据截止的那一天。为了让大模型真正落地,我们需要赋予它与外部世界交互的能力——也就是调用工具(Function Calling / Tool Calling)。
今天,我们将从零开始,用 Python 手搓一个最基础但五脏俱全的 Agent。
本期 Demo 任务:打造一个“气象小助手”,它能够自主判定用户意图,调用外部天气 API 获取实时数据,并整理成一份天气日报。
1. 系统流程:Agent 是如何调用工具的?
很多人以为给大模型加工具非常复杂,其实核心流程就是一个**“多轮对话”的闭环**:
-
用户提问:用户输入“今天北京天气怎么样?适合穿什么?”
-
大模型决策(意图识别):LLM 分析问题,发现自己不知道实时天气,但它知道你给过它一个叫
get_weather的工具。于是,LLM 返回一段特殊格式的消息,告诉代码:“我要调用get_weather,参数是location="北京"”。 -
本地代码执行:我们的本地代码拦截到 LLM 的请求,执行真正的
get_weather("北京")函数,拿到结果(例如:25度,晴)。 -
结果回传与生成:代码将“25度,晴”作为一条新的消息发送给 LLM。LLM 结合这个事实,生成最终的自然语言回复给用户。
2. 核心代码实现
我们使用 Python 和 openai 官方库来实现。即使你使用的是国产大模型(如通义千问、智谱等),只要兼容 OpenAI API 格式,这套代码也能直接跑。
2.1 定义本地工具
首先,我们需要一个能获取天气的 Python 函数。这里为了演示,我们用 Mock 数据代替真实的 API 请求。
import json
def get_weather(location: str) -> str:
"""模拟一个调用外部天气的 API"""
# 实际应用中这里会调用如和风天气、OpenWeather的真实API
weather_db = {
"北京": {"temp": "25°C", "condition": "晴朗", "wind": "微风"},
"上海": {"temp": "28°C", "condition": "多云", "wind": "东南风"},
}
result = weather_db.get(location, {"error": "未找到该城市天气"})
return json.dumps(result, ensure_ascii=False)
2.2 定义工具描述 (Tool Schema)
为了让 LLM 知道有这个工具可用,我们需要按照 JSON Schema 的格式向它描述这个工具的名称、作用和参数。
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的当前实时天气情况",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市名称,例如:北京、上海",
}
},
"required": ["location"],
},
}
}
]
2.3 编写 Agent 核心逻辑
这是整个系统的灵魂所在:处理 LLM 的响应,执行函数,并将其塞回对话历史中。
from openai import OpenAI
client = OpenAI(api_key="YOUR_API_KEY")
def run_agent(user_input: str):
messages = [{"role": "user", "content": user_input}]
# 第一轮:询问大模型
response = client.chat.completions.create(
model="gpt-4o-mini", # 或其他支持 Tool Calling 的模型
messages=messages,
tools=tools,
tool_choice="auto"
)
response_message = response.choices[0].message
tool_calls = response_message.tool_calls
# 判断大模型是否决定调用工具
if tool_calls:
print(f"⚙️ [系统日志] Agent 决定调用工具: {tool_calls[0].function.name}")
# 将大模型的调用请求也存入历史
messages.append(response_message)
for tool_call in tool_calls:
function_name = tool_call.function.name
function_args = json.loads(tool_call.function.arguments)
# 本地执行对应的函数
if function_name == "get_weather":
function_response = get_weather(location=function_args.get("location"))
# 将函数的执行结果封装进 message 传给 LLM
messages.append(
{
"tool_call_id": tool_call.id,
"role": "tool",
"name": function_name,
"content": function_response,
}
)
# 第二轮:带着工具返回的结果,再次呼叫大模型
final_response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
)
return final_response.choices[0].message.content
return response_message.content
# 运行测试
if __name__ == "__main__":
user_query = "帮我查一下北京今天的天气,并写一段简短的出行日报。"
print(f"👤 用户:{user_query}\n")
result = run_agent(user_query)
print(f"🤖 Agent:\n{result}")
3. 踩坑与问题分析
在实际开发和将 Agent 部署到生产环境的过程中,你一定会遇到以下几个坑:
坑 1:参数幻觉(Hallucinated Arguments)
现象:工具需要 location,但大模型偏偏传给你一个拼写错误的字段,或者凭空捏造了你没有定义的参数,导致本地 Python 代码报错(如 KeyError)。 解法:在代码层面必须做好异常处理(try-except)。如果解析 JSON 失败或缺少必需参数,不要直接崩溃,而是把错误信息(例如 "Error: Missing required argument 'location'")作为 tool 的结果返回给大模型,聪明的大模型会自动发现错误并自我修正,重新发起调用。
坑 2:死循环调用(Infinite Loops)
现象:大模型调用 API 失败,然后它不认输,反复发起同样的调用,导致 Tokens 被迅速耗尽。 解法:在系统外层强行限制最大工具调用次数(Max Steps)。例如设定一个 step < 5 的计数器,如果循环超过 5 次还没给出最终回复,直接强制中断并向用户抛出兜底提示。
坑 3:不知道什么时候该用工具
现象:你问大模型“今天天气如何”,它却直接开始胡编乱造,完全不触发 get_weather。 解法:
-
优化 Tool Schema:大模型是根据
description来判断是否调用的。你的 description 必须足够精确。不要只写“天气工具”,要写“当用户询问任何关于实时天气、气温、降水概率时,必须调用此工具”。 -
System Prompt 加持:在系统的 System 消息中明确立规矩:“你是一个拥有实时网络搜索能力的助手。面对你不确定的事实,必须优先使用工具查询,禁止凭空捏造。”
更多推荐



所有评论(0)