这个问题问的是工具调用链路,本质上就是在解决一个问题:怎么让大语言模型能准确地去调用外部工具,同时保证整个过程可控、可容错。它不是一个简单的API调用,而是一个从理解用户意图到最终返回结果的完整闭环。
先说前置设计。工具注册这块,我一般会用OpenAI的[[Function Calling]] Schema来定义每个工具,把name、description、parameters都写清楚。这里有个关键点:description一定要写到位,不能太含糊。比如一个查询订单的工具,你要是只写“查询订单”,模型可能就会在用户说“帮我查一下”的时候选它,但实际上用户想查的是物流状态,不是订单信息。所以description要精确描述工具的能力边界,不然模型很容易选错。
接下来是核心流程,我把它分成几步。第一步是意图识别和工具选择。系统Prompt里我会明确告诉模型它的角色是工具调度助手,然后给出当前可用的工具列表和对话上下文。输出格式我会强制成JSON,比如{"tool": "xxx", "reason": "..."},或者直接用Function Call。如果场景比较复杂,比如用户问“帮我安排下周的会议并通知参会人”,这就涉及两个工具,我会考虑用两阶段:先做意图分类,判断是要安排会议还是通知人,然后再细选工具。
第二步是参数提取和校验。模型输出参数的时候,我会利用它的JSON输出能力,严格按照Schema生成。但模型有时候会漏掉必填参数或者格式不对,比如日期写成了“明天”,工具需要的是“2025-03-28”。所以我会加一个校验层,做类型检查、必填项检查,还有业务规则校验。校验失败的时候,我不会直接报错,而是把错误信息回传给模型,让它重新生成。这样一般两三次就能纠正过来。
第三步是API执行。这块有一个重要的落地风险:安全隔离。工具代码一定要跑在沙箱里,限制网络和文件访问,不然恶意输入可能导致数据泄露。另外超时控制也很关键,我会设一个硬超时,比如5秒,防止模型死循环或者工具卡住。对于重IO的工具,比如要查数据库或者调外部API,我会设计成异步队列,避免阻塞主链路。
执行完拿到结果后,需要把结果格式化成自然语言,注入到对话历史里。同时要保留原始调用记录,比如tool_call_id,这样支持多轮工具链。比如用户先查了库存,发现不够,又让调货,这两步就能串起来。
这里我想说一下边界划分。[[ReAct]]模式适合多步推理的场景,比如用户问“帮我比较一下这两款手机的参数,然后推荐一个”,模型需要先查参数,再对比,再推荐,每一步都要显式输出思考过程。而Function Calling更适合单步、确定性的调用,比如“查一下明天北京的天气”。真正落地的时候,我通常会把两者结合起来:简单查询直接用Function Calling,复杂推理走ReAct,这样既高效又灵活。
最后是错误处理,我一般会做分层。参数级别的错误,比如格式不对,我会让模型自纠正,限制三次。工具执行失败,比如API返回500,我会把错误摘要返回给模型,让它询问用户或者切换备选工具。系统级别的异常,比如沙箱挂了,我就直接降级,回复用户“该功能暂不可用”。
这里有个值得深挖的点:错误处理中的自纠正机制。如果模型连续三次都生成错误的参数,比如一直把日期格式写错,这时候是继续让它重试还是直接报错?我的做法是看错误类型:如果是模型能力问题,比如它就是不理解日期格式,我会考虑给它few-shot示例;如果是工具本身的问题,比如参数设计不合理,那我会反过来优化Schema。
所以整体上,我会把工具调用链路看成是一个从意图到执行的工程化系统,核心是让模型在可控范围内发挥能力,同时做好兜底。