三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Context Engineering:解决AI Agent幻觉与上下文失忆的工程化实践

Context Engineering:解决AI Agent幻觉与上下文失忆的工程化实践

1. 项目概述:从“幻觉”到“事实”的工程化之路

最近在折腾各种AI Agent项目时,我遇到了一个几乎每个开发者都会头疼的问题:Agent在复杂任务链中“跑偏”了。比如,你让它先查天气,再根据天气推荐穿搭,最后生成出行建议。结果它可能在第二步就忘了第一步查到的具体温度,或者把“上海”的天气信息张冠李戴到了“北京”的推荐里。这种“上下文失忆”或“事实混淆”的现象,本质上就是模型在当前推理步骤中,没能“看到”正确且完整的事实信息。这直接导致了输出不可靠、任务失败。为了解决这个问题,一个被称为“Context Engineering”(上下文工程)的实践领域正在迅速兴起。它不再是简单地往提示词里堆砌信息,而是一套系统性的工程方法,旨在精准控制、动态构建和高效维护Agent推理过程中的上下文信息流,确保每一步决策都基于最相关、最准确的事实。

简单来说,Context Engineering的核心目标就是:让Agent在需要的时候,看到它应该看到的东西,并且相信那是真的。这听起来像是一句正确的废话,但实现起来却涉及从算法选型、数据结构设计到工程架构的全链路思考。它直接决定了你的Agent是像一个健忘的实习生,还是一个可靠的专业助手。无论是构建一个处理工单的客服Agent,还是一个分析财报的金融Agent,上下文的质量就是其智能的基石。如果你也正在为Agent的“幻觉”和“混乱”而苦恼,那么深入理解并实践Context Engineering,将是提升你Agent项目稳定性和可用性的关键一步。

2. Context Engineering的核心设计思路与价值

为什么传统的提示词工程(Prompt Engineering)在复杂Agent场景下会力不从心?因为提示词工程更多关注于单次交互的输入优化,是静态的、一次性的。而Agent的任务往往是多步的、动态的、状态依赖的。Context Engineering则将视角从“单次输入”提升到了“全流程状态管理”。

2.1 从静态提示到动态上下文的范式转变

传统的做法可能是一个超长的系统提示(System Prompt),试图一次性告诉模型所有规则和知识。但在长对话或多步任务中,模型有限的上下文窗口(Context Window)会迅速被占满,新旧信息相互干扰,重要细节被淹没在历史中。Context Engineering的思路是反过来的:我们不追求在开始时装载所有信息,而是设计一套机制,在任务执行的每个步骤,动态地、按需地组装一个最精简、最相关的上下文片段,提供给模型。

这就像给Agent配备了一个智能的“工作台”和一位“图书管理员”。工作台(当前上下文)上只摆放当前步骤必须用到的工具和资料。图书管理员(上下文管理引擎)则根据任务进展,随时从庞大的资料库(长期记忆、知识库、工具输出等)中检索出需要的文件,替换掉工作台上过时的部分。这样,Agent的“注意力”始终能聚焦在关键事实上。

2.2 核心价值:准确性、效率与可控性

实施Context Engineering会带来三个层面的显著提升:

  1. 准确性(降低幻觉):这是最直接的价值。通过确保提供给模型的事实来源可靠(如来自权威知识库、经过验证的工具调用结果),并减少无关信息的干扰,模型基于错误或缺失信息进行“脑补”的可能性大大降低。例如,在代码生成Agent中,确保当前步骤的上下文里包含本项目特定的API文档片段,而不是泛泛的编程语言知识,能有效避免生成不可用的接口调用。

  2. 效率(节省Token,提升速度):大模型的API调用成本和处理延迟与输入的Token数量直接相关。通过动态构建精准上下文,避免了在每个步骤都传递冗长的完整历史。这不仅降低了成本,也往往能提升模型的响应速度,因为模型需要处理的信息噪音更少,目标更明确。

  3. 可控性与可调试性:当Agent行为出现偏差时,传统的长提示词很难定位问题根源。而一个设计良好的Context Engineering系统,会清晰地记录每个步骤的上下文构成:包含了哪些信息?来自哪个来源?为什么选择这些信息?这为调试提供了清晰的审计线索。你可以像查看日志一样,审查每一步的“思维原料”,从而快速定位是知识检索错了,还是信息组装逻辑有bug。

