☰
大模型Agent如何调用工具?最小可跑案例拆解完整闭环
2026/9/28 8:40:22 网站建设 项目流程

昨天有个朋友跑来问我:Agent 到底是怎么调用工具的?模型不是只会生成文字吗,它凭什么知道该调哪个 API,参数怎么填,调用完拿到结果之后又是怎么"顺着话头"继续回答用户的?这个问题问得特别好,因为它正好戳中了 Agent 开发里最核心、也最容易让人绕晕的一环——工具调用与结果回填。很多人跑过框架示例、调过几轮 prompt,但始终没搞清楚"模型输出一段 JSON 之后到底发生了什么",遇到 Agent 卡住、报错、答非所问就完全不知道从哪下手。这篇文章就用一个最小可跑的案例,把整条链路拆开讲清楚,适合正在学 Agent 开发、准备做自己的智能体项目的朋友。

1. Agent 和普通对话有什么本质区别

1.1 对话助手只会"说",Agent 会"做"

传统聊天机器人或者说纯对话式 AI,本质上就是"你把问题发过去,模型给你一段文字回复"。它当然也能写诗、能讲道理、能解释概念,但它的世界仅限于训练数据和上下文里写明的信息。你问它"今天北京天气怎么样",它要么说"我无法实时获取天气信息",要么凭训练记忆瞎编一个,因为它的训练数据里根本没有今天的气象数据。

Agent 不一样。Agent 的核心特征是"能对外部世界采取行动":它可以通过调用天气 API 拿到实时数据,可以通过搜索引擎接口查最新资料,可以执行一段 SQL 去查数据库,可以调用代码解释器算一道题,甚至可以调用另一个 Agent 去完成任务。这里的"调用"不是模型自己执行,而是模型在对话里"提出申请",由工程代码去实际执行,再把执行结果交还给模型。这就是 Agent 与普通对话的本质区别:模型负责思考和决策,外部代码负责行动和反馈,两者循环往复,直到问题解决。

1.2 工具调用在 Agent 工作流里处于什么位置

一个完整的 Agent 工作流,通常可以拆成五步:理解用户意图、规划任务拆解、选择工具、执行工具、整合结果。这里最容易被人忽略的一点是,工具调用并不是独立的一步,它夹在"决策"和"整合"之间,是整个循环的发动机。

我见过不少初学者画流程图画得很漂亮:意图识别→任务规划→工具选择→执行→生成回答,看着很顺。但实际写代码的时候才发现,这五步根本不是五个独立的模块,而是共用同一个模型、在同一个对话上下文中反复进行的。模型每输出一次内容,要么是调工具的"请求",要么是给用户的"最终回答",程序要做的只是判断"这次输出是哪种",然后决定是去执行工具还是把结果返回给用户。理解了这一点,后面所有代码就都顺了。

2. 工具调用背后的核心原理:一次完整的请求-决策-执行-反馈闭环

2.1 模型不执行工具,它只输出"调用申请"

先把这个最关键的概念说透:大模型本身不会执行任何工具,它连最简单的加法都算不对(或者说计算方式和我们不一样),更不可能自己去访问网络、查数据库。当你在 API 里开启了 tool calling / function calling 功能后,模型做的事其实是"在回复中输出一段结构化的调用申请",通常是一段 JSON,里面写明工具名和参数。

举个例子,用户问"北京天气怎么样",模型内部会经历一个推理过程:用户想知道天气→我有一个 get_weather 工具可以查天气→工具需要城市名参数→北京对应参数值是 beijing。然后它在回复里输出类似这样的内容:{"name": "get_weather", "arguments": "{"city": "beijing"}"}。

程序拿到这段 JSON 后,才会真正去调用你写好的 get_weather 函数,把 city 参数传进去,执行完拿到结果。整个过程中,模型始终只是个"提出请求的人",真正干活的是你的代码。这个边界如果不清楚,后面排查问题时会非常痛苦——很多人以为是模型执行出错,其实是自己代码没执行或者结果没正确回填。

2.2 工具描述(Tool Schema)就是给模型看的"使用说明书"

模型怎么知道有哪些工具、每个工具需要什么参数?靠的就是你在请求里传给它的工具描述,官方叫 tools 参数,也有人叫 function schema。你可以把它理解成给模型的一份"工具使用说明书",每个工具都包含三个关键信息:工具叫什么名字、这个工具是干什么的、需要哪些参数以及参数格式。

