一、智能体 Agent
我们前面已经在简单使用agent了,这部分我们详细聊聊agent。
和模型对话是一种 请求-响应的模式,这对于解决复杂问题显然不够,我们希望有一种机制能让模型自主解决问题,能记忆、调用工具、循环,于是Agent登场了。
一个最简单的agent创建如下。
import { createAgent } from "langchain";
const agent = createAgent({
model,
systemPrompt: SYSTEM_PROMPT,
tools: [get_user_location, get_weather_for_location],
responseFormat: toolStrategy(ResponseFormat),
// debug: true, # 开启debug模式,会打印出agent的运行过程
checkpointer=checkpointer,
middleware=[]
});
可以看出重点的部分:
-
model: Agent的推理引擎
-
tools: Agent可以使用的工具(Tool calling)
-
responseFormat: Agent的输出格式(结构化输出)
-
checkpointer: Agent的状态检查点,用于保存和恢复Agent的状态(记忆)
-
middleware: 中间件
agent 本质上是一个有状态的有限状态机 (FSM)。它会自动在“模型推理”和“工具执行”之间跳转/循环,直到问题解决。
而checkpointer会记录每次invoke后的完整状态(可理解为“存档”)。
1. 中间件
先介绍下「中间件」的概念,因为后面很多能力都依赖「中间件」的机制(比如动态模型、短期记忆)。
对于学过web框架的同学一定对「中间件」有所了解,Langchian也是借助了一些成熟的工程设计思路,将「中间件」概念集成到Langchain,解决了以往存在的痛点:
无中间件的Agent

有中间件的Agent

