LLM上下文工程:模型注意力如何决定Agent成败
发布时间:2026/10/22 4:18:26 锦皓数字建站

做Agent有一个特别容易被低估的环节上下文工程。这个词听起来像是把提示词调出花样的进阶版但实际做下来你会发现它决定了一个Agent是“看起来聪明”还是“真的能干”。我最早丢掉幻想是在一个很简单的场景里让模型从一段很长的材料里提取某个关键字段它死活提取不对——不是没理解要求而是它仿佛根本没“看到”那段材料。后来我把材料挪到对话末尾问题瞬间消失了。那一刻我意识到在LLM这里“看到了什么”和我们以为的完全是两码事。这一期是手搓Agent系列的第三篇我只聊一个底层问题一段文本被送进模型之后到底经历了什么模型的注意力会落在哪里哪些位置、哪些格式会决定一段信息的最终命运把这个问题想清楚你后面做工具调用、多轮记忆、知识库接入都会顺很多。1. 上下文工程是从哪里冒出来的1.1 没有上下文工程Agent 处处碰壁先把我对上下文工程的定义放这儿它是关于“如何选择、组织、更新、压缩”送进模型的那批文本的设计方法。很多人刚开始把注意力全放在单条Prompt的措辞上结果发现提示词写得再漂亮一旦上下文里混了太多无关内容或者信息位置不对输出照样翻车。我之前做了一个自动整理会议纪要的Agent。第一版实现特别简单就是把“用户上传的材料 用户的要求 历史生成内容”一股脑追加进上下文。结果在处理一份三十页的材料时模型会丢掉前面几页的信息回答得像只看了最后几段。这不是模型蠢是我没设计它的“视线范围”。对比传统开发一个函数只要你把参数传对了它一定读得到LLM不是这样的它在一个固定长度的token流上做概率预测哪些token最终会被“注意”到是模型自己决定的而且这个决定受位置、格式、周围干扰的强烈影响。所以想手搓Agent上下文工程是绕不过去的一关。1.2 模型和我们以为的“看到”不是一回事这不是文字游戏。你和我“看到一句话”是在理解含义LLM“看到一句话”是把文本拆成token放进一个固定大小的窗口里参与大量的矩阵运算。它没有那种“翻开第一页、找到第五页某个段落”的阅读动作。它的全部视野就是当前窗口里的token序列。模型每生成一个词都会回头对窗口里的所有token算一遍注意力分布。所以上下文里有没有某条信息、它放在哪儿、格式清不清晰会直接决定后续生成的走向。把上下文工程理解为“给模型设计它可能关注到的信息布局”这个角度就对了。Agent侧的上下文构建核心代码其实简单到有点让人放松警惕# agent_context.py class AgentContext: def __init__(self, system_prompt: str, max_tokens: int 8000): self.messages [{role: system, content: system_prompt}] self.max_tokens max_tokens def add_user_input(self, text: str): self.messages.append({role: user, content: text}) def add_tool_result(self, name: str, result: str): # 工具返回值必须结构化地塞进上下文不能直接往历史里丢 block f[工具 {name} 的返回]\n{result}\n[/工具结束] self.messages.append({role: tool, content: block}) def build(self): # 真正的工程点在于build之前先做裁剪、摘要、去重 return self.messages这个骨架想表达的是系统提示词、用户需求、工具结果、历史对话全都会被拼进同一个上下文里。很多人觉得API支持messages数组就万事大吉其实消息一多互相干扰才是灾难的源头。2. 现场还原在模型眼里上下文的真实样貌2.1 你的文本先过一道Tokenizer你输入的文本并不是以字符为单位被模型读取的。模型先把文本切成一串token每个token可能是半个词、一个词根、一个汉字或一个小片段。中文和英文的切法不一样中文经常一个字或一个词对应一个token。这意味着“你写了多少字”和“模型实际处理多少token”不是一回事。我见过不少开发者觉得五万字的材料扔进去没事结果一算token数早超了或者严重挤占了输出空间。所以上下文工程的第一步永远是估算token。先把要放进去的内容离线和估算一下再决定哪些能留、哪些必须压缩。所以工程上的结论是你定义的“上下文”不是字符量而是token量你的信息布局从一开始就要按token预算来做。不要等到线上运行才发现窗口被历史消息占满模型连最基本的指令都快看不见了那就很被动了。2.2 位置编码带来的“先来后到”Transformer靠位置编码记住顺序。现在主流模型一般用相对位置编码或旋转位置编码目的是让模型感知token之间的相对前后关系。位置编码带来的直接结果就是同一段内容放在上下文的不同位置模型对它的利用程度是不同的。我常用一个不一定严谨但很贴切的类比人在听讲座时对开头和结尾记得最牢中间一段相对模糊。LLM不会“记得牢”或“模糊”得这么拟人但注意力分布上确实有类似的倾向。最明显的例子是当你在长Prompt的中间某一段写了一个非常关键的限定条件模型执行时经常会忽略你要是把这条规则放在Prompt开头或结尾再强调一遍执行成功率会显著提高。这个现象不是玄学是注意力机制下不同位置的权重差异所致。后面我会写实验验证。2.3 注意力不是“阅读”而是“打分”真正决定模型把注意力放在哪儿的机制叫自注意力。简单说模型生成下一个token之前会让序列里所有token两两计算一个相关度分数分数高的位置更能影响输出。这个相关度是基于词义和上下文算出来的不是像人眼那样去“找答案”。所以上下文里塞了太多与当前任务无关的内容会稀释有用的注意力。比如你放了二十条知识库片段模型得自己判断哪些相关判断错了就会漏掉关键信息。注意力机制本身不会给信息来源做可信度认证它只做语义相关性打分。虽然现在模型能力一直在变强但本质上还是概率模型你不能假设它一定“看到”了你认为重要的地方。这里也顺带提一句KV Cache。同一个上下文连续问多轮时前面重复的token会被缓存、避免重复计算。上下文越长KV Cache占用的空间越大。这也是我们不建议盲目把历史全塞进去的重要原因上下文变长不只是贵和慢注意力也可能被稀释得更厉害。3. 三个关键现象理解了这些才算看懂了 LLM 的视线3.1 注意力会“淹没在中间”学理上有个知名的概括叫“迷失在中间”。意思是当需要从上下文的多段材料里找信息时模型对开头和结尾的内容利用率更高对中间内容的利用率偏低。原因和注意力机制里位置权重有关也跟长序列内部互相干扰有关。我自己做个了简单测试把一个重要指令放在三段话的中间位置模型经常漏掉放在开头或结尾几乎不会漏。材料越长中间位置的信息越容易被忽略。所以做上下文工程第一个基本原则就是关键指令放两端背景材料放中间。越重要的约束越要靠后或靠前。延伸出一个操作技巧模型总是不遵循某条规则的时候先别急着换措辞把它从正文中间挪到系统提示词末尾试一次很可能直接就解决了。这也是“上下文工程”和单纯“写提示词”最大的差别。3.2 越靠后的内容越有“话语权”和迷失在中间相辅相成的是近因效应模型会更倾向于利用靠近上下文末尾的内容。这跟人聊天很像最后一句往往最容易被人接住。在Agent场景里这意味着最新一次工具调用的结果、最近一轮的用户输入应该放在消息序列比较靠后的位置系统设定和背景知识可以放前面。这一点在多轮Agent里尤其重要。很多时候模型不按工具返回结果回答反而按之前的旧假设来推理就是因为工具结果被放在了中间旧假设却紧贴着用户当前问题。我后来把所有工具返回值放在用户输入的前面让用户当前问题紧接着出现在最末尾回答准确率明显提升。通用经验上下文信息的“话语权”排序大致是底部大于顶部顶部大于中间。设计消息队列时把“当前要回答的问题”放在最后“工具刚返回的数据”放在倒数第二“系统设定”放开头“背景知识、历史记录”放中间。如果光排序还不够就搭配下一节的结构化标签。3.3 历史中的事实不一定是模型凭据这个现象特别容易造成误解模型并不是像数据库一样“查询”历史它只是生成时参考整个序列给出的概率。你把三个事实放在上下文里模型可能只利用其中一个最显眼的。如果同时塞入多条相似信息模型还可能记串。比如上下文中同时出现“订单A金额是100元”和“订单B金额是200元”模型不一定能准确区分哪个对应哪个。我的对策是把事实放入上下文前用结构化标签明确标识。像这样订单信息 订单编号A001/订单编号 金额100元/金额 /订单信息模型对结构化标签的敏感度远高于散文式描述有时候加一对尖括号比写十句“请注意以下信息很重要”更有效。因为标签把语义边界划清楚了注意力在计算时更容易把同一个实体的属性关联起来。这属于极低成本、极高回报的上下文工程手段强烈建议马上用起来。4. 动手实验看模型“视线”的有效姿势4.1 简单实验同一指令换个位置空谈理论不直观建议你花半小时做个实验。固定同一个任务准备三段不同的规则文本分别放在上下文的开头、中间、结尾然后各跑几遍记录模型对规则的执行成功率。我自己用这个办法验证过一个简单例子让模型从一段材料中提取“项目启动时间”材料里给了三个日期规则明确要求“以最近的日期为准”。规则放开头时执行成功率大概七成放中间时大概五成放结尾时接近八成。数值会随模型能力和随机性浮动但“中间最差”的结论很稳定。这个实验最大的意义在于你会知道自己的Prompt该往哪儿调整而不是盲目地“再写狠一点”。上下文工程设计的第一步就是能预判模型关注力的重心。4.2 用 logprobs 观察模型“选择”如果只靠经验判断也行但想看更细可以用logprobs。多数模型API支持返回每个token的生成概率。如果模型在某个关键位置生成了低概率的token说明它没多大把握或者上下文里没有足够支撑。我调试Agent时会先开logprobs跑一轮。如果模型在回答“项目启动时间是X”时那个X的概率很低基本可以断定上下文里相关信息不足或者位置没放对。用概率辅助判断能帮我把“猜模型为什么错”变成“观察模型哪里没把握”效率差很多。4.3 用“小窗口自测”训练上下文嗅觉我还常用一个训练上下文设计能力的小方法把窗口人为调小强迫自己只保留最有价值的内容。这个过程就像打包行李装不下了你才认真思考哪些是真必需品。多做几次之后会形成一种条件反射先判断哪些信息是决策依据哪些只是背景噪音。具体操作上我把候选信息分三级核心指令、事实参数、装饰性描述。核心指令必须保留且放在显眼位置事实参数按需放进结构块里装饰性描述能删就删。这样整理出来的上下文往往比“塞满完整对话历史”的效果更好。你现在手头要是有正在调的Agent可以直接按这个方式重排一遍消息顺序。5. 常见问题与避坑实录5.1 常见误区速查表误区实际情况正确思路把上下文当成数据库模型不会像SQL一样精确查历史注意力可能漏掉信息用结构标签、位置设计、压缩摘要保证关键信息不漏以为模型会逐字阅读所有token注意力分布不均匀中间的信息容易被忽略提到关键规则或数据时放到两端窗口越大越好上下文变长会稀释注意力也增加延迟和成本按需裁剪把窗口预算留给关键信息所有token地位一样不同位置的注意力权重差别很大按话语权排序设计消息队列这张表是我在多个项目里反复踩坑后总结的。每次模型表现不对劲我先对照这张表排查一遍很多时候问题不是模型不行而是上下文结构不合理。5.2 我踩过的坑和现在的做法第一个坑是往上下文里硬塞知识库全文。某个内部项目里我把一份几十页的材料整段塞进上下文想的是“模型知道得越多回答越准”。结果输出越来越偏向最后几页的内容中间部分的专业名词甚至开始被它自创解释。后来改成只放摘要需要细节再通过检索补充效果立刻稳了。第二个坑是历史消息无限增长。Agent多轮对话后我把所有历史都保留在上下文中首token延迟一路走高更麻烦的是模型回答开始前后矛盾。后来加了两个机制超过一定轮数的历史先做摘要再老的消息直接丢弃。上下文稳定在可控范围回答质量也恢复了不少。第三个坑是工具结果格式混乱。不同工具的返回值五花八门模型读起来很吃力。后来我统一用方括号标签包裹工具结果再注明字段含义。前面提到了模型对边界标记非常敏感加完标签后准确率明显提升。你花十分钟统一一遍工具返回格式可能比花一天换模型管用得多。这些坑的底层教训是同一句话上下文工程不是写提示词的技巧它是Agent系统设计的一部分。上下文里的每一条消息都应该经过“是否有必要”和“放哪里合适”这两道筛选。5.3 给“上下文工程上”一个实用检查清单最后给一个可以直接套用的检查清单。每次手搓Agent、调Agent之前照着过一遍能省不少事预估当前上下文的token总数确认是否超过窗口的85%。检查关键指令是不是出现在开头或结尾而不是埋在正文中间。给工具返回值加上结构化标签明确字段边界。移除与当前任务无关的历史轮次必要时改成摘要。把用户当前问题放在消息序列最后让它贴住模型的生成位置。对多个同类型实体尽量用列表或表格呈现而不是散落在正文里。对长材料先做摘要和分层不要整段堆入。条件允许时开logprobs观察关键生成位置的置信度。这次把“LLM到底看到了什么”讲透之后我最大的体会是大多数Agent表现不好不是因为模型不够聪明是我们没把信息摆在它最容易注意到的位置。下次做Agent迭代可以先不急着换模型、加RAG试着重新设计上下文里的信息顺序效果可能会让你意外。上下文工程下我准备聊更贴近实操的部分多轮对话里上下文怎么压缩、记忆摘要怎么做、Agent状态机怎么维护。你也正在手搓Agent的话可以先拿这份清单去试一轮欢迎回来交流踩到的坑。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。