AI Agent Harness Engineering 工具调用技术栈深度解析:从概念模型到生产级落地的全链路拆解


摘要/引言

你有没有想象过这样一个场景:周末早上,你对着家里的智能音箱说一句“订一张明天下午三点从北京朝阳站到上海虹桥站的二等座,用我支付宝绑定的工资卡,同时帮我收拾明天的出差行李箱——正装、降噪耳机、笔记本电脑、充电宝、明天下午两点半用滴滴拼车约一辆从家到朝阳站的车(预算20元以内),再帮我查一下虹桥站到上海分公司的最快地铁和最晚公交时间,存到我的Google Calendar备注里,对了,记得提醒我今天晚上把待签的合同打印出来,明天早上出门前看一眼邮箱有没有老板的新邮件”。这句话包含了5个以上不同类别的第三方工具(铁路订票、支付、虚拟整理、网约车、交通查询、日历提醒、本地打印、邮件检查),需要智能音箱理解复杂的意图层级(主意图是出差准备,下面有5个子意图,每个子意图还有参数约束:朝阳站→虹桥站、二等座、明天下午三点、工资卡支付、行李箱的具体物品、滴滴拼车预算、地铁公交+存Calendar+晚上打印+早上看邮件的动作链),还要在遇到问题时自动处理异常(比如二等座没票要不要问我要不要一等座?朝阳站出发前拼车司机爽约要不要自动换一家平台?Google Calendar同步失败要不要换存到我的飞书日历?)——而这一切,本质上就是一个生产级AI Agent工具调用系统要解决的核心问题。

在2023年GPT-4、Claude 3等大语言模型(LLM)通用能力爆发之后,“通用人工智能助手”不再是科幻电影里的概念——但LLM本身存在知识截止日期(比如GPT-4 Turbo截止到2023年12月,无法实时查询天气、股票、新闻)、逻辑推理复杂时的幻觉问题无法直接操作物理世界或数字系统(比如无法订票、无法发邮件、无法控制智能家居设备)等三大致命缺陷。而工具调用(Tool Calling) 正是填补这些缺陷的关键技术:LLM不再是一个只会“说话”的聊天机器人,而是变成了一个可以“思考”“决策”“动手干活”的智能Agent(代理)。

根据OpenAI 2024年3月发布的《Tool Calling in the Real World》白皮书,87%的企业级LLM应用已经集成了工具调用功能,而拥有10个以上工具的Agent应用的用户留存率比只有文本交互的应用高出320%——工具调用已经成为AI Agent从“实验室演示”走向“生产级落地”的核心引擎。但遗憾的是,目前市面上关于工具调用的技术文章大多停留在“OpenAI Function Calling怎么用”“LangChain怎么调用本地函数”的入门层面,很少有文章从概念模型问题边界技术栈深度拆解算法实现生产级架构最佳实践未来趋势等全链路角度进行系统性解析——这也正是本文要填补的空白。

本文将用12000+字的篇幅,带你深入理解AI Agent Harness Engineering(以下简称Harness Eng,即“Agent工具编排工程”)的所有核心内容:从“为什么需要工具调用”“工具调用的本质是什么”的概念模型讲起,到“工具定义、工具发现、意图理解、工具选择、工具执行、结果整合、异常处理”的核心流程算法实现,再到“LangChain、AutoGPT、LlamaIndex、Dify、Coze、OpenAI Assistants API”的主流工具调用技术栈深度对比,最后到“生产级Agent工具调用系统的架构设计、接口设计、最佳实践、常见坑点”——读完本文,你不仅能自己实现一个包含10+工具的通用AI Agent,还能掌握如何在生产环境中部署、监控、优化这样的系统。


目录

  1. 核心概念与问题背景:工具调用的定义、LLM的三大缺陷、工具调用的历史演变
  2. 工具调用的核心流程与边界外延:7步核心流程详解、工具调用的适用场景与非适用场景
  3. 概念结构与核心要素组成:工具元数据、工具注册中心、意图解析器、工具选择器、工具执行器、结果整合器、异常管理器、Agent状态机
  4. 核心要素关系与ER架构图:ER实体关系图、交互关系时序图、核心属性维度对比表
  5. 工具调用的数学模型与算法实现:意图解析的语义相似度计算、工具选择的强化学习模型、结果整合的摘要生成模型、异常处理的规则引擎+LLM混合模型
  6. 主流工具调用技术栈深度解析
    • 入门级:OpenAI Function Calling、Claude Tool Use、Google Gemini Function Calling
    • 框架级:LangChain Tools/Agents、AutoGPT Architecture、LlamaIndex Query Engine Tools
    • 平台级:Dify Agent Studio、Coze Workflow Agent、OpenAI Assistants API
    • 技术栈对比表:功能、灵活性、学习曲线、生产支持
  7. 生产级Agent工具调用系统的全链路落地
    • 项目背景与需求分析
    • 环境安装与配置
    • 系统功能设计
    • 系统架构设计(微服务化、容器化、可观测性)
    • 系统接口设计(RESTful API、WebSocket)
    • 系统核心实现源代码(Python+FastAPI+LangChain+Docker+Prometheus+Grafana)
  8. 最佳实践与常见坑点
    • 最佳实践:工具定义原则、工具分组方法、提示工程技巧、异常处理策略、可观测性建设
    • 常见坑点:工具选择幻觉、参数解析错误、工具执行超时、结果整合冗余、多轮对话状态丢失
  9. 行业发展与未来趋势:工具调用的演变历史表、多模态工具调用、自主工具构建、分布式Agent协作、工具调用的合规性与安全性
  10. 本章小结与行动号召

