跳到正文
RadarAI· LangChain:Blog(RSS)·· 3 小时前AI 评分55

LangChain 讲解 AI Agent 上下文工程为何从检索开始

LangChain 讲解 AI Agent 的上下文工程为何从检索开始

AI 导读

LangChain 发文指出 AI Agent 检索面临低效搜索、一次性塞满上下文窗口、无关或陈旧内容三类问题,并以竞争分析 Agent 为例说明应对方法。

正文 · AI 翻译

LangChain:Blog·2026-10-24 01:00· 44 分钟前AI 评分64

AI Agent 的上下文工程始于检索

AI 导读

LangChain 发文讲解 Agent 上下文工程中的检索问题,指出低效搜索、一次性塞满上下文窗口、无关或陈旧内容三类痛点。

正文 · 原文

想象你正在参加一场开卷考试:从技术上讲,你拥有所有答案,但不知为何,这反而让你更难找到正确的那一个。

  • 你该从哪里开始搜索?
  • 你如何知道自己的来源是正确的?
  • 你如何优化时间,以免把全部精力都耗费在重复搜索上?

高效地找到正确的信息并没有那么简单。

给 Agent 一个问题,它也会陷入类似的困境:它的上下文窗口有限,所以和我们人类一样,它必须检索信息来完成某些任务(而不是把所有内容都记住)。在搜索时,Agent 会面临以下困难:

  • 低效搜索:每次搜索都要消耗 token 和时间。一个笨拙搜索的 Agent(例如打开每个文件只为看看里面有什么,或者不断改写同一个查询)可能在找到任何有用信息之前,就已经让搜索结果填满了上下文。
  • 一次性塞入过多上下文:一次搜索就倾倒出完整的数据导出或十几份文档,一下子填满上下文窗口,让真正的工作没有余地。
  • 无关或陈旧的上下文:过时或跑题的信息对模型来说,可能和正确答案一样有说服力。一旦它进入上下文窗口,Agent 就可能在此基础上继续构建(而这个错误会贯穿之后的每一步)。

幸运的是,Agent 可以运用多种技术来检索正确的信息。agent harness 的职责就是策略性地应用这些技术,为给定任务在正确的时间把正确的上下文交给模型。

本文介绍 Agent 检索(只查找并拉取当前任务所需的信息)面临的挑战、我们可以用来应对这些挑战的技术,以及在设计 agent harness 时如何牢记这些要点(以 LangChain 的 Deep Agents harness 为例)。

关于上下文一旦进入上下文窗口后如何管理,请参阅这篇关于上下文工程的博客。

我们的示例:一个竞品分析 Agent

让我们看看这如何应用于一个真实用例:竞品分析 Agent。每周一,一个 Agent 会研究数百个竞争对手,并按团队模板撰写报告。它需要:

  • 找到上周关于 A 公司的笔记
  • 在庞大的市场数据导出中查找某个具体的营收数字
  • 在新闻档案中搜索任何新内容

以上每一项都是不同类型的检索,因此面临不同的问题。我们使用 Deep Agents harness 构建了我们的 Agent,因此可以直接应用一些技术来尽量减少这些问题。

1. 低效搜索

我们的 Agent 必须为每个竞争对手找到三样东西。如果每一样都要尝试十几次(打开文件只为看看里面有什么,或者因为来源使用了不同的词而改写搜索),它的上下文窗口在还没写出报告一个字之前,就已经被失败的搜索填满了。

因此,最好的搜索就是 Agent 不必重复的搜索,这意味着要为每项工作使用正确的工具。

从内置的文件系统工具开始

智能体搜索信息最常见的方式之一是利用文件系统。然而,如果我们的智能体打开每个文件查看内容,搜索效率会非常低:它读取的每个文件都会进入上下文窗口,因此仅靠浏览就可能把上下文填满。

更好的做法是先检查文件名和关键词(就像开卷考试时你会先翻书的索引,而不是逐页阅读)。有两个常见的文件系统工具正是这样做的:

  • glob 按名称模式查找文件。例如,**/company-a*.md 可以找到上周关于 Company A 的笔记。
  • grep 在文件内部搜索,而不会把文件加载到上下文窗口中。它匹配字面文本,可以返回匹配的文件、匹配的行或匹配计数。

