Skip to content

上下文工程 Context Engineering ​

看在前面 ​

2025-2026 年 AI 岗面试的新高频:「上下文工程和提示词工程什么关系?」「多轮对话上下文怎么管?」。一句话定位:提示词工程优化"一段指令",上下文工程优化"整个窗口里所有东西"。系统提示、工具定义、检索片段、对话历史、中间推理、输出,每一项都在争抢同一个有限预算。

这篇按四个决策点写:预算配比、长对话压缩、位置效应(开头结尾 vs 中间)、与系统提示的稳定性设计。素材延续 ai-service 的实践(finance_diagnosis.py 的 SYSTEM_PROMPT、_render 结构化输入)。概念篇基础见 LLM基础-Token上下文与幻觉。检索片段怎么进窗口见 RAG体系与项目定位。提示词逐条解剖见系统提示词工程-从财务诊断说起。

一、窗口是预算表:五个账户抢一个钱包 ​

上下文窗口(输入 + 输出共享,如 128K)不是"够用就行"的容量问题,是要显式做预算分配的稀缺资源:

text
┌────────────────────────── 上下文窗口(预算 N)──────────────────────────┐
│ 系统提示      │ 工具定义      │ 历史          │ 检索/数据     │ 输出预留   │
│ 角色/规则/契约│ schema+docstr│ 多轮消息      │ RAG片段/表格  │ max_tokens │
│   ~5-10%     │   10-30%     │   20-60%      │   10-40%     │   硬上限    │
└──────────────────────────────────────────────────────────────────────┴
  稳定不变       按需注册        可压缩可裁剪     越相关越好      留不够=截断

四条配比纪律(面试可直接引用):

  1. 每一项都要付租金:工具定义即使从不被调用也占 token。LLM 每轮都"看见"全部 schema,10 个工具塞进去不选中,白交 10 份租金。工具按 agent 场景裁剪注册是上下文工程最立竿见影的优化之一;
  2. 输出预算是硬约束不是愿望:max_tokens 留不够,答案写到一半被截。提示词里"不超过 300 字"与 API 参数双保险(前者概率约束,后者物理约束);
  3. 结构化输入最省:ai-service 的 _render 把三张表渲染成 6 行清单而不是甩原始 JSON。同等信息量,清单形态 token 减半且注意力集中。数字带参考区间和【偏高】标记,判断依据预先算好;
  4. 窗口变大不等于可以乱塞:128K 窗口塞 100K 无关内容。成本(token 计费)、延迟(prefill 变慢)、质量(lost in the middle)三头受损。"放得下"不等于"用得好"(详见姊妹篇 KV cache 一节)。

拿一个"智能客服"会话做预算账(32K 窗口示例,数量级示意):

text
账户            内容                          占用        治理动作
system          角色+红线+输出契约             ~0.6k       常量版本化,季度评审
工具定义        6 个工具 schema+说明           ~2.5k       从 10 个裁到 6 个(按场景注册)
检索片段        RAG top-5(rerank 后)         ~2.0k       片段瘦身,最相关放靠前
历史            近 8 轮原文 + 旧轮滚动摘要     ~4.0k       token 阈值触发压缩
本轮输入        用户问题+工单上下文            ~0.4k       结构化渲染
输出预留        max_tokens                    ~1.0k       与"350字以内"双保险
─────────────────────────────────────────────
合计 ~10.5k / 32K:留有余量,而不是把窗口当免费仓库塞到 30k

二、长对话压缩:历史不是档案库,是工作记忆 ​

多轮对话无限累积历史必然撞墙。三级压缩策略,从轻到重:

text
第 1 级:滑动窗口
  保留最近 K 轮(系统提示永远在),旧轮整段丢弃
  实现:messages = [system] + history[-K*2:] + [user]
  优点简单;缺点"用户第 2 轮说的偏好"第 20 轮被丢掉,答非所问

第 2 级:摘要压缩
  超出窗口的旧轮用 LLM 压成一段滚动摘要(含关键事实/决策/偏好),替换原文
  ┌ system ─┬─ 摘要:用户在咨询家庭财务,已确认月收入2万,有房贷... ─┬─ 近K轮原文 ─┐
  保留语义骨架,成本 = 一次额外的摘要调用;要点是摘要提示词要钉死"保留数字/结论/约束"

第 3 级:记忆外置(RAG for memory)
  历史全部落库(向量+元数据),每轮只检索相关片段进窗口
  对话即检索,窗口永远只装"这轮需要的";代价是检索错了记忆就丢了

