大模型Agent工具调用实战:Function Calling原理与避坑指南 1. 为什么 Agent 需要“手脚”聊到 Agent很多人一开始会有一个误解觉得只要把大模型接上提示词它就能自己完成所有事情。实际上模型再聪明它本质上仍然是一个“大脑”而且是一个有信息边界的大脑——模型训练时看到的数据是固定的它不知道此时此刻的外面天气、不知道你的私有数据库里存了什么、更不可能替你去真的下单、发邮件、操作某个系统。它能做的只是在它的知识范围里“想”和“说”。然而在一个真实的自动化流程里光会想和说是远远不够的。这个时候就需要给 Agent 装上“手脚”也就是工具调用。所谓工具调用就是让大模型在生成回复的过程中主动声明“我需要调用某个外部函数”然后由我们的程序去真正执行这个函数再把执行结果回传给模型让模型基于真实结果继续推理。这样 Agent 就不再是一个只会输出文字的聊天机器人而是一个能查天气、能查数据库、能调用业务接口、能操作文件、能控制软件的真正常规意义上的“智能体”。我见过很多刚开始接触 Agent 的开发者最常问的问题是为什么不能让模型直接把函数结果写进回答里原因很简单模型没有能力自己去执行函数。它只能生成文本而“执行”这个动作发生在模型之外。工具调用的核心设计就是把“决策”和“执行”分开模型负责判断什么时候需要工具、需要哪个工具、参数是什么程序负责真正调用工具并把结果交给模型做下一步判断。这种分工方式是当前大模型应用层最实用的一种工程范式。这篇内容适合已经开始接触大模型应用开发、想从“聊天机器人”跨到“Agent 应用”阶段的开发者。假设你已经了解基本的 API 调用方式知道 prompt 是什么但对 Function Calling 还没有完整实践过那么这篇内容可以帮你把工具的整套流程跑通并且避开那些我在实际开发中踩过的坑。2. 工具调用的核心设计思路2.1 模型不执行函数只“声明意图”在继续之前我们先去理解一下工具调用的底层设计逻辑。很多人第一次看到 Function Calling 结果时会被返回的 JSON 结构迷惑模型返回的不是最终答案而是一个类似“我要调用 search_products参数是{keyword: 手机 page: 1}”的结构。新手往往会觉得这算什么回答其实这正是工具调用最核心的一步——模型在表达一种“意图”。模型通过训练学会了这样的行为当它遇到需要外部信息或外部操作的问题时它不会直接硬编一个答案而是输出一个结构化的函数调用请求。程序拿到这个请求后负责做两件事第一验证这个请求是否合法第二真正去执行对应的函数。执行完毕后把结果以一条工具消息的形式再传给模型。模型看到这个结果之后才能继续生成最终回复。这种设计有一个极大的好处模型不需要真的理解外部世界的细节它只需要学会“在合适的时候调用合适的工具”。而外部世界的能力比如查询数据库、调用 API、读写文件全部由我们的代码负责。这也意味着模型能力的边界取决于我们的工具集的丰富程度。你给 Agent 提供了多少个可靠的工具它就相当于多了多少种“超能力”。2.2 工具定义的本质是一份“说明书”每个工具要能被模型正确使用必须有清晰、无歧义的“说明书”。在大多数平台上这个说明书是以 JSON Schema 的形式传给模型的。一个工具定义通常包括名称、描述、参数结构。其中最容易低估的就是“描述”。我举个实际例子假设你要让 Agent 能查询订单状态你定义了一个工具叫 query_order_status参数是 order_id。如果你在描述里只写“查询订单状态”模型可能不清楚这个 order_id 是什么格式、是不是必填、需不需要配合用户提供别的信息。但如果你写“查询订单状态用于根据用户提供的订单编号获取最新物流信息订单编号格式为 13 位数字通常以字母 SO 开头”模型就能更准确地把用户的话映射到这个工具上。描述越精准模型选错工具的概率越低。这里还有一个容易被忽略的点参数描述也同样重要。比如参数 order_id 如果不说明格式模型很有可能把用户说的“我那个单号是 1234”直接当作 order_id而实际上你需要的是完整单号。当我们把参数描述写成“完整的 13 位订单编号不含空格和分隔符”时模型就有机会反问用户补充信息而不是将一个残缺值传进工具。2.3 工具选择从“开关”到“路由”当工具数量很少时模型选择工具是一件很轻松的事。但一旦工具数量增加到十几个甚至几十个模型的选择准确率就会明显下降。这就像一个人手里只有两个按钮时按错概率很低但面对一整面墙的按钮按错就很正常了。因此在设计 Agent 的工具层时有一个常见实践对工具做分组或路由。你可以先让模型判断用户意图属于哪个域比如“订单域”“库存域”“售后域”然后只把对应域内的工具传给模型而不是一次性把所有工具都塞进上下文。这么做的好处有两个一是减少模型的选择范围提高准确率二是减少上下文里的 token 消耗因为每个工具定义都会占据一定长度的上下文空间。如果工具数量特别多还可以考虑用召回的方式先把所有工具的描述做向量化用户问题来临时先做一次相似度检索挑选出最相关的 N 个工具再传给模型。这个套路实际项目中已经有不少团队在用本质上是把“工具选择”当成一个检索问题来解。3. 核心细节解析与实操要点3.1 工具定义 Schema 的结构与写法先来看一个标准的工具定义长什么样。下面这段是基于 OpenAPI 风格的工具描述我用在项目里最常见的“查询天气”举例{ type: function, function: { name: get_weather, description: 根据城市名称查询实时天气信息适用于用户询问天气、温度、降雨、风力等情况。, parameters: { type: object, properties: { city: { type: string, description: 城市中文名称例如北京、上海、广州 }, unit: { type: string, enum: [celsius, fahrenheit], description: 温度单位celsius 为摄氏度fahrenheit 为华氏度, default: celsius } }, required: [city] } } }这里有几个关键细节值得注意。第一所有字段的名称要用英文小写加下划线不要用中文也不要使用特殊字符。虽然某些平台可能允许更宽松的命名但保持一致的风格能减少模型生成错误参数名的概率。第二参数的 type 必须写对。如果参数是一个对象而不是字符串不要偷懒把它写成 string。模型是从 Schema 学习参数的结构的写错了它就会按错误的方式来生成。第三required 数组要明确列出所有必填参数。不要把必填参数仅仅写在描述里模型不一定能稳定地记住“描述里说必填”这件事但它在生成工具调用时会严格遵守 required 列表。还有一个常见问题默认值。有些开发者会在参数里写 default认为这样可以省去让模型填值。实际上不同的平台对 default 的处理方式并不一致有些平台会把 default 当作模型可选的依据有些会忽略。我在实践中更倾向于不依赖 default 字段而是在服务端执行工具时兜底处理如果参数缺失由工具函数内部补默认值。这样即使模型没有传某个可选参数程序也能正常执行。3.2 工具注册与调用的完整链路很多初学者会以为工具调用就是把一个函数名告诉模型就够了实际上完整的链路要复杂得多。我按一次真实请求的完整经过来拆解第一步整理工具列表。把当前会话可用的所有工具做成一个数组作为请求的一部分发送给模型。这一步需要维护一个工具注册表统一管理每个工具的名称、描述、参数结构、执行函数、权限要求等信息。第二步模型请求。用户输入先转为一次模型请求此时模型已经知道有哪些工具可用。模型经过推理后返回两种可能一种是直接生成普通文本回复说明它觉得不需要调用工具另一种是返回工具调用请求包含一个或多个工具名称和参数。第三步程序拦截。我们不能把模型的返回直接交给用户而要先检查是否包含 tool_calls。如果包含就进入工具执行阶段。第四步执行工具。根据模型返回的工具名称在注册表里找到对应的执行函数传入解析好的参数执行并拿到结果。这里建议所有工具函数都做好超时控制避免模型正在等待时某个外部 API 卡住。第五步回传结果。把工具执行结果封装成一条 tool 消息和之前的对话历史一起再次发送给模型。模型看到工具结果后生成最终的自然语言回复。这个链路里每一步都有扩展空间。比如第四步和第五步之间你可以设计人工审核节点也可以设计工具结果缓存还可以在工具执行前做参数二次校验。这些都是后话先把基本链路跑通后面再慢慢加复杂度。3.3 多工具并行调用的处理策略当前主流的大模型平台大多已经支持在一条回复里返回多个工具调用请求。举个例子用户问“帮我查一下北京的天气顺便告诉我明天适合穿什么”模型可能会同时返回 get_weather 和 get_clothing_suggestion 两个工具请求。这时程序可以选择串行执行也可以并行执行。我建议的做法是对于相互不依赖的工具尽量并行执行。但在实现时要注意一个细节并行不等于“同时修改共享状态”。如果两个工具会写入同一个文件或同一个数据表并行执行可能带来数据竞争问题。这种情况下要么在工具设计上把写入操作收敛到单一工具里要么做好锁机制。还有一个容易被忽略的点当模型一次返回多个工具调用时其中一个工具的参数可能是基于另一个工具的返回值而定的。举个常见的例子用户说“帮我查一下订单详情然后根据订单里的商品信息给我推荐搭配商品”。模型可能先返回 get_order_detail再返回 recommend_products但 recommend_products 的参数需要依赖 get_order_detail 的执行结果。如果把这两个工具直接并行执行recommend_products 就会因为缺少参数而失败。所以在实际实现时我们需要在收到多个工具调用请求后先做一次依赖判断。简单的方法是如果发现某个工具的参数里有“需要等另一个工具执行后才能获得”的字段就改为串行执行——先执行被依赖的工具把结果注入上下文再让模型生成新的工具调用。这个逻辑听起来简单但我在多个项目里都见过因为强行并行导致的诡异 bug。3.4 工具结果返回的格式设计工具执行完的结果要如何回传给模型也是有讲究的。最关键的原则是工具结果是给模型看的不是给用户看的。很多开发者习惯把工具结果原样抛回比如一个 Python 字典里面 key 是英文缩写value 又是嵌套结构模型很可能看不懂。比较好的实践是在工具函数内部把返回结果整理成简洁、结构化的文本或 JSON必要时附带简要的说明文字。举个例子查询订单状态的工具内部可能返回原始数据但我通常会在工具函数出口做一次格式化变成类似这样的内容订单状态查询结果订单号 SO20240115001当前状态为“已发货”物流公司为某快递运单号为 7730123456789最新节点更新时间为 2024年1月16日 14:23显示快件已到达某个转运中心。这种格式对模型非常友好它不需要去理解原始数据表结构直接就能基于这段文本生成用户可读的回复。但也要注意另一个极端返回内容过长。如果工具结果是一份几万字的日志直接把全部文本塞给模型不仅消耗大量 token还可能让模型“迷失”在无关细节中。这种情况下我建议在工具内部先做摘要只返回关键信息。4. 实操过程实现一个带工具的 Agent4.1 环境准备与依赖选择接下来我们动手做一个简单的 Agent它支持两个工具一个是查天气一个是查当前时间。选择这两个工具是为了把链路演示清楚实际项目中你可以替换成任何业务接口。开发环境我建议使用 Python主要因为生态成熟处理 JSON 这类数据结构非常顺手。如果你是 Node.js 技术栈下面的逻辑完全一样只需要把语法换成你熟悉的语言即可。核心依赖只有两个一个是大模型 SDK另一个是标准库里的 json、datetime。为了不让代码绑定到具体某个平台我会用伪代码风格写你只需要把它映射到自己的大模型客户端上。需要注意的坑请务必先检查你的 SDK 版本是否支持工具调用参数。早期版本很多不支持 tools 参数调用时会静默忽略导致模型永远不返回 tool_calls排查起来非常费劲。升级 SDK 之后再确认一下平台侧的模型是否启用了工具调用能力有些模型需要在请求参数里显式开启。4.2 定义工具注册表我们先定义一个统一的工具注册表。它要同时保存工具的定义信息和执行函数这样发送给模型的工具说明和实际执行的函数就能一一对应不会出现“模型想调用 A 工具程序却不知道 A 是什么”的问题。TOOL_REGISTRY {} def register_tool(schema, func): TOOL_REGISTRY[schema[function][name]] { schema: schema, func: func }然后注册第一个工具查询当前时间。这个工具看似简单但它是验证工具调用链路是否通的最佳工具因为它不需要依赖任何外部 API返回结果稳定。import datetime def get_current_time(timezone_strAsia/Shanghai): # 这里省略时区转换细节默认使用系统本地时间 now datetime.datetime.now() return {time: now.strftime(%Y-%m-%d %H:%M:%S)} register_tool( { type: function, function: { name: get_current_time, description: 获取当前日期和时间适用于用户询问现在几点、今天是几号等场景。, parameters: { type: object, properties: {}, required: [] } } }, get_current_time )第二个工具查询天气。为了让演示更简单我们就用写死的假数据来模拟外部接口的调用实际项目中可以把这段代码替换成真实天气服务商的 API 请求。def get_weather(city): # 真实项目中这里应该调用天气服务商 API并处理超时、错误码等 fake_weather { 北京: 晴气温 -2°C 到 8°C西北风 3 级空气质量良, 上海: 多云气温 6°C 到 12°C东风 2 级空气质量良, 广州: 小雨气温 15°C 到 19°C北风 2 级空气质量优 } if city in fake_weather: return {city: city, weather: fake_weather[city]} return {city: city, weather: 暂未获取到该城市的天气信息} register_tool( { type: function, function: { name: get_weather, description: 根据城市中文名称查询实时天气情况适用于询问天气、温度、出行建议等场景。, parameters: { type: object, properties: { city: { type: string, description: 城市中文名称例如北京、上海、广州 } }, required: [city] } } }, get_weather )这里有个注册表设计的小心得在注册工具时就同时保存 schema 和 func后续无论是构造请求还是执行调用都从同一个注册表取数据避免两处维护导致不一致。如果项目里工具数量很多还可以再加上标签、权限级别、超时时间等元信息让注册表成为一个完整的工具治理中心。4.3 核心循环让模型在“推理”和“执行”之间交替接下来是 Agent 的主循环。这个循环的设计是整个 Agent 的心脏先调用模型检查返回结果里有没有工具调用意图如果有执行工具把结果追加到消息列表里再次调用模型。这个循环会一直持续直到模型不再返回工具调用直接输出最终回复。def run_agent(user_input, messagesNone): if messages is None: messages [ {role: system, content: 你是一个智能助手可以调用工具来获取实时信息。}, {role: user, content: user_input} ] tools [TOOL_REGISTRY[name][schema] for name in TOOL_REGISTRY] max_iterations 5 # 防止死循环 for _ in range(max_iterations): response call_model(messagesmessages, toolstools) assistant_msg response[message] messages.append(assistant_msg) tool_calls assistant_msg.get(tool_calls) if not tool_calls: return assistant_msg[content] # 逐个执行工具调用 for tool_call in tool_calls: fn_name tool_call[function][name] fn_args json.loads(tool_call[function][arguments]) result execute_tool(fn_name, fn_args) messages.append({ role: tool, tool_call_id: tool_call[id], content: json.dumps(result, ensure_asciiFalse) }) return 已达到最大迭代次数请简化问题或稍后再试。 def execute_tool(fn_name, fn_args): if fn_name not in TOOL_REGISTRY: return {error: f未知工具: {fn_name}} func TOOL_REGISTRY[fn_name][func] try: return func(**fn_args) except TypeError as e: return {error: f参数错误: {str(e)}}这里有几个关键参数和设计点需要展开讲。第一max_iterations 必须设置。我在开发初期因为没有设置这个上限遇到过模型在一个问题上反复调用同一个工具陷入死循环的情况。特别是在工具返回内容含义模糊时模型会一直尝试调用工具“再查一次”结果越查越偏。设置一个合理的上限通常是 3 到 8 次是避免资源浪费的最基本手段。第二每个工具调用的 id 要正确回传。在追加 tool 消息时tool_call_id 必须和 assistant 消息里 tool_calls 请求中的 id 对应。如果某个平台要求这个小细节漏掉或者写错模型端会报错或无法正确关联工具与结果。建议在追加消息前打印一下整个消息列表肉眼检查一遍结构。第三参数解析要用 try-except 包裹。模型返回的 arguments 是 JSON 字符串理论上是标准 JSON但偶尔会因为模型生成不规范而出现解析失败。更稳妥的做法是在解析失败时把错误信息返回给模型让它重新生成一次工具调用而不是直接让整个请求崩溃。把这段代码用本地环境跑起来输入“现在几点”你会看到模型调用 get_current_time返回时间然后生成一段自然语言回复。输入“北京天气怎么样”模型会调用 get_weather 并基于返回结果组织回答。这就是一个最基础的“有手有脚”的 Agent。4.4 用日志观察 Agent 的思考过程做 Agent 开发时我强烈建议在开发阶段打开完整的日志输出观察每一次模型请求与响应。你可以打印这样几类日志发出去的 messages 结构、返回的 assistant 消息、识别到的 tool_calls、执行工具后的结果、追加 tool 消息后的完整列表。这一步非常重要因为 Agent 的错误往往不是语法错误而是逻辑链路错误。比如模型明明已经返回了 tool_calls程序却因为解析方式不对把它当成普通文本回复直接返回给了用户又比如工具执行成功但结果回传时消息角色写错模型失去上下文。这些隐蔽问题靠单步调试很难发现但一打开完整日志问题很快就能定位。我在一个项目里遇到过这样的问题模型连续调用三次工具每次都成功但最终回复依然是“抱歉我无法获取信息”。查日志时发现工具结果虽然正确返回了但我在追加 tool 消息时把 role 写成了 assistant导致模型把它当成一条普通的模型回复来看完全没有把它与上文的工具调用关联起来。修复之后整个流程立刻正常了。所以日志不是可选项是 Agent 调试的必需品。5. 常见问题与排查技巧实录5.1 模型就是不肯调用工具这是最常遇到的问题工具定义已经传了prompt 里也写了“你可以调用工具”但模型每次都直接生成普通文本回答。遇到这种情况我一般按照下面顺序排查第一步检查模型是否真的支持工具调用。有些模型或 API 端点不支持 Function Calling即使你传了 tools 参数它也会默默忽略。去确认模型列表里的支持标识或者直接用一个最简单的工具验证一遍。第二步检查 tools 参数是否真正传到了请求里。很多 SDK 封装得很“友好”但如果你用了错误的参数位置比如把 tools 放到了顶层而不是请求体里可能不会报错只是工具请求丢失了。第三步检查工具定义是否符合要求。最常见的问题是这个平台要求 parameters 里必须有 type: object而你的定义里漏了或者工具的 description 为空。某些平台对工具定义的完整度要求很严格缺了字段就整体拒绝解析。第四步换个方式引导。在系统提示词里增加一句“当用户询问到实时信息时请优先调用工具获取数据”。虽然模型理论上是靠工具定义来学习的但提示词在引导模型使用工具方面依然有效果。5.2 工具参数名错误或者多传了参数模型在生成工具调用时偶尔会出现参数名和定义不一致或者多传了一些没有在 schema 里定义的参数。这种问题在工具参数复杂、字段较多时尤其常见。比如 schema 里定义的是 user_id模型却生成了 userId导致函数执行时 TypeError。处理这个问题我推荐两个思路。第一个思路是宽容模式在 execute_tool 里做一层参数名映射把常见变体映射到标准名。第二个思路是把错误信息返回给模型当 TypeError 发生时不要把异常吞掉而是把错误信息整理成“工具调用失败未知参数 userId可用参数为 user_id”作为 tool 消息内容回传。模型看到之后往往会立即自我纠正重新发起一次正确的工具调用。我见过不少团队在工具调用失败时直接放弃回复用户“系统异常”。其实模型是有自我纠错能力的只要我们把失败信息反馈给它它大概率能自己修复参数问题。这是 Agent 开发里非常实用的一招。5.3 工具执行结果太长模型理解不了工具执行返回的结果往往是一个很大的 JSON 对象。如果直接把整个对象抛给模型模型能“看到”但并不代表它能“理解”清楚。特别是在结果层级很深、字段命名很抽象的情况下模型很容易忽略关键信息。我的做法是每个工具函数在返回结果前先用代码把原始数据加工成一段结构化文本。比如查询一个列表时不要直接返回完整列表而是返回“共查询到 42 条记录前 5 条为...其余记录因数量过多已省略”。这种经过裁剪和摘要的结果模型用起来准确率高得多token 消耗也少得多。如果业务上确实需要模型理解完整数据比如让它做全文分析那可以单独设计另一个工具专门接收完整数据并返回分析结果而不是让通用工具承担“查询 分析”两个职责。5.4 工具调用死循环模型在拿到工具结果后可能会因为结果不符合预期而反复调用同一个工具。我见过一个案例模型查询用户订单状态后发现订单已发货但它为了确认“是否真的发货了”连续调用了五次查询工具每次都拿到同一个结果然后才放弃。这种死循环的根源往往不是模型“笨”而是工具结果与用户问题的匹配度不够或者提示词里没有强调“如果工具结果已经足够回答用户就不要再调用工具”。我一般会在系统提示词里明确写“仅在信息不足时调用工具。若工具返回结果已包含足够信息请直接根据结果回答用户。”同时依靠 max_iterations 来做硬性兜底。两个手段一起上死循环问题基本能解决。6. 工具调用的进阶扩展设计6.1 工具权限与安全边界随着 Agent 的能力越来越强工具调用涉及的操作也越来越敏感。比如一个能删除文件、能发邮件、能改数据库的 Agent如果权限控制做得不好后果不堪设想。我建议在工具注册表里增加权限级别字段比如只读、可写、高危操作。在高危工具执行前程序应该插入一个人工确认环节而不是直接执行。例如“确认要向用户发送这封邮件吗请回复确认。”这样虽然牺牲了一些自动化程度但能避免很多不可逆的错误操作。这个设计在一些客服系统中已经被普遍采用称之为“人类审批节点”。另外工具调用过程中要特别留意参数注入问题。不要盲目信任模型生成的参数值尤其是涉及路径、命令、SQL 的字段。举个例子如果工具会执行一段 SQL那么参数里的表名和条件最好做白名单校验否则模型有可能根据用户输入的恶意内容生成一段破坏性 SQL。这一点在把 Agent 接入生产环境时是必须面对的工程问题。6.2 工具结果缓存有些工具的结果在一段时间内不会变化比如基础的天气信息或者某个订单的状态短时间内不变。每次调用都去请求外部 API既增加响应延迟又消耗外部服务的配额。一个简单的缓存方案是在工具注册表里为每个工具配置一个 cache_ttl 字段比如 300 秒。在 execute_tool 时先检查有没有未过期的缓存结果如果有就直接返回缓存的 JSON否则才真正执行函数。缓存的 key 可以用工具名加参数序列化后的字符串来生成保证同一参数的结果可以被复用。需要注意的是写操作类工具绝对不能加缓存。缓存只适用于幂等、结果稳定的查询类工具。把这一点贯彻到注册表设计里可以让缓存机制更安全。6.3 与流式输出的结合当 Agent 执行工具需要一定时间时如果界面一直静默用户会以为系统卡死了。更好的体验是启用流式输出先让模型输出一段“我正在查询天气请稍候”然后执行工具再流式输出最终结果。这样用户的等待感会大幅降低。实现流式的关键是分两段调用模型第一段模型生成可能包含 tool_calls 的内容我们不把文字直接当最终结果而是先缓存并展示或者干脆抑制输出第二段工具执行完毕后再发起一次模型请求用流式方式输出最终回复。有些平台支持在流式过程中同时返回 tool_calls 增量这就需要在解析端做好中间状态的判断。这个扩展实现起来有一定工作量但一旦做好用户体验的差别非常明显。尤其是在面向普通用户的 C 端产品里流式几乎是标配。6.4 让 Agent 学会“拒绝调用工具”并不是所有问题都需要调用工具。有些用户随口说“你好”模型如果硬要去查询一次天气那就会显得很蠢。工具调用的理想状态是该用的时候毫不犹豫不该用的时候坚决不用。这需要系统提示词和工具定义的共同作用。在系统提示词里明确“仅在用户问题涉及实时数据、私有数据或外部操作时才调用工具”同时让每个工具的描述都带有明确的触发条件比如“仅当用户明确询问某城市天气时使用”。描述里增加触发条件是我在实际使用中发现的提高工具调用准确率的有效技巧。你也可以在 Agent 的设计里加入一层轻量前置分类先用一个快速判断模型决定当前问题是否需要工具如果不需要就完全跳过工具传递和调用逻辑。这个方法能减少很多不必要的计算和延迟。7. 写在最后的个人经验做了这么久的 Agent 应用我的一个深体会是工具调用看起来是“配置一堆函数”实际上它是在设计一套“人机协作的接口协议”。模型是决策者不是万事通工具是执行者不是摆设程序是协调者不是旁观者。三者之间的消息结构、参数约定、异常处理共同决定了 Agent 是聪明还是混乱。在实际项目中我建议你从最小闭环开始先接一个工具跑通再规划更多先不做复杂的权限和缓存稳定了再加。等基础链路跑顺了再逐步引入并行调用、缓存、人工审核这些进阶机制。你会发现Agent 质量的提升并不是靠一个魔法参数而是靠对每一个工具调用细节的打磨。最后分享一个小技巧在开发和调试阶段可以准备一组固定的话术比如“现在几点”“北京天气怎么样”“帮我同时查一下上海和广州的天气”。每次改动后用同一组话术跑一遍对比日志差异这样任何回归问题都能第一时间暴露。这个习惯救过我很多次希望也能帮到你。