做AI应用开发这几年,我注意到圈子里对同一个问题的答案越来越统一:当模型本身的能力已经够用,真正决定应用上限的,是喂给模型的信息质量。这也是“Context Engineering(上下文工程)”这个新词慢慢取代“提示词工程”成为讨论焦点的原因。一句话解释它:围绕大语言模型应用,系统性地设计、组织、管理和优化每次请求中进入模型视野的信息,让模型在有限的上下文窗口里,拿到最该拿到的内容,并以最不容易出错的方式理解任务。
这个主题适合两类人。一类是刚接触AI应用开发的工程师,想搞清楚提示词和上下文到底什么关系;另一类是已经在做Agent、知识库问答或多轮对话系统的开发者,正在被上下文溢出、检索命中率低、多轮行为漂移这些问题困扰。它解决的是同一个核心痛点:模型能力已经很强,但不知道怎么让它稳定发挥。
1. 为什么突然都在聊“上下文工程”
1.1 从“会写提示词”到“会管理上下文”
大概前两年,大家还在比谁的提示词写得妙,比如“你是一个擅长X的Y,请用Z格式输出”。那时候模型窗口小,任务单,写好一段提示词就能解决大部分问题。现在不一样了,应用越做越复杂:一个智能客服要同时看用户历史订单、售后政策、商品库存、最近几轮对话;一个编程助手要把整个项目的相关文件塞进上下文里再让模型改某一行代码。
当输入从一个几十字的提示词变成几千行的资料包,“怎么写”就退居其次,关键是“怎么选、怎么排、怎么更新”。提示词工程关心的是单次请求内的表达技巧,上下文工程关心的是从对话开始到结束的整条信息链:知识从哪来、多轮历史怎么压缩、重要指令如何防止被淹没、当天用不完的信息怎么存到下一次。
我常打一个比方:提示词工程是给一位专家写一张便利贴,上下文工程是给这位专家做完整的入职培训、提供工作台、管理资料柜,还定期清理桌面。便利贴写得好有用,但专家能不能稳定输出,更多取决于整个工作环境的设计。
1.2 一个直观的对比案例
拿同一个需求举例:让模型根据客户订单信息输出退款结果。
简单提示词的做法是:
根据以下信息判断能否退款:客户购买了“7天无理由退货商品”,已超过退货期限。这种写法,能不能成功全看模型当时心情。信息没结构化、政策没表述清楚、没有约束输出格式。
上下文工程的做法则是先画一条完整信息流:
- 第一层,系统指令:你是电商客服退款专家,只能基于提供的订单和政策信息作答,不知道就直说;
- 第二层,客户概要:用户ID、购买商品、订单时间、金额、历史售后记录;
- 第三层,政策原文:相关品类的退换货规则、时效、特殊条款;
- 第四层,当前问题:用户真实输入“我上周买的外套能退吗”;
- 第五层,输出约束:返回“可退/不可退/需人工介入”三者之一,并给出政策依据。
同样一个需求,后者把“模型需要依赖什么信息、以什么方式推理、如何输出”全都显式化。这不是花哨的技巧,而是工程化的思维:控制变量、明确依赖、减少模糊地带。
2. 上下文工程的核心构成要素
我把上下文工程拆成五个模块,做项目时按这个框架走,基本不会漏东西。
2.1 上下文窗口的预算管理
上下文窗口是硬约束。不管商用模型还是开源模型,窗口总有上限。即使号称百万级上下文,窗口越大意味着成本越高、相应速度越慢。真正要做的不是把窗口填满,而是像做预算一样分配每一部分token。
一个典型的预算分配思路,我按16k窗口举例:
- 系统提示词:固定占用,控制在1.5k-2k token,写核心角色行为;
- 外部检索知识:按需注入,单次控制在3k-5k token,只放当前问题可能用到的;
- 对话历史:压缩后保留,近2轮原文保留,更早的按摘要处理,控制在2k token;
- 用户当前输入:一般0.5k-1k token;
- 模型输出预留:至少2k-4k token,防止生成到一半被截断。
计算一下:2k + 4k + 2k + 1k + 3k = 12k,留出4k给输出和存储buffer。这个预算不固定,但逻辑要固定:先算必须占用的,再给检索和输出预留,最后才决定历史留多少。
我做每个项目的第一件事,就是把这个预算表画出来,贴在代码注释最上面。没有预算就开发,后面一定会被各种上下文溢出问题逼得返工。
2.2 系统级提示的结构化设计
系统提示词不是“写一段话就行”,它应该是一份有固定结构的配置。我常用的结构是这样:
- 角色声明:说明模型是什么、服务对象是谁;
- 任务定义:说清楚要解决什么问题,交付形式是什么;
- 输入说明:描述系统会提供哪些输入字段,字段缺失怎么办;
- 约束条件:列出绝对不能做的事,比如“不得编造政策条目”“不得评论客户”;
- 推理指南:给出步骤化的推理路径,要求模型逐步思考;
- 输出格式:强制规定JSON结构或Markdown模板,附一个示例;
- 兜底策略:当信息不足时应该怎么回,而不是硬猜。
为什么一定要结构化成七个模块,而不是写一大段话?因为大语言模型对结构化文本的遵守程度明显更高。把指令拆成带编号的模块后,后续调参会非常方便:哪个模块出了问题就改哪个模块,其他部分不动。比如发现模型经常编造政策,就只改“约束条件”和“兜底策略”,而不用整篇重写。
2.3 外部知识的检索与注入
单纯靠系统提示词里写死的知识远远不够,所以上下文工程必然要接外部知识源:商品库、文档库、订单系统、规则库。常规做法是检索增强生成,但这里有个经常被忽略的重点:检索来的内容不能直接拼到上下文里,必须经过处理和筛选。
我做的第一版知识注入其实是脏数据直接拼,后来发现模型经常被无关信息带偏,比如商品详情页里出现“赠品”“限量”等词,它就开始答非所问。后来的处理流程变成了这样:
- 把原始文档切片,每片控制在200-500字之间;
- 标注每片文档的来源、可信度、适用范围;
- 检索时除了相关度分数,还做关键词过滤,剔除与当前问题主体不一致的片段;
- 注入上下文前,按“和当前问题直接相关 > 政策性强 > 辅助参考”排序,最重要的放最后,最次要的放中间;
- 在注入内容前加一行说明:“以下是从知识库检索到的原始资料,仅可引用,不可修改”。
第5点看起来不起眼,但实测下来对稳定性帮助非常大。模型对“资料”和“可修改的说明文字”的边界认知更清晰了,引用时不容易自己发挥。
这里的经验是:检索的最终目标不是找到最多内容,而是找到最不影响模型判断的内容。宁可少注入几段精挑细选的信息,也不要一股脑塞入几十段低质量片段。
2.4 多轮记忆与压缩策略
做多轮对话系统时,最大的坑是“上下文无限增长”。每轮对话都全量保留,很快窗口就满了,然后不得不强制丢弃,结果模型忘了用户一楼说的需求,开始胡言乱语。
一个合理的记忆架构是分三层:
- 近期原文层:保留最近2-3轮对话原文,因为当前问题最可能需要这些信息;
- 中期摘要层:将更早的对话按主题压缩成摘要,保留关键实体,比如用户要的商品名称、退款原因、时间节点;
- 长期属性层:从对话里持续抽取用户画像级信息,比如“客户偏好顺丰”“客户曾投诉过一次”等,写进系统提示词顶部。
我用的压缩方式不是简单丢给模型让它随便总结,而是给一个摘要模板:“用户主要诉求 + 已确认的事实 + 已提供的服务 + 未解决问题”。每压一轮就套这个模板,摘要信息密度非常高。压缩时可以用一次独立的对模型调用,代价不大,但效果比“用上一轮摘要继续拼接”好很多。
记忆这里有个开销问题,不是所有对话都需要三层全开。如果应用场景是“一次性问答型客服”,长期属性层可以完全砍掉,只留近期原文层;如果是“持续陪伴型助手”,长期层才需要精心维护。不要为不需要的记忆功能写复杂代码。
2.5 上下文的评测与调优
上下文工程最容易被误解的地方是:它是一次性的设计工作。实际上它跟任何工程系统一样,需要持续评测和迭代。
我会为每个上下文工程方案建一个评测集,包含三类样本:
- 正常样本:常见问题,验证基本效果;
- 边界样本:问题跨多个政策规则,验证模型综合判断能力;
- 对抗样本:问题里带误导信息,比如故意说“我朋友说能退”,验证模型是否会上当被带偏。
每跑一轮就记录三件事:正确答案是否答对、是否引用了正确来源、有没有产生幻觉。上下文工程的优势在这种迭代中体现得非常明显:同一个模型,上下文调整前后,评测分数可能从60分提到85分。这就是工程的价值,不用换模型、不用重新训练,靠信息编排就能提升效果。
3. 完整的实操过程:某电商智能客服助手的落地记录
前面把理论框架列完,用一个我实际跟完的项目来说明整体流程。某电商公司要做智能客服助手,需求是覆盖订单查询、退换货咨询、物流答疑三类问题,要求所有回答都基于内部政策库和订单数据,不许自由发挥。
3.1 需求梳理:先想清楚这个系统需要知道什么
第一步不是写代码,而是和业务方把“模型需要知道什么”这个问题彻底问清楚。最后列表如下:
- 订单类:订单状态、订单金额、退款进度,这是结构化数据,来自订单接口;
- 政策类:退换货规则、时效标准、特殊品类规则,这是文档,来自政策库;
- 客户类:客户历史售后次数、是否黑名单用户,这是辅助判断信息;
- 对话类:客户在本次会话里的陈述,比如“我申请了退款但没到账”。
这个梳理动作极其关键。它决定了知识检索要接哪些数据源、上下文里要预留多少空间。当时业务方最开始提的需求是“模型懂所有电商规则”,等梳理完发现其实每个问题最多只需要两到三类信息,完全不需要把全量政策库塞进去。第一版就因此省掉了一半的token消耗。
3.2 上下文架构与提示词模板设计
基于需求梳理,上下文架构就非常清晰:每次请求由四段信息拼装而成。系统提示词、检索得到的三到五段政策片段、订单接口返回的结构化JSON摘要、最近两轮对话历史加上当前用户问题。
这段提示词的结构在实际生产环境里长这样(脱敏后简化):
你是一名资深电商售后客服专员。你的任务是根据给定信息,判断客户问题属于哪类咨询,并按规则输出处理结论。 【系统输入说明】 1. 你将收到客户基础信息、订单快照、政策片段和对话上下文; 2. 订单快照中的字段若缺失,视为该信息不可用,不得猜测; 3. 政策片段是从知识库检索的原始资料,只能引用,不能修改或扩展。 【输出要求】 必须输出JSON对象,字段为: - category: 订单查询/退换货咨询/物流答疑/不在范围 - conclusion: 结论要点,不超过50字 - policy: 引用政策片段编号,无引用则填空数组 - need_manual: true或false,当信息不足时设为true 【推理步骤】 一、判断客户问题的核心意图; 二、检查所需信息是否齐备; 三、与政策片段逐条匹配; 四、给出结论并标注政策依据; 五、如果无法判断,need_manual设为true。 【示例】 输入:客户问“退货到什么时候截止” 订单快照:无商品信息,政策片段FQ-04:7天无理由退货从签收次日算起。 输出:{"category":"退换货咨询","conclusion":"需说明签收次日开始7天内可退","policy":["FQ-04"],"need_manual":false}这套模板的特点是每个模块边界非常清楚,模型几乎不依赖“临场发挥”。实测时发现,JSON输出的格式正确率从裸写提示词时的78%提到了96%。这个提升全部来自上下文结构的功劳,模型没换、数据没换。
3.3 检索与记忆模块的分工
检索模块当时用的是向量检索加关键词过滤的混合方案。具体分工是:向量检索负责找出语义接近的政策片段,关键词过滤负责剔除主体偏离的内容。比如客户问“运费险怎么退”,向量检索可能把“运费标准”“保险政策”“退货流程”都捞出来,关键词过滤再根据“运费险”“理赔”“金额”把无关茬剔除,最终剩两到三段最相关的政策。
记忆模块只分两层:两层就能稳住正常客服场景。近期轮次保留原文,超过三轮就把信息折叠起来,折叠方式是只留下“客户诉求要点+客服已答复要点”。如果同一问题反复出现,直接丢弃旧轮次,避免模型被重复信息绕晕。这套方案跑了一段时间,没有再出现过“答了前面忘了后面”的尴尬。
这里有一个值得提醒的细节:记忆模块写代码时容易过度设计。当时团队里有同学提议做长期画像、做情感记忆,被我否了。客服场景里用户画像价值不大,反而占用上下文预算。做前先把预算表拿出来问问:这个信息放进去,能不能直接提升一条回答的正确率?不能就不放。上下文工程的精神就是做减法。
3.4 评测集构建与几轮迭代记录
项目推进时我坚持先建评测集再调上下文。评测集包含大约300条脱敏后的历史工单,覆盖三类问题,还额外加了50条对抗样本。第一轮上线的结果很真实:整体准确率85%,看起来不错,但拆开看全是问题。退换货咨询准确率90%,订单查询只有76%,物流答疑最低,只有68%。
排查后发现,订单查询准确率低的根因是订单快照的信息密度太低:直接把订单接口返回的原始JSON拼进上下文,里面有大量与退款无关的字段。于是改成了提取摘要,只保留订单号、状态、金额、售后进度,并压缩成一行自然语言描述。这一改,订单查询准确率从76%涨到88%。物流答疑则是因为政策库对物流时效的描述太分散,同一时效在五六个文档里各写了一部分,检索总是只捞到一半。后来专门建了一个“物流时效汇总”文档,作为唯一权威来源,这一步把物流答疑又从68%拉到89%。
整个调优过程没有换算法、没有调模型参数,做的全是上下文信息的筛选、重构、排序。这就是上下文工程的日常:不是改代码逻辑,而是改信息进入模型的方式。
4. 常见问题与排查思路速查
在多个项目里踩过不少坑,这里整理几个高频问题,每个都给出排查路径。
4.1 回答质量突然下降,怀疑是上下文的问题
先别急着乱改提示词,按固定步骤排查:
- 查看当次请求的实际上下文拼装结果,确认系统提示词是否在开头、检索内容是否被意外重复拼接;
- 计算上下文占比,超过预算上限时先处理历史压缩;
- 检查检索注入的文档片段里有没有冲突信息,比如两段政策对同一标准描述不一致;
- 做隔离测试:单独让模型只看系统提示词,看行为是否正常;再逐步叠加各类信息,直到复现问题。
最长遇到的原因是“相关但不可用”的数据堆积。多段看似相关的文档放进去后,模型没法分辨谁更重要,输出就被平均了。解决办法是给注入的每段内容加一个优先级标签,明确告诉模型“优先使用标记为A的资料,B仅作参考”。上下文工程里最常见的问题不是缺信息,而是信息太多变成噪音。
4.2 检索命中率不高,上下文里没有正确答案
出现这种情况先排查检索链路,但也要意识到:检索质量不佳时,与其反复调检索算法,不如在上下文工程层面做补偿。
一个有效的补偿方案是对用户的模糊意图进行改写,在检索前把“我不太懂,能退吗”改写成“询问商品退货条件和退货期限”,再进行检索。这一步通常放在上下文工程模块里做,不需要改检索库。改写后的查询命中率明显提升。还有一个思路是引入“检索不到时的兜底策略”:明确告诉模型,如果资料与问题无关,直接返回需人工介入,而不是硬答。
4.3 多轮对话后模型行为漂移,不再遵守初始指令
这是典型的上下文吞没问题。初始约束写在系统提示词开头,多轮以后被大量对话历史和检索内容挤出了模型的注意力范围。特别当模型处理长输入时,对中后段内容的关注度明显下降,开头部分发挥的作用也在减弱。
解决办法有两种,实测都有效:一是在每轮拼装上下文时,把最关键的约束语句在靠近结尾的位置重新声明一次;二是定期把用户的关键诉求与最终指令折叠到同一段简短文本里,每次都放在用户问题后面。前者适合指令类约束,比如“不要编造政策”“只输出JSON”;后者适合任务目标类约束,比如“用户的根本诉求是退款,所有回答都要围绕如何退款”。核心思路就是关键信息不能被埋在中间。
4.4 上下文变大后成本与延迟飙升
上下文工程做得好不好,直接体现在账本上。令牌消耗翻倍时第一反应是压缩历史,然后优化检索注入。有几个确定性的优化手段:
- 检索片段单条长度从500字降到250字,相关度基本不变,成本降一半;
- 系统提示词里的示例只保留一个,用最典型的,不要堆五个以上示例;
- 对话摘要从每次请求都压缩改为每两轮压缩一次;
- 长期属性层只在事件发生时更新,不随每轮对话重复写入。
这些优化做完,一般能省30%-40%的输入token。注意要控制延迟,输入token越大,首字响应越慢。如果业务对延迟敏感,宁可牺牲一部分检索覆盖面,也要控制单次上送的总长度。
4.5 一些值得尝试的进阶技巧
最后分享几个容易提升上限的做法。一是上下文可视化调试,把每条线上请求的拼装结果导出成日志,人工预览模型到底看到了什么,这一步能发现大量预想不到的拼接错误。二是上下文版本化,把每次调整上下文结构的配置跟评测结果一起存档,方便回滚和对比。三是面向输出的上下文回注,当模型输出质量高时,把高质量输出的关键步骤摘要回写给记忆层,这样后者在后续轮次里可以沿着正确路径推理。
做过两三个完整项目的朋友应该都有同感:上下文工程到后期,做的不再是简单的拼字符串,而是一套带反馈的编排系统。输入侧要整理信息,输出侧要做评测,记忆侧要做更新,整个过程像在设计一套精密的控制系统。
我个人一直觉得,这项能力在未来一两年会从“少数人的实践经验”变成“AI应用开发的标配技能”。因为模型本身的能力正在趋同,各家底层大模型差距越来越小,比拼的最终就是应用层的信息编排水平。谁能把上下文用得又准又省,谁的应用体验就会明显更好。与其等工具成熟,不如从手头项目开始一步步做积累。