第 6 章:Context Selection
本章导读
- 核心问题:如何从大量候选信息中选择当前最有用的上下文。
- 关键词:Candidate Recall、Permission Filter、Scoring、Deduplication、Budget Packing
- 学习产出:为 CI 失败分析设计可解释的上下文选择策略。
1. 本章要解决什么问题
上一章我们拆解了 Context 的组成。
现在问题变成:
候选上下文很多,但模型一次只能看到有限内容,到底该选哪些?
这就是 Context Selection。
在 Agent 系统中,上下文来源可能非常多:
用户任务
对话历史
项目文档
代码文件
Git Diff
测试日志
工具结果
检索文档
长期记忆
环境信息
Policy
如果全部塞进模型,会出现三个问题:
- token 成本高;
- 延迟变长;
- 关键信息被噪声淹没。
因此 Context Selection 的目标不是“找所有相关信息”,而是:
为当前 Agent 的下一步决策,选择最小、最高信号、最可操作的一组上下文。
本章要回答:
- Context Selection 与 RAG 检索有什么区别;
- 什么是候选上下文、评分、排序、预算裁剪;
- 如何综合相关性、时效性、可信度、权限和 token 成本;
- 为什么不同任务阶段需要不同选择策略;
- 如何用 Java / Spring Boot 设计 ContextSelector。
2. 对应 Anthropic 文章
本章主要对应:
- Effective Context Engineering for AI Agents
Anthropic 在文章中强调:
good context engineering means finding the smallest possible set of high-signal tokens
这句话落到工程上,就是 Context Selection。
它不是简单的向量检索,而是一个完整的决策管线。
3. Context Selection 的核心思想
3.1 选择目标不是“相关”,而是“有用”
很多系统把上下文选择简化为相似度检索。
例如:
用户问题 → 向量检索 topK 文档 → 拼接给模型
这在简单问答中可行,但在 Agent 中远远不够。
因为 Agent 需要的不只是语义相关信息,而是对当前决策有用的信息。
例如用户任务是:
分析 CI 为什么失败。
语义相关的信息可能包括:
- CI 使用说明;
- 历史 CI 报告;
- 测试框架文档;
- 失败日志;
- 最近提交;
- 相关测试类;
- 构建脚本。
但当前第一步最有用的可能只是:
失败摘要
失败测试名
关键错误栈
最近 Git Diff
项目测试命令
所以 Selection 的标准应是:
对下一步行动是否有帮助?
而不只是“语义上像不像”。
3.2 选择是动态的
Context Selection 不是任务开始时做一次就结束。
Agent 每一轮都可能需要重新选择上下文。
例如 Coding Agent 的不同阶段:
| 阶段 | 优先上下文 |
|---|---|
| Explore | README、AGENTS.md、目录结构、相关文件路径 |
| Plan | 用户目标、代码结构、相似实现、约束 |
| Edit | 具体代码片段、接口定义、测试样例 |
| Test | 测试命令、失败输出、最近修改 |
| Fix | 错误栈、失败断言、相关实现 |
| Summarize | Git Diff、验证结果、风险列表 |
这说明 Context Selection 必须感知 Agent Phase。
3.3 选择必须受预算约束
如果没有预算,Selection 会变成无限追加。
Context Budget 至少要考虑:
- 最大输入 token;
- 预留输出 token;
- 每类上下文的预算;
- 工具定义预算;
- 最近工具结果预算;
- Memory 预算。
例如 32K 输入预算可以这样分配:
System / Policy: 2K
User Task / Task State: 3K
Project Knowledge: 4K
Code Context: 12K
Tool Results: 6K
Memory: 2K
Buffer: 3K
预算不是为了省钱而已,更是为了保持注意力质量。
3.4 选择需要可解释
生产环境中,必须能回答:
为什么这段文档进入了上下文?
为什么那段日志被丢弃?
为什么召回了这个历史 Memory?
为什么 Agent 没看到某个关键文件?
所以 Context Selection 应记录选择日志。
每个被选中的 ContextChunk 应包含:
- 来源;
- 分数;
- 选择原因;
- token 估算;
- 是否被压缩;
- 是否因权限过滤;
- 是否因预算裁剪。
这对调试 Agent 非常重要。
4. Context Selection 管线
一个可生产化的 Selection 管线可以分成六步。
Candidate Recall
↓
Permission Filter
↓
Scoring
↓
Deduplication
↓
Budget Packing
↓
Rendering Order
4.1 Candidate Recall:候选召回
候选上下文来自多个 Provider:
- ProjectKnowledgeProvider;
- CodeSearchProvider;
- GitDiffProvider;
- IssueProvider;
- ToolResultProvider;
- MemoryProvider;
- EnvironmentProvider。
召回阶段的目标是“尽量不要漏掉可能有用的信息”。
但召回结果还不能直接进入模型。
4.2 Permission Filter:权限过滤
进入评分前,先做权限过滤。
因为没有权限的信息不应该进入后续流程。
过滤依据包括:
- 用户权限;
- 项目权限;
- 数据敏感度;
- 工具权限;
- 当前任务模式;
- 是否需要脱敏。
注意:权限过滤必须由程序执行,不能依赖模型自觉。
4.3 Scoring:评分
评分可以综合多个因素:
score = relevance * 0.40
+ recency * 0.20
+ authority * 0.15
+ taskPhaseFit * 0.15
+ userPin * 0.10
- tokenCostPenalty
Relevance:相关性
与用户目标、当前计划、最近工具结果的相关程度。
Recency:时效性
最近产生的信息通常更重要。
例如当前测试失败输出比一周前的历史失败更重要。
Authority:权威性
不同来源可信度不同。
例如:
代码实际实现 > README 描述
测试输出 > Agent 猜测
用户明确要求 > 历史偏好
TaskPhaseFit:阶段匹配
当前阶段需要不同上下文。
Verify 阶段,测试输出优先级高;Plan 阶段,架构文档和相关代码优先级高。
TokenCostPenalty:成本惩罚
同等价值下,短上下文优先。
这符合“小而高信号”的原则。
4.4 Deduplication:去重
上下文经常重复。
例如:
- README 和 AGENTS.md 都包含测试命令;
- 工具结果和对话历史都包含错误摘要;
- Memory 和项目文档都提到同一约定;
- 多个搜索结果指向同一文件。
去重不是简单按文本相同判断,而是按语义和来源合并。
合并后应保留更权威、更短、更近期的版本。
4.5 Budget Packing:预算装箱
排序后还要放入有限预算。
常见策略:
- 高优先级必选;
- 每类上下文有上限;
- 长内容先压缩;
- 低分内容丢弃;
- 保留输出 token;
- 保留一定 buffer 给后续工具结果。
预算装箱可以类比背包问题:
价值 = 上下文效用
重量 = token 数
背包容量 = context budget
4.6 Rendering Order:渲染顺序
选中上下文后,还要决定顺序。
顺序会影响模型注意力。
推荐顺序:
1. System / Policy
2. User Task
3. Current Task State
4. High-priority Evidence
5. Relevant Project Knowledge
6. Relevant Code / Documents
7. Tool Results
8. Memory
9. Output Requirements
但不同任务可以调整。
例如故障排查时,关键错误证据应该靠前。
5. 静态 Context 与动态 Context
5.1 静态 Context
静态 Context 相对稳定,例如:
- System Prompt;
- 项目规范;
- AGENTS.md;
- 架构原则;
- 工具说明;
- 用户偏好。
静态 Context 适合在任务开始时加载。
但也不应全部加载,而应按任务类型选择。
5.2 动态 Context
动态 Context 随任务变化,例如:
- 最近工具结果;
- 当前测试输出;
- 当前 Git Diff;
- 当前计划;
- 最新用户反馈;
- 当前错误假设。
动态 Context 往往更接近当前决策,因此在很多阶段优先级更高。
5.3 二者的平衡
如果只给静态 Context,Agent 可能不了解最新情况。
如果只给动态 Context,Agent 可能忘记项目规则。
好的选择策略应该让二者平衡。
6. 架构图 / 流程图
6.1 Selection 管线
6.2 多因素评分
6.3 阶段感知选择
7. Java / Spring Boot 落地方案
7.1 ContextCandidate
public record ContextCandidate(
ContextChunk chunk,
double relevance,
double recency,
double authority,
double phaseFit,
double tokenCostPenalty,
List<String> reasons
) {
public double finalScore() {
return relevance * 0.40
+ recency * 0.20
+ authority * 0.15
+ phaseFit * 0.15
- tokenCostPenalty;
}
}
7.2 ContextScorer
public interface ContextScorer {
ContextCandidate score(ContextChunk chunk, ContextRequest request);
}
可以组合多个 scorer:
public class CompositeContextScorer implements ContextScorer {
private final List<ContextScorer> scorers;
@Override
public ContextCandidate score(ContextChunk chunk, ContextRequest request) {
// 合并 relevance、recency、authority、phaseFit 等分数
return null;
}
}
7.3 ContextSelector
public interface ContextSelector {
List<ContextChunk> select(
List<ContextChunk> candidates,
ContextRequest request,
ContextBudget budget
);
}
一个基础实现:
public class BudgetAwareContextSelector implements ContextSelector {
private final PermissionFilter permissionFilter;
private final ContextScorer scorer;
private final ContextDeduplicator deduplicator;
private final TokenEstimator tokenEstimator;
@Override
public List<ContextChunk> select(
List<ContextChunk> candidates,
ContextRequest request,
ContextBudget budget
) {
List<ContextChunk> allowed = permissionFilter.filter(candidates, request);
List<ContextCandidate> scored = allowed.stream()
.map(chunk -> scorer.score(chunk, request))
.sorted(Comparator.comparing(ContextCandidate::finalScore).reversed())
.toList();
List<ContextCandidate> deduped = deduplicator.deduplicate(scored);
return packWithinBudget(deduped, budget);
}
private List<ContextChunk> packWithinBudget(
List<ContextCandidate> candidates,
ContextBudget budget
) {
List<ContextChunk> selected = new ArrayList<>();
int used = 0;
for (ContextCandidate candidate : candidates) {
int tokens = tokenEstimator.estimate(candidate.chunk());
if (used + tokens <= budget.availableInputTokens()) {
selected.add(candidate.chunk());
used += tokens;
}
}
return selected;
}
}
7.4 SelectionTrace
为了可观测性,需要记录选择过程:
public record SelectionTrace(
String taskId,
List<SelectedContext> selected,
List<RejectedContext> rejected,
int totalCandidates,
int selectedTokens,
Instant createdAt
) {}
拒绝原因可能包括:
NO_PERMISSION
LOW_SCORE
DUPLICATE
BUDGET_EXCEEDED
EXPIRED
SENSITIVE
这类 trace 对调试非常有价值。
8. 常见误区
8.1 只靠向量相似度
向量相似度只能回答“语义相近”,不能回答:
- 是否新鲜;
- 是否权威;
- 是否有权限;
- 是否适合当前阶段;
- 是否值得 token 成本。
8.2 忽略工具结果
很多系统只检索静态文档,却忽略最新工具结果。
但 Agent 的下一步决策往往最依赖刚刚发生的观察。
8.3 没有预算意识
如果没有预算,系统会不断追加上下文,直到性能下降。
8.4 没有选择日志
当 Agent 答错时,如果不知道它看到了什么、没看到什么,就无法改进系统。
8.5 每个任务使用同一套选择策略
不同任务、不同阶段需要不同上下文。
固定 topK 检索不适合复杂 Agent。
9. 贯穿案例:CI 失败分析 Agent
在 build #4312 中,Context Selection 的目标是从大量候选信息中选择最能支持下一步判断的内容。
候选上下文可能包括:
完整 CI 日志:20000 tokens
关键错误栈:1200 tokens
PR Diff 摘要:4000 tokens
AGENTS.md:1000 tokens
README 全文:8000 tokens
历史类似失败:1500 tokens
无关模块代码:6000 tokens
一个合理选择是:
必选:用户任务、只读 Policy、CI 失败摘要、关键错误栈
高优先级:PR Diff 摘要、AGENTS.md 测试规则
按需加载:完整日志、相关代码文件
可选:历史类似失败
丢弃:无关模块代码、README 全文
选择依据不是简单语义相似度,而是:
- 是否直接支持当前根因判断;
- 是否新鲜;
- 是否来自权威来源;
- 是否符合当前阶段;
- token 成本是否合理;
- 当前用户是否有权限。
因此,CI 失败分析 Agent 的 ContextSelector 应优先选择关键错误、最近变更和项目规则,而不是盲目 topK 检索。
10. 本章小结
本章讨论了 Context Selection。
核心结论:
- Context Selection 选择的是“对下一步决策有用”的信息,而不只是语义相关的信息。
- Selection 是动态过程,应随 Agent 阶段变化。
- 选择策略要综合相关性、时效性、权威性、阶段匹配、权限和 token 成本。
- Context Budget 是选择过程的硬约束。
- 生产级 Agent 必须记录 SelectionTrace。
一句话总结:
Context Selection 的目标,是在有限注意力预算中,把模型下一步最需要的信息放到最显眼的位置。
11. 实践任务
任务 1:实现 ContextScorer
实现一个基础评分器,输入 ContextChunk,输出:
relevance
recency
authority
phaseFit
tokenCostPenalty
finalScore
任务 2:设计阶段化选择策略
为 Coding Agent 的以下阶段设计上下文优先级:
Explore
Plan
Edit
Test
Fix
Summarize
任务 3:记录 SelectionTrace
设计一张数据库表保存上下文选择日志。
至少包含:
- task_id;
- chunk_id;
- selected;
- score;
- reason;
- token_estimate;
- created_at。
任务 4:CI 失败分析选择实验
给定一份 CI 日志、Git Diff 和 AGENTS.md,手工选出 10 条最应该进入模型的上下文,并说明理由。