这块是工具调用能不能成功的核心。工具描述写得太笼统,模型就不知道该什么时候调;参数说明写得不清晰,模型就会传错参数或者漏传必填项。我见过最典型的错误是有人把 description 写成"For internal use"之类的话,模型根本不知道这个工具能干嘛,自然永远不调用它。描述应该用"当用户想要查询某个城市的天气时使用","务必填写城市中文名,例如北京、上海"这种明确、带触发条件和示例的写法。

2.3 执行结果如何"喂回"给模型

工具执行完成后,结果必须以一种特殊格式回填到对话历史里,这一环是整个"继续回答"的关键。在 OpenAI 兼容接口里,这个结果是一条 role 为 tool 的消息,并且必须携带 tool_call_id,用来对应之前模型的某次工具调用请求。为什么需要这个 id?因为一次对话里可能有多个工具并行调用,模型需要知道哪条结果对应哪个请求。

回填之后,模型会"看到"完整的对话链:用户问了什么→模型决定调哪个工具→工具返回了什么结果。基于这些信息,它才能继续推理,要么生成最终回答,要么发现结果还不够,继续发起下一轮工具调用。整个对话历史就像一场连续剧,模型每次读到的上下文都包含最新的"剧情进展"。

3. 从 0 到 1 手写一个带工具调用的 Agent

3.1 最简方案:不依赖框架,直接用官方 API

现在市面上有 LangChain、LlamaIndex、Dify、Coze 等各种框架和平台,封装得很完善,但我的建议是:第一次学工具调用,一定不要用框架,就用最原始的 API 手写一遍循环。因为框架把底层逻辑都藏起来了,模型输出、工具执行、结果回填这些关键动作你根本看不见,出了问题也不知道是哪一环的锅。手写一遍,哪怕代码丑一点,你也能把整条链路刻在脑子里。

下面我用 OpenAI 兼容接口的 Python SDK 为例,这个接口已经是事实标准,国内外很多大模型厂商的 API 都兼容它,你只需要改 base_url 和 api_key 就能切换到其他模型。

3.2 定义工具和执行函数

先定义两个最简单的工具,一个查天气,一个做计算,够用但不啰嗦:

