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

Tool Calling 的实现细节——Agent 如何决定调用哪个工具

一、一个让我意识到问题所在的测试案例

上篇博客写完之后,我以为 GraphRAG 检索模块接入 Agent 的工作已经基本完成了——工具封装成了@tool,传参是list[str],返回的是序列化后的自然语言描述。逻辑上没有任何问题。

然后跑了一个测试:

用户输入:"我妈最近在吃华法林,昨天感冒了,我给她买了点芬必得,能一起吃吗?"

期望行为:Agent 提取出["华法林", "布洛芬"](芬必得应归一化为布洛芬),调用query_drug_graph,图谱返回 HIGH 级冲突,输出阻断结论。

实际发生的:Agent 在 Thought 阶段写了"我需要查询华法林和芬必得之间的冲突关系",然后触发了工具调用——但传入的参数是["华法林", "芬必得"],而不是["华法林", "布洛芬"]

"芬必得"在图谱的别名索引里是有的,所以这次查询侥幸成功了。但这暴露了一个我没预料到的问题:工具调用的参数内容,完全由 LLM 在 Thought 阶段决定,而 LLM 的决策受工具描述、System Prompt 和上下文共同影响。任何一个环节写得不够严格,传进来的参数都可能是错的。

在普通的 QA 应用里,工具调用参数错了,最多是回答质量差一点。但在用药安全场景里,工具调用漏传了"妊娠期"这个状态标签,系统就会漏掉"妊娠期禁用布洛芬"这条规则——这不是体验问题,是安全问题。


二、Tool Calling 在 ReAct 里的角色

先理一下 Tool Calling 在我们系统里的具体位置。

ReAct 的每一轮循环是这样的:

Thought: [LLM 写出推理过程,决定下一步做什么] Action: [选择调用哪个工具,生成调用参数] Observation: [工具执行,返回结果] Thought: [基于结果决定是否继续调用工具,或输出最终结论]

Tool Calling 发生在 Action 这一步。LLM 要做两个决策:

  1. 要不要调用工具(还是直接回答)
  2. 调用哪个工具,传什么参数

这两个决策都是由 LLM 根据上下文"推理"出来的,不是硬编码的。这就意味着它们都可能出错。

在目前的系统里只有一个工具——query_drug_graph,所以"调用哪个工具"这个问题暂时不存在,但"传什么参数"的问题非常真实。


三、工具描述(Docstring)是工具调用稳定性的第一道门

LangChain 的@tool装饰器会把函数的 docstring 作为工具描述暴露给 LLM。LLM 读这个描述来判断:这个工具适合解决什么问题、什么时候应该调用它、参数应该传什么。

我第一版的工具描述是这样的:

@tool def query_drug_graph(entities: list[str]) -> str: """ 查询医药知识图谱,检测给定药物之间是否存在冲突。 参数: entities: 需要检测的药物列表,例如 ["布洛芬", "华法林"] """

这个描述有两个问题:

问题一:只说了"药物",没提患者状态和食物。LLM 读到"药物列表",就只会传药物,把"妊娠期"这样的患者状态标签完全漏掉。我在博客二里已经提到这个坑,但当时以为在 System Prompt 里加一条提醒就够了——实测下来不够。工具描述本身不说明,LLM 在 Thought 阶段就不会把状态标签纳入"该传什么"的考量。

问题二:示例参数太简单,没有覆盖患者状态的格式。["布洛芬", "华法林"]这个例子暗示了"只传药物名"的使用方式,强化了错误行为。

修改后的工具描述:

