手写Tool Use三种方案:从字符串解析到约束解码与框架抽象
发布时间:2026/10/22 10:46:18 锦皓数字建站

1. 从一道面试题说起为什么“手写 Tool Use”成了高频考点“手写一个 Tool Use”——这六个字第一次出现在面试邀约里的时候我正坐在工位上啃一个冷掉的三明治。当时第一反应是这玩意儿不是调个 API 就完事了吗但真坐到白板前面试官把需求一摊开我才意识到这道题考的根本不是“你会不会用某个库”而是你对大模型与外部工具之间那层协议的理解深度。所谓 Tool Use通俗讲就是让语言模型在生成回答的过程中能够主动“喊一声”去调用外部函数比如查天气、算数学、读数据库然后把工具返回的结果再揉进最终回复里。它解决的核心问题是模型本身只会“说”不会“做”而真实业务里大量需求是“既要会说又要会做”。适合看这篇内容的人包括正在准备相关岗位面试的开发者、想给自己产品加工具调用能力的工程师以及单纯好奇“模型怎么知道该调哪个函数”的技术爱好者。我前后用了三种方案来实现它从最裸的手写解析到半结构化的约束解码再到借助现成框架的抽象层。三种方案各有各的坑也各有各的适用场景。下面我把整个思考过程、代码骨架和踩坑记录完整摊开你拿去就能复现。2. 方案一纯手写字符串解析最笨但最透明2.1 为什么先选最原始的路子面试场景下面试官最想看的往往不是你能背出多少框架名而是你能不能把“模型输出一段文本程序从中提取出工具名和参数”这件事讲清楚。所以我第一种方案刻意不用任何库纯靠字符串处理。这样做的好处是每一个字节的流转都在你掌控之中出了问题你能精确定位到是哪一步的解析逻辑写歪了。核心思路分三步第一在系统提示词里约定一种工具调用的文本格式第二模型生成后用正则或字符串查找把工具名和参数抠出来第三执行对应函数把结果拼回对话历史再让模型生成最终回答。2.2 约定格式与提示词设计格式约定是这套方案的命门。我试过几种写法最后发现用类似下面的结构最稳你可以调用以下工具 - get_weather(city: str): 查询指定城市的天气 - calculate(expression: str): 计算数学表达式 当你需要调用工具时请严格输出 tool_call {name: 工具名, arguments: {参数名: 参数值}} /tool_call这里有几个细节值得说。第一工具描述必须写清楚参数类型和含义否则模型很容易传错参数名。第二用tool_call这种自定义标签包裹比让模型直接输出 JSON 更容易用正则定位因为模型经常会在 JSON 前后加一堆解释性文字。第三我特意要求 JSON 里用arguments作为键而不是直接把参数平铺这样解析逻辑统一不用为每个工具写不同的提取代码。提示提示词里一定要加一句“如果不需要调用工具直接正常回答”否则模型可能每轮都硬塞一个工具调用出来。2.3 解析与执行的代码骨架解析部分我用的是“先定位标签再解析 JSON”的两段式import re import json def parse_tool_call(text): pattern rtool_call(.*?)/tool_call match re.search(pattern, text, re.DOTALL) if not match: return None try: payload json.loads(match.group(1).strip()) return payload.get(name), payload.get(arguments, {}) except json.JSONDecodeError: return None执行部分就是一个简单的分发字典TOOL_REGISTRY { get_weather: get_weather, calculate: calculate, } def execute_tool(name, arguments): func TOOL_REGISTRY.get(name) if not func: return f错误未知工具 {name} try: return func(**arguments) except Exception as e: return f工具执行出错{e}整个循环就是模型生成 → 解析 → 如果有工具调用就执行 → 把结果作为新消息追加 → 再次生成直到没有工具调用为止。2.4 这套方案的致命短板跑通之后问题很快就来了。最典型的是模型偶尔会输出格式不规范的 JSON比如参数值里带了未转义的引号或者干脆把arguments写成了字符串而不是对象。我统计过一轮在几百次调用里大约有百分之三到五的概率解析失败。另一个问题是模型有时会在tool_call标签外面再包一层解释比如“好的我来帮你查一下tool_call...”虽然正则还能匹配但如果它把标签写错一个字符就彻底歇菜。所以这套方案适合面试白板、教学演示或者对稳定性要求不高的内部脚本。真上生产你得加大量兜底和重试逻辑维护成本会迅速攀升。3. 方案二约束解码加 JSON Schema把格式错误扼杀在生成阶段3.1 从“事后补救”转向“事前约束”方案一的痛点在于格式错误是生成之后才发现的属于被动挨打。方案二的思路是在模型生成每一个 token 的时候就限制它只能输出符合 JSON Schema 的内容。这就是所谓的约束解码也叫结构化输出。具体做法是给模型传一个 JSON Schema描述工具调用的结构然后通过推理引擎的约束能力强制输出合法 JSON。不同推理后端支持程度不一样有的原生支持有的需要借助语法约束库。我用的是一种基于语法规则的约束方式把 Schema 翻译成一套生成规则。3.2 JSON Schema 的设计要点Schema 设计直接决定了约束的松紧程度。太松等于没约束太紧又会让模型没法表达“不调用工具”的意图。我最后用的结构是这样的{ type: object, properties: { tool_calls: { type: array, items: { type: object, properties: { name: {type: string, enum: [get_weather, calculate]}, arguments: {type: object} }, required: [name, arguments] } }, final_answer: {type: string} }, required: [final_answer] }这里的关键设计是tool_calls和final_answer可以同时存在也可以只存在其中一个。模型如果决定调用工具就填tool_calls如果直接回答就填final_answer。name字段用enum限定彻底杜绝了模型编造工具名的可能。3.3 约束解码的实际效果与代价效果立竿见影。格式错误率从方案一的百分之三到五直接降到了零因为从生成机制上就不可能产出非法 JSON。但代价也很明显推理速度会变慢。约束解码需要在每一步对候选 token 做合法性检查相当于给生成过程加了一层过滤器。我实测下来同样的硬件条件下开启约束后每秒生成的 token 数大约下降百分之十五到二十。另一个隐性成本是灵活性下降。如果哪天你想让模型输出一段带自然语言的解释再附带工具调用Schema 就得改而改 Schema 往往意味着重新调试约束规则。所以这套方案适合对格式稳定性要求极高、且工具调用结构相对固定的场景比如企业内部的数据查询机器人。注意约束解码对推理引擎有要求不是所有部署方式都支持。选型前先确认你的推理后端能不能吃 JSON Schema否则这套方案根本跑不起来。3.4 与方案一的对比对比维度方案一手写解析方案二约束解码格式错误率约百分之三到五接近零推理速度正常下降百分之十五到二十实现复杂度低纯字符串处理中依赖推理引擎能力灵活性高格式随便改低改 Schema 成本高适用场景演示、内部脚本生产环境、固定结构这张表我在面试时直接画在白板上面试官看完点了点头说“你知道什么时候不用什么比知道怎么用更重要”。4. 方案三借助框架抽象层把脏活累活交出去4.1 什么时候该上框架前两种方案都是“自己造轮子”好处是理解深坏处是维护烦。当工具数量超过十个、参数结构开始嵌套、还需要处理多轮并行调用的时候手写解析的复杂度会指数级上升。这时候就该考虑用现成的框架抽象层了。我选的是一种提供工具注册、自动 Schema 生成、调用编排的轻量框架。它的核心价值在于你只需要用装饰器把函数标记成工具框架自动从类型注解生成 JSON Schema自动处理解析和执行你只管写业务逻辑。4.2 工具注册的写法用框架之后工具定义变得非常干净from framework import tool tool def get_weather(city: str) - str: 查询指定城市的天气 return f{city}今天晴气温25度 tool def calculate(expression: str) - str: 计算数学表达式 return str(eval(expression))框架会读取函数名、参数类型和文档字符串自动生成给模型看的工具描述和 JSON Schema。这一步省掉的代码量非常可观而且文档字符串直接变成工具描述写文档和写代码合二为一不容易出现描述和实现不一致的情况。4.3 编排循环的简化方案一里那个“生成→解析→执行→追加→再生成”的循环框架通常封装成一个run方法agent Agent(tools[get_weather, calculate]) result agent.run(北京天气怎么样顺便算一下 23 乘以 47)框架内部会自动处理多轮工具调用、并行执行、结果拼接。我实测下来同样的任务方案三的代码量大约是方案一的五分之一而且不用操心格式解析。4.4 框架方案的隐藏成本但框架不是银弹。第一个问题是调试黑盒化。当工具调用出错时你很难像方案一那样直接打印中间文本看看到底哪一步歪了得去翻框架的日志或者读源码。第二个问题是版本锁定。框架升级可能改变工具描述生成方式导致原本跑得好好的提示词突然失效。第三个问题是过度抽象。有些框架为了通用性把简单问题复杂化一个工具调用绕了七八层性能损耗不小。所以我的建议是工具少于五个、结构简单方案一或方案二足够工具多、需要快速迭代方案三更划算。面试时如果能把这层取舍讲清楚比只会背框架 API 强得多。5. 三种方案的横向拆解与选型逻辑5.1 从“谁负责保证格式正确”看本质差异三种方案最根本的区别在于格式正确性的责任方不同。方案一把责任交给后置解析代码方案二把责任交给推理引擎的约束机制方案三把责任交给框架的抽象层。理解这一点选型时就不会迷糊。方案一的解析代码是你自己写的出了 bug 你全责但你也拥有全部控制权。方案二的约束规则是推理引擎执行的你只需要写对 Schema但引擎不支持就白搭。方案三的格式处理是框架包办的你省心但框架出问题你只能等更新或者自己打补丁。5.2 性能、成本与可维护性的三角权衡我在实际项目里做过一轮粗略测算。假设每天一万次工具调用方案一的解析失败重试带来的额外 token 消耗大约在百分之二左右方案二没有重试但推理变慢导致 GPU 占用时间增加约百分之十八方案三的框架开销大约在百分之五到八之间。这些数字因模型和硬件而异但量级关系可以参考。可维护性方面方案一改一个工具要动提示词、解析逻辑、分发字典三处方案二改工具要动 Schema 和提示词两处方案三改工具只动函数本身一处。工具越多方案三的优势越明显。5.3 面试场景下的回答策略如果面试官让你“手写一个 Tool Use”我的经验是先问清楚约束条件。是要求零依赖纯手写还是可以用库是单工具还是多工具对格式错误率有没有要求问清楚之后再选方案并且主动说出另外两种方案的取舍。这样展示的不是“我会写”而是“我会判断”。我那次面试就是先写了方案一的核心解析逻辑然后口头补充了方案二的约束思路和方案三的框架抽象最后说“如果生产环境让我选我会用方案二打底、方案三做上层编排”。面试官后来反馈说这个回答让他看到了工程判断力而不只是编码能力。6. 实操中踩过的坑与排查清单6.1 参数类型不匹配的隐蔽 bug方案一里我遇到过最坑的一个问题模型把数字参数输出成了字符串。比如calculate的expression参数模型输出了{expression: 23 * 47}这没问题但get_weather如果有个days参数是整数模型可能输出{days: 3}。我的执行函数直接**arguments传进去Python 不会自动转类型结果函数内部拿字符串去做算术就炸了。解决办法是在执行前加一层类型校验和转换根据工具的函数签名把参数转成正确类型。框架方案通常自带这层处理手写方案就得自己补。6.2 多轮调用时的上下文膨胀工具调用结果追加到对话历史后上下文会迅速变长。如果工具返回的是大段 JSON 或长文本几轮下来就可能超出模型上下文窗口。我的做法是对工具返回结果做摘要或截断只保留关键字段。比如天气查询只保留温度和天气状况不要把整个 API 响应塞进去。提示可以在系统提示词里加一句“工具返回结果可能被截断请基于已有信息回答”让模型对截断有预期。6.3 常见问题速查表问题现象可能原因排查方向模型不调用工具直接瞎编答案提示词没强调工具可用性检查系统提示词是否列出工具及触发条件工具名解析出来是错的模型编造了不存在的工具名用 enum 限定工具名或加白名单校验JSON 解析失败模型输出格式不规范改用约束解码或加正则兜底和重试参数类型错误模型把数字输出成字符串执行前按函数签名做类型转换多轮调用后回答变慢上下文过长截断工具返回结果或做摘要压缩并行调用结果顺序错乱多个工具同时执行给每个调用加唯一 ID按 ID 匹配结果6.4 一个容易被忽略的细节工具描述的语气工具描述不只是给模型看的也是给未来的维护者看的。我试过把描述写得非常简短结果模型经常误解工具用途。后来改成“查询指定城市的当前天气状况返回温度和天气描述”调用准确率明显提升。描述里最好包含工具做什么、参数是什么、返回什么三要素缺一不可。7. 如果继续往下走还能扩展什么三种方案跑通之后我后来又做了一些延伸尝试。比如给工具调用加权限控制某些敏感工具只在特定条件下才暴露给模型比如加调用缓存同样的参数短时间内重复调用直接返回缓存结果省掉一次执行再比如加人工确认环节涉及写操作的工具先弹窗让用户确认再执行。这些扩展的共同点是它们都不改变 Tool Use 的核心循环只是在循环的某个环节插入额外的检查或优化。理解了核心机制扩展就是搭积木。我个人在实际操作中的体会是Tool Use 最难的部分从来不是“怎么调”而是“怎么让模型在该调的时候调、不该调的时候不调”这个边界感的调教比任何框架选型都更考验功力。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。