在构建 LLM(大语言模型)应用时,我们的开发路径通常遵循一条清晰的进化曲线:从最简单的"你问我答"文本生成,到让模型输出结构化数据,再到赋予模型操作外部世界(API、数据库)的能力,最后构建能够自主规划、拆解任务的智能体(Agent)。
本文将基于 LangChain.js v1.0+ 的最新标准,通过代码实战,带你深入理解 Function Call、Tool、Memory 和 Agent 的底层机制与最佳实践。
1. 基础:与之对话 (Basic Model)
在上一篇文章中,我们介绍了 LangChain.js 的基础。在这个阶段,模型仅仅是一个"文本生成器"。它处于一个封闭的真空中:不知道当前的时间,无法访问互联网,也没有记忆(除非我们将历史记录传给它)。
typescript
import { ChatOpenAI } from "@langchain/openai";
const model = new ChatOpenAI({
model: "gpt-4o",
temperature: 0
});
// 基础调用
const response = await model.invoke("你好,请介绍一下你自己。");
console.log(response.content);
局限性: 它是被动的。如果你问它"今天北京天气怎么样?",它只能根据训练时的截止数据告诉你"我无法获取实时信息"。

2. 桥梁:Function Call (函数调用)
为了打破"真空",OpenAI 等模型厂商引入了 Function Calling (现在的标准术语是 Tool Calling)。
核心概念
很多开发者有一个误区:认为 Function Call 是让 LLM 直接运行代码。
事实并非如此。 它的本质是:LLM 能够根据你的自然语言指令,智能地从你提供的工具列表中选择一个,并生成符合该工具参数要求的 JSON 数据。
真正的执行(调用 API、查询数据库),依然由你的代码在本地完成。
原生流程演示
在使用高级封装之前,我们需要理解底层发生了什么。以下是使用 LangChain.js 手动处理 Tool Call 的完整流程:
typescript
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, ToolMessage } from "@langchain/core/messages";
// 1. 定义工具的具体逻辑(查询实时天气)
const getCurrentWeather = async (city: string): Promise<string> => {
// 模拟调用
const weatherData = {
city,
temperature: 25,
condition: "晴朗",
updateTime: new Date().toLocaleString(),
};
return JSON.stringify(weatherData); // 工具通常返回字符串
};
// 2. 定义工具描述(JSON Schema,供模型理解)
const weatherToolSchema = {
type: "function",
function: {
name: "getCurrentWeather",
description: "获取指定城市的实时天气信息",
parameters: {
type: "object",
properties: {
city: { type: "string", description: "城市名称,例如:北京" },
},
required: ["city"],
},
},
};
// 3. 初始化并绑定工具
const model = new ChatOpenAI({ model: "gpt-4o", temperature: 0 });
// 注意:使用 bindTools 是更现代的写法
const modelWithTools = model.bindTools([weatherToolSchema]);
// 4. 第一轮交互:用户提问
const userQuery = "北京现在的天气怎么样?";
const messages = [new HumanMessage(userQuery)];
const aiResponse = await modelWithTools.invoke(messages);
// 5. 检查模型是否想要调用工具
if (aiResponse.tool_calls && aiResponse.tool_calls.length > 0) {
console.log("模型决定调用工具:", aiResponse.tool_calls);
const toolCall = aiResponse.tool_calls[0];
// 6. 执行对应的函数 (手动路由)
if (toolCall.name === "getCurrentWeather") {
const args = toolCall.args; // LangChain 自动帮我们 parse 好了 JSON
const toolResult = await getCurrentWeather(args.city);
// 7. 将工具结果封装为 ToolMessage
const toolMessage = new ToolMessage({
tool_call_id: toolCall.id!, // 必须回传 ID 以便模型匹配
content: toolResult,
name: toolCall.name
});
// 8. 将工具结果回传给模型,进行第二轮推理
const finalResponse = await modelWithTools.invoke([
...messages,
aiResponse, // 必须包含模型上一轮的回复(其中包含 tool_calls 请求)
toolMessage // 以及工具的执行结果
]);
console.log("最终回答:", finalResponse.content);
}
}
痛点: 你会发现,上面的代码中包含了大量的 if/else 逻辑来判断模型是否调用了工具、调用了哪个工具、以及手动执行并回传结果。如果工具有几十个,这部分代码将难以维护。