@tool def query_drug_graph(entities: list[str]) -> str: """ 查询医药知识图谱,检测给定实体(包括药物、患者状态标签、食物)之间是否存在 配伍禁忌、用药禁忌或药食冲突。 重要:必须同时传入以下三类已识别实体,不得遗漏: 1. 药物名称(通用名或已归一化的商品名),例如:"布洛芬"、"华法林" 2. 患者状态标签(从标准标签集合中选取),例如:"妊娠期"、"胃溃疡"、"肾功能不全" 3. 患者提到的相关食物,例如:"西柚" 遗漏患者状态标签会导致系统漏报人群禁忌,在医疗场景下属于严重错误。 参数: entities: 实体列表,例如 ["布洛芬", "华法林", "妊娠期", "西柚"] 返回: 结构化的冲突检测结果,包含风险等级、机制描述和处置建议 """ result = search_conflicts(entities) return serialize_for_llm(result)

改完之后重跑之前那个测试用例,这次 Thought 阶段的推理变成了:"用户描述了患者在服用华法林,现在想加用芬必得(布洛芬)。我需要查询华法林和布洛芬之间的冲突,同时注意患者目前没有提到特殊状态标签……"

工具参数传入了["华法林", "布洛芬"](归一化正确),图谱返回了 HIGH 级 DDI 冲突,输出了阻断结论。这才是预期行为。

一个经验:工具描述里的措辞不是文档,是对 LLM 的指令。写工具描述要像写 System Prompt 一样认真,而不是像写注释一样随意。


四、System Prompt 的约束层——工具描述的兜底

工具描述能解决大部分问题,但不能解决所有问题。有些边界情况需要在 System Prompt 里做更细粒度的约束。

我目前 System Prompt 里与 Tool Calling 相关的部分是这样的:

## 工具调用规范 你被赋予了 query_drug_graph 工具,用于查询药物、患者状态和食物之间的冲突关系。 你必须严格遵守以下规则: ### 何时调用工具 - 用户提及任意药物名称时,必须调用 query_drug_graph,不得跳过 - 即使你认为自己"知道"答案,也必须通过工具调用来获取图谱的确定性依据 - 禁止仅凭训练数据中的药理知识直接回答用药安全问题 ### 传参规范 1. 药物名称必须使用通用名或已知商品名,禁止使用"某某类药物"等泛指描述 - 正确:["布洛芬", "华法林"] - 错误:["非甾体抗炎药", "抗凝药"] 2. 患者状态标签必须从标准集合中选取(见下方列表),不得自造标签 3. 若患者描述中有食物信息(如"吃了西柚"),必须一并传入 4. 若某实体无法归一化为标准名称,使用占位符 __UNRECOGNIZED__:{原始描述} ### 标准患者状态标签集合 胃溃疡 / 妊娠期 / 哺乳期 / 肾功能不全 / 肝功能不全 老年患者 / 儿童 / 磺胺类过敏 / 青霉素过敏 ### 工具调用失败时的处理 若工具返回报错或空结果,不得自行推断,必须输出: action_type: FALLBACK,并标注"图谱查询异常,建议咨询专业医师" ### 禁止行为 - 禁止在未调用工具的情况下输出任何用药建议或冲突判断 - 禁止将工具返回的"未发现冲突"解读为"用药安全"(图谱覆盖范围有限)

这里有几个设计细节值得说明:

为什么要禁止"不调工具直接回答"?LLM 训练数据里有大量药理知识,它完全有能力直接回答"布洛芬和华法林有冲突"。但这违反了我们的核心架构原则——用药安全判断必须来自知识图谱,不能来自 LLM 的参数记忆。所以必须在 Prompt 层面明确禁止。

__UNRECOGNIZED__占位符是怎么回事?如果 LLM 碰到一个它无法归一化的药名(比如某个不常见的进口药),我需要它把这个信息传回来,而不是静默地丢弃。占位符格式让后续逻辑可以解析出哪些实体是未识别的,然后在unrecognized字段里返回给前端。

"未发现冲突 ≠ 用药安全"这条为什么要写进去?因为图谱覆盖范围有限,返回"未发现冲突"只意味着"图谱里没有记录这个冲突",不代表真的没有冲突。如果 LLM 把这两个意思等同,就会给出错误的安全保证。这条约束迫使 LLM 在输出"未发现冲突"时,措辞是"根据当前知识库未发现已知冲突",而不是"可以放心服用"。


