在第一讲里,我们让 Agent 学会了“动嘴”(思考与对话),第二讲让它学会了“发声”(API 调用与流式输出)。但这还不够,真正的 Agent 必须能“动手”干活。
这一讲,我们将解锁大模型最强大的原生能力之一:Function Calling(工具调用)。这是大模型从“聊天机器人”进化为“智能体”的分水岭。
一、为什么需要 Function Calling?
如果没有 Function Calling,大模型就像一个被关在玻璃罩里的超级大脑:
它能告诉你“北京现在天气是 25 度”,因为它背过书。
但它无法直接去查实时的天气 API,也无法直接操作你的电脑、数据库或文件系统。
Function Calling 就是打破玻璃罩的那只手。它允许大模型在对话过程中,识别出用户的需求,并主动返回一个“调用指令”,告诉程序:“我要用某个工具,参数是 XXX”。
二、Function Calling 的核心原理
整个过程像一个“三幕剧”:
第一幕(声明):你告诉大模型:“我这里有几个工具,名字叫
get_weather和calculator,分别用来查天气和算数学题。”第二幕(决策):用户问:“今天北京热吗?顺便帮我算下 125 * 47”。大模型分析后,决定调用
get_weather和calculator。第三幕(执行):大模型返回结构化的 JSON 数据(而非自然语言)。你的代码拿到这个 JSON,去真正执行对应的 Python 函数,然后把结果再喂给大模型,大模型最后组织成一句人话回答用户。
三、实战:打造一个带工具的 Agent
我们将创建一个具备“天气查询”和“计算器”能力的 Agent。
1. 定义工具集(Tool Schema)
首先,我们需要按照 OpenAI 兼容的格式,定义我们的工具:
# 工具定义列表 tools = [ { "type": "function", "function": { "name": "get_weather", "description": "获取指定城市的当前天气温度(摄氏度)。", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "城市名称,如北京、上海"} }, "required": ["city"] } } }, { "type": "function", "function": { "name": "calculator", "description": "执行数学计算,支持加减乘除。", "parameters": { "type": "object", "properties": { "expression": {"type": "string", "description": "数学表达式,如 '125 * 47'"} }, "required": ["expression"] } } } ] # 本地对应的 Python 函数实现 def get_weather(city: str): # 模拟天气数据 API mock_data = {"北京": 25, "上海": 28, "广州": 32} temp = mock_data.get(city, 20) return f"{city}当前温度是 {temp}°C" def calculator(expression: str): # 安全计算:只允许数字和基础运算符 try: allowed_chars = set("0123456789+-*/.() ") if not all(c in allowed_chars for c in expression): return "表达式包含非法字符" result = eval(expression) # 注意:生产环境慎用 eval,这里仅为演示 return str(result) except: return "计算失败"2. 构建 Agent 循环逻辑
import json import requests API_KEY = "你的API_KEY" BASE_URL = "https://api.deepseek.com/v1/chat/completions" def call_llm_with_tools(messages, tools): headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" } payload = { "model": "deepseek-chat", "messages": messages, "tools": tools, "tool_choice": "auto", # 让模型自动决定是否调用工具 "temperature": 0.0 } response = requests.post(BASE_URL, headers=headers, json=payload) response.raise_for_status() return response.json() def agent_run_with_tools(user_input): messages = [ {"role": "system", "content": "你是一个有帮助的助手,可以调用工具获取天气或进行计算。"}, {"role": "user", "content": user_input} ] # 第一步:模型决策是否调用工具 response_data = call_llm_with_tools(messages, tools) message = response_data["choices"][0]["message"] # 检查是否有工具调用 if "tool_calls" in message and message["tool_calls"]: print("[模型决策]: 需要调用工具...") tool_call = message["tool_calls"][0] func_name = tool_call["function"]["name"] args = json.loads(tool_call["function"]["arguments"]) print(f"[调用工具]: {func_name}({args})") # 执行本地函数 if func_name == "get_weather": tool_result = get_weather(**args) elif func_name == "calculator": tool_result = calculator(**args) else: tool_result = "工具不存在" print(f"[工具返回]: {tool_result}") # 将工具结果返回给模型,让模型总结回答 messages.append(message) # 添加模型的调用请求 messages.append({ "role": "tool", "tool_call_id": tool_call["id"], "name": func_name, "content": tool_result }) # 第二步:模型根据工具结果生成最终回答 final_response = call_llm_with_tools(messages, tools) # 注意:第二次调用不需要传 tools,或者传空,视模型而定,这里为了简单省略 final_content = final_response["choices"][0]["message"]["content"] return final_content else: # 如果没有调用工具,直接返回内容 return message["content"] # 运行测试 if __name__ == "__main__": user_input = "今天北京热吗?顺便帮我算下 125 乘以 47 是多少?" output = agent_run_with_tools(user_input) print(f"\n[最终输出]: {output}")3. 运行效果预览
[模型决策]: 需要调用工具... [调用工具]: get_weather({'city': '北京'}) [工具返回]: 北京当前温度是 25°C [调用工具]: calculator({'expression': '125 * 47'}) [工具返回]: 5875 [最终输出]: 北京当前温度是 25°C,不算太热。125 乘以 47 的结果是 5875。四、避坑指南(排雷专区)
模型瞎编函数名(Hallucination)
症状:模型返回的
tool_calls里的name并不是你定义的get_weather,而是weather_api_v2。解法:在
system prompt中严格限定可用工具列表,并且开启strict模式(如果支持)。同时,代码中要有兜底判断:如果func_name不在我们的字典里,就返回错误,不要继续执行。
参数格式不对(JSON 解析失败)
症状:
json.loads(tool_call["function"]["arguments"])报错。解法:大模型的参数生成有时不规范。务必在
parameters定义中写清楚type和required。如果解析失败,可以尝试让模型重新生成,或者使用正则提取。
无限递归调用
症状:Agent 陷入死循环,一直调用同一个工具。
解法:在代码中加入
max_iter(最大迭代次数)限制,比如最多允许调用 3 次工具,超过就强制结束并提示用户。
五、课后作业
扩展工具集:增加一个名为
search_web的工具(模拟返回一段文本),让 Agent 能回答“今天是几号?”这种需要实时知识的提问。人工确认机制:修改代码,如果调用的是“删除文件”这类危险工具,Agent 不直接执行,而是输出:“即将执行删除操作,请确认[Y/N]”,只有在用户输入 Y 时才真正执行。
观察日志:在代码中打印出每一次
messages的完整内容,观察大模型是如何通过role: tool的消息把控制权交还给模型的。
六、总结
这一讲我们掌握了:
Function Calling 的运作机制:声明 -> 决策 -> 执行 -> 反馈。
如何让大模型调用本地 Python 函数:通过 JSON Schema 定义接口。
构建了一个具备实际功能的 Agent:能查天气、能计算。
学会了处理工具调用中的常见异常。
至此,你的 Agent 已经拥有了“手脚”,不再是只会动嘴的聊天机器人。下一讲,我们将讲解ReAct 模式(思考 + 行动),让 Agent 学会像人类一样,在复杂任务中进行多步骤的推理和规划。
🧰 开发之余,处理 Base64、JWT 解析、JSON 格式化、Crontab 计算、PDF 合并压缩这些碎片需求,我常用一个纯前端本地工具箱:zz365.top(子页 PDF 大师:PDF 大师 - zz365工具箱)。所有计算在浏览器完成,文件不上传服务器,关页即清。免费、无登录、无广告,适合开发者当常驻标签页。