Context Engineering 实战 03|运行时该给模型看什么:动态上下文组装

Context Engineering 实战 03|运行时该给模型看什么:动态上下文组装

AIReview 的 code review 系统有 200 条 review 规则,早期全量塞进 context。上线第一周就发现问题:模型给一个纯前端的 React 组件提了"SQL 查询应该加索引"的 review 意见。

排查后发现不是个例。200 条规则全量注入时,模型的行为分成三类:

模型行为               比例    说明
────────             ────   ─────────
正确遵循相关规则        12%    审到点子上了
强行套用不相关规则       16%    给前端组件查 SQL 注入
忽略规则               72%    大部分规则直接忽视

只有 12% 的规则被正确使用。更多的规则被忽略或乱用。

改成动态选择后——按文件类型 + diff 内容选 10-15 条相关规则注入——遵循率从 45% 到 83%,误报(强行套用不相关规则)从 22% 降到 4%。

这个变化不是 prompt 优化——是把"拼字符串"变成了"pipeline 工程"。


五阶段 Pipeline

把上下文组装拆成五步,每步做一件事:

Source → Filter → Rank → Trim → Assemble
  │        │        │       │        │
  │        │        │       │        └→ 按注意力曲线排列位置
  │        │        │       └→ 按 token 预算裁剪
  │        │        └→ 按语义相关度排序
  │        └→ 按硬规则过滤不相关的
  └→ 收集所有候选 context
阶段输入操作输出可观测指标
Source用户请求收集所有可用的 context 来源候选集合来源数、总 token
Filter候选集合按类型/语言/条件过滤过滤后的集合过滤比例
Rank过滤后的集合embedding 语义排序排序后的 top-k最低相关度
Trim排序后的列表按预算裁剪低优先级项预算内的 context截断条数
Assemble预算内 context按 U 形注意力排列最终 prompt各部分占比

每个阶段可以独立优化、独立加日志、独立出 bug。这是"工程化"的核心——拆到每一步都可以单独诊断。


Source:先把所有牌亮出来

一次 AIReview 的 code review 请求,所有可能对判断有帮助的信息:

来源                  内容                        预估 token
──────              ──────                      ────────
diff                本次代码变更                  200-2000
file_context        被修改文件的完整内容            500-5000
related_files       import 链上下游文件            500-3000
review_rules        200 条 review 规则            6000
pr_description      PR 描述和 commit message      100-500
history_comments    这个文件的历史 review 意见      200-1000
author_patterns     该作者的历史踩坑模式            100-500

总计可能 8000-18000 token。而 context window 里给动态内容的预算只有 4000-6000 token(system prompt、工具定义、对话历史已经占掉了一大块)。

这一步不做任何过滤——先把所有可能有用的信息源列出来。


Filter:硬规则砍掉明确不相关的

Filter 用确定性规则做粗筛。不做语义理解——只做条件匹配。

def filter_rules(all_rules, diff_info):
    filtered = []
    for rule in all_rules:
        if rule.file_types and diff_info.file_type not in rule.file_types:
            continue
        if rule.languages and diff_info.language not in rule.languages:
            continue
        if rule.change_types and diff_info.change_type not in rule.change_types:
            continue
        filtered.append(rule)
    return filtered

一个 .tsx 文件的 diff 进来,SQL 规则、Python 规则、Go 规则全部砍掉。200 条 → 30-50 条。

Filter 的原则:宁可多留,不要误删。 这一步砍掉的应该是"明确不相关"的——给 React 组件检查 SQL 注入显然不相关。但"函数不要超过 50 行"这种通用规则,留着,交给下一步判断。

Filter 出过一次 bug:一条 “API 返回格式统一” 的规则被标记为 languages: ["python"],但实际上前后端都需要这条规则。Filter 把它在 TypeScript 的 review 中过滤掉了,导致几个 API 返回格式不一致的问题漏检。

修复:给这条规则的 languages 字段改成空(不限语言),让它进入 Rank 阶段由语义匹配来决定。Filter 的规则要尽量宽松——宁可多进一些到 Rank,不要在这里误杀。


Rank:语义匹配选最相关的

Filter 之后的 30-50 条都是"可能相关"的。Rank 用 embedding 相似度找出"最相关"的。

def rank_rules(filtered_rules, diff_text, top_k=15):
    diff_emb = embed(diff_text)
    scored = []
    for rule in filtered_rules:
        score = cosine_similarity(diff_emb, rule.embedding)
        scored.append((rule, score))
    scored.sort(key=lambda x: x[1], reverse=True)
    return scored[:top_k]

工程细节:

规则 embedding 预计算。 200 条规则的 embedding 在启动时算好存缓存。运行时只需要算一次 diff 的 embedding + 200 次余弦相似度计算。延迟在 50ms 以内。

