跳到主要内容
构建 AI Agent 框架:从规划到执行的全流程解析 | 极客日志
TypeScript AI 算法
构建 AI Agent 框架:从规划到执行的全流程解析 综述由AI生成 AI Agent 利用语言模型作为推理引擎,具备自主发现问题、确定目标及执行方案的能力。核心组件包括规划(Planning)、记忆(Memory)和工具(Tool)。思维链(CoT)和 ReAct 框架在 Agent 中的应用,展示了如何通过 Prompt 工程引导模型进行多步推理和工具调用。通过构建 AgentExecutor 实现执行循环,协调工具调用与状态更新,并探讨了函数声明生成及错误处理等优化方向。
魔法巫师 发布于 2025/2/6 更新于 2026/4/30 7 浏览AI Agent
Agent 的核心思想是使用语言模型来选择要采取的一系列操作。在 Agent 中,语言模型被用作推理引擎来确定要采取哪些操作以及按什么顺序。相比于传统机械或软件被动的'给予输入——>做出输出'的模式,Agent 由于更加强调自主的发现问题、确定目标、构想方案、选择方案、执行方案、检查更新的特性,因此可以被认为是一类拥有'自主智能的实体',而被广泛称之为智能体。
⚠️下文介绍的 Plan/Memory/Tool 三组件只是 Agent 框架一种简单的拆分(参考了 LangChain),除此外还有多种理解 Agent 框架的视角,读者若感兴趣可以自行学习以下 Agent 框架:
Baby Agent
Auto GPT
Agents 框架
清华大学的 XAgent
Planning 规划
子目标和分解:AI Agent 将大型任务分解为更小的、可管理的子目标,从而能够有效处理复杂的任务。
反思和完善:Agent 可以对过去的行为进行自我批评和自我反思,从错误中吸取教训,并针对未来的步骤进行完善,从而提高最终结果的质量。
Memory 记忆
短期记忆:所有的上下文学习(参见提示工程)都是利用模型的短期记忆来学习。
长期记忆:这为 AI Agent 提供了长期保留和调用无限信息的能力,通常是通过利用外部向量存储和快速检索来实现。
Tool / Toolkit
Agent 学习调用外部 API 来获取模型权重中缺失的额外信息,通常这些信息在预训练后很难更改,包括当前信息、代码执行能力、对专有信息源的访问等。
工具是代理可以调用的功能,本质上就是一个函数。使用工具是 AI Agent 最迷人最先进的特性。
思考框架
CoT
语言智能可以被理解为'使用基于自然语言的概念对经验事物进行**'理解'以及在概念之间进行 '推理'**的能力'。
理解能力上,作为'语言模型'的大模型具备概念理解能力并不难理解,但是仅仅像 Word2vec 一样只能得到'国王'与'男人'的'距离'更近的结论对于语言智能而言必然远远不够。
真正让人惊讶的是大模型在推理上的能力涌现。推理,一般指根据几个已知的前提推导得出新的结论的过程,区别于理解,推理一般是一个'多步骤'的过程,推理的过程可以形成非常必要的'中间概念',这些中间概念将辅助复杂问题的求解。
2022 年,在 Google 发布的论文《Chain-of-Thought Prompting Elicits Reasoning in Large Language Models》中首次提出,通过让大模型逐步参与将一个复杂问题分解为一步一步的子问题并依次进行求解的过程可以显著提升大模型的性能。而这一系列推理的中间步骤就被称为思维链(Chain of Thought) 。
CoT 大家应该都比较熟了,使用它的方法很简单:
对于 Zero-Shot,只需要在 Prompt 的结尾加一句:
Let 's think step by step
对于 One-Shot 或 Few-Shot,需要在 Prompt 中适当地为大模型提供一些示例。
拓展:CoT 其实也在逐渐进化,出现了各种让大模型进行复杂链路思考的方式。
ReAct 无论是环境的反馈,还是人类的指令,Agent 都需要完成一个对接收到的信息进行'理解',并依据得到的理解进行意图识别,转化为下一步任务的过程。在前文中之所以介绍 CoT,是因为使用 CoT 可以大大帮助模型对现有输入进行'感知',激活大模型对任务的拆分规划和推理能力。借鉴 CoT,我们可以归纳出基本的 Agent 框架并延伸,ReAct 就是其中之一。
从 [Yao 等人,2022] 引入了一个框架,其中 LLMs 以交错的方式生成 推理轨迹 和 任务特定操作 。
生成推理轨迹使模型能够诱导、跟踪和更新操作计划,甚至处理异常情况。操作步骤允许与外部源(如知识库或环境)进行交互并且收集信息。
ReAct 框架允许 LLMs 与外部工具交互来获取额外信息,从而给出更可靠和实际的回应。
结果表明,ReAct 可以在语言和决策任务上的表现要高于几个最先进水准要求的基线。ReAct 还提高了 LLMs 的人类可解释性和可信度。总的来说,作者发现了将 ReAct 和链式思考 (CoT) 结合使用的最好方法是在推理过程同时使用内部知识和获取到的外部信息。
运作机理 ReAct 的灵感来自于'行为'和'推理'之间的协同作用,正是这种协同作用使得人类能够学习新任务并做出决策或推理。
链式思考 (CoT) 提示显示了 LLMs 执行推理轨迹以生成涉及算术和常识推理的问题的答案的能力,以及其他任务 [(Wei 等人,2022)](opens in a new tab)。但它因缺乏和外部世界的接触或无法更新自己的知识,而导致事实幻觉和错误传播等问题。
ReAct 是一个将推理和行为与 LLMs 相结合通用的范例。ReAct 提示 LLMs 为任务生成口头推理轨迹和操作。这使得系统执行动态推理来创建、维护和调整操作计划,同时还支持与外部环境 (例如,Wikipedia) 的交互,以将额外信息合并到推理中。
Answer the following questions as best you can. You have access to the following tools:
{tools}
Use the following format:
Question: the input question you must answer
Thought: you should always think about what to do
Action: the action to take, should be one of [{tool_names}]
Action Input: the input to the action
Observation: the result of the action
... (this Thought/Action/Action Input/Observation can repeat N times)
Thought: I now know the final answer
Final Answer: the final answer to the original input question
Begin!
Question: {input}
Thought:{agent_scratchpad}
搭建一个 ReAct 框架
简单的 ReAct 先对各组件进行硬编码。我们为大模型提供一个 get_word_length 工具(Tool),使用 ReAct 框架看看他能否解决一些基本问题。
Answer the following questions as best you can. You have access to the following tools:
get_word_length(word: str) -> int:
"""Returns the length of a word."""
Use the following format:
Question: the input question you must answer
Thought: you should always think about what to do
Action: the action to take, should be one of [get_word_length]
Action Input: the input to the action
Observation: the result of the action
... (this Thought/Action/Action Input/Observation can repeat N times)
Thought: I now know the final answer
Final Answer: the final answer to the original input question
Begin!
Question:How many letters in the word educa
⚠️注意!这里只是简单在用大模型调试 prompt,get_word_length 工具并没有具体的代码实现也没有运行。之所以能算出结果都是靠大模型自身的推理能力!
Agent Agent 相当于整体框架的思维推理系统,通常由大模型、Prompt 提供支持。不同的智能体有不同的推理提示风格、不同的输入方式以及不同的解析输出方式,依赖于用户对应用的自定义,说白了就是对大模型进行一层封装更方便管理。
这里提到的 Agent 其实就是大模型本身,我们在框架中最好将大模型的 API 进行面向对封装,方便与框架中其他组件交互。
这里我使用的是 OpenAI 的 GPT-3.5-turbo 模型。
class LLMSingleActionAgent {
llm : AzureLLM
tools : StructuredTool []
stop : string []
private _prompt : string = '{input}'
constructor ({ llm, tools = [], stop = [] }: LLMSingleActionAgentParams ) {
this .llm = llm
this .tools = tools
if (stop.length > 4 )
throw new Error ('up to 4 stop sequences' )
this .stop = stop
}
}
Agent Inputs 代理输入 对大模型的输入可以是普通的 Prompt 字符串;也可以是键值对,结合 Prompt Template 拼接出最终的 Prompt 字符串。
Agent Outputs 代理输出 输出是要执行的下一个操作或要发送给用户的最终响应(AgentAction 或 AgentFinish)。
对大模型的输入可以是普通的 Prompt 字符串;也可以是键值对,结合 Prompt Template 拼接出最终的 Prompt 字符串。我们这里使用 ReAct 框架,每次输入的 Prompt 都有固定的模板 Template(见上文 ReAct 的模板)。因此需要一个填充模板的函数,我们简单约定变量按 {var} 的格式插入,用正则表达式将字符串替换。
function fillPromptTemplate (promptTemplate : string , inputs : promptInputs ) {
let res = promptTemplate
for (const [key, val] of Object .entries (inputs))
res = res.replaceAll (new RegExp (`{\s*${key} \s*}` , 'g' ), val)
return res
}
Action
AgentAction 代理行动 这是一个数据类,表示代理应采取的操作。它有一个 tool 属性(这是应该调用的工具的名称)和一个 tool_input 属性(该工具的输入)。
AgentFinish 代理完成 这表示代理准备好返回给用户时的最终结果。它包含一个 return_values 键值映射,其中包含最终的代理输出。通常,这包含 output 键,其中包含代理响应的字符串。
Intermediate Steps 中间步骤 这些代表先前的代理操作以及当前代理运行的相应输出。这些对于传递到未来的迭代非常重要,因此代理知道它已经完成了哪些工作。其类型为 List[Tuple[AgentAction, Any]]。请注意,观察目前保留为 Any 类型,以实现最大程度的灵活性。实际上,这通常是一个字符串。
Tool
在 Prompt 中描述工具 export abstract class StructuredTool {
name : string
description : string
constructor (name : string , description : string ) {
this .name = name
this .description = description
}
abstract call (arg : string , config ?: Record <string , any >): Promise <string >
getSchema (): string {
return `${this .declaration} | ${this .name} | ${this .description} `
}
abstract get declaration (): string
}
工具类有 name 和 description 两个属性,通过 getSchema 函数返回对该工具的文本描述。这里我们先简单地将两个描述信息拼接一下,为 Agent 提供 4 个算数工具。
⚠️注意!这里只是简单在用大模型调试 prompt,这几个工具并没有具体的代码实现也没有运行。之所以能算出结果都是靠大模型自身的推理能力!(简单的加减乘除即使没有外部工具他也能做到)
1 . Addition Tool : A tool for adding two numbers
2 . Subtraction Tool : A tool for subtracting two numbers
3 . Division Tool : A tool for dividing two numbers
4 . Multiplication Tool : A tool for multiplying two numbers
尽管 Agent 经过多步思考成功解决了这个问题,但有个小 bug,在第二步中,Agent 调用乘法工具传入了 3 个参数。如果我们实现工具的时候函数只接受两个参数这里就会报错。
出现这个问题是因为我们提供的工具描述还不够准确,不仅要提供工具的名字和功能描述,最好的就是将函数的声明也带上。因为我们的函数代码本身是硬编码的,要尽一切可能降低大模型输出的随机性,严格按我们的要求输出。
为工具函数生成函数声明
方案一:手动 Copy 一个笨办法就是直接 copy 自己写的函数声明,硬编码到工具的 description 中。
按两下 cv 键还是很快的。缺点就是 1. 不够灵活,修改函数声明时需要把硬编码的字符串也更改 2. 万一写的是宽松类型的代码(js、python),还得自己想函数声明。
例如对于 call 函数,直接把这个复制下来放到 description 中就行了。
方案二:AI 自动生成 根据'大模型不收敛'定理——当你想为大模型做一件事时,先想想这件事本身是不是也能让大模型做。我们也可以让大模型为工具函数生成函数声明。
请为下面的{language}代码生成函数声明:
{code}
请用一两句话描述下面{language}代码的功能:
{code}
该方案的优缺点和方案一相同。主要针对宽松类型的代码,省了点脑子。
方案三:zod 自动生成 Zod 是一个以 TypeScript 为首的模式声明和验证库,弥补了 TypeScript 无法在运行时进行校验的问题。
依靠 zod 的一些插件,我们可以直接将 zod 定义的类型对象转换成类型声明字符串。
优点就是非常灵活,且支持运行时。唯一的缺点就是要学习 zod 的用法。
将函数声明加入 Prompt 后,可以看到 Agent 学会了多次进行乘法,符合我们的函数声明。
⚠️如果大模型实在是笨的学不会传入正确数量的参数,那就只能将我们的工具函数修改为兼容动态参数的形式了。
Executor 代理执行器 executor 是 Agent 的运行时,可以理解为 AI Agent 的大脑,他协调各个组件并指导操作。这实际上是调用代理,执行它选择的操作,将操作输出传递回代理,然后重复。
class AgentExecutor {
agent : LLMSingleActionAgent
tools : StructuredTool [] = []
maxIterations : number = 15
constructor (agent : LLMSingleActionAgent ) {
this .agent = agent
}
addTool (tools : StructuredTool | StructuredTool [] ) {
const _tools = Array .isArray (tools) ? tools : [tools]
this .tools .push (..._tools)
}
}
最关键的就是 executor 的执行循环了,executor 会始终进行如下事件循环直到 目标被解决了 或者 思考迭代次数超过了最大次数:
根据之前已经完成的所有步骤(一个步骤包括 ReAct 框架中的 Thought、Action、Observation)和 目标(用户的问题)规划出接下来的 Action(使用什么工具 以及 工具的输入)
检测是否已经达成目标,即 Action 是不是 ActionFinish。是的话就返回结果,不是的话说明还有行动要完成
根据 Action,执行具体的工具,等待工具返回结果。工具返回的结果就是这一轮步骤的 Observation
保存当前步骤到记忆上下文,如此反复
async call (input : promptInputs): Promise <AgentFinish > {
const toolsByName = Object .fromEntries (
this .tools .map (t => [t.name , t]),
)
const steps : AgentStep [] = []
let iterations = 0
while (this .shouldContinue (iterations)) {
const output = await this .agent .plan (steps, input)
console .log (iterations, output)
if ('returnValues' in output)
return output
const actions = Array .isArray (output)
? output as AgentAction []
: [output as AgentAction ]
const newSteps = await Promise .all (
actions.map (async (action) => {
const tool = toolsByName[action.tool ]
if (!tool)
throw new Error (`${action.tool} is not a valid tool, try another one.` )
const observation = await tool.call (action.toolInput )
return { action, observation : observation ?? '' }
}),
)
steps.push (...newSteps)
iterations++
}
return {
returnValues : { output : 'Agent stopped due to max iterations.' },
log : '' ,
}
}
运行 这里我们为大模型提供了加减乘除四个工具(按理来说这四个工具不需要实际的函数大模型也可以执行 hhh,但本质上是不一样的)我们可以看到大模型最后成功迭代出了计算结果为 9336 元。
describe ('agent' , () => {
const llm = new AzureLLM ({
apiKey : Config .apiKey ,
model : Config .model ,
})
const agent = new LLMSingleActionAgent ({ llm })
agent.setPrompt (REACT_PROMPT )
agent.addStop (agent.observationPrefix )
agent.addTool ([new AdditionTool (), new SubtractionTool (), new DivisionTool (), new MultiplicationTool ()])
const executor = new AgentExecutor (agent)
executor.addTool ([new AdditionTool (), new SubtractionTool (), new DivisionTool (), new MultiplicationTool ()])
it ('test' , async () => {
const res = await executor.call ({ input : '一种减速机的价格是 750 元,一家企业需要购买 12 台。每台减速机运行一小时的电费是 0.5 元,企业每天运行这些减速机 8 小时。请计算企业购买及一周运行这些减速机的总花费。' })
expect (res).toMatchInlineSnapshot (`
{
"log": "Final Answer: The total cost of purchasing and operating the gearboxes for a week is 9336 yuan.",
"returnValues": {
"output": "The total cost of purchasing and operating the gearboxes for a week is 9336 yuan.",
},
}
` )
}, { timeout : 50000 })
})
可改进的地方
处理代理选择不存在的工具的情况
加入更复杂的记忆 Memory 系统
多个 Agent 合作
更复杂的工具,在一个 Action 中调用多个工具
处理工具错误的情况
处理代理生成无法解析为工具调用的输出的情况
所有级别(代理决策、工具调用)的日志记录和可观察性到标准输出
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
RSA密钥对生成器 生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
Mermaid 预览与可视化编辑 基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
随机西班牙地址生成器 随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online