LangChain Agent 中间件实战(下):DeepAgents文件权限、长期记忆与对话摘要

LangChain Agent 中间件实战(下):DeepAgents 文件权限、长期记忆与对话摘要

上篇解决了 Agent 运行过程的扩展问题:生命周期日志、状态统计、请求改写、流程短路和工具包装。继续往下做,一个真正能长期使用的 Agent 还会遇到三个更具体的问题:

  1. Agent 可以读写哪些文件,怎样限制敏感文件和写入范围?
  2. 项目约定与用户偏好怎样跨对话保留下来?
  3. 对话越来越长以后,怎样降低上下文长度,同时不丢掉旧消息原文?

DeepAgents 的三个 Middleware 正好对应这三类能力:

Middleware 主要职责
createFilesystemMiddleware 给 Agent 提供文件工具,并执行路径权限规则
createMemoryMiddleware 从指定文件加载长期记忆,注入模型上下文
createSummarizationMiddleware 在达到阈值时生成滚动摘要,并归档被压缩的旧消息

这三者都涉及"记住信息",但它们管理的不是同一种数据。本文会先分别实现,再把它们放到同一张状态图中理解。

一、统一的模型和 ESM 路径准备

示例继续使用环境变量创建模型:

js 复制代码
import "dotenv/config";
import { ChatOpenAI } from "@langchain/openai";

const model = new ChatOpenAI({
  model: process.env.MODEL_NAME,
  apiKey: process.env.OPENAI_API_KEY,
  configuration: {
    baseURL: process.env.OPENAI_BASE_URL,
  },
  temperature: 0,
});

因为代码运行在 Node.js ESM 中,没有直接使用 CommonJS 的 __dirname,所以通过 import.meta.url 计算当前模块目录:

js 复制代码
import path from "node:path";
import { fileURLToPath } from "node:url";

const __dirname = path.dirname(fileURLToPath(import.meta.url));

这里经过了两步转换:

text 复制代码
import.meta.url
  ↓ fileURLToPath
当前模块的本地文件路径
  ↓ path.dirname
当前模块所在目录

后面三个示例都以这个目录为基准创建各自的工作区,避免把演示文件散落到其他位置。

二、用 FilesystemBackend 建立虚拟工作区

先创建一个文件系统工作区:

js 复制代码
import {
  createFilesystemMiddleware,
  FilesystemBackend,
} from "deepagents";

const workspaceDir = path.join(__dirname, "workspace");

const backend = new FilesystemBackend({
  rootDir: workspaceDir,
  virtualMode: true,
});

这两个配置要放在一起理解:

  • rootDir 指定本地真实工作区;
  • virtualMode: true 让 Agent 使用以 / 开头的虚拟绝对路径。

例如,Agent 操作:

text 复制代码
/todo.md

底层实际访问的是:

text 复制代码
workspace/todo.md

因此,提示词可以稳定地告诉 Agent"工作区根路径为 /",而不用把运行机器上的真实绝对路径暴露给模型。

三、用有序权限规则保护文件系统

仅仅把路径映射到工作区还不够。Agent 获得 read_file、write_file、edit_file 等工具后,还需要明确哪些操作能通过。

下面的规则实现三个目标:

  • 禁止读取 /secret.txt;
  • 允许写入 /todo.md;
  • 拒绝其他所有写入。
js 复制代码
const permissions = [
  {
    operations: ["read"],
    paths: ["/secret.txt"],
    mode: "deny",
  },
  {
    operations: ["write"],
    paths: ["/todo.md"],
    mode: "allow",
  },
  {
    operations: ["write"],
    paths: ["/**"],
    mode: "deny",
  },
];

1. 规则是"首次匹配生效"

判断时会同时检查操作类型和路径,并使用第一条匹配规则。因此顺序直接影响结果。

以写 /todo.md 为例:

  1. 第一条只处理 read,跳过;
  2. 第二条匹配 write + /todo.md,得到 allow;
  3. 已经得到结果,不再落到后面的全局写入拒绝规则。