注意:Context Engineering并非要完全取代提示词工程。前者关注信息供给的“管道”和“原料”,后者关注如何利用这些原料的“菜谱”。两者需要协同工作。一个精妙的提示词(菜谱)如果拿到了错误的食材(上下文),依然做不出好菜。

3. 上下文的核心构成与动态组装策略

要工程化地管理上下文,首先需要解构它。一个典型的Agent步骤上下文,通常由多个具有不同生命周期和来源的“信息块”动态拼接而成。

3.1 上下文的层次化分解

我们可以将上下文视为一个分层的结构,每一层都有其特定的管理策略:

  1. 系统指令层(System Instruction):这是最稳定的一层,定义了Agent的角色、核心行为准则和基础能力框架。它通常在会话开始时注入一次,并在整个生命周期中保持有效或仅做微小调整。例如:“你是一个专业的IT支持助手,专注于解决网络连接问题。请逐步思考,在给出最终答案前,必须调用工具验证你的推断。”

  2. 会话记忆层(Conversation Memory):存储当前会话中用户与Agent的历史交互。全量存储会占用大量窗口,因此需要策略进行摘要(Summarization)、选择性保留或向量化检索。例如,只保留最近N轮对话的原始内容,更早的历史则压缩成一段摘要:“用户之前报告了Wi-Fi连接时断时续,已尝试重启路由器无效。”

  3. 工作记忆层(Working Memory / Scratchpad):这是Context Engineering的焦点。它存储当前任务链的中间状态、上一步的工具执行结果、临时变量等。这部分内容必须精确、实时且高度相关。例如,上一步工具调用get_current_weather返回的{“city”: “Beijing”, “temp”: 22, “condition”: “Sunny”},就应该被完整、结构化地放入工作记忆,供下一步的“推荐穿搭”使用。

  4. 外部知识层(External Knowledge):从向量数据库、知识图谱或API实时获取的领域知识。这部分需要通过检索增强生成(RAG)技术动态插入。关键在于检索的“精准度”和“新鲜度”。例如,在回答关于某款新产品特性的问题时,应检索产品最新的官方文档,而不是几个月前的社区帖子。

  5. 工具规格层(Tool Specifications):描述Agent可调用工具的清单。通常不需要在每一步都全量提供,可以采用“懒加载”或“按需描述”的策略。当模型表现出调用某个工具的意图时,再将那个工具的具体描述(函数签名、参数说明、示例)加入上下文。

3.2 动态组装策略:规则引擎与学习型路由

如何决定每一步该组装哪些信息块?这里有两种主流思路:

基于规则的策略(Rule-based):这是最直接可控的方式。你可以为不同类型的任务步骤定义明确的上下文模板。例如:

  • “信息查询”步骤:上下文 = 系统指令 + 当前用户问题 + 相关历史摘要 + 检索到的知识片段。
  • “工具执行”步骤:上下文 = 系统指令 + 工具描述 + 必要的参数上下文(从上一步工作记忆中提取)。
  • “结果综合”步骤:上下文 = 系统指令 + 原始用户问题 + 所有相关工具调用结果 + 关键历史。

这种方式逻辑清晰,易于调试,但缺乏灵活性,需要人工为各种场景设计模板。

学习型路由策略(Learned Routing):更高级的做法是训练一个轻量级模型(或使用一个小型LLM作为路由器),来预测当前步骤最需要的上下文类型和内容。这个路由器以任务目标、当前状态等为输入,输出一个上下文组装计划。这更灵活,但需要数据训练,且增加了系统复杂性。

