大家好,我是程序员小策。
先做个自测------你现在怎么给LLM管理上下文?
A. 全量塞入 --- 对话历史、检索文档、工具结果全拼成一大段文本,有多少塞多少
B. 手动截断 --- 设一个固定轮数N,只保留最近N轮对话,超出直接砍掉
C. 摘要压缩 --- 用LLM对历史对话做一轮摘要,保留"关键信息"再塞给下一轮
D. 分层组装 --- 按Agent角色和任务阶段,按需供给不同层级的上下文
**如果选了A或B,先别急------这两种方案在小Demo上完全够用。**三个问题下来回答准确率很高,一度觉得"上下文管理?不存在的"。
但一旦上了生产------用户连续追问50轮、多Agent协同工作、长文档RAG检索------A和B的硬伤就全暴露了:A导致token爆炸,B导致关键信息丢失。前者浪费钱,后者丢失上下文,LLM的回答质量断崖式下降。
今天聊的就是怎么从A/B走到C/D------Context Engineering的完整工程实践。
一、问题定义:上下文窗口不是无底洞
上下文窗口(Context Window) 是LLM一次能"看到"的最大token数量。看起来很大------GPT-4o有128K,Claude 3有200K------但实际用起来,200K也不够你造的。
为什么?因为每一轮对话,你都在往里面塞东西:系统提示词、对话历史、检索结果、工具调用结果、Agent的中间推理......这些东西像滚雪球一样越滚越大。等到雪球滚到窗口边界,轻则多花几倍的token费用,重则LLM直接报错或者输出质量断崖式下降。
Context Engineering(上下文工程):系统性地管理、组装、压缩、检索注入LLM上下文窗口的所有信息,确保在有限的token预算内,给LLM提供"刚好够用且质量最高"的上下文,而不是"越多越好"。
打个比方。做菜的时候,你不会把冰箱里所有食材都倒进锅里------你会根据菜谱挑选最合适的食材,控制每种食材的量,按顺序下锅。Context Engineering就是给AI做菜的那本菜谱:什么该放、放多少、什么时候放、什么绝对不能放。
二、核心概念:Context Engineering的四个维度
Context Engineering不是单一技术,而是一套组合拳。拆开来看,有四个核心维度:
维度一:上下文窗口管理 --- 控制进入LLM上下文窗口的信息总量,包括滑动窗口截断、token预算分配、优先级排序。
维度二:记忆分层 --- 将"记忆"按生命周期分为短期(会话内消息)、长期(跨会话用户偏好)、工作记忆(当前任务状态),分别存储和检索。
维度三:上下文压缩 --- 在信息进入LLM之前,通过摘要、去重、实体提取等手段减少token体积,同时保留语义信息。
维度四:上下文组装 --- 将系统提示词、对话历史、检索结果、工具输出等不同来源的信息,按优先级和角色拼装成最终prompt。
回到做菜的比喻:窗口管理是"控制总菜量"(做多了吃不完),记忆分层是"食材分门别类存放"(干货放柜子、鲜肉放冰箱),上下文压缩是"食材预处理"(削皮、切块、焯水),上下文组装是"按顺序下锅"(先炒肉再放菜)。
下面我们一个一个看代码实现。
三、策略一:滑动窗口截断------最朴素也最容易被忽略的细节
最直接的方案:限制对话历史的最大条数。只保留最近N轮对话,超出部分直接丢弃。
python
from typing import List, Dict, Any, Optional
from datetime import datetime
from pymongo import MongoClient, ASCENDING
from bson import ObjectId
from dotenv import load_dotenv
import os
import logging
load_dotenv()
class HistoryMongoTool:
"""
MongoDB 历史对话记录读写工具 --- 基于原生 PyMongo 实现
核心功能:封装MongoDB的连接、集合初始化、索引创建,
为上层提供统一的上下文存取入口
"""
def __init__(self):
self.mongo_url = os.getenv("MONGO_URL")
self.db_name = os.getenv("MONGO_DB_NAME")
self.client = MongoClient(self.mongo_url)
self.db = self.client[self.db_name]
self.chat_message = self.db["chat_message"]
# 核心索引:session_id + 时间戳降序
# 为什么用复合索引?因为"按会话查最近N条"是上下文管理的最高频查询
self.chat_message.create_index([("session_id", 1), ("ts", -1)])
logging.info(f"Successfully connected to MongoDB: {self.db_name}")
# 单例模式:全局只有一个数据库连接,避免反复握手
_history_mongo_tool = None
def get_history_mongo_tool() -> HistoryMongoTool:
global _history_mongo_tool
if _history_mongo_tool is None:
_history_mongo_tool = HistoryMongoTool()
return _history_mongo_tool
def get_recent_messages(session_id: str, limit: int = 10) -> List[Dict[str, Any]]:
"""
查询指定会话的最近N条对话记录,返回原始字典格式
结果按时间正序排列,可直接喂给LLM作为上下文
为什么按时间正序?因为LLM阅读上下文的顺序是从旧到新,
而MongoDB的索引是降序的(ts:-1),所以这里用 ASCENDING 反转
"""
mongo_tool = get_history_mongo_tool()
try:
query = {"session_id": session_id}
cursor = mongo_tool.chat_message.find(query) \
.sort("ts", ASCENDING) \
.limit(limit)
return list(cursor)
except Exception as e:
logging.error(f"Error getting recent messages: {e}")
return []
def save_chat_message(
session_id: str,
role: str,
text: str,
message_id: Optional[str] = None
) -> str:
"""
写入/更新单条会话记录到MongoDB
无message_id时新增,有message_id时更新(幂等写入)
"""
ts = datetime.now().timestamp()
document = {
"session_id": session_id,
"role": role,
"text": text,
"ts": ts
}
mongo_tool = get_history_mongo_tool()
if message_id:
mongo_tool.chat_message.update_one(
{"_id": ObjectId(message_id)},
{"$set": document}
)
return message_id
else:
result = mongo_tool.chat_message.insert_one(document)
return str(result.inserted_id)
这段代码解决了一个基础问题:存消息、取最近N条。但仅靠滑动窗口,问题远没解决------N设多大?设小了关键信息丢失,设大了token照样爆炸。
四、策略二:记忆分层------把"记忆"分成三个抽屉
滑动窗口只管了"量",没管"质"。把用户在第3轮说的"我喜欢用Python"和在第18轮说的"帮我查一下今天天气"同等对待------前者是用户偏好,应该长期记住;后者是临时查询,会话结束就可以忘掉。
这就是记忆分层要做的事。
Mem0(mem0ai/mem0,GitHub 20K+ stars,Y Combinator S24项目)是这方面做得最成熟的开源方案。它把记忆分成三层:
- User Memory:跨会话的长期记忆,比如用户偏好、个人信息
- Session Memory:当前会话的短期记忆,对话结束即失效
- Agent Memory:Agent自身的知识和工作状态
核心代码逻辑:
python
from mem0 import Memory
from openai import OpenAI
# 初始化Mem0(底层用向量数据库存储记忆,支持语义检索)
openai_client = OpenAI()
memory = Memory()
def chat_with_memories(message: str, user_id: str = "default_user") -> str:
"""
带记忆检索的对话函数 --- 核心上下文工程模式:
1. 根据用户输入,从长期记忆中检索相关记忆
2. 将检索到的记忆注入system prompt
3. 对话结束后,从对话中自动提取新的记忆存储
为什么用system prompt而不是user prompt放记忆?
system prompt的优先级更高,LLM更倾向于遵守其中的指令,
把用户偏好放在system prompt中能更好地影响LLM的行为
"""
# 第一步:检索相关记忆(语义搜索 + BM25关键词 + 实体链接多路融合)
relevant_memories = memory.search(
query=message,
filters={"user_id": user_id},
top_k=3 # 只取Top-3,避免记忆膨胀
)
memories_str = "\n".join(
f"- {entry['memory']}" for entry in relevant_memories["results"]
)
# 第二步:将记忆注入system prompt,作为上下文的一部分
system_prompt = (
f"You are a helpful AI. Answer the question based on query and memories.\n"
f"User Memories:\n{memories_str}"
)
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": message}
]
# 第三步:调用LLM
response = openai_client.chat.completions.create(
model="gpt-4o-mini",
messages=messages
)
assistant_response = response.choices[0].message.content
# 第四步:从本轮对话中提取新记忆,增量存储
# 为什么用ADD而不是UPDATE?避免覆盖旧记忆,保持记忆的累积性
messages.append({"role": "assistant", "content": assistant_response})
memory.add(messages, user_id=user_id)
return assistant_response
Mem0的检索不是简单的向量搜索,而是多信号融合:语义相似度 + BM25关键词匹配 + 实体链接,三种信号并行打分后融合排序。这意味着"我喜欢Python"和"我用Python写了三年后端"能被正确关联------尽管字面上没有公共关键词,但语义上高度相关。
五、策略三:上下文压缩------把3000字文档压成300字精华
RAG场景中,最经典的浪费是:检索返回了10篇文档,每篇3000字,你全塞进了prompt。3万字的上下文,用户的问题可能只跟其中500字有关。
上下文压缩解决的就是这个问题:在信息进入LLM之前,先做一轮"瘦身"。
这里给出一个实际项目中的上下文压缩实现,来自知识库Agent的生产代码:
python
import tiktoken
from typing import List, Dict, Any
def build_context_aware_prompt(
query: str,
history: List[Dict[str, Any]],
retrieved_docs: List[Dict[str, Any]],
max_tokens: int = 8000,
system_prompt: str = ""
) -> List[Dict[str, str]]:
"""
上下文感知的prompt构建器 --- 核心上下文工程入口
三层策略:
1. token预算分配:system_prompt固定配额,剩余的在history和docs之间动态分配
2. 历史截断:从最近一轮开始往前取,直到token预算用完
3. 文档截断:按相关性排序,优先保留高相关文档的完整内容
为什么不是简单的"各取一半"?
因为不同场景下history和docs的权重不同------
追问场景history权重高,首问场景docs权重高。
"""
encoding = tiktoken.get_encoding("cl100k_base")
# 第一步:计算system_prompt的token占用
system_tokens = len(encoding.encode(system_prompt))
remaining_budget = max_tokens - system_tokens - 500 # 预留500给输出
# 第二步:截断历史消息,从最近开始往前取
history_messages = []
history_token_count = 0
history_budget = int(remaining_budget * 0.4) # 历史占40%预算
for msg in reversed(history):
msg_text = f"{msg['role']}: {msg['text']}"
msg_tokens = len(encoding.encode(msg_text))
if history_token_count + msg_tokens > history_budget:
break
history_messages.insert(0, msg_text)
history_token_count += msg_tokens
# 第三步:截断检索文档,按相关性排序截断
doc_budget = remaining_budget - history_token_count
doc_contexts = []
doc_token_count = 0
for doc in retrieved_docs:
doc_text = f"[来源: {doc.get('source', 'unknown')}]\n{doc['content']}"
doc_tokens = len(encoding.encode(doc_text))
if doc_token_count + doc_tokens > doc_budget:
# 预算不够了,截断当前文档
remaining = doc_budget - doc_token_count
if remaining > 100: # 至少保留100个token才有意义
truncated = encoding.decode(
encoding.encode(doc_text)[:remaining]
)
doc_contexts.append(truncated + "\n...[已截断]")
break
doc_contexts.append(doc_text)
doc_token_count += doc_tokens
# 第四步:组装最终prompt
messages = [{"role": "system", "content": system_prompt}]
context_parts = []
if doc_contexts:
context_parts.append("【参考资料】\n" + "\n---\n".join(doc_contexts))
if history_messages:
context_parts.append("【对话历史】\n" + "\n".join(history_messages))
user_context = "\n\n".join(context_parts)
messages.append({
"role": "user",
"content": f"{user_context}\n\n【当前问题】\n{query}"
})
return messages
关键设计决策:历史占40%预算、文档占60%不是拍脑袋的------这是在生产环境的A/B测试中验证过的。历史占比太高,回答会过度依赖对话上下文而忽略知识库;文档占比太高,回答会像"百科朗读"一样缺乏对话感。
六、策略四:上下文分层装配------小说多Agent系统的真实案例
前面三种策略都是"对话系统"场景。但如果你面对的是多Agent协作场景,上下文工程的复杂度会上升一个数量级。
拿一个真实的小说创作Agent系统(LoreSmith项目)来举例。这个系统有多个Agent:架构师Agent负责大纲规划、作家Agent负责章节写作、评审Agent负责质量把控。每个Agent需要的上下文完全不同:
python
from dataclasses import asdict
from typing import Any, Dict
class NovelContextTool:
"""
小说上下文工具 --- 多Agent场景下的上下文分层组装
核心设计思想:
不同阶段的Agent需要不同层级的上下文,
不是"把所有信息给所有Agent",而是"按需供给"
- chapter <= 0: 架构师级别 --- 只需要故事前提、大纲、人物列表
- chapter > 0: 作家级别 --- 额外需要最近章节摘要、时间线、活跃伏笔
"""
def __init__(self, store, style: str = "default") -> None:
self.store = store
self.style = style
def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
chapter = int(args.get("chapter", 0) or 0)
result: Dict[str, Any] = {}
# 所有Agent共享的基础上下文
progress = self.store.progress.load()
result["progress_status"] = {
"phase": progress.phase,
"flow": progress.flow,
"next_chapter": progress.next_chapter(),
"pending_rewrites": progress.pending_rewrites,
}
# 故事前提:所有Agent都需要
premise = self.store.outline.load_premise()
if premise:
result["premise"] = premise
# 人物列表:所有Agent都需要
chars = self.store.characters.load()
if chars:
result["characters"] = [asdict(x) for x in chars]
# 世界规则:所有Agent都需要
rules = self.store.world.load_world_rules()
if rules:
result["world_rules"] = [asdict(x) for x in rules]
# 写作风格参考
result["style_reference"] = self._load_style_text()
# 章节级Agent才有额外上下文
if chapter > 0:
# 最近章节摘要:避免上下文爆炸,只给摘要不给全文
recent_summaries = self.store.summaries.load_recent(
chapter, limit=3
)
if recent_summaries:
result["recent_summaries"] = recent_summaries
# 时间线:当前章节之前的关键事件
timeline = self.store.timeline.load_before(chapter)
if timeline:
result["timeline"] = timeline
# 活跃伏笔:哪些伏笔还没回收
foreshadows = self.store.foreshadow.load_active()
if foreshadows:
result["foreshadow_ledger"] = foreshadows
# 人物关系状态:当前章节的人物关系快照
relationships = self.store.relationships.load()
if relationships:
result["relationship_state"] = relationships
return result
这个设计的核心洞察:上下文不是"越多越好",而是"越精准越好"。架构师Agent不需要知道上一章的具体描写措辞,它只需要知道故事进展到哪了、人物状态如何。作家Agent才需要具体的细节上下文。
这就是上下文工程在多Agent场景中的核心价值:按角色分层、按需供给、避免信息过载。
七、边界与陷阱:四个最常见的翻车场景
看起来很完美了对吧?但实际落地中,以下几个坑踩一个就够你喝一壶的。
陷阱一:滑动窗口截断了关键信息。 用户在第1轮说"我的名字叫张三",你设了N=10,第11轮N=10的窗口把第1轮丢了。LLM从此不知道用户叫什么。
解法:关键信息不要只放在对话历史里。 用Mem0这类记忆层单独存储用户画像、偏好、关键事实,不受滑动窗口限制。
陷阱二:Token计数不准确。 你用字符数估算token,但中英文的token密度完全不同------一个中文字约1.5-2个token,一个英文单词约1-1.5个token。用字符数估算,中文场景可能偏差50%以上。
解法:必须用模型对应的tokenizer做精确计数。 tiktoken库是最常用的选择,但要注意不同模型用的编码器不同------GPT-4用cl100k_base,GPT-3.5用p50k_base。
陷阱三:上下文压缩丢失了关键信息。 你对检索文档做了摘要压缩,但摘要把"合同金额500万"压缩成了"涉及一笔大额交易"------500万和大额交易在法律场景中是完全不同的概念。
解法:对结构化数据(数字、日期、名称)不做压缩,只压缩描述性文本。 或者用LLMLingua这类"感知压缩"工具,它知道哪些词对LLM更重要。
陷阱四:多Agent上下文中,Agent A的中间输出被Agent B误解。 Agent A说"人物状态:愤怒",Agent B理解成"角色应该暴怒攻击",但实际只是"内心不满"。
解法:Agent间的上下文传递要有明确的schema定义。 不要传自然语言,传结构化数据------{"emotion": "anger", "intensity": 3, "target": "self"} 比"他很生气"精确得多。
八、项目实战:在知识库Agent中完整落地Context Engineering
说完了理论,我们来看一个完整落地的案例。这个项目是一个日均处理500+次问答的智能知识库Agent,我在其中完整落地了上述四种策略。
项目背景:用户上传技术文档,Agent基于文档内容回答用户问题。核心挑战是文档可能很长(一篇能有2万字),用户可能连续追问(一个会话50+轮),上下文窗口很容易被打爆。
落地策略组合:
| 策略 | 在这个项目中的落地方式 | 关键参数 |
|---|---|---|
| 滑动窗口 | MongoDB存储,按session_id+ts索引,limit动态调整 | 初始limit=20,token超预算时自动降为10 |
| 记忆分层 | 用户偏好存Redis(T K=7天),会话历史存MongoDB(T K=24小时) | 偏好记忆最多5条,会话历史最多50条 |
| 上下文压缩 | 检索文档先过Reranker排序,取Top-3,每篇截断到1500 token | 文档预算占60%,历史预算占40% |
| 上下文组装 | System prompt设置系统角色 + 用户偏好,User prompt拼接文档 + 历史 + 当前问题 | 总token预算8000,预留500给LLM输出 |
核心代码:Token预算驱动的上下文组装器
python
import tiktoken
from typing import List, Dict, Any, Optional
class ContextAssembler:
"""
上下文组装器 --- 生产级上下文工程的核心入口
设计原则:
1. Token预算优先:先算账再装货,不是装了再算
2. 优先级排序:system > 用户偏好 > 最新文档 > 历史 > 旧文档
3. 优雅降级:预算不够时,逐级缩减而非直接报错
"""
def __init__(
self,
model: str = "gpt-4o",
max_tokens: int = 8000,
output_reserve: int = 500
):
self.encoding = tiktoken.encoding_for_model(model)
self.max_tokens = max_tokens
self.output_reserve = output_reserve
def assemble(
self,
system_prompt: str,
user_preferences: Optional[List[str]] = None,
retrieved_docs: Optional[List[Dict[str, Any]]] = None,
history: Optional[List[Dict[str, Any]]] = None,
query: str = ""
) -> List[Dict[str, str]]:
budget = self.max_tokens - self.output_reserve
# 1. System prompt:最高优先级,全部保留
system_block = system_prompt
budget -= self._count(system_block)
# 2. 用户偏好:次高优先级,最多5条
pref_block = ""
if user_preferences:
pref_lines = [f"- {p}" for p in user_preferences[:5]]
pref_block = "【用户偏好】\n" + "\n".join(pref_lines)
budget -= self._count(pref_block)
# 3. 检索文档:按Reranker打分排序,截断填充
doc_block, budget = self._fill_docs(retrieved_docs or [], budget)
# 4. 对话历史:剩余预算全部给历史
history_block, budget = self._fill_history(history or [], budget)
# 5. 组装
messages = [{"role": "system", "content": system_block}]
user_content = "\n\n".join(
b for b in [pref_block, doc_block, history_block,
f"【当前问题】\n{query}"] if b
)
messages.append({"role": "user", "content": user_content})
return messages
def _count(self, text: str) -> int:
return len(self.encoding.encode(text))
def _fill_docs(
self, docs: List[Dict[str, Any]], budget: int
):
"""按优先级填充文档,预算不够时截断"""
parts = []
for doc in docs[:3]: # 最多3篇文档
text = f"[{doc.get('source', 'doc')}]\n{doc['content']}"
tokens = self._count(text)
if tokens <= budget:
parts.append(text)
budget -= tokens
elif budget > 100:
truncated = self.encoding.decode(
self.encoding.encode(text)[:budget]
)
parts.append(truncated + "\n...[截断]")
budget = 0
break
else:
break
return "\n---\n".join(parts) if parts else "", budget
def _fill_history(
self, history: List[Dict[str, Any]], budget: int
):
"""从最近开始往前填充历史"""
parts = []
for msg in reversed(history):
text = f"{msg['role']}: {msg['text']}"
tokens = self._count(text)
if tokens <= budget and budget > 0:
parts.insert(0, text)
budget -= tokens
else:
break
return "\n".join(parts) if parts else "", budget
# 使用示例
if __name__ == "__main__":
assembler = ContextAssembler(model="gpt-4o", max_tokens=8000)
messages = assembler.assemble(
system_prompt="你是一个专业的技术文档问答助手。",
user_preferences=["用户偏好Python", "用户是后端开发"],
retrieved_docs=[
{"content": "FastAPI是一个现代Web框架...(省略3000字)", "source": "fastapi_doc"},
{"content": "Pydantic用于数据校验...(省略2000字)", "source": "pydantic_doc"},
],
history=[
{"role": "user", "text": "什么是FastAPI?"},
{"role": "assistant", "text": "FastAPI是一个Python Web框架..."},
],
query="那它和Django有什么区别?"
)
total_tokens = sum(
len(assembler.encoding.encode(m["content"])) for m in messages
)
print(f"总token数: {total_tokens} / {assembler.max_tokens}")
实际落地中踩到的坑:
- Reranker排序不稳定:同一篇文档在不同query下得分波动很大。解决方案是加一层缓存------同一文档的Reranker分数缓存5分钟,减少重复计算。
- Token计数本身也有开销:每次请求都调用tiktoken编码,在高并发下CPU占用明显。解决方案是预计算文档的token数并存入数据库,组装时只做累加。
- 用户偏好和系统指令冲突:用户偏好说"给我简短回答",但系统指令要求"详细解释技术原理"。解决方案是优先级分层------用户偏好影响风格但不覆盖系统指令的核心约束。
九、方案对比
| 策略 | 核心思路 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 滑动窗口截断 | 只保留最近N轮对话 | 实现简单,零额外开销 | 丢失早期关键信息,N值难确定 | 短会话、简单问答 |
| 记忆分层 | 按生命周期分短期/长期/工作记忆,独立存储 | 长期记忆不受窗口限制,语义检索精准 | 需要额外的存储和检索基础设施 | 需要个性化、跨会话记忆的场景 |
| 上下文压缩 | 摘要、去重、实体提取,减少token体积 | 大幅降低token消耗,节省成本 | 可能丢失关键细节,压缩本身消耗token | 长文档、RAG检索结果处理 |
| 分层上下文组装 | 按Agent角色和任务阶段,按需供给不同层级的上下文 | 精准供给,避免信息过载 | 设计复杂度高,需要深入理解业务流程 | 多Agent协作、复杂业务流程 |
选择策略 :小项目从滑动窗口起步,有跨会话记忆需求时引入Mem0做记忆分层,RAG场景必然要加上下文压缩,多Agent系统必须做分层上下文组装。四种策略不是互斥的,而是叠加使用的。
十、面试追问
面试追问1:Token预算分配中,为什么历史占40%而不是50%?这个比例是怎么确定的?
回答方向:这不是拍脑袋的。在生产环境中通过A/B测试验证------同一个问题集,分别用30/70、40/60、50/50的比例测试回答质量(用LLM-as-Judge打分),40/60在"信息完整性"和"对话连贯性"之间取得了最佳平衡。不同场景的最优比例不同,需要根据自己的业务数据做测试。
面试追问2:如果用户的上下文需求超过了模型的最大窗口(比如需要处理50万字的文档),除了截断还有什么办法?
回答方向:三种思路------①分块处理(Map-Reduce模式:把文档分成多个块,每块独立生成摘要,再汇总摘要生成最终答案);②RAG+Agentic检索(不让LLM直接读全文,而是让Agent自主决定检索哪些片段);③用支持更长上下文的模型(如Gemini 2.5 Pro的100万token窗口),但要注意长上下文下LLM的"中间丢失"问题(Lost in the Middle)。
面试追问3:Mem0的ADD-only记忆策略(只增不删不改)有什么缺陷?
回答方向:会导致记忆冗余------用户说"我喜欢Python",后来又改成"我现在更喜欢Go",ADD-only会在记忆库中同时保留两条矛盾信息。Mem0的解法是通过检索时的语义排序让最新记忆排前面,但本质上没有解决冲突。更好的做法是加入"记忆时效衰减"机制------旧记忆的检索权重随时间降低。
面试追问4:在多Agent场景中,如果Agent A的输出上下文很大(比如生成了一章5000字的小说),传给Agent B时怎么处理?
回答方向:不传全文,传摘要+关键状态。比如作家Agent写完一章后,不把整章传给评审Agent,而是传"章节摘要(500字)+ 关键指标(人物出场次数、情节推进点、伏笔回收情况)"。评审Agent基于摘要做质量判断,发现具体问题时再请求原文片段------这是"按需拉取"而不是"全量推送"。
总结
Context Engineering的本质不是"塞更多",而是"塞更准"。
读完这篇你应该能:自己实现一个带token预算管理的上下文组装器、理解记忆分层(短期/长期/工作记忆)的区别和落地方式、在RAG场景中做上下文压缩而非全量塞入、在多Agent系统中设计按角色分层的上下文供给策略。
下一步,建议你去看Mem0的源码,特别是它的add和search方法------理解它是怎么用LLM自动从对话中提取结构化记忆的,这是Context Engineering从"人工规则"走向"智能管理"的关键一步。
mem0ai/mem0 --- GitHub 20K+ stars,Apache 2.0 协议,生产可用。