当前位置: 首页 > news >正文

Agent的诞生(二):让模型开始调用工具

让模型开始调用工具

1. 让模型开始调用工具

第一篇里,我先让 Agent 跑通了最基础的多轮对话,但只会对话还不够。如果模型只能基于已有上下文回答问题,那与普通聊天应用并无本质区别;只有模型能借助工具接触外部环境时,Agent 才真正具备“做事”的能力。

本篇围绕四个问题展开:

  1. Agent 如何把工具暴露给模型
  2. Agent 如何识别模型发起的工具调用
  3. Agent 如何把工具结果回填给模型
  4. 这个过程为什么会循环下去,直到模型给出最终回答

回答完这些问题便可以理清模型调用工具的链路:Agent 暴露工具 → 模型发起调用 → Agent 执行工具 → Agent 回填结果 → 模型继续推理 / 返回最终回答,利用流程图可以更直观的了解这个过程。

2. Agent 如何把工具暴露给模型

要让模型调用工具,第一步不是执行工具,而是告诉模型:当前有哪些工具可用,它们分别能做什么。

实现上,Agent 会把可用工具整理成一份结构化清单,并随着对话请求一起发送给模型。这份清单至少包含工具名称功能描述参数定义。定义越清晰,模型越容易判断该用哪个工具、该传什么参数。

从接口层面看,这份清单通过模型 API 请求中的tools参数传递给模型。模型“看到”的不是本地函数实现,而是一份由 Agent 整理好的工具说明。

所谓“给模型加工具”,就是先把函数转换成模型能理解的描述结构。工具名称是否稳定、用途说明是否明确、参数字段是否具体,都会直接影响调用效果,这是工具能否被正确调用的关键。

# 工具清单
TOOLS = [
{
"type": "function",
"function": {
"name": "get_system_date",
"description": "获取系统当前日期和时间,格式为 yyyy-MM-dd HH:mm:ss。",
"parameters": {
"type": "object",
"properties": {}
}
}
},
...
]
# 模型API调用
response = client.chat.completions.create(
model=model,
messages=messages,
tools=TOOLS,
stream=False,
)

3. Agent 如何识别模型发起工具调用

模型拿到工具清单后,并不会每一轮对话都调用工具。很多时候,它仍然会直接返回普通文本。对 Agent 来说,下一个关键问题是:如何判断模型返回的是给用户的推理结果,还是工具调用请求。

在模型 API 接口里(本文使用的 OpenAI 接口)有一些字段可以作为判断依据,其中finish_reason可以检查模型的回应是要调用工具还是推理结果,tool_calls则包含模型希望调用的工具名称参数信息。Agent 根据这些字段识别调用请求,再决定执行哪个工具。

这里有一个关键点:真正发起调用意图的是模型,Agent 的职责是识别这个意图并继续处理。它更像是模型与外部能力之间的解释层和执行层。

while True:
....
finish_reason = response.choices[0].finish_reason
message = response.choices[0].message
tool_call = message.tool_calls[0]
# 判断模型是否调用工具
if finish_reason == 'stop' or not tool_calls: # 如果是调用工具 finish_reason = 'tool_calls'
print("AI > " + message.content)
break
# 获取工具名称和参数
tool_name = tool_call.function.name
tool_arguments = json.loads(tool_call.function.arguments or "{}")
# 调用工具获取结果
tool_result = execute_tool(tool_name, tool_arguments)
...

4. Agent 如何把工具结果回填给模型

识别到工具调用请求后,Agent 会执行对应工具,但执行工具不是终点。模型调用工具,是为了获取原本拿不到的外部信息;Agent 还必须把结果回填上下文,再发送给模型。

这一步 Agent 需要收集工具运行的标准输出错误信息,并组织成一条新的上下文消息。模型对每个调用的工具都有ID标识,回填工具结果时要与ID对应。tool_call_id字段记录调用工具的ID标识,content字段则承载具体结果。只有结果被正确回填,模型才能基于新信息继续推理。

# 回填执行结果
messages.append(
{
"role": "tool",
"tool_call_id": tool.id,
"content": tool_result
}
)

5. 这个过程为什么会持续到模型直接回答为止

模型收到一次工具执行结果后,不一定会立刻给出最终回答。模型会先根据新信息继续判断:如果信息还不够,就再次发起工具调用;如果信息已经足够,则停止调用工具并生成最终推理结果。

Agent 需要循环处理这条链路:暴露工具、识别调用、执行工具、回填结果,再把结果交还给模型继续推理。只要模型仍然需要外部信息,这个过程就会持续;直到某一轮里,模型不再请求工具,而是返回面向用户的回答,这一轮任务才算结束。