3. 封装:LangChain.js 中的 Tool
为了解决上述痛点,LangChain.js 提供了 Tool 的标准定义和 @langchain/core/tools 中的 tool 辅助函数。
它将函数逻辑 、参数Schema (Zod) 和 元数据 封装在一起,让工具具备更强的可复用性。
typescript
import { tool } from "@langchain/core/tools";
import { z } from "zod";
// 使用 tool 函数封装
const calculator = tool(
async ({ operation, a, b }) => {
console.log(`正在执行计算: ${a} ${operation} ${b}`);
switch (operation) {
case "add": return `${a + b}`;
case "multiply": return `${a * b}`;
default: return "Error: Unknown operation";
}
},
{
name: "calculator",
description: "执行基本的数学运算(加法或乘法)",
// Zod 不仅用于校验,LangChain 还会自动将其转换为 OpenAI 需要的 JSON Schema
schema: z.object({
operation: z.enum(["add", "multiply"]).describe("运算类型"),
a: z.number().describe("第一个数字"),
b: z.number().describe("第二个数字"),
}),
}
);
// 现在的调用变得非常简单
const llmWithCalc = model.bindTools([calculator]);
const res = await llmWithCalc.invoke("计算 5 乘以 8 是多少");
// res.tool_calls 会自动包含解析好的参数
console.log("工具调用结果:", res.tool_calls);

