ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AI Agent上下文工程实战:从翻车到精准管理

AI Agent上下文工程实战:从翻车到精准管理 1. 上下文工程到底在解决什么问题1.1 从一次真实的翻车现场说起去年冬天我帮一个做电商客服的朋友调他们的 AI Agent场景很简单用户问“我上周买的那个蓝色杯子什么时候到”Agent 需要查订单、查物流、然后回复。模型用的是当时比较主流的一个开源大语言模型ReAct 模式工具调用写得也没问题。但实测下来十次里有三次答非所问——要么把上一个用户的订单信息混进来要么把系统提示词里的示例当成真实订单号返回。排查了两天才发现问题不是模型不行也不是工具写得烂而是上下文窗口里塞的东西太杂了。系统提示、历史对话、工具返回结果、少样本示例、用户当前问题全部平铺直叙地拼在一起模型根本分不清哪些是当前任务相关的哪些是噪音。更要命的是多轮对话累积下来早期无关的闲聊把真正重要的订单信息挤到了窗口边缘模型的注意力机制在长上下文里对边缘信息的召回率明显下降。这件事让我彻底意识到AI Agent 的能力上限很大程度上不取决于模型本身而取决于你往上下文窗口里放什么、怎么放、按什么顺序放。这就是上下文工程Context Engineering要解决的核心问题。1.2 上下文工程和提示词工程不是一回事很多人把这两个概念混着用其实差别很大。提示词工程Prompt Engineering关注的是单次交互中指令怎么写——措辞、格式、少样本示例的组织方式。而上下文工程关注的是整个 Agent 运行周期内上下文窗口这个有限资源如何动态管理。打个比方提示词工程像是你写一张便签告诉助理要做什么上下文工程像是你管理助理的整个工作台——哪些文件放在桌面、哪些归档、哪些直接扔掉、新文件来了怎么替换旧文件。Agent 每执行一步思考、调工具、观察结果工作台上的东西都在变你得有一套策略保证它始终能看到最关键的信息。从工程视角看上下文工程至少包含这几个维度写入策略什么信息值得放进上下文系统指令、工具定义、历史摘要、检索结果、当前观察组织策略这些信息按什么顺序、什么结构排列分区、标记、优先级压缩策略窗口快满时怎么裁剪摘要、滑窗、重要性打分隔离策略多任务、多用户场景下如何避免上下文串扰检索策略外部知识怎么按需注入而不是全量塞入这五个维度构成了上下文工程的完整拼图后面我会逐一拆开讲。1.3 为什么现在必须重视这件事三个现实压力逼着大家不得不做上下文工程第一模型上下文窗口虽然在变大但有效注意力没同比提升。128K、200K 甚至 1M 窗口的模型不少但实测中“大海捞针”式的关键信息召回率在窗口后半段会明显衰减。塞满不等于用好。第二Agent 的 token 成本是实打实的。ReAct 模式下每一步都要把完整上下文重新喂给模型轮次一多token 消耗呈线性甚至超线性增长。我见过一个没做上下文管理的 Agent单次任务跑了 40 多轮token 账单直接飙到几块钱一次业务根本扛不住。第三多轮、多工具、多模态的复杂度在叠加。现在的 Agent 不只是聊天还要调 API、读文件、看图片、操作浏览器。每种输入的数据结构和体积都不一样没有统一的上下文管理框架代码会迅速变成一团乱麻。提示如果你现在的 Agent 还在用“把所有历史消息拼成一个字符串丢给模型”的土办法那上下文工程就是你下一个必须补的课。2. 上下文窗口的结构化设计2.1 把上下文当成一块有分区的地皮我习惯把上下文窗口想象成一块地皮你得规划好哪里盖什么。一个经过工程化设计的 Agent 上下文通常包含以下几个功能区按优先级从高到低排列区域内容是否可压缩典型占比系统指令区角色设定、行为约束、输出格式不可压缩5%-10%工具定义区可用工具的 schema 描述可精简5%-15%长期记忆区用户画像、历史偏好摘要可摘要5%-10%任务状态区当前目标、已完成步骤、待办动态更新10%-20%检索知识区RAG 召回的相关文档片段可裁剪20%-40%对话历史区近期交互记录可滑窗15%-30%当前输入区用户最新问题或工具返回不可压缩5%-10%这个比例不是死的要根据任务类型调。比如客服场景对话历史占比高知识问答场景检索区占比高自动化操作场景任务状态区占比高。2.2 系统指令区越稳定越好越靠前越好系统指令是整个上下文的“宪法”它定义了 Agent 是谁、能做什么、不能做什么。我的经验是这部分内容一旦确定在整个会话周期内尽量不要改动。原因在于大语言模型对上下文开头的注意力权重通常更高而且很多推理框架会对系统提示做 KV Cache 缓存。如果你频繁改动系统指令缓存失效每次推理都要重新计算延迟和成本都会上升。写系统指令有几个实操要点用明确的分隔符标记边界比如用###或 XML 标签把不同区块隔开让模型清楚知道哪里是指令、哪里是数据把最硬的约束放在最前面比如“禁止编造订单号”“必须调用工具查询而非凭记忆回答”输出格式用示例而非描述与其说“请用 JSON 格式返回”不如直接给一个 JSON 示例我踩过的一个坑是早期把工具定义和系统指令混在一起写结果模型经常把工具描述里的示例参数当成真实参数用。后来把工具定义单独抽成一个区块用清晰的标签包裹问题就消失了。2.3 工具定义区schema 要精简描述要精准工具定义是 ReAct 模式 Agent 的命脉。模型靠它决定“我该调哪个工具、传什么参数”。但工具定义也是最容易膨胀的部分——一个功能完整的 Agent 可能有几十个工具每个工具的 schema 加上描述轻松吃掉几千 token。我的精简策略是只暴露当前任务可能用到的工具。不要把所有工具一次性全塞进去而是根据任务类型动态加载工具子集。比如用户问天气就只加载天气查询工具订单工具、支付工具全部隐藏。参数描述去掉冗余。description字段写清楚用途即可不要写成长篇说明文。枚举值用数组列出不要用自然语言描述。工具名用动词开头get_order_status比order更清晰模型选择时更不容易出错。下面是一个精简前后的对比示例// 精简前描述冗长参数说明啰嗦 { name: query_order, description: 这个工具用于查询用户的订单信息可以查询订单状态、物流信息、预计送达时间等。使用时需要提供订单号订单号通常是数字组成的字符串。, parameters: { order_id: { type: string, description: 用户的订单编号一般是一串数字长度在10到20位之间 } } } // 精简后描述精准参数说明简洁 { name: query_order, description: 查询订单状态与物流信息, parameters: { order_id: { type: string, description: 订单编号 } } }实测下来精简后的工具定义在保持调用准确率的前提下token 占用能降低 40% 以上。2.4 任务状态区Agent 的“工作记忆”这是最容易被忽视但极其关键的区域。ReAct 模式下的 Agent 是多步执行的每一步都需要知道“我现在在哪、已经做了什么、下一步该干嘛”。如果这些信息散落在对话历史里模型需要自己去翻找和推断很容易迷失。我的做法是在上下文里维护一个显式的任务状态块结构大概是这样[任务状态] 目标查询用户上周购买的蓝色杯子的物流状态 已完成 - 已识别用户意图为订单查询 - 已从对话中提取订单号ORD-20241201-8834 待办 - 调用 query_order 工具获取物流信息 - 组织回复语言 当前步骤准备调用工具这个块每轮更新放在对话历史之前、系统指令之后。好处是模型一眼就能看到当前进度不需要从冗长的历史里重新推理。我在多个项目里对比过加了任务状态块之后Agent 的步骤冗余率平均下降 30% 左右完成任务所需的轮次明显减少。2.5 对话历史区滑窗加摘要的组合拳对话历史是上下文膨胀的主要来源。一个跑了 20 轮的 Agent历史消息轻松占满窗口。全量保留不现实全量丢弃又丢失信息。我的方案是滑窗 摘要的组合近期 N 轮保留原文N 一般取 3 到 5保证最近交互的细节不丢失更早的历史压缩成摘要用模型生成一段简短的任务进展描述替换掉原始消息关键信息单独提取比如订单号、用户明确表达的偏好抽出来放进任务状态区不依赖历史保留摘要的生成时机也有讲究。不要每轮都重新摘要那样成本太高。我的做法是当历史区 token 占用超过阈值比如窗口的 30%时触发一次摘要把最老的一半消息压缩掉。注意摘要会丢失细节所以摘要里必须保留所有实体订单号、日期、金额、用户明确说的偏好。我一般会在摘要 prompt 里强制要求“列出所有出现的数字和专有名词”。3. ReAct 模式下的上下文流转实战3.1 ReAct 循环里上下文是怎么变的ReActReasoning Acting是当前 Agent 最主流的运行模式它的核心循环是思考Thought→ 行动Action→ 观察Observation→ 再思考。每一轮循环上下文都会追加新的内容。我用一个具体的订单查询例子把每一轮上下文的变化拆给你看初始上下文用户提问后[系统指令] 你是电商客服助手... [工具定义] query_order, query_logistics... [任务状态] 目标待识别 [对话历史] 空 [当前输入] 我上周买的蓝色杯子到哪了第 1 轮 - 思考模型输出 Thought“用户想知道订单物流但我没有订单号需要先查用户最近订单。” 这段思考追加到上下文。第 1 轮 - 行动模型输出 Actionquery_recent_orders(user_idU123)。追加到上下文。第 1 轮 - 观察工具返回[{order_id: ORD-8834, product: 蓝色杯子, date: 2024-12-01}]。追加到上下文同时更新任务状态区提取出订单号。第 2 轮 - 思考模型看到观察结果输出 Thought“找到订单 ORD-8834现在查物流。”第 2 轮 - 行动query_logistics(order_idORD-8834)。第 2 轮 - 观察返回物流信息。追加。第 3 轮 - 思考信息齐全组织回复。最终输出给用户的自然语言回复。整个过程上下文增长了 6 到 8 条消息。如果不做管理这些消息会一直累积。而实际上第 1 轮的“查最近订单”这个中间步骤在最终回复时已经不需要了可以在任务状态区记录“已获取订单号”之后把原始的工具调用和返回从历史里裁掉。3.2 中间步骤的裁剪策略这是上下文工程里最能省 token 的一招中间工具调用的原始记录在信息被提取后就可以裁剪。具体做法是每当一个工具返回结果先判断这个结果里有哪些信息是后续需要的把它们提取到任务状态区或一个专门的“事实区”然后原始的工具调用记录就可以标记为可裁剪。比如上面例子中query_recent_orders返回了一个订单列表我们只需要订单号其他字段商品名、日期在后续步骤用不到就可以只保留订单号把整个返回结果裁掉。但这里有个坑有些信息你当下觉得没用后面可能突然需要。我的经验是保留一个“已获取事实”的清单把所有提取过的关键信息都列在里面原始记录裁掉。这样既省空间又不丢信息。3.3 工具返回结果的格式化处理工具返回的原始数据往往是 JSON字段多、嵌套深直接塞进上下文非常浪费 token。我一般会做一层格式化转换# 原始返回 { code: 0, message: success, data: { order_id: ORD-8834, status: shipped, logistics: { company: 顺丰, tracking_no: SF1234567890, latest: 已到达杭州转运中心, update_time: 2024-12-05 14:30:00 } } } # 格式化后塞进上下文 订单 ORD-8834已发货顺丰 SF1234567890最新状态已到达杭州转运中心12-05 14:30一行自然语言替代了几十行 JSONtoken 占用可能只有原来的五分之一而且模型理解起来更直接。这个转换逻辑写在工具调用的后处理层对模型透明。3.4 多轮对话中的上下文隔离多用户场景下上下文隔离是必须的。我见过一个反面案例一个 Agent 服务同时处理多个用户的请求结果因为上下文管理没做好A 用户的订单信息泄漏到了 B 用户的回复里。这在业务上是致命的。隔离的核心原则是每个会话session拥有独立的上下文空间会话之间不共享任何可变状态。实现上每个 session 一个独立的上下文对象包含自己的系统指令实例、任务状态、对话历史。共享的只有只读的资源比如工具定义模板、知识库索引。如果用的是支持多轮对话的 API注意每次请求都要把该 session 的完整上下文传过去不要依赖服务端的会话状态否则并发场景下会串。4. 上下文压缩与检索的进阶技巧4.1 什么时候该压缩压缩到什么程度压缩不是越早越好也不是越狠越好。我的判断标准是看窗口占用率和任务阶段窗口占用低于 50%不压缩保留完整信息50% 到 75%启动轻度压缩裁剪中间步骤摘要早期历史75% 到 90%中度压缩合并相似消息精简工具定义超过 90%重度压缩只保留任务状态、关键事实和最近 2 轮对话任务阶段也影响压缩策略。任务刚开始时历史短不用压缩任务快结束时很多中间步骤已经不需要可以大胆裁。最忌讳的是在任务关键推理阶段做激进压缩容易把模型搞懵。4.2 基于重要性的消息打分如果不想简单地按时间滑窗可以给每条消息打一个重要性分数压缩时优先保留高分消息。打分维度我一般用这几个是否包含实体订单号、金额、日期、用户明确偏好有则加分是否是用户直接输入用户说的话比工具返回更重要是否被后续引用如果后面的思考或行动引用了这条消息加分时间衰减越早的消息分数越低但有关键实体的不衰减打分可以用规则实现也可以用一个小模型来判。规则实现简单可控小模型更灵活但增加成本。我一般先用规则效果不够再上模型。4.3 检索增强里的上下文注入时机RAG检索增强生成是上下文工程的重要输入源但检索结果什么时候注入、注入多少很有讲究。我的做法是按需检索、延迟注入。不要在一开始就把所有可能相关的文档塞进去而是在 Agent 执行到需要外部知识的步骤时才触发检索把结果注入到当前步骤的上下文里。比如一个技术支持 Agent用户描述问题时先不检索当 Agent 判断需要查文档时用当前的问题描述去检索把 top-3 的相关片段注入。这样检索的 query 更精准因为带了上下文注入的内容也更相关。注入的片段也要做格式化去掉 HTML 标签、页眉页脚、无关段落只保留核心内容。我一般会把每个片段控制在 200 到 500 字太长就再切。4.4 上下文工程的评估指标做完这些优化怎么知道有没有效果我一般盯这几个指标指标含义优化目标平均轮次完成任务所需平均步数越低越好Token 消耗单次任务总 token 数越低越好任务成功率正确完成的比例越高越好关键信息召回率需要的信息是否在上下文中越高越好上下文占用峰值窗口最高占用率留足余量这几个指标要一起看。有时候压缩太狠token 降了但成功率也降了那就得不偿失。我一般会做一个 A/B 对比一组用完整上下文一组用压缩上下文看成功率和 token 的权衡点在哪。5. 常见问题与排查实录5.1 模型“忘记”了前面的关键信息这是最高频的问题。表现是 Agent 在第 5 轮突然问用户“请问您的订单号是多少”而订单号在第 2 轮就已经提供了。排查思路先看订单号是否还在上下文里。如果被压缩掉了那就是压缩策略太激进需要把关键实体加入“不可压缩”白名单。如果还在上下文里看它的位置。如果在窗口很靠前的位置而当前轮次上下文很长可能是注意力衰减导致的。解决办法是把关键信息在任务状态区重复一份放在靠近当前输入的位置。检查是否有格式问题。有时候订单号被夹在一大段 JSON 里模型没注意到。格式化成自然语言后通常能解决。我的通用做法是所有关键实体在任务状态区常驻一份不管历史怎么压缩任务状态区始终保留。这样模型每轮都能在固定位置看到关键信息。5.2 工具调用参数传错表现是模型调工具时传了错误的参数比如把示例里的参数值当成真实值或者参数格式不对。根因通常是工具定义里的示例太“真实”模型分不清示例和实际值。解决办法示例参数用明显的占位符比如order_id: 用户订单号在系统指令里明确“工具参数必须来自对话中的真实信息不得使用示例值”工具定义和对话历史之间用清晰的分隔符隔开还有一个常见原因是参数名有歧义。比如id这种参数名模型不知道是订单 id 还是用户 id。改成order_id、user_id就清楚了。5.3 上下文串扰导致答非所问多用户或多任务场景下A 的信息跑到 B 的回复里。这个问题的根因几乎都是上下文对象被共享了。排查时重点看每个 session 是否独立创建了上下文对象是否有全局变量在存上下文异步处理时是否有竞态条件修复方法就是严格隔离每个 session 一个上下文实例生命周期跟随 session。共享的只读资源用不可变对象。5.4 上下文太长导致响应变慢窗口占用高的时候推理延迟会明显上升。这时候除了压缩还可以考虑把不常变的系统指令和工具定义做 KV Cache避免重复计算把长上下文的任务拆成多个短上下文的子任务分步执行用更小的模型做中间步骤的推理只在关键步骤用大模型我实测过一个方案把 ReAct 的思考步骤用 7B 小模型做最终回复用大模型做整体延迟降低 40%成功率基本持平。当然这取决于任务复杂度简单任务效果更好。5.5 常见问题速查表问题现象可能原因排查方向解决手段忘记关键信息压缩过度/注意力衰减检查实体是否在上下文任务状态区常驻关键实体工具参数错误示例误导/参数名歧义检查工具定义占位符明确参数名上下文串扰上下文对象共享检查 session 隔离每 session 独立上下文响应变慢窗口占用过高看 token 占用率压缩KV Cache任务拆分答非所问噪音过多看上下文里无关内容占比精简工具定义裁剪中间步骤重复调用工具任务状态不清晰看任务状态区是否更新显式维护任务状态块6. 我踩过的坑和几条实在建议6.1 不要迷信“窗口越大越好”刚开始做 Agent 的时候我总觉得窗口大是优势恨不得把所有能塞的都塞进去。结果发现塞得越多模型越容易分心。后来我反过来做能不放的就不放能晚放的就不早放效果反而更好。上下文工程的核心不是“塞满”而是“精准”。每一段放进上下文的内容都要能回答“它对当前这一步推理有什么用”。回答不上来的就不该在里面。6.2 任务状态块是我最推荐的单项优化如果只能做一件事我会选加任务状态块。它的投入产出比最高实现简单就是拼一段结构化文本效果立竿见影轮次减少、成功率提升。我经手的项目里加了任务状态块的 Agent平均任务完成轮次能降 20% 到 40%。任务状态块的内容不用很复杂目标、已完成、待办、当前步骤四行就够。关键是每轮都要更新保持和实际进度一致。6.3 压缩策略要留“后悔药”压缩最怕的是把后面需要的信息提前扔了。我的做法是压缩不是真删而是把被压缩的内容存到一个外部存储里上下文里只留一个引用标记。如果后续步骤发现需要可以按引用把内容捞回来重新注入。这样既省了窗口空间又不会真的丢信息。实现上就是维护一个消息池上下文里放的是消息 id需要时按 id 取原文。6.4 多测试边界情况上下文工程的 bug 往往在边界情况下才暴露超长对话、多工具连续调用、工具返回异常、用户中途改需求。我一般会专门构造这些边界用例来测连续 20 轮对话后关键信息是否还在工具返回空结果或报错时 Agent 是否优雅处理用户在第 10 轮突然切换话题旧上下文是否干扰新任务并发 10 个 session 时是否有串扰这些测试跑一遍能发现大部分上下文管理的问题。6.5 最后分享一个实用技巧如果你现在正在搭 Agent还没做任何上下文管理我建议你从最简单的开始先加一个任务状态块再把工具返回结果做格式化。这两步改动量很小但能立刻看到效果。等这两步稳定了再逐步上压缩、摘要、检索注入这些进阶手段。上下文工程不是一次性的设计而是随着你对任务理解加深不断迭代的过程。我现在的项目里上下文结构已经改了七八版每一版都是根据实际跑出来的问题调整的。别指望一次设计到位先跑起来再优化。这个方向后续还可以往多模态上下文管理扩展比如图片、音频怎么压缩和注入那是另一个值得深挖的话题了。
返回列表