一、核心概念与问题背景

1.1 工具调用(Tool Calling)的定义

在正式讲解工具调用的技术细节之前,我们需要先明确几个核心概念的定义——因为目前业界对“工具调用”“Function Calling”“Agent”这些术语的使用非常混乱,经常出现混淆。

1.1.1 什么是“工具(Tool)”?

在Harness Eng领域,工具(Tool) 是指可以被Agent调用的、具有特定输入输出接口的、可重复执行的功能单元——它可以是:

  • 本地函数:比如Python里的def add(a, b): return a + b
  • 第三方API:比如OpenWeatherMap的天气查询API、12306的铁路订票API、支付宝的支付API
  • 本地命令行工具(CLI):比如lsgrepffmpeg
  • 数据库查询:比如SELECT * FROM users WHERE id = ?
  • RAG检索器:比如基于Elasticsearch的企业文档检索器、基于Chroma的知识库向量检索器
  • 多模态工具:比如Stable Diffusion的图像生成API、Whisper的语音转文字API、GPT-4V的图像理解API
1.1.2 什么是“工具调用(Tool Calling)”?

工具调用(Tool Calling) 是指Agent根据用户的输入(多轮对话历史、Agent状态),从注册好的工具库中选择一个或多个合适的工具,解析出工具需要的参数,执行工具,获取工具的输出结果,最后将结果整合到对用户的回复中的完整过程——注意,这里的“调用”不一定是“单次调用”,也可能是“多次顺序调用”“多次并行调用”“嵌套调用”(比如先调用天气查询API获取明天的天气,再根据天气结果决定是否调用雨伞购买API)。

1.1.3 什么是“Function Calling”?

很多人会把“工具调用”和“Function Calling”混为一谈——实际上,Function Calling只是工具调用的一种实现形式,是OpenAI在2023年6月推出的GPT-3.5 Turbo和GPT-4的一项原生功能:你可以把函数的元数据(函数名、函数描述、参数类型、参数描述、是否必填)用JSON Schema的格式传给OpenAI API,OpenAI API会根据用户的输入,自动返回“是否需要调用函数”“如果需要的话调用哪个函数”“函数的参数是什么”——而你需要自己写代码来执行函数、整合结果。

除了OpenAI的Function Calling之外,Claude、Google Gemini、Llama 3等大模型也推出了类似的原生功能:Claude的叫“Tool Use”,Google Gemini的叫“Function Calling”,Llama 3的叫“Tool Use Prompting”(虽然不是原生API,但可以通过特定的提示词让模型输出符合格式的工具调用请求)。

1.1.4 什么是“AI Agent”?

根据Lilian Weng(OpenAI前研究科学家)在2023年6月发表的《LLM Powered Autonomous Agents》文章,AI Agent是指由LLM作为核心大脑(Central Controller),加上感知模块(Perception Module,比如语音识别、图像识别、文本理解)、记忆模块(Memory Module,比如短期记忆、长期记忆、工作记忆)、行动模块(Action Module,比如工具调用、文本生成、物理控制)组成的智能系统——可以看出,工具调用只是AI Agent行动模块的一部分,但却是最重要的一部分,因为没有工具调用,Agent就只能“纸上谈兵”,无法真正解决实际问题。


1.2 LLM的三大致命缺陷与工具调用的必要性

为什么工具调用如此重要?因为LLM本身存在三大致命缺陷,而工具调用是目前唯一能系统性解决这三大缺陷的技术方案。

1.2.1 缺陷一:知识截止日期(Stale Knowledge)

所有的闭源大模型(GPT-4、Claude 3、Google Gemini)和大部分开源大模型(Llama 3、Qwen 2、Mistral)都有一个知识截止日期——也就是模型训练数据的最后时间点。比如:

  • GPT-4 Turbo:截止到2023年12月
  • Claude 3 Opus:截止到2024年2月
  • Google Gemini 1.5 Pro:截止到2024年3月
  • Llama 3 8B/70B:截止到2023年10月