每个深度智能体都内置了这些工具,以及 ls、read_file、write_file 和 edit_file。无论文件位于何处(智能体状态、本地磁盘、持久化存储或沙箱),这些工具都能正常工作,因为它们都通过智能体的后端访问。

当关键词不够用时,添加检索工具

Grep 在你明确知道要查找哪些词时非常有用,但它只匹配你提供的精确文本。这对我们示例中的第三项任务来说是个问题:在新闻档案中搜索任何新内容。如果我们的智能体用 grep 搜索“Company A funding”,就会漏掉一篇标题为“Company A closes $50M Series B”的文章,尽管那正是它要找的内容。

要按含义而非精确词语进行搜索(语义搜索),你需要添加一个由向量存储支持的检索工具。检索工具是智能体可以调用来获取信息的任何工具:它接收一个查询,在某个数据源中查找,并返回找到的内容(最接近的匹配项)。

检索工具的匹配方式取决于其背后的数据源。由向量存储支持时,它按含义匹配,但检索工具也可以建立在其他数据源之上:

  • SQL 数据库,用于结构化查询(如销售数据或客户记录)
  • 搜索 API(用于从网络获取最新信息)
  • MCP 服务器(用于他人已构建好的工具,如文档搜索)

如需深入了解语义搜索的工作原理,请参阅LangChain 的指南。

让智能体决定何时搜索

许多检索方案在开始时搜索一次,然后给出答案。这对 FAQ 机器人(2 步 RAG)有效,但对我们的智能体不适用。假设在研究 Company C 的过程中,它发现了一篇关于某款产品发布的文章,而它此前并不知道这款产品,现在它需要该产品的定价(它在开始时不可能知道要查找这个)。如果只搜索一次,报告就会在缺少这一信息的情况下发出。

研究型智能体需要智能体式检索(而非 2 步 RAG):由智能体决定何时搜索、搜索什么,以及是否需要更多信息。使用 Deep Agents 时,它默认就是这样工作的:智能体在循环中运行,可以按需多次调用任何工具。

将你的搜索知识打包为技能

为了帮助智能体更好地搜索,你可以将搜索知识打包为一个技能:针对特定任务的一组指令(例如使用哪个索引、如何表述查询以及如何格式化引用)。

智能体在任务需要之前只能看到每个技能的名称和简短描述;之后才会加载完整指令,因此小众知识在需要时可用,其余时间不占用上下文(这种技术称为渐进式披露)。这里有一个视频讲解。

渐进式披露(动画)

2. 一次性加载过多上下文

然而,尽管优化了搜索过程,即使是正确的搜索也可能返回过多内容。我们的智能体只需要市场数据导出中的一个数字(B公司的季度营收),但加载全部40,000行来找到它就会填满上下文窗口。我们的检索工具也有同样的问题:如果它把每篇新闻文章都直接返回给模型,那么每次搜索四个结果、跨五家公司,很快就会累积起来。

一次一页地读取大文件

文件系统工具的设计目标是尽可能少返回内容。read_file一次读取一页文件(默认100行),智能体可以设置偏移量(从哪一行开始)和限制(读取多少行)来跳转到所需部分。因此,要找到B公司的营收,智能体会先用grep搜索它,然后只读取匹配位置附近的几行。

让运行框架卸载过大的结果

有时工具返回的内容仍然超出预期。当工具结果过大(超过约20,000个token)时,运行框架会将其保存到/large_tool_results/下的文件中,并把路径和简短预览(首尾几行)交给智能体。智能体随后可以用read_file的偏移量和限制只读回它需要的部分。

让你的检索工具返回文件路径

你可以把同样的思路应用到自己的检索工具上,而不必等待大小限制触发:让它把每个结果写入文件,只返回路径。这是“检索、卸载、委派”模式的第一部分(LangChain的RAG指南对此有完整讲解)。