在实际项目中,我通常采用混合策略:对于核心、高频的任务路径,使用精心设计的规则模板以保证稳定;对于边缘或探索性场景,则设置一个兜底的、基于语义相似度的检索策略,从所有可用信息中动态抓取最相关的部分。

4. 关键技术实现:从理论到代码

理解了设计思路,我们来看看如何用代码实现一个基础的Context Engineering模块。这里以一个基于Python的简易任务型Agent为例。

4.1 定义上下文数据结构

首先,我们需要一个清晰的数据结构来表征上下文。

from typing import Dict, List, Any, Optional from pydantic import BaseModel class ContextChunk(BaseModel): """上下文信息块""" content: str # 文本内容 source: str # 来源,如 “system”, “memory”, “tool_[name]”, “kb” priority: int = 1 # 优先级,用于组装排序 metadata: Dict[str, Any] = {} # 元数据,如时间戳、置信度 class AgentStepContext(BaseModel): """单一步骤的完整上下文""" system_instruction: str working_memory: List[ContextChunk] # 工作记忆 conversation_memory: List[ContextChunk] # 会话记忆(近期) external_knowledge: List[ContextChunk] # 外部知识 tool_specs: Optional[List[ContextChunk]] = None # 工具规格(按需加载) def assemble_for_llm(self, max_tokens: int = 8000) -> str: """将上下文组装成LLM可接受的提示文本""" # 1. 系统指令始终在最前 prompt_parts = [f"# System Instruction\n{self.system_instruction}\n"] # 2. 按优先级和类型合并其他块,并考虑Token限制 all_chunks = self.working_memory + self.conversation_memory + self.external_knowledge if self.tool_specs: all_chunks.extend(self.tool_specs) # 按优先级排序,高优先级在前 all_chunks.sort(key=lambda x: x.priority, reverse=True) current_tokens = self._estimate_tokens(prompt_parts[0]) for chunk in all_chunks: chunk_text = f"\n# From {chunk.source}\n{chunk.content}" chunk_token_count = self._estimate_tokens(chunk_text) if current_tokens + chunk_token_count > max_tokens: break # Token超出限制,停止添加 prompt_parts.append(chunk_text) current_tokens += chunk_token_count return "\n".join(prompt_parts) def _estimate_tokens(self, text: str) -> int: """简单的Token估算(实际应用中应使用与模型匹配的Tokenizer)""" return len(text) // 4 # 粗略估算

这个AgentStepContext类定义了每一步上下文的组成部分,并提供了一个assemble_for_llm方法,负责根据优先级和Token限制,将各个信息块组装成最终的提示文本。

4.2 实现工作记忆管理器

工作记忆是上下文中最活跃的部分,需要精心管理。

class WorkingMemoryManager: """工作记忆管理器,负责存储和更新任务链的中间状态""" def __init__(self): self.memory: Dict[str, Any] = {} # 键值对存储 self.chunk_history: List[ContextChunk] = [] # 历史块记录,用于调试 def update_from_tool_result(self, tool_name: str, result: Dict[str, Any]): """根据工具调用结果更新工作记忆""" # 例如,工具 get_weather 返回 {"city": "Beijing", "temp": 22} # 我们将其转化为易读的文本,并存储原始数据 summary = f"工具 `{tool_name}` 执行成功。结果:{result}" # 创建上下文块 chunk = ContextChunk( content=summary, source=f"tool_{tool_name}", priority=5, # 工具结果通常优先级很高 metadata={"raw_result": result, "timestamp": time.time()} ) self.chunk_history.append(chunk) # 同时,将关键结构化数据存入键值对,便于后续步骤提取 for key, value in result.items(): self.memory[f"{tool_name}.{key}"] = value def get_relevant_chunks(self, current_step_intent: str) -> List[ContextChunk]: """根据当前步骤的意图,返回相关的工作记忆块""" # 这里可以实现简单的基于关键词的匹配,或更复杂的语义匹配 relevant = [] for chunk in self.chunk_history[-5:]: # 只看最近5个块 if self._is_relevant(chunk, current_step_intent): relevant.append(chunk) return relevant def _is_relevant(self, chunk: ContextChunk, intent: str) -> bool: """简易相关性判断(实际项目应使用嵌入向量相似度计算)""" intent_keywords = intent.lower().split() content_lower = chunk.content.lower() return any(keyword in content_lower for keyword in intent_keywords)

