当前位置: 首页 > news >正文

12-大模型智能体开发工程师:Function Calling原理与实战

系列文章导航:AI系列文章导航目录-持续更新中

第12课:Function Calling原理与实战

📝 本文摘要:本文详解Function Calling机制——没有它Agent只能聊天,有了它Agent能调API、查数据库、发邮件。内容包括:无Function Calling时的变通方案(Prompt解析JSON/ReAct)、Function Calling的完整工作流(工具定义→模型决策→输出结构化→调用执行→结果返回)、OpenAI vs Anthropic工具调用格式对比、多函数并行调用、实战示例(天气查询+数据库+邮件发送),以及Function Calling局限性讨论。

Function Calling是Agent"动手"的基础。没有它,Agent只能聊天;有了它,Agent能调API、查数据库、发邮件——真正地"做事"。


一、Function Calling的本质

一句话理解:Function Calling让模型能够"动手做事"。没有它,模型只能聊天;有了它,模型能调API、查数据库、发邮件——真正地"做事"。

核心原理:模型不是自己执行函数,而是告诉你"我想调哪个函数、传什么参数",然后你的代码去执行。

整个流程类比: 你(开发者) → 给模型一份"工具清单"(告诉它有哪些工具可用) 用户 → 提问题 模型 → 判断: 这个问题需要用工具吗? ├─ 不需要 → 直接回答 └─ 需要 → 输出: "我要调get_weather,参数是{city:北京}" 你(开发者) → 执行get_weather("北京"),把结果返回给模型 模型 → 基于工具结果生成最终回答: "北京今天晴,28°C"

1.1 没有Function Calling时怎么做

方式1: Prompt Hack "请输出以下格式来调用工具: TOOL_CALL: {\"name\": \"get_weather\", \"args\": {\"city\": \"北京\"}}" 问题: - 模型不一定按格式输出 - 格式可能被模型"创造性"修改 - 参数可能不符合预期类型 - 每次都要写很长的Prompt 方式2: 正则匹配 让模型输出自然语言,用正则提取意图 "我想查北京天气" → 正则匹配 → 调get_weather("北京") 问题: - 复杂意图很难用正则覆盖 - 参数提取不可靠

1.2 Function Calling做了什么

核心思想: 把"工具调用"变成模型的原生能力 传统方式: 模型只输出文本 → 你从文本中提取意图 → 你调API 问题: 提取意图不可靠,格式不稳定 FC方式: 模型输出结构化的工具调用 → 你直接调API 优势: 100%结构化,参数类型正确,可靠性极高 本质: 模型被训练成"知道什么时候该调工具、怎么调" 这不是Prompt技巧,而是模型能力(通过专门训练获得) 类比: 传统方式 = 你跟一个不会用电脑的人说"帮我查下天气",他口头告诉你怎么查,你自己操作 FC方式 = 你跟一个会用电脑的人说"帮我查下天气",他直接打开天气网站查给你

1.3 Function Calling的完整流程

这是Agent最核心的循环,必须彻底理解!

┌─ 你的代码 ──────────────────────────────────────────┐ │ │ │ 1. 定义工具 (tools参数) │ │ → 告诉模型: 你有哪些工具可用,每个工具做什么 │ │ 2. 发送: messages + tools → LLM API │ │ │ ├─ LLM处理 ─────────────────────────────────────────┤ │ │ │ 3. LLM判断: 需要调工具吗? │ │ ├── 不需要 → 直接生成文本回复 │ │ └── 需要 → 生成tool_calls (结构化的工具调用) │ │ { │ │ "name": "get_weather", │ │ "arguments": "{\"city\": \"北京\"}" │ │ } │ │ │ ├─ 你的代码 ──────────────────────────────────────────┤ │ │ │ 4. 解析tool_calls,执行对应函数 │ │ → 你的代码调用真实的get_weather()函数 │ │ 5. 把结果加入messages,再次调用LLM │ │ → 告诉模型: 工具返回了什么结果 │ │ │ ├─ LLM处理 ─────────────────────────────────────────┤ │ │ │ 6. LLM基于工具结果生成最终回复 │ │ → "北京今天天气晴,温度28°C,湿度45%" │ │ │ └──────────────────────────────────────────────────┘ 注意: 整个过程中,LLM从未直接执行任何函数! 它只是"说"它想调什么,你的代码负责实际执行。 这是安全性的关键——你可以在执行前做权限检查、参数验证等。

二、Function Calling实战

2.1 定义工具