可以看出,Langchain中间件的本质就是钩子,在agent流程的每个步骤前后设置回调钩子,能对执行步骤进行拦截处理,从而达到更细粒度流程控制。
哪些常见的中间件呢?
- 对话历史记录整理
- 日志记录
- 提示词转换
- 工具选择
- 重试、回退逻辑
- 速率限制
具体怎么做呢?
使用 createMiddleware 函数创建中间件, Langchain提供了下面两种类型风格的钩子。
1.Node-style hooks:
-
beforeAgent - 代理启动前(每次invoke调用开始执行一次)
-
beforeModel - 每次模型调用前
-
afterModel - 每次模型响应后
-
afterAgent - 代理完成时(每个invoke调用结束执行一次)
例子:统计invoke调用次数,是否reach到顶。自定义中间件:
import { createMiddleware, AIMessage } from "langchain";
const createMessageLimitMiddleware = (maxMessages: number = 50) => {
return createMiddleware({
name: "MessageLimitMiddleware",
beforeModel: {
canJumpTo: ["end"],
hook: (state) => {
if (state.messages.length === maxMessages) {
return {
messages: [new AIMessage("Conversation limit reached.")],
jumpTo: "end",
};
}
return;
}
},
afterModel: (state) => {
const lastMessage = state.messages[state.messages.length - 1];
console.log(`Model returned: ${lastMessage.content}`);
return;
},
});
};
2.Wrap-style hooks:
-
wrapModelCall - 在每个模型调用周围
-
wrapToolCall - 在每个工具调用周围
例子:设置invoke失败重试的最大次数。自定义中间件如下:
import { createMiddleware } from "langchain";
const createRetryMiddleware = (maxRetries: number = 3) => {
return createMiddleware({
name: "RetryMiddleware",
wrapModelCall: (request, handler) => {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return handler(request);
} catch (e) {
if (attempt === maxRetries - 1) {
throw e;
}
console.log(`Retry ${attempt + 1}/${maxRetries} after error: ${e}`);
}
}
throw new Error("Unreachable");
},
});
};
下面我们会逐渐了解和学习到这些装饰器的使用。
2. 模型
模型的配置分为静态模型和动态模型。
- 静态模型:在agent创建时就确定好的模型,比如OpenAI的
gpt-3.5-turbo、gpt-4等。
- 动态模型:在agent运行时根据上下文动态切换的模型,比如根据用户输入的问题选择不同的模型。
我们重点看下动态模型是如何实现的,这里给出一个场景:根据用户问题的复杂度,动态选择模型。
- 使用
qwen_32b模型判断问题复杂度
- 简单,则使用
deepseek V3.2;复杂,则使用GLM 4.7
- 最终模型返回回答
动态模型的选择需要依靠「中间件」的方式来实现。
1.定义三个模型
function createChatModel(model: string, maxTokens: number) {
return new ChatOpenAI({
model,
apiKey: settings.siliconflow_api_key,
configuration: {
baseURL: settings.siliconflow_base_url,
},
temperature: 0.9,
maxTokens,
timeout: 60_000,
});
}
const glmModel = createChatModel(settings.glm_model, 10_000);
const dsModel = createChatModel(settings.ds_model, 10_000);
const qwenRouterModel = createChatModel(settings.qwen3_32b_model, 64);
2.工具函数,根据“模型响应”用来判断问题复杂度
/**
* 从输入中提取最新的用户文本
* @param input 输入内容,可能是字符串或包含消息数组的对象
* @returns 最新的用户文本内容
*/
function extractLatestUserText(input: unknown): string {
if (typeof input === "string") return input;
const messages: BaseMessage[] | undefined = Array.isArray(input)
? (input as BaseMessage[])
: (input as { messages?: BaseMessage[] } | null | undefined)?.messages;
if (!messages?.length) return "";
for (let i = messages.length - 1; i >= 0; i -= 1) {
const msg = messages[i];
if (msg instanceof HumanMessage) return String(msg.content);
}
return String(messages.at(-1)?.content ?? "");
}
/**
* 判断用户问题的复杂度
* @param userText 用户输入的文本
* @returns 问题复杂度,"simple" 或 "complex"
*/
async function judgeComplexity(userText: string): Promise<"simple" | "complex"> {
const response = await qwenRouterModel.invoke([
{
role: "system" as const,
content:
"你是问题复杂度分类器。根据用户问题判断复杂度:\n- simple:单一事实/常识问答、简单翻译/润色、很短的直接回答、无需多步推理或设计。\n- complex:需要多步推理、方案设计/架构、长文写作、复杂代码/调试、严谨数学推导、对比权衡。\n只输出:simple 或 complex。",
},
{ role: "user" as const, content: userText },
]);
const text = String((response as any)?.content ?? "")
.trim()
.toLowerCase();
if (text === "simple" || text.includes("simple") || text.includes("简单")) return "simple";
if (text === "complex" || text.includes("complex") || text.includes("复杂")) return "complex";
return "complex";
}
3.定义中间件,拦截模型请求
const dynamicModelMiddleware = createMiddleware({
name: "DynamicModelMiddleware",
wrapModelCall: async (request, handler) => {
const userText = extractLatestUserText({ messages: (request as any)?.messages });
const complexity = await judgeComplexity(userText);
const model = complexity === "simple" ? dsModel : glmModel;
request.model = model
return handler(request);
},
});
4.测试
/** 测试动态模型选择 */
async function testDynamicModelSelection() {
const checkpointer = new MemorySaver();
const agent = createAgent({
model: dsModel as any,
checkpointer,
contextSchema,
middleware: [dynamicModelMiddleware],
});
const config = { configurable: { thread_id: "1" }, context: { userId: "1" } };
const r1 = await agent.invoke(
{ messages: [{ role: "user", content: "1.9 和1.11 哪个数字大?" }] },
config,
);
const ai1 = r1.messages.at(-1);
console.log("响应内容:\n", ai1?.content);
console.log("调用模型:\n", getModelNameFromMessage(ai1));
/*
调用模型:
deepseek-ai/DeepSeek-V3.2-Exp
*/
const r2 = await agent.invoke(
{
messages: [
{
role: "user",
content: "请用langchain 1.x 设计一个简单的问答系统,用户可以向系统咨询某地的天气信息,包括天气工具调用。",
},
],
},
config,
);
const ai2 = r2.messages.at(-1);
console.log("响应内容:\n", ai2?.content);
console.log("调用模型:\n", getModelNameFromMessage(ai2));
/*
调用模型:
Pro/zai-org/GLM-4.7
*/
}
3. 工具
构建Agent的时候可以传入一个tools数组,绑定工具。
Agent的工具部分,包括三种场景:
1.先说说工具的错误处理,有时候工具处理出错了,我们希望反馈给LLM 自定义的错误信息。
1.1 继续前面的例子中 “1.9 和1.11 哪个数字大?”的问题,有时候LLM回答不正确,那么我希望它能调用工具来回答。首先工具定义如下:
import { createAgent, createMiddleware, tool, toolStrategy } from "langchain";
const compareTwoNumbers = tool(
({ a, b }: { a: number; b: number }) => {
if (a > b) return 1;
if (a < b) return -1;
return 0;
},
{
name: "compare_two_numbers",
description: "比较两个数字a,b的大小",
schema: z.object({
a: z.number().describe("第一个数字"),
b: z.number().describe("第二个数字"),
}),
},
);
1.2 可能出现调用tool出错(比如传参错误,内部触发边界错误等等),那么,可以使用 wrapToolCall 定义tool调用阶段的钩子(中间件),来处理错误。
const handleToolErrorsMiddleware = createMiddleware({
name: "HandleToolErrorsMiddleware",
wrapToolCall: async (request, handler) => {
try {
return await handler(request);
} catch (e) {
// 返回自定义的错误消息给LLM
return new ToolMessage({
content: `Tool error: Please check your input and try again. (${e})`,
tool_call_id: request.toolCall.id || '',
});
}
},
});
1.3 测试agent
/** 2.测试工具:比较两个数字 */
async function testToolCompareTwoNumbers() {
const checkpointer = new MemorySaver();
const agent = createAgent({
model: dsModel,
checkpointer,
tools: [compareTwoNumbers],
contextSchema,
middleware: [ handleToolErrorsMiddleware],
});
const r = await agent.invoke(
{ messages: [{ role: "user", content: "1.9 和 1.11 哪个数字大?" }] },
{ configurable: { thread_id: "1" }, context: { userId: "1" } },
);
const ai = r.messages.at(-1);
console.log("响应内容:\n", ai?.content);
console.log("调用模型:\n", getModelNameFromMessage(ai));
/*
响应内容:
比较结果是 **1**,这意味着 1.9 > 1.11。
所以,**1.9** 比 **1.11** 大。
虽然1.11在小数点后有两位数字,但比较小数大小时是看整体数值:
- 1.9 实际上是 1.90
- 1.11 是 1.11
- 1.90 > 1.11
调用模型:
deepseek-ai/DeepSeek-V3.2-Exp
*/
}
2.ReAct循环中使用。工具是可以在agent循环中被反复使用的。
2.1 上面的testToolCompareTwoNumbers就是一个例子:调用一次Tool compareTwoNumbers后发现 可以得出答案,就停止循环了返回结果。如果发现问题还没解决就会继续 思考/调用工具 循环,直到有最终答案。
3.动态工具。我们可以预先注册工具,然后根据上下文动态调用工具。
3.1 比如 我 希望不同用户角色,能调用的工具是不一样的。普通用户无法使用管理员才能调用的工具。
3.2 官方示例(拦截工具调用,替换tools):
import { createAgent, createMiddleware } from "langchain";
const toolSelectorMiddleware = createMiddleware({
name: "ToolSelector",
wrapModelCall: (request, handler) => {
// Select a small, relevant subset of tools based on state/context
const relevantTools = selectRelevantTools(request.state, request.runtime);
const modifiedRequest = { ...request, tools: relevantTools };
return handler(modifiedRequest);
},
});
const agent = createAgent({
model: "gpt-4.1",
tools: allTools,
middleware: [toolSelectorMiddleware],
});
4. 响应格式
1.createAgent提供了一个参数responseFormat,可以传入一个zod对象,用来约束输出,这块内容在 第二篇有介绍过。底层是LLM模型的结构化输出强约束和Langchain的校验兜底。
2.如果没有 responseFormat,需要从agent.invoke 的结果的messages中取最后一条消息,得到最终的回答,并且是字符串.
3.而使用 responseFormat后,agent.invoke 的结果会多一个structuredResponse字段, 并且是一个结构化对象。
4.我们继续之前的比较数字大小的例子,这次对响应结果加约束。
const compareResultSchema = z.object({
num1: z.number().describe("第一个数字"),
num2: z.number().describe("第二个数字"),
result: z.number().int().describe("比较结果,1 表示 num1 大于 num2,-1 表示 num1 小于 num2,0 表示相等"),
});
/** 测试响应格式 */
async function testResponseFormat() {
const checkpointer = new MemorySaver();
const agent = createAgent({
model: glmModel,
checkpointer,
tools: [compareTwoNumbers],
contextSchema,
responseFormat: compareResultSchema,
middleware: [handleToolErrorsMiddleware],
});
const r = await agent.invoke(
{ messages: [{ role: "user", content: "1.9 和 1.11 哪个数字大?" }] },
{ configurable: { thread_id: "1" }, context: { userId: "1" } },
);
console.log(r.structuredResponse);
/*
{
num1: 1.9,
num2: 1.11,
result: 1,
}
*/
console.log(r.messages.at(-1)?.content);
/*
Returning structured response: {"num1":1.9,"num2":1.11,"result":1}
*/
}
5. 状态检查点
checkpointer 是用来保存和恢复 agent 的状态。它具备以下能力
- 记忆能力(记忆历史消息)
- 线程隔离
- 故障恢复和“时空旅行”
记录历史对话记录
如果你不传递checkpointer,那么agent是没有记忆能力的,下面例子中模型将无法记住你的名字
async function testNoCheckpointer() {
console.log("\n" + "=".repeat(50));
console.log("测试 1: createAgent 不带 checkpointer (应该无记忆)");
console.log("=".repeat(50));
const agent = createAgent({ model });
const config = { configurable: { thread_id: "1" } };
console.log("\n【步骤 1】\n [用户]: 嗨!我叫 Bob。");
const response1 = await agent.invoke(
{ messages: [{ role: "user", content: "嗨!我叫 Bob。" }] },
config,
);
console.log(`[Agent]: ${response1.messages.at(-1)?.content ?? ""}`);
console.log("\n【步骤 2】\n [用户]: 我叫什么名字?");
const response2 = await agent.invoke(
{ messages: [{ role: "user", content: "我叫什么名字?" }] },
config,
);
console.log(`[Agent]: ${response2.messages.at(-1)?.content ?? ""}`);
}
下面例子中模型能记住你的名字。
async function testWithCheckpointer() {
console.log("\n" + "=".repeat(50));
console.log("测试 2: createAgent 带 checkpointer (应该有记忆)");
console.log("=".repeat(50));
const checkpointer = new MemorySaver();
const agent = createAgent({ model, checkpointer });
const config = { configurable: { thread_id: "thread-1" } };
console.log("\n【步骤 1】\n [用户]: 嗨!我叫 Alice。");
const response1 = await agent.invoke(
{ messages: [{ role: "user", content: "嗨!我叫 Alice。" }] },
config,
);
console.log(`[Agent]: ${response1.messages.at(-1)?.content ?? ""}`);
console.log("\n【步骤 2】\n [用户]: 我叫什么名字?");
const response2 = await agent.invoke(
{ messages: [{ role: "user", content: "我叫什么名字?" }] },
config,
);
console.log(`[Agent]: ${response2.messages.at(-1)?.content ?? ""}`);
}
线程隔离
所谓线程隔离就是你可以用同一个agent 发起多个会话,每个会话是独立的,互不干扰的。
下面的例子演示了,什么是线程隔离。通过第二个参数 { configurable: { thread_id: "thread-A" } } 设置线程。
def test_checkpointer_thread_isolation():
"""
测试 3: create_agent 使用 checkpointer 和不同的 thread_id。
预期:记忆通过 thread_id 隔离。
"""
print("\n" + "="*50)
print("测试 3: create_agent 线程隔离")
print("="*50)
memory = InMemorySaver()
agent = create_agent(qwen3_32b_model, checkpointer=memory)
# 1. 线程 A 交互
print("\n[线程 A] 用户: 嗨!我叫 Charlie。")
agent.invoke(
{"messages": [{"role": "user", "content": "嗨!我叫 Charlie。"}]},
{"configurable": {"thread_id": "thread-A"}}
)
# 2. 线程 B 交互
print("\n[线程 B] 用户: 你好!我叫 疯狂踩坑人")
agent.invoke(
{"messages": [{"role": "user", "content": "你好!我叫 疯狂踩坑人"}]},
{"configurable": {"thread_id": "thread-B"}}
)
# 3. 线程 A 交互 (问名字)
print("\n[线程 A] 用户: 我叫什么名字?")
response_a = agent.invoke(
{"messages": [{"role": "user", "content": "我叫什么名字?"}]},
{"configurable": {"thread_id": "thread-A"}}
)
print(f"[线程 A] Agent: {response_a['messages'][-1].content}")
# 4. 线程 B 交互 (问名字)
print("\n[线程 B] 用户: 我叫什么名字?")
response_b = agent.invoke(
{"messages": [{"role": "user", "content": "我叫什么名字?"}]},
{"configurable": {"thread_id": "thread-B"}}
)
print(f"[线程 B] Agent: {response_b['messages'][-1].content}")