如果把 write + /** + deny 放在允许规则前面,/todo.md 也会先被拒绝。

最终权限矩阵如下:

操作 路径 命中的规则 结果
read /secret.txt 精确读取拒绝 拒绝
write /todo.md 精确写入允许 允许
write /hack.txt 全局写入拒绝 拒绝
read /todo.md 没有读取拒绝规则 允许

最后一行很容易被忽略:这组规则不是"默认拒绝一切"。没有匹配到拒绝规则的操作仍然可以通过。因此它表达的是"除敏感文件外可以读取,但只能写指定文件"。

2. /** 表示递归覆盖工作区

js 复制代码
{
  operations: ["write"],
  paths: ["/**"],
  mode: "deny",
}

这条路径会匹配当前虚拟根目录下各层级的文件,所以不仅 /hack.txt 会被拒绝,其他子目录中的写入也会落入该规则,除非前面已经有更具体的允许规则。

3. Agent 权限不约束宿主程序自己的 fs 调用

为了测试读取拒绝,可以在启动时由 Node.js 代码准备敏感文件:

js 复制代码
import fs from "node:fs";

fs.writeFileSync(
  path.join(workspaceDir, "secret.txt"),
  "机密:不得读取",
  "utf-8"
);

这段代码可以成功写入,是因为它是宿主程序直接调用 Node.js fs,并没有经过 Agent 的文件工具。permissions 约束的是 createFilesystemMiddleware 暴露出来的文件操作,不会反过来拦截应用程序自身的文件系统 API。

这一区分非常关键:

text 复制代码
宿主程序 fs.writeFileSync
  └─ 直接操作真实文件

Agent 调用 write_file
  └─ Filesystem Middleware 检查权限
       ├─ allow:交给 Backend
       └─ deny:返回权限错误

四、把 Backend、权限和 Agent 连接起来

完整的 Agent 配置如下:

js 复制代码
import {
  createAgent,
  HumanMessage,
} from "langchain";

const agent = createAgent({
  model,
  tools: [],
  systemPrompt: `
工作区根路径为 /。用 ls、read_file、write_file、edit_file 操作文件,
路径以 / 开头。中文回答。
  `,
  middleware: [
    createFilesystemMiddleware({
      permissions,
      backend: new FilesystemBackend({
        rootDir: workspaceDir,
        virtualMode: true,
      }),
    }),
  ],
});

这里顶层 tools 仍然为空,文件工具由 Middleware 注入。System Prompt 则告诉模型:

  • 工作区的虚拟根路径是 /;
  • 应该调用哪些文件工具;
  • 文件路径要以 / 开头。

Backend 负责真正落地文件操作,Middleware 负责提供工具与权限检查,System Prompt 负责让模型正确使用它们。三者职责不同,缺少任何一层都会影响最终行为。

五、为什么权限失败要检查 ToolMessage

可以写一个专门的测试函数验证拒绝行为:

js 复制代码
async function expectDenied(label, prompt) {
  console.log(`\n== ${label}(预期拒绝)===\n`, prompt, "\n");

  try {
    const { messages } = await agent.invoke(
      {
        messages: [new HumanMessage(prompt)],
      },
      {
        recursionLimit: 5,
      }
    );

    const denied = messages.some(
      (message) =>
        message.getType?.() === "tool" &&
        message.status === "error" &&
        /permission denied/.test(String(message.content))
    );

    console.log(denied ? "OK:已拒绝" : "未触发拒绝(异常)");
  } catch (error) {
    const message = error.cause?.message ?? error.message;
    console.log("X", message);
  }
}

await expectDenied(
  "禁止写",
  "只调用 write_file,路径 /hack.txt,内容:text"
);

这里同时处理了两条失败通道:

  1. Agent 正常完成,但消息列表里出现 status === "error" 的工具消息;
  2. 调用过程直接抛出异常,由 catch 记录错误。

权限拒绝通常是一次可识别的工具失败,不一定会让整个 agent.invoke 以异常结束。因此,只写 try/catch 不能完整验证权限;还要检查工具消息的类型、状态和内容。

recursionLimit: 5 则限制这次测试的执行步数,防止模型在工具失败后反复尝试。

六、从"能读文件"到"把文件当作长期记忆"

文件系统 Middleware 让 Agent 能按需读取文件,但长期记忆还有一个额外要求:在模型回答前,应该主动把指定记忆放进上下文,而不是每次都等模型先决定调用 read_file。

这就是 createMemoryMiddleware 的作用。

先准备两个记忆来源:

js 复制代码
const workspaceDir = path.join(__dirname, "workspace-memory");

const projectMemoryPath = "/AGENTS.md";
const preferencesMemoryPath = "/memory/preferences.md";

const backend = new FilesystemBackend({
  rootDir: workspaceDir,
  virtualMode: true,
});

两个路径分别承载不同类型的信息:

记忆文件 应保存的内容
/AGENTS.md 项目说明、技术栈、架构、仓库约定、入口脚本等项目事实
/memory/preferences.md 用户语言、包管理器、回答风格、工具偏好等个人偏好

把它们分开,不是为了目录好看,而是为了避免项目事实和个人偏好相互污染。

七、文件系统 Middleware 与记忆 Middleware 要配合使用

创建 Agent 时,同时挂载两个 Middleware:

js 复制代码
import {
  createFilesystemMiddleware,
  createMemoryMiddleware,
} from "deepagents";

const agent = createAgent({
  model,
  tools: [],
  systemPrompt: [
    "你是项目助手。工作区根路径为 /,可用 ls、read_file、write_file、edit_file。",
    "根据 <agent_memory> 回答;用户要求记住时,必须立刻调用 edit_file,并按照类型写入对应文件。",
    `- ${projectMemoryPath}:项目说明、技术栈、架构、仓库约定等。`,
    `- ${preferencesMemoryPath}:用户个人偏好(语言、包管理器、回答风格等)。`,
    "不要混写:项目事实不要写入 preferences,个人偏好不要写入 AGENTS.md。",
  ].join("\n"),
  middleware: [
    createFilesystemMiddleware({ backend }),
    createMemoryMiddleware({
      backend,
      sources: [
        projectMemoryPath,
        preferencesMemoryPath,
      ],
    }),
  ],
});

两者的分工是:

text 复制代码
createMemoryMiddleware
  └─ 从 sources 加载记忆,并注入模型上下文

createFilesystemMiddleware
  └─ 提供 edit_file 等工具,让 Agent 能把新记忆写回文件

sources 声明的是"从哪里加载记忆",并不会根据语义自动决定新内容该写入哪个文件。分类写入由 System Prompt 明确约束,Agent 再调用 edit_file 实际完成更新。

因此,长期记忆并不是一个隐藏数据库自动猜测并保存所有对话,而是一条可以观察的链路:

text 复制代码
启动一次 Agent 调用
  ↓
Memory Middleware 读取指定文件
  ↓
内容以 <agent_memory> 进入模型上下文
  ↓
用户提出需要长期保留的信息
  ↓
Agent 按 System Prompt 选择目标记忆文件
  ↓
Filesystem Middleware 提供 edit_file
  ↓
Backend 把更新写入真实工作区

八、一次完整的分类记忆实验

依次发送四个请求:

js 复制代码
const prompts = [
  "根据记忆,这个项目是做什么的?只回答一句。",
  "请记住:我常用的包管理器是 pnpm。",
  "请记住:本仓库主入口脚本是 src/deepagents/memory-agent.mjs。",
  "我常用什么包管理器?本 Demo 主入口脚本路径是什么?各用一行回答。",
];

第一条测试现有项目记忆能否被读取;第二条是用户偏好;第三条是项目事实;第四条要求同时回忆两种信息。

为了让四次 invoke 仍属于同一段对话,需要显式保存并继续传入消息历史:

js 复制代码
let messages = [];

for (const prompt of prompts) {
  console.log("\n用户:", prompt);

  const { messages: newMessages } = await agent.invoke(
    {
      messages: [
        ...messages,
        new HumanMessage({ content: prompt }),
      ],
    },
    {
      recursionLimit: 30,
    }
  );

  messages = newMessages;

  console.log("回复:", messages.at(-1)?.content);
}

循环中的关键不是 for,而是这两步:

js 复制代码
messages: [...messages, new HumanMessage({ content: prompt })]

把旧消息与本轮用户消息一起交给 Agent;然后:

js 复制代码
messages = newMessages;

保存 Agent 返回的完整消息列表,供下一轮继续使用。

执行记忆写入后,两类信息应该分别落在不同位置:

md 复制代码
# Project Documentation

## Important Notes
- 主入口脚本:`src/deepagents/memory-agent.mjs`

以及:

md 复制代码
# User Preferences

## Tool Preferences
- Preferred package manager: pnpm

最终提问同时需要两种信息,Agent 可以从注入的记忆与当前对话中组织出:

text 复制代码
pnpm
src/deepagents/memory-agent.mjs

记忆写入与消息传递是两条不同的连续性

这个实验同时保留了两种上下文:

  • messages 保留当前进程中这几轮对话的连续性;
  • Markdown 文件保留可再次加载的长期记忆。

如果只传 messages,进程结束后这些信息不会自动成为文件记忆;如果只写记忆文件而不传旧消息,同一次多轮对话的具体上下文也会缺失。两者不能互相替代。

九、长对话为什么还需要摘要

长期记忆解决的是"哪些稳定事实应该跨对话保留",但不应该把整段聊天原文都塞进项目说明或用户偏好。另一方面,如果始终把所有 messages 原样发送给模型,上下文会不断增长。

摘要 Middleware 解决的是另一类问题:

text 复制代码
较早的对话 → 压缩成滚动摘要
最近的消息 → 保留原文,维持局部连贯性
被压缩的旧消息 → 归档到文件,便于回查

先创建独立工作区与归档路径:

js 复制代码
const workspaceDir = path.join(
  __dirname,
  "workspace-summarization"
);

const historyPathPrefix = "/conversation_history";

const backend = new FilesystemBackend({
  rootDir: workspaceDir,
  virtualMode: true,
});

再定义中文摘要提示词:

js 复制代码
const summaryPrompt = `
你是对话摘要助手,请用中文总结以下对话,包含:
1. 讨论的主要话题
2. 达成的关键结论或决定
3. 继续对话所需的重要上下文

保持简洁,不要罗列无关的细节。
待摘要的对话:{conversation}
摘要:
`;

{conversation} 是待摘要对话的占位位置。提示词关注的是"后续继续对话所需的信息",而不是简单逐句复述。

十、配置触发阈值与保留窗口

摘要 Agent 的核心配置如下:

js 复制代码
const agent = createAgent({
  model,
  tools: [],
  systemPrompt:
    "你是会话助手,记住用户提到的关键事实,中文简短回答。" +
    "若看到[此前对话摘要],请据此继续对话。",
  middleware: [
    createSummarizationMiddleware({
      model,
      backend,
      historyPathPrefix,
      summaryPrompt,
      trigger: { type: "messages", value: 8 },
      keep: { type: "messages", value: 4 },
    }),
  ],
});

其中:

  • model 用于生成摘要;
  • backend 用于写入归档;
  • historyPathPrefix 指定归档的虚拟目录;
  • summaryPrompt 控制摘要内容;
  • trigger 表示有效上下文消息数达到阈值时触发摘要;
  • keep 表示摘要后保留最近 4 条消息原文。

这里统计的是消息条数,不是"用户提问 + AI 回答"组成的轮数。一次普通问答通常产生两条消息,有工具调用时还可能出现工具消息。

为什么通常不是第 8 条刚产生就立刻摘要

摘要检查发生在 Agent 准备调用模型时。假设前四轮没有工具调用:

text 复制代码
第 1~8 条:四轮问答完成
第 9 条:第五轮用户消息进入

第 8 条是上一轮模型刚生成的回答;那次模型调用已经结束。等第 9 条用户消息进入、Agent 再次准备调用模型时,中间件才看到当前上下文已达到阈值并执行摘要。

当时共有 9 条消息,keep: 4 会保留最近 4 条,因此更早的 5 条进入本次压缩与归档范围。这个"第 9 条触发"的结论只适用于无工具消息的这组问答,不代表所有 Agent 都固定在第 9 条触发。

十一、摘要场景必须传递完整 Agent 状态

这是摘要实验中最值得注意的代码:

js 复制代码
let state = {
  messages: [],
};

for (const prompt of prompts) {
  state = await agent.invoke(
    {
      ...state,
      messages: [
        ...state.messages,
        new HumanMessage(prompt),
      ],
    },
    {
      recursionLimit: 30,
    }
  );
}

前面的长期记忆示例只取出了 messages,这里却保存整个 state,原因是摘要 Middleware 还会在状态中维护摘要事件等信息。

对比两种写法:

js 复制代码
// 只保留消息
const { messages } = await agent.invoke(...);
js 复制代码
// 保留 Middleware 返回的全部状态
state = await agent.invoke(...);

摘要场景需要第二种。下一轮调用时先展开:

js 复制代码
...state

让上一轮的摘要相关状态继续存在,再单独构造追加了新用户消息的 messages。如果每轮只传回消息列表,运行时的摘要事件就无法沿着这段手动维护的会话继续传递。

这也是为什么"消息历史"和"Agent 完整状态"不能总被视为同一个东西。

十二、如何观察摘要事件与有效上下文

每轮执行完成后,可以读取:

js 复制代码
const event = state._summarizationEvent;

当摘要已经产生时,事件里用到三个关键字段:

js 复制代码
event.summaryMessage
event.cutoffIndex
event.filePath

它们分别表示:

字段 含义
summaryMessage 当前滚动摘要消息
cutoffIndex 原始消息历史被摘要到的位置
filePath 本次会话历史归档文件的虚拟路径

根据摘要与截止位置,可以还原主模型当前使用的有效上下文:

js 复制代码
const effectiveMessages = [
  event.summaryMessage,
  ...state.messages.slice(event.cutoffIndex),
];

这里揭示了一个看似矛盾、其实非常重要的设计:

  • state.messages 仍可以保留完整原始消息;
  • 主模型不必每次接收全部原始消息;
  • 它看到的是最新摘要,加上截止位置之后的近期消息。

可以逐条打印验证:

js 复制代码
effectiveMessages.forEach((message, index) => {
  const type = message.getType?.() ?? "unknown";

  console.log(
    `${index + 1}. [${type}]`,
    message.content
  );
});

console.log(
  "有效上下文消息数:",
  effectiveMessages.length
);

十三、如何判断本轮是否生成了新摘要

摘要事件一旦存在,后续没有触发新摘要的轮次也仍可能读到当前事件。因此,不能只用 if (event) 判断"本轮刚刚摘要"。

可以记住上一条摘要消息:

js 复制代码
let lastSummaryMessage = null;

每轮比较:

js 复制代码
if (event) {
  const isNewSummary =
    event.summaryMessage !== lastSummaryMessage;

  if (isNewSummary) {
    console.log("\n===== 触发了摘要 =====");
    console.log("摘要内容:");
    console.log(event.summaryMessage.content);
    console.log("摘要截止位置:", event.cutoffIndex);
    console.log("历史归档文件:", event.filePath);

    lastSummaryMessage = event.summaryMessage;
  }
}

逻辑不是"有摘要事件就打印",而是"摘要消息对象和上次记录不同才打印"。这样,观察日志不会把沿用旧摘要误报成一次新摘要。

十四、归档文件检测与摘要检测不是一回事

还可以通过目录内容观察首次创建的历史文件:

js 复制代码
const historyDir = path.join(
  workspaceDir,
  historyPathPrefix.replace(/^\//, "")
);

function listHistoryFiles() {
  if (!fs.existsSync(historyDir)) return [];
  return fs.readdirSync(historyDir);
}

let knownHistory = new Set(listHistoryFiles());

每轮结束后比较文件名:

js 复制代码
const currentHistory = listHistoryFiles();

for (const file of currentHistory) {
  if (!knownHistory.has(file)) {
    console.log(
      "\n新增历史归档文件:",
      path.join(historyDir, file)
    );
  }
}

knownHistory = new Set(currentHistory);

这段代码检测的是"是否出现了新文件名"。如果后续摘要继续向同一个会话归档文件追加内容,文件名集合没有变化,这个检测不会再次提示。

所以示例用了两套观察手段:

  • lastSummaryMessage 用于判断是否产生了新的摘要事件;
  • knownHistory 用于判断目录中是否新增了归档文件。

二者不能互相代替。

十五、归档保存原文,摘要负责压缩

达到阈值后,历史文件中的内容类似:

md 复制代码
## Summarized at 2026-10-14T14:57:31.811Z

Human: 请记住:我的宠物猫叫小橘。
AI: 好的,已记住你的宠物猫叫小橘。
Human: 请记住:我住在北京。
AI: 好的,已记住你住在北京。
Human: 请记住:我喜欢喝拿铁。

这里保存的是本次被压缩的旧消息原文,不是摘要文本。摘要可能只保留"用户养了一只叫小橘的猫,住在北京并喜欢拿铁",而归档仍保留原始角色和表达。

三类数据由此各司其职:

数据 保存什么 用来做什么
state.messages 完整原始消息历史 保存运行时原始对话
_summarizationEvent.summaryMessage 截止当前的滚动摘要 缩短主模型有效上下文
conversation_history 每次被压缩的旧消息原文 留下可回查的历史归档

当第二次达到阈值时,摘要不是简单堆成"摘要 A + 摘要 B"。旧摘要会与本次新进入压缩范围的消息一起形成更新后的滚动摘要;归档则继续记录此前尚未归档的原始消息。

十六、演示环境为什么会先清空摘要工作区

摘要实验在启动时执行:

js 复制代码
fs.rmSync(workspaceDir, {
  recursive: true,
  force: true,
});

fs.mkdirSync(workspaceDir, {
  recursive: true,
});

它的目的很明确:每次实验都从空目录开始,便于判断本轮究竟创建了哪些归档文件。

但这也意味着每次启动都会删除上次的历史归档。因此,这段清理逻辑属于 Demo 初始化,不应和"通过文件实现持久化"的业务目标混在一起。如果希望跨启动保留归档,就不能在每次运行前删除整个目录。

十七、三种能力放在一起怎样理解

现在可以画出完整的数据关系:

text 复制代码
                          ┌────────────────────┐
                          │  用户本轮消息       │
                          └─────────┬──────────┘
                                    ↓
┌────────────────────┐    ┌────────────────────┐
│ 项目/偏好记忆文件   │ →  │ Memory Middleware  │
│ AGENTS.md 等        │    │ 注入 agent_memory  │
└────────────────────┘    └─────────┬──────────┘
                                    ↓
                          ┌────────────────────┐
                          │ Agent 有效上下文    │
                          └─────────┬──────────┘
                                    ↓
                         达到摘要阈值了吗?
                           ├─ 否:直接调用模型
                           └─ 是
                               ├─ 生成滚动摘要
                               ├─ 保留最近消息
                               └─ 旧消息原文归档

模型需要读写文件时
  ↓
Filesystem Middleware 检查 operation + path
  ├─ allow:Backend 映射到真实工作区
  └─ deny:返回 status=error 的工具消息

再从用途上比较:

能力 数据来源 何时进入模型上下文 是否写入磁盘
普通文件工具 工作区文件 模型主动调用读取工具时 写工具可以落盘
长期记忆 sources 指定的 Markdown Memory Middleware 在模型调用时注入 Agent 可用 edit_file 更新
对话摘要 旧摘要与待压缩消息 达到阈值后,以摘要替代较早原文 被压缩的旧消息可归档

十八、最容易混淆的五个边界

1. 虚拟根目录不等于操作系统根目录

virtualMode: true 时,Agent 看到的 / 映射到 rootDir。/todo.md 是工作区内文件,不是操作系统真正的 /todo.md。

2. 文件系统权限不限制宿主代码

permissions 检查 Agent 文件工具。应用程序直接执行的 fs.writeFileSync 不走这套规则。

3. Memory Middleware 负责加载,不负责语义分类

sources 给出记忆文件列表;"项目事实写入 AGENTS.md、用户偏好写入 preferences.md"由 System Prompt 约束,实际写入由文件工具完成。

4. 消息历史不等于完整 Agent 状态

普通多轮聊天只保存 messages 可以维持对话,但摘要 Middleware 还产生 _summarizationEvent 等状态。手动续接摘要会话时,应把整个返回状态传给下一轮。

5. 归档文件不等于摘要文件

归档保存被压缩的旧消息原文,摘要消息用于主模型的短上下文。二者一个偏向可回查性,一个偏向调用效率。

十九、总结

一个文件化 Agent 的上下文并不是简单的"把聊天记录保存下来",而是由不同层次共同组成:

  1. FilesystemBackend 把虚拟路径映射到受控工作区;
  2. createFilesystemMiddleware 提供文件工具,并用有序规则控制读写;
  3. createMemoryMiddleware 主动加载项目事实和用户偏好,形成长期记忆;
  4. createSummarizationMiddleware 在消息达到阈值后压缩旧上下文,同时归档旧消息原文;
  5. 调用方负责在多轮执行之间正确传递 messages 或完整 state。

最值得记住的是这三句话:

text 复制代码
权限决定 Agent 能操作什么。
记忆决定哪些稳定信息每次都进入上下文。
摘要决定长对话中哪些内容用短文本继续携带。

把三者边界分清后,文件读写、长期记忆和长对话压缩就不再是三套互不相干的功能,而是一套可观察、可限制、可持续传递的 Agent 上下文工程。

相关推荐
u0111026753 小时前
JPG、PNG、WebP 怎么选?按透明度、体积和使用场景做判断
前端·javascript·vue.js·图像处理·ai作画
OnlineProxy3 小时前
为 LangChain 和 CrewAI 构建自定义工具:将代理 IP 轮换封装至智能体函数中
网络协议·tcp/ip·langchain
hhb_6184 小时前
一键生成数据看板,ECharts实战演示
前端·javascript·echarts
const_qiu6 小时前
第一章 1.1-1.2:基础模型调用与 TypeScript 入门 —— Java 开发者的视角
java·javascript·typescript
for_ever_love__8 小时前
LangChain 降级、重试与超时:让服务在模型抽风时还能用
python·自然语言处理·langchain·agent·超时·重试·with_retry
for_ever_love__9 小时前
LangChain 批量调用与并发控制:batch、abatch 与速率限制
人工智能·langchain·异步·config·invoke·abatch
衍辉channel10 小时前
衍辉AI速递 10.14|谷歌发布Gemini 4 Argon模型等9条AI资讯
javascript
vipxieliang10 小时前
JavaScript 事件循环与任务队列:从 setTimeout 到 Promise
前端·javascript
for_ever_love__10 小时前
LangChain RunnableConfig 详解:运行时传参、并发控制与回调注入
语言模型·langchain·llm·prompt·config·invoke·运行时参数