Skip to content

Repo Context Builder

本课交付结果

你将交付 RepoContextBuilder.build(input):失败文件优先,其次引用来源,再按搜索分数排序;相同文件的重叠行范围只保留更高优先项,完整片段在字符预算内装箱并携带 reason。

岗位问题

搜索、失败解析和引用分析会产生重复候选。同一函数的 1–20 行与 10–30 行同时进入上下文既浪费预算,也让模型误以为是两份证据。只按分数排序又可能把真正失败文件挤到后面。

前置检查

前置知识快照

先完成第 10 课基础预算:

bash
pnpm --filter @learn-traeai/coding-agent-bootcamp verify:lesson -- 10

候选必须带 path、start/end、content、reason 和 score;行范围是闭区间且从 1 开始。

第10课只按score做稳定装箱,本课加入代码任务语义:当前失败文件最重要,引用定义通常比普通文本搜索可靠,重复行窗口不应占两次预算。Builder消费已安全读取的候选,不自行遍历仓库或执行搜索。

行范围是证据身份的一部分。start:1,end:105,12在同文件重叠,若同时出现会重复上下文;不同文件相同行号不冲突。content必须确实对应范围,Builder不盲目合并,因为没有重新读取合并后的准确文本。

原理拆解

排序键依次为:是否是 failureFile、reason 优先级、score、原始索引。遍历时先检查与已选范围是否重叠,再检查完整 content 是否容纳;放不下或重叠都标记 truncated,但继续尝试后续小片段。

mermaid
flowchart TD
  A[failureFile + candidates + maxChars] --> B[校验预算与行范围]
  B --> C[携带原始index]
  C --> D[失败文件优先]
  D --> E[reference > failure reason > search]
  E --> F[score降序 + index稳定]
  F --> G{与已选同路径范围重叠?}
  G -- 是 --> H[省略并标记truncated]
  G -- 否 --> I{完整content装得下?}
  I -- 否 --> H
  I -- 是 --> J[复制加入并累计字符]
  H --> K[继续后续候选]
  J --> K
  K --> L[items + usedChars + truncated]

failureFile优先级为零,无论candidate自身reason与score,只要path等于失败文件就排前。测试故意让失败文件低分,证明错误定位证据不会被高分普通搜索挤掉。若失败文件有多个候选,仍按reason与score内部排序。

非失败文件reason优先级reference一、failure二、search三。这里candidate reason failure表示失败相关证据,但显式failureFile更强;引用来源通常是符号定义或调用关系,比关键词命中更接近语义。具体映射应由评测调整,排序规则必须显式。

同一优先级score降序,完全同分保留原始index。稳定性让相同输入产生相同Prompt和Eval;生产候选来自并发检索时原始顺序可能不稳,可用稳定candidate id作为最终tie-break。

重叠条件是闭区间交集:a.start <= b.end && b.start <= a.end。1–10与10–20在第10行重叠,只保留高优先;1–9与10–20不重叠。不要用字符content相似度替代明确文件行范围。

只与已选items比较,而不是所有排名更高候选。高分候选若因预算太大没选,不应阻止后续与它重叠但更小、可容纳的窗口;选择事实决定去重集合。检查顺序“重叠后预算”主要影响省略原因,最终truncated都为true。

过大候选使用continue,不break。预算三,失败片段四字符放不下,后续reference两字符仍可加入。这个性质继承第10课,避免一个巨大失败文件浪费所有上下文。

truncated表示至少有候选因重叠或预算省略,不表示items内容被截断。Builder永远保留完整content;上层看到true可以读取更小窗口、分轮构建或在回答中声明证据不完整。

输入行范围先校验整数、start>=1、end>=start。非法候选即使最终预算放不下也要拒绝,避免坏证据悄悄混入Trace或排序。maxChars必须非负整数,零合法并返回空items与存在候选时truncated。

代码实验

失败实现

只按score原地排序并截字符串会破坏输入与证据:

ts
candidates.sort((a, b) => b.score - a.score);
return candidates.map((item) => item.content).join("\n").slice(0, maxChars);

正确排序携带index,并在完整候选级装箱:

ts
const reasonPriority = { reference: 1, failure: 2, search: 3 } as const;
const ranked = input.candidates.map((item, index) => ({ item, index }))
  .sort((a, b) => {
    const af = a.item.path === input.failureFile ? 0 : reasonPriority[a.item.reason];
    const bf = b.item.path === input.failureFile ? 0 : reasonPriority[b.item.reason];
    return af - bf || b.item.score - a.item.score || a.index - b.index;
  });

for (const { item } of ranked) {
  if (items.some((selected) => overlaps(selected, item))) {
    omitted = true;
    continue;
  }
  if (usedChars + item.content.length > input.maxChars) {
    omitted = true;
    continue;
  }
  items.push({ ...item });
  usedChars += item.content.length;
}

打开本课 Lab

bash
pnpm --dir bootcamps/coding-agent/labs/25-repo-context-builder/starter test
pnpm --dir bootcamps/coding-agent/labs/25-repo-context-builder/solution test

Starter 应声明 实现上下文范围去重;Solution 应通过 5 项测试。

失败大片段跳过测试同时验证优先级与预算:

ts
const result = builder.build({
  maxChars: 3,
  candidates: [
    c("a.ts", 1, 1, "1234", "failure", 10),
    c("b.ts", 1, 1, "12", "reference", 1),
  ],
});
expect(result.items).toEqual([
  c("b.ts", 1, 1, "12", "reference", 1),
]);
expect(result).toMatchObject({ usedChars: 2, truncated: true });

关键实现讲解

重叠条件为同路径且 a.start <= b.end && b.start <= a.end。保留显式行窗口,不把两段盲目合并,因为合并后的 content 需要重新读取才能与行号一致。返回候选副本,保持调用方输入不变。

浅复制对当前标量字段足够;未来metadata嵌套需schema复制。输入签名可改为readonly,内部从不sort原数组。连续build同一候选应输出相等,Trace中原始候选顺序不因调用改变。

字符预算是教学近似,生产用渲染后token成本,包括路径标题、行号、围栏与reason标签。选择算法可注入cost函数,仍保持完整候选、稳定排序与continue语义。预算计算必须与实际Prompt模板一致。

运行与验证

真实运行输出

bash
pnpm --filter @learn-traeai/coding-agent-bootcamp verify:lesson -- 25
pnpm --filter @coding-agent/repo-context test

检查低分 failureFile 仍排第一,reference 胜过高分普通 search,1–10 与 5–12 只留一段;超大片段被跳过后仍可放入后续小片段。

真实Solution五项应全部通过:

text
solution: 5 passed, 0 failed
覆盖: failureFile置顶 / reference优先 / 保留行窗 / 重叠去重 / 预算与truncated

再测试边界相邻与端点重叠、不同文件相同行、非法范围、零预算、同分稳定、输入冻结和两个重叠候选中高优项因预算未选的情况。

常见失败与排查

故障案例 1

真正失败文件被高分搜索挤出上下文。

症状:模型看到大量相关调用点,却缺少测试明确报错的文件。

根因:排序只看通用score,没有任务级failureFile优先键。

定位:给失败文件低分、普通搜索高分,检查第一项与预算选择。

修复:显式failureFile优先级零,再比较reason与score;排序规则进入Trace。

故障案例 2

同一函数出现两遍浪费预算。

症状:1–20和10–30窗口同时发送,模型误以为是不同证据。

根因:按content或候选ID去重,没有比较同路径闭区间重叠。

定位:构造1–10、5–12、11–20三段,前两重叠,第三与第一相邻不重叠。

修复:对已选范围使用标准区间交集;保留更高排名完整窗口。

故障案例 3

一个超大失败片段导致上下文为空。

症状:预算仍可容纳小reference,但Builder遇到第一项后直接结束。

根因:装不下时break,错误假设后续候选都更大。

定位:高优四字符、低优两字符、预算三,检查低优是否入选。

修复:过大标记omitted并continue;上游为失败文件生成更小语义窗口。

  • 全局按 score:失败现场被普通匹配挤掉。
  • 只按 path 去重:同文件不同函数被误删。
  • 发现大片段放不下就 break:浪费剩余预算。
  • 丢掉 reason:Trace 无法解释选取依据。