Memory:构建对话上下文
默认情况下,LLM API 是无状态的(Stateless)。要实现连续对话,我们需要管理对话历史记录(History)。
在 LangChain.js v1.0+ 中,我们使用 MemorySaver 和其他检查点(Checkpointer)来管理对话状态。
1. 本地内存存储(MemorySaver)
typescript
import { MemorySaver } from "@langchain/langgraph";
import { createAgent } from "@langchain/langgraph";
// 初始化内存检查点
const memorySaver = new MemorySaver();
// 创建智能代理
const agent = createAgent({
model: "gpt-4o", // 使用最新模型
tools: [], // 工具列表
checkpointer: memorySaver,
});
// 开始对话
const result = await agent.invoke(
{ messages: [{ role: "user", content: "你好,我是Bob。" }] },
{ configurable: { thread_id: "1" } }
);
console.log("第一次回复:", result.messages[0].content);
// 继续对话
const nextResult = await agent.invoke(
{ messages: [{ role: "user", content: "你记得我叫什么吗?" }] },
{ configurable: { thread_id: "1" } }
);
console.log("第二次回复:", nextResult.messages[0].content);
2. Redis 持久化存储
对于需要在多个服务实例间共享对话状态的场景,可以使用 Redis 作为持久化存储:
typescript
import { RedisCheckpointer } from "@langchain/langgraph-checkpoint-redis";
import { createAgent } from "@langchain/langgraph";
// 配置 Redis 连接
const redisUrl = "redis://localhost:6379";
const redisCheckpointer = new RedisCheckpointer(redisUrl);
// 创建智能代理
const agent = createAgent({
model: "gpt-4o",
tools: [],
checkpointer: redisCheckpointer,
});
// 使用与之前相同的代码调用
3. PostgreSQL 持久化存储
对于需要结构化存储和更强大查询能力的场景,可以使用 PostgreSQL:
typescript
import { PostgresCheckpointer } from "@langchain/langgraph-checkpoint-postgres";
import { createAgent } from "@langchain/langgraph";
// 配置 PostgreSQL 连接
const dbUrl = "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable";
const postgresCheckpointer = new PostgresCheckpointer({
connectionString: dbUrl,
tableName: "langgraph_checkpoints",
});
// 创建智能代理
const agent = createAgent({
model: "gpt-4o",
tools: [],
checkpointer: postgresCheckpointer,
});
// 使用与之前相同的代码调用
4. 对话历史压缩
为了确保对话历史不超过模型的上下文限制,我们可以使用 ConversationBufferWindowMemory 或自定义压缩策略:
typescript
import { ConversationBufferWindowMemory } from "@langchain/memory";
import { ChatOpenAI } from "@langchain/openai";
// 创建带窗口记忆的模型
const model = new ChatOpenAI({
model: "gpt-4o",
temperature: 0,
});
const memory = new ConversationBufferWindowMemory({
k: 5, // 保留最近5轮对话
memoryKey: "chat_history",
returnMessages: true,
});
// 在每次调用前,将历史记录注入到消息中
const history = await memory.loadMemoryVariables({});
const messages = [
new HumanMessage("你好,我是Bob。"),
...history.chat_history,
];
const response = await model.invoke(messages);
await memory.saveContext({ input: "你好,我是Bob。" }, { output: response.content });
长期记忆:知识库增强
Checkpointer 解决的是"短期/会话级记忆"。如果需要让 Agent 记住几天前甚至几个月前的信息,我们需要引入 Vector Store (向量数据库)。
- 原理:将用户的重要信息(如个人喜好、历史决策)Embed 之后存入向量库(如 Pinecone, Weaviate)。
- 检索:在每次对话前,先去向量库搜索相关的"记忆片段",将其作为 Context 注入到 Prompt 中。这是 RAG (Retrieval-Augmented Generation) 的一种应用形式。
typescript
import { MemorySaver } from "@langchain/langgraph";
import { createAgent } from "@langchain/langgraph";
import { OpenAIEmbeddings } from "@langchain/openai";
import { Chroma } from "@langchain/community/vectorstores/chroma";
// 初始化向量存储
const embeddings = new OpenAIEmbeddings();
const vectorStore = new Chroma(
embeddings,
{ collectionName: "long_term_memory" }
);
// 创建智能代理
const agent = createAgent({
model: "gpt-4o",
tools: [],
checkpointer: new MemorySaver(),
});
// 保存长期记忆
const saveLongTermMemory = async (userId: string, content: string) => {
await vectorStore.addDocuments([
{
pageContent: content,
metadata: { userId, timestamp: new Date().toISOString() }
}
]);
};
// 检索长期记忆
const retrieveLongTermMemory = async (userId: string, query: string) => {
const results = await vectorStore.similaritySearch(query, 3);
return results.map(r => r.pageContent);
};
// 在代理中使用
const result = await agent.invoke(
{ messages: [{ role: "user", content: "告诉我你记得我的名字。" }] },
{ configurable: { thread_id: "1" } }
);
// 保存新记忆
await saveLongTermMemory("1", result.messages[0].content);
// 之后可以检索
const memories = await retrieveLongTermMemory("1", "名字");
console.log("长期记忆:", memories);
4. 编排:LangChain.js 中的 Agent
当我们有了 Model 和封装好的 Tool,谁来负责那个繁琐的"循环"?
如果用户问:"先查一下北京的天气,然后根据气温计算一下如果要穿三层衣服,每层衣服的平均厚度"。
这需要一个系统能够:
- 思考 (Thought):先调天气工具。
- 行动 (Action):执行天气工具。
- 观察 (Observation):拿到 25度。
- 再思考:现在需要计算。
- 再行动:执行计算工具。
- 最终回答。
现代 Agent 构建指南
在现代 LangChain.js 中,我们推荐使用 createAgent,这是最稳定且通用的 Agent 类型。
typescript
import { createAgent } from "@langchain/langgraph";
import { tool } from "@langchain/core/tools";
import { z } from "zod";
import { HumanMessage } from "@langchain/core/messages";
// 搜索工具 - 用于检索指定关键词的信息
const search = tool(
({ query }) => `搜索结果:${query}`,
{
name: "search",
description: "搜索信息",
schema: z.object({
query: z.string().describe("要搜索的关键词"),
}),
}
);
// 天气查询工具 - 获取指定地点的天气信息
const getWeather = tool(
({ location }) => `${location} 的天气:晴天,72华氏度`,
{
name: "get_weather",
description: "获取指定地点的天气信息",
schema: z.object({
location: z.string().describe("要查询天气的地点"),
}),
}
);
// 创建智能代理
const agent = createAgent({
model: "gpt-4o",
tools: [search, getWeather],
});
// 调用代理查询北京天气
const result = await agent.invoke({
messages: [new HumanMessage("北京的天气怎么样?")]
});
console.log("最终回答:", result.messages[0].content);