检查点 checkpoint
运行下面代码,查看memory的变化
async function testCheckpoints() {
console.log("\n" + "=".repeat(50));
console.log("测试 4: 检查 checkpointer 保存的 checkpoint");
console.log("=".repeat(50));
const checkpointer = new MemorySaver();
const agent = createAgent({ model, checkpointer });
const config = { configurable: { thread_id: "thread-1" } };
console.log("\n[用户]: 嗨!我叫 疯狂踩坑人。");
const response = await agent.invoke(
{ messages: [{ role: "user", content: "嗨!我叫 疯狂踩坑人。" }] },
config,
);
console.log(`[Agent]: ${response.messages.at(-1)?.content ?? ""}`);
const checkpoints: unknown[] = [];
for await (const checkpoint of checkpointer.list({ configurable: { thread_id: "thread-1" } })) {
checkpoints.push(checkpoint);
}
console.log("checkpoint 数量:", checkpoints.length);
for (const c of checkpoints) {
console.log(c, "\n");
}
}
memory保存了很多检查点,每个检查点都有id、ts(时间)和channel_values.messages等信息。

每个checkpoint代表一次存档,通过某一个checkpoint就可以恢复agent的状态,这样你可以穿越回去到之前invoke的任一个状态,这非常重要。
二、记忆 Memory
短期记忆 Short-term memory
短期记忆主要指在单个会话内,LLM 能够“记得”刚刚说过的话。它的目的是保持对话的连贯性。
管理消息 - SummarizationMiddleware
1.前面提到的checkpointer 会保存你的历史消息,但是这样会存在两个问题:
- LLM 上下文限制:发送给大模型的 Token 数量超过其最大窗口,导致报错。
- 成本与延迟增加:每次调用都会携带冗长的历史记录,消耗更多 Token 且模型响应变慢
2.针对这些问题,业界的处理方案基本就是:
- 裁剪最早的消息,保持一个固定大小的"窗口"
- 对裁剪的历史消息,使用LLM进行总结,作为一条

