手写Tool Use调度逻辑:三种实现方案与面试实战指南
1. 面试现场为什么“手写 Tool Use”成了高频考题1.1 从“背八股”到“现场造轮子”的转变这两年技术面试的风向变了。前几年大家还在背 Transformer 的注意力公式、手推反向传播现在面试官更倾向于把电脑推到你面前说一句“给你二十分钟手写一个 Tool Use 的调度逻辑。”我第一次遇到这个题的时候脑子里第一反应是——这不就是让模型调用外部函数吗但真正动手写才发现面试官想看的根本不是你能不能调通一个 API而是你对“模型如何决定调用哪个工具、参数怎么传、结果怎么回灌、失败怎么兜底”这一整条链路的理解深度。所谓 Tool Use通俗讲就是让语言模型在对话过程中能够识别出“这个问题我需要借助外部能力才能回答”然后主动生成一个结构化的调用请求交给外部系统执行再把执行结果拼回上下文让模型基于真实数据继续推理。它解决的核心问题是模型本身的知识是静态的、有截止时间的、无法访问私有数据的而 Tool Use 让它变成了一个可以“动手做事”的智能体。这个能力适合谁来深入如果你在做 AI 应用开发、智能客服、自动化工作流、数据分析助手或者任何需要让模型和真实世界交互的场景Tool Use 都是绕不开的基础设施。哪怕你只是刚入门的开发者理解它的三种典型实现方案也能让你在面试和实际项目中少走很多弯路。1.2 面试官到底在考察什么我后来复盘那次面试发现面试官的评分点其实很清晰。第一你是否理解 Tool Use 的本质是“模型输出结构化指令 外部执行 结果回灌”这个闭环而不是简单地调个接口。第二你能否在不同方案之间做出合理的取舍比如什么时候用提示词工程硬编码什么时候用原生函数调用什么时候自己写解析器。第三你有没有考虑边界情况——工具不存在怎么办、参数格式错了怎么办、工具执行超时怎么办、模型连续调用同一个工具陷入死循环怎么办。这些考察点背后其实对应着三种主流的实现方案。第一种是基于提示词的纯文本协议方案第二种是基于模型原生能力的函数调用方案第三种是基于解析器加状态机的自定义调度方案。三种方案没有绝对的优劣关键在于你的场景需要什么级别的可控性、稳定性和开发效率。接下来我会把三种方案逐一拆开讲清楚每一种的设计思路、核心代码、实操细节和我踩过的坑。2. 方案一基于提示词的纯文本协议方案2.1 设计思路与适用场景这个方案的核心思想是不依赖任何模型厂商的特殊能力完全通过系统提示词来约定一套文本协议让模型按照固定格式输出工具调用请求然后由外部程序解析这段文本并执行。它的最大优势是通用性极强任何能生成文本的模型都能用不挑供应商、不挑版本。缺点是稳定性完全依赖模型对格式的遵循程度模型偶尔会“自由发挥”导致解析失败。我选择这个方案作为第一种实现是因为它最能体现你对 Tool Use 底层机制的理解。面试官看到你不依赖任何高级 API 就能把闭环跑通会认为你具备从零搭建的能力。这个方案特别适合以下场景使用的模型不支持原生函数调用、需要快速做原型验证、或者需要对输出格式有极强自定义需求的场合。2.2 协议设计与提示词编写协议设计是这个方案的地基。我一般会定义一个简单的 JSON 结构包含三个字段tool_name、parameters、reasoning。reasoning字段是我强烈建议加上的它让模型在调用前先解释为什么选这个工具一方面提升了可解释性另一方面实测能显著降低模型乱调工具的概率。系统提示词大概长这样你是一个可以使用工具的助手。当用户的问题需要借助外部工具才能回答时 你必须严格按照以下 JSON 格式输出调用请求不要输出任何其他内容 {tool_name: 工具名称, parameters: {参数名: 参数值}, reasoning: 选择该工具的原因} 当前可用工具列表 1. get_weather查询指定城市的天气参数为 city字符串 2. search_database在内部数据库中搜索信息参数为 query字符串和 limit整数 3. send_email发送邮件参数为 recipient字符串、subject字符串、body字符串 如果不需要工具就能回答直接正常回复即可。这里有个关键细节工具列表的描述要尽可能精确包括参数类型和含义。我试过把参数类型写得很模糊结果模型传过来的参数一会儿是字符串一会儿是数字解析器直接崩溃。另外提示词里一定要强调“不要输出任何其他内容”否则模型很可能会在 JSON 前后加上“好的我来帮你查询”之类的废话增加解析难度。2.3 解析器实现与容错处理解析器的核心任务是从模型输出中提取出合法的 JSON。但现实很骨感模型输出经常带着 markdown 代码块标记、前后缀说明文字、甚至多个 JSON 对象。我的做法是先用正则表达式定位第一个{和最后一个}截取中间部分再用 JSON 解析器尝试解析。如果失败就进入容错流程。import json import re def parse_tool_call(model_output): # 去掉 markdown 代码块标记 cleaned re.sub(r(?:json)?, , model_output).strip() # 定位 JSON 边界 start cleaned.find({) end cleaned.rfind(}) if start -1 or end -1: return None candidate cleaned[start:end1] try: parsed json.loads(candidate) if tool_name in parsed and parameters in parsed: return parsed except json.JSONDecodeError: # 尝试修复常见问题比如单引号、尾随逗号 candidate candidate.replace(, ) candidate re.sub(r,\s*}, }, candidate) try: return json.loads(candidate) except json.JSONDecodeError: return None return None容错处理里我踩过最大的坑是“模型输出多个工具调用”。比如用户问“帮我查天气然后发邮件通知同事”模型可能会输出两个 JSON 对象。这时候解析器只取第一个就会丢逻辑。我的解决方案是让解析器返回一个列表支持批量解析然后在调度层按顺序执行。这个细节在面试中如果主动提出来面试官通常会眼前一亮。2.4 结果回灌与多轮循环工具执行完之后结果不能直接丢给用户而是要拼回对话上下文让模型基于真实结果生成自然语言回复。我的做法是构造一条 role 为tool的消息内容包含工具名和执行结果追加到消息列表末尾然后再次调用模型。messages.append({role: assistant, content: model_output}) messages.append({role: tool, content: json.dumps(tool_result, ensure_asciiFalse)}) final_response call_model(messages)这里要注意一个循环控制问题模型可能会连续调用工具比如查完天气又想查空气质量。我设置了一个最大循环次数通常是 5 次超过就强制让模型基于已有信息回答。这个阈值不是拍脑袋定的我实测下来 3 到 5 次能覆盖绝大多数合理场景再多基本就是模型在绕圈子了。注意纯文本协议方案最大的风险是模型“假装调用”。它可能输出一段看起来像 JSON 但实际是自然语言的内容解析器返回 None 后如果不做处理整个流程就断了。我的经验是解析失败时把原始输出和错误信息一起回灌给模型让它重新生成通常第二次就能成功。3. 方案二基于原生函数调用的方案3.1 原生能力的优势与限制如果你使用的模型平台支持原生函数调用那这个方案在稳定性上会碾压纯文本协议。所谓原生函数调用就是你在请求里声明一个 tools 数组模型会返回一个结构化的 tool_calls 字段包含函数名和参数完全不需要你自己解析文本。这相当于把“格式遵循”这件事从概率问题变成了确定性问题。但原生方案也有它的限制。第一它绑定特定平台换模型就得重写。第二不同平台对 tools 声明的格式要求不完全一样迁移成本不低。第三有些平台对工具数量有上限或者对参数 schema 的支持不够灵活。所以我在实际项目中会把它作为首选但始终保留一套纯文本协议作为降级方案。3.2 工具声明的结构化定义原生方案的核心是工具声明。你需要用平台规定的 JSON Schema 格式描述每个工具的名称、功能、参数类型和必填项。这个声明会作为请求的一部分发给模型模型据此决定是否调用以及如何填参。tools [ { type: function, function: { name: get_weather, description: 查询指定城市的实时天气信息, parameters: { type: object, properties: { city: { type: string, description: 城市名称例如北京、上海 }, unit: { type: string, enum: [celsius, fahrenheit], description: 温度单位默认摄氏度 } }, required: [city] } } } ]这里有个容易被忽略的细节description的质量直接决定模型选工具的准确率。我做过对比测试把描述从“查询天气”改成“查询指定城市的实时天气信息包括温度、湿度和风力”工具选择的准确率提升了将近两成。原因很简单描述越具体模型越能判断当前问题是否匹配这个工具的能力边界。3.3 调用链路与参数校验原生方案的调用链路比纯文本方案清晰得多。模型返回的tool_calls是一个数组每个元素包含id、function.name和function.arguments。arguments是一个 JSON 字符串需要二次解析。执行完工具后你要构造一条 role 为tool的消息带上对应的tool_call_id这样模型才能把结果和调用对应起来。tool_call response.choices[0].message.tool_calls[0] func_name tool_call.function.name func_args json.loads(tool_call.function.arguments) # 参数校验 if func_name get_weather: if city not in func_args: raise ValueError(缺少必填参数 city) result get_weather(**func_args) messages.append(response.choices[0].message) messages.append({ role: tool, tool_call_id: tool_call.id, content: json.dumps(result, ensure_asciiFalse) })参数校验这一步千万别省。我遇到过模型把unit传成摄氏度而不是celsius的情况虽然声明了 enum但模型偶尔还是会越界。所以在执行前做一层校验和归一化能避免很多莫名其妙的报错。3.4 并行调用与结果合并原生方案还有一个纯文本方案很难做到的能力并行工具调用。当用户的问题需要同时查多个独立数据源时模型可以在一次返回中给出多个tool_calls你可以并发执行它们大幅缩短响应时间。import concurrent.futures def execute_tool_call(tool_call): func_name tool_call.function.name func_args json.loads(tool_call.function.arguments) return func_name, execute_function(func_name, func_args) with concurrent.futures.ThreadPoolExecutor() as executor: results list(executor.map(execute_tool_call, tool_calls)) for tool_call, (func_name, result) in zip(tool_calls, results): messages.append({ role: tool, tool_call_id: tool_call.id, content: json.dumps(result, ensure_asciiFalse) })并行执行的时候要注意工具之间是否有依赖关系。如果工具 B 的参数依赖工具 A 的输出那就不能并行必须串行。我一般会在工具声明里加一个depends_on字段来标记依赖调度层据此决定并行还是串行。这个设计在面试中主动提出来能体现你对工程复杂度的预判能力。提示原生函数调用虽然稳定但不要假设模型永远不会出错。我实测下来当工具数量超过 15 个时模型选错工具的概率会明显上升。这时候可以考虑做工具分组或者先用一个轻量模型做工具路由再交给主模型执行。4. 方案三基于解析器加状态机的自定义调度方案4.1 为什么还需要第三种方案前两种方案各有短板。纯文本协议太脆弱原生函数调用太依赖平台。第三种方案是我在做一个需要同时对接多个模型供应商的项目时逼出来的我需要一套统一的调度层不管底层模型返回什么格式上层逻辑都不变。于是我把 Tool Use 抽象成一个状态机解析器负责把各种格式归一化状态机负责管理调用生命周期。这个方案的核心价值在于“解耦”。模型层、解析层、调度层、执行层各自独立任何一层换实现都不影响其他层。它适合中大型项目、多模型混用的场景或者对可观测性要求很高的生产环境。4.2 状态机设计与状态流转我把整个 Tool Use 生命周期拆成六个状态IDLE、PARSING、VALIDATING、EXECUTING、FEEDBACK、DONE。每个状态有明确的入口条件和出口条件状态之间的流转由事件驱动。状态触发事件动作下一状态IDLE收到用户输入调用模型PARSINGPARSING模型返回解析工具调用VALIDATINGVALIDATING解析成功校验参数EXECUTINGVALIDATING解析失败回灌错误重试PARSINGEXECUTING校验通过执行工具FEEDBACKFEEDBACK执行完成结果回灌模型PARSING 或 DONEDONE无需继续调用输出最终回复IDLE这个状态机的好处是每个环节的异常都有明确的处理路径。比如解析失败不是直接报错而是回到 PARSING 让模型重新生成执行超时则进入 FEEDBACK 把超时信息告诉模型让它决定是重试还是换工具。4.3 解析器的多格式适配解析器层我设计了一个统一的接口内部支持三种解析策略JSON 直接解析、正则提取解析、以及针对特定平台返回结构的适配解析。解析器会依次尝试这些策略直到成功或全部失败。class ToolCallParser: def __init__(self): self.strategies [ self._parse_native_format, self._parse_json_block, self._parse_regex_fallback ] def parse(self, raw_output): for strategy in self.strategies: result strategy(raw_output) if result: return result return None def _parse_native_format(self, raw): # 适配原生函数调用的返回结构 if hasattr(raw, tool_calls) and raw.tool_calls: return [ { tool_name: tc.function.name, parameters: json.loads(tc.function.arguments), call_id: tc.id } for tc in raw.tool_calls ] return None def _parse_json_block(self, raw): # 从文本中提取 JSON 块 text str(raw) matches re.findall(r\{[^{}]*tool_name[^{}]*\}, text) results [] for m in matches: try: results.append(json.loads(m)) except json.JSONDecodeError: continue return results if results else None def _parse_regex_fallback(self, raw): # 最后的兜底用正则提取关键字段 text str(raw) name_match re.search(rtool_name\s*:\s*([^]), text) if not name_match: return None params_match re.search(rparameters\s*:\s*(\{[^}]\}), text) params json.loads(params_match.group(1)) if params_match else {} return [{tool_name: name_match.group(1), parameters: params}]这个多策略解析器的设计思路是“尽力而为”。生产环境中你永远不知道模型会返回什么奇怪的东西多一层兜底就多一分稳定性。我实测下来三层策略叠加能把解析成功率从纯 JSON 解析的 85% 提升到 99% 以上。4.4 执行层的隔离与超时控制执行层我做了两件关键的事隔离和超时。隔离是指每个工具的执行都在独立的上下文中进行一个工具崩溃不会影响其他工具。超时是指每个工具都有独立的执行时间上限超过就强制中断并返回超时错误。import signal class ToolExecutor: def __init__(self, timeout_seconds10): self.timeout timeout_seconds self.registry {} def register(self, name, func): self.registry[name] func def execute(self, tool_name, parameters): if tool_name not in self.registry: return {error: f工具 {tool_name} 不存在} def handler(signum, frame): raise TimeoutError(f工具 {tool_name} 执行超时) signal.signal(signal.SIGALRM, handler) signal.alarm(self.timeout) try: result self.registry[tool_name](**parameters) return {success: True, data: result} except TimeoutError as e: return {success: False, error: str(e)} except Exception as e: return {success: False, error: f执行异常{str(e)}} finally: signal.alarm(0)超时时间设多少合适我的经验是查询类工具 5 到 10 秒写入类工具 15 到 30 秒涉及外部网络请求的工具可以放宽到 30 秒。但无论如何都要设上限否则一个卡住的工具会把整个对话拖死。这个细节在面试中如果被问到“你怎么保证系统稳定性”直接把这个设计讲出来就很有说服力。5. 三种方案的横向对比与选型建议5.1 关键维度对比把三种方案放在一起对比能更清楚地看到各自的适用边界。我从六个维度做了整理维度纯文本协议原生函数调用自定义状态机通用性极高任何模型可用低绑定平台高可适配多平台稳定性中依赖模型遵循度高结构化返回高多策略兜底开发成本低提示词加解析器低声明即可高需要设计状态机可控性高格式完全自定义中受平台约束极高全链路可控并行调用需自行实现原生支持需自行实现可观测性低中高状态可追踪这张表不是让你死记硬背而是帮你在面对具体需求时快速定位。比如你只是做个内部工具用原生函数调用最省事如果你要做一个面向多模型供应商的产品自定义状态机虽然前期投入大但后期维护成本低得多。5.2 选型决策树我一般用三个问题来决定用哪个方案。第一个问题你用的模型支持原生函数调用吗如果支持且不需要换模型直接用方案二。第二个问题你需要同时对接多个模型供应商吗如果是方案三。第三个问题你只是做个快速原型或者模型不支持原生调用方案一。这三个问题按顺序问下来基本能覆盖八成以上的场景。剩下的两成是混合场景比如主流程用原生调用但保留纯文本协议作为降级。这种混合方案我在生产环境用过效果很稳。5.3 面试中的表达策略回到面试场景面试官让你“手写一个 Tool Use”你如果只写一种方案哪怕写得再好也只能证明你会用。但如果你能主动说出三种方案的取舍逻辑并解释为什么在当前场景下选择某一种那就证明你不仅会用还理解背后的工程权衡。我的建议是先用方案二快速写出一个能跑的版本展示你对原生能力的掌握然后主动说“但如果模型不支持原生调用我会用方案一”并简要写出协议设计和解析器最后提一句“如果要做多模型适配我会抽象成方案三的状态机”。这样层层递进面试官会觉得你的知识体系是完整的而不是碎片化的。6. 实操中的常见问题与排查技巧6.1 模型不调用工具怎么办这是最常见的问题。用户明明问了一个需要查天气的问题模型却直接编了一个答案。排查思路分三步第一检查系统提示词里工具描述是否足够清晰模型可能没理解这个工具能干什么。第二检查用户问题是否真的需要工具有时候模型判断“这个问题我能直接回答”也是合理的。第三检查工具数量是否过多超过 15 个之后模型的选择困难症会明显加重。我的解决技巧是在提示词里加一句“如果你不确定是否需要使用工具优先使用工具获取真实数据。”这句话能显著提升工具调用率。另外把最常用的工具放在列表最前面也有一定效果。6.2 参数格式错误怎么兜底模型传参格式错误是家常便饭。字符串传成数字、数组传成字符串、必填参数缺失我都遇到过。兜底策略分两层第一层是执行前的 schema 校验用 JSON Schema 验证参数结构不通过就返回错误信息让模型重新生成。第二层是执行中的类型转换比如模型传了limit: 10我在执行前自动转成整数。def normalize_params(params, schema): normalized {} for key, spec in schema.items(): if key not in params: if spec.get(required): raise ValueError(f缺少必填参数 {key}) continue value params[key] expected_type spec.get(type) if expected_type integer and isinstance(value, str): value int(value) elif expected_type string and not isinstance(value, str): value str(value) normalized[key] value return normalized这个归一化函数看起来简单但能挡掉大量低级错误。我建议把它做成工具执行的标准前置步骤不要省。6.3 死循环调用怎么中断模型连续调用同一个工具、或者反复在两个工具之间横跳这个坑我踩过不止一次。最典型的是模型查天气没查到就反复查同一个城市每次都说“再试一次”。我的中断策略是记录每个工具的调用次数同一个工具连续调用超过 3 次就强制中断把已有结果汇总让模型直接回答。call_history {} def check_loop(tool_name): call_history[tool_name] call_history.get(tool_name, 0) 1 if call_history[tool_name] 3: return True return False除了次数限制还可以加时间窗口限制比如 30 秒内同一个工具调用超过 5 次就中断。这两个策略叠加基本能杜绝死循环。6.4 工具执行结果太长怎么处理有些工具返回的数据特别长比如数据库查询返回几百条记录直接塞回上下文会撑爆 token 限制。我的处理方式是在工具执行层做截断和摘要。截断是只保留前 N 条记录摘要是对结果做一个统计概述。def truncate_result(result, max_items10): if isinstance(result, list) and len(result) max_items: return { total: len(result), items: result[:max_items], note: f结果共 {len(result)} 条仅展示前 {max_items} 条 } return result这个处理要在工具执行完之后、回灌模型之前做。我一般把上限设在 10 到 20 条之间具体看单条数据的大小。如果单条数据本身就很大还要对字段做裁剪只保留关键字段。注意截断和摘要一定要在结果里明确标注“这是部分结果”否则模型可能会基于不完整的数据给出错误结论。我见过模型把“前 10 条”当成“全部 10 条”来回答闹出过笑话。7. 我踩过的坑与实战心得7.1 工具描述比工具实现更重要这是我最大的心得。刚开始做 Tool Use 的时候我把大量精力花在工具函数的实现上觉得只要功能正确就行。结果模型选工具的准确率一直上不去。后来我把每个工具的描述当成“给模型看的说明书”来写把功能、参数含义、适用场景、甚至不适用场景都写清楚准确率立刻上了一个台阶。举个例子我有个工具叫search_docs最初的描述是“搜索文档”。模型经常在用户问“今天天气怎么样”的时候也调它。后来改成“在内部知识库中搜索技术文档和产品说明不适用于查询实时数据或天气信息”误调用率直接降了一半。所以我的建议是工具描述要写到“一个新人看了就知道什么时候该用、什么时候不该用”的程度。7.2 错误信息要“对模型友好”工具执行失败时返回的错误信息不是给用户看的是给模型看的。所以错误信息要包含足够的上下文让模型能判断下一步怎么做。比如“参数 city 缺失”就比“参数错误”好得多模型看到前者会知道要补一个城市名看到后者只能瞎猜。我一般会在错误信息里包含三部分什么工具、什么参数、期望什么格式。比如“工具 get_weather 的参数 city 缺失请提供城市名称字符串。”这样模型下一轮生成时就能精准修正。7.3 日志要记全但别记敏感信息调试 Tool Use 的时候日志是你的救命稻草。我一般会记录模型原始输出、解析后的工具调用、执行参数、执行结果、回灌内容、最终回复。这条链路完整记下来出问题的时候一眼就能定位是哪一环断了。但日志里千万别记敏感信息。工具参数里可能包含用户隐私、内部数据、密钥之类的。我的做法是在日志层做脱敏对特定字段做掩码处理。这个习惯在生产环境里尤其重要别问我怎么知道的。7.4 面试手写时的取舍面试现场时间有限不可能把三种方案都完整实现。我的策略是选一种写完整另外两种写核心逻辑。比如方案二写完整的调用链路方案一写协议设计和解析器核心函数方案三画状态流转图加关键代码片段。这样既展示了深度又展示了广度。另外面试时一定要主动说边界情况。哪怕代码没写完你嘴上说“这里我会加一个超时控制”“这里我会做参数校验”面试官也会认为你考虑周全。手写代码考的不是代码量是思维完整度。8. 从面试题到生产系统的延伸思考8.1 工具注册与动态发现面试时工具列表是写死的但生产环境里工具是动态增减的。我后来把工具注册做成了一个独立的模块支持运行时注册和注销。每个工具注册时提供名称、描述、参数 schema 和执行函数调度层自动感知变化。这样新增一个工具不需要改调度代码只需要注册一下就行。class ToolRegistry: def __init__(self): self._tools {} def register(self, name, description, parameters, func): self._tools[name] { description: description, parameters: parameters, func: func } def unregister(self, name): self._tools.pop(name, None) def get_tool_specs(self): return [ { name: name, description: info[description], parameters: info[parameters] } for name, info in self._tools.items() ]这个设计让工具管理和业务逻辑彻底解耦。我现在的项目里工具注册是通过配置文件加装饰器自动完成的新增工具只需要写一个函数加一行装饰器非常省事。8.2 权限控制与审计生产环境的 Tool Use 必须考虑权限。不是所有用户都能调用所有工具比如发送邮件的工具应该只有特定角色能用。我的做法是在工具注册时加一个required_permission字段调度层在执行前检查当前用户是否有对应权限。审计则是记录谁在什么时候调用了什么工具、传了什么参数、得到了什么结果。这个记录不仅用于排查问题也是安全合规的刚需。我一般会把审计日志写到独立的存储里和业务日志分开方便长期保留和检索。8.3 性能优化的一点经验Tool Use 的性能瓶颈通常在模型调用次数上。每多一轮工具调用就多一次模型请求延迟成倍增加。我的优化思路是能并行就并行能缓存就缓存能预判就预判。并行前面讲过了。缓存是指对相同参数的相同工具调用做结果缓存比如天气查询同一个城市 5 分钟内的结果可以直接复用。预判是指根据用户问题的模式提前加载可能用到的工具减少冷启动时间。这三个手段叠加我在实际项目里把平均响应时间从 8 秒降到了 3 秒左右。8.4 可观测性建设最后说一个容易被忽视但极其重要的点可观测性。Tool Use 的链路比普通对话长得多出问题时如果只能看到最终回复根本没法定位。我一般会埋三类指标工具调用成功率、平均执行耗时、解析失败率。这三个指标能覆盖大部分异常情况。再配合链路追踪每次对话生成一个 trace_id从模型请求到工具执行到结果回灌每个环节都打上这个 id。排查问题时按 trace_id 一搜整条链路一目了然。这套东西在面试里不一定用得上但在实际工作中能帮你省下大量排查时间。提示可观测性建设不要等出问题才做。我吃过这个亏系统上线三个月后第一次出故障因为没有链路追踪排查了整整一天。后来补上之后同类问题十分钟就能定位。这个投入越早做回报越大。三种方案写下来我最深的体会是Tool Use 表面上是让模型调函数本质上是在设计一套“人机协作的协议”。协议设计得好模型和外部系统就能顺畅配合设计得不好就会陷入无尽的调试。面试官让你手写考的不是你能不能写出代码而是你有没有设计协议的思维。这个思维一旦建立起来不管换什么模型、什么平台你都能快速搭出一套可用的 Tool Use 系统。