这意味着,如果你问GPT-4 Turbo“2024年巴黎奥运会的开幕式时间是什么时候?”“2024年第一季度苹果公司的营收是多少?”“今天北京的天气怎么样?”——模型要么会说“我的知识截止到2023年12月,无法回答这个问题”,要么会生成幻觉(Hallucination),编造一个看起来很合理但实际上完全错误的答案。

而工具调用可以完美解决这个问题:你只需要给Agent注册一个实时天气查询API(比如OpenWeatherMap API)、一个实时新闻/财经数据查询API(比如新浪财经API、路透社API)——Agent就可以根据用户的问题,自动调用这些工具获取最新的信息,然后整合到回复中。

1.2.2 缺陷二:复杂逻辑推理的幻觉问题(Hallucination)

虽然LLM在简单的逻辑推理(比如“1+1等于几?”“小明比小红大3岁,小红今年5岁,小明今年几岁?”)上表现不错,但在复杂的多步逻辑推理(比如数学证明、代码调试、法律合同审查)上,经常会出现幻觉问题——模型会编造一些不存在的定理、代码函数、法律条款,来支撑自己的推理过程。

比如,如果你问GPT-4 Turbo“如何用Python实现一个快速排序算法?”——模型大概率会给你一个正确的答案;但如果你问GPT-4 Turbo“如何用Python实现一个高效的分布式快速排序算法,支持PB级别的数据量?”——模型可能会编造一些不存在的分布式计算库,或者给出一个逻辑上有漏洞的算法。

而工具调用可以通过“让专业的工具做专业的事”来减少幻觉问题:你可以给Agent注册一个代码执行工具(比如Python REPL、Jupyter Kernel)、一个数学计算工具(比如Wolfram Alpha API、SymPy)、一个代码调试工具(比如PyCharm的调试API)——Agent在遇到复杂的逻辑推理问题时,会自动调用这些专业工具,而不是自己瞎编。

1.2.3 缺陷三:无法直接操作物理世界或数字系统(No Actionability)

LLM本质上是一个文本生成模型——它的输出只是一串文本,无法直接操作物理世界(比如开关灯、打开空调、收拾行李箱),也无法直接操作数字系统(比如订票、发邮件、转账、修改数据库)。

比如,如果你对着GPT-4 Turbo的网页版说“订一张明天下午三点从北京朝阳站到上海虹桥站的二等座”——模型只会给你一段文字,告诉你“你可以打开12306官网,输入出发站、到达站、日期、时间,然后选择二等座,点击支付”——但它不会真的帮你订票。

而工具调用可以让Agent具备“动手能力”:你可以给Agent注册一个12306订票工具(比如通过第三方API或者模拟浏览器操作)、一个支付宝支付工具、一个飞书日历提醒工具、一个滴滴出行工具——Agent就可以真的帮你完成订票、支付、提醒、约车的所有操作。


1.3 工具调用的历史演变

虽然工具调用在2023年LLM通用能力爆发之后才成为业界关注的焦点,但它的历史其实可以追溯到20世纪60年代——我们可以把工具调用的发展历史分为四个阶段:

1.3.1 第一阶段:规则引擎驱动的工具调用(1960s-2010s)

在这个阶段,没有大语言模型,工具调用完全由规则引擎(Rule Engine) 驱动——你需要事先编写大量的“如果-那么”(If-Then)规则,来决定什么时候调用什么工具、传递什么参数。

比如,早期的智能客服系统就是典型的规则引擎驱动的工具调用:

  • 如果用户说“查一下我的订单”,那么调用订单查询API,参数是用户的手机号
  • 如果用户说“我要退款”,那么调用退款申请API,参数是用户的手机号和订单号
  • 如果用户说“转人工客服”,那么调用转接人工API,参数是用户的排队号

规则引擎驱动的工具调用的优点是可控性强执行效率高——因为所有的逻辑都是事先写好的,不会出现幻觉问题;但缺点也非常明显:灵活性差维护成本高——如果用户的问题稍微超出了规则的范围(比如用户说“查一下我上个月买的那件红色衣服的订单,能不能帮我换个尺码?”),系统就会直接报错或者转人工;而且随着规则数量的增加,规则之间的冲突会越来越多,维护成本会呈指数级增长。

1.3.2 第二阶段:语义理解驱动的工具调用(2010s-2022s)

随着自然语言处理(NLP)技术的发展(比如词向量Word2Vec、GloVe、BERT),工具调用进入了“语义理解驱动”的阶段——你不需要再写大量的“如果-那么”规则,只需要给每个工具写一段语义描述,然后用语义相似度算法来计算用户的问题和每个工具的语义描述的相似度,选择相似度最高的工具来调用。