这个管理器不仅存储文本块,还维护了一个结构化的键值存储(self.memory),这使得后续步骤可以精确引用之前的结果,例如在提示词中直接写入{{memory.get('get_weather.temp')}}

4.3 集成外部知识检索(RAG)

让Agent看到正确事实的关键,往往在于接入准确的外部知识源。

class KnowledgeRetriever: """知识检索器,负责从向量数据库获取相关信息""" def __init__(self, vector_db_client): self.db = vector_db_client def retrieve_for_query(self, query: str, user_context: Dict[str, Any], top_k: int = 3) -> List[ContextChunk]: """根据查询和用户上下文检索知识""" # 1. 优化查询:结合原始问题和用户上下文生成更精准的查询 enhanced_query = self._enhance_query(query, user_context) # 2. 执行向量检索 search_results = self.db.similarity_search(enhanced_query, k=top_k) # 3. 将结果封装为ContextChunk knowledge_chunks = [] for i, doc in enumerate(search_results): chunk = ContextChunk( content=f"知识片段 {i+1}: {doc.page_content}", source=f"kb_{doc.metadata.get('source', 'unknown')}", priority=3, # 知识优先级通常低于直接的交互结果 metadata={ "score": doc.metadata.get('score', 0), "source_doc": doc.metadata.get('title', 'N/A') } ) knowledge_chunks.append(chunk) return knowledge_chunks def _enhance_query(self, query: str, context: Dict) -> str: """利用工作记忆中的信息增强查询""" # 例如,如果工作记忆中有城市信息,则添加到查询中 if 'city' in context: return f"{query} 关于 {context['city']}" return query

实操心得:在RAG环节,查询增强(Query Enhancement)是提升召回精度的关键一步。单纯依赖用户的原始提问进行检索,效果往往不佳。利用工作记忆中的实体、属性等信息对查询进行重写或扩展,能显著提高检索到相关事实的概率。例如,用户问“这家公司的营收增长如何?”,而工作记忆中已有{"company": "Apple Inc.", "year": "2023"},那么增强后的查询可以是“Apple Inc. 2023 fiscal year revenue growth”。

5. 在典型Agent架构中的集成实践

Context Engineering不是一个独立的模块,它需要深度融入Agent的运行循环中。以经典的ReAct(Reasoning + Acting)框架为例,我们可以看看如何改造它。

5.1 改造ReAct循环

标准的ReAct循环是:思考(Thought)-> 行动(Action)-> 观察(Observation)-> … -> 最终答案(Answer)。我们需要在每一步的“思考”之前,动态准备好上下文。