工程取舍句式:短对话滑动窗口够用。长任务(客服工单、诊疗随访)摘要 + 外置记忆组合。摘要的触发点用 token 阈值而不是轮数。轮数与 token 不成正比,一条消息塞表格能顶十轮闲聊。另一个常被忽略的坑:工具调用的中间结果(一次返回 5K token 的查询结果)比寒暄更该被压缩。它是"已消费的输入",摘要成一句"查询到 3 笔异常交易,明细已存"就够后续轮使用。

三、位置效应:开头结尾强,中间是盲区 ​

LLM 对窗口内信息的注意力不均匀。开头(primacy)和结尾(recency) recalled 好,中间显著变差(lost in the middle 现象)。姊妹篇幻觉三成因里"上下文挤压"的实验依据就是这个现象。对上下文工程的三条直接推论:

  • 指令放两头,数据放中间:系统提示在开头(对话模板里 system 天然在首位)。最新用户输入/临场指令在结尾。长的检索片段、表格数据放中段——布局服从注意力分布,而不是服从叙述顺序;

  • 长文档问答时建议关键段排前:rerank 之后不只排序,还负责"把最相关的放在提示词里靠前的位置"。这是检索质量向生成质量的最后一跳;

  • 多轮对话里 system 的地位是"每轮复读":system prompt 每一轮都完整出现在窗口开头。这是它比"第一轮说过一次"的用户消息稳定的原因。ai-service 把角色、红线、输出契约全部放 SYSTEM_PROMPT(ChatPromptTemplate.from_messages 的 ("system", ...)),正是利用这个位置特权。跨轮稳定的约束进 system(开头恒定),单次数据进 human(结尾最新)。防注入的分界线也在这里(system 是可信区,human 数据用分隔符圈住,见提示词篇)。

  • few-shot 示例同样吃顺序效应:示例放指令后、问题前,且最后一个示例的影响最大(离生成位置最近)。最需要模型模仿的样板放末尾(详见 Few-shot与思维链)。

四、system prompt 的工程化:它是代码不是文案 ​

python
# ai-service finance_diagnosis.py 的 SYSTEM_PROMPT(线上迭代过的样本)
SYSTEM_PROMPT = """你是家庭财务顾问。请基于给出的家庭财务数据做健康诊断:
1. 指出 3 个以内的财务风险,每个风险引用具体数据;
2. 给出对应的改善建议;
3. 全文 350 字以内,不要使用 Markdown 格式。
"""

注:此处为简化示意(4 行版);线上完整 6 行版(含三句话概括与免责声明)见 系统提示词工程-从财务诊断说起。

从上下文工程视角看 4 行各自的作用:角色句收敛输出分布("财务顾问"排除闲聊/泛泛建议)。"引用具体数据"锚定注意力到中段的结构化输入,抑制幻觉。数量约束防罗列。字数与格式约束同时服务三头——产品(下游渲染契约)、成本(输出 token 是计费大头)、窗口(输出占预算)。这四条合起来是"上下文预算的显式管理"在提示词层的落地。

工程化配套:SYSTEM_PROMPT 用常量版本化(迁入不改写)、温度钉在工厂(蒸馏任务 0.2,输出方差小、A/B 才可比)。详见提示词篇,此处只强调一点:system prompt 是整个上下文里唯一"每轮都生效"的杠杆。它的每一个字都该能回答"删除会坏什么"。

五、Agent 循环:上下文工程的放大场景 ​

ReAct 式智能体把预算问题放大到极限。每轮循环的窗口都是上一轮的全文 + 新观察,不管理就是指数膨胀:

text
第 1 轮:system(0.5k) + 工具定义(3k) + 问题(0.2k) + 思考/行动(0.3k)          ≈ 4k
第 2 轮:上面全部 + 工具返回(5k) + 新思考(0.3k)                             ≈ 9k
第 5 轮:累计 5 次工具返回(各 2~5k)+ 历史思考                            ≈ 30k+
   ↑ 三条失控源:工具返回原样全量进窗口、循环上限太高、失败重试也留痕