比如,早期的Siri、Google Assistant就是典型的语义理解驱动的工具调用:

  • 你给“天气查询工具”写的语义描述是“查询某个城市在某个时间的天气情况”
  • 你给“闹钟设置工具”写的语义描述是“设置一个在某个时间响的闹钟”
  • 当用户说“明天北京会下雨吗?”时,系统用BERT计算用户的问题和两个工具的语义描述的相似度,发现和“天气查询工具”的相似度更高,于是调用天气查询API,参数是“北京”和“明天”

语义理解驱动的工具调用的优点是灵活性比规则引擎强——不需要再写大量的规则,只要语义描述写得好,就能覆盖大部分用户的问题;但缺点也很明显:只能处理单轮对话无法处理多步调用参数解析能力差——比如用户说“先帮我查一下明天北京的天气,如果下雨的话就帮我设置一个明天早上7点的闹钟提醒我带伞”,语义理解驱动的系统根本无法处理这种复杂的多步调用问题;而且参数解析经常会出错(比如把“明天下午三点”解析成“明天早上三点”)。

1.3.3 第三阶段:大语言模型原生Function Calling驱动的工具调用(2023年6月-2023年12月)

2023年6月,OpenAI推出了GPT-3.5 Turbo和GPT-4的原生Function Calling功能——这标志着工具调用进入了“大语言模型原生驱动”的阶段。

原生Function Calling的核心创新点在于:

  1. 支持多轮对话:可以根据整个对话历史来决定是否调用工具、调用什么工具、传递什么参数
  2. 支持多步调用:可以自动决定先调用哪个工具,再调用哪个工具,甚至可以根据前一个工具的输出结果来决定后一个工具的调用参数
  3. 支持嵌套调用:可以在一个工具的执行过程中调用另一个工具
  4. 参数解析能力强:因为有LLM的强大语义理解能力,参数解析的准确率比语义理解驱动的系统高很多

除了OpenAI之外,Claude、Google Gemini、Llama 3等大模型也在2023年下半年和2024年上半年推出了类似的原生功能——这使得工具调用的门槛大大降低,任何一个会写Python代码的开发者都能在几个小时内实现一个包含多个工具的AI Agent。

1.3.4 第四阶段:AI Agent Harness Engineering驱动的工具编排(2024年1月至今)

虽然原生Function Calling功能很强,但它也存在一些问题:

  1. 工具管理混乱:如果你的Agent有100个以上的工具,那么把所有工具的元数据都传给LLM是不现实的——因为LLM的上下文窗口(Context Window)是有限的(比如GPT-4 Turbo的上下文窗口是128K tokens,但如果把100个工具的元数据都传进去,可能就要占掉几K甚至几十K tokens)
  2. 工具选择幻觉:即使你把工具的元数据都传给LLM,LLM也可能会选择一个不合适的工具,或者编造一个不存在的工具
  3. 异常处理能力弱:原生Function Calling没有内置的异常处理机制——如果工具执行超时、工具返回错误、参数解析错误,你需要自己写代码来处理这些异常
  4. 可观测性差:原生Function Calling没有内置的日志、监控、追踪机制——你不知道Agent什么时候调用了什么工具、传递了什么参数、工具执行了多长时间、返回了什么结果、有没有出现异常
  5. 生产支持不足:原生Function Calling没有内置的认证、授权、限流、熔断、降级机制——无法直接在生产环境中部署

为了解决这些问题,业界在2024年提出了AI Agent Harness Engineering(Agent工具编排工程) 的概念——Harness Eng不仅关注“怎么调用一个工具”,更关注“怎么管理、编排、监控、优化100个以上的工具,让Agent在生产环境中稳定、高效、安全地运行”。

目前,主流的Harness Eng工具包括:

  • 框架级:LangChain Agents、AutoGPT Architecture、LlamaIndex Query Engine Tools
  • 平台级:Dify Agent Studio、Coze Workflow Agent、OpenAI Assistants API

二、工具调用的核心流程与边界外延

2.1 工具调用的7步核心流程详解

虽然不同的Harness Eng工具的实现细节不同,但它们的核心流程都是一样的——我们可以把工具调用的核心流程分为7个步骤

2.1.1 步骤一:用户输入与状态更新(User Input & State Update)

这是工具调用的第一步,也是最简单的一步:

  1. 用户输入:用户通过文本、语音、图像等方式向Agent发送输入
  2. 状态更新:Agent将用户的输入更新到短期记忆(Short-Term Memory)工作记忆(Working Memory)

这里需要明确几个记忆模块的定义(根据Lilian Weng的文章):

  • 短期记忆(Short-Term Memory):也叫“上下文记忆(Context Memory)”,存储的是最近几轮对话的历史——通常直接放在LLM的上下文窗口中,不需要额外的存储介质
  • 长期记忆(Long-Term Memory):存储的是用户的偏好、历史行为、知识库内容——通常需要额外的存储介质(比如向量数据库Chroma、Pinecone,关系型数据库MySQL、PostgreSQL)
  • 工作记忆(Working Memory):存储的是当前任务的中间结果——比如先调用天气查询API获取的明天北京的天气,再调用闹钟设置API时需要用到这个结果