fromopenaiimportOpenAIimportjson client=OpenAI()# 工具定义(告诉模型有哪些工具可用)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"],"additionalProperties":False}}},{"type":"function","function":{"name":"query_order","description":"查询订单信息","parameters":{"type":"object","properties":{"order_id":{"type":"string","description":"订单编号"}},"required":["order_id"],"additionalProperties":False}}}]

2.2 完整的Function Calling循环

# 实际的工具实现defget_weather(city:str,unit:str="celsius")->dict:"""模拟天气API"""weather_data={"北京":{"temp":28,"condition":"晴","humidity":45},"上海":{"temp":32,"condition":"多云","humidity":78},"深圳":{"temp":35,"condition":"雷阵雨","humidity":85},}data=weather_data.get(city,{"temp":25,"condition":"未知","humidity":50})ifunit=="fahrenheit":data["temp"]=data["temp"]*9/5+32returndatadefquery_order(order_id:str)->dict:"""模拟订单查询API"""orders={"ORD001":{"status":"已发货","items":["手机壳","充电器"],"total":128.5},"ORD002":{"status":"待发货","items":["耳机"],"total":299.0},}returnorders.get(order_id,{"status":"未找到","items":[],"total":0})# 工具映射tool_map={"get_weather":get_weather,"query_order":query_order,}# 完整的Agent循环defagent_chat(user_message:str)->str:messages=[{"role":"system","content":"你是一个智能助手,可以查询天气和订单信息。"},{"role":"user","content":user_message}]max_rounds=5# 防止死循环for_inrange(max_rounds):response=client.chat.completions.create(model="gpt-4o-mini",messages=messages,tools=tools,tool_choice="auto"# auto: 模型自己决定是否调工具)msg=response.choices[0].message# 情况1: 模型直接回复(不需要调工具)ifmsg.contentandnotmsg.tool_calls:returnmsg.content# 情况2: 模型要调工具ifmsg.tool_calls:messages.append(msg)# 把模型的tool_call加入历史fortool_callinmsg.tool_calls:func_name=tool_call.function.name func_args=json.loads(tool_call.function.arguments)print(f" → 调用工具:{func_name}({func_args})")# 执行工具result=tool_map[func_name](**func_args)# 把工具结果加入messagesmessages.append({"role":"tool","tool_call_id":tool_call.id,"content":json.dumps(result,ensure_ascii=False)})return"抱歉,处理过程中遇到了问题。"# 测试print(agent_chat("北京今天天气怎么样?"))# → 调用工具: get_weather({'city': '北京'})# → "北京今天天气晴,温度28°C,湿度45%。"print(agent_chat("我的订单ORD001到哪了?"))# → 调用工具: query_order({'order_id': 'ORD001'})# → "您的订单ORD001已发货,包含手机壳和充电器,总金额128.5元。"print(agent_chat("你好"))# → 不调工具,直接回复问候

2.3 tool_choice参数(控制模型是否/如何调用工具)

# "auto"(自动模式): 模型自己决定是否调工具(默认)tool_choice="auto"# "none"(禁用模式): 禁止调工具,强制纯文本回复tool_choice="none"# "required"(强制模式): 强制调工具,模型必须选择一个工具调用tool_choice="required"# 指定工具(指定模式): 强制调用某个特定工具tool_choice={"type":"function","function":{"name":"get_weather"}}

三、Function Calling的底层原理

3.1 模型是怎么学会调工具的

训练阶段: 1. 收集大量"用户意图→工具调用"的配对数据 2. 用SFT(Supervised Fine-Tuning,监督微调)教模型: 看到什么意图时应该输出什么工具调用 3. 用RLHF(Reinforcement Learning from Human Feedback,基于人类反馈的强化学习)优化: 工具调用正确的给奖励 推理阶段: 1. tools参数被转换为特殊Token序列,拼接到输入中 2. 模型看到这些特殊Token,"知道"有工具可用 3. 当判断需要调工具时,输出tool_calls格式的Token序列 4. 这些Token序列被API层解析为结构化的JSON

3.2 并行工具调用

# 一个回复中可以包含多个tool_callsresponse=client.chat.completions.create(model="gpt-4o",messages=[{"role":"user","content":"北京和上海今天天气怎么样?"}],tools=tools)# 模型会并行输出两个tool_calls:# tool_calls[0]: get_weather({"city": "北京"})# tool_calls[1]: get_weather({"city": "上海"})# 你需要都执行,然后把两个结果都返回

四、Function Calling的工程化要点

4.1 工具描述是关键