治理清单(对照 ai-service 的 ReAct 实践,详见 ReAct与LangGraph工程实践):

  1. 工具返回即压缩:进入历史前截断/摘要("查询成功,3 条记录已存")。明细放工具侧缓存,模型要细节再查。"已消费输入"不占下一轮预算;
  2. 循环上限 + 预算熔断双闸:recursion_limit 防逻辑死循环。token 预算熔断防"合法但昂贵"的循环。两个闸各管一种失控;
  3. 失败观察要瘦身:堆栈 500 行进窗口毫无价值。给模型的失败信息只要"哪里错 + 可纠正的下一步"。喂模型的业务失败与拦在模型外的系统故障分层,见智能体篇;
  4. 长任务换外置记忆:跨天/跨会话的 agent 任务,历史全量落库。每轮只装"任务目标摘要 + 近期行动 + 相关记忆检索结果"。对话窗口只是执行缓存,不是任务数据库。

一句话收束:单轮对话的上下文工程是配比问题,agent 循环的上下文工程是生命周期问题。进窗口的东西都要回答"它对第 N 轮还有用吗,什么时候退役"。

追问链 ​

面试官:上下文工程和提示词工程的区别?

范围不同。提示词工程优化单段指令的措辞与结构。上下文工程把窗口当作一个整体系统设计。要回答:什么进窗口(检索/记忆/工具)、进多少(预算配比)、放哪里(位置效应)、活多久(压缩/淘汰)。提示词工程是上下文工程的子集。2026 年的岗位 JD 已经普遍用后者,因为它覆盖了 agent 时代的真问题:工具定义膨胀和多轮历史失控。

面试官:多轮对话里用户第一轮说了重要约束,第 20 轮模型忘了,怎么办?

先归因再动手:滑动窗口把那轮丢了(→ 摘要压缩/外置记忆)。内容还在窗口但位置太深注意力稀释(→ 关键约束提取后并入 system 或每轮复读)。本身表述模糊(→ 主动确认)。先判断是"不在窗口"还是"在但没用上",两种病两种药——这个归因顺序是回答的核心。

面试官:工具太多导致系统变笨,怎么治?

工具定义占预算 + 同类工具在语义空间重叠导致误调。三层治法:一是裁剪(按 agent 场景注册子集)。二是合并(三个查询工具合成一个带参数的工具)。三是分层(先路由到子 agent,子 agent 只带自己那组工具)。评估指标从"工具召回率"换成"误调率 + 完成率"一起看。

面试官:摘要压缩会不会丢信息?丢了的谁负责?

会丢,所以要设计"不可丢清单":摘要提示词钉死数字、结论、用户显式约束必须保留。关键事实不参与摘要而是结构化外置(键值对/数据库),每轮随 system 注入。摘要管叙述,结构化记忆管事实,两层分工比单层摘要可靠。

面试官:为什么 RAG 片段放中间,不怕 lost in the middle 吗?

怕,所以顺序是 rerank 决定的:最相关的放最靠前,次要的靠后垫底,而"指令 + 问题"在结尾再拉一次注意力。同时片段本身要小而精(切分策略见 RAG 篇)。10 个精准片段比 50 个混杂片段既省预算又避开中段盲区。位置是免费的优化,只用排个序。

面试官:system prompt 写多长合适,越长越稳吗?

不是线性关系:约束的有效性随长度递减(指令越多,单条被遵守的概率越低)。超过一屏后"约束打架"和"互相稀释"开始出现。工程做法:每条约束过"删除会坏什么"评审(提示词篇的纪律),合并不同类项,红线放前、示例放后。几十行的巨型 system prompt 通常是把"应该做进代码的校验"错放进了提示词。确定性逻辑回代码,提示词只留概率层做不到的部分。

面试官视角小结 ​

  1. 定位:上下文工程 = 管理整个窗口的系统工程,提示词工程是其子集。窗口是预算不是容量;
  2. 配比:系统提示/工具定义/历史/数据/输出五账户。工具按需注册、输出双保险(提示词 + max_tokens)。结构化输入最省。塞满窗口三头受损(成本/延迟/注意力);
  3. 压缩:滑动窗口 → 摘要(钉死数字与结论的滚动摘要)→ 记忆外置(对话即检索)。触发用 token 阈值。已消费的工具返回值优先压缩;
  4. 位置:开头结尾强、中间盲区(lost in the middle)。指令两头、数据中段。system 每轮复读所以是跨轮稳定的唯一杠杆;
  5. system prompt 工程化:常量版本化、每行答得出"删除会坏什么"。与低温/结构化输入配合把输出压进窄轨道。ai-service 的 SYSTEM_PROMPT 是可直接引用的样本;
  6. agent 循环:每轮窗口 = 上轮全文 + 新观察。工具返回即压缩、循环上限 + token 熔断双闸、长任务记忆外置。单轮是配比问题,循环是生命周期问题。

2026-10 新增(Round1 补缺)。