从零搭建 AI Agent 框架:原理、ReAct 与实现
AI Agent 框架通过语言模型作为推理引擎,具备自主发现问题、确定目标及执行方案的能力。深入解析 Agent 核心组件规划、记忆与工具,对比思维链与 ReAct 框架的运作机理。结合 TypeScript 代码示例,演示如何构建 Agent、定义工具 Schema 及设计执行器循环,涵盖错误处理、中间步骤管理及最佳实践,为开发者提供从理论到落地的完整技术指南。

AI Agent 框架通过语言模型作为推理引擎,具备自主发现问题、确定目标及执行方案的能力。深入解析 Agent 核心组件规划、记忆与工具,对比思维链与 ReAct 框架的运作机理。结合 TypeScript 代码示例,演示如何构建 Agent、定义工具 Schema 及设计执行器循环,涵盖错误处理、中间步骤管理及最佳实践,为开发者提供从理论到落地的完整技术指南。

AI Agent(智能体)的核心思想是使用语言模型(LLM)来选择要采取的一系列操作。在 Agent 架构中,语言模型被用作推理引擎,以确定要采取哪些操作以及按什么顺序执行。
相比于传统软件被动地"给予输入——>做出输出"的模式,Agent 更加强调自主的发现问题、确定目标、构想方案、选择方案、执行方案、检查更新的特性。因此,它被视为一类拥有"自主智能的实体"。
一个典型的 Agent 框架通常包含以下三个核心组件(参考 LangChain 设计思路):
Agent 学习调用外部 API 来获取模型权重中缺失的额外信息。这些信息通常在预训练后很难更改,包括当前实时信息、代码执行能力、对专有数据源的访问等。 工具本质上是 Agent 可以调用的函数。使用工具是 AI Agent 最迷人且先进的特性之一。
语言智能可以被理解为"使用基于自然语言的概念对经验事物进行理解以及在概念之间进行推理的能力"。
大模型具备概念理解能力,但仅仅像 Word2Vec 一样得到语义距离是不够的。真正让人惊讶的是大模型在推理上的能力涌现。推理一般指根据几个已知的前提推导得出新的结论的过程,区别于理解,推理是一个"多步骤"的过程。
2022 年,Google 发布的论文《Chain-of-Thought Prompting Elicits Reasoning in Large Language Models》首次提出,通过让大模型逐步参与将一个复杂问题分解为一步一步的子问题并依次进行求解的过程,可以显著提升大模型的性能。这一系列推理的中间步骤被称为思维链(Chain of Thought)。
使用方法:
Let's think step by step。无论是环境的反馈还是人类的指令,Agent 都需要完成对接收信息的"理解",并依据理解进行意图识别,转化为下一步任务。
ReAct 框架引入了 LLMs 以交错的方式生成 推理轨迹(Reasoning Trajectory)和 任务特定操作(Task-specific Actions)。
ReAct 框架允许 LLMs 与外部工具交互来获取额外信息,从而给出更可靠和实际的回应。结果表明,ReAct 在语言和决策任务上的表现优于多个基线模型,同时提高了人类可解释性和可信度。
运作机理: ReAct 的灵感来自于"行为"和"推理"之间的协同作用。链式思考(CoT)提示显示了 LLMs 执行推理轨迹的能力,但因缺乏和外部世界的接触而导致事实幻觉。ReAct 将推理和行为结合,提示 LLMs 为任务生成口头推理轨迹和操作,支持动态推理来创建、维护和调整操作计划。
常用模板:
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}
先对各组件进行硬编码。我们为大模型提供一个 get_word_length 工具,使用 ReAct 框架看看它能否解决一些基本问题。
System Prompt:
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!
User Input:
Question: How many letters in the word educa
注意:在实际调试阶段,如果工具未具体实现,大模型可能依靠自身推理能力估算结果。但在生产环境中,必须确保工具的真实可用性。
Agent 相当于整体框架的思维推理系统,通常由大模型、Prompt 提供支持。不同的智能体有不同的推理提示风格、不同的输入方式以及不同的解析输出方式。
这里提到的 Agent 其实就是大模型本身,我们在框架中最好将大模型的 API 进行面向对象的封装,方便与框架中其他组件交互。
interface LLMSingleActionAgentParams {
llm: AzureLLM;
tools?: StructuredTool[];
stop?: string[];
}
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;
}
setPrompt(prompt: string): void {
this._prompt = prompt;
}
async plan(steps: any[], input: string): Promise<any> {
// 实际实现需调用 LLM API
return {};
}
}
对大模型的输入可以是普通的 Prompt 字符串;也可以是键值对,结合 Prompt Template 拼接出最终的 Prompt 字符串。
输出是要执行的下一个操作或要发送给用户的最终响应(AgentAction 或 AgentFinish)。
我们需要填充模板的函数,约定变量按 {var} 的格式插入,用正则表达式将字符串替换:
function fillPromptTemplate(promptTemplate: string, inputs: Record<string, any>): string {
let res = promptTemplate;
for (const [key, val] of Object.entries(inputs)) {
res = res.replaceAll(new RegExp(`\\{\\s*${key}\\s*}`, 'g'), val);
}
return res;
}
这是一个数据类,表示代理应采取的操作。它有一个 tool 属性(这是应该调用的工具的名称)和一个 tool_input 属性(该工具的输入)。
这表示代理准备好返回给用户时的最终结果。它包含一个 return_values 键值映射,其中包含最终的代理输出。通常,这包含 output 键,其中包含代理响应的字符串。
这些代表先前的代理操作以及当前代理运行的相应输出。这些对于传递到未来的迭代非常重要,因此代理知道它已经完成了哪些工作。其类型为 List<Tuple[AgentAction, Any]>。
工具类有 name 和 description 两个属性,通过 getSchema 函数返回对该工具的文本描述。
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;
}
为了降低大模型输出的随机性,不仅要提供工具的名字和功能描述,最好的就是将函数的声明也带上。
直接 copy 自己写的函数声明,硬编码到工具的 description 中。缺点是不够灵活,修改函数声明时需要把硬编码的字符串也更改。
根据"大模型不收敛"定理,我们可以让大模型为工具函数生成函数声明。
Prompt 示例:
请为下面的{language}代码生成函数声明:
{code}
Zod 是一个以 TypeScript 为首的模式声明和验证库,弥补了 TypeScript 无法在运行时进行校验的问题。依靠 Zod 的一些插件,我们可以直接将 Zod 定义的类型对象转换成类型声明字符串。
优点是非常灵活,且支持运行时。唯一的缺点是需要学习 Zod 的用法。
将函数声明加入 Prompt 后,可以看到 Agent 学会了多次进行乘法,符合我们的函数声明。如果大模型实在无法传入正确数量的参数,可以将工具函数修改为兼容动态参数的形式。
代理执行器 executor 是 Agent 的运行时,可以理解为 AI Agent 的大脑,它协调各个组件并指导操作。这实际上是调用代理,执行它选择的操作,将操作输出传递回代理,然后重复。
class AgentExecutor {
agent: LLMSingleActionAgent;
tools: StructuredTool[] = [];
maxIterations: number = 15;
constructor(agent: LLMSingleActionAgent) {
this.agent = agent;
}
addTool(tools: StructuredTool | StructuredTool[]): void {
const _tools = Array.isArray(tools) ? tools : [tools];
this.tools.push(..._tools);
}
async call(input: Record<string, any>): Promise<any> {
const toolsByName = Object.fromEntries(
this.tools.map(t => [t.name, t]),
);
const steps: any[] = [];
let iterations = 0;
while (this.shouldContinue(iterations)) {
const output = await this..(steps, input.);
.(iterations, output);
( output) {
output;
}
actions = .(output)
? output
: [output];
newSteps = .(
actions.( (action) => {
tool = toolsByName[action.];
(!tool) {
();
}
observation = tool.(action.);
{ action, : observation ?? };
}),
);
steps.(...newSteps);
iterations++;
}
{
: { : },
: ,
};
}
(: ): {
iterations < .;
}
}
最关键的就是 executor 的执行循环了,executor 会始终进行如下事件循环直到目标被解决了或者思考迭代次数超过了最大次数:
AgentFinish。是的话就返回结果,不是的话说明还有行动要完成。这里我们为大模型提供了加减乘除四个工具。我们可以看到大模型最后成功迭代出了计算结果。
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 calculation', async () => {
const res = await executor.call({
input: '一种减速机的价格是 750 元,一家企业需要购买 12 台。每台减速机运行一小时的电费是 0.5 元,企业每天运行这些减速机 8 小时。请计算企业购买及一周运行这些减速机的总花费。'
});
expect(res.returnValues.output).();
}, { : });
});
虽然基础框架已搭建完成,但在生产环境中还需要考虑以下优化点:
本文详细讲解了如何从零搭建一个基于 ReAct 模式的 AI Agent 框架。通过拆解 Planning、Memory、Tool 三大核心组件,并结合 CoT 推理方法,实现了具备自主规划能力的智能体。代码部分展示了如何使用 TypeScript 封装 Agent 和 Executor,以及如何规范工具定义以减少幻觉。掌握这些技术有助于开发者构建更强大的自动化应用,应对复杂的业务场景。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online