模型选择工具完全依赖description! ❌ 差的描述: "获取信息" → 模型不知道获取什么信息 ✅ 好的描述: "查询指定城市的当前天气信息,包括温度、天气状况和湿度" → 模型知道什么时候该调这个工具 参数描述同样重要: ❌ "城市" → 模型可能传"北京朝阳区" ✅ "城市名称,如北京、上海、广州" → 模型传正确的值

4.2 错误处理

defagent_chat_robust(user_message:str)->str:messages=[{"role":"user","content":user_message}]for_inrange(5):try:response=client.chat.completions.create(model="gpt-4o-mini",messages=messages,tools=tools)exceptExceptionase:returnf"API调用失败:{e}"msg=response.choices[0].messageifnotmsg.tool_calls:returnmsg.contentor""messages.append(msg)fortool_callinmsg.tool_calls:try:func_name=tool_call.function.name func_args=json.loads(tool_call.function.arguments)iffunc_namenotintool_map:result={"error":f"未知工具:{func_name}"}else:result=tool_map[func_name](**func_args)exceptjson.JSONDecodeError:result={"error":"参数解析失败"}exceptTypeErrorase:result={"error":f"参数类型错误:{e}"}exceptExceptionase:result={"error":f"执行失败:{e}"}messages.append({"role":"tool","tool_call_id":tool_call.id,"content":json.dumps(result,ensure_ascii=False)})return"处理超时,请稍后重试。"

4.3 工具设计原则

1. 单一职责: 每个工具只做一件事 ✅ query_order() + create_refund() ❌ order_operation(type="query_or_refund") 2. 清晰的输入输出: 参数类型明确,返回值结构化 3. 有边界: 工具应该有明确的成功/失败状态 4. 安全性: 危险操作需要确认(如删除、支付) 5. 幂等性: 同样的输入应该得到同样的结果

五、不同模型的Function Calling对比

模型并行调用强制调用Structured Output可靠性
GPT-4o★★★★★
GPT-4.1★★★★★
Claude 3.5+★★★★★
DeepSeek-V3部分★★★★☆
Qwen2.5部分部分★★★★☆
本地模型有限★★★☆☆

📝 作业

作业1:实现一个带有3个工具的Agent

实现一个"个人助手Agent",支持以下工具:

  1. search_web(query)- 搜索网络(模拟实现)
  2. calculate(expression)- 计算数学表达式
  3. translate(text, target_lang)- 翻译文本(模拟实现)

参考答案

fromopenaiimportOpenAIimportjson client=OpenAI(base_url="http://localhost:11434/v1",api_key="ollama")# 工具实现defsearch_web(query:str)->str:mock_results={"Python":"Python是由Guido van Rossum创建的高级编程语言,最新版本3.12","AI":"2026年AI领域最热门的方向是Agent和推理模型",}forkey,valinmock_results.items():ifkey.lower()inquery.lower():returnvalreturnf"搜索'{query}'的结果:未找到相关信息"defcalculate(expression:str)->str:try:# 安全起见,只允许基本数学运算allowed=set("0123456789+-*/().% ")ifall(cinallowedforcinexpression):result=eval(expression)returnstr(result)return"不支持的表达式"except:return"计算错误"deftranslate(text:str,target_lang:str)->str:mock={"hello":"你好","world":"世界","你好":"Hello","世界":"World"}words=text.lower().split()result=" ".join(mock.get(w,w)forwinwords)returnf"[{target_lang}]{result}"tool_map={"search_web":search_web,"calculate":calculate,"translate":translate}tools=[{"type":"function","function":{"name":"search_web","description":"搜索互联网获取信息","parameters":{"type":"object","properties":{"query":{"type":"string","description":"搜索关键词"}},"required":["query"],"additionalProperties":False}}},{"type":"function","function":{"name":"calculate","description":"计算数学表达式,如'2+3*4'","parameters":{"type":"object","properties":{"expression":{"type":"string","description":"数学表达式"}},"required":["expression"],"additionalProperties":False}}},{"type":"function","function":{"name":"translate","description":"翻译文本到目标语言","parameters":{"type":"object","properties":{"text":{"type":"string","description":"要翻译的文本"},"target_lang":{"type":"string","description":"目标语言,如'en'或'zh'"}},"required":["text","target_lang"],"additionalProperties":False}}}]defagent_chat(user_message:str)->str:messages=[{"role":"system","content":"你是一个个人助手,可以搜索信息、计算和翻译。"},{"role":"user","content":user_message}]for_inrange(5):response=client.chat.completions.create(model="qwen2.5:7b",messages=messages,tools=tools,tool_choice="auto")msg=response.choices[0].messageifmsg.contentandnotmsg.tool_calls:returnmsg.contentifmsg.tool_calls:messages.append(msg)fortcinmsg.tool_calls:args=json.loads(tc.function.arguments)result=tool_map[tc.function.name](**args)print(f" →{tc.function.name}({args}) ={result}")messages.append({"role":"tool","tool_call_id":tc.id,"content":json.dumps({"result":result},ensure_ascii=False)})return"处理超时"# 测试print(agent_chat("帮我算一下(128+256)*3等于多少"))print(agent_chat("Python是谁创建的?"))