课后作业

加入 import graph、测试到实现的引用边和 token 计数器;对重叠片段实现重新读取后的安全合并,并比较与简单去重的 Eval 结果。

验收 Rubric

维度通过标准常见扣分
排序failure → reference → search只看分数
范围同文件闭区间正确去重只按路径或不去重
预算完整片段、可跳过大项任意截断或提前停止
解释每段保留 reason无法追溯来源

总项目增量

总项目包:@coding-agent/repo-context

总项目路径:packages/repo-context/src/context-builder.ts

总项目验证命令:pnpm --filter @coding-agent/repo-context test

Week 5 完成计划—事件—检查点—指令—上下文链路。Week 6 将为整条链加入 Trace、查询 UI 与评测。

延伸阅读

方案对比与工程取舍

简单拼接所有结果召回高但噪声和成本无界;纯score top-k紧凑却忽略失败与重复;MMR引入相似度提升多样性但参数难解释;本课分层优先+范围去重是确定、可审计基线。生产可在保持硬安全不变量上增加reranker。

failureFile是强先验,但Parser可能误判或路径已过期。候选仍要来自安全读取并验证存在;无法读取时记录省略原因,不让空占位消耗优先级。多失败文件可扩展为集合与错误顺序,不用单字符串硬选一个。

reason应来自可信检索管线,不允许模型自行把普通搜索标为failure提升优先。Failure Parser产生failure,引用分析器产生reference,search tool产生search;Builder验证枚举并记录source id。数据provenance防优先级注入。

score只在同层比较,避免不同检索器分数量纲混用。语义向量0.9与文本BM25 20不可直接比较;先按reason分层,各层内部归一或使用rank。跨源reranker需要校准语料与版本。

范围去重比内容hash更适合重叠窗口,但完全相同内容可能来自生成文件或复制代码,不应自动合并不同路径,因为来源和修改目标不同。可以另做内容重复降权,仍保留path证据。

重叠窗口选择高排名一段可能丢掉另一段独有尾部。盲合并content不可信,因为缺少中间原文与行号;更好是上游RangeMerger收集并集1–30,重新安全读取一次,再成为新候选。Builder保持纯选择器。

行号在文件修改后会漂移。候选绑定content hash与文件version,Prompt生成前验证;编辑发生后重新搜索/读取,不复用旧范围。Checkpoint resume比较workspace hash,避免恢复旧上下文修改新文件。

闭区间端点语义必须全链一致。搜索hit line、parser line与reader slice都从一开始,end包含;若某层使用零基或半开区间,去重会错一行。Contracts包定义类型与测试,不靠注释约定。

范围本身不能证明content对应。候选生成器返回sourceHash和实际start/end,Builder可抽样或全部验证内容;不可信插件候选必须重新通过文件工具读取。否则插件能把任意指令伪装成代码片段。

预算优先完整语义单元。函数太大可上游切为签名、相关分支和调用点,带parent id;不能在字符中间slice。模型看到语法完整且来源明确,通常比更多残片更有效。

reason标签应写入Prompt,例如“失败文件”“引用定义”“搜索命中”,帮助模型理解为何存在,不把搜索猜测当确定根因。标签和路径/行号成本纳入token预算,不能只计content。

truncated细分更可观测:overlap、budget、invalid、missing、policy。Lab只给boolean,上层若看到大量overlap说明窗口生成冗余,大量budget说明预算或切片需调整。原因列表不必发送模型,可进Trace与评测。

当failure大片段放不下却小reference入选,truncated提示模型缺失核心证据。规划器可请求更小failure窗口并重新build,而不是直接回答。Builder结果可带missingCritical,若所有failure候选被省略则阻止写操作。

文件多样性可限制每文件最多N段,防单一大文件占满;但失败文件可能需要实现与测试两个远隔区域。软衰减比硬上限灵活,评测真实修复成功率选择。任何策略仍需稳定tie-break。

依赖关系可以成组:接口定义与实现、失败测试与被测函数。候选group要求整组装箱,若预算不足全省略或用最小组;类似0/1 knapsack。贪心基线不处理组,文章明确扩展点。