top_k 不是固定的。 设一个相关度下限(比如 0.65)。如果 top-15 里只有 8 条超过 0.65,就只注入 8 条。宁缺毋滥——一条不相关的规则比少一条相关的规则更有害。

AIReview 实际运行中发现的一个问题:Rank 阶段选出的 top-15 里偶尔会有两条互相矛盾的规则。比如一条说"错误处理用 try-catch",另一条说"这个模块用 Result 模式不用 try-catch"。两条的 embedding 跟 diff 都相关(因为 diff 里确实有错误处理代码),但同时注入会让模型困惑。

解决方法:在 Rank 之后加一步去重——如果两条规则的 embedding 相互相似度大于 0.9,只保留跟 diff 相关度更高的那条。


Trim:预算裁剪

Rank 之后可能还超预算。Trim 不是从后面简单截断——而是按优先级压缩。

优先级    规则类型    处理方式
──────   ──────    ─────────
P0       安全类     保留完整描述,不压缩
P1       Bug 类    保留完整描述
P2       性能类     压缩到一句话摘要
P3       风格类     超预算时第一批被砍

一条 P2 规则从"当函数参数超过 5 个时,应该考虑将参数封装为对象,这样可以提高可读性和可维护性,并且方便添加默认值和验证逻辑"(48 token)压缩成"参数 > 5 个时封装为对象"(12 token)。

Trim 的预算不要用满。留 10-15% 的余量——context 完全塞满的模型输出质量会下降,需要给模型"思考空间"。


Assemble:位置排列

最后把选好的信息按正确的位置拼装。Transformer 的注意力分布是 U 形——开头和结尾高、中间低。

AIReview 最终的拼装顺序和每块的实际 token 预算:

位置          内容                            token     原因
────        ──────                          ─────    ────
开头         Base Layer(身份 + 输出格式)      300      注意力最高区
紧跟开头     动态 review 规则(top-k)          400      重要规则在高注意力区
中段上部     相关文件上下文                     800      辅助信息,注意力低一点也行
中段下部     PR 描述 + 历史 review 意见         300      背景信息
紧贴用户输入  diff 内容                         500      模型处理时会重点关注
结尾         Guard Layer(安全约束)             50       U 形右端高注意力区
                                            ─────
                                       总计 2350

跟全量注入的对比:

方式             总 token    规则遵循率    误报率
──────          ────────   ─────────   ─────
全量注入          ~12000     45%         22%
Pipeline 动态选择  ~2350     83%         4%

token 减少 80%,遵循率翻倍,误报率降到五分之一。


Pipeline 的可观测性:出问题时 5 分钟定位

每个阶段加一行日志。不用复杂的监控系统——在每个阶段输出关键数字就行:

[Source]    sources=7, total_tokens≈14200
[Filter]    rules: 200→38, file_type=typescript, language=ts
[Rank]      top_k=15, min_score=0.71, max_score=0.94
[Trim]      budget=500, actual=480, trimmed=0
[Assemble]  total=2350, rules=17%, diff=21%, file_ctx=34%

一次真实的排查过程:模型在 review 一个 Python 文件时漏掉了明显的类型错误。看日志:

[Filter]    rules: 200→42, file_type=python
[Rank]      top_k=15, min_score=0.58, max_score=0.87

min_score 只有 0.58——低于通常的 0.65 下限。说明 Rank 阶段选出的 top-15 里有相关度很低的规则混入。追查发现:Python 类型相关的规则描述用的是"type hints"和"annotation",但 diff 里用的是"typing"这个关键词。embedding 空间里"typing"和"type hints"的相似度只有 0.62。

修复:给 Python 类型规则的描述里补上"typing"关键词,重新算 embedding。修复后 min_score 回到 0.72。

没有 pipeline 日志,这个问题的排查路径是:看最终输出 → 猜是哪个规则漏了 → 猜是模型没遵循还是规则没注入 → 手动检查 context。几个小时。有了日志,从 Rank 的 min_score 异常直接定位,20 分钟。


三个常见误区

误区 1:全塞。 "为了不遗漏,所有信息都给模型看。"结果:45% 遵循率。

误区 2:随机选。 "随机抽 15 条应该差不多?"实测:随机 15 条的遵循率 52%——比全塞好一些(token 少了),但远不如语义匹配的 83%。随机选中的规则可能跟当前 diff 无关。

误区 3:只按长度截断。 "超预算了从后面砍。"后面的可能恰好是最重要的。应该先 Rank 再 Trim——排序在前,截断在后。

方式              选中规则数    遵循率    误报率
──────           ─────────   ──────   ─────
全塞 200 条        200         45%     22%
随机选 15 条       15          52%     14%
语义匹配 top-15    15          83%     4%

好的 Context Engineering 不是写得多,是选得准。把上下文组装当 data pipeline 来设计,每个阶段都可以独立优化和测试。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值