1. 面试现场:从“八股文”到“真刀真枪”的转变
这几年技术面试的风向变得特别快。早些年大家准备面试,背一背“进程与线程的区别”、“TCP 三次握手”,再刷几百道算法题,基本就能应付大多数场面。但这套打法在最近一两年明显失灵了,尤其是涉及到 AI 应用开发、大模型落地相关的岗位,面试官越来越不满足于听你讲概念,而是直接把电脑推到你面前:“来,手写一个 Tool Use 的实现,语言不限,思路讲清楚就行。”
我第一次遇到这种要求的时候,脑子是懵的。Tool Use,说白了就是让大模型能够调用外部工具——查天气、算数学、搜数据库、发邮件。听起来好像不难,不就是让模型输出一段 JSON,然后我去解析执行吗?但真当你坐在那里,面对一张白纸或者一个空白的编辑器,面试官在旁边盯着你,你会发现要考虑的东西远比想象中多:模型怎么知道有哪些工具可用?工具的参数格式怎么定义?模型输出的调用请求怎么解析?执行结果怎么回传给模型?多轮调用怎么管理?错误怎么处理?
那次面试我用了三种方案来回答,从最朴素的字符串匹配,到基于结构化输出的函数调用,再到基于 Agent 框架的完整实现。面试官最后说了一句话我印象很深:“你不是在写代码,你是在设计一个协议。”这句话点醒了我,Tool Use 的本质确实不是简单的“调个 API”,而是一套模型与外部世界交互的协议设计。
这篇文章就把我当时面试的思路完整还原出来,再加上我后来在实际项目中踩过的坑和总结的经验。如果你也在准备类似的面试,或者正在做 AI 应用开发,需要让模型调用外部能力,那这篇内容应该能帮你少走不少弯路。我会从最基础的原理讲起,把三种方案的实现细节、适用场景、优缺点都拆开揉碎,最后再分享一些只有真正上手做过才会知道的避坑技巧。
2. 先搞明白:Tool Use 到底在解决什么问题
2.1 大模型的三条“硬伤”与工具调用的必要性
要理解 Tool Use 的价值,得先承认大模型有几个绕不过去的硬伤。
第一个是知识截止。模型的训练数据是有时间边界的,你问它今天的天气、最新的股价、某个刚发布的库的用法,它要么不知道,要么一本正经地胡说八道。这不是模型笨,是它的知识被冻结在了训练完成的那一刻。
第二个是计算不可靠。你让模型算 1234 乘以 5678,它可能会给你一个看起来很像那么回事但完全错误的答案。因为模型本质上是概率模型,它在“预测下一个 token”,而不是在“做数学运算”。对于需要精确计算的任务,靠模型自己算就是碰运气。
第三个是无法与外部世界交互。模型不能帮你发一封邮件、不能查数据库、不能调用你公司内部的 API、不能操作文件系统。它只能输出文本,而文本本身不产生任何实际动作。
Tool Use 就是来解决这三个问题的。它的核心思路很朴素:既然模型自己不擅长这些事,那就让它学会“求助”。当模型遇到自己搞不定的问题时,它不硬答,而是输出一个结构化的请求,告诉外部系统“我需要调用某个工具,参数是这些”,外部系统执行完把结果返回给模型,模型再基于结果继续回答。
打个比方,模型就像一个知识渊博但手脚被绑住的顾问,Tool Use 就是给他配了一个助理。顾问说“帮我查一下上个月的销售数据”,助理去查了把结果拿回来,顾问再基于数据给出分析。顾问不需要自己会查数据库,他只需要知道“什么时候该让助理去查”以及“怎么把需求说清楚”。
2.2 一个完整的 Tool Use 闭环包含哪些环节
很多人以为 Tool Use 就是“模型输出 JSON,我解析执行”,这只说对了一半。一个完整的闭环至少包含五个环节:
- 工具注册与描述:你得告诉模型有哪些工具可用,每个工具是干什么的,需要什么参数。这个描述的质量直接决定了模型能不能正确选择工具。
- 意图识别与工具选择:模型根据用户的输入,判断是否需要调用工具,以及调用哪个工具。
- 参数生成与格式化:模型生成调用工具所需的参数,并且要符合预定义的格式。
- 执行与结果返回:外部系统解析模型的输出,执行实际的操作,把结果整理成模型能理解的格式返回。
- 结果整合与最终回答:模型基于工具返回的结果,生成最终给用户的回答。
这五个环节里,任何一个出问题都会导致整个流程失败。比如工具描述写得含糊,模型就会选错工具;参数格式没定义清楚,解析就会失败;返回结果太长,可能会超出模型的上下文窗口。所以 Tool Use 不是一个单点技术,而是一套需要精心设计的系统工程。
2.3 为什么面试官偏爱这道题
站在面试官的角度,让你手写 Tool Use 是一道性价比极高的题。它能在短时间内考察你多个维度的能力:
- 对大模型原理的理解:你是否清楚模型的输出本质上是 token 序列,你是否理解结构化输出和自由文本输出的区别。
- 协议设计能力:你能否设计一套清晰的工具描述格式和调用协议,这直接反映你的抽象能力。
- 工程实现能力:解析、错误处理、多轮管理,这些都是实打实的编码功夫。
- 边界情况考虑:模型输出了非法 JSON 怎么办?工具执行失败了怎么办?模型陷入循环调用怎么办?
- 技术选型判断:你选择哪种方案,为什么选它,这体现了你的技术判断力。
所以这道题没有标准答案,面试官想看的是你的思考过程和技术品味。下面我就把当时用的三种方案逐一拆解。
3. 方案一:基于提示词工程的字符串解析方案
3.1 核心思路与实现原理
这是最原始、最不依赖任何框架的方案,核心思路就一句话:在提示词里约定好输出格式,然后从模型返回的文本里把工具调用请求抠出来。
具体来说,你需要在系统提示词里写清楚:你有哪些工具可用,每个工具的参数是什么,当你需要调用工具时,请按照特定的格式输出。比如约定用<tool_call>标签包裹 JSON:
你可以使用以下工具: - get_weather(city: string): 查询指定城市的天气 - calculate(expression: string): 计算数学表达式 当你需要调用工具时,请输出: <tool_call> {"name": "工具名", "arguments": {"参数名": "参数值"}} </tool_call>然后模型返回的文本里如果包含这个标签,你就用正则表达式把它提取出来,解析 JSON,执行对应的函数,再把结果拼回对话历史里。
这个方案的实现大概长这样:
import re import json def parse_tool_call(text): pattern = r'<tool_call>\s*(\{.*?\})\s*</tool_call>' match = re.search(pattern, text, re.DOTALL) if not match: return None try: return json.loads(match.group(1)) except json.JSONDecodeError: return None def execute_tool(tool_call, tools_registry): name = tool_call.get("name") args = tool_call.get("arguments", {}) if name not in tools_registry: return {"error": f"未知工具: {name}"} try: result = tools_registry[name](**args) return {"result": result} except Exception as e: return {"error": str(e)}3.2 提示词设计的几个关键细节
这个方案看起来简单,但提示词的设计有很多讲究,写不好模型根本不按你的格式来。
第一,工具描述要具体且互斥。如果你有两个工具,一个叫search,一个叫query,模型很容易搞混。描述里要写清楚每个工具的适用场景,最好给出正例和反例。比如“查询天气用 get_weather,不要用 search”。
第二,参数类型要明确。模型对类型的理解有时候很模糊,你要在描述里写清楚是字符串、数字还是布尔值。对于枚举类型的参数,把所有可选值都列出来。
第三,给出完整的输出示例。模型是模仿高手,你给它一个标准的输出样例,它照着模仿的准确率会高很多。样例要覆盖单工具调用、多工具调用、不需要调用工具这几种情况。
第四,强调格式的严格性。明确告诉模型“必须严格按照上述格式输出,不要添加任何额外的解释文字”。否则模型很可能在 JSON 前后加一堆“好的,我来帮你查询”之类的话,增加解析难度。
3.3 这个方案的优缺点与适用场景
先说优点。零依赖,不需要任何特定的模型 API 支持,只要模型能输出文本就能用。灵活,格式完全由你定义,想怎么改就怎么改。透明,整个流程你都能看到,调试起来直观。
再说缺点,而且缺点很明显。解析脆弱,模型稍微不听话,输出格式偏一点,正则就匹配不上。没有类型保证,模型可能给你一个字符串类型的数字,或者漏掉必填参数。多轮调用管理麻烦,你得自己维护对话历史,判断什么时候该停止调用。提示词占用上下文,工具描述和格式说明会吃掉不少 token。
这个方案适合什么场景?适合快速原型验证,或者你用的模型不支持原生的函数调用能力。在实际生产环境里,如果模型本身支持结构化输出,我不太推荐用这个方案,维护成本太高。
实操心得:用这个方案的时候,一定要在解析失败时做重试。我的做法是把解析错误的信息作为新的用户消息发回给模型,让它重新按格式输出。通常重试一两次就能成功,但一定要设置最大重试次数,防止死循环。
4. 方案二:基于原生函数调用能力的结构化方案
4.1 函数调用协议的工作机制
方案一的痛点在于“靠约定”,而方案二的核心进步在于“靠协议”。现在主流的大模型服务基本都支持原生的函数调用能力,你不需要在提示词里教模型怎么输出格式,而是通过 API 的参数把工具定义传进去,模型会在内部处理好工具选择,直接返回结构化的调用请求。
这个机制的工作流程是这样的:你在调用模型时,除了传消息,还传一个tools参数,里面用 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"] } } } ] response = model.chat(messages=messages, tools=tools) if response.tool_calls: for call in response.tool_calls: result = execute(call.function.name, call.function.arguments) messages.append({ "role": "tool", "tool_call_id": call.id, "content": result }) response = model.chat(messages=messages, tools=tools)4.2 JSON Schema 定义工具的实战技巧
方案二的关键在于工具定义的 JSON Schema 写得好不好。Schema 写得好,模型选工具又准又快;写得烂,模型要么选错工具,要么参数填错。
description 字段是重中之重。这是模型判断“什么时候该用这个工具”的唯一依据。写 description 的时候要站在模型的角度想:用户在什么情况下会需要这个功能?把典型的使用场景写进去。比如不要只写“查询天气”,而要写“当用户询问某个城市的天气、温度、是否下雨时使用此工具”。
参数描述要消除歧义。如果参数是日期,要写清楚格式是YYYY-MM-DD还是MM/DD/YYYY。如果参数有单位,要写清楚是摄氏度还是华氏度。如果参数有取值范围,要写清楚上下限。我见过太多因为参数描述不清导致模型传错值的案例。
required 字段要谨慎。只把真正必需的参数放进 required,可选参数给默认值。如果把太多参数标为必需,模型可能会为了凑参数而编造值。
枚举类型要列全。如果一个参数只能是几个固定值之一,用 enum 列出来,模型就不会自由发挥了。
下面是一个对比表格,展示写得差和写得好的工具定义:
| 维度 | 差的写法 | 好的写法 |
|---|---|---|
| 功能描述 | “搜索” | “根据关键词搜索产品数据库,返回匹配的产品列表,适用于用户询问产品信息、价格、库存时” |
| 参数描述 | “city: 城市” | “city: 城市名称,使用中文全称,如‘北京市’而非‘北京’” |
| 类型定义 | “count: 数量” | “count: 返回结果数量,整数,范围 1-50,默认 10” |
| 枚举值 | “type: 类型” | “type: 搜索类型,可选值:product(产品)、order(订单)、user(用户)” |
4.3 多轮工具调用的状态管理
方案二相比方案一,最大的工程挑战变成了多轮调用的状态管理。因为模型可能连续调用多个工具,也可能调用一个工具后基于结果再调用另一个,你需要维护一个清晰的状态机。
核心要管理的东西包括:对话历史(包括用户的原始问题、模型的工具调用请求、工具的执行结果)、当前轮次(防止无限循环)、已调用工具的记录(防止重复调用同一个工具)。
我的做法是维护一个消息列表,每次模型返回工具调用请求时,把请求和对应的执行结果都追加进去,然后再次调用模型。同时设置一个最大轮次限制,比如 10 轮,超过就强制让模型基于已有信息给出回答。
MAX_ROUNDS = 10 rounds = 0 while rounds < MAX_ROUNDS: response = model.chat(messages=messages, tools=tools) if not response.tool_calls: break for call in response.tool_calls: result = execute(call.function.name, call.function.arguments) messages.append({"role": "tool", "tool_call_id": call.id, "content": result}) rounds += 1 if rounds >= MAX_ROUNDS: messages.append({"role": "user", "content": "请基于已有信息直接回答,不要再调用工具。"}) response = model.chat(messages=messages)注意事项:工具返回的结果一定要做长度控制。有些工具返回的数据特别长,比如查询数据库返回几百条记录,直接塞回上下文会瞬间撑爆 token 限制。我的做法是在工具执行层做截断或摘要,只返回最相关的部分,并在结果里注明“已截断”。
5. 方案三:基于 Agent 框架的完整编排方案
5.1 从“单次调用”到“自主规划”的跃迁
方案一和方案二本质上都是“被动响应式”的:用户问一个问题,模型判断要不要调工具,调完给答案。但真实场景往往更复杂,用户的需求可能需要多个步骤、多个工具协作才能完成。比如“帮我分析一下上个月的销售情况,如果同比下降超过 10% 就发邮件通知我”,这里面涉及查数据库、做对比计算、条件判断、发邮件四个步骤。
方案三的思路是把 Tool Use 升级成 Agent 编排。模型不再只是“选择工具”,而是“规划任务”。它会先把复杂任务拆解成子任务,然后决定每个子任务用什么工具,按什么顺序执行,中间结果怎么传递。
这个方案通常需要借助 Agent 框架来实现,比如 ReAct(推理+行动)模式、Plan-and-Execute 模式等。核心思想是给模型一个“思考-行动-观察”的循环:模型先思考当前该做什么,然后选择一个行动(调用工具),观察行动的结果,再决定下一步。
5.2 ReAct 循环的实现骨架
ReAct 是目前最常用的 Agent 编排模式,它的核心是一个循环:Thought(思考)→ Action(行动)→ Observation(观察)→ 再 Thought,直到任务完成。
实现上,你需要给模型一个特殊的提示词模板,引导它按这个格式输出:
你可以使用以下工具:[工具列表] 请按以下格式回答: 思考:我需要做什么 行动:工具名[参数] 观察:工具返回的结果(由系统填充) 当你得出最终答案时,用: 最终答案:你的回答然后你的代码循环解析模型的输出,遇到“行动”就执行工具,把结果作为“观察”拼回去,继续下一轮,直到出现“最终答案”。
def react_loop(question, tools, max_steps=15): prompt = build_react_prompt(question, tools) for step in range(max_steps): output = model.generate(prompt) if "最终答案:" in output: return extract_final_answer(output) action = parse_action(output) if action: observation = execute_tool(action, tools) prompt += f"\n{output}\n观察:{observation}\n" else: prompt += f"\n{output}\n请继续思考或给出最终答案。\n" return "达到最大步数限制,任务未完成。"5.3 任务规划与工具编排的进阶玩法
ReAct 是“走一步看一步”,适合步骤不太确定的探索性任务。但如果任务流程相对固定,可以用 Plan-and-Execute 模式:先让模型制定一个完整的计划,把任务拆成有序的步骤列表,然后逐步执行,每执行完一步检查是否需要调整计划。
这种模式的好处是全局视野更强,不会因为中间某一步的结果而迷失方向。缺点是如果计划制定得不好,后面全盘皆输。所以通常会在执行过程中加入“重新规划”的机制,允许模型在发现计划不可行时重新制定。
还有一种更高级的玩法是多 Agent 协作。把不同的工具分配给不同的 Agent,每个 Agent 负责一个领域,由一个协调者 Agent 来分配任务。比如一个负责查数据的 Agent、一个负责分析的 Agent、一个负责发通知的 Agent。这种架构适合工具数量特别多、职责边界清晰的场景。
不过要提醒一句,Agent 框架虽然强大,但调试难度和不确定性也成倍增加。模型可能在循环里绕圈子,可能调用不存在的工具,可能把简单任务复杂化。我在实际项目里的经验是:能用方案二解决的,就不要上方案三。Agent 编排是给真正复杂的多步骤任务准备的,杀鸡用牛刀反而容易出问题。
6. 三种方案横向对比与选型建议
6.1 多维度对比表格
把三种方案放在一起对比,能更清楚地看出各自的定位:
| 对比维度 | 方案一:字符串解析 | 方案二:原生函数调用 | 方案三:Agent 编排 |
|---|---|---|---|
| 实现复杂度 | 低 | 中 | 高 |
| 依赖条件 | 无特殊要求 | 模型需支持函数调用 | 需 Agent 框架 |
| 解析可靠性 | 低,依赖格式约定 | 高,结构化返回 | 中,依赖循环控制 |
| 多工具协作 | 需手动管理 | 支持,需状态管理 | 原生支持 |
| 复杂任务规划 | 不支持 | 不支持 | 支持 |
| 调试难度 | 低 | 中 | 高 |
| 上下文消耗 | 高(提示词占用量大) | 中 | 高 |
| 适用场景 | 原型验证、简单场景 | 生产环境、单步调用 | 复杂多步任务 |
| 推荐指数 | 两颗星 | 四颗星 | 三颗星(按需) |
6.2 选型决策树:什么场景用什么方案
选型其实可以按几个问题来决策:
第一个问题:你的模型支持原生函数调用吗?如果支持,直接上方案二,不要犹豫。方案一在可靠性上完全没法比。如果不支持,那只能先用方案一顶着,同时考虑换模型。
第二个问题:你的任务需要多步规划吗?如果用户的一个请求需要多个工具按顺序协作,且步骤不固定,那考虑方案三。如果只是单次工具调用,方案二足够。
第三个问题:你对稳定性的要求有多高?生产环境对稳定性要求高,方案二的确定性更强。方案三的不确定性较大,需要更多的测试和兜底机制。
第四个问题:你的团队有 Agent 开发经验吗?方案三的调试成本很高,如果团队没有相关经验,贸然上马可能会陷入无尽的调试泥潭。
6.3 面试时怎么回答才加分
回到面试场景,面试官让你手写 Tool Use,你如果能把三种方案都讲清楚,并且说明各自的适用场景和选型理由,基本就稳了。
我的建议是:先讲方案二,因为它是当前的主流实践。把函数调用的协议、JSON Schema 的设计、多轮状态管理讲透。然后提一下方案一,说明它是没有原生支持时的降级方案,以及它的局限性。最后提方案三,展示你对复杂场景的思考,但明确说明它适合什么场景、有什么风险。
这样回答的好处是:既展示了你的工程能力(方案二的细节),又展示了你的技术视野(知道有方案一和方案三),还展示了你的判断力(知道什么场景用什么)。面试官想听的从来不是“你会几种方案”,而是“你为什么选这个方案”。
7. 实操避坑指南与常见问题排查
7.1 工具描述写不好导致模型选错工具
这是最常见的问题。模型选错工具,十有八九是工具描述的问题,而不是模型的问题。
排查思路:先把所有工具的描述拿出来,假装自己是一个完全不了解业务的人,只看描述,能不能准确判断每个工具的使用场景?如果连你都判断不了,模型更判断不了。
解决技巧:给每个工具的描述里加上“什么时候用”和“什么时候不用”。比如“查询订单状态用 get_order_status,不要用 search_orders,后者只用于模糊搜索订单列表”。另外,工具名称本身也要有区分度,不要用query1、query2这种无意义的名字。
7.2 参数格式错误与类型不匹配的修复
模型传错参数类型是另一个高频问题。比如需要整数的地方传了字符串,需要数组的地方传了单个值。
解决技巧:在 JSON Schema 里把类型定义得尽可能严格,用 enum 限制取值范围,用 pattern 限制字符串格式。同时在工具执行层做参数校验和类型转换,比如把字符串"10"自动转成整数10。如果转换失败,把错误信息返回给模型,让它重新生成参数。
实操心得:我习惯在工具执行层加一层“参数清洗”,对每个参数做类型检查和默认值填充。这样即使模型偶尔传错,也能兜住大部分情况,不至于整个流程崩溃。
7.3 模型陷入循环调用怎么破
方案三最容易出现的问题就是模型在循环里出不来,反复调用同一个工具,或者在不同的工具之间来回跳。
解决技巧:设置最大轮次限制是必须的,但光限制轮次不够,还要检测重复调用。我的做法是记录每次调用的工具名和参数,如果发现连续两次调用完全相同,就强制中断,把已有信息整理后让模型直接回答。另外,在提示词里明确告诉模型“不要重复调用已经成功执行过的工具”。
7.4 工具返回结果过长撑爆上下文
这个问题在查询类工具上特别常见。用户问“帮我看看最近的订单”,工具返回了 500 条记录,直接塞回上下文,token 瞬间爆掉。
解决技巧:在工具执行层做结果处理。可以按相关性排序后截断,只返回前 N 条;也可以做摘要,把关键信息提取出来;还可以分页,先返回第一页,告诉模型“还有更多结果,如需继续请指定页码”。核心原则是:返回给模型的结果要精炼,只包含模型做决策所需的最少信息。
7.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决方案 |
|---|---|---|---|
| 模型不调用工具,直接回答 | 工具描述不清晰或提示词未强调 | 检查工具 description 是否说明了使用场景 | 补充场景描述,在系统提示词里强调“需要实时信息时必须调用工具” |
| 解析工具调用失败 | 输出格式不符合约定 | 打印模型原始输出,检查格式偏差 | 方案一加强格式约束和重试;方案二检查 API 参数 |
| 参数类型错误 | Schema 定义不严格 | 检查 JSON Schema 的 type 和 required | 严格定义类型,执行层做类型转换和校验 |
| 循环调用同一工具 | 缺少循环检测机制 | 记录调用历史,检查是否重复 | 设置最大轮次,检测重复调用并中断 |
| 上下文超限 | 工具返回结果过长 | 检查工具返回的数据量 | 截断、摘要或分页返回结果 |
| 多轮调用后回答质量下降 | 对话历史太长,模型注意力分散 | 检查消息列表长度 | 压缩历史消息,只保留关键信息 |
8. 我踩过的坑与实战经验总结
8.1 工具数量不是越多越好
刚开始做的时候,我恨不得把所有能用的功能都封装成工具,觉得工具越多模型能力越强。结果发现工具一多,模型选错的概率直线上升。后来我把工具数量控制在 10 个以内,把一些低频功能合并或者去掉,准确率明显提升。
经验是:工具的描述空间是有限的,工具越多,每个工具分到的“注意力”就越少。与其堆数量,不如把核心工具做精。如果确实需要很多工具,考虑用方案三的多 Agent 架构,把工具分组管理。
8.2 错误处理比正常流程更重要
正常流程谁都能写,但 Tool Use 的稳定性恰恰取决于错误处理。模型输出格式错了怎么办?工具执行超时了怎么办?网络断了怎么办?这些边界情况如果不处理,线上环境分分钟教你做人。
我的做法是给每个环节都加兜底:解析失败就重试,重试失败就降级为让模型直接回答;工具执行超时就返回超时信息让模型决定下一步;网络异常就捕获异常并记录日志。核心原则是:任何单点失败都不能让整个流程崩溃。
8.3 日志记录是调试的生命线
Tool Use 的调试比普通功能难得多,因为中间涉及模型的“黑盒”决策。你只能看到输入和输出,看不到模型为什么这么选。所以完整的日志记录至关重要。
我习惯记录这些东西:每次模型调用的完整输入输出、每次工具调用的名称参数和结果、每次解析的成功失败情况、整个流程的轮次和耗时。这些日志在排查问题时能帮你快速定位是模型的问题、解析的问题还是工具的问题。
8.4 给准备面试的朋友几句实在话
如果你正在准备这类面试,我的建议是:不要只背概念,一定要动手写一遍。三种方案都自己实现一个最小可运行的版本,哪怕代码很粗糙。只有真正写过,你才能体会到方案一解析失败时的抓狂、方案二状态管理的繁琐、方案三循环控制的微妙。
面试的时候,面试官更看重的是你的思考过程,而不是代码的完美程度。你可以一边写一边说:“这里我考虑用正则解析,但我知道它很脆弱,如果模型支持函数调用我会优先用方案二。”这种表达方式比闷头写完再解释要好得多。
最后说一个我自己的体会:Tool Use 这个技术点,表面上是“让模型调工具”,本质上是在设计一套人机协作的协议。你要考虑模型能理解什么、不能理解什么,要考虑信息怎么传递、错误怎么恢复。把这套协议设计好了,模型的能力边界就能大大扩展。这也是为什么面试官喜欢考这道题——它考的不是某个 API 的用法,而是你设计系统的能力。