2.1.2 步骤二:意图理解与任务拆解(Intent Understanding & Task Decomposition)

这是工具调用的核心第一步——Agent需要根据用户的输入和对话历史,理解用户的真实意图,并将复杂的意图拆解成多个可执行的子任务

比如,用户的输入是“订一张明天下午三点从北京朝阳站到上海虹桥站的二等座,用我支付宝绑定的工资卡,同时帮我收拾明天的出差行李箱——正装、降噪耳机、笔记本电脑、充电宝、明天下午两点半用滴滴拼车约一辆从家到朝阳站的车(预算20元以内)”:

  • 真实意图:为明天去上海的出差做准备
  • 子任务拆解
    1. 子任务1:查询明天下午三点从北京朝阳站到上海虹桥站的二等座是否有票
    2. 子任务2:如果有票,用支付宝绑定的工资卡订票
    3. 子任务3:虚拟整理明天的出差行李箱(列出需要带的物品)
    4. 子任务4:明天下午两点半用滴滴拼车约一辆从家到朝阳站的车(预算20元以内)
    5. 子任务5:将订票信息、拼车信息、行李箱物品清单存到用户的飞书日历备注里

意图理解与任务拆解通常有两种实现方式:

  1. 纯LLM驱动:直接用LLM来理解意图、拆解任务——优点是灵活性强,缺点是可能会出现幻觉问题
  2. 混合驱动:先用意图识别模型(比如基于BERT的分类模型)来识别用户的意图类别,再用LLM来拆解任务——优点是可控性强,减少幻觉问题,缺点是需要额外的训练数据
2.1.3 步骤三:工具发现与过滤(Tool Discovery & Filtering)

如果你的Agent只有10个以下的工具,那么你可以把所有工具的元数据都传给LLM——但如果你的Agent有100个以上的工具,那么直接把所有工具的元数据都传给LLM是不现实的,因为:

  1. 占用上下文窗口:LLM的上下文窗口是有限的,工具元数据越多,留给对话历史和用户输入的空间就越小
  2. 增加工具选择幻觉的概率:工具越多,LLM选择不合适的工具或者编造不存在的工具的概率就越大

这时候,你就需要工具发现与过滤这一步:

  1. 工具发现:从工具注册中心(Tool Registry) 中获取所有可用工具的元数据
  2. 工具过滤:根据用户的意图和子任务,从所有可用工具中过滤出Top N个最相关的工具(N通常设置为5-10)

工具过滤通常有两种实现方式:

  1. 语义相似度过滤:用语义嵌入模型(比如OpenAI的text-embedding-3-small、Cohere的embed-v3.0、 sentence-transformers的all-MiniLM-L6-v2)将用户的意图、子任务和每个工具的元数据(工具名、工具描述、参数描述)转换成向量,然后计算余弦相似度(Cosine Similarity),选择相似度最高的Top N个工具
  2. 关键词匹配过滤:从用户的意图、子任务中提取关键词,然后和每个工具的元数据中的关键词进行匹配,选择匹配度最高的Top N个工具——这种方式通常作为语义相似度过滤的补充
2.1.4 步骤四:工具选择与参数解析(Tool Selection & Parameter Parsing)

这是工具调用的核心第二步——Agent需要从过滤后的Top N个工具中选择一个或多个合适的工具,并解析出每个工具需要的参数。

工具选择与参数解析通常有两种实现方式:

  1. 原生LLM驱动:直接用OpenAI的Function Calling、Claude的Tool Use、Google Gemini的Function Calling等原生功能——优点是实现简单,参数解析准确率高,缺点是依赖闭源大模型
  2. 提示词驱动:通过特定的提示词(Prompt Engineering)让开源大模型(比如Llama 3、Qwen 2、Mistral)输出符合格式的工具调用请求——优点是不依赖闭源大模型,灵活性强,缺点是实现复杂,参数解析准确率可能不如原生功能

原生LLM驱动的工具调用请求通常是一个JSON格式的对象,比如:

{
  "id": "call_abc123",
  "type": "function",
  "function": {
    "name": "book_train_ticket",
    "arguments": "{\"departure_station\": \"北京朝阳站\", \"arrival_station\": \"上海虹桥站\", \"departure_date\": \"2024-06-20\", \"departure_time\": \"15:00\", \"seat_type\": \"二等座\", \"payment_method\": \"支付宝绑定的工资卡\"}"
  }
}
2.1.5 步骤五:工具执行与结果获取(Tool Execution & Result Retrieval)

这是工具调用的实际操作步骤——Agent根据工具调用请求,执行对应的工具,并获取工具的输出结果。

