先把话说在前面我自己踩过一个特别典型的坑。半年前我做了一个带读网页、查资料、自动写总结这类工具的 Agent丢进长时间会话里跑前 10 轮表现惊为天人说什么都能接住。可跑到第 25 轮左右它开始记错用户偏好甚至把一个已经完成的任务又原封不动执行了一遍。当时我第一反应是“模型智力不行”后来把对话日志一帧一帧拉出来看才意识到问题根本不在模型而是上下文工程没做对。AI Agent 的“越聊越笨”绝大多数时候不是模型变蠢了而是喂给模型的上下文出了问题。这个话题我观察了很久发现凡是做 Agent 落地的人最容易忽略、也最喜欢甩锅给模型的恰恰就是这一层。我不打算讲模型选型也不聊训练只聊上下文工程里最关键的 5 个取舍。每个取舍背后都有代价没有标准答案只有适合当前场景的答案。适合正在做 AI Agent 搭建、天天纠结 token 预算、想让 Agent 稳定跑完长任务的开发者参考看完可以直接拿这些取舍逻辑回自己项目里做决策。1. 先搞清楚Agent 是怎么“越聊越笨”的1.1 上下文窗口不是硬盘是白板很多人把上下文窗口理解成“记忆硬盘”以为窗口越大记住的东西就越多。这个理解是错的。更准确的类比是一块白板刚开始是干净的你往上写流程、写需求模型看得清清楚楚写到后面白板不够用了要么新的内容把旧的盖上要么你手动擦掉一部分。模型每次推理时能看到的只有当前白板上的东西而不是整个对话历史。这解释了一个怪现象同一个模型你在对话框里问它简单问题它聪明得很丢进 Agent 里跑长任务它却像个鱼一样只有 7 秒记忆。不是模型变了是它眼前的白板被写满了而且写上去的内容里大部分是没有用的工具输出、中间思考过程、重复日志。更麻烦的是白板上的字迹不是越新越重要。用户的原始约束可能写在第 3 轮到第 40 轮时已经被挤到边角甚至擦掉了模型自然只能靠猜。猜的结果就是“一本正经地胡说八道”。1.2 三种常见的衰减机制我把长期跑 Agent 观察到的“变笨”过程归纳成三种机制理解了它们再看后面的 5 个取舍就顺了。第一种是截断挤压。上下文窗口有硬上限一旦达到上限最常见的处理方式是“先进先出”最早的内容被踢出去。早期用户说过的偏好、业务规则、禁止事项往往就这么被悄悄挤掉了。等后面某个步骤需要用到这些信息时模型拿不到只能自己脑补一补就偏。第二种是信噪比劣化。token 总量有限塞进来的内容里 80% 都是工具返回结果、日志、重复的系统提示真正关键的指令只占很小一块。大家知道 Transformer 的注意力机制是“一视同仁”地扫描所有 token 的信息密度低了关键细节被淹没在噪声里模型抓错重点的概率大幅上升。第三种是错误级联。模型基于残缺上下文做出一步错误推测这个推测会被当成新的“事实”写进后续上下文后面的步骤全部建立在这个错误前提上。你以为它只是犯了一个错实际上它是沿着错误一路狂奔越聊越理直气壮。2. 取舍一上下文窗口留多长才算“够用又不浪费”2.1 窗口越大越聪明别被参数带偏市面上常见模型的上下文窗口从 8K、32K、128K 到 200K 都有。不少团队选模型时追着大窗口跑心想“一次塞进去不会错”。但大窗口是有代价的而且代价还不小首先是 token 费用。调用模型是按 token 计费的上下文越长每次调用的成本越高。一个 Agent 完成任务通常要调用十几二十次模型上下文从 4K 涨到 8K总成本直接翻倍。我见过一个自动化运营的 Agent 项目上线一周成本爆掉原因就是每次调用都把全部历史带上了。其次是延迟。上下文长度直接影响推理速度输入 token 翻倍单次响应时间可能从 1 秒涨到 3 秒以上。用户感受是明显的“变卡了”。再一个是注意力稀释。实验做多了你会发现同样一个复杂指令放在 4K 上下文里执行正确率明显高于塞在 20K 上下文中。上下文越长模型越容易漏掉埋在中段的关键信息。所以“大窗口”只是给了你更多操作空间不等于你应该每次都用满。2.2 用 token 预算倒推窗口大小我的习惯是每个任务先算 token 预算再定窗口策略而不是反过来。举个例子。假设你的 Agent 要做多轮信息收集和写作单轮消息大约是系统指令 1,000 token用户输入 200 token工具返回结果平均 600 token模型回复 400 token。一轮完整交互大概 2,200 token。如果在一次会话里要跑 50 轮全量保留就是 110K token已经顶到主流窗口的上限还没给你留任何缓冲。这时候我会把预算拆成几块内容区块预算占比说明系统级指令10%角色、输出格式、工具使用规则长期事实区15%用户偏好、业务约束、项目背景近期对话缓存40%最近 5~10 轮原文滑动窗口工具结果区25%最近任务的必要返回其余摘要检索增强区10%按需检索到的历史片段如果上下文窗口是 128K我通常会把硬上限设成 96K留 25% 余量给突发情况。达到 80% 用量时就触发压缩或归档而不是等窗口塞满了才处理。这个比例没有标准答案但先算清楚每一轮大概消耗多少 token再决定“最多保留几轮原文”会让你对窗口使用有掌控感而不是两眼一抹黑。3. 取舍二原文一字不差还是压缩后保留3.1 原文保留的最大价值是“不失真”要我说原文是金摘要只是铜。原文不会编造细节用户第 12 轮说过“报告里不要出现百分比”原文里就是这句话模型读了几十轮之后依然能看到原话准确率高得吓人。但原文的代价也很明显太占地方而且充满噪声。工具返回个 JSON一次可能就 2000 token里面真正有用的可能就一个字段。这两千 token 既挤占了窗口又稀释了注意力。所以一味留原文长任务一定是死路一条除非你的任务只有三五轮。3.2 摘要压缩为什么常常压不准既然原文太贵很多人自然想到“用模型把历史对话总结一下”。这个方向没错但直接做会踩三个坑。第一个坑是细节丢失。你把 10 轮对话压成 200 字模型很可能只提取了“主要意图”但把用户提过的精确数字、明确禁止项、指定格式全压没了。摘要适合保留“发生过什么”但不擅长保留“必须怎么做”。第二个坑是编造。让模型总结时如果原文里信息不足或表述模糊模型会自动补全合理但从未出现的细节。这个后果比截断还可怕——截断只是丢失信息摘要可能产生虚假信息。虚假信息一旦进入上下文后面所有推理都建立在沙地上。第三个坑是级联失真。为了控制长度你可能会对摘要再做摘要。摘要的摘要误差会逐级放大最后得到的内容可能只剩一句“用户要求写个报告”所有细节全没了。3.3 三级混合策略原文和摘要各司其职我现在的做法是分层而不是二选一。第一层是硬性约束区。用户偏好、业务红线、输出格式、关键参数这些不可压缩、不可改写永远以原文形式常驻上下文。你可以把这些内容当作 Agent 的“宪法”摘要可以不提但宪法必须原样保留。第二层是近期对话区。最近 5~10 轮的对话保留原文窗口滑动保证模型对当前正在做的事有完整、准确的理解。这个区域直接对接“当前任务”失真成本最高不值得省。第三层是历史记忆区。更早的内容做结构性摘要而且摘要必须带模板。我做总结时会强制要求输出四个字段已完成动作、还未完成的事、不可改变的约束、不确定/存疑的地方。有了这四个字段即便历史再长模型也能快速知道“哪些事不能动、哪些事还没做”而不是只记得一句笼统的概括。4. 取舍三短期会话记忆还是长期外部记忆4.1 上下文窗口再大也装不下 Agent 的“一生”如果 Agent 只在一个会话里工作前面说的窗口策略够用了。但很多 Agent 是要长期跑的今天你跟它说“以后所有报告都用简体字”明天它换个会话帮你干活这句约束如果只存在于上一个会话的上下文里它就等于从没存在过。所以必须把上下文划分为两层短期会话记忆和长期外部记忆。短期记忆是当前会话中的上下文窗口相当于工作台长期记忆是存放在外部存储的事实相当于你的档案柜。工作台随时可以清空档案柜要一直保留。这个区分是 Agent 能“越来越懂你”而不是“越来越笨”的前提。4.2 第二大脑的存储选型向量库、KV 存储、还是文件长期外部记忆不是摆个数据库就行要根据数据类型选存储我的经验分三种语义检索型向量数据库适合用户没给明确关键词、需要“相关内容联想”的场景。比如 Agent 写周报时需要从过去 30 天的对话里找出和“项目风险”相关的段落这时候向量检索最合适。但要注意向量相似度不等于事实正确检索结果必须经过一道验证不能让模型直接采信。精确检索型KV 数据库或关系型数据库适合用户 ID、偏好开关、业务参数这类结构化事实。比如“用户喜欢繁体/简体”“输出格式必须为 Markdown”“禁止触达某个频道”。这些数据用结构化字段存读取快、不漂移、不会产生歧义可靠性远超向量方案。原始日志型文件或消息队列适合保存全部原始对话和工具结果。不直接注入上下文主要用来事后回放、排查问题。我排查 Agent 变笨时90% 的线索都来自这里的原始记录。顺便说一句现在“基于 Rust 写 AI Agent”讨论度很高有些团队甚至用 Rust 重写 Agent 引擎就是为了让记忆检索、调度链路快一个数量级。这其实说明一个问题决定 Agent 长期能力的恰恰是这层外部记忆和调度架构而不是模型窗口本身。4.3 记忆同步的正确时机外部记忆不是每个回合都写。高频写入会导致两个问题一是调用成本爆增每个回合你都要多调一次模型做“记忆抽取”二是写入噪声太多把有价值的事实淹没在碎片里。我一般用两个触发条件。第一个是时间条件每完成一个独立子任务或每 5 轮做一次记忆抽取。第二个是异常条件检测到用户表达了新偏好、修改了旧要求、或者给出了明确的禁止/允许指令立刻写入外部存储。抽取的内容也不是“总结全部”而是抽“用户画像的变化、业务约束的增减、未完成任务的更新”。这样档案柜里存的东西大部分是有用的不是流水账。会话启动时再从外部存储按需加载本会话需要的长期事实。加载多少可以自己控制全量加载一定会爆窗口只加载与当前任务语义相关的部分即可。一句话总结写入要克制读取要精准。5. 取舍四按时间顺序全给还是按相关性捡重点5.1 顺序保留是“流水账”相关性检索是“索引卡片”默认情况下把历史对话按时间排列喂给模型是最省事的。但这在整个上下文比较长的时候很有问题流水账里什么都有有用信息被自身淹没。模型需要自己从一堆无关内容里“捞针”捞不到就是变笨。另一种思路是不关心顺序只关心相关性。把上下文当作一个检索系统每次请求时根据当前用户问题去外部记忆里捞出最相关的 3~5 个片段再组装进上下文。这个思路接近 RAG也是很多 Agent 框架的默认选项。但这里有个很容易翻车的地方相关性排序和“任务重要性”不是一回事。用户第 3 轮说过“这份合同必须在一小时内发出”这一条在语义上和 40 轮后的“把合同发给我”高度相关但按时间线它早被冲掉了如果你只按“最近消息”截取这条关键约束就没了。所以我建议不要把顺序和检索对立起来而是混合使用近期对话用顺序窗口保真历史信息用相关性检索补全。5.2 调整 prompt 段落位置通常可以显著提高召回除了用什么还要注意放在哪。我观察到一个反复出现的现象把关键信息放在上下文的开头和结尾模型响应相关指令的准确率明显高于放中间。这也符合对注意力机制的理解开头有“首位效应”结尾有“近因效应”中间的内容最容易互相覆盖、被忽略。实操时我会把最终注入的上下文排成这样的顺序固定身份角色在最前本次任务的直接指令和约束靠后紧挨着用户消息历史和检索片段夹在中间。这样模型每次开口前最先看到身份、最后看到当前任务中间是参考资料。如果你有“用户明确要求 XYZ”一定要把它放在靠近末尾的位置别让它躺在历史摘要的中段。5.3 检索粒度与触发时机怎么才算“刚刚好”检索粒度太粗等于没检索所有内容都进来窗口照样爆粒度太细每句话都要检索一次模型疯狂重复引用同一批内容也是浪费。我的经验是把检索的触发时机定在三个节点用户新消息进入时、工具返回结果后、子任务状态切换时。每次检索的目标不是“覆盖全部历史”而是“回答当前这一步最需要知道的信息”。额外给一个避坑建议检索结果不要“提头去尾”直接塞进上下文最好在每段前后加一个明确的开始和结束标记比如memory…/memory或者简单的分隔线。这样模型能识别出“这部分是外部引用”而不是自己的推理结论减少把检索内容误当事实的机率。这个细节非常小但排查时你会发现很多“胡说八道”的根源就是模型把检索到的片段和真实结论混在一起了。6. 取舍五系统提示词往重了写还是往轻了写6.1 系统提示词绝不是越长越好很多团队都有这个执念把所有规则、行业术语、工具说明全塞进 system prompt恨不得写五千字。结果就是 token 预算被吃掉一大块同时模型要同时“遵守”几十条规则规则一多互相冲突或者被忽略的概率急剧上升。你把 30 条规则塞给它它真正内化的可能只有前几条和最后几条中间的规则全成了摆设。我并不是说 system prompt 应该越短越好太短也不行。角色信息缺失、工具调用格式不定义清楚模型会自行发挥Agent 的稳定性立马被击穿。正确的方向不是“重”或“轻”而是“分层动态组装”。6.2 动态组装让系统指令在正确的时间出现在正确的位置我把系统提示拆成三层静态层真正不变的内容比如“你是文档助手”“输出用 Markdown”“调用工具时必须填写必填参数”。这些内容全项目共享写一次数量控制在几百 token 以内。动态层每次请求时根据当前任务临时拼装包括本次任务目标、可用的工具列表、用户当前身份与画像、本次需要遵守的特别约束。比如用户本次指定“只要结论不要分析”那就把这条临时约束放在最靠后的位置紧贴用户消息。上下文层历史摘要和检索片段按前文说的排列规则注入。一个可参考的伪代码结构长这样def build_prompt(session, user_message, memories): # 1) 固定身份与全局规则放在开头 blocks [session.global_rules] # 2) 本次任务动态指令 blocks.append(session.get_current_task_instruction()) # 3) 外部记忆与历史摘要放在中间 blocks.append(memories.retrieve(user_message)) # 4) 近期对话原文 blocks.append(session.recent_messages(6)) # 5) 用户当前消息与“最终指令”放最后 blocks.append(user_message) return join(blocks)这个结构本身不复杂但它保证了每个环节各司其职身份信息恒定任务信息明确历史信息按需加载当前指令永远最后出现。比起把所有规则堆在前面让模型自己权衡这种组装方式稳定得多。7. 落地实操让它成为一套可维护的框架而不是某次灵光一现7.1 按“热、温、冷”三层管理上下文生命周期只懂取舍还不够要有一个能长期跑不崩的框架。我习惯把上下文划分为热、温、冷三层热数据当前任务正在使用的信息。比如正在写的文档内容、最近 5 轮对话、本次调用的工具结果。这些随每次请求完整保留窗口预算给得最足。温数据上一个子任务的摘要、仍可能被引用的前置结果。以结构化摘要形式保留量不大按需展开。冷数据更早的全部原始记录、已完成任务的细节。放入外部记忆只通过检索进入上下文。这个分层也可以直接对应你选的存储结构热数据在内存里温数据在 KV/缓存里冷数据在向量库或文件里。这样一套分层的生命周期既能控制 token 预算又能兼顾对话的连贯性而且排查问题时有据可查。7.2 量化“变笨”给你的 Agent 建立一套体检指标判断 Agent 有没有变笨不能靠感觉。我给自己项目定的指标有四个关键信息召回率准备一组测试问题把早期对话中的约束作为“标准答案”跑一轮长会话后问模型这些约束看能答对多少。比如第 3 轮用户说了“全程不要提竞品”到第 20 轮问一句“本项目有什么禁忌”答对就是召回成功。偏好遵从率跑一系列生成任务检查输出是否符合用户既定偏好比如格式、语气、简单句。重复执行率在长任务末尾检查工具调用序列统计有没有出现“已经完成又做一遍”的步骤。中断恢复率在第 20 轮强行打断让它描述“当前任务进行到哪一步”看回答和实际状态的匹配度。出现异常时除了看最终结果还要保留“压缩前的原文”和“注入后的最终上下文”。很多问题的定位都靠对比这两份内容是原始信息丢了还是信息在压缩时被写歪了一目了然。8. 现场问题排查实录三个让我头疼的场景8.1 任务执行到一半Agent 把用户上限参数改掉了这个场景发生在一次自动文案生成项目上。用户最初设定“每篇文章上限 800 字”跑到第 15 轮Agent 突然生成了一篇 2000 字的长文。查日志发现用户那句“上限 800 字”早被滑出窗口Agent 只能从历史里含糊记得“好像有字数要求”于是自己脑补了一个“越详细越好”。修法就是把它放进“硬性约束区”任何截断都不允许碰它。这类问题的核心教训是凡是和“业务红线”相关的参数永远不能只靠窗口滑动来保留。8.2 工具结果把当前用户消息冲出了窗口另一个常见场景是Agent 调了三次搜索工具每次返回上千 token 的结果结果第三次工具输出还没处理完用户当前消息就被挤出了有效注意力范围。模型看着一堆工具 JSON 回答用户自然答非所问。我的调整是滑动窗口不是简单的先进先出要优先保住“最近用户消息”和“当前任务指令”工具输出反而可以放进一个可折叠区域必要时先做个几十 token 的浓缩再进窗口。8.3 摘要越抽越薄把业务约束抽没了还有一次是摘要链出了问题。原始摘要 1200 字到第二轮压缩变成 300 字用户“不要用感叹号”的要求直接蒸发。后来我改成强制模板摘要每轮总结必须保留“不可违反约束”字段并且这个字段只允许原文摘录、不允许改写。从那以后无论压缩多少层业务约束基本不会丢。这里顺便建议所有摘要工作最好用一个单独的模型调用来生成不要在主对话里顺手总结避免互相干扰。8.4 给上下文加“标记线”的隐蔽收益最后分享一个小技巧在注入检索片段和外部记忆前后加上明确的标记。习惯用memory和/memory标签来包裹外部信息。这个做法的两个好处一是模型能更清楚地区分“外部引用”和“自身推论”减少事实混淆二是排查 prompt 时你可以一眼看到哪些内容来自检索、哪些来自原文。别小看这个细节它能让很多问题从“玄学”变成“可定位”。9. 写到最后再分享一点个人习惯上下文工程这件事说到底就是做权衡窗口长度换成本原文保真换摘要压缩短期记忆换长期外部记忆顺序上下文换相关性检索系统指令的重量换动态组装。每一边都有收益也都有不可忽视的代价。没有哪个方案是永远最优的但一个好的 Agent 项目一定是把每个取舍都明确写进代码、写进监控指标的。我自己现在的习惯是每个 Agent 项目上线第一天就把 token 预算表、上下文分层结构、变笨体检指标写进 README。后面不管谁来维护、改了多少版只要这套上下文策略还在Agent 就不会跑着跑着突然“智商归零”。如果你正在被“越聊越笨”折磨不妨先别急着换更大的模型回去把上下文日志拉出来对照上面这 5 个取舍逐一检查。多数时候问题不在模型而在你喂给它的那一堆字里。
阅读完成 · 觉得有帮助?