3.下面用一个例子来演示,Langchain是如何通过SummarizationMiddleware管理对话消息的。这个中间件做的事就是上面的方案:裁剪+总结。
import { createAgent, summarizationMiddleware } from "langchain";
const systemPrompt = "你是一个人工智能助手";
function buildModels() {
const baseConfig = {
apiKey: settings.siliconflow_api_key,
configuration: {
baseURL: settings.siliconflow_base_url,
},
timeout: 60_000,
} as const;
const agentModel = new ChatOpenAI({
...baseConfig,
model: settings.qwen3_32b_model,
temperature: 0.7,
maxTokens: 2000,
});
const summarizerModel = new ChatOpenAI({
...baseConfig,
model: settings.qwen3_32b_model,
temperature: 0.2,
maxTokens: 512,
});
return { agentModel, summarizerModel };
}
async function testSummarizationShortMemory() {
const { agentModel, summarizerModel } = buildModels();
const checkpointer = new MemorySaver();
const agent = createAgent({
model: agentModel,
tools: [],
systemPrompt,
middleware: [
summarizationMiddleware({
model: summarizerModel,
trigger: { messages: 4 }, // 也可以按 tokens 来
keep: { messages: 4 }, // 也可以按 tokens 来
summaryPrefix: "对话摘要:",
summaryPrompt:
"请将以下对话历史压缩成简短的中文摘要,保留关键信息(事实、偏好、约束、决定、结论):\n{messages}",
}),
],
checkpointer,
});
const userInputs = [
"我叫小明,住在北京。",
"请记住我更喜欢用中文回答。",
"我这周想做一个 LangChain 的学习计划。简短控制在100字",
"计划要按天拆分,每天不超过1小时。简短控制在100字",
"顺便提醒我:周三晚上要健身。最后请把所有安排再用要点总结一次。",
];
const config = { configurable: { thread_id: "short-memory-demo" } };
for (let idx = 0; idx < userInputs.length; idx += 1) {
const text = userInputs[idx] || '';
const r = await agent.invoke({ messages: [{ role: "user", content: text }] }, config);
const messages = r.messages;
const last = messages.at(-1);
console.log(
`\n[Turn ${idx + 1}] 当前上下文消息数:${messages.length}(trigger=4, keep=4)\n`,
);
console.log(`用户:${text}`);
console.log(`助手:${String(last?.content ?? "")}`);
}
}
4.trigger: { messages: 4 },keep: { messages: 4 } 表示当消息超过4条(不包含system_prompt)时触发裁剪,裁剪的信息保留4条。 这样一来,「消息窗口」大小控制在4。
5.理论上keep可以大于trigger,这样总结消息和keep的消息在内容上就会有重叠。
6.trigger和keep 除了通过message控制,还可以通过tokens控制,比如超过多少tokens就压缩。
7.从第三轮开始,当前消息数:6. 这意味下一次对话前,就会触发压缩,实际消息窗口大小为4.