工具执行通常有两种方式:

  1. 本地执行:直接在Agent所在的服务器上执行本地函数、本地CLI工具——优点是执行效率高,缺点是安全性差(如果用户让Agent执行rm -rf /这样的命令,后果不堪设想)
  2. 远程执行:通过API接口在远程服务器上执行第三方工具、数据库查询、RAG检索器——优点是安全性高,缺点是执行效率可能不如本地执行,需要处理网络超时、网络错误等问题

为了保证安全性,生产级Agent工具调用系统通常只支持远程执行——如果必须要执行本地函数或者CLI工具,也需要在沙箱环境(Sandbox Environment) 中执行(比如Docker容器、AWS Lambda、Google Cloud Functions)。

2.1.6 步骤六:结果整合与状态更新(Result Integration & State Update)

这是工具调用的中间步骤——Agent将工具的输出结果整合到工作记忆(Working Memory)短期记忆(Short-Term Memory) 中,并判断是否需要继续调用工具。

判断是否需要继续调用工具通常有两种方式:

  1. 原生LLM驱动:直接用LLM来判断——将对话历史、用户输入、工具调用请求、工具输出结果都传给LLM,让LLM决定是否需要继续调用工具
  2. 任务状态机驱动:用有限状态机(Finite State Machine, FSM) 来判断——事先定义好任务的状态流转规则,根据当前任务的状态和工具的输出结果来决定下一步的操作

比如,子任务1是“查询明天下午三点从北京朝阳站到上海虹桥站的二等座是否有票”:

  • 如果工具输出结果是“有票,剩余50张”,那么任务状态流转到“子任务2:订票”
  • 如果工具输出结果是“二等座没票,一等座有剩余10张,商务座有剩余2张”,那么任务状态流转到“子任务1.1:询问用户是否需要换一等座或商务座”
  • 如果工具输出结果是“所有座位都没票”,那么任务状态流转到“子任务1.2:查询明天下午两点到四点之间的其他车次是否有票”
2.1.7 步骤七:最终回复生成与状态持久化(Final Response Generation & State Persistence)

这是工具调用的最后一步——Agent根据所有的工具输出结果和对话历史,生成对用户的最终回复,并将短期记忆(Short-Term Memory) 中的重要信息持久化到长期记忆(Long-Term Memory) 中。

最终回复生成通常是纯LLM驱动的——将对话历史、用户输入、所有工具的调用请求和输出结果都传给LLM,让LLM生成一段自然、流畅、符合用户需求的回复。

状态持久化通常是将用户的偏好(比如喜欢坐二等座、喜欢用支付宝支付)、历史行为(比如最近订过什么票、最近约过什么车)持久化到关系型数据库(MySQL、PostgreSQL) 中,将对话历史的摘要持久化到向量数据库(Chroma、Pinecone) 中——这样,下次用户再和Agent对话时,Agent就可以根据长期记忆中的信息,提供更个性化的服务。


2.2 工具调用的适用场景与非适用场景

虽然工具调用很强大,但它并不是万能的——我们需要明确工具调用的适用场景非适用场景,避免“为了调用工具而调用工具”。

2.2.1 适用场景

工具调用的适用场景主要包括以下5类

  1. 实时信息查询:比如查询天气、股票、新闻、航班、火车、酒店
  2. 复杂逻辑计算:比如数学计算、代码执行、代码调试、数据分析
  3. 数字系统操作:比如订票、发邮件、转账、修改数据库、发布社交媒体内容
  4. 物理世界操作:比如开关灯、打开空调、控制智能家居设备、机器人导航
  5. 多模态内容生成与理解:比如图像生成、图像理解、语音转文字、文字转语音、视频生成
2.2.2 非适用场景

工具调用的非适用场景主要包括以下3类

  1. 创造性内容生成:比如写小说、写诗歌、写剧本、设计海报——这些场景不需要调用工具,直接用LLM生成即可
  2. 简单的逻辑推理:比如“1+1等于几?”“小明比小红大3岁,小红今年5岁,小明今年几岁?”——这些场景直接用LLM推理即可,调用工具反而会增加延迟
  3. 需要情感共鸣的场景:比如心理咨询、情感陪伴——这些场景需要的是LLM的情感理解能力,而不是工具调用能力,调用工具反而会让用户觉得冷冰冰的

三、概念结构与核心要素组成

3.1 工具调用系统的概念结构

工具调用系统的概念结构可以分为三层

  1. 用户交互层(User Interaction Layer):负责和用户进行交互,接收用户的输入,展示Agent的输出——比如网页界面、移动APP、智能音箱、微信公众号
  2. 核心引擎层(Core Engine Layer):这是工具调用系统的核心,负责意图理解、任务拆解、工具发现、工具选择、工具执行、结果整合、异常处理——比如LLM、意图识别模型、语义嵌入模型、工具注册中心、状态机
  3. 工具层(Tool Layer):负责提供具体的功能——比如本地函数、第三方API、本地CLI工具、数据库查询、RAG检索器、多模态工具