class ContextAwareReActAgent: def __init__(self, llm_client, toolkit, context_manager, knowledge_retriever): self.llm = llm_client self.tools = toolkit self.ctx_manager = context_manager # 综合管理上下文的组件 self.retriever = knowledge_retriever def run(self, user_query: str, max_steps: int = 10): # 1. 初始化上下文 self.ctx_manager.initialize(user_query) for step in range(max_steps): # 2. 为当前步骤动态构建上下文 step_context = self.ctx_manager.build_step_context( step_index=step, available_tools=self.tools.list_tool_names() ) llm_prompt = step_context.assemble_for_llm() # 3. LLM基于精准上下文进行“思考”和决策 llm_response = self.llm.generate(llm_prompt) thought, action = self._parse_react_response(llm_response) # 4. 记录思考过程到工作记忆 self.ctx_manager.working_memory.add_chunk(f"Thought {step}: {thought}") if action["name"] == "FinalAnswer": return action["args"]["answer"] # 5. 执行工具调用 tool_result = self.tools.execute(action["name"], action["args"]) # 6. 将观察结果(工具输出)作为新事实更新到工作记忆 self.ctx_manager.working_memory.update_from_tool_result( action["name"], tool_result ) # 7. 根据新结果,决定是否需要检索外部知识 if self._needs_knowledge_check(thought, tool_result): knowledge_chunks = self.retriever.retrieve_for_query( query=user_query, user_context=self.ctx_manager.working_memory.memory ) self.ctx_manager.external_knowledge.set_chunks(knowledge_chunks) return "达到最大步数限制,任务未完成。"

在这个改造后的循环中,ContextManager成为了核心枢纽。它在每一步build_step_context时,会综合当前任务状态、历史、工作记忆和可能的检索结果,组装出最合适的提示。这使得LLM的每一次“思考”都建立在坚实、相关的事实基础之上。

5.2 与规划器(Planner)的协同

对于更复杂的任务,Agent通常需要一个顶层的规划器(Planner)来分解任务。Context Engineering与规划器的协同至关重要。

  1. 规划器输出作为上下文指南:规划器生成的子任务列表,其本身就应该作为高优先级的上下文信息。例如,规划器输出[“查询天气”, “推荐衣物”, “规划行程”],那么在执行“推荐衣物”步骤时,这个计划就应该在上下文中,让Agent知道自己处于整体计划的哪一环,避免偏离主线。

  2. 上下文反馈优化规划:工作记忆中记录的子任务执行结果(成功/失败,产生了什么数据),可以反馈给规划器,用于动态调整后续计划。这就形成了一个“感知->规划->执行->上下文更新->再规划”的闭环。

6. 高级模式与优化技巧

当基础框架搭建完成后,可以考虑引入一些高级模式来进一步提升效果。

6.1 上下文压缩与摘要

对于长篇幅的会话历史或文档内容,直接放入上下文窗口可能不现实。此时需要压缩技术。

  • 提取式摘要:使用小型模型或规则,从长文本中提取最关键的事实陈述、实体和关系,组成一个浓缩版。例如,将一段500字的用户问题描述,压缩成“用户:张三,问题:笔记本电脑无法开机,已尝试插电和长按电源键。目标:获得故障诊断步骤。”
  • 生成式摘要:让LLM自己总结之前的对话历史。可以在每个回合结束后,自动生成一个“本轮摘要”存入工作记忆,并在后续步骤中用这个摘要替代原始长对话。

注意事项:摘要不可避免地会丢失信息。一个最佳实践是混合存储:保留最近1-2轮完整对话,更早的则用摘要替代。同时,对于工具返回的结构化数据(如JSON),应优先以结构化形式存储在工作记忆的键值对中,而不是将其转换为自然语言摘要,以避免信息失真。

6.2 事实验证与来源标注

为了进一步增强可信度,可以在上下文中明确标注事实的来源。

# 在组装上下文时,为每个信息块添加清晰的来源标签 context_content = """ # 已知事实: 1. [来自用户输入] 用户说:“我的iPhone 15无法充电。” 2. [来自知识库-苹果支持文档] “iPhone 15使用USB-C端口。请检查充电线是否为Apple认证的USB-C线。” 3. [来自工具调用-设备诊断] 系统诊断工具返回:“端口检测到轻微异物堵塞。” """

这种显式的来源标注有两个好处:一是让LLM对不同可信度的信息有所权衡;二是在最终输出给用户时,可以附带来源,增加回答的可信度,例如“根据苹果官方支持文档的建议,您可以先尝试…”。