高级 Agent 使用技巧
1. 添加自定义提示词
typescript
const agent = createAgent({
model: "gpt-4o",
tools: [search, getWeather],
// 自定义系统提示词
systemMessage: "你是一个天气助手,专注于提供准确的天气信息和建议。",
});
2. 处理工具调用错误
typescript
const agent = createAgent({
model: "gpt-4o",
tools: [search, getWeather],
// 自定义错误处理
onError: (error) => {
console.error("工具调用错误:", error);
return `抱歉,我遇到了问题: ${error.message}`;
},
});
3. 限制工具调用次数
typescript
const agent = createAgent({
model: "gpt-4o",
tools: [search, getWeather],
// 限制工具调用次数
maxIterations: 5,
});
4. 与外部系统集成
typescript
// 示例:使用实际API获取天气
const getRealWeather = tool(
async ({ location }) => {
const response = await fetch(`https://api.weatherapi.com/v1/current.json?key=YOUR_KEY&q=${location}`);
const data = await response.json();
return JSON.stringify({
location: data.location.name,
tempC: data.current.temp_c,
condition: data.current.condition.text
});
},
{
name: "get_real_weather",
description: "获取指定地点的实时天气信息",
schema: z.object({
location: z.string().describe("要查询天气的地点"),
}),
}
);
5. 实战:构建多步骤智能助手
让我们构建一个完整的多步骤助手,结合天气查询和计算功能:
typescript
import { createAgent } from "@langchain/langgraph";
import { tool } from "@langchain/core/tools";
import { z } from "zod";
import { MemorySaver } from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
// 1. 定义工具
const getWeather = tool(
async ({ location }) => {
// 模拟实际API调用
const temperature = Math.floor(Math.random() * 30) + 15;
return JSON.stringify({
location,
temperature,
condition: temperature > 25 ? "晴朗" : "多云"
});
},
{
name: "get_weather",
description: "获取指定地点的实时天气信息",
schema: z.object({
location: z.string().describe("要查询天气的地点"),
}),
}
);
const calculator = tool(
async ({ operation, a, b }) => {
switch (operation) {
case "add": return `${a + b}`;
case "multiply": return `${a * b}`;
default: return "Error: Unknown operation";
}
},
{
name: "calculator",
description: "执行基本的数学运算(加法或乘法)",
schema: z.object({
operation: z.enum(["add", "multiply"]).describe("运算类型"),
a: z.number().describe("第一个数字"),
b: z.number().describe("第二个数字"),
}),
}
);
// 2. 创建Agent
const memorySaver = new MemorySaver();
const agent = createAgent({
model: new ChatOpenAI({ model: "gpt-4o", temperature: 0 }),
tools: [getWeather, calculator],
checkpointer: memorySaver,
});
// 3. 运行多步骤对话
const result = await agent.invoke(
{
messages: [new HumanMessage("北京今天的天气是25度,如果要穿三层衣服,每层衣服的平均厚度是1.5厘米,计算总厚度。")]
},
{ configurable: { thread_id: "weather_calculation" } }
);
console.log("最终回答:", result.messages[0].content);
总结
- Model: 大脑,负责推理,但无法直接行动。
- Function Call: 协议,让 Model 输出结构化指令。
- Tool: 封装,将代码逻辑包装成 Model 可理解的积木。
- Agent: 编排者,利用 Model 的推理能力,循环调用 Tool,解决复杂的多步问题。