3.2 核心要素组成详解

工具调用系统的核心要素包括8个:工具元数据、工具注册中心、意图解析器、工具选择器、工具执行器、结果整合器、异常管理器、Agent状态机——下面我们来详细讲解每个核心要素的定义、作用、实现方式。

3.2.1 核心要素一:工具元数据(Tool Metadata)

定义:工具元数据是指描述工具的属性和接口的信息——它是Agent理解工具、选择工具、调用工具的基础。

作用

  1. 让Agent理解工具的功能
  2. 让Agent知道怎么调用工具(接口、参数)
  3. 用于工具发现与过滤
  4. 用于工具选择

实现方式
工具元数据通常用JSON Schema的格式来描述——因为JSON Schema是一种标准化的格式,LLM很容易理解和解析。

一个完整的工具元数据通常包括以下7个字段

  1. name:工具的名称——必须是唯一的,不能和其他工具重名
  2. description:工具的功能描述——必须清晰、具体、准确,不能模糊不清
  3. parameters:工具的参数描述——用JSON Schema的格式来描述每个参数的名称、类型、描述、是否必填、默认值、取值范围
  4. returns:工具的返回结果描述——用JSON Schema的格式来描述返回结果的名称、类型、描述
  5. category:工具的类别——比如“天气查询”“铁路订票”“支付”“闹钟设置”——用于工具分组和过滤
  6. tags:工具的标签——比如“实时信息”“数字系统操作”“中文”——用于工具过滤
  7. security_level:工具的安全级别——比如“低”“中”“高”——用于权限控制

下面是一个“book_train_ticket(铁路订票)”工具的元数据示例:

{
  "name": "book_train_ticket",
  "description": "预订中国铁路的火车票,支持选择出发站、到达站、出发日期、出发时间、座位类型、乘客信息、支付方式",
  "parameters": {
    "type": "object",
    "properties": {
      "departure_station": {
        "type": "string",
        "description": "出发站的名称,必须是中国铁路官方认可的车站名称,比如“北京朝阳站”“上海虹桥站”",
        "minLength": 2,
        "maxLength": 20
      },
      "arrival_station": {
        "type": "string",
        "description": "到达站的名称,必须是中国铁路官方认可的车站名称,比如“北京朝阳站”“上海虹桥站”",
        "minLength": 2,
        "maxLength": 20
      },
      "departure_date": {
        "type": "string",
        "description": "出发日期,格式必须是YYYY-MM-DD,比如“2024-06-20”",
        "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
      },
      "departure_time_range": {
        "type": "object",
        "description": "出发时间范围,可选,如果不指定则查询全天的车次",
        "properties": {
          "start_time": {
            "type": "string",
            "description": "出发时间范围的开始时间,格式必须是HH:MM,比如“14:00”",
            "pattern": "^\\d{2}:\\d{2}$"
          },
          "end_time": {
            "type": "string",
            "description": "出发时间范围的结束时间,格式必须是HH:MM,比如“16:00”",
            "pattern": "^\\d{2}:\\d{2}$"
          }
        },
        "required": ["start_time", "end_time"]
      },
      "seat_type": {
        "type": "string",
        "description": "座位类型,必须是以下之一:“二等座”“一等座”“商务座”“硬卧”“软卧”“硬座”“软座”",
        "enum": ["二等座", "一等座", "商务座", "硬卧", "软卧", "硬座", "软座"]
      },
      "passenger_list": {
        "type": "array",
        "description": "乘客信息列表,至少包含一个乘客",
        "items": {
          "type": "object",
          "properties": {
            "name": {
              "type": "string",
              "description": "乘客的姓名",
              "minLength": 2,
              "maxLength": 20
            },
            "id_type": {
              "type": "string",
              "description": "乘客的证件类型,必须是以下之一:“身份证”“护照”“港澳通行证”“台湾通行证”",
              "enum": ["身份证", "护照", "港澳通行证", "台湾通行证"]
            },
            "id_number": {
              "type": "string",
              "description": "乘客的证件号码"
            },
            "phone_number": {
              "type": "string",
              "description": "乘客的手机号码,格式必须是中国大陆的手机号码,比如“13800138000”",
              "pattern": "^1[3-9]\\d{9}$"
            }
          },
          "required": ["name", "id_type", "id_number", "phone_number"]
        },
        "minItems": 1,
        "maxItems": 5
      },
      "payment_method": {
        "type": "string",
        "description": "支付方式,必须是以下之一:“支付宝”“微信支付”“银行卡”",
        "enum": ["支付宝", "微信支付", "银行卡"]
      }
    },
    "required": ["departure_station", "arrival_station", "departure_date", "seat_type", "passenger_list", "payment_method"]
  },
  "returns": {
    "type": "object",
    "properties": {
      "success": {
        "type": "boolean",
        "description": "订票是否成功"
      },
      "message": {
        "type": "string",
        "description": "订票结果的消息,如果成功则包含订单号、车次信息、座位信息,如果失败则包含失败原因"
      },
      "order_id": {
        "type": "string",
        "description": "订单号,只有订票成功时才会有"
      },
      "train_number": {
        "type": "string",
        "description": "车次号,比如“G1”,只有订票成功时才会有"
      },
      "seat_info": {
        "type": "string",
        "description": "座位信息,比如“15车01A号”,只有订票成功时才会有"
      }
    },
    "required": ["success", "message"]
  },
  "category": "交通出行",
  "tags": ["铁路订票", "中国铁路", "数字系统操作", "中文"],
  "security_level": "高"
}
3.2.2 核心要素二:工具注册中心(Tool Registry)

