Function Calling 实战指南:从原理到落地,让大模型真正调用外部函数
1. 为什么 function calling 值得你花时间搞明白第一次看到 function calling 这个词很多人会以为它是什么新出的编程语法糖或者某个框架的专属功能。其实不是。它本质上是一套让大语言模型能够“主动调用外部函数”的机制。你可以把它理解成给模型装了一双手——以前模型只能动嘴告诉你“今天北京天气大概多少度”现在它能直接伸手去查天气接口然后把真实数据拿回来告诉你。我最早接触这个能力是在做一个客服工单自动分类的小工具时。当时的需求很简单用户发一段话我需要判断这段话属于“退款”“物流”“账号问题”还是“其他”然后自动填进工单系统。纯靠提示词让模型输出分类结果也能跑但问题在于输出格式不稳定有时候多一个句号有时候分类名称大小写不一致后处理写得我头皮发麻。后来换成 function calling 的方式把分类逻辑封装成一个函数让模型直接返回结构化的参数整个世界都清净了。所以这篇文章适合谁看如果你正在做 AI 应用开发尤其是需要让模型和外部系统打交道的场景比如查数据库、调 API、操作文件、发邮件、生成图表那 function calling 是你绕不过去的一环。如果你只是偶尔用用对话模型写写文案那可以先收藏等哪天需要做自动化流程了再翻出来。我会从设计思路讲到实操细节再到踩过的坑尽量把这件事说透。2. function calling 到底在解决什么问题2.1 从“模型只会说话”到“模型能干活”的转折大语言模型的核心能力是理解和生成自然语言。你给它一段文字它给你一段文字。这个模式在聊天场景下够用但一旦涉及到真实业务短板就暴露了。比如你想让模型帮你查一下某个订单的物流状态它只能根据训练数据里的通用知识告诉你“一般快递三天左右到”但它没法知道你那个具体订单的实时位置。function calling 的出现就是为了补上这块短板。它的工作方式是你在调用模型的时候除了传入用户的消息还传入一组函数定义。每个函数定义包含函数名、功能描述、参数列表和参数类型。模型在理解用户意图后如果判断需要调用某个函数它不会直接执行而是返回一个结构化的调用请求告诉你“我要调用 get_order_status 这个函数参数是 order_id12345”。然后由你的代码去实际执行这个函数再把执行结果传回给模型模型最后根据结果生成自然语言回复。这个流程听起来多了一步但正是这一步让模型从“只会说”变成了“能干活”。而且因为函数是你自己写的你可以控制它去查数据库、调第三方接口、读本地文件模型只负责决策“什么时候该调哪个函数”以及“参数填什么”。2.2 和传统提示词工程的核心差异有人会问我直接在提示词里写“请输出 JSON 格式包含分类和置信度”不也能拿到结构化结果吗确实可以但两者有本质区别。传统提示词工程依赖模型对格式的“自觉遵守”。你写“请输出 JSON”模型大部分时候会照做但偶尔会加一句“好的以下是结果”然后再输出 JSON或者把字段名写成中文或者该用数字的地方用了字符串。你得写一堆正则去清洗还得处理各种边界情况。function calling 则是把格式约束变成了机制约束。模型返回的调用请求是严格按照你定义的 schema 来的字段名、类型、必填项都由 schema 控制。如果模型想调一个不存在的函数或者参数类型不对调用方可以直接拒绝。这就把“格式对不对”的问题从概率问题变成了确定性问题。另一个差异在于多轮协作。传统提示词方式下如果你需要模型先查天气再根据天气推荐穿搭你得把两步写在一个提示词里或者手动分两次调用。function calling 支持模型在一次回复中发起多个调用请求也支持在多轮对话中逐步调用不同函数整个流程更接近人类解决问题的自然节奏。2.3 典型应用场景盘点function calling 的适用场景其实比很多人想象的广。我列几个自己做过或者见过别人做的例子。第一个是数据查询类。比如用户问“上个月华东区的销售额是多少”模型调用 query_sales 函数参数是 region“华东”、month“2024-06”函数去查数据库返回数字模型再组织成一句话回复。这类场景的关键是函数要能处理各种参数组合并且对查不到数据的情况有友好返回。第二个是操作执行类。比如用户说“帮我把这封邮件发给张三”模型调用 send_email 函数参数是 recipient、subject、body。这里要注意的是涉及写操作或不可逆操作时最好加一道确认机制让模型先返回调用请求由用户确认后再真正执行。第三个是计算与转换类。比如用户上传一张表格问“帮我算一下这列的平均值”模型调用 calculate_average 函数参数是数据数组。虽然模型自己也能算但涉及精确计算时走函数更可靠尤其是大数运算和浮点数处理。第四个是外部服务集成类。比如查汇率、查航班、查快递、生成图片、调用翻译服务这些都可以封装成函数让模型按需调用。我甚至见过有人把家里的智能灯控接口封装成函数然后通过对话控制开关灯虽然有点绕但确实能跑通。3. 核心机制拆解一次完整的 function calling 是怎么跑起来的3.1 函数定义的三要素名称、描述、参数 schema函数定义是整个机制的基石。定义写得好不好直接决定了模型能不能正确调用。我总结下来有三个要素必须认真对待。函数名要见名知义。不要用 f1、func_a 这种名字模型看不懂。用 get_weather、search_products、create_ticket 这种动宾结构模型一看就知道是干什么的。如果同一个应用里有多个相似函数名字要有区分度比如 get_user_by_id 和 get_user_by_email别都叫 get_user。功能描述要写清楚“什么时候用”和“不用什么时候”。很多人只写“查询天气”这不够。我会写成“根据城市名称查询当前天气状况包括温度和天气现象。当用户询问某个城市的天气、气温、是否下雨等问题时使用此函数。如果用户问的是历史天气或未来预报不要使用此函数。”这样模型在决策时就有更明确的边界。参数 schema 要严格。每个参数都要指定类型string、number、boolean、array、object必填项要标 required枚举值要列 enum。比如查询天气的函数city 参数类型是 stringunit 参数类型是 string 但 enum 限定为 [“celsius”, “fahrenheit”]。这样模型在填参数时就不会乱来。如果某个参数有默认值在描述里写清楚模型在用户没提的时候会自动省略该参数由你的函数代码去处理默认逻辑。3.2 模型侧的处理逻辑意图识别与参数抽取当你把用户消息和函数定义一起发给模型后模型内部会做几件事。首先它理解用户想干什么然后在函数列表里找匹配的。如果找到匹配的它会从用户消息里抽取参数值。比如用户说“帮我查一下北京今天热不热”模型识别出要调 get_weather然后从“北京”抽取出 city“北京”从“今天”推断出时间范围从“热不热”推断出用户关心温度可能还会把 unit 设为 celsius。这里有个细节值得注意模型抽取参数时可能会做推理。比如用户说“我这边下雨了”模型如果知道用户所在城市从上下文或用户资料里可能会自动填入 city 参数。但如果上下文里没有城市信息模型可能会返回一个缺少必填参数的调用请求或者干脆不调用函数而是反问用户“请问您在哪个城市”。具体行为取决于模型实现和你的提示词引导。另一个细节是模型可能同时发起多个调用。比如用户说“帮我查一下北京和上海的天气”模型可以一次返回两个 get_weather 调用请求分别带不同的 city 参数。你的代码需要能处理这种并行调用分别执行后再把结果一起传回去。3.3 调用方的执行与回传把结果喂回模型拿到模型的调用请求后你的代码要做三件事解析请求、执行函数、回传结果。解析请求就是拿到函数名和参数对象。大部分 SDK 会把这个结构解析好给你你直接读就行。执行函数就是调用你本地定义的那个函数传入参数拿到返回值。回传结果时要注意格式通常需要把结果包装成一个特定角色的消息比如 role“function” 或 role“tool”并带上对应的调用 ID这样模型才知道这个结果是回应哪个调用的。回传的结果内容建议用 JSON 字符串结构尽量扁平。比如查天气返回 {“temperature”: 25, “condition”: “sunny”, “city”: “北京”}不要嵌套太深。如果结果很长考虑截断或摘要因为模型的上下文窗口有限。如果函数执行出错也要把错误信息回传比如 {“error”: “city not found”}模型会根据错误信息决定是重试、换参数还是告诉用户查不到。3.4 多轮对话中的状态管理function calling 在多轮对话里会变得更有意思。比如用户先问“北京天气怎么样”模型调用 get_weather 拿到结果回复“北京今天晴25度”。然后用户接着说“那上海呢”模型需要理解“那上海呢”是在延续上一个问题于是再次调用 get_weather参数 city“上海”。这里的关键是对话历史要完整保留包括之前的函数调用请求和返回结果。这样模型才能理解上下文。但也要注意历史不能无限增长否则会超出上下文窗口。我的做法是保留最近几轮完整对话更早的对话做摘要或者只保留关键信息。还有一种情况是函数调用失败后的重试。比如模型调 get_weather 时 city 参数填错了函数返回错误模型看到错误后可能会自动修正参数再调一次。这个重试逻辑不需要你写模型自己会处理但你要确保错误信息足够清晰让模型知道错在哪。4. 手把手实操从零搭一个可用的 function calling 流程4.1 环境准备与基础调用框架我以 Python 为例用 OpenAI 风格的接口来演示。你需要准备的东西很简单一个能访问模型 API 的 key一个 HTTP 请求库requests 或 httpx以及 Python 3.8 以上环境。如果你用官方 SDK直接 pip install openai 就行。先定义一个最简单的函数比如查天气。为了演示方便我不接真实天气 API而是用一个本地字典模拟数据。函数定义如下def get_weather(city: str, unit: str celsius) - dict: mock_data { 北京: {temperature: 25, condition: 晴}, 上海: {temperature: 28, condition: 多云}, 广州: {temperature: 32, condition: 雷阵雨}, } if city not in mock_data: return {error: f未找到城市 {city} 的天气数据} result mock_data[city].copy() if unit fahrenheit: result[temperature] result[temperature] * 9 / 5 32 result[city] city result[unit] unit return result然后定义函数的 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] } } } ]这个 schema 里我特意把 unit 设为可选并给了 enum 限定。这样模型在用户没提单位时就不会乱填用户提了华氏度它也能正确识别。4.2 定义函数 schema 的五个关键细节上面那个 schema 看起来简单但有几个细节如果没处理好后面会出问题。第一个细节是 description 要写“使用时机”。我见过很多人只写“查询天气”结果模型在用户问“明天天气”时也调这个函数但函数只返回当前天气导致答非所问。所以我在 description 里明确写了“当前天气状况”并补充“如果用户问的是历史天气或未来预报不要使用此函数”。这样模型就会在遇到预报类问题时选择不调用或者调用其他函数。第二个细节是参数描述要具体。city 参数的描述我写了“例如北京、上海、广州”这能帮助模型理解参数格式。如果参数是日期描述里最好写“格式为 YYYY-MM-DD例如 2024-06-01”。如果参数是枚举把可选值列在 enum 里同时在描述里也提一下双保险。第三个细节是 required 要准确。只把真正必须的参数放进 required。如果把可选参数也放进去模型每次都得填反而增加出错概率。比如 unit 有默认值就不该 required。第四个细节是参数类型要严格。数字就是 number 或 integer不要用 string 代替。布尔就是 boolean。数组要指定 items 类型。对象要指定 properties。类型不严格的话模型可能返回 “25” 而不是 25你的函数处理起来就得多做类型转换。第五个细节是函数数量要控制。一次传给模型的函数不要太多一般建议不超过 10 到 20 个。函数太多会让模型在决策时混淆也增加 token 消耗。如果确实有很多功能可以按场景分组每次只传当前场景相关的函数。4.3 发起请求与解析模型返回的调用指令定义好函数和 schema 后就可以发起请求了。下面是一个完整的调用示例import json from openai import OpenAI client OpenAI(api_key你的key) def chat_with_tools(user_message: str): messages [{role: user, content: user_message}] response client.chat.completions.create( modelgpt-4o, messagesmessages, toolstools, tool_choiceauto ) message response.choices[0].message if message.tool_calls: for tool_call in message.tool_calls: function_name tool_call.function.name arguments json.loads(tool_call.function.arguments) print(f模型请求调用{function_name}参数{arguments}) if function_name get_weather: result get_weather(**arguments) else: result {error: f未知函数 {function_name}} messages.append(message) messages.append({ role: tool, tool_call_id: tool_call.id, content: json.dumps(result, ensure_asciiFalse) }) final_response client.chat.completions.create( modelgpt-4o, messagesmessages, toolstools ) return final_response.choices[0].message.content return message.content这段代码里有个关键点tool_choice 参数。设为 “auto” 时模型自己决定调不调函数。你也可以设为 “required” 强制模型必须调某个函数或者指定具体函数名。在调试阶段我建议先用 “auto”观察模型的行为是否符合预期。另一个关键点是消息顺序。模型返回的 assistant 消息包含 tool_calls要先 append 到 messages 里然后再 append tool 角色的结果消息。顺序反了模型会报错。tool_call_id 必须和请求里的 id 对应上否则模型不知道这个结果是回应哪个调用的。4.4 执行函数并回传结果的完整代码把上面的片段串起来就是一个可运行的完整流程。我再补充几个实用细节。第一参数校验。模型返回的参数不一定完全符合你的预期比如 city 可能带了空格或者 unit 传了 “C” 而不是 “celsius”。在调用函数前加一层校验和清洗def safe_call(function_name, arguments): if function_name get_weather: city arguments.get(city, ).strip() unit arguments.get(unit, celsius).lower() if unit not in [celsius, fahrenheit]: unit celsius return get_weather(city, unit) return {error: f未知函数 {function_name}}第二异常处理。函数执行可能抛异常比如网络请求超时、数据库连接失败。用 try-except 包住把异常信息转成结构化错误回传try: result safe_call(function_name, arguments) except Exception as e: result {error: str(e)}第三结果大小控制。如果函数返回的数据很大比如一个长列表直接回传会占用大量 token。我的做法是只回传关键字段或者做分页。比如查订单列表只返回前 10 条和总数模型需要更多再调一次。第四日志记录。每次函数调用都记一条日志包括函数名、参数、返回结果、耗时。这在排查问题时非常有用。我遇到过模型反复调同一个函数但参数一直不对的情况看日志才发现是某个参数的描述有歧义。4.5 多函数协作与并行调用的处理当你有多个函数时模型可能一次返回多个调用请求。比如用户说“帮我查一下北京天气然后给张三发封邮件提醒他带伞”模型可能同时调 get_weather 和 send_email。你的代码需要遍历所有 tool_calls分别执行然后把所有结果都 append 到 messages 里。并行调用时要注意执行顺序。如果函数之间有依赖关系比如先查用户 ID 再查订单模型通常会分两轮调用第一轮拿到用户 ID 后再发起第二轮。但如果模型判断错了依赖关系同时发起了两个有依赖的调用你的代码可能会因为缺少前置数据而失败。这种情况下可以在函数描述里写清楚依赖关系比如“调用此函数前需要先获取 user_id”。还有一种情况是模型发起了多个调用但其中一个失败了。我的处理方式是成功的正常回传结果失败的也回传错误信息让模型自己决定是重试还是告知用户部分失败。不要因为一个失败就丢弃所有结果那样模型会困惑。5. 踩坑实录那些文档里不会写的经验5.1 模型不调用函数怎么办这是最常见的问题。你定义好了函数用户也问了相关的问题但模型就是不用函数直接用自己的知识回答。原因通常有三个。第一个原因是函数描述不够明确。模型不知道这个函数是干什么的或者不知道什么时候该用。解决办法是把 description 写得更具体明确使用场景。比如不要写“查询天气”写“当用户询问某个城市的实时天气、温度、是否下雨、湿度、风力等信息时使用此函数获取准确数据。不要依赖模型自身知识回答天气问题因为天气数据是实时变化的。”第二个原因是提示词里没有引导。你可以在 system message 里加一句“当需要实时数据或执行操作时优先使用提供的函数不要凭记忆回答。”这能提高模型调用函数的意愿。第三个原因是模型能力差异。不同模型对 function calling 的支持程度不一样。有些小模型可能对函数定义的理解不够好这时候要么换模型要么在提示词里给更详细的示例。5.2 参数填错或漏填的排查思路参数问题是第二常见的坑。模型可能把城市名填成省份把日期格式搞错或者漏掉必填参数。排查时我一般按这个顺序看。先看参数描述是否清晰。如果 city 参数只写“城市”模型可能填“北京市”也可能填“北京”还可能填“朝阳区”。把描述改成“城市名称使用常用简称例如北京、上海、广州不要带‘市’字”就能减少歧义。再看 enum 是否完整。如果 unit 参数只允许 celsius 和 fahrenheit但模型返回了 “C”说明 enum 没起作用。检查一下 schema 里 enum 的写法是否正确有些 SDK 对 enum 的支持有差异。然后看 required 是否合理。如果某个参数其实有默认值但被放进了 required模型每次都得填填错的概率就增加了。把它从 required 里移出来在函数代码里处理默认值。最后看模型版本。不同版本的模型对参数抽取的准确率有差异。如果某个版本频繁出错可以试试升级或降级模型版本。我在实际项目里遇到过某个模型版本对中文城市名识别不好换成另一个版本就正常了。5.3 函数返回结果太长导致上下文爆炸这个问题在查询类函数里特别常见。比如用户问“帮我查一下最近一个月的订单”函数返回了 500 条记录每条记录有 20 个字段整个 JSON 几万 token直接把上下文撑爆。我的处理方式是分层返回。第一次只返回摘要比如“最近一个月共有 500 笔订单总金额 12 万元其中已完成 450 笔待处理 50 笔。如需查看具体订单请指定日期范围或订单状态。”如果用户继续追问再调函数返回更详细的数据。另一种方式是分页。函数接受 page 和 page_size 参数每次只返回一页。模型需要更多数据时会再调一次。但要注意模型可能不知道还有更多数据所以在返回结果里要带上 total_count 和 has_more 字段并在函数描述里说明分页机制。还有一种方式是字段过滤。函数接受 fields 参数只返回用户关心的字段。比如用户只问订单金额就只返回 amount 字段不要返回整个订单对象。5.4 多轮对话中函数调用历史的管理多轮对话里函数调用的请求和结果都会留在对话历史里。如果对话轮次多了历史会越来越长。我一般这样管理。保留最近 5 到 10 轮的完整对话包括函数调用。更早的对话做摘要比如把“用户问了北京天气模型调了 get_weather返回晴 25 度”压缩成“用户询问过北京天气结果为晴 25 度”。摘要可以由模型生成也可以手动写规则。如果某个函数调用的结果已经不再需要比如用户已经得到了答案并转向新话题可以把那轮函数调用的详细结果替换成简短摘要释放上下文空间。还有一种做法是只保留函数调用的关键信息比如函数名和主要参数不保留完整返回结果。但这样模型在后续对话中可能无法引用之前的详细数据需要根据场景权衡。5.5 安全性考量哪些函数不该暴露给模型function calling 给了模型执行操作的能力这也意味着风险。有几类函数我建议谨慎暴露。第一类是涉及资金操作的。比如转账、支付、退款这些函数如果被模型误调用后果很严重。如果确实需要一定要加二次确认让模型先返回调用请求由用户明确确认后再执行。第二类是删除类操作。比如删除文件、删除记录、清空数据这些操作不可逆。我的做法是把删除函数设计成“标记删除”实际执行的是软删除数据还在只是标记为已删除。这样即使模型误调也能恢复。第三类是发送类操作。比如发邮件、发短信、发消息这些操作一旦执行就收不回来。同样建议加确认机制或者限制发送范围比如只能发给用户自己。第四类是权限相关的。比如修改密码、修改权限、授权访问这些函数不应该让模型直接调用应该走独立的验证流程。一个通用的原则是读操作可以放开写操作要加确认删除和资金操作要严格限制。函数描述里也可以加一句“此操作不可逆请确认后再调用”提醒模型谨慎。6. 进阶技巧让 function calling 更稳更高效6.1 用 few-shot 示例提升调用准确率如果模型在某些场景下总是调错函数可以在 system message 里加几个示例。比如用户北京今天热吗 助手调用 get_weather参数 city“北京”unit“celsius”用户帮我看看上海明天会不会下雨 助手调用 get_weather_forecast参数 city“上海”date“2024-06-02”这些示例不需要太长两三个就够。关键是覆盖容易混淆的场景让模型看到正确的调用方式。我试过在一个分类场景里加示例准确率从 85% 提升到了 96%。6.2 函数粒度的取舍大函数还是小函数设计函数时一个常见的纠结是该做一个大而全的函数还是拆成多个小函数大函数的好处是调用次数少模型决策简单。比如一个 query_data 函数接受 table、filters、fields、limit 等参数什么查询都能干。但坏处是参数复杂模型容易填错而且函数内部逻辑会变得很臃肿。小函数的好处是每个函数职责单一参数简单模型容易调对。比如 get_user_by_id、get_order_by_user、get_product_by_category 分开。坏处是函数数量多模型决策时可能选错。我的经验是按业务实体拆分每个实体一个函数但函数内部支持多种查询方式。比如用户相关就一个 get_user 函数参数里支持 id、email、phone 等多种查询方式但只返回一个用户。这样既不会函数太多也不会参数太复杂。6.3 缓存与去重避免重复调用模型有时候会重复调用同一个函数参数也一样。比如用户问“北京天气”模型调了一次 get_weather拿到结果后可能又调一次。这浪费 token 也浪费时间。我的做法是在调用层加一个简单的缓存。用函数名加参数序列化后的字符串作为 key缓存结果。如果模型再次调用相同函数相同参数直接返回缓存结果不再执行。缓存有效期根据数据实时性决定天气数据可以缓存 5 分钟用户资料可以缓存 1 小时。但要注意缓存不能影响模型的决策。如果模型因为缓存而拿到了旧数据可能会给出错误答案。所以缓存时间要合理并且在返回结果里可以带上时间戳让模型知道数据的新鲜度。6.4 监控与日志知道模型在调什么上线之后监控是必不可少的。我一般记录这几个指标函数调用次数、成功率、平均耗时、参数分布、错误类型。函数调用次数能看出哪些功能被用得最多。成功率低说明函数定义或实现有问题。平均耗时长说明函数内部有性能瓶颈。参数分布能看出模型填参数的倾向比如是不是总把城市填成省份。错误类型能帮你快速定位问题是参数错误、网络超时还是数据不存在。日志里我还会记录完整的对话上下文方便复现问题。但要注意脱敏不要把用户隐私信息写进日志。6.5 从 function calling 到 agent 的演进路径function calling 是构建 agent 的基础能力。当你有了几个稳定的函数之后可以逐步扩展成更复杂的 agent。比如加一个规划函数让模型先制定步骤再逐步执行。或者加一个反思函数让模型检查自己的调用结果是否合理。我自己的演进路径是这样的最开始只有一个查询函数后来加了操作函数再后来加了多步规划现在是一个能处理多轮复杂任务的 agent。每一步都是在上一版稳定运行之后再扩展不要一上来就搞大而全的设计。一个实用的建议是先把一个场景做深做透确保 function calling 在这个场景下稳定可靠再考虑扩展到其他场景。我见过太多项目一开始就定义几十个函数结果每个都不稳定最后整个系统没法用。7. 我个人在实际操作中的几点体会function calling 这个能力刚上手时会觉得多了一层转发很麻烦但用熟了之后会发现它其实是把不确定性从“模型输出格式”转移到了“函数实现逻辑”上。格式的不确定性你没法控制但函数逻辑是你自己写的可控性高得多。我踩过最大的坑是在函数描述上偷懒。早期我觉得函数名起清楚就行了描述随便写写。结果模型经常在不该调的时候调该调的时候不调。后来我把每个函数的描述都当成给新人的说明书来写明确写清楚“什么时候用”“什么时候不用”“参数怎么填”“返回什么”问题就少了很多。另一个体会是不要试图让模型做太多决策。函数调用的决策链越短越好。如果一个任务需要模型连续调五个函数才能完成中间任何一步出错都会导致整体失败。更好的做法是把一些固定流程封装成一个函数让模型只调一次。比如“下单”这个操作内部可能涉及查库存、锁库存、生成订单、扣款这些都在一个函数里完成模型只需要调 create_order 就行。最后一点是关于测试。function calling 的测试不能只测函数本身还要测模型在各种输入下的调用行为。我一般会准备一组测试用例包括正常输入、边界输入、模糊输入、多意图输入然后观察模型的调用决策是否符合预期。这个测试集要持续维护每次改函数定义或换模型版本都跑一遍。这个内容后续还可以这样扩展把 function calling 和流式输出结合起来让模型在调用函数的同时逐步输出思考过程或者把多个函数调用编排成工作流用状态机管理执行顺序又或者把函数调用结果缓存起来做离线分析优化函数设计。这些方向我都还在摸索有机会再单独写。