减轻AI代理中的上下文污染
上下文工程意味着给AI提供它在需要时能良好执行所需的一切。这包括指令、示例、数据、工具和过去的互动,所有这些都在运行时打包到模型的输入上下文中。
可以这样思考:
语言模型就像一个CPU,它的上下文窗口是工作内存。我们的任务是将正确的代码、数据和指令混合加载到内存中,使模型能够正确完成任务。
这些上下文可以来自许多地方:
- 用户的查询
- 系统级指令
- 搜索结果
- 工具输出
- 之前的步骤摘要
上下文工程是将这些部分组合成一个连贯的输入。它不是静态提示,而是根据任务实时组装的内容。
在这篇博客中,我将介绍一些方法来解决上下文污染。这包括:
- 解释什么是AI代理领域的上下文污染。
- 介绍一种被广泛用于解决AI代理上下文污染的方法。
- 展示该方法的端到端实现,并说明如何使用langgraph代理工作流来减轻上下文污染。
1、什么是上下文污染?
当幻觉或错误进入上下文并被视为真相时,就会发生上下文污染。一旦进入,模型会不断引用它,通常会强化这个错误。
这对代理来说尤其有害。如果错误的事实出现在目标、摘要或记忆中,代理可能会追求不可能的结果或重复不再有意义的动作。这个问题随着时间的推移会加剧,一旦上下文被污染,修复起来会很困难。
好消息是,有办法处理这个问题!
2、减少上下文干扰
我们使用一种叫做上下文卸载的方法来减轻上下文污染。这有助于保持代理的专注。
上下文卸载意味着将信息存储在语言模型的活动上下文窗口之外。我们通过使用外部工具或内存系统来实现这一点,这些系统可以单独存储数据。然后模型可以在需要时访问这些存储的数据。
为什么上下文卸载有用?
随着上下文窗口的增长,我们可能会假设可以将所有内容都存储在里面。但研究表明,这会导致问题。当重要信息深埋在上下文中时,模型使用它的准确性会降低。这被称为上下文腐化。
通过卸载关键信息并在需要时检索它们,我们可以避免过度加载模型的工作内存。这有助于模型保持准确性和减少混淆。
3、上下文卸载的实际应用
人类在处理复杂任务时会做笔记。代理也开始做类似的事情。
Anthropic的研究显示,首先由主代理思考任务,然后将其计划保存到内存中。这可以防止当上下文窗口太大时计划丢失。
另一个例子来自Manus,其中代理将工具输出和任务计划卸载到文件系统中。这些会被写入和重写,随着代理的进步。这有助于代理记住其目标,而不需要将其全部保留在其活动上下文中。
有不同的方式来实现上下文卸载。例如,一个草稿板可以只是运行时状态的一部分,或者它可以是一个工具调用,写入外部文件。
在单个任务中,草稿板帮助代理管理他们的思考。对于更长期的交互,像反思和记忆这样的方法就派上用场了。这些让代理可以从之前的会话中回忆有用的信息。ChatGPT和Cursor等产品使用类似的内存系统来提高多次交互的性能。
在所有情况下,核心思想都很简单:代理在会话期间存储有用的信息,以便以后需要时使用。
4、在LangGraph中使用上下文卸载
LangGraph使用一个状态对象在节点之间传递数据。这个状态对象充当共享内存或草稿板。在执行过程中,代理可以将重要的笔记、计划或输出写入此状态。代理的其他部分随后可以在工作流中访问和使用这些数据。
这种结构允许我们管理哪些内容留在模型的上下文中,哪些内容被卸载。这有助于保持代理的专注和正确性。
让我们构建一个具有以下功能的langgraph代理:
- 带有草稿板的代理:一个可以写入和读取草稿板以避免上下文污染的代理。
- 上下文卸载工作流:将计划和发现存储在模型上下文之外,并在需要时带入。
- 工具驱动的研究循环:使用网络搜索和草稿板存储。
- 使用LangGraph的状态图:管理推理步骤和卸载的上下文。
- 使用LangGraph的持久内存:使用键值存储实现跨线程草稿板内存。
- 使用线程进行检查点:保存中间代理状态,如聊天线程,以便以后继续。
让我们开始吧。
要求:
"bs4>=0.0.2",
"dotenv>=0.9.9",
"ipykernel>=6.30.0",
"langchain>=0.3.27",
"langchain-community>=0.3.27",
"langchain-google-genai>=2.1.8",
"langgraph>=0.6.3",
"langgraph-bigtool>=0.0.3",
"langgraph-supervisor>=0.0.29",
"pandas>=2.3.1",
"rich>=14.1.0",
"tiktoken>=0.9.0"
步骤 1:导入所有必要的库
# === 标准库 ===
import getpass
import os
from typing_extensions import Literal
# === 显示工具 ===
from IPython.display import Image, display
# === 数据建模 ===
from pydantic import BaseModel, Field
# === 格式化工具 ===
from utils import format_messages
# === LangChain / 工具 ===
from langchain_core.messages import SystemMessage, ToolMessage, HumanMessage
from langchain_core.tools import tool
from langchain_google_genai import ChatGoogleGenerativeAI, GoogleGenerativeAIEmbeddings
from langchain_tavily import TavilySearch
# === LangGraph ===
from langgraph.graph import END, START, StateGraph, MessagesState
# === 加载环境变量 ===
from dotenv import load_dotenv
load_dotenv()
步骤 2:带有草稿板的代理状态
class ScratchpadState(MessagesState):
"""代理状态,包含一个额外的草稿板字段用于保存中间笔记。"""
scratchpad: str = Field(description="用于存储笔记的草稿板")
步骤 3:草稿板工具
@tool
class WriteToScratchpad(BaseModel):
"""用于将笔记写入草稿板内存的工具。"""
notes: str = Field(description="要保存到草稿板的笔记")
@tool
class ReadFromScratchpad(BaseModel):
"""用于从草稿板中读取之前保存的笔记的工具。"""
reasoning: str = Field(description="代理想要检索过去笔记的原因")
步骤 4:搜索工具和Gemini LLM设置
#Tavily用于实时网络搜索
search_tool = TavilySearch(max_results=5, topic="general")
llm = ChatGoogleGenerativeAI(model="gemini-2.5-pro", temperature=1)
步骤 5:绑定工具到LLM
tools = [ReadFromScratchpad, WriteToScratchpad, search_tool]
tools_by_name = {tool.name: tool for tool in tools}
llm_with_tools = llm.bind_tools(tools)
步骤 6:系统提示
scratchpad_prompt = """你是一个具有网络搜索和持久草稿板的高级研究助理。你的研究工作流程:
1. 检查草稿板:查找与你的任务相关的现有笔记。
2. 创建研究计划:思考任务并制定计划。
3. 写入草稿板:保存计划和发现。
4. 使用搜索:使用搜索工具查找信息。
5. 更新草稿板:将新结果添加到你的笔记中。
6. 迭代:按需重复。
7. 完成任务:使用所有收集的信息呈现最终输出。
可用工具:
- WriteToScratchpad
- ReadFromScratchpad
- TavilySearch
"""
步骤 7:创建langgraph工作流
def llm_call(state: ScratchpadState) -> dict:
return {
"messages": [
llm_with_tools.invoke(
[SystemMessage(content=scratchpad_prompt)] + state["messages"]
)
]
}
def tool_node(state: ScratchpadState) -> dict:
result = []
for tool_call in state["messages"][-1].tool_calls:
tool = tools_by_name[tool_call["name"]]
observation = tool.invoke(tool_call["args"])
if tool_call["name"] == "WriteToScratchpad":
notes = observation.notes
result.append(ToolMessage(content=f"Wrote to scratchpad: {notes}", tool_call_id=tool_call["id"]))
update = {"messages": result, "scratchpad": notes}
elif tool_call["name"] == "ReadFromScratchpad":
notes = state.get("scratchpad", "")
result.append(ToolMessage(content=f"Notes from scratchpad: {notes}", tool_call_id=tool_call["id"]))
update = {"messages": result}
elif tool_call["name"] == "tavily_search":
result.append(ToolMessage(content=observation, tool_call_id=tool_call["id"]))
update = {"messages": result}
return update
def should_continue(state: ScratchpadState) -> Literal["tool_node", "__end__"]:
last_message = state["messages"][-1]
return "tool_node" if last_message.tool_calls else END
agent_builder = StateGraph(ScratchpadState)
agent_builder.add_node("llm_call", llm_call)
agent_builder.add_node("tool_node", tool_node)
agent_builder.add_edge(START, "llm_call")
agent_builder.add_conditional_edges("llm_call", should_continue, {"tool_node": "tool_node", END: END})
agent_builder.add_edge("tool_node", "llm_call")
agent = agent_builder.compile()
步骤 8:显示代理图
display(Image(agent.get_graph(xray=True).draw_mermaid_png()))
步骤 9:提出一个问题
query = "比较Xaira和Cohere的融资轮次和最新进展。"
state = agent.invoke({"messages": [HumanMessage(content=query)]})
format_messages(state['messages'])
输出:
╭─────────────────────────────────────────────────── 🧑 Human ────────────────────────────────────────────────────╮
│ 比较Xaira和Cohere的融资轮次和最新进展。 │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭───────────────────────────────────────────────────── 📝 AI ─────────────────────────────────────────────────────╮
│ │
│ 🔧 工具调用: ReadFromScratchpad │
│ 参数: { │
│ "reasoning": "我需要检查是否有任何关于Xaira或Cohere的现有笔记来指导我的研究计划。" │
│ } │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────────── 🔧 工具输出 ─────────────────────────────────────────────────╮
│ 草稿板中的笔记: │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭───────────────────────────────────────────────────── 📝 AI ─────────────────────────────────────────────────────╮
│ 好的,我会比较Xaira和Cohere的融资和最新进展。我将首先概述一个研究计划, │
│ 然后使用网络搜索来找到它们的融资轮次、投资者、新产品、合作伙伴关系和人员变动。 │
│ 我会在过程中记录我的发现,然后综合信息进行完整的比较。 │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────────── 🔧 工具输出 ─────────────────────────────────────────────────╮
│ Wrote to scratchpad: ## Research Plan: Xaira vs. Cohere │
│ │
│ **Objective:** Compare the funding rounds and recent developments of Xaira and Cohere. │
│ │ │
│... │
│ │
│ **Outcome:** A detailed comparison of Xaira and Cohere's financial backing and recent progress, highlighting │
│ their competitive positions in the AI landscape. │
│ │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭───────────────────────────────────────────────────── 📝 AI ─────────────────────────────────────────────────────╮
│ 我有了研究计划。我将从Xaira开始,先进行广泛的搜索以了解公司及其融资情况。接下来,我会研究Cohere的融资和最新进展。最后,我将综合所有信息以创建全面的比较。 │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────────── 🔧 工具输出 ─────────────────────────────────────────────────╮
│ {'query': 'Xaira funding and launch', 'follow_up_questions': None, 'answer': None, 'images': [], 'results': │
│ [{'url': 'https://www.drugdiscoverytrends.com/xaira-therapeutics-billion-dollar-ai-biotech-funding/', 'title': │
│ ...
|through $1 billion in committed funding.', 'score': 0.60498303, 'raw_content': None}], 'response_time': 0.92} │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭───────────────────────────────────────────────────── 📝 AI ─────────────────────────────────────────────────────╮
│ │
│ 🔧 工具调用: WriteToScratchpad │
│ 参数: { │
│ "notes": "## Xaira: Funding and Recent Developments\n\n**Funding:**\n* **Total Funding:** Over $1 billion │
| .... to human diseases.\n* **Location:** Headquartered │
│ in the San Francisco Bay Area.\n* **Status:** Emerged from stealth mode and states it is \"ready\" to begin │
│ developing drugs.\n" │
│ } │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────────── 🔧 工具输出 ─────────────────────────────────────────────────╮
│ Wrote to scratchpad: ## Xaira: Funding and Recent Developments │
│ │
│ **Funding:** │
│ * **Total Funding:** Over $1 billion in committed capital at launch.
... │ │
│ * **Status:** Emerged from stealth mode and states it is "ready" to begin developing drugs. │
│ │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭───────────────────────────────────────────────────── 📝 AI ─────────────────────────────────────────────────────╮
│ │
│ 🔧 工具调用: tavily_search │
│ 参数: { │
│ "query": "Cohere funding rounds and recent developments" │
│ } │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────────── 🔧 工具输出 ─────────────────────────────────────────────────╮
│ {'query': 'Cohere funding rounds and recent developments', 'follow_up_questions': None, 'answer': None, │
│ 'images': [], 'results': [{'url': 'https://exa.ai/websets/directory/cohere-funding', 'title': 'Cohere Inc. │
│ Funding Rounds & Investors - Exa', 'content': 'Key funding milestones included significant Series C and Series │
│... │
│ 'https://www.theinformation.com/articles/ai-startup-cohere-projects-200-million-revenue-pace-new-funding-nears' │
│ Projects $200 Million Revenue Pace as New Funding Nears. Cohere, a Canadian artificial intelligence developer │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭───────────────────────────────────────────────────── 📝 AI ─────────────────────────────────────────────────────╮
│ │
│ 🔧 工具调用: WriteToScratchpad │
│ 参数: { │
│ "notes": "## Cohere: Funding and Recent Developments\n\n**Funding:**\n* **Total Funding:** $970 million │
│ over 5 funding rounds.\n* **Funding Round Details:**\n * Series D (June 2024): $500 million, valuing the │
... │
│ } │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────────── 🔧 工具输出 ─────────────────────────────────────────────────╮
│ Wrote to scratchpad: ## Cohere: Funding and Recent Developments │
│ │
│ **Funding:** │
│ * **Total Funding:** $970 million over 5 funding rounds. │
│ * **Funding Round Details:** │
... │
│ * **Focus:** Building a suite of large language models (LLMs) and making them accessible to developers and │
│ enterprises through an API. │
│ │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭───────────────────────────────────────────────────── 📝 AI ─────────────────────────────────────────────────────╮
│ │
│ 🔧 工具调用: ReadFromScratchpad │
│ 参数: { │
│ "reasoning": "我已经收集了关于Xaira和Cohere的融资和最新进展的信息。现在我将综合这些信息以提供全面的比较。" │
│ } │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭──────────────────────────────────────────────── 🔧 工具输出 ─────────────────────────────────────────────────╮
│ Notes from scratchpad: ## Cohere: Funding and Recent Developments │
│ │
│ **Funding:** │
│ * **Total Funding:** $970 million over 5 funding rounds. │
... │
│ **Recent Developments:** │
│ * **Product Launches:** │
│ * **Command R+:** A new, highly advanced language model designed for enterprise use cases. │
... │
│ │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭───────────────────────────────────────────────────── 📝 AI ─────────────────────────────────────────────────────╮
│ 对Xaira和Cohere的深入比较揭示了在竞争激烈的AI领域中两种不同的策略。 │
... │
│ │
│ ### 融资 │
│ │
│ | 特征 | Xaira | Cohere | │
│ | --- | --- | --- | │
│ | **总融资额** | 超过10亿美元 | 大约9.7亿美元 | │
... │
│ │
│ **分析:** Xaira的融资以其规模和时机著称,在启动时获得了最大的种子轮之一。 │
... │
│ ### 最新进展 │
│ │
│ | 特征 | Xaira | Cohere | │
│ | --- | --- | --- | │
│ | **公司状态**| 于2024年4月推出,从秘密模式中出现。 | 一家成立已久的公司,拥有不断增长的 │
... │
│ │
│ **分析:** Xaira是一个新进入者,专注于制药和生物技术行业的特定高风险领域。 │
... │
│ │
│ ### 总结 │
│ │
│ * **Xaira** 是一个资金雄厚的新手,针对一个特定且高风险的行业:药物研发。它的巨额融资使其能够在早期阶段快速推进。 │
...
│ * **Cohere** 是一家更成熟的AI公司,已经建立了良好的声誉和坚实的融资基础。它的重点是构建一系列大型语言模型(LLMs),并通过API使其对开发者和企业可访问。 │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
这里是完整的 langsmith trace.
注意:我们可以通过进一步更新系统提示,使代理更好地遵循严格的规则,即何时写入和何时直接从草稿板获取输入。
步骤 10:打印草稿板中的笔记
from rich.console import Console
from rich.pretty import pprint
console = Console()
console.print("\n[bold green]草稿板:[/bold green]")
from rich.markdown import Markdown
Markdown(state['scratchpad'])
输出:
草稿板:
Cohere: 融资和最新进展
融资:
• 总融资额: 9.7亿美元,共5轮融资。
• 融资轮细节:
• D轮 (2024年6月): 5亿美元,估值55亿美元。
• C轮 (2023年6月): 2.7亿美元。
• 主要投资者: Inovia Capital, NVIDIA, Oracle, Salesforce Ventures, 等等。
最新进展:
• 产品发布:
• Command R+: 一款专为企业使用场景设计的先进语言模型。
• Embed V3: 一款高性能文本嵌入模型。
• 开源倡议: 释放了一些模型作为开源。
• 战略合作:
• Oracle: 将其模型集成到Oracle的云服务中。
• NVIDIA: 与NVIDIA合作进行模型训练和部署。
• Salesforce: 与Salesforce合作为企业的客户提供AI。
• 扩展:
• 在纽约市开设了新办公室。
• 显著扩展了团队。
• 领导层: 由Aidan Gomez共同创立,他是原始“Attention is All You Need”论文的合著者,该论文介绍了Transformer架构。
的作者之一。
• 重点: 构建一系列大型语言模型(LLMs),并通过API使其对开发者和企业可访问
。
这起到了一个 “”think”步骤的作用,正如Anthropic所描述的。
它还展示了 “recitation”效应,正如Manus所描述的,代理重复关键信息以保持专注。
让我们创建一个 InMemoryStore
用于几个不同会话中使用:
步骤 11:内存存储和命名空间设置
LangGraph 提供了工具来在代理运行之间持久化和重用上下文:
- 检查点:将整个图状态保存到线程中,类似于聊天历史记录。
- 长期记忆(BaseStore):在多个线程之间保存选定的数据(例如,笔记、计划、用户档案)。
- InMemoryStore:一个内置的键值存储,用于本地测试长期记忆。
from langgraph.store.memory import InMemoryStore
# 初始化内存存储以用于长期记忆
store = InMemoryStore()
# 定义一个命名空间来组织上下文
namespace = ("rlm", "scratchpad")
# 添加持久上下文到存储
store.put(
namespace,
"scratchpad",
{
"scratchpad": "关于发展中国家可再生能源采用的研究项目。需要跟踪的关键领域:政策框架、技术障碍、融资机制以及试点项目的成功案例。"
}
)
步骤 12:查看存储的内存
from rich.console import Console
from pprint import pprint
# 检索存储的草稿板数据
scratchpad = store.get(namespace, "scratchpad")
# 显示存储的数据
console = Console()
console.print("\n[bold green]从内存中检索的上下文:[/bold green]")
pprint(scratchpad)
输出:
从内存中检索的上下文:
Item(namespace=['rlm', 'scratchpad'], key='scratchpad', value={'scratchpad': '关于发展中国家可再生能源采用的研究项目。需要跟踪的关键领域:政策框架、技术障碍、融资机制以及试点项目的成功案例。'}, created_at='2025-08-06T06:16:07.997988+00:00', updated_at='2025-08-06T06:16:07.997995+00:00')
步骤 13:带有存储的持久化工具节点
现在,让我们将我们在上面的LangGraph工作流中所做的内容嵌入进去。
我们将使用两个参数编译我们的工作流:
checkpointer
: 在每一步保存图状态到线程store
: 在线程之间持久化上下文
from langgraph.store.base import BaseStore
from langgraph.checkpoint.memory import InMemorySaver
def tool_node_persistent(state: ScratchpadState, store: BaseStore) -> dict:
result = []
for tool_call in state["messages"][-1].tool_calls:
tool = tools_by_name[tool_call["name"]]
observation = tool.invoke(tool_call["args"])
if tool_call["name"] == "WriteToScratchpad":
notes = observation.notes
result.append(ToolMessage(content=f"Wrote to scratchpad: {notes}", tool_call_id=tool_call["id"]))
store.put(namespace, "scratchpad", {"scratchpad": notes})
update = {"messages": result}
elif tool_call["name"] == "ReadFromScratchpad":
stored_data = store.get(namespace, "scratchpad")
notes = stored_data.value["scratchpad"] if stored_data else "未找到笔记"
result.append(ToolMessage(content=f"草稿板中的笔记: {notes}", tool_call_id=tool_call["id"]))
update = {"messages": result}
elif tool_call["name"] == "tavily_search":
result.append(ToolMessage(content=observation, tool_call_id=tool_call["id"]))
update = {"messages": result}
return update
步骤 14:构建并编译带有持久内存和检查点的代理
agent_builder_persistent = StateGraph(ScratchpadState)
agent_builder_persistent.add_node("llm_call", llm_call)
agent_builder_persistent.add_node("tool_node", tool_node_persistent)
agent_builder_persistent.add_edge(START, "llm_call")
agent_builder_persistent.add_conditional_edges("llm_call", should_continue, {"tool_node": "tool_node", END: END})
agent_builder_persistent.add_edge("tool_node", "llm_call")
# 用于线程历史的检查点
checkpointer = InMemorySaver()
# 用于长期上下文的内存存储
memory_store = InMemoryStore()
# 编译持久代理
agent = agent_builder_persistent.compile(
checkpointer=checkpointer,
store=memory_store
)
步骤 15:使用线程ID调用代理并显示输出
config = {"configurable": {"thread_id": "1"}}
state = agent.invoke({
"messages": [HumanMessage(content="你能搜索Commonwealth Fusion Systems的融资轮次和最新进展吗?")]
}, config)
console.print("\n[bold cyan]工作流结果(线程 1) - 启动研究:[/bold cyan]")
format_messages(state['messages'])
这里是完整的 langsmith trace.
步骤 16:通过新线程访问先前对话的草稿板
现在,让我们开始一个新的对话,看看代理是否可以访问前一次对话的草稿板。
# 跨线程内存持久化演示
config_2 = {"configurable": {"thread_id": "2"}}
messages_2 = agent.invoke({
"messages": [HumanMessage(content="Helion Energy筹集的资金与Commonwealth Fusion Systems相比如何?")]
}, config_2)
console.print("\n[bold cyan]工作流结果(线程 2) - 跨线程内存访问:[/bold cyan]")
format_messages(messages_2['messages'])
查看这个 langsmith trace.
代理跨线程检查草稿板。
正如我们所看到的,代理能够检查草稿板中的存储信息并在其工作流中使用它。
注意:我测试了代码,对我来说 gemini-2.5-pro
无法很好地与设置的提示配合使用。它适合测试。然而,openai-40-mini表现非常好,并确保正确存储和更新sractchpad。
恭喜你走到这一步!
5、结束语
我们现在看到了如何有效地处理代理工作流中的上下文污染。在这篇博客中,我们实现了上下文卸载。这些方法有助于模型专注于当前任务,而不会被不必要的信息淹没。
此外,这种设置有助于减少上下文窗口中的不必要数据,并提高模型的推理能力。在长而多步骤的工作流中,长历史可能会分散模型的注意力,因此特别有用。
原文链接:Mitigate Context Poisoning in AI Agents Using Context Engineering
汇智网翻译整理,转载请标明出处