定义:工具注册中心是指存储、管理、检索所有工具元数据的中心化系统——它是工具发现与过滤的基础。

作用

  1. 存储所有工具的元数据
  2. 提供工具的注册、更新、删除接口
  3. 提供工具的检索接口(按类别、按标签、按语义相似度、按关键词)
  4. 提供工具的权限控制接口(不同的用户可以访问不同安全级别的工具)

实现方式
工具注册中心通常可以用以下3种方式来实现:

  1. 关系型数据库(MySQL、PostgreSQL):适合存储结构化的工具元数据,提供按类别、按标签、按关键词的检索功能——优点是实现简单,缺点是语义相似度检索功能需要额外的语义嵌入模型和向量数据库
  2. 向量数据库(Chroma、Pinecone、Weaviate):适合存储工具元数据的向量,提供按语义相似度的检索功能——优点是语义相似度检索速度快,缺点是结构化检索功能不如关系型数据库
  3. 混合存储(关系型数据库+向量数据库):这是生产级工具注册中心的最佳实现方式——用关系型数据库存储结构化的工具元数据,用向量数据库存储工具元数据的向量,同时提供结构化检索和语义相似度检索功能

下面是一个用PostgreSQL存储工具元数据的表结构示例:

CREATE TABLE IF NOT EXISTS tools (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) UNIQUE NOT NULL,
    description TEXT NOT NULL,
    parameters JSONB NOT NULL,
    returns JSONB NOT NULL,
    category VARCHAR(50) NOT NULL,
    tags TEXT[] NOT NULL,
    security_level VARCHAR(20) NOT NULL CHECK (security_level IN ('低', '中', '高')),
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    is_active BOOLEAN NOT NULL DEFAULT TRUE
);

CREATE INDEX IF NOT EXISTS idx_tools_category ON tools (category);
CREATE INDEX IF NOT EXISTS idx_tools_tags ON tools USING GIN (tags);
CREATE INDEX IF NOT EXISTS idx_tools_security_level ON tools (security_level);
CREATE INDEX IF NOT EXISTS idx_tools_is_active ON tools (is_active);
3.2.3 核心要素三:意图解析器(Intent Parser)

定义:意图解析器是指根据用户的输入和对话历史,理解用户的真实意图,并将复杂的意图拆解成多个可执行的子任务的模块——它是工具调用的核心第一步。

作用

  1. 理解用户的真实意图
  2. 将复杂的意图拆解成多个可执行的子任务
  3. 提取每个子任务的关键信息

实现方式
意图解析器通常有两种实现方式:

  1. 纯LLM驱动:直接用LLM来理解意图、拆解任务——优点是灵活性强,不需要额外的训练数据,缺点是可能会出现幻觉问题,响应时间可能较长
  2. 混合驱动:先用意图识别模型(比如基于BERT的分类模型)来识别用户的意图类别,再用LLM来拆解任务——优点是可控性强,减少幻觉问题,响应时间可能较短,缺点是需要额外的训练数据

下面是一个纯LLM驱动的意图解析器的提示词示例:

你是一个专业的AI Agent意图解析器,你的任务是根据用户的输入和对话历史,理解用户的真实意图,并将复杂的意图拆解成多个可执行的子任务。

## 输入格式
- 对话历史(可选):最近几轮的对话历史,格式为JSON数组,每个元素包含“role”(user/assistant)和“content”(内容)
- 用户输入:当前用户的输入,格式为字符串

## 输出格式
你必须严格按照以下JSON格式输出,不要输出任何其他内容:
```json
{
  "intent": "用户的真实意图,必须清晰、具体、准确,不能模糊不清",
  "sub_tasks": [
    {
      "id": "子任务的唯一标识符,格式为sub_task_1、sub_task_2...",
      "description": "子任务的描述,必须清晰、具体、可执行",
      "dependencies": "子任务的依赖列表,格式为数组,包含子任务的
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