排序后的布局不一定同序。选择阶段按价值,Prompt可按文件/行号重组,减少认知跳转;failure片段可在开头和结尾摘要。布局后重新计算成本与hash,Trace保留selection rank和render order两个字段。

lost-in-the-middle提示重要片段不应全堆中间。任务、安全指令固定首部,失败证据靠前,验收条件尾部复述;Context Builder只返回items,Prompt Composer负责布局。职责分离便于评测位置效果。

缓存键包含candidate集合hash、failureFile、budget、排序版本与cost model。内容或规则变化自动失效;不按session标题缓存。相同build结果hash支持模型Prompt cache并降低成本。

输入数量也要上限。排序十万候选耗CPU内存,检索器先各自top-k,Builder验证总数和content总字节。恶意插件不能用大量低分候选制造拒绝服务;资源错误归budget且不可原样重试。

隐私策略在Builder前过滤敏感路径与content,过滤项不能因高failure优先重新加入。若失败发生在不可发送秘密文件,Runtime用本地摘要或用户接管。相关性永远不能覆盖权限。

评测拆成召回与选择:候选全集是否包含修复所需证据,Builder是否在预算内选中,模型是否成功。缺证据不能归罪装箱,选错不能只调模型。Trace的reason/rank/省略原因支持因果诊断。

测试夹具覆盖failure低分、reference低分胜search高分、范围保留、重叠、预算continue;扩展性质测试随机候选,断言无选中范围重叠、used等于content成本和且不超预算、输入不变、输出确定。

变异测试去掉failure优先、交换reason顺序、把闭区间写错、先预算后重叠导致不一致、过大break、原地sort、任意slice。门禁应全部失败,证明上下文质量规则可长期维护。

用第一项Lab路径观察排序。a.ts是search分十,b.ts是failure reason分一,failureFile明确b.ts。计算af时b为零,a为search三,因此b排第一,不受分数差影响。结果保留b的start四、end五和content bb。失败事实优先于通用相似度。

第二项没有failureFile,s.ts search分一百,r.ts reference分一。reference优先级一小于search三,所以r在前。若把score写在reason之前,测试会失败;排序比较器字段顺序就是检索策略,必须通过代码与Trace公开。

第三项只放一个a.ts 10–20,Builder返回同一显式窗口,不把它规范成1开始或根据content行数改end。候选生成器负责范围正确,Builder只验证整数关系并复制。保持来源坐标让后续编辑和Trace可定位。

第四项a.ts 1–10分二、5–12分一,排序先第一段,第二段与已选交集,省略并truncated。若顺序分数反转,则保留5–12。去重不是固定保留最短或最早,而是保留当前策略认为价值最高的窗口。

端点案例1–10与10–12共享第10行,闭区间判重;1–10与11–12不重叠。错误使用 < 会把共享端点重复,错误使用>=可能把相邻片段也删掉。表驱动把这些边界锁住。

不同路径即使范围相同、content相同也都可入选,because path是证据身份。生成代码与源代码可能重复,模型仍需知道两个位置;内容相似度降权属于额外策略,不应混入范围不变量。

预算三例中失败片段1234先排序但成本四,省略;b.ts reference 12成本二加入,used二,truncated true。若实现遇大就break,items为空;若任意slice,会把失败片段变123,丢失完整性。测试直接区分三种方案。

零预算与空候选的truncated不同。零预算且有非空候选,所有被省略所以true;空候选没有省略所以false。content空字符串成本零,可入选,但是否有价值应在候选schema拒绝空内容,避免大量零成本噪声。

非法范围要在排序前全量验证。若前一个合法片段已加入后才遇非法,虽然函数只在内存没有外部副作用,调用方可能已拿不到部分结果;一次性验证让错误确定。path也应非空规范相对路径,score有限数字,content字节有上限。

NaN score会让比较器返回NaN并被sort当零,顺序依赖输入;Infinity压过所有候选。生产schema要求有限分数并按源归一。类型声明number不阻止运行时JSON传入特殊值或缺字段。

failureFile路径先规范化。./b.tsb.ts若不统一,明明同文件却失去优先;大小写与symlink别名也需由安全文件层提供canonical relative id。Builder不访问磁盘,只相信已规范path contract。

