直接说你有没有遇到过这种场景想让模型去查一下天气、算一笔账、调一个内部接口结果它一本正经地胡编乱造。以前做大模型应用最头疼的就是模型活在语言世界里碰不到现实世界的数据。Function Calling函数调用这个东西出来之后等于给模型装了一双手让它能把对话里的意图翻译成程序能执行的指令。这篇文章我会把Function Calling从原理到代码完整拆开包括底层机制、一次函数是怎么被编排起来的、实际联调中最容易翻车的几个细节以及哪些场景其实不适合硬上Function Calling。整篇内容偏向实战代码以Python为主框架层面会兼顾OpenAI SDK和通用的函数协议写法适合那些已经在做LLM应用但感觉模型输出不够可控的开发者。1. Function Calling解决的不是联网而是模型与系统的握手问题先说一个最常见的误解很多人觉得Function Calling是让模型去联网搜索其实完全不是一回事。联网搜索只是函数调用里的一个具体案例Function Calling真正解决的是模型如何与外部系统安全、准确、可验证地交互。模型本身是一个概率性的文本生成系统它没有真实执行的能力。你问它今天北京天气怎么样它只能根据训练数据里见过的类似表述去猜测猜对了是运气猜错了就是一本正经地胡说八道。Function Calling的核心思路是让模型根据自己的能力边界把用户的自然语言请求转化为一个结构化的函数调用声明——函数名、参数、必要的说明——然后由我们自己的代码去真正执行这个调用再把执行结果返回给模型让模型基于真实结果生成最终回复。这个设计把智能和求真拆开了。模型负责理解意图、抽取关键参数、判断调用顺序我们的代码负责处理真实世界的数据两者通过一次结构化的中间表示完成握手。正因为中间这一层是结构化数据不是让模型生成JSON字符串而是模型直接输出一个可校验的函数调用结构所以我们才能对模型的行为做约束、校验、容错和审计。从工程角度看这其实是把不可控的自由文本输出压缩到了一个受控的动作空间里。你可以把Function Calling理解成给模型发了一张只能填固定空白的表格而不是让它随便写一篇小作文。模型不能发明函数只能在开发者预先定义好的函数列表里做选择和组合这从源头就限制了幻觉风险。我自己在实际项目里的体会是Function Calling把模型能力和业务确定性完成了解耦。业务逻辑该怎么执行是工程师说了算模型只负责回答应该调用哪个、参数是什么。只要这个分工想清楚了后面不管接SQL、接HTTP接口还是接内部流程工具路都是通的。2. 一次完整的函数调用模型的内部决策拆解要写好Function Calling的代码第一步是把一次函数调用生命周期完整吃透。我见过很多开发者在代码里东抄一行西抄一段报错就换字段名最后跑通了也不明白为什么某些参数必须那样传。2.1 从用户输入到函数选择模型如何理解工具清单当我们在API请求里传tools参数时实际上做了一件很重要的事我们不是在告诉模型怎么做事而是在描述可选的世界行为。每个tools条目里包含函数名、函数描述、参数Schema。注意函数名和描述是模型用来做选择判断的关键依据很多开发者不重视描述字段随便写一句话结果模型经常选错函数。我的经验是函数描述要写清楚这个函数解决什么问题在什么情境下应该用什么情境下不应该用。举个例子如果你同时定义了get_current_weather和get_weather_forecast两个函数描述里只是简单地写获取天气和获取天气预报模型很容易在用户需要未来五天天气时调用错。正确写法是get_current_weather当用户询问当前天气状况时使用提供城市名、地区或邮编作为参数。get_weather_forecast当用户询问未来若干天的天气预报时使用提供城市名、天数范围作为参数。描述越具体模型的工具选择准确率越高。这不是玄学而是模型底层机制决定的模型在生成下一个token时会对所有候选函数做语义相似度计算描述文本就是它唯一的判断依据。2.2 工具参数Schema模型是按你的约束生成结构的第二个关键点是参数Schema。Function Calling允许模型自由发挥的空间完全由你定义的参数Schema决定。比如天气函数的参数location如果你只定义成type: string模型会填北京也可能填Beijing还可能填北京市朝阳区同一个语义实体出现各种变体你后端做映射时头都要大。正确的做法是用枚举约束或者在描述里给出固定的格式要求。再比如天数参数days如果你不限定取值范围模型可能填1也可能填999你业务上根本不可能支持999天。加上minimum: 1, maximum: 14之后模型输出的参数就会落在合法区间内。有一点需要特别说明Schema约束不是强约束。模型在理解上下文不充分的情况下仍可能输出不符合Schema的参数。你不能把安全性寄托在模型自觉上后端的参数校验和兜底逻辑必须存在。Scheme的作用是提高正确率而不是保证正确率。2.3 工具调用的循环决策与停止条件Function Calling的完整生命周期不是用户问一句、模型答一句那么简单。如果需要调用函数模型第一轮返回的不是对用户的回复而是一个tool_calls结构。这时候我们必须解析出函数名和参数在自己的代码里真正执行函数把执行结果以role: tool的消息追加回对话历史再次把完整对话历史发给模型。这个循环可能会发生多次。因为某些复杂任务需要先查A再查B甚至查完B之后模型判断还需要补一个C。所以每次模型返回tool_calls我们就循环执行直到模型返回的是普通对话内容为止。这里有个特别容易被忽视的点你在第二轮发给模型的时候之前的对话历史、第一轮的tool_calls、以及第一轮的函数执行结果都必须完整保留。很多人只把最新结果怼回去模型完全没有上下文参照答非所问。我自己常用的循环终止条件有两个一是模型响应的finish_reason变为stop二是tool_calls字段为空。两个条件其实等价但不同SDK里表现略有差异稳妥做法是两者都判断。2.4 为什么函数调用是在对话体系里完成的而不是独立API有些读者可能会问既然Function Calling本质上就是产生结构化指令为什么不单独拆一个API让模型输出一段规划然后再去做执行这个问题的答案是独立的函数规划模型和全链路对话模型是两种粒度前者处理的是单个任务后者处理的是一段对话里可能出现的多轮工具切换。在真实业务里用户常常边说边改需求查一下上海的天气……哦不对查杭州下周的天气然后顺便帮我看看杭州有什么好玩的。如果拆成独立规划API这种中途变化很难处理。而放在对话体系里模型自己会根据用户的新指令重新决策函数调用甚至推翻上一轮的选择这才是和真人助手最接近的交互体验。所以Function Calling设计的核心哲学就是工具调用不是脱离对话的附加功能而是对话内部展开的行动计划。对话历史既是模型的记忆也是模型组织计划的空间。3. 从零搭一个Function Calling服务代码实操与关键参数理论部分讲清楚了接下来上代码。我会以Python环境为例使用OpenAI兼容的SDK写法因为这个协议目前是事实标准所有主流国产模型和开源模型都做了适配后续迁移成本最低。3.1 环境准备与大模型服务选择你需要准备一个支持Function Calling的模型接口。常见的选择有OpenAI系列模型国产的各类商用大模型比如通义千问、文心一言、智谱GLM等开源的Qwen、DeepSeek等模型通过vLLM部署后自带OpenAI兼容协议无论选哪一家代码层面的差别只在base_url和api_key上。下面我以通用写法做演示你只需要替换成你自己的接口地址和密钥就能跑通。pip install openai这里提醒一句如果你用的是国产模型很多SDK版本要求比较绕建议直接用openai库并设置base_url比用各家自己的SDK更省心因为各家SDK的接口签名经常升级参考文档的代码动不动就废弃而OpenAI兼容协议反而稳定。3.2 定义你的第一个工具函数天气预报场景我们的目标是实现一个天气助手能理解自然语言请求调用天气查询函数再基于查询结果生成回复。为了完全可控我没有接真实天气API而是用两个伪造函数模拟真实的数据返回。import json import random from openai import OpenAI client OpenAI( base_urlhttps://your-llm-api.example.com/v1, # 换成你的服务地址 api_keyyour-api-key, # 换成你的密钥 ) # 模拟真实天气服务实际项目中这里应该是HTTP调用或RPC调用 def get_current_weather(location: str, unit: str celsius) - dict: 查询指定地点当前天气的真实状态 # 这里实际应该调一个天气API比如 open-meteo、和风天气等返回结构化数据 # 为了演示这里直接生成一个随机结果并构造一条业务报文 temp_map { 北京: random.choice([0, 5, 10, 15, 20]), 杭州: random.choice([12, 16, 18, 22, 25]), 广州: random.choice([20, 24, 26, 28, 30]), } return { location: location, temperature: temp_map.get(location, 15), unit: unit, condition: random.choice([晴, 多云, 小雨, 阴]), } def get_weather_forecast(location: str, days: int) - dict: 查询指定地点未来days天的天气预报 temp_list [random.randint(-5, 30) for _ in range(days)] return { location: location, forecast_days: temp_list, unit: celsius, }这里有一个工程上的建议工具函数都写成纯函数或者与业务逻辑解耦的模块不要在工具函数内部写复杂的业务判断最好只做参数校验调用真实服务返回结构化数据这样后期接真实API时不需要重构整个对话流程。3.3 将工具和Schema一起发送给模型接下来把函数定义成tools。这里展示我推荐的Schema写法直接使用JSON Schema格式tools [ { type: function, function: { name: get_current_weather, description: 当用户询问某个地点当前的天气状况时使用参数location必须是城市名unit设定温度单位, parameters: { type: object, properties: { location: { type: string, description: 城市名例如北京、杭州、广州 }, unit: { type: string, enum: [celsius, fahrenheit], description: 温度单位默认摄氏 } }, required: [location] } } }, { type: function, function: { name: get_weather_forecast, description: 当用户询问未来某几天的天气预报时使用例如下周天气或未来三天天气, parameters: { type: object, properties: { location: { type: string, description: 城市名例如北京、杭州、广州 }, days: { type: integer, minimum: 1, maximum: 14, description: 要查询的天数 } }, required: [location, days] } } } ]我特别想强调一下description字段的重要性。模型没有试运行的能力它判断什么时候调用哪个函数唯一依据就是这段描述文本。描述里不仅要说该函数做什么还要写清什么时候调用、什么时候不要调用。比如对get_current_weather如果用户说北京今天适合出去玩吗模型应该调当前天气如果用户说北京这两天天气怎么样这应该调用预报函数。这些语义边界写进描述后工具选择的准确率会有非常明显的提升。3.4 主循环请求、解析、执行、回填、再生成下面是核心对话循环。这也是整个Function Calling的骨架逻辑值得反复琢磨。def run_conversation(user_query): messages [{role: user, content: user_query}] # 第一轮请求让模型决定是否调用工具 response client.chat.completions.create( modelyour-model-name, # 替换成实际模型名例如 qwen-plus 或 gpt-4o messagesmessages, toolstools, tool_choiceauto, ) assistant_message response.choices[0].message # 关键判断如果模型返回了tool_calls就说明它想调用函数 if assistant_message.tool_calls: messages.append(assistant_message) # 逐一处理模型想要调用的函数 for tool_call in assistant_message.tool_calls: function_name tool_call.function.name arguments json.loads(tool_call.function.arguments) print(f[执行] 调用函数: {function_name}, 参数: {arguments}) # 这里用字典分发函数避免大段if-else function_map { get_current_weather: get_current_weather, get_weather_forecast: get_weather_forecast, } result function_map[function_name](**arguments) # 把工具执行结果追加到对话历史中role必须是tool messages.append({ role: tool, tool_call_id: tool_call.id, content: json.dumps(result, ensure_asciiFalse), }) # 携带完整对话历史再次请求让模型基于真实结果生成回复 second_response client.chat.completions.create( modelyour-model-name, messagesmessages, toolstools, ) return second_response.choices[0].message.content return assistant_message.content print(run_conversation(北京今天天气怎么样适合出门吗)) print(run_conversation(未来三天杭州的天气怎么样))这段代码的可复用性在于function_map分发机制。后续要加新函数只需要在tools里加一条定义同时在function_map里注册对应的可调用对象主循环逻辑完全不用动。比起写十几个if function_name xxx这种分发方式维护起来舒服得多。3.5 多工具连续调用的正确处理方式上面的示例假设模型一次最多调用一个工具。但真实场景里模型可能同时发起多个工具调用比如用户问比一比北京和广州今天气温模型有可能同时发起两个get_current_weather调用。这时代码里的for tool_call in assistant_message.tool_calls就会起到作用它会依次执行两个调用、依次追加结果。这里有个实践经验并发调用比顺序调用好但要注意服务限流。如果你的后端弱或者外部API有限频顺序执行反而更安全。大多数SDK允许一次返回多个tool_calls但真实外部服务未必能扛住并发压力稳妥起见建议设置一个MAX_PARALLEL_TOOL_CALLS参数控制并发量。还应当考虑多轮调用的情形第一轮执行完两个天气查询后模型还可能觉得需要再查一下空气质量然后继续发起新调用。所以不要只做两次请求而是把它做成一个while循环直到模型不再返回tool_calls为止。3.6 一个健壮版的多轮循环框架下面我给出一个能处理任意轮次工具调用的完整框架这套骨架在多次生产项目中稳定运行建议收藏。def run_multi_tool_conversation(user_query, max_iterations5): messages [{role: user, content: user_query}] iterations 0 while iterations max_iterations: iterations 1 response client.chat.completions.create( modelyour-model-name, messagesmessages, toolstools, tool_choiceauto, ) assistant_message response.choices[0].message # 没有tool_calls时说明模型已准备好最终回复 if not assistant_message.tool_calls: return assistant_message.content messages.append(assistant_message) for tool_call in assistant_message.tool_calls: function_name tool_call.function.name arguments json.loads(tool_call.function.arguments) # 越界保护参数解析失败时绝不能中断整个对话 try: result function_map[function_name](**arguments) except Exception as e: result {error: str(e), message: f函数{function_name}执行失败} messages.append({ role: tool, tool_call_id: tool_call.id, content: json.dumps(result, ensure_asciiFalse), }) return 已达到最大工具调用轮次终止对话。max_iterations这个参数很关键它能防止模型进入工具调用死循环——比如A函数的结果喂回去后模型又调A来回不停。实际项目里常见的问题是模型对某个工具上瘾每次对话都触发调用这时候轮次上限就是最后的兜底保障。4. 参数细节与真实项目中的微调心得代码跑通只是开始。从能跑到在复杂业务里稳定产出中间隔着一堆细节。下面挑几个最影响结果的部分展开说。4.1tool_choice参数auto、none与forced的使用边界默认情况下我们传tool_choiceauto让模型自己判断是否需要调用函数、调用哪个函数。这适用于大多数场景。但某些业务场景下你需要强制行为。tool_choicenone强制模型不得调用任何工具只能基于对话内容直接回复。适合纯闲聊阶段、或者在开场白阶段不期望模型去拉任何数据时使用。tool_choicerequired或者直接指定函数名如{type: function, function: {name: get_current_weather}}强制模型必须调用某个函数。适合用户请求必须落库的流程比如必须先查询再回答的场景。我在实际项目里遇到一个比较隐晦的问题当对话进入多轮、上下文变长之后auto模式下模型偶尔会忘掉该调用工具直接凭记忆作答。这时在关键业务节点上强制tool_choice能稳定输出。这算是一个小技巧业务里如果需要先查后答就主动在对应节点传递tool_choice指定函数不要依赖模型自觉。4.2 工具参数里的枚举、默认值与required字段写JSON Schema时最容易犯的错误是过度约束。以为约束越多模型越准确结果模型频繁返回参数不完整或者触发校验失败最后还得自己写一堆修正逻辑。我的建议是凡是模型很难判断或经常乱填的字段都设置required凡是可选字段尽量给默认值。枚举字段一定要给足可选项不要留其他。模型遇到不在枚举里的值时会自动选一个最接近的如果你enum里给的是[celsius, fahrenheit]用户说用华氏模型能正确映射如果你给的是[摄氏, 华氏]模型也会尝试映射但准确性会下降。参数命名最好贴近常见语言习惯。比如用英文名称location而不要用拼音缩写loc模型对标准英文参数名的理解远好于自定义缩写。4.3 函数执行出错时错误信息该如何喂回模型这一点值得单独拎出来说。工具执行阶段经常会发生外部API超时、参数不合法、权限校验失败等问题。这时候最忌讳的做法是直接抛异常让整个对话崩掉或者把一段晦涩的堆栈文本丢回给模型。正确做法是把错误包装成工具返回数据用结构化的错误说明发给模型让它自己决定下一步是修正参数重试还是用自然语言向用户解释失败原因。举个例子result { status: error, error_code: RATE_LIMITED, message: 天气服务暂时不可用请稍后重试或改用其他城市查询 }模型收到这种结果后通常会生成一段抱歉天气服务暂时不可用建议你稍后再试的回复——体验上远好于抛出异常让用户面对一个错误页面。这个设计思路本质上是把系统异常也变成了模型上下文的一部分让模型充当解释器和缓冲器。4.4 上下文长度函数定义会占用大量tokens每次请求都要把tools完整传进上下文也就是说函数定义占用的是每一轮请求的token成本。如果你的函数列表特别多一次请求可能会消耗上千甚至几千token。几个优化策略只传当前场景可能用到的函数不要把所有函数都塞进去。比如用户明确问天气就别传电商相关的函数。你需要做一层分类路由根据第一轮意图筛选工具子集。精简函数描述。描述写得精炼但信息密度高不要堆砌无关的营销文案。复用一个长期对话时可以考虑把不常调用的工具定义移出后续轮次只保留当前上下文需要的核心函数。实测下来工具筛选策略处理得好不好直接影响响应速度和账号消耗。一个大列表每次都传到服务端不仅仅是钱的问题模型在大量候选函数里做选择也更容易出错。5. 排查链路Function Calling联合调试地图任何功能上了生产环境就一定会有排查需求的。我这里把实际排障中总结出的一个三段式排查链路写出来按这个顺序基本能覆盖绝大多数问题。5.1 第一段排查模型到底有没有正确识别函数当模型没有返回预期的工具调用时第一件事不是改代码而是去查看模型的完整响应。把response.choices[0].message整个打出来看看tool_calls字段到底是什么如果tool_calls为空说明模型判断不需要调用任何自定义工具这时候问题出在tools定义或description上。常见原因函数描述和用户问题语义差距太大参数Schema不完整导致模型认为信息不足无法调用或者tool_choicenone被错误设置。如果tool_calls存在但函数名不对说明多个函数的描述文本区分度不够。比如两个函数描述都提到了天气模型就分不清该用哪个。如果函数名对但参数内容不对通常是Schema约束太宽松或者类型定义错误。比如你把天数days定义成字符串模型就很可能填三天而不是3。这个阶段的核心要领就是不要凭感觉改代码要把模型的意图层打印出来看。模型想不想调、想调哪个、想怎么调在这个字段里一目了然。5.2 第二段排查工具执行链路是否稳定确认模型调用正确之后下一个排查点是工具本身的执行链路。我见过不少项目在本地联调时一切正常一上生产环境外部API就开始超时或返回异常数据结构。这里有几个必查项工具函数有没有做超时控制。外部HTTP调用没有超时设置会直接拖垮整个对话响应时间建议统一给工具函数包一层timeout。工具返回的数据类型是否和你Schema里定义的一致。比如模型预期return是{temperature: 20}你后端返回了字符串20°C模型在做最终总结时容易产生错误理解。工具内是否有重试机制。在大模型场景里单次工具调用失败率其实不低如果函数内部能做一次重试对话成功率会明显提升。我的建议是给每个工具函数都设计统一的返回结构比如{status: success | error, data: ..., message: ...}这样模型更容易理解排查时也更好抓日志。5.3 第三段排查生成阶段为什么还在幻觉最后一段问题跟模型无关跟Prompt写法有关。模型收到工具返回的真实数据后在生成最终回复时仍然可能润色过头——比如天气服务明明返回25度模型却说成接近30度注意防晒。这是因为模型在tokens生成阶段会根据语言习惯对数字做近似的口语化表达。解决方法是在系统提示词里加一条硬性规定你必须严格依据函数返回的数据作答不得修改具体数值不得推测函数未提供的信息。实测这个提示词能显著降低数值型数据的失真率。如果还不行就在最终消息生成前做一次校验用规则检查敏感数值是否与工具返回一致不一致就强制用模板化回复兜底。5.4 常见错误对照表遇到问题先查这张表现象可能原因推荐解法模型不返回tool_callstools描述太模糊或tool_choice设置为none精化description改用auto检查函数定义是否传进请求返回了错误的函数多函数描述相似边界不清晰重构描述明确各函数适用场景和不适用的场景函数参数乱填Schema约束过松字段类型错误补充枚举、minimum/maximum校正类型使用required锁字段缺失必填参数参数没有被充分描述或模型没理解上下文在函数描述中解释参数来源并设置required字段工具函数执行报错外部API超时、数据结构不一致、权限校验不通过增加超时、重试、错误信息包装最终回复老是在数字上胡说模型对数值做了口语化改写系统提示词强制引用数据必要时用规则校验响应变慢工具列表太长消耗大量token按场景动态筛选工具精简描述这张表我每次培训新同事时都直接贴出来很多问题看一遍就能定位到具体原因。6. 进阶经验把Function Calling做成一套可复用的能力模块上面讲的是单点功能到真实项目里你要面对的往往是几十个函数、多个场景、多种模型切换。下面分享几个关于架构设计的经验帮你把Function Calling从一个demo变成一个能力平台。6.1 函数注册中心的自动发现机制当函数数量多起来之后手工维护tools列表和function_map会越来越痛苦。我建议用装饰器做一个函数注册中心这样每写一个新的工具函数它就会被自动收集到定义列表和调用映射里。TOOL_REGISTRY {} def register_tool(description: str, schema: dict): def decorator(func): func_name func.__name__ TOOL_REGISTRY[func_name] { function: func, description: description, parameters: schema, } return func return decorator register_tool( description查询指定地点当前天气状况, schema{ type: object, properties: { location: {type: string, description: 城市名} }, required: [location] } ) def get_current_weather(location: str): # 实际查询逻辑 pass然后生成tools列表时只需要遍历TOOL_REGISTRY自动把定义拼出来调用分发时也从注册中心取函数。新增一个工具函数代码层面只加一个函数和装饰器主循环零改动。这个模式是我在维护十几个工具函数时总结出来的强烈推荐。6.2 场景化工具编排同一个函数不同场景给不同描述有时候同一个后端函数在不同场景下想让它触发的方式完全不同。比如同一个查用户信息的接口在客服场景里希望模型在用户咨询订单问题时调用在风控场景里希望模型在用户身份核验时调用。你不需要写两个函数只要在场景A和场景B分别维护不同的tools描述文本指向同一个注册函数。实现上就是为每个场景维护一个SCENARIO_TOOLS配置里面按需筛选注册中心里的函数并允许覆盖description。这样函数实现和触发策略完全解耦不同业务线之间可以共享底层能力同时各配各的触发规则。6.3 加入验证层工具的参数不合法时主动修复Schema校验之外我强烈建议再做一层业务校验。比如查询天气的城市名北京市朝阳区望京SOHO这种经过模型推断但可能过拟合的输入虽然格式合法但实际城市服务不认识。这层校验要做两件事用正则或字典把模型输出映射为你知道能处理的格式校验不通过时不直接失败而是生成一条参数错误信息返回给模型引导模型重新给出正确参数典型做法是把修正后的错误信息与合法格式示例一起包装返回模型读了之后大概率自己能纠正过来。这个工具返回错误再纠正的链路比一味加正则去堵漏洞要优雅得多。6.4 日志与审计不要忽视Function Calling的观测性Function Calling本质上把模型带入了业务系统的执行层这意味着你必须有完整的日志记录。我的做法是每一次工具调用都记录用户请求、模型判断、工具参数、工具返回、最终回复、耗时、模型消耗的token数。有了这些日志之后至少能回答这五个问题用户最后到底得到了什么答案模型有没有调用它不该调用的函数工具调用失败是哪一环导致的每次对话平均消耗多少token哪个函数被调用的最多哪个定义最容易被误解尤其是第五个问题它能反向指导你优化函数描述。如果某函数被误调用的频率很高多半是描述和参数Schema写得有歧义。7. 哪些场景不适合硬上Function Calling最后必须泼一盆冷水Function Calling不是万灵药有些场景硬上反而会让系统更复杂、更脆弱。7.1 高频低延迟查询场景如果用户请求大部分时候只查一个固定列表比如查一下自己的订单状态这种方式通过一个函数调用绕了好大一圈模型解析参数、工具执行、再让模型总结链路慢且token开销大。如果你的场景可以用规则直接命中就没必要让模型参与决策。比如订单查询直接用ICD12你的订单编号是xxx去查数据库更简单。模型应该在用户用自然语言表达模糊需求时介入而不是对每个请求都做一次工具调用编排。7.2 必须保证100%确定性的场景Function Calling本身是概率性的。哪怕模型已经训练得很稳它仍可能在某些极端上下文里选错函数、填错参数。如果你的场景涉及支付操作、权限变更、删除数据这类不可回退动作不能单纯依赖模型的函数选择判断。这类高风险动作的正解是把Function Calling的决策结果作为建议动作在代码里增加二次确认或者人为审批环节绝不能把最终执行权完全交给一个概率模型。我之前处理过一个案例某团队让模型自动调用删除用户的接口结果用户说了一句反话模型当真就去调删了幸好后面加了审批流才没出事。7.3 上下文已经超长的长对话Function Calling需要把工具定义和完整历史一起传给模型这会让上下文长度压力雪上加霜。当对话已经有几十轮时再叠加几十个工具定义很容易触发上下文窗口上限。这时候需要考虑分段记忆方案把早前的对话摘要化只保留当前轮次所需的工具列表。7.4 工具数量过多超过20个时的退化现象实测下来当一次请求里塞进来的工具超过15到20个之后模型的函数选择准确率会明显下降。这个现象在多个模型上都存在原理上是模型在超长工具列表里的注意力开始分散。解决思路一个是通过意图路由先缩小工具候选范围另一个是把工具粒度变粗——将多个近似的功能合并成一个工具用内部action字段区分。后者看似把单个函数变复杂了但整体准确率往往更高。8. 从Function Calling到Agent下一步该往哪走Function Calling是Agent体系的基石之一但它只是一个原子能力离真正的智能Agent还差一段。前面我们讲了模型决定调用什么函数、我们负责执行这一层接下来的演进方向是让模型具备更强的规划和多步决策能力。我最近在做的一个模拟项目X里已经把Function Calling升级为了一套轻量的任务编排协议模型第一次只决定第一步该做什么执行完结果返回后它再根据当前状态决定第二步。每走一步都重新审视上下文而不是在开始时一次性列出完整计划。这种边做边想的循环加上缓存机制比一次性Plan的鲁棒性高很多推荐有兴趣的朋友在Function Calling基础稳定之后往这个方向探索。另外一件事值得持续关注越来越多的模型API开始原生支持并行工具调用和结构化输出能力。对工程师来说这意味着可以少写很多解析和兜底代码把精力放在真正的业务逻辑上。不过任何新特性上线前都要先在小流量灰度环境里验证不要拿生产环境的稳定性去赌模型行为。这一点我从做Function Calling的第一天到现在都一直坚持也是最有价值的工程底线。Function Calling适合当跳板不能当终点。先把模型能稳定调函数这一步做扎实后面的Agent能力、多步规划、记忆管理都会顺理成章地长出来。