Context工程:从全量塞入到智能记忆管理

大家好,我是程序员小策。

先做个自测------你现在怎么给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}")

实际落地中踩到的坑

  1. Reranker排序不稳定:同一篇文档在不同query下得分波动很大。解决方案是加一层缓存------同一文档的Reranker分数缓存5分钟,减少重复计算。
  2. Token计数本身也有开销:每次请求都调用tiktoken编码,在高并发下CPU占用明显。解决方案是预计算文档的token数并存入数据库,组装时只做累加。
  3. 用户偏好和系统指令冲突:用户偏好说"给我简短回答",但系统指令要求"详细解释技术原理"。解决方案是优先级分层------用户偏好影响风格但不覆盖系统指令的核心约束。

九、方案对比

策略 核心思路 优点 缺点 适用场景
滑动窗口截断 只保留最近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的源码,特别是它的addsearch方法------理解它是怎么用LLM自动从对话中提取结构化记忆的,这是Context Engineering从"人工规则"走向"智能管理"的关键一步。

mem0ai/mem0 --- GitHub 20K+ stars,Apache 2.0 协议,生产可用。