五、调试 ReAct 循环:三类需要特别处理的失败模式

失败模式一:参数类型正确但语义错误

现象:工具调用触发了,参数格式也是list[str],但内容是泛化的类型描述而不是具体实体名。

复现用例:用户输入"我在吃降压药和血液稀释剂,可以一起用吗?"

实际参数:["降压药", "血液稀释剂"]

期望参数:这种情况 LLM 应该识别出信息不足,触发追问,而不是把"降压药"当成实体传入图谱(图谱里当然查不到这个词)。

根因:工具描述里没有明确说明"泛指描述"和"具体实体名"的区别,LLM 在信息不足时倾向于"尝试调用工具"而不是"先追问"。

解决方案:在 System Prompt 里加了这条:

若用户描述的药物是泛指类型(如"降压药"、"消炎药"、"血液稀释剂")而非具体药名, 禁止直接调用工具。应先输出追问: "请问具体是哪种[药物类型]?例如,常见的[药物类型]包括……"

加了这条约束之后,LLM 在遇到"降压药 + 血液稀释剂"这类输入时,会先输出追问而不是尝试查图谱。

失败模式二:应该调工具但没调

现象:用户明确提到了药物,但 LLM 直接在 Thought 之后给出了结论,跳过了 Action 步骤。

复现用例:用户输入"感冒了,吃点泰诺行吗?"(只有一种药,没有明显的配伍冲突场景)

LLM 推理:"用户只提到了一种药物,未与其他药物联用,因此无需检索冲突……"然后直接给出了"泰诺是常用感冒药,正常剂量下通常是安全的"之类的回答。

问题:这违反了"任何药物查询都必须走图谱"的规则。用户没提其他药,但可能同时在吃其他药;用户没提患者状态,但可能有胃溃疡或肝病。跳过图谱查询就意味着跳过了这些隐患的检测。

解决方案:在 System Prompt 里把触发条件写得更宽:

只要用户输入中包含任意药物名称(包括仅提及单一药物的情况), 必须调用 query_drug_graph,同时传入已知的患者状态信息。 若用户未提供患者状态,仍需调用工具(传入已知药物), 并在结论中注明"未获得患者完整信息,以下判断基于有限数据"。

失败模式三:循环次数失控

现象:Agent 在 Thought 阶段反复判断"信息还不够充分",触发多次工具调用,每次传入的参数有细微差异,最终延迟暴增,有时甚至超过前端超时限制。

复现用例:用户输入"我在吃好几种药,感觉有点不放心"(没有具体药名)。

LLM 行为序列:

Thought: 用户提到在吃多种药,但未说明具体药名,我需要追问 Action: [触发追问,但因输出格式问题追问没有正常传给前端] Thought: 没有收到用户回复,或许我可以先查询常见药物组合... Action: query_drug_graph(["某药A", "某药B"]) # 凭空猜测 Thought: 结果不够确定,再查一次...

根因:追问失败(输出格式问题)导致 Agent 陷入了一个"信息不足 → 尝试行动 → 结果不满足 → 再次行动"的死循环。

解决方案有两层:

一是工程层面加硬限制——在 LangChain 的 Agent 配置里设置max_iterations=5,超过就强制终止并返回 FALLBACK 状态。