工具调用闭环不仅要能跑通,还要有边界控制,实现时需要限制最大调用次数。不然一旦模型持续重复请求工具,系统就可能陷入无限循环,token 就会快速消耗。

整个流程框架如下图:

Agent 循环代码:

def run_agent_turn(messages):
# 每轮对话重置工具调用次数
tool_call_count = 0
tool_limit_reached = False
while True:
finish_reason, assistant_message = request_chat_completion(messages)
messages.append(assistant_message)
tool_calls = assistant_message.tool_calls or []
if finish_reason == 'stop' or not tool_calls:
print("AI > " + (assistant_message.content or ""))
break
if tool_limit_reached:
for tool_call in tool_calls:
messages.append(
{
"role": "tool",
"tool_call_id": tool_call.id,
"content": error_result(
"TOOL_CALL_LIMIT_EXCEEDED",
"本轮工具调用已达到上限,当前工具未执行。请下一轮继续。",
),
}
)
print("AI > 本轮工具调用已达到上限,请下一轮继续。")
break
for tool_call in tool_calls:
if tool_call_count >= MAX_TOOL_CALLS_PER_TURN:
tool_limit_reached = True
messages.append(
{
"role": "tool",
"tool_call_id": tool_call.id,
"content": error_result(
"TOOL_CALL_LIMIT_EXCEEDED",
"超过工具最大调用次数,请根据已有信息直接回答问题。",
),
}
)
continue
tool_result = execute_tool_call(tool_call)
tool_call_count += 1
print(f"tool[{tool_call.function.name}] > {tool_result}")
messages.append(
{
"role": "tool",
"tool_call_id": tool_call.id,
"content": tool_result,
}
)

6. 小结:模型调用工具的关键环节

本篇完成模型调用工具闭环,关键环节包括四部分:

  • 工具暴露:通过接口tools参数把工具清单发送给模型,让模型知道当前可用能力及参数结构。
  • 调用识别:模型返回的数据中finish_reasontool_calls判断是否发起工具调用,并解析工具名称与参数。
  • 结果回填:将工具执行结果按tool_call_idcontent对应工具标识ID和结果回填上下文,交还模型继续推理。
  • 循环控制:持续处理“调用工具—回填结果—继续推理”的过程,并限制调用次数避免无限循环。
http://www.jsqmd.com/news/1092138/

相关文章:

  • AES与Serpent对称加密算法:原理、对比与Python/Android/Qt实战
  • 为什么你用光模块测试FPGA IBERT不通
  • OneMore插件终极指南:如何用160+个强大功能彻底改造你的OneNote体验
  • GESP4级C++考试语法知识(一、指针(9、指针与函数调用)
  • 特殊上位机权限管理方案
  • AI插件开发实战:基于JS脚本的Illustrator色标生成器设计与实现
  • Matlab2020b 从零到一:一份详尽的个人安装与避坑指南
  • 今天发现采用360下载wps比网页版快多了,下载的是同一个版本。-但是重新安装了wps,还是有些卡顿,稍微好了一丢丢,这个到底什么原因?
  • 三角洲S10裂变新赛季上线[特殊字符]Mac玩家再也不用错过核电站新图!
  • SMUDebugTool完全指南:专业级AMD Ryzen处理器硬件调试工具深度解析
  • C# CAD二次开发消息提示技巧
  • 如何免费解锁Wand专业版:告别订阅费的终极指南
  • 抖音无水印下载器:三步免费保存高清视频的完整指南
  • TUSB4020B评估模块拆解:从电源设计到信号完整性,打造稳定USB集线器
  • 【技巧揭秘】告别LaTeX插图虚线阴影:从Visio到PDF的完美转换链
  • 开发了一个浏览器新标签页,欢迎大家体验
  • 如何通过R3nzSkin项目掌握游戏内存修改技术:5个实战应用场景解析
  • 从习题到实战:TCP拥塞控制与窗口机制深度解析
  • LangGraph 架构避坑:智能体职责拆分与流式回调透传机制剖析
  • WindTerm高效配置与个性化调优指南
  • d2s-editor:暗黑破坏神2存档编辑器的3分钟终极指南
  • 启鸣AI赋能大学课堂,西班牙访学团沉浸式体验天立智慧教学
  • Free Spire.XLS for Python 免费库实现。
  • 德州仪器Value Soundbar参考设计:基于PCM3070与MSP430的音频系统开发实战
  • 【RV1103/RV1106】基于Buildroot定制蓝牙文件系统:从依赖解析到实战排错
  • 在博客设置 页脚HTML代码 贴入如下代码
  • Dataify 跨境电商数据采集全攻略实战
  • 最新毕设选题- 大数据篇
  • 私钥登录ssh服务器
  • 再见,期待下次相遇