手写Tool Use三种方案:从字符串解析到流式增量解析

发布时间:2026/10/18 9:36:14
手写Tool Use三种方案:从字符串解析到流式增量解析 1. 面试现场从“八股文”到“手写 Tool Use”的转向这两年技术面试的风向变得特别快。前几年大家还在卷“手写防抖节流”、“实现Promise.all”后来变成“手写一个简易Vue响应式”现在大模型应用开发岗位多了面试官的口味也跟着变了。我最近面了几家做AI应用落地的团队发现一个很明显的趋势面试官不再满足于你只会调API、写Prompt而是直接扔过来一道题——“手写一个Tool Use的实现”。第一次听到这个题的时候我脑子里闪过的是一堆问号。Tool Use这东西平时用LangChain或者各家SDK的时候不就是定义个函数、写个描述、传给模型就完事了吗但面试官要的是“手写”意思是你得把中间那层黑盒拆开自己实现一套从模型输出到函数执行再到结果回传的完整链路。说白了就是让你造一个迷你版的Agent执行引擎。这篇文章我打算把当时面试里用到的三种方案完整复盘一遍。三种方案分别对应不同的抽象层级第一种是最裸的字符串解析方案第二种是基于JSON Schema的结构化输出方案第三种是流式增量解析方案。每一种我都会把核心代码、设计取舍、踩过的坑讲清楚。如果你也在准备AI应用方向的面试或者单纯想搞明白Tool Use底层到底怎么跑通的这篇内容应该能帮你省下不少自己摸索的时间。先说一下这道题的基本设定。面试官通常会给你一个模拟场景有一个大模型可以是真实API也可以是Mock的它能够根据用户输入决定是否调用某个工具。你需要实现一个调度器负责把用户问题、工具定义一起发给模型模型返回一个“我要调用某个工具”的指令你解析这个指令执行对应的本地函数再把执行结果塞回对话历史再次请求模型直到模型给出最终自然语言回答。整个流程要能跑通而且要考虑多轮工具调用、参数校验、异常处理这些边界情况。听起来不复杂但真动手写的时候你会发现细节特别多。比如模型返回的调用指令格式不固定怎么办参数类型不对怎么处理工具执行超时了怎么兜底这些才是面试官真正想考察的地方。2. 方案一字符串解析方案——最原始但最能体现理解深度2.1 核心思路与适用场景第一种方案我称之为“字符串解析方案”。它的核心逻辑非常直接在系统提示词里约定一种特定的文本格式让模型按照这个格式输出工具调用指令然后用正则或者字符串分割的方式把指令提取出来。这种方案的优势在于完全不依赖任何SDK的高级功能只要模型能返回文本你就能解析。劣势也很明显格式约束靠提示词模型不一定每次都听话解析逻辑要写得非常健壮。但在面试场景下面试官往往最看重这种方案因为它能直接证明你理解Tool Use的本质——无非就是“模型输出结构化文本程序解析后执行”。我当时在面试里先写的就是这个方案。原因很简单它最快能跑通而且代码量少面试官能一眼看懂你的思路。等面试官认可了基本逻辑再往上叠加更复杂的方案节奏会比较舒服。2.2 提示词设计与格式约定提示词的设计是这套方案的关键。我当时的做法是在系统提示词里明确告诉模型当你需要调用工具时必须输出一个特定标记包裹的JSON块。比如用tool_call和/tool_call作为边界标记里面放一个JSON对象包含name和arguments两个字段。为什么用标记包裹而不是直接让模型输出JSON因为模型在自然语言回复里可能会夹杂JSON示例如果没有边界标记你很难判断哪段JSON是真正的调用指令。加上标记之后解析逻辑就变成了“先找标记再解析标记内的内容”容错率会高很多。提示词里还需要把可用工具列表传进去。每个工具包含名称、描述、参数说明。描述要写得足够清晰因为模型完全靠这段文字来判断该不该调用、怎么传参。我一般会把参数说明写成类似“参数名: 类型, 说明”的格式简单直接模型理解起来不容易出错。2.3 解析逻辑与容错处理解析部分我写了一个函数输入是模型的原始输出文本输出是一个结构化的调用指令或者None。核心步骤分三步第一步用正则找到标记对第二步把标记内的内容做JSON解析第三步校验name字段是否在已注册的工具列表里。这里有几个容错点值得展开说。第一模型可能会在标记内输出多余的空格或换行JSON解析前要先做trim。第二模型可能会把JSON写成单引号或者不带引号的键这种情况我一般会先尝试标准JSON解析失败后再用一些宽松的替换规则兜底。第三如果模型输出了多个工具调用标记要决定是全部执行还是只执行第一个。我当时的做法是全部提取出来按顺序执行这样能支持并行工具调用的场景。还有一个细节如果解析失败不要把异常直接抛给用户而是把“解析失败”这个信息作为工具执行结果塞回对话历史让模型自己纠正。这个技巧在实际项目里非常有用能大幅降低因为格式问题导致的对话中断。2.4 方案一的优缺点与面试表现这套方案的优点很明显实现简单、依赖少、逻辑透明。面试官能清楚看到你理解每一步在干什么。缺点也同样明显格式约束完全靠提示词模型一旦不遵守整个链路就断了。而且字符串解析的性能和健壮性都不如结构化输出方案。但我在面试里的体会是先写这套方案反而加分。因为它展示了你对“模型输出不可控”这个现实问题的认知。很多候选人一上来就用SDK的高级功能面试官反而会觉得你只会调包不理解底层。你先手写一个字符串解析版本再引出“有没有更可靠的方式”面试官会觉得你是在有意识地做技术选型而不是碰巧用了个库。3. 方案二JSON Schema结构化输出方案——工程化程度最高3.1 从“靠约定”到“靠约束”的转变写完方案一之后我主动跟面试官说字符串解析方案在Demo里能用但生产环境里不够稳因为模型输出格式不可控。这时候就可以引出第二种方案——基于JSON Schema的结构化输出。现在主流的大模型API基本都支持结构化输出功能你可以传一个JSON Schema进去模型会保证输出符合这个Schema。这就把“格式约束”从提示词层面提升到了API层面可靠性完全不是一个量级。面试官听到这里通常会点头因为这体现你知道生产环境和Demo的区别。3.2 Schema定义与工具注册机制这套方案的核心是设计一个通用的工具注册机制。每个工具注册时除了提供执行函数还要提供一份JSON Schema描述它的参数。这份Schema会被动态组装成一个大的Schema描述“模型可以返回一个工具调用对象对象包含工具名和参数”。我当时的实现是维护一个工具注册表用字典存储工具名到执行函数和Schema的映射。每次请求模型前遍历注册表把所有工具的Schema合并成一个oneOf或者anyOf的结构。这样模型返回的结果天然就是结构化的不需要再做字符串解析。这里有个设计取舍值得说是把所有工具放在一个Schema里用oneOf描述还是让模型先选工具再传参我选的是前者因为一次请求就能拿到完整调用指令延迟更低。代价是Schema会比较大工具多了之后模型可能选错。实际项目里如果工具超过二十个我会考虑做一层工具检索先根据用户问题筛选出候选工具再组装Schema。3.3 参数校验与类型转换结构化输出虽然保证了格式但不保证语义正确。比如Schema里定义参数是整数模型可能返回一个浮点数定义是枚举值模型可能返回一个不在枚举里的字符串。所以参数校验这层不能省。我写了一个校验函数根据每个工具的参数Schema逐字段检查类型、范围、枚举值。校验失败时不是直接报错而是把具体的校验错误信息作为工具执行结果返回给模型让模型重新生成。这个“错误回传”机制是Tool Use里非常关键的一环很多新手会忽略导致一旦参数错了整个对话就卡死。类型转换也值得提一句。模型返回的JSON里数字可能是字符串形式布尔值可能是true而不是true。我一般会在校验前先做一轮宽松转换能转就转转不了再报错。这样能减少很多不必要的重试。3.4 方案二的工程价值与面试加分点这套方案在面试里的加分点在于“工程化思维”。你不仅实现了功能还考虑了可靠性、可扩展性、错误处理。面试官如果追问“工具特别多怎么办”、“Schema特别大怎么办”、“模型选错工具怎么办”你都有话可说。我当时还被问到一个问题结构化输出和Function Calling有什么区别我的回答是Function Calling本质上是结构化输出的一种特化形式它把Schema定义和调用解析都封装好了用起来更方便但底层逻辑是一样的。面试官对这个回答比较满意因为说明我不是只会用API而是理解API背后的机制。4. 方案三流式增量解析方案——面向实时交互的进阶玩法4.1 为什么需要流式解析前两种方案都是“等模型输出完整结果再解析”。但在实际产品里用户往往希望看到模型“正在思考”的过程尤其是工具调用这种耗时操作如果等全部生成完再执行体验会很差。这就引出了第三种方案流式增量解析。流式解析的核心挑战在于模型的输出是一个token一个token来的你需要在输出过程中就识别出工具调用指令而不是等全部输出完。这要求你的解析器是“增量”的能处理不完整的JSON片段。4.2 增量解析器的实现思路我当时的实现思路是维护一个状态机。状态机有几个状态等待标记开始、收集中、解析中、完成。每来一个token就喂给状态机状态机根据当前状态决定是继续收集还是触发解析。具体来说当检测到tool_call标记开始时状态切换到“收集中”后续token都追加到一个缓冲区。当检测到/tool_call标记结束时状态切换到“解析中”对缓冲区内容做JSON解析。如果JSON不完整比如流被截断就保留缓冲区等后续token补齐。这里有个难点标记本身也是逐字符来的你可能收到tool、_call这样的分片。所以标记匹配也要做增量处理不能简单用includes判断。我当时的做法是维护一个滑动窗口每次新token进来就和窗口拼接检查是否包含完整标记。4.3 流式场景下的错误处理流式场景的错误处理比非流式复杂得多。因为流可能中途断开JSON可能永远补不齐。我当时的策略是设置一个超时阈值如果超过一定时间还没收到结束标记就强制结束收集尝试解析已有内容。如果解析失败就把已收集的内容作为“不完整调用”记录同时给模型发一个系统消息说明情况让它重新生成。还有一个细节流式输出时模型可能在工具调用标记前后夹杂自然语言。比如“好的我来帮你查一下天气 tool_call.../tool_call 稍等”。这种情况下自然语言部分应该实时展示给用户工具调用部分则进入解析流程。所以解析器要能把“展示流”和“解析流”分开处理。4.4 三种方案的横向对比与选型建议把三种方案放在一起对比能更清楚地看到各自的适用场景。我整理了一个表格从实现复杂度、可靠性、实时性、适用场景四个维度做了对比。维度字符串解析方案JSON Schema方案流式增量解析方案实现复杂度低中高格式可靠性低依赖提示词高API保证中依赖解析器健壮性实时性差需等完整输出差需等完整输出好可边生成边执行适用场景Demo、面试手写生产环境、工具较多实时交互产品、语音助手调试难度低中高选型建议很直接面试手写用方案一展示理解深度生产落地用方案二保证可靠性做实时交互产品用方案三提升用户体验。实际项目里这三种方案往往不是互斥的而是可以组合使用。比如用方案二做基础调用在需要流式展示的场景下叠加方案三的解析器。5. 面试官真正想考察的三个能力点5.1 对“模型输出不可控”的认知面完这几场之后我最大的感受是面试官出这道题根本不是想看你写出多优雅的代码而是想看你有没有“模型输出不可控”这个认知。Tool Use的所有复杂性本质上都来源于模型输出不是100%可靠的。你写的每一层校验、每一个容错分支都是在跟这个不确定性做斗争。如果你一上来就说“用Function Calling就行了”面试官会觉得你只停留在调用层。但如果你能说出“Function Calling底层也是结构化输出我手写的话会怎么处理格式异常”那就完全不一样了。这个认知差距就是初级和高级的分水岭。5.2 工程化思维与边界处理第二个考察点是工程化思维。面试官会通过追问来试探你的边界处理能力。比如“如果工具执行抛异常了怎么办”、“如果模型连续三次都返回错误参数怎么办”、“如果用户问题里包含提示词注入怎么办”。这些问题没有标准答案但你的回答能体现你平时有没有真正在项目里踩过坑。我一般会准备几个通用的兜底策略工具执行加超时和异常捕获异常信息回传给模型连续失败超过阈值就降级为直接回答对用户输入做基本的清洗和长度限制。这些策略不一定最优但能说明你有生产环境的意识。5.3 技术选型的权衡表达能力第三个考察点是技术选型的权衡表达。面试里最忌讳的是“只有一种方案”。你如果能主动说出“方案一简单但不可靠方案二可靠但依赖API方案三实时但复杂”面试官会觉得你是在有意识地做选择而不是碰巧写了个能跑的代码。这种权衡表达能力在实际工作里比写代码本身更重要。因为真实项目里你永远是在约束条件下做选择时间、人力、性能、可靠性不可能全都占。能清楚说出每个选择的代价和收益才是资深工程师的标志。6. 手写Tool Use的常见坑与排查清单6.1 格式解析类问题格式解析是最高频的坑。我整理了几个典型问题和对应的排查思路。问题现象可能原因排查方法解决方案解析不到工具调用模型没按格式输出打印原始输出检查标记是否存在强化提示词增加示例JSON解析失败模型输出单引号或尾逗号打印标记内内容人工检查宽松替换后再解析参数类型不对模型把数字写成字符串打印解析后的参数对象增加类型转换层多个调用只执行一个解析逻辑只取了第一个检查提取逻辑是否遍历改为遍历所有标记6.2 执行调度类问题执行调度类的坑往往更隐蔽。比如工具执行时间过长导致整个对话卡住或者工具执行抛异常导致对话中断。我一般会给每个工具执行加一个超时包装超时后返回一个“执行超时”的结果给模型让模型决定是重试还是换工具。还有一个坑是工具之间的依赖关系。比如工具A的输出是工具B的输入这种场景下调度器要能识别依赖顺序。我当时的做法是让模型在调用指令里显式声明依赖调度器按依赖顺序执行。如果模型没声明就按返回顺序串行执行虽然效率低但至少不会错。6.3 对话状态管理类问题对话状态管理是最容易被忽略的部分。Tool Use往往涉及多轮交互每一轮的工具调用结果都要正确追加到对话历史里。如果历史管理不当模型可能会重复调用同一个工具或者忘记之前的调用结果。我的经验是每次工具执行完都要把“工具名、参数、执行结果”作为一个完整的消息追加到历史里并且明确标注这是工具执行结果不是用户输入。这样模型在下一轮生成时能清楚看到之前发生了什么。另外历史长度要控制太长的历史会挤占上下文窗口我一般会保留最近五轮的工具调用记录更早的做摘要压缩。7. 从面试题到生产Tool Use的扩展方向7.1 工具检索与动态注册面试里工具数量少直接全量传给模型就行。但生产环境里工具可能有几十上百个全量传进去Schema会爆炸。这时候就需要工具检索先根据用户问题用向量检索或者关键词匹配筛选出最相关的几个工具再组装Schema传给模型。动态注册也很重要。生产环境里工具可能会热更新不能每次加工具都重启服务。我一般会设计一个工具注册中心支持运行时注册和注销Schema动态生成。这样新增工具只需要注册一下不用改调度器代码。7.2 多工具并行与依赖编排单工具调用是最简单的场景。复杂场景下模型可能一次返回多个工具调用这些调用之间可能有依赖关系也可能完全独立。独立的可以并行执行提升效率有依赖的必须串行。我当时的实现是让模型在调用指令里加一个depends_on字段声明依赖哪些调用。调度器根据依赖关系构建一个有向无环图然后做拓扑排序执行。这个方案在面试里没来得及展开但在实际项目里非常有用尤其是做数据分析类Agent的时候多个工具之间的数据流转很常见。7.3 可观测性与调试工具生产环境里Tool Use的调试是个大问题。你很难复现一个“模型偶尔选错工具”的bug。所以可观测性建设很重要。我一般会在调度器里埋点记录每次请求的完整输入输出、解析结果、执行耗时、错误信息。这些日志汇总起来能帮你快速定位是提示词问题、Schema问题还是模型本身的问题。还有一个实用技巧做一个“回放”功能把历史请求的输入重新跑一遍对比输出差异。这在模型升级或者提示词调整后特别有用能快速评估改动的影响。8. 我个人的面试复盘与建议面完这几场之后我最大的体会是手写Tool Use这道题考的不是你代码写得多快而是你对“模型能力边界”的理解有多深。你写的每一行容错代码背后都是对模型不确定性的敬畏。如果让我给准备这道题的人一个建议我会说先把方案一写熟做到十分钟内能跑通一个最小闭环。然后重点准备方案二的Schema设计和参数校验这是生产环境的核心。方案三流式解析可以作为加分项面试官问到实时性的时候能接上话就行。另外面试里一定要主动说出每种方案的取舍。不要等面试官问“有没有更好的方案”你要自己引出“这个方案在什么场景下不够用所以我会考虑另一种”。这种主动的技术选型表达比被动回答问题更能体现你的经验。最后说一个细节面试官如果让你“手写”尽量用伪代码或者简洁的Python不要纠结语法细节。重点是逻辑清晰、边界完整。我见过有人花二十分钟纠结一个正则怎么写结果核心调度逻辑没时间写了非常可惜。先把主干跑通再补细节这个节奏在面试里很关键。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询