2932 字
约 9 分钟
15
S24-B06 · 上下文工程入门:给模型什么、不给什么
同一个 OrderService,问"这里有什么问题",答"整体结构清晰,建议补充注释";换成"用户重复提交订单时库存被扣两次,找出原因并给出最小改动",答案直接落在 deduct 的非幂等调用上。模型、温度、代码全都没变,变的只是上下文。所谓上下文工程,就是把这种"碰运气"做成可重复的动作:决定放什么、放多少、放在哪。
1. 先从一次失败的提问看起
❌ 我的 Spring 服务下单有问题,帮我看下
(附:OrderService.java 全文 800 行 + tail -n 2000 的日志)
✅ 目标:修掉重复下单导致的重复扣库存
现象:同一 orderNo 下 stock 被扣两次,两次 deduct 间隔 12ms
约束:只能改 OrderService 与 StockClient 调用处;不改表结构;不加新依赖
相关代码:OrderService#create(第 40-95 行)、StockClient#deduct
验收:并发 20 请求同一 orderNo,库存只减 1;现有 OrderServiceTest 全绿
| 变量 | 第一次 | 第二次 | 差异来自 |
|---|---|---|---|
| 模型/温度 | 相同 | 相同 | 不是这里 |
| 代码量 | 800 行整文件 | 两个类、两个方法 | 相关面 |
| 目标 | "有问题" | "重复扣库存" | 可判定性 |
| 约束 | 无 | 三条硬边界 | 搜索空间 |
| 验收 | 无 | 可观测断言 | 能否自检 |
答案质量的差别几乎全部由后三行决定。前两行是你最容易改、也最没用力的地方。
2. 窗口里真正占用的是什么
| 组成 | 典型占比 | 特点 | 变质速度 |
|---|---|---|---|
| 系统提示 / 规则 | 固定,常被低估 | 每轮都在,最稳定 | 慢 |
| 对话历史 | 随轮次线性增长 | 含大量已解决的问题 | 快 |
| 工具结果 / 日志 | 单次就可能上万 token | 原文噪声最密 | 极快 |
| 文件内容 | 由你挑选 | 决定"看得见什么" | 手动 |
| 当前提问 | 往往最少 | 决定"看哪里" | 无 |
"装得下"和"看懂"是两件事:窗口是注意力预算,不是硬盘。放在第三万 token 处的一句"不要改其他文件",与贴在提问正上方的一句,生效概率差别很大。判据因此不是"塞不塞得进去",而是"这一步是否真的需要它"。
3. 该给的五类,不该给的三类
| 该给的 | 具体写什么 | 反例 |
|---|---|---|
| 目标 | 一句话说清修什么、加什么 | "优化一下" |
| 约束 | 不能改的、不能加的、必须保持的 | 只说"注意兼容" |
| 相关代码 | 唯一的那条调用链与数据流 | 整个仓库、整个文件 |
| 错误信息 | 完整堆栈 + 触发输入 + 版本 | 截图里半行报错 |
| 验收标准 | 能跑出真/假的断言或命令 | "看着对就行" |
| 不该给的 | 为什么 | 替代做法 |
|---|---|---|
| 无关文件 | 稀释注意力,诱导跨文件乱改 | 只贴被调用到的类 |
| 整份日志 | 99% 是噪声,定位成本转给模型 | 先 grep 出 20 行关键行 |
| 闲聊历史 | 旧结论会盖住新事实 | 摘要成三行"已确认事实" |
4. "给多少"的判据:删掉它,行为会变吗
最小化不是删到最短,而是删到行为不变。把上下文当实验变量:
1. 固定任务与问法,跑一次基线,保存输出
2. 只删掉一块(一个文件 / 一段历史 / 一条日志)
3. 用同一问法重跑,比对新旧输出
输出不变 → 噪声,永久删掉
输出变差 → 必要信息,保留并注明为什么
输出变好 → 干扰项,删掉并记进"曾经害过我"清单
4. 对每块重复,最后得到最小充分集
判据只有这一条:删掉后行为是否变化。凭"这个可能有用"做加法,结果必然是越堆越多、越堆越糊。
5. 位置效应:关键约束放前面还是后面
| 位置 | 适合放什么 | 原因 |
|---|---|---|
| 最前(规则/开场) | 角色、不可违反的硬约束 | 每轮复述,衰减最慢 |
| 任务描述里 | 目标、验收标准 | 与"这一步做什么"绑定 |
| 代码之后 | 具体到行的指认 | 刚看完代码,指认有落点 |
| 最后一句 | 输出格式、"只改这一个方法" | 紧邻生成,指令权重最高 |
实践口诀:硬约束放最前,格式要求放最后,具体指认贴着代码。把"不要改其他文件"埋在中间段落,多半会被静默丢弃。
6. 格式的影响:结构化段落 vs 大段代码块
❌ 贴 300 行代码,末尾一句"看下哪里有问题"
→ 典型输出:一段综述,"建议加强幂等设计",指不到行
✅ 结构化字段 + 小片段
【位置】OrderService.java:78 create()
【现状】先 orderMapper.insert,再 stockClient.deduct
【问题】insert 成功后 deduct 超时重试,deduct 会再执行一次
【片段】
orderMapper.insert(order);
stockClient.deduct(item.getSku(), item.getQty());
【期望】同一 orderNo 只扣一次
| 格式 | 适用 | 成本 |
|---|---|---|
| 结构化字段(位置/现状/问题/期望) | 定位类问题 | 写的人多花一分钟 |
| 小代码块(<40 行,带行号) | 语法与逻辑细节 | 需要先裁剪 |
| 整文件代码块 | 只适合"通读并总结" | 注意力被摊薄 |
| 截图 | 几乎不用 | 不可搜索、不可复制、行号错位 |
7. 文件怎么选:三条可执行路径
| 路径 | 做法 | 依据 |
|---|---|---|
| 按报错堆栈 | 堆栈里每个类名都是一个候选文件 | at com.x.OrderService.create(OrderService.java:78) |
| 按 import 关系 | 从入口类向外一跳就停 | grep -rn "import .*StockClient" src/ |
| 按目录约定 | controller→service→mapper 顺着走 | ls src/main/java/com/x/{controller,service,mapper} |
# 从一次报错反推文件清单
grep -oE '\(([A-Za-z0-9_]+\.java):[0-9]+\)' err.log \
| sed -E 's/[()]//g; s/:[0-9]+//' | sort -u
# 输出就是该贴的文件名,通常 2-5 个
优先顺序:堆栈 > import > 目录。堆栈是运行时给的证据,目录只是人的想象。
8. 长历史怎么压缩
【已确认事实】(只写被验证过的)
- 重复扣库存发生在 deduct 重试路径,orderNo 未做幂等
- StockClient#deduct 不能改签名(下游三个服务共用)
【已排除】
- 不是唯一索引问题(orderNo 有唯一约束,但 insert 先成功)
【待办】
- deduct 前用 Redis SETNX(orderNo) 占位一次
规则是留结论、丢过程。"我试了 A 不行,又试了 B 不行"对下一步没有信息量;"B 不行,因为 X"是结论,必须留。压缩完最好换一轮新会话,把摘要当天花板重新开始。
9. 四类噪声
| 噪声 | 表现 | 处理 |
|---|---|---|
| 重复信息 | 同一段代码在历史里出现三次 | 只留最后一次,注明"以此为准" |
| 过期结论 | 上一轮判断已被推翻 | 显式标"已废弃",别让模型猜 |
| 矛盾指示 | 前面说可改表,后面说不许改表 | 合并成一条并写优先级 |
| 无关背景 | 顺带提的业务沿革 | 删 |
矛盾指示最贵:模型不会报错,它会自己挑一个,而你不知道它挑了哪个。
10. 一次只改一个变量
调上下文和调代码一样,同时改三处就分不清是谁起的作用:
第 1 轮:只改目标("有问题" → "重复扣库存")
第 2 轮:只改范围(整文件 → 两个方法)
第 3 轮:只改格式(大段代码 → 结构化字段)
第 4 轮:只改位置(约束从中间挪到最前)
每轮固定同一问法、同一模型、同一温度,逐轮记录输出
11. 最小示例:同一个 bug,三种上下文
L1(差)我的下单接口有问题,帮我看看。
→ 输出:泛泛的"建议加分布式锁、加幂等表",没有指认位置
L2(中)这个 create 会重复扣库存,帮我看下。
(附 OrderService 整文件 300 行)
→ 输出:指到 deduct 可能重试,但建议改 StockClient 签名——违反你的约束
L3(好)目标:同一 orderNo 只扣一次库存。
约束:不改 StockClient 签名,不改表结构,不加新依赖。
代码:OrderService.java:40-95 + StockClient#deduct(共约 30 行)
错误:deduct 超时后重试,两次间隔 12ms
验收:并发 20 请求同 orderNo,库存只减 1。
只改 create 方法,输出 diff。
→ 输出:SETNX 占位 + 失败释放的 diff,范围正确
三级差别不在模型,而在四件事:目标可判定、约束显式、范围收敛、验收可跑。
12. 排错
| 现象 | 原因 | 处理 |
|---|---|---|
| 答案笼统,全是"建议" | 目标不可判定 | 改写成能跑出真假的断言 |
| 改了不该改的文件 | 没有显式范围约束 | 加"只改 X,其他文件不动" |
| 指的行号对不上 | 贴的是旧版本代码 | 贴前记 git rev-parse --short HEAD |
| 反复推荐已排除的方案 | 排除结论没进上下文 | 写"已排除:A,因为 B" |
| 长会话后期答非所问 | 关键约束被历史挤淡 | 压成摘要,重开一轮 |
| 贴截图后还被问"代码在哪" | 图像文字不可靠 | 换成文本代码块 |
| 粘贴后缩进错乱 | 混了 tab 与空格、中文全角 | 统一 4 空格,先跑一次格式化 |
13. 边界
| 情况 | 更合适的做法 |
|---|---|
| 需要理解整个仓库结构 | 先产出目录级地图,再按图取文件 |
| 改动横跨 10 个以上文件 | 拆成多个单一目标的会话 |
| 需要精确行号与语法检查 | 用 IDE / 编译器,不用对话 |
| 判断"这段代码对不对" | 写测试,让测试给结论 |
| 需求本身还没定 | 先定需求,上下文工程救不了模糊需求 |
一句话:上下文工程管的是信息的选择与摆放,管不了需求没想清楚这件事。
14. 自测
- 为什么"装得下"和"看得懂"是两件事?
- 判断某块上下文该不该删,唯一的判据是什么?
- 硬约束应该放在上下文的什么位置,为什么?
- 堆栈、import 关系、目录约定,三者优先用哪个?
- 长历史压缩时,什么该留、什么该丢?
- 同一段代码三次问答结果不同,你会先固定哪个变量?
S24-B06 · 上下文工程入门:给模型什么、不给什么
https://www.clxhxhhr.top/posts/6136/ 评论
0 条
还没有评论,先写一条吧。