范围去重性能是O(n×已选),小top-k足够。候选上千时可按path维护已选区间树或排序列表,将查询优化;优化必须保留按rank逐个决策与完整输出。先用评测证明瓶颈,再增加数据结构复杂度。

如果高优重叠片段因预算未选,后续低优小片段应该有机会。当前代码先重叠检查items,因高优未加入items,不会阻止它。测试可以让第一段成本十、第二重叠成本二、预算二,预期第二入选。去重针对实际Context,不针对候选意图。

反过来,高优已选后预算还够,重叠低优省略并truncated,即使省略不节省额外字符也要标记候选集合不完整。上层想知道不是所有证据都呈现;详细reason区分是redundant而非budget。

分数与reason在返回item中保留,模型Prompt是否展示score由Composer决定。通常展示reason和来源即可,原始数值可能误导模型;Trace保留score用于调参。选择数据和展示数据可以不同,但path/content/hash一致。

构建结果hash基于items规范顺序、path、range、contentHash、reason、预算策略版本,不含原始content全文或时间。Checkpoint保存hash,resume重新构建比较;变化则重新规划,防旧上下文继续编辑。

Instruction entries与代码items争用总Prompt预算。更高层先为系统/AGENTS/任务/输出预留,再把剩余传maxChars;Builder不能看到模型总窗口就擅自占满。分区预算让安全规则永远不会被代码候选挤掉。

失败输出本身也占预算。Parser摘要、stack位置生成failure候选,原始日志不直接混入代码列表。Prompt Composer在错误摘要与文件片段之间建立引用,例如failure-1 -> b.ts:4-5,模型能追踪因果而不重复全文。

多轮Agent不应每次累计旧Context。每轮根据当前failure与workspace重新build,历史只保留摘要和hash;否则已修文件旧片段持续占窗口。持久化事件记录每轮selection,回放可解释变化,但模型只看当前有效证据。

选择失败文件也可能造成隧道视野。修复需要接口定义或调用方,因此reference层紧随;普通search补长尾。评测若发现失败文件过强挤掉必要定义,可为每层保留最低配额,而不是取消优先级。分层配额仍需完整预算与稳定规则。

安全文件过滤在打分前完成。.env即使是failureFile也不发送;可以生成“敏感文件未加载”占位和允许用户本地查看。Builder不拥有提升权限,优先级只能在允许候选集合内作用。

最终端到端演练:测试失败Parser给出b.ts,搜索产生a/b窗口,引用分析给接口,Builder在小预算中先选b小窗、再接口,去掉重叠搜索,报告used/truncated。模型修复后新失败变化,下一轮hash不同;相同输入重复build完全一致。相关性、去重、预算和恢复一致性同时得到证明。

验收还应对每个省略候选保留机器原因与原始排名。调试时可以回答“为何没看到这个函数”:它是与更高优窗口重叠、超过预算、被安全策略过滤,还是输入本身非法。没有省略证据,团队容易盲目扩大窗口,增加成本却不解决召回问题。

对Prompt实际渲染做最后一次token计数,若路径标题和围栏让总成本超限,从最低优items尾部完整移除并更新truncated/report hash,不能从文本末尾硬截。Builder字符预算与Composer token预算形成两道确定门禁,后一道保留前一道的完整性语义。

最后用真实任务评测,而不只看装箱率:固定预算下所需文件召回率、首轮补丁通过率、重复片段比例、平均token与省略后恢复率。策略升级只有在质量或成本证据改善时发布,并保留旧版本回放对照。

上下文的最终标准不是“装得满”,而是在可证明的预算内保留完成当前任务最关键、最少重复且来源清晰的证据。

下一课衔接

Week5完成长任务的状态与上下文基础:Todo状态机定义执行进度,JSONL与Checkpoint支持恢复,Instruction Loader加载目标规则,Repo Context Builder选择当前证据。Week6将把全过程写成可查询Trace,构建Trace Viewer和Benchmark/Evaluation,让每次计划、工具、补丁、恢复都有可量化证据。

  • Program slicing 与 dependency graph。
  • MMR、多样性和检索重排。
  • Context provenance 与可解释 RAG。

从零实现 Mini Code Agent Runtime