前言
AI时代,人人都在用Cluade Code、Codex、Cusor等Agent开发,AI Agent最近已经变得非常强大,编译效率几乎比古法编程高了一个数量级。
在如此依赖AI Agent之后,我不禁产生一些疑问,我真的懂AI Agent吗?我真的会用AI Agent吗?在很多时候,AI Agent在我面前几乎是一个黑盒,我输入一些内容,然后AI Agent给我一些答复,写了一些代码,有时候这些代码都很正确,AI Agent似乎完全理解了我的需求,而有时候和我要的结果大相径庭。
几乎这些原因,我想我应该对AI Agent有一些更多的了解,实践是最好的老师,在这个想法之下,我问AI,我该如何学习开发自己的Agent,AI在思考了一会后输出了一大段文字,我在其中找到了一个适合入门的框架,LangChain。
用最好理解的话来说,LangChain就是一个用来开发Agent的框架,学习路劲比较平滑,有Python和TypeScript两种语言可选。
因为前端开发的背景,自然而然就选择了LangChain,有了学习的方向,该如何执行学习计划呢?没错,还是AI Agent,我新建了一个项目,然后和Cluade Code经过了简单的对话,我就得到了一个学习计划。然后再经过几轮对话,我得到了一个完整的项目,用于理解和学习LangChain
基础概念
LLM(大语言模型)
大语言模型(Large Language Model,简称LLM)是一种基于深度学习的人工智能技术,主要用于生成和理解自然语言文本。这些模型通过在大量文本数据上进行训练,学习语言的模式和结构,从而能够生成自然语言文本或理解语言文本的含义。我们使用的ChatGpt、DeepSeek、Gemini等都是大语言模型。
一句话总结
LLM = 一个输入文字 → 输出文字的 AI 程序。你给它 Prompt,它返回 Completion。
Agent
Agent,或称智能代理,是指能够自主感知环境、做出决策并采取行动的计算实体。Agent的核心功能包括感知、决策和行动。这些功能使得Agent能够在动态环境中自主地执行任务,从而减少对人类干预的依赖。Agent 能自己做决策、多步执行、知道何时停止。Agent 把 LLM 放进反馈循环里,让它在"思考→行动→观察"中逼近答案。
一句话总结
Agent = LLM(大脑)+ Tools(手脚)+ 思考循环(什么时候做什么)。让 LLM 能自主决策、多步执行。
有了两个基础概念,让我们从代码来看Agent是如何实现的。
基本对话
要实现一个基本对话,只需要LLM的能力即可。
// 1. 创建模型实例,使用deepseek
const model = new ChatDeepSeek({
model: config.model,
apiKey: config.apiKey,
temperature: 0.7,
});
// 2. 调用模型
const result = await model.invoke("你好!请用一句话介绍你自己");
// 3. 查看结果
// result 是 AIMessage 类型,包含多个字段
console.log("✅ 收到回复:");
console.log(` ${result.content}\n`);
model.invoke() 是基础的大模型调用,类似使用deepSeek网页端进行一次对话,你问,大模型回答。
多轮对话
在人类的对话中,我们之所以能把聊天正常进行下去,是因为人类有记忆,能记住之前的对话。而大模型的每次对话都是独立的,如果没有上下文,大模型就只能进行单论的对话。
const model = new ChatDeepSeek({
model: config.model,
apiKey: config.apiKey,
temperature: 0.7,
});
// ─── 演示 1:没有历史消息 ─────────────────
console.log("📌 演示 1:没有上下文(模型不记得之前说了什么)\n");
console.log("💬 Q1: 我叫小明,我是一名前端工程师");
const r1 = await model.invoke("我叫小明,我是一名前端工程师");
console.log(`🤖 A1: ${r1.content}`);
console.log('\n💬 Q2: 我叫什么名字?我的职业是什么?');
const r2 = await model.invoke("我叫什么名字?我的职业是什么?");
console.log(`🤖 A2: ${r2.content}`);
console.log(" ⚠️ 注意:模型不知道你是谁,因为每次 invoke 是独立的\n");
要让大模型具有上下文,需要用消息数组
// ─── 演示 2:传递完整消息历史 ─────────────────
console.log("📌 演示 2:传递完整消息历史(模型能记住上下文)\n");
// 消息数组 = 对话状态
// 类比前端的状态: const [messages, setMessages] = useState([])
const messages: BaseMessage[] = [
new SystemMessage("你是一个友好的助手,用中文回答问题。"),
new HumanMessage("我叫小明,我是一名前端工程师"),
];
// 第一轮
const r3 = await model.invoke(messages);
console.log(`💬 Q1: 我叫小明,我是一名前端工程师`);
console.log(`🤖 A1: ${r3.content}`);
// 把 AI 的回复也加入到消息历史中
messages.push(r3);
// 第二轮 —— 模型能记住!
messages.push(new HumanMessage("我叫什么名字?我的职业是什么?"));
console.log('\n💬 Q2: 我叫什么名字?我的职业是什么?');
const r4 = await model.invoke(messages);
console.log(`🤖 A2: ${r4.content}`);
console.log(" ✅ 注意:这次模型记住了,因为历史消息一起传入了\n");
console.log("📦 当前消息数组有", messages.length, "条消息:");
messages.forEach((m, i) => {
console.log(` [${i}] ${m.type}: ${typeof m.content === 'string' ? m.content.substring(0, 40) + '...' : '[complex content]'}`);
});
对话记忆
事实上,LangChain框架提供了封装的记忆功能,不需要手动在message中添加对话历史。
const model = new ChatDeepSeek({
model: config.model,
apiKey: config.apiKey,
temperature: 0.7,
});
// 1. 创建对话历史管理器
// 类比:这个就是对话的 "数据库"
// 它负责存储和检索所有的对话消息
const history = new InMemoryChatMessageHistory();
console.log("📦 创建 InMemoryChatMessageHistory(内存对话历史)\n");
// 2. 添加系统提示词
await history.addMessage(
new SystemMessage("你是一个友好的助手,用中文回答问题。")
);
// 3. 封装一个简单的聊天函数 —— 每次调用都自动:
// a) 获取历史消息
// b) 添加用户消息
// c) 调用模型
// d) 保存 AI 回复
async function chat(userInput: string): Promise<string> {
// 获取历史消息
const pastMessages = await history.getMessages();
// 添加新的用户消息并调用模型
const messages = [...pastMessages, new HumanMessage(userInput)];
const response = await model.invoke(messages);
// 保存 AI 回复到历史
await history.addMessage(new HumanMessage(userInput));
await history.addMessage(response);
return typeof response.content === "string" ? response.content : "[复杂内容]";
}
// 4. 多轮对话 —— 不需要手动管理 messages 数组!
console.log("📌 演示:自动记忆对话\n");
console.log("💬 用户: 我叫小明,我喜欢 React 和 TypeScript");
const r1 = await chat("我叫小明,我喜欢 React 和 TypeScript");
console.log(`🤖 AI: ${r1}\n`);
console.log("💬 用户: 我最喜欢的后端框架是什么?");
const r2 = await chat("我最喜欢的后端框架是什么?");
console.log(`🤖 AI: ${r2}\n`);
console.log("💬 用户: 我的名字是什么?我喜欢哪些技术?");
const r3 = await chat("我的名字是什么?我喜欢哪些技术?");
console.log(`🤖 AI: ${r3}\n`);
// 5. 查看历史
const allMessages = await history.getMessages();
console.log(`📊 当前对话历史共 ${allMessages.length} 条消息\n`);
// 6. 清空历史
await history.clear();
console.log("🧹 清空对话历史...");
console.log(`📊 当前对话历史共 ${(await history.getMessages()).length} 条消息`);
封装的chat方法,会把每次对话的内容都存在历史中,让大模型拥有该次对话的记忆。
工具
Agent除了能进行对话之外,还有很重要的一个功能是可以使用工具,例如可以查询网络上的信息、修改本地文件等。
// ─── 1. 创建工具 ─────────────────
// 工具 = 名字 + 描述 + 参数 schema + 执行函数
// 类比前端的:定义一个带类型校验的 API handler
const getWeather = tool(
// 工具的执行函数 —— 真正干活的代码
async ({ location }) => {
// 模拟天气数据(实际项目中这里会调用真实的天气 API)
const weatherData: Record<string, string> = {
"北京": "晴天,25°C,湿度 40%,微风",
"上海": "多云,28°C,湿度 65%,东南风 3 级",
"深圳": "阵雨,30°C,湿度 80%,西南风 2 级",
};
return weatherData[location] ?? `未找到 ${location} 的天气数据`;
},
{
// 工具元数据 —— 告诉 LLM 怎么调用这个工具
name: "get_weather",
description: "获取指定城市的当前天气信息。输入城市名称即可。",
// Zod schema = 参数校验规则
schema: z.object({
location: z.string().describe("城市名称,例如:北京、上海、深圳"),
}),
}
);
const calculator = tool(
async ({ expression }) => {
try {
// 注意:生产环境不要直接 eval,这里仅用于学习演示
const result = Function(`"use strict"; return (${expression})`)();
return `计算结果: ${expression} = ${result}`;
} catch {
return `无法计算: ${expression}`;
}
},
{
name: "calculator",
description: "执行数学计算。支持加减乘除、括号等基本运算。",
schema: z.object({
expression: z.string().describe("数学表达式,例如:3 + 5 * 2"),
}),
}
);
console.log("🔧 已创建 2 个工具:get_weather, calculator\n");
const model = new ChatDeepSeek({
model: config.model,
apiKey: config.apiKey,
temperature: 0,
});
// ─── 2. 绑定工具 ─────────────────
// bindTools() 告诉模型:"你可以使用这些工具"
// 返回一个新的 Runnable,调用时会自动带上工具定义
const modelWithTools = model.bindTools([getWeather, calculator]);
// ─── 3. 演示:模型自动决定是否调用工具 ─────────────────
// 场景 A:需要工具的问题 —— 模型会返回 tool_calls
console.log("📌 场景 A:模型识别需要调用工具\n");
console.log('💬 用户: 北京今天天气怎么样?');
const responseA = await modelWithTools.invoke([
new HumanMessage("北京今天天气怎么样?"),
]);
const toolCallsA = responseA.tool_calls ?? [];
console.log(`🤖 模型决定: ${toolCallsA.length > 0 ? "🔧 调用工具" : "💬 直接回答"}`);
if (toolCallsA.length > 0) {
for (const tc of toolCallsA) {
console.log(` 工具名: ${tc.name}`);
console.log(` 参数: ${JSON.stringify(tc.args)}`);
}
}
// 场景 B:不需要工具的问题 —— 模型会直接回答
console.log('\n📌 场景 B:模型识别不需要工具\n');
console.log('💬 用户: 什么是 TypeScript?');
const responseB = await modelWithTools.invoke([
new HumanMessage("什么是 TypeScript?"),
]);
const toolCallsB = responseB.tool_calls ?? [];
console.log(`🤖 模型决定: ${toolCallsB.length > 0 ? "🔧 调用工具" : "💬 直接回答"}`
);
console.log(` 回复: ${typeof responseB.content === 'string' ? responseB.content.substring(0, 100) + '...' : '[复杂内容]'}`);
// ─── 4. 手动执行工具调用 ─────────────────
console.log("\n📌 演示:手动执行工具调用\n");
console.log('💬 用户: 北京天气怎么样?上海呢?(还需要算 123 * 456)');
const responseC = await modelWithTools.invoke([
new HumanMessage("北京天气怎么样?上海呢?(另外帮我算一下 123 * 456)"),
]);
const toolCallsC = responseC.tool_calls ?? [];
console.log(`🔧 模型想要调用 ${toolCallsC.length} 个工具:`);
for (const tc of toolCallsC) {
console.log(` - ${tc.name}(${JSON.stringify(tc.args)})`);
}
// 执行工具并创建 ToolMessage
const toolByName = new Map<string, StructuredToolInterface>([
[getWeather.name, getWeather],
[calculator.name, calculator],
]);
for (const tc of toolCallsC) {
const tool = toolByName.get(tc.name);
if (!tool) {
console.log(` ⚠️ 未找到工具: ${tc.name}`);
continue;
}
// invoke(toolCall) 会自动解析参数、调用函数、返回 ToolMessage
const toolMessage = await tool.invoke(tc);
console.log(` ✅ ${tc.name} 结果: ${toolMessage.content}`);
}
使用model.bindTools可以让模型使用绑定的工具。大模型在接受到信息之后,可以自己判断是否要是用工具,在定义工具的时候定义了description,大模型可以根据description判断是否要使用工具。
基础Agent
一个基础的LLM,现在有了记忆,有了工具,接下来只需要一个思考的循环,就可以成为一个agent,把以上的内容串起来,并给他一个循环,就是基础的Agent了。
const getWeather = tool(
async ({ location }) => {
const weatherData: Record<string, string> = {
"北京": "晴天,25°C,湿度 40%,北风 2 级",
"上海": "多云,28°C,湿度 65%,东南风 3 级",
"深圳": "阵雨,30°C,湿度 80%,西南风 2 级",
"杭州": "晴转多云,26°C,湿度 55%,东风 2 级",
};
return weatherData[location] ?? `未找到 ${location} 的天气数据(已知城市:北京、上海、深圳、杭州)`;
},
{
name: "get_weather",
description: "获取指定中国城市的当前天气信息",
schema: z.object({
location: z.string().describe("城市名称,例如:北京、上海、深圳"),
}),
}
);
const calculator = tool(
async ({ expression }) => {
try {
const result = Function(`"use strict"; return (${expression})`)();
return `${expression} = ${result}`;
} catch {
return `无法计算表达式: ${expression}`;
}
},
{
name: "calculator",
description: "执行数学计算,支持加减乘除、括号、幂运算等",
schema: z.object({
expression: z.string().describe("数学表达式,例如:(3 + 5) * 2"),
}),
}
);
// ─── Agent 循环 ───────────────────────────────
/**
* Agent 循环的核心实现
*
* ReAct 模式:Reasoning(推理)+ Acting(行动)
*
* 循环流程:
* 1. LLM 接收消息 → 思考(Reasoning)
* 2. 如果决定调用工具 → 行动(Acting)
* 3. 工具返回结果 → 观察(Observation)
* 4. 重复 1-3,直到 LLM 给出最终答案
*/
async function runAgent(
model: ChatDeepSeek,
tools: StructuredToolInterface[],
userInput: string,
options: {
maxIterations?: number;
verbose?: boolean;
} = {}
): Promise<{ content: string; messages: BaseMessage[]; iterations: number }> {
const { maxIterations = 10, verbose = true } = options;
// 步骤 1:构建工具注册表(按名称索引)
const toolByName = new Map(tools.map((t) => [t.name, t]));
const toolNames = tools.map((t) => t.name).join(", ");
// 步骤 2:绑定工具到模型
const modelWithTools = model.bindTools(tools);
// 步骤 3:初始化消息列表
const systemPrompt = `你是一个智能助手,可以使用以下工具来帮助用户:
- ${tools.map((t) => `${t.name}: ${t.description}`).join("\n- ")}
使用工具时:
1. 如果需要信息,主动调用合适的工具
2. 基于工具返回的结果给出准确的回答
3. 如果工具返回错误,尝试其他方法或如实告知用户`;
const messages: BaseMessage[] = [
new SystemMessage(systemPrompt),
new HumanMessage(userInput),
];
if (verbose) {
console.log("🤖 Agent 启动!");
console.log(`🔧 可用工具: ${toolNames}`);
console.log(`📝 用户输入: ${userInput}\n`);
}
// 步骤 4:Agent 主循环
let iterations = 0;
while (iterations < maxIterations) {
iterations++;
if (verbose) {
console.log(`─── 第 ${iterations} 轮迭代 ───`);
}
// 4a. 调用 LLM
const response: AIMessage = await modelWithTools.invoke(messages);
messages.push(response);
// 4b. 检查是否有工具调用
if (!response.tool_calls || response.tool_calls.length === 0) {
// ✅ 没有工具调用 → LLM 给出了最终答案
if (verbose) {
console.log(`✅ Agent 完成(共 ${iterations} 轮迭代)\n`);
}
return {
content: typeof response.content === "string" ? response.content : "[复杂内容]",
messages,
iterations,
};
}
// 4c. 有工具调用 → 执行工具
if (verbose) {
console.log(`🔧 LLM 想要调用 ${response.tool_calls.length} 个工具:`);
}
for (const tc of response.tool_calls) {
const tool = toolByName.get(tc.name);
if (verbose) {
console.log(` 📞 调用: ${tc.name}(${JSON.stringify(tc.args)})`);
}
if (!tool) {
// 工具未找到
const errorMsg = `错误:未找到工具 "${tc.name}"。可用工具:${toolNames}`;
if (verbose) console.log(` ❌ ${errorMsg}`);
messages.push(
new ToolMessage({
content: errorMsg,
tool_call_id: tc.id!,
name: tc.name,
})
);
continue;
}
try {
// invoke(toolCall) → 自动解析参数、调用函数、返回 ToolMessage
const toolMessage = await tool.invoke(tc);
if (verbose) {
const preview =
typeof toolMessage.content === "string"
? toolMessage.content.substring(0, 80)
: "[复杂内容]";
console.log(` ✅ 结果: ${preview}`);
}
messages.push(toolMessage);
} catch (err) {
const errorMsg = `工具执行出错: ${err instanceof Error ? err.message : String(err)}`;
if (verbose) console.log(` ❌ ${errorMsg}`);
messages.push(
new ToolMessage({
content: errorMsg,
tool_call_id: tc.id!,
name: tc.name,
})
);
}
}
if (verbose) console.log(); // 空行
}
// 达到最大迭代次数 → 强制要求 LLM 总结
if (verbose) {
console.log(`⚠️ 达到最大迭代次数 (${maxIterations}),强制要求总结`);
}
messages.push(
new HumanMessage("请基于已有的工具调用结果,给出最终答案。不要继续调用工具。")
);
const finalResponse = await model.invoke(messages);
return {
content: typeof finalResponse.content === "string" ? finalResponse.content : "[复杂内容]",
messages,
iterations,
};
}
// ─── 主函数 ───────────────────────────────────
async function main() {
console.log("═══════════════════════════════════════");
console.log(" 步骤 5:手写 Agent 循环 ⭐");
console.log("═══════════════════════════════════════\n");
validateConfig();
const model = new ChatDeepSeek({
model: config.model,
apiKey: config.apiKey,
temperature: 0, // 温度设为 0 让工具调用更可预测
});
const tools = [getWeather, calculator];
// ─── 测试场景 ─────────────────
const testCases = [
"北京今天天气怎么样?",
"北京的温度加上上海温度(假设上海28°C)是多少?",
];
for (const input of testCases) {
console.log("━".repeat(50));
const result = await runAgent(model, tools, input, {
maxIterations: 10,
verbose: true,
});
console.log(`💬 用户: ${input}`);
console.log(`🤖 Agent: ${result.content}`);
console.log(`📊 统计: ${result.iterations} 轮迭代, ${result.messages.length} 条消息\n`);
}
}
main().catch((err) => {
console.error("❌ 出错:", err.message);
process.exit(1);
});
以上runAgent中,有一个while循环,每次循环调用一次invoke,根据invoke中是否使用工具的结果,判断是否进入下一次循环或者退出。
总结
以上就是基本的Agent知识学习。
- 如何调用大模型
- 使用消息和历史使得大模型具有记忆
- 让大模型使用工具
- 基本的循环