本节目标:理解如何让大模型调用外部工具(函数),从"只能说"变成"能做事"。这是 Agent 和 MCP 的基础。
一、为什么大模型需要工具?
1.1 大模型的天然局限
大模型擅长的: 大模型不擅长的: ✓ 理解语言 ✗ 精确计算(1234 × 5678 = ?) ✓ 生成文本 ✗ 获取实时信息(今天的天气) ✓ 逻辑推理 ✗ 操作外部系统(发邮件、查数据库) ✓ 代码理解 ✗ 访问最新数据(最新股价) 解决方案:给大模型一些"工具",让它在需要时自己调用!1.2 一个生活化的比喻
大模型 = 一个很聪明的助手 没有工具的助手: 你:"帮我查一下明天北京的天气" 助手:"我记得北京这个季节一般是...(可能说错)" 有工具的助手: 你:"帮我查一下明天北京的天气" 助手:(打开天气App查了一下)"明天北京晴,15-25度" 工具让助手从"猜测"变成"查证"!二、Function Calling 工作原理
2.1 整体流程
关键理解:模型只负责"决定调用什么",不负责"真正执行"。执行是你的程序做的。
三、实战:OpenAI Function Calling
3.1 定义工具
# 第1步:定义可用的工具(用 JSON Schema 描述)tools=[{"type":"function","function":{"name":"get_weather","description":"获取指定城市的天气信息","parameters":{"type":"object","properties":{"city":{"type":"string","description":"城市名称,如'北京'、'上海'"},"unit":{"type":"string","enum":["celsius","fahrenheit"],"description":"温度单位"}},"required":["city"]}}},{"type":"function","function":{"name":"calculate","description":"进行数学计算","parameters":{"type":"object","properties":{"expression":{"type":"string","description":"数学表达式,如 '2 + 3 * 4'"}},"required":["expression"]}}}]3.2 完整调用流程
fromopenaiimportOpenAIimportjson client=OpenAI()# 实际的工具函数defget_weather(city:str,unit:str="celsius")->str:"""模拟天气API(实际项目中会调用真实API)"""weather_data={"北京":{"temp":22,"condition":"晴"},"上海":{"temp":25,"condition":"多云"},}data=weather_data.get(city,{"temp":20,"condition":"未知"})returnjson.dumps({"city":city,"temperature":data["temp"],"condition":data["condition"]},ensure_ascii=False)defcalculate(expression:str)->str:"""安全的数学计算"""try:result=eval(expression)# 注意:生产环境应使用更安全的方式returnjson.dumps({"expression":expression,"result":result})exceptExceptionase:returnjson.dumps({"error":str(e)})# 工具函数映射tool_functions={"get_weather":get_weather,"calculate":calculate,}# 完整的对话循环defchat_with_tools(user_message:str):messages=[{"role":"user","content":user_message}]# 第1次调用:模型决定是否使用工具response=client.chat.completions.create(model="gpt-4o",messages=messages,tools=tools,)message=response.choices[0].message# 检查模型是否要调用工具ifmessage.tool_calls:messages.append(message)# 把模型的回复加入对话# 执行每个工具调用fortool_callinmessage.tool_calls:func_name=tool_call.function.name func_args=json.loads(tool_call.function.arguments)print(f" 调用工具:{func_name}({func_args})")# 执行函数result=tool_functions[func_name](**func_args)# 把结果告诉模型messages.append({"role":"tool","tool_call_id":tool_call.id,"content":result,})# 第2次调用:模型根据工具结果生成最终回答final_response=client.chat.completions.create(model="gpt-4o",messages=messages,)returnfinal_response.choices[0].message.contentelse:# 不需要工具,直接返回returnmessage.content# 测试print(chat_with_tools("北京今天天气怎么样?"))# 调用工具:get_weather({'city': '北京'})# 输出:北京今天天气晴朗,气温22度,适合外出活动!print(chat_with_tools("帮我算一下 1234 × 5678"))# 调用工具:calculate({'expression': '1234 * 5678'})# 输出:1234 × 5678 = 7,006,652print(chat_with_tools("你好啊"))# (不调用工具,直接回答)# 输出:你好!有什么我可以帮你的吗?四、Anthropic Tool Use(Claude)
Claude 的工具使用方式略有不同,但核心思想一样:
importanthropicimportjson client=anthropic.Anthropic()# 定义工具tools=[{"name":"get_weather","description":"获取指定城市的天气信息","input_schema":{"type":"object","properties":{"city":{"type":"string","description":"城市名称"}},"required":["city"]}}]# 第1次调用response=client.messages.create(model="claude-sonnet-4-6-20250415",max_tokens=1024,tools=tools,messages=[{"role":"user","content":"北京天气怎么样?"}])# 检查是否需要调用工具ifresponse.stop_reason=="tool_use":# 找到工具调用块tool_use=next(bforbinresponse.contentifb.type=="tool_use")print(f"调用工具:{tool_use.name}({tool_use.input})")# 执行工具并返回结果tool_result=get_weather(**tool_use.input)# 第2次调用:带上工具结果final_response=client.messages.create(model="claude-sonnet-4-6-20250415",max_tokens=1024,tools=tools,messages=[{"role":"user","content":"北京天气怎么样?"},{"role":"assistant","content":response.content},{"role":"user","content":[{"type":"tool_result","tool_use_id":tool_use.id,"content":tool_result}]}])print(final_response.content[0].text)五、多工具并行调用
模型可以一次调用多个工具,你的程序并行执行后一起返回:
用户:"帮我查一下北京和上海的天气,顺便算一下 100 * 200" 模型返回(一次性3个调用): 1. get_weather(city="北京") 2. get_weather(city="上海") 3. calculate(expression="100 * 200") 你的程序并行执行这3个函数 → 把3个结果一起告诉模型 → 模型综合回答importasyncioasyncdefexecute_tools_parallel(tool_calls):"""并行执行多个工具调用"""tasks=[]forcallintool_calls:func=tool_functions[call.function.name]args=json.loads(call.function.arguments)# 创建异步任务tasks.append(asyncio.to_thread(func,**args))# 并行执行所有任务results=awaitasyncio.gather(*tasks)returnresults六、工具设计最佳实践
6.1 好的工具描述
# ❌ 差的工具定义{"name":"query","description":"查询数据","parameters":{"properties":{"q":{"type":"string"}}}}# 问题:名称模糊、描述太简单、参数不清楚# ✅ 好的工具定义{"name":"search_products","description":"在商品数据库中搜索商品。""支持按商品名称、类别、价格范围搜索。""返回匹配的商品列表,包含名称、价格、库存信息。","parameters":{"properties":{"keyword":{"type":"string","description":"搜索关键词,如'笔记本电脑'、'耳机'"},"category":{"type":"string","enum":["电子产品","图书","服装","食品"],"description":"商品类别筛选"},"max_price":{"type":"number","description":"最高价格(元),如 1000"},"limit":{"type":"integer","description":"返回结果数量上限,默认10","default":10}},"required":["keyword"]}}6.2 工具设计原则
┌────────────────────────────────────────────────────────────┐ │ 工具设计七原则 │ │ │ │ 1. 命名清晰:用动词+名词,如 search_products, send_email │ │ 2. 描述详细:说清楚做什么、需要什么参数、返回什么 │ │ 3. 参数精确:用 enum 限制可选值,用 description 解释 │ │ 4. 职责单一:一个工具只做一件事 │ │ 5. 错误友好:返回有意义的错误信息 │ │ 6. 安全第一:敏感操作需确认,限制危险参数 │ │ 7. 结果简洁:只返回必要的信息,不要返回大段无关数据 │ └────────────────────────────────────────────────────────────┘七、错误处理与安全
7.1 工具调用可能出错
defsafe_tool_execution(func_name:str,func_args:dict)->str:"""安全地执行工具函数"""try:# 检查函数是否存在iffunc_namenotintool_functions:returnjson.dumps({"error":f"未知工具:{func_name}"})# 执行函数(设置超时)importsignaldeftimeout_handler(signum,frame):raiseTimeoutError("工具执行超时")signal.signal(signal.SIGALRM,timeout_handler)signal.alarm(30)# 30秒超时result=tool_functions[func_name](**func_args)signal.alarm(0)# 取消超时returnresultexceptTimeoutError:returnjson.dumps({"error":"工具执行超时,请稍后重试"})exceptExceptionase:returnjson.dumps({"error":f"工具执行失败:{str(e)}"})7.2 安全考量
┌────────────────────────────────────────────────────────────┐ │ 安全检查清单 │ │ │ │ ✓ 永远不要让模型直接执行任意代码 │ │ ✓ 对危险操作(删除、支付)添加人工确认步骤 │ │ ✓ 限制工具的访问范围(只读 vs 读写) │ │ ✓ 对输入参数进行验证和清洗 │ │ ✓ 设置执行超时和重试限制 │ │ ✓ 记录所有工具调用日志 │ │ ✓ 防止 Prompt 注入导致的恶意工具调用 │ └────────────────────────────────────────────────────────────┘八、从 Function Calling 到 MCP
Function Calling 的局限: 每个 AI 应用都要自己实现工具 → 重复造轮子 ┌─────────┐ ┌─────────┐ ┌─────────┐ │ App A │ │ App B │ │ App C │ │ ┌─────┐ │ │ ┌─────┐ │ │ ┌─────┐ │ │ │天气 │ │ │ │天气 │ │ │ │天气 │ │ ← 每个App都要写一遍 │ │数据库│ │ │ │数据库 │ │ │ │邮件 │ │ │ │邮件 │ │ │ │搜索 │ │ │ │搜索 │ │ │ └─────┘ │ │ └─────┘ │ │ └─────┘ │ └─────────┘ └─────────┘ └─────────┘ MCP 的解决方案:标准化! ┌─────────┐ ┌─────────┐ ┌─────────┐ │ App A │ │ App B │ │ App C │ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │ └────────────┼────────────┘ │ MCP 标准协议 │ ┌────────────┼────────────┐ │ │ │ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ │ 天气 │ │ 数据库 │ │ 邮件 │ ← 写一次,到处用 │ Server │ │ Server │ │ Server │ └─────────┘ └─────────┘ └─────────┘九、本篇小结
┌─────────────────────────────────────────────────────┐ │ 本篇知识地图 │ │ │ │ Function Calling = 让大模型能调用外部工具 │ │ │ │ 核心流程: │ │ 定义工具 → 用户提问 → 模型选择工具 → │ │ 程序执行 → 返回结果 → 模型回答 │ │ │ │ 关键点: │ │ ├── 模型只负责"决定调用什么" │ │ ├── 你的程序负责"真正执行" │ │ ├── 支持多工具并行调用 │ │ └── 需要注意安全性 │ │ │ │ 工具设计原则: │ │ 命名清晰 + 描述详细 + 职责单一 + 安全第一 │ │ │ │ 演进方向: │ │ Function Calling → MCP(标准化协议) │ └─────────────────────────────────────────────────────┘十、扩展学习资源
必读
- OpenAI Function Calling 文档 —— 官方指南
- Anthropic Tool Use 文档 —— Claude 工具使用
- Gemini Function Calling —— Google 的实现
推荐
- Gorilla LLM —— 专门优化工具调用的研究项目
- ToolBench —— 工具使用评测基准
动手实践
- 实现一个能查天气 + 算数学的对话机器人
- 给现有项目的 API 包装成 Function Calling 工具
- 尝试让模型在一次对话中连续调用多个工具完成复杂任务
下一篇章预告:将讲解MCP(Model Context Protocol)——一个标准化的协议,让工具像 USB 一样"即插即用"。
觉得有用的话,点个关注吧!大模型方面你想看什么?留言区说,我来写。
声明:本博客内容素材来源于网络,文章由AI技术辅助生成。如有侵权或不当引用,请联系作者进行下架或删除处理。