这是一个简化版的检索工具。它执行搜索,把每个结果保存为智能体后端中的一个文件,并且只返回文件路径列表:‍

backend = StateBackend()

@tool
def search_news(query: str) -> str:
    """Search the news archive and save each result to a file."""
    docs = vector_store.similarity_search(query, k=4)
    files = [(f"/retrieved/result_{i}.md", doc.page_content.encode()) for i, doc in enumerate(docs)]
    backend.upload_files(files)
    return "Saved results to: " + ", ".join(path for path, _ in files)

现在主智能体看到的是四个短路径,而不是四篇完整文章。

3. 无关或过时的上下文

如果结果是错误的,那么结果再小也无济于事。这四个文件中的任何一个都可能关于另一家名称相似的公司,或者是两年前的报道。离题或过时的文本对模型来说看起来和正确答案一样有说服力,因此目标是将它完全排除在主智能体的上下文之外。

让子智能体过滤结果

然而,主智能体仍然必须读取这些文件才能判断哪些重要(而且它打开的每篇离题文章都会进入其上下文)。这就是“检索、卸载、委派”模式中“委派”部分的用武之地:子智能体读取每个检索到的文件,只返回关键事实。

  1. 定义子智能体:描述告诉主智能体何时使用它(每次调用处理一个文件),系统提示告诉子智能体如何处理该文件。‍
chunk_analyst = {
		"name": "chunk-analyst",
		"description": "Analyze one retrieved file. Pass the question and a single path under /retrieved/.",
		"system_prompt": "Use read_file to read the assigned file. Return the key facts and the source URL.",
}
  1. 把检索工具和子智能体交给主智能体。传入你在search_news中使用的同一个后端,以便子智能体能够读取工具保存的文件。
agent = create_deep_agent(
		model=model,
		tools=[search_news],
		backend=backend,  # same instance, so read_file can see /retrieved/
		subagents=[chunk_analyst],
)

‍现在的流程是:

  1. 搜索:主智能体调用search_news并得到四个路径。
  2. 委派:它把每个路径发送给chunk-analyst子智能体,它们可以并行运行。
  3. 综合:每个分析员返回一份带有来源的简短摘要。主智能体将它们合并,如果缺少某些内容就再次搜索。

原始结果写入文件,嘈杂的读取在拥有各自上下文窗口的子智能体中完成,主智能体只看到结论。任何偏离主题的内容都留在子智能体的上下文中,主智能体的上下文窗口保持干净!

从最新的来源获取

过滤确实能把偏离主题的结果挡在外面,但它无法修正过时的结果。我们的竞品分析智能体每周一运行,所以上个月的索引抓不到本周的新闻。

一个解决方案:搜索 API 或 MCP 服务器可以为智能体提供当前信息(而不是几个月前被索引的内容)。如果你的数据带有日期,在检索工具中按日期过滤也能把旧结果挡在外面。

小心检索到的文本

反过来,相关的当前结果也可能带来问题!由于检索到的文本是数据而非指令,你检索到的任何内容都可能包含提示注入(而且没有任何提示词或分隔符策略能完全防止这一点)。务必验证输出:检查答案是否引用了你预期的来源,以及论断是否与之一致。

需要记住的检索策略

以下是你需要留意的(无论你基于什么框架构建):

  • 使用最有效且可行的工具。如果你知道确切的词,grep 胜过嵌入查找。把语义搜索留到你真正需要它的时候。
  • 分页读取而非整体加载。分块读取大文件,而不是一次性全部读入。
  • 返回指针而非载荷。把检索到的内容写入文件,并把路径交给智能体。
  • 委派读取。让子智能体读取并总结,这样主智能体只看到结论。
  • 验证依据。对照检索到的内容检查答案,并把检索到的文本视为不可信。

试一试

uv add deepagents

想向现场讲师学习更多?我将在 11 月 9 日讲解这些内容:在此注册!

‍

检索只是故事的一半:信息如何找到通往智能体的路径。当智能体随着时间推移开始积累信息时,我们如何管理它的上下文窗口?

了解更多请查看:

来源:LangChain:Blog · langchain.com

来源:RadarAI · aihot.news