下一篇文章见:AI系列文章导航目录-持续更新中

http://www.zskr.cn/news/1432654.html

相关文章:

  • 如何安全地在本地导出浏览器Cookie:Get cookies.txt LOCALLY终极指南
  • 别再只会用cp和mv了!Linux软链接的5个高效用法,让你文件管理效率翻倍
  • 深入MS7200芯片:如何用FPGA I2C配置国产HDMI接收器实现4K@30Hz信号环通
  • 2026年四平市本地上门黄金回收门店指南 彩金+铂金+金条+白银回收门店联系方式推荐 - 大熊猫898989
  • 用Pandas rolling处理股票数据:从计算5日线到构建简易交易信号(附完整代码)
  • 从概念到打印:SOLIDWORKS拓扑优化结果,如何一键导出为可3D打印的STL文件?
  • Hologres建表别再乱配索引了!从一次慢查询排查,聊聊字典、位图、聚簇索引的真实选择逻辑
  • 2026年日照市正规上门黄金白银回收品牌门店名录 K金+铂金+金条+银条回收门店联系方式推荐+指南 - 盛世金银回收
  • 手把手教你玩转STM32G4的IAP:从CubeMX配置到生成.bin文件,一个视频全搞定
  • 新兴科技如何重塑无障碍生活:从传感器到AI的辅助技术栈解析
  • 2026年三明市正规上门黄金白银回收品牌门店名录 K金+铂金+金条+银条回收门店联系方式推荐+指南 - 盛世金银回收
  • CORB-Planner:高速无人机避障轨迹规划技术解析
  • 2026年临沂市本地上门黄金回收门店指南 彩金+铂金+金条+白银回收门店联系方式推荐 - 大熊猫898989
  • 别再被加密狗卡住!手把手教你搞定dSPACE 2017A与MATLAB 2016b的完整激活流程
  • 2026年随州市本地上门黄金回收门店指南 彩金+铂金+金条+白银回收门店联系方式推荐 - 大熊猫898989
  • UE5项目实战:不用源码版,如何在任意类中安全创建UserWidget?
  • 2026年三亚市正规上门黄金白银回收品牌门店名录 K金+铂金+金条+银条回收门店联系方式推荐+指南 - 盛世金银回收
  • 2026年台州市本地上门黄金回收门店指南 彩金+铂金+金条+白银回收门店联系方式推荐 - 大熊猫898989
  • 终极指南:免费解密网易云音乐NCM文件,ncmdumpGUI完整使用教程
  • 2026年贺州市本地上门黄金回收门店指南 彩金+铂金+金条+白银回收门店联系方式推荐 - 大熊猫898989
  • 2026年太原市本地上门黄金回收门店指南 彩金+铂金+金条+白银回收门店联系方式推荐 - 大熊猫898989
  • 2026年汕头市正规上门黄金白银回收品牌门店名录 K金+铂金+金条+银条回收门店联系方式推荐+指南 - 盛世金银回收
  • 论文投稿前必看:如何用LaTeX把算法伪代码调得既专业又符合期刊格式要求
  • AI内容生成中的智能文档分块策略:从原理到工程实践
  • UniApp App端自定义UserAgent实战:从基础设置到高级应用场景(含plus.navigator API详解)
  • STM32G473 IAP实战:用CAN总线给设备远程升级固件,附完整工程代码
  • 基于DOM解析与样式提取的HTML到Figma转换技术深度解析
  • 别再瞎调参了!手把手教你用Paddle-OCR微调PP-OCRv4,搞定发票、车牌等垂类识别
  • 从Kali切回Ubuntu有点懵?给安全研究员的Ubuntu系统升级避坑指南
  • OpenGL+FreeGLUT实战:手把手教你用矩阵堆栈搞定图形学里的平移、旋转和缩放