8.下面是第三轮对话的messages打印结果,可以看到前面总结的消息。可以发现Langchain默认的做法是: 使用了总结性提示'here is a summary of ...',将1,2,3,4次消息发给了LLM做总结'

9.此外,Langchain还提供了RemoveMessage 来删除指定的消息,这个结合Langgraph会比较实用。
存储介质
import { MemorySaver } from "@langchain/langgraph"; 中的MemorySaver是将状态数据保存在内存中,那么程序已结束,这些状态就会丢失。所以官方更推荐在生产环境使用 PostgresSaver
bun add @langchain/langgraph-checkpoint-postgres
在生产环境中,使用一个由数据库支持的检查点:
import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres";
const DB_URI = "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable";
const checkpointer = PostgresSaver.fromConnString(DB_URI);
当然,除了Postgres数据库介质,还有其他存储介质,见文档,比如Sqlite。
bun add @langchain/langgraph-checkpoint-sqlite
但是这有个问题,@langchain/langgraph-checkpoint-sqlite 包依赖了Node的better-sqlite3,这个包目前不支持bun.
所以,只能用替代品:
bun add @beshkenadze/langgraph-checkpoint-libsql
import { SqliteSaver } from "@beshkenadze/langgraph-checkpoint-libsql";
async function testSqliteSaver() {
const { agentModel } = buildModels();
const checkpointer = SqliteSaver.fromConnString("file:checkpoints.db");
const agent = createAgent({
model: agentModel,
tools: [],
systemPrompt,
checkpointer,
});
const config = { configurable: { thread_id: "test_sqlite_saver" } };
const r1 = await agent.invoke(
{ messages: [{ role: "user", content: "你好,我叫“疯狂踩坑人”" }] },
config,
);
console.log("[assistant]", String(r1.messages.at(-1)?.content ?? ""));
// [assistant]
// 我是你的AI助手!不过“疯狂踩坑人”这个名字挺有...
const r2 = await agent.invoke(
{ messages: [{ role: "user", content: "请问我叫什么名字?" }] },
config,
);
console.log("[assistant]", String(r2.messages.at(-1)?.content ?? ""));
// [assistant]
// 你叫“疯狂踩坑人”呀...
}
你会发现运行目录下多出一个checkpoints.db的数据库文件。
用sqlite客户端工具(比如vscode插件Database Client)打开查看checkpoints表,可以看到,这些数据都保存到了表里。

长期记忆 Long-term memory
长期记忆是指跨越多天、多周甚至数个不同会话,系统依然能记得用户的偏好、事实或历史背景。
-
核心机制: 检索增强生成(RAG)与外部数据库。它不直接塞进当前的 Prompt 窗口,而是通过“按需检索”的方式工作。
-
LangChain 实现方式:
-
向量数据库(Vector Stores): 如 Pinecone, Milvus, Chroma。将历史对话或知识切片并嵌入(Embedding),当用户提问时,通过语义搜索找回相关片段。
-
VectorStoreRetrieverMemory: LangChain 特有的组件,允许将向量数据库作为记忆组件挂载。
-
实体记忆(Entity Memory): 提取对话中的特定实体(如“我的名字叫 疯狂踩坑人”)并存入结构化数据库。
-
存储位置: 外部持久化数据库(磁盘)。
-
优势: 理论上拥有无限容量,且不会占用不必要的 Token。只有当相关信息被触发时,才会被提取出来。
这个我们后面再聊,后面会慢慢介绍向量数据库和RAG。