import json from openai import OpenAI client = OpenAI( api_key="你的key", base_url="你的接口地址" # 用兼容OpenAI接口的服务 ) tools = [ { "type": "function", "function": { "name": "get_weather", "description": "查询指定城市的当前天气情况。当用户询问天气、温度、是否下雨等问题时使用。", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市中文名,例如:北京、上海、广州" } }, "required": ["city"] } } }, { "type": "function", "function": { "name": "calculate", "description": "执行四则运算,当用户提出数学计算需求时使用。", "parameters": { "type": "object", "properties": { "expression": { "type": "string", "description": "数学表达式,例如:12.5 * 4 + 2" } }, "required": ["expression"] } } } ] def get_weather(city: str) -> str: # 真实项目里这里会调用天气服务商的API # 这里用模拟数据演示 weather_map = { "北京": "晴,气温26℃,东南风2级", "上海": "多云,气温28℃,湿度65%", "广州": "阵雨,气温30℃,体感较热" } return weather_map.get(city, f"{city}暂无数据,可以尝试其他城市") def calculate(expression: str) -> str: # 注意:生产环境请使用安全可靠的表达式解析库 # 不要直接用eval处理用户输入,这里仅为演示 try: result = eval(expression) return f"{expression} = {result}" except Exception as e: return f"计算失败:{str(e)}" # 工具名到函数的映射表 TOOL_MAP = { "get_weather": get_weather, "calculate": calculate }

这里有个容易被忽略的细节:工具描述里一定要写清楚"什么时候用、参数格式是什么、有没有示例值",模型不是人,它判断是否调用工具全靠这段描述。我在实际项目中会把 description 写到 50 字以上,把触发条件、典型场景、注意事项全塞进去,宁可啰嗦也不要含糊。

3.3 主循环:判断模型是想调用工具还是想直接回答

核心的 Agent 循环,其实就是一个 while 循环,每次迭代做三件事:让模型基于当前对话历史生成回复、判断回复里有没有工具调用请求、有就执行并回填结果然后继续循环,没有就当作最终答案返回给用户。

def run_agent(user_query: str, max_iterations: int = 5): messages = [ {"role": "system", "content": "你是一个乐于助人的智能助手,可以通过工具获取实时信息。"}, {"role": "user", "content": user_query} ] for step in range(max_iterations): response = client.chat.completions.create( model="你使用的模型名", messages=messages, tools=tools, ) assistant_message = response.choices[0].message messages.append(assistant_message) # 没有工具调用请求,说明这就是最终回答 if not assistant_message.tool_calls: return assistant_message.content print(f"第{step + 1}轮:模型请求调用 {len(assistant_message.tool_calls)} 个工具") # 执行每一个工具调用 for tool_call in assistant_message.tool_calls: fn_name = tool_call.function.name fn_args = json.loads(tool_call.function.arguments) print(f" -> 调用 {fn_name},参数:{fn_args}") # 从映射表里找到对应的执行函数 if fn_name in TOOL_MAP: result = TOOL_MAP[fn_name](**fn_args) else: result = f"错误:未知工具 {fn_name}" # 关键一步:把执行结果以tool角色回填到对话历史 messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": str(result) }) # 继续下一轮循环,模型会基于最新的tool结果继续推理 return "已达到最大迭代次数,任务未能完成,请优化问题后重试。" if __name__ == "__main__": print(run_agent("北京天气怎么样?顺便帮我算一下 123.45 * 678"))

你只要跑一遍这个代码就会看到完整的调用过程:模型先输出一个工具调用申请,程序打印"调用 get_weather,参数:{'city': '北京'}",然后把结果回填,模型继续请求调 calculate,再回填,最后模型把两段信息整合成一句完整的回答返回。

这整段代码的核心就一个判断:if not assistant_message.tool_calls。它决定了 Agent 是"继续干活"还是"交作业"。我刚开始写的时候,每次都要打印 messages 看全部上下文,确认模型到底收到了什么,强烈建议你也这么做,比任何框架日志都直观。

4. 实战中的关键参数与细节调优

4.1 温度、轮数上限这些参数怎么设

参数看起来不起眼,实际影响却非常大。温度(temperature)控制模型输出的随机性,做工具调用时我 Generally 设成 0 或者 0.2 以下。为什么?因为工具调用本质上是"结构化决策",需要模型稳定地输出合法的 JSON 调用申请,而不是发挥创造力。温度太高,模型可能输出格式不规范、参数张冠李戴,甚至凭空编一个工具名出来。

最大迭代轮数(max_iterations)是另一个必须设的参数。工具调用的循环如果没有上限,模型可能陷入"调工具→看结果→再调工具"的死循环。我见过最夸张的情况是一个 Agent 连续调了二十多轮工具,最后返回的还是工具调用请求,完全停不下来。给个 3 到 8 轮的上限比较合理,既能完成多步骤任务,又不会让用户等太久、花太多 token。

4.2 工具结果太长、格式异常怎么办

工具返回的结果五花八门:可能是几十万字的网页正文,可能是格式乱掉的 CSV,可能是带转义字符的 JSON。这些内容直接回填给模型,一方面浪费 token,另一方面会严重干扰模型的注意力。我的习惯是:回填之前先做一轮"结果预处理"。

预处理通常包括三步:截断、结构化、错误标注。超长文本截断到几千字以内,把关键信息提取出来;结构化数据转成简洁的文本描述,比如 JSON 转成"字段:值"的列表;如果工具执行失败,就把错误信息包装成标准格式返回给模型,让模型知道"刚才那步没成功,你可以换个方式重试或如实告诉用户"。

这里有我踩过的一个坑:刚开始我把工具错误直接抛异常,整个 Agent 就崩了,也就是很多人遇到的"agent execution terminated due to error"。后来改成把错误信息当作普通 tool 结果回填,模型反而能优雅地处理,比如告诉用户"查询失败了,请检查网络"或者换个工具重试。记住一个原则:工具可以失败,但 Agent 不能因为工具失败而死掉。错误也是信息,把它交还给模型,让模型决定下一步。

5. 常见问题与排查技巧实录

5.1 模型不调用工具或乱调用工具

这是出现频率最高的问题。模型面对该调工具的场景就是不调,或者明明有合适的工具却偏要胡编乱造一个答案。我排查这类问题的顺序很固定:

先看工具描述是不是够清楚。描述里有没有写明白触发条件?参数说明里有没有给示例?我会把所有工具的描述打出来逐字读一遍,看它是"给同事看的需求文档"还是"给模型看的说明书"。其次看模型本身是否支持工具调用,不是所有模型都支持 function calling,有些旧版模型你传了 tools 参数它也只会忽略掉。最后看是不是上下文里已经有足够信息,如果模型觉得历史消息里已经有答案了,它就不会再去调工具,这时候你可以让系统提示词明确要求"必须基于最新工具结果回答"。

5.2 参数解析失败和工具执行报错

模型输出的 arguments 是一段 JSON 字符串,但模型偶尔会生成不标准的 JSON,比如漏掉引号、多了个逗号。json.loads 直接解析就会报错。我的处理方式是写一个容错解析函数,去掉 markdown 代码块标记,尝试修复常见格式问题,解析不了再返回错误信息让模型自己修正。

工具执行报错也是家常便饭:API 超时、网络抖动、数据为空、第三方服务限流。之前说过了,不要抛异常,把错误信息转成字符串回填给模型,并附上一些指导性的话,比如"该工具暂时不可用,建议尝试其他方式"。模型很擅长顺着提示调整方案,你给它的错误信息越完整,它下一步的决策就越靠谱。

5.3 多工具协作和上下文管理

复杂任务往往需要多个工具配合,比如先查天气再根据天气推荐穿搭,或者先搜索资料再总结成报告。模型会在一次回复里发起多个工具调用(parallel tool calls),程序要逐个执行,把每条结果用正确的 tool_call_id 回填。这个 id 的对应关系出了问题,模型就会把"上海的天气"当成"北京天气的计算结果",回答自然全乱了。

上下文管理同样要注意。每轮工具调用都会往对话历史里追加两条消息,几十轮下来上下文会越来越长,既烧 token 又可能超出模型上下文窗口。我常用的做法是做历史消息压缩或裁剪:把早期的工具调用记录折叠成一句摘要,只保留最近几轮的完整细节。具体怎么折,可以在系统提示词里让模型定期总结前面的关键信息。

常见问题排查方向解决办法
模型从不调用工具工具描述不清、模型不支持工具调用重写描述,增加触发条件与示例;更换支持 function calling 的模型
模型胡编参数参数 schema 不严格、缺少校验增加 required 字段、枚举约束;执行前校验参数
工具结果被忽略结果格式混乱、太长预处理截断、结构化、突出关键信息
Agent 陷入死循环缺少轮数上限、结果不明确设置 max_iterations,让工具结果包含"是否需要继续"的判断依据
工具报错导致任务中断异常直接抛出把错误转为 tool 消息回填,让模型自主决策下一步
多工具结果串线tool_call_id 对应错误确保每条 tool 消息都带正确 id,打印日志核对

6. 一些实操体会和后续扩展方向

把最小案例跑通只是第一步,真正要做一个能用的 Agent,还有几个方向值得继续深入:一是接入长期记忆,让 Agent 在多次对话之间记住用户偏好和历史决策,这属于 Agent 记忆体系里的长期记忆部分,短期记忆靠对话上下文,长期记忆需要向量数据库或者外部存储;二是做工具调用的权限控制和结果校验,生产环境一定要对模型传入的参数做白名单校验,不要把 eval、shell 这类高危操作直接暴露给模型,安全这根弦不能松;三是可以用 ReAct 的思维链模式替代隐式的推理,让模型把"想法"显式输出出来,调试起来会直观得多。

回到最开始那个问题:Agent 怎么调用工具,并根据执行结果继续回答?答案就一句话——模型负责输出工具调用申请,程序负责执行并把结果作为 tool 消息回填到对话历史里,模型读取完整历史后继续推理,如此循环直到它不再请求工具、直接给出最终回答。这个循环听起来简单,但每一个环节都有它的细节和坑。我个人最大的体会是:别急着上框架,先把循环手写跑通,把 messages 打出来亲眼看一遍模型到底收到了什么,你对 Agent 的理解会瞬间上一个台阶。之后再去用框架,你会发现那些封装好的 Agent 平台,底层逻辑其实也就是这么回事。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询