Anthropic 在 2026 年发布的数据显示,基于 Agent 架构的 AI 应用比纯 Chat 架构的任务完成率高出 3.2 倍,但市面上的 Agent 框架(LangChain、LlamaIndex、CrewAI)动辄数万行代码,抽象层叠抽象层,出了问题根本没法调试。本文的核心观点是:一个生产可用的 AI Agent 框架,核心逻辑不超过 300 行 TypeScript。理解了底层原理,你才能在框架之上做出正确的架构决策。
📌 记住: Agent 的本质不是「更聪明的 Chat」,而是一个 感知-推理-行动-观察 的循环。理解这个循环,就理解了所有 Agent 框架的共同内核。
🧠 一、Agent 核心架构:从 Chat 到 Agent 的本质区别
1.1 Chat 模式 vs Agent 模式
Chat 模式是单轮交互:用户发消息,LLM 返回回答,结束。整个过程是被动的——LLM 只负责「回答」,不负责「行动」。Agent 模式完全不同——LLM 会自主决定下一步做什么:调用工具、查询数据库、执行代码,然后根据结果继续推理,直到任务完成。
这个区别的实际影响有多大?举个例子:用户问「北京和上海哪个更热?」,Chat 模式下 LLM 只能凭训练数据猜测(可能过时),而 Agent 模式会先查天气 API、再做比较计算、最后给出有数据支撑的回答。准确性差距是数量级的。
Chat 模式: 用户 → LLM → 回答
Agent 模式: 用户 → LLM → 思考 → 调用工具 → 观察 → 继续思考 → ... → 最终回答
核心区别在于 Tool Calling(工具调用) 和 Agent Loop(代理循环) 两个机制。Tool Calling 让 LLM 从「只能说」变成「能做事」;Agent Loop 让 LLM 从「一次性回答」变成「多步推理」。两者结合,才是真正的 Agent。
一个完整的 Agent 由四个组件构成:
| 组件 | 职责 | 类比 |
|---|---|---|
| 🧠 LLM | 推理与决策 | 大脑 |
| 🔧 Tools | 执行操作 | 双手 |
| 💾 Memory | 存储上下文 | 记忆 |
| 🔄 Loop | 驱动循环 | 意志力 |
💡 提示: Loop 引擎最容易被忽视但最关键。没有 Loop,Agent 就退化为一次性的 Function Calling。Loop 的核心设计决策包括:最大循环次数(防失控)、错误处理策略(防崩溃)、和终止条件判断(防死循环)。
🔧 二、Tool Calling 机制实现
2.1 工具注册中心
Tool Calling 的本质是结构化输出协议——LLM 返回 JSON 表示「我想调用某个工具,参数是 xxx」。这个协议最初由 OpenAI 在 2023 年提出(当时叫 Function Calling),现在已成为行业标准,Anthropic、Google、Mistral 都支持。
工具定义的关键在于 description 字段——它直接决定了 LLM 能否正确选择和使用工具。好的 description 应该:说清楚工具做什么、接受什么输入、返回什么输出、什么时候该用什么时候不该用。模糊的 description 会导致 LLM 频繁选错工具,这是新手最常犯的错误。
先实现工具注册中心:
// 工具定义与注册中心
interface ToolDefinition {
name: string;
description: string; // 告诉 LLM 这个工具做什么
parameters: {
type: 'object';
properties: Record<string, { type: string; description: string; enum?: string[] }>;
required: string[];
};
execute: (args: Record<string, unknown>) => Promise<string>;
}
class ToolRegistry {
private tools = new Map<string, ToolDefinition>();
register(tool: ToolDefinition): void {
if (this.tools.has(tool.name)) throw new Error(`Duplicate tool: ${tool.name}`);
this.tools.set(tool.name, tool);
}
// 给 LLM 看的定义(不含 execute 函数)
getDefinitions() {
return Array.from(this.tools.values()).map(({ execute, ...def }) => def);
}
async execute(name: string, args: Record<string, unknown>): Promise<string> {
const tool = this.tools.get(name);
if (!tool) return JSON.stringify({ error: `Unknown tool: ${name}` });
try {
return await tool.execute(args);
} catch (err) {
return JSON.stringify({ error: `Tool "${name}" failed: ${err instanceof Error ? err.message : String(err)}` });
}
}
}
2.2 注册实际工具
// 天气查询 + 数学计算两个示例工具
const registry = new ToolRegistry();
registry.register({
name: 'get_weather',
description: '获取指定城市的当前天气信息,返回温度、湿度和天气状况',
parameters: {
type: 'object',
properties: {
city: { type: 'string', description: '城市名称,如"北京"' }
},
required: ['city']
},
execute: async (args) => {
const data: Record<string, object> = {
'北京': { city: '北京', temp: 32, humidity: 45, condition: '晴' },
'上海': { city: '上海', temp: 28, humidity: 72, condition: '多云' },
};
return JSON.stringify(data[args.city as string] ?? { error: '城市未找到' });
}
});
registry.register({
name: 'calculate',
description: '执行数学计算,支持加减乘除',
parameters: {
type: 'object',
properties: {
expression: { type: 'string', description: '数学表达式,如 "2 + 3 * 4"' }
},
required: ['expression']
},
execute: async (args) => {
// ⚠️ 生产环境请使用 mathjs 等安全解析器
try {
const result = Function(`"use strict"; return (${args.expression})`)();
return JSON.stringify({ expression: args.expression, result });
} catch {
return JSON.stringify({ error: `无法计算: ${args.expression}` });
}
}
});
⚠️ 警告: 上面的
calculate使用了Function()构造器,生产环境是严重安全隐患。应使用 mathjs 或 expr-eval 等安全表达式解析库。
2.3 LLM 调用封装
// 将工具转换为 OpenAI function calling 格式并调用 LLM
interface LLMMessage {
role: 'system' | 'user' | 'assistant' | 'tool';
content: string;
tool_call_id?: string;
tool_calls?: Array<{
id: string;
type: 'function';
function: { name: string; arguments: string };
}>;
}
async function callLLM(messages: LLMMessage[], tools: object[]): Promise<LLMMessage> {
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: 'gpt-4o',
messages,
tools,
tool_choice: 'auto', // 让模型自己决定是否调用工具
temperature: 0.1, // Agent 场景建议低温度
}),
});
const data = await response.json();
return data.choices[0].message;
}
🔄 三、Agent Loop:ReAct 模式的核心实现
3.1 ReAct 循环
ReAct(Reasoning + Acting)是 2022 年由 Yao et al. 提出的 Agent 推理模式,目前被几乎所有主流框架采用。每轮循环包含三个步骤:Thought(LLM 分析当前状态,决定下一步)→ Action(调用工具获取外部信息)→ Observation(将工具结果反馈给 LLM)。
ReAct 的核心优势在于可解释性——每一步推理都有明确的 Thought 记录,开发者可以清楚地看到 LLM 为什么做出某个决策。这比「端到端黑盒」的方案更容易调试和审计。但 ReAct 也有缺点:每轮都要调用 LLM,延迟和成本随循环次数线性增长。对于简单任务(1-2 步),直接的 Function Calling 更高效;只有需要多步推理、条件分支、或错误恢复的场景,才值得使用 Agent Loop。
3.2 完整 Agent Loop(核心代码,不到 80 行)
// Agent Loop:ReAct 模式的核心实现
interface AgentConfig {
maxIterations?: number;
systemPrompt?: string;
verbose?: boolean;
}
class MiniAgent {
private registry: ToolRegistry;
private config: Required<AgentConfig>;
constructor(registry: ToolRegistry, config: AgentConfig = {}) {
this.registry = registry;
this.config = {
maxIterations: config.maxIterations ?? 10,
systemPrompt: config.systemPrompt ?? '你是一个有用的AI助手,可以使用工具来完成任务。',
verbose: config.verbose ?? false,
};
}
async run(userInput: string): Promise<string> {
const messages: LLMMessage[] = [
{ role: 'system', content: this.config.systemPrompt },
{ role: 'user', content: userInput },
];
const tools = this.registry.getDefinitions().map(t => ({
type: 'function' as const, function: t
}));
for (let i = 0; i < this.config.maxIterations; i++) {
const assistant = await callLLM(messages, tools);
messages.push(assistant);
// 没有工具调用 → 任务完成
if (!assistant.tool_calls?.length) return assistant.content ?? '';
// 执行所有工具调用
for (const tc of assistant.tool_calls) {
const args = JSON.parse(tc.function.arguments);
if (this.config.verbose) console.log(`🔧 ${tc.function.name}(${JSON.stringify(args)})`);
const result = await this.registry.execute(tc.function.name, args);
if (this.config.verbose) console.log(`📋 ${result.substring(0, 200)}`);
messages.push({ role: 'tool', content: result, tool_call_id: tc.id });
}
}
return '⚠️ 达到最大循环次数,任务未完成';
}
}
3.3 运行示例:多步推理
// 创建 Agent 并执行需要 3 步的多步任务
const agent = new MiniAgent(registry, {
maxIterations: 8,
verbose: true,
systemPrompt: '你是天气分析助手。先查天气,需要计算时用计算工具,最后给出分析结论。',
});
const result = await agent.run('北京和上海的温差是多少?哪个城市更适合出门?');
console.log(result);
预期执行流程:
🔧 get_weather({"city":"北京"}) → {"temp":32,"humidity":45,"condition":"晴"}
🔧 get_weather({"city":"上海"}) → {"temp":28,"humidity":72,"condition":"多云"}
🔧 calculate({"expression":"32-28"}) → {"result":4}
✅ 最终回答:温差 4°C,建议北京(晴,湿度低)
💾 四、Memory 系统:让 Agent 拥有记忆
4.1 对话历史管理
基础 Agent 有一个严重缺陷:每轮对话都是「一次性」的。当对话历史超过上下文窗口(Context Window),早期的信息就会丢失。更糟的是,工具调用的 JSON 结果通常很长(几百到几千 token),几轮对话就能撑爆上下文。
Memory 系统的核心策略是分层管理:短期记忆(当前对话的完整历史)、工作记忆(当前任务的关键中间结果)、和压缩记忆(旧对话的自动摘要)。下面实现的是短期记忆 + 自动压缩的方案,这是最实用的起步方案:
// 带自动摘要的对话记忆
class ConversationMemory {
private messages: LLMMessage[] = [];
private summary = '';
constructor(private maxMessages = 20) {}
add(message: LLMMessage): void {
this.messages.push(message);
if (this.messages.length > this.maxMessages) this.compress();
}
private compress(): void {
const keep = Math.floor(this.maxMessages * 0.6);
const old = this.messages.slice(0, -keep);
this.messages = this.messages.slice(-keep);
const oldText = old
.filter(m => m.role === 'user' || m.role === 'assistant')
.map(m => `${m.role}: ${(m.content ?? '').substring(0, 200)}`)
.join('\n');
this.summary += `\n[历史摘要]\n${oldText}\n`;
}
getMessages(): LLMMessage[] {
const result: LLMMessage[] = [];
if (this.summary) result.push({ role: 'system', content: `之前的对话摘要:${this.summary}` });
result.push(...this.messages);
return result;
}
}
⚠️ 警告: 工具调用历史可能包含敏感信息。生产环境中务必对
args和result做脱敏处理后再存储。
⚡ 五、流式输出与错误恢复
5.1 流式 Agent
用户不想等 Agent 完成所有循环才看到结果。用 AsyncGenerator 实现实时输出:
// 流式 Agent:实时输出每个步骤
class StreamingAgent extends MiniAgent {
async *runStream(userInput: string): AsyncGenerator<{
type: 'thinking' | 'tool_call' | 'tool_result' | 'answer';
content: string;
}> {
const messages: LLMMessage[] = [
{ role: 'system', content: this.config.systemPrompt },
{ role: 'user', content: userInput },
];
const tools = this.registry.getDefinitions().map(t => ({
type: 'function' as const, function: t
}));
for (let i = 0; i < this.config.maxIterations; i++) {
yield { type: 'thinking', content: `🔄 第 ${i + 1} 轮推理...` };
const assistant = await callLLM(messages, tools);
messages.push(assistant);
if (!assistant.tool_calls?.length) {
yield { type: 'answer', content: assistant.content ?? '' };
return;
}
for (const tc of assistant.tool_calls) {
const args = JSON.parse(tc.function.arguments);
yield { type: 'tool_call', content: `🔧 ${tc.function.name}(${JSON.stringify(args)})` };
const start = Date.now();
const result = await this.registry.execute(tc.function.name, args);
yield { type: 'tool_result', content: `📋 (${Date.now() - start}ms) ${result.substring(0, 300)}` };
messages.push({ role: 'tool', content: result, tool_call_id: tc.id });
}
}
yield { type: 'answer', content: '⚠️ 达到最大循环次数' };
}
}
5.2 错误恢复策略
Agent Loop 中最常见的失败是 LLM「幻觉工具调用」。错误处理策略:
| 错误类型 | 处理策略 | 推荐做法 |
|---|---|---|
| 工具不存在 | 返回错误列表给 LLM | ✅ 返回清晰的错误信息 |
| 参数格式错误 | 反馈给 LLM 自动修正 | ✅ 包含期望的参数格式 |
| 工具执行超时 | 设置超时中断 | ✅ 使用 AbortController |
| API 限流 | 指数退避重试 | ✅ 最多重试 3 次 |
| LLM 幻觉工具名 | 列出可用工具 | ✅ 在 error 中列出可选项 |
⚠️ 警告: 永远不要让 Agent Loop 无限循环。设置
maxIterations(建议 5-15),并监控 token 消耗。一个失控的 Agent 可能在几分钟内烧掉上百美元。
📊 六、框架对比与选型建议
| 特性 | Mini Agent(本文) | LangChain | LlamaIndex | CrewAI |
|---|---|---|---|---|
| 代码量 | ~300 行 | ~50,000 行 | ~30,000 行 | ~15,000 行 |
| 学习曲线 | ⭐ 极低 | ⭐⭐⭐⭐ 高 | ⭐⭐⭐ 中 | ⭐⭐⭐ 中 |
| 调试难度 | ✅ 直接看代码 | ❌ 层层抽象 | ⚠️ 部分可见 | ⚠️ 部分可见 |
| Memory | ✅ 基础 | ✅ 完善 | ✅ 完善 | ✅ 完善 |
| Multi-Agent | ❌ 需扩展 | ✅ 内置 | ⚠️ 有限 | ✅ 核心特性 |
| 依赖体积 | ~0(纯 TS) | ~15MB | ~10MB | ~8MB |
⚡ 关键结论: 如果需求是「LLM + 几个工具 + 简单记忆」,300 行代码完全够用。只有需要复杂多 Agent 协作、RAG 集成时,才值得引入大型框架。
生产环境必须做的五件事
- ✅ 设置 Token 上限:每次 LLM 调用限制
max_tokens,防止输出过长 - ✅ 超时控制:每个工具调用设置 30 秒超时,使用
AbortController - ✅ 日志记录:记录每轮 Thought/Action/Observation,用于调试审计
- ✅ 成本监控:跟踪 token 消耗,设置每日预算上限
- ✅ 输入验证:对 LLM 输出的工具参数做严格校验,不要信任模型输出
必须避免的五个坑
- ❌ 不要用 eval 执行 LLM 生成的代码——最严重的安全漏洞
- ❌ 不要把系统密钥放在工具描述中——LLM 可能泄露
- ❌ 不要忽略工具调用错误——错误信息是 LLM 自我修正的关键信号
- ❌ 不要在 Agent Loop 中做数据库写操作——LLM 决策可能有误
- ❌ 不要让 Agent 访问生产环境——先在沙箱验证
⚡ 关键结论: Agent 的安全性与能力成正比。一个能调用 10 个工具的 Agent,风险面是单工具 Agent 的 10 倍。遵循最小权限原则——只给 Agent 完成任务所必需的最少工具和权限。
📝 总结
一个 AI Agent 框架的核心只需要三个组件:Tool Registry(工具注册)、Agent Loop(ReAct 循环)、Memory(记忆管理)。理解这三层,你就拥有了评估任何 Agent 框架的能力。
选型建议:
- 🎯 快速原型 / 学习原理:本文的 Mini Agent,300 行搞定
- 🎯 单 Agent + 多工具生产应用:LangChain 或自研,重点做好错误处理和监控
- 🎯 多 Agent 协作:CrewAI 或 AutoGen,做好成本控制
- 🎯 RAG 增强 Agent:LlamaIndex,检索和索引能力最强
推荐工具:OpenAI Function Calling 文档、Anthropic Tool Use、Vercel AI SDK、Instructor
💡 提示: Agent 开发的核心不是代码量,而是对 LLM 行为的理解。多试几个 prompt,观察 LLM 在不同场景下的 Tool Calling 决策,这比任何框架文档都有价值。