6.3 上下文缓存与复用

对于多轮对话中重复出现的相似问题,重复进行知识检索和上下文组装是低效的。可以设计一个上下文缓存机制。将组装好的、针对某个意图(Intent)的上下文模板及其对应的LLM出色响应缓存起来。当检测到相似的意图时,可以直接复用或微调缓存中的上下文,快速生成响应,大幅降低延迟和成本。

7. 常见问题、调试与避坑指南

在实际开发中,Context Engineering会面临许多挑战。以下是一些常见问题及解决思路。

7.1 问题:Agent仍然基于过时或错误的事实进行推理

  • 排查点1:工作记忆更新时机。检查工具调用结果是否在Observation阶段被正确、完整地解析并更新到了工作记忆中。一个常见的错误是只存储了成功/失败状态,而丢失了关键的结果数据字段。
  • 排查点2:上下文组装优先级。确认在assemble_for_llm方法中,高优先级的工作记忆块(如最新工具结果)是否排在了前面。如果被大量低优先级的会话历史挤到了Token窗口之外,模型自然就“看”不到它。
  • 排查点3:知识检索的准确性。检查检索查询(Query)是否合理。尝试将LLM“思考”过程中生成的、更精确的中间问题作为检索查询,而不是始终用原始用户问题。

7.2 问题:上下文膨胀导致Token超限或成本过高

  • 策略1:实施严格的“淘汰”策略。为每一类上下文块定义生命周期。例如,工具结果只在后续3步内保持高优先级,之后自动降级或移入历史摘要。
  • 策略2:采用更智能的摘要模型。对于会话历史,不要简单截断,而是使用专门微调过对话摘要的小模型(如GPT-3.5-Turbo)进行压缩,在更小的Token空间内保留更多信息。
  • 策略3:分层上下文窗口。一些先进的框架支持“分层注意力”,将系统指令、关键事实放在模型更易关注的“近端”上下文,将历史细节放在“远端”。可以探索此类模型或库的应用。

7.3 问题:多Agent协作中的上下文混乱

在多个Agent分工协作的场景下,上下文管理更为复杂。每个Agent都有自己的工作记忆,但它们之间需要共享事实。

  • 解决方案:建立共享事实总线(Shared Fact Bus)。设计一个全局的、结构化的存储(如Redis),用于存放被验证过的、需要跨Agent共享的关键事实。每个Agent在需要时从中读取,并在产生新事实时写入。同时,每个Agent仍需维护自己的本地工作记忆,用于处理私有状态。
  • 关键点:事实版本与冲突解决。当两个Agent对同一事实(如“客户满意度评分”)有不同看法时,需要定义冲突解决策略,例如时间戳最新优先、或由一个专用的“协调员Agent”进行仲裁。

7.4 调试技巧:上下文快照与可视化

调试Context问题最有效的方法是“可视化”每一步的上下文。

  • 输出每一步的组装提示:在开发日志中,完整记录每一步assemble_for_llm生成的最终提示文本。这能让你直观地看到模型“看到了什么”。
  • 构建上下文仪表盘:开发一个简单的Web界面,以时间线形式展示每个步骤的上下文构成,用不同颜色区分来源(系统、记忆、工具、知识)。当Agent行为异常时,通过回放时间线,能快速定位是哪个环节的信息出了问题。

Context Engineering是将AI Agent从“玩具”推向“工具”的关键工程实践。它没有银弹,需要你根据具体的任务领域、模型特点和资源约束,精心设计和持续调优。我的体会是,与其追求一个复杂无比的通用上下文管理系统,不如先从解决当前项目中最痛的一两个事实一致性痛点开始,设计一个最小可用的上下文管道,然后在迭代中逐步扩展其能力。记住,目标始终是:让模型在正确的时间,拥有做出正确决策所需的全部正确信息。这个过程本身,就是对智能系统可预测性和可靠性的一场深度修炼。

← 返回列表