agent_executor = AgentExecutor( agent=agent, tools=tools, max_iterations=5, # 硬限制循环次数 max_execution_time=25, # 秒级超时(留出前端 30s timeout 的余量) handle_parsing_errors=True, # 输出格式解析失败时不崩溃 verbose=True # 开发阶段开启,生产阶段关闭 )

六、一个有意思的发现:工具调用的"心理学"

在做了大量测试之后,我注意到一个有意思的规律:

LLM 在 Thought 阶段的措辞,能预测它即将调用工具的参数质量。

如果 Thought 里写的是:

"患者目前服用华法林,现在想加用布洛芬(芬必得),我需要查询这两种药物之间的相互作用, 同时需要考虑患者是否有特殊状态……"

那么接下来的工具参数通常是准确的。

如果 Thought 里写的是:

"用户在问用药安全问题,我来查一下……"

那么接下来的参数很可能是泛化的、不完整的。

这个规律让我想到一个调试思路:在开发阶段,可以把verbose=True,然后用 Thought 的质量来评估 System Prompt 的有效性——不用等到工具调用完成,看 Thought 阶段 LLM 的推理是否"抓住了问题的关键",就能大致判断这次工具调用会不会出问题。

这也验证了 ReAct 范式的一个优势:显式的推理链不只是帮助 LLM 思考,也帮助开发者调试。

http://www.jsqmd.com/news/760078/

相关文章:

  • YOLO训练入门(下)学习笔记(第四集)
  • 【AI模型】模型量化技术详解
  • 大模型代码生成与代理任务评估框架及优化实践
  • 2026年5月专业靠谱的全屋定制TOP5:基于全案交付与口碑验证的权威榜单 - 商业科技观察
  • 告别手动测试:深入解读Vector CANoe LIN一致性测试模块(ISO17987/J2602标准覆盖哪些内容?)
  • 2026树枝粉碎机品牌评分出炉!博尚9.8分领跑,全能配置+高性价比,市政/物业首选品牌 - 会飞的懒猪
  • 大模型输入的“灵魂”步骤:Embedding如何让0、1、2变得有“意义”?
  • 2026年5月全屋定制品牌权威盘点:精工智造如何定义家的品质 - 商业科技观察
  • 前端学习打卡 Day1:从0到1认识前端与HTML基础结构
  • 大语言模型逻辑验证框架:原理、实现与应用
  • 2026年5月全屋整装十大公认品牌——选对品牌,装好一个家 - 商业科技观察
  • 超表面技术在水下定位系统中的应用与优化
  • 前端已死?2026年,转型AI Agent工程师才是你的“续命”良方!
  • 基于Flutter的OpenClaw桌面控制台开发:架构设计与跨平台实践
  • 4J36低膨胀合金有哪些?符合国标的4J36低膨胀合金厂商推荐 - 品牌2026
  • CANoe诊断测试避坑指南:ISO 15765-2网络层时间参数(N_Ar, N_As, N_Br...)详解与实战监控
  • 2026年5月厨柜定制选购白皮书:从物理参数到精工交付的品质解码 - 商业科技观察
  • 利用Taotoken多模型能力为嵌入式系统设计文档寻找最优的生成模型
  • 告别Docker依赖!用tileserver-gl-light在Windows/Mac上5分钟搭建本地地图服务
  • 不只是建模:手把手教你用TCAD为GaN功率器件做‘虚拟实验’(DOE与参数校准篇)
  • GitHub汉化插件:3分钟告别英文界面,让中文开发者更高效
  • 别再手动配IP了!用Cloud-Init在OpenStack上5分钟搞定CentOS 7云主机初始化(附完整配置流程)
  • 用快马ai快速构建你的第一个android天气应用原型
  • 2026年5月橱柜定制品牌十大排名:金牌家居领跑高端厨房定制 - 商业科技观察
  • 【连续11届稳定EI检索、快至3个月】第十二届先进制造技术与应用材料国际学术会议(ICAMMT 2026)
  • 高效散热调校:Fan Control终极风扇控制软件深度解析
  • 2026园林树枝粉碎机厂家品牌排名 - 会飞的懒猪
  • 利用Taotoken CLI工具一键完成团队开发环境统一配置
  • AI赋能数字攻击面评估:MCP服务器实现自动化安全审计
  • VIEWE 4英寸圆形HDMI触摸屏开发与应用指南