跳到主要内容

第 2 章:上下文检索主循环

第 1 章给了零件(索引 + 8 个 API + proxy)。这一章把它们接成一个会自己迭代的循环:LLM 反复检索,直到它敢说「bug 在这几个位置」。再看 ACR 怎么把「模型给的(很可能不精确的)位置」兜底成真实代码。

2.1 它要解决的小问题

一次检索很难够。模型先搜 get_backend,看了结果发现还得看 rc_context,再搜……这是一个多轮对话:每轮模型「选一些 API」,系统跑完把结果回灌,模型再决定「继续搜」还是「我定位好了」。ACR 用 Python 生成器(generator) 把这个来回织得很干净。

2.2 一轮长什么样

主循环在 SearchManager.search_iterative(search_manage.py:29),它驱动 agent_search.generator(agent_search.py:88)。一轮的控制流(LR,命中即停):

┌── search agent ──┐
issue → │ 选 API(写散文) │
└────────┬─────────┘
│ 散文响应
┌──────▼──────┐
│ agent_proxy │ 抽成 JSON,校验签名
└──────┬──────┘
┌────────┴─────────────┐
有 bug_locations 且 有 API_calls?
无 API_calls? │
│ ▼
▼ 跑 backend 搜索 → 结果
降级解析成 BugLocation │
│ ▼
成功?─是→ 返回 回灌结果,进入下一轮
│ (agent 先「分析」再「再选」)
否→ 提示更多搜索,下一轮

生成器的「输入 / 输出」约定很关键(search_manage.py:48-50agent_search.py:130-135):

  • 生成器 yield(模型响应文本, 消息线程)
  • 调用方 send(搜索结果字符串, 是否要重选):re_search=True 表示「你上次的响应没法用,重来」;False 表示「这是结果,接着分析」。

这个双值协议让一个生成器同时处理「正常推进」和「让模型重试」两条路,代码不必到处写状态机。

2.3 search agent 的三段式 prompt

agent_search.generator 每轮其实跟模型聊三段(agent_search.py:122-163):

  1. 选 API(SELECT_PROMPT,agent_search.py:25): 列出 8 个 API 让模型挑,强调「参数要具体、一轮可以多调几个」。
  2. 分析结果(ANALYZE_PROMPT,agent_search.py:41): 结果回来后,先让模型回答「这段代码干嘛的 / 和 bug 啥关系 / 期望行为是什么」——逼它思考再下结论
  3. 分析后再选(ANALYZE_AND_SELECT_PROMPT,agent_search.py:51): 问它「还要不要更多上下文?bug 在哪些位置?」。如果还要上下文就继续给 API(并把 bug 位置留空);如果定位好了就给出 bug_locations(含每个位置的 intended_behavior 期望行为)。

注意 intended_behavior 这个字段:ACR 不只要 bug 在哪,还要模型说清「这里应该改成什么行为」——这段话后面会原样喂给写补丁的 agent,是连接「检索」和「补丁」的桥。

2.4 终止条件:什么时候算「定位好了」

search_iterative 里(search_manage.py:114),判定是:proxy 抽出的 JSON 里有 bug_locations 且没有 API_calls——即模型不再要求搜索、只给位置。这时进入降级解析(下一节)。其它情况:

  • proxy 抽不出有效 JSON(selected_apis is None)→ 提示模型重选,下一轮(search_manage.py:82-90)。
  • API_calls → 逐个跑 backend,结果拼成字符串回灌,进入下一轮分析(search_manage.py:162-191)。
  • 降级解析后一个真实位置都捞不到 → 提示「检查参数或多搜点」,下一轮(search_manage.py:152-157)。
  • 轮数用尽(config.conv_round_limit,默认 15)→ 放弃,返回空位置「硬着头皮写补丁」(search_manage.py:193-196)。

每一轮的对话和 proxy 抽取结果都落盘(search_round_N.jsonagent_proxy_N.json),便于复盘。

2.5 降级解析:把「不精确的位置」捞成「真实代码」

这是本章的精华。模型给的 bug_location它说的,可能:方法名对但类名漏了、类名拼了、只给了文件……ACR 用 get_bug_loc_snippets_new(search_backend.py:759)做六级降级,从最精确往最宽松退,命中即停:

顺序模型给了什么用哪个搜索行号
1方法 + 类search_method_in_classsearch_backend.py:804
2方法 + 文件search_method_in_filesearch_backend.py:837
3类 + 文件search_class_in_filesearch_backend.py:842
4get_class_full_snippetsearch_backend.py:847
5方法search_methodsearch_backend.py:850
6文件get_file_contentsearch_backend.py:853

六级全落空才返回空列表(search_backend.py:856)。这套「逐级退让」就是为什么模型给个不太准的位置,ACR 往往还能捞到对的代码。

两个贴心处理:

  • Class.method 自动拆分。 如果模型把类和方法塞进一个字段写成 WCS._array_converter,代码会 split(".") 拆开(search_backend.py:773-779)。
  • 附带「类上下文」和「父类覆写」。 当定位到「类里的方法」时(第 1 级命中),ACR 还额外抓两样:① 整个类定义当上下文(search_class_in_file,search_backend.py:832),标注「This class provides additional context」;② 父类里被覆写的同名方法(_get_inherited_methods,search_backend.py:713)——它沿 class_relation_index 做 BFS,找最近祖先里定义了同名方法的,因为 bug 可能其实在父类。这是「结构感知」在定位上的又一次发力。

2.6 原理演示:六级降级的骨架

# 示意,非源码:bug location 的逐级降级
def resolve_bug_location(file, cls, method):
# 从最精确到最宽松,命中即停
for attempt in [
lambda: search_method_in_class(method, cls) if method and cls else None,
lambda: search_method_in_file(method, file) if method and file else None,
lambda: search_class_in_file(cls, file) if cls and file else None,
lambda: get_class_full_snippet(cls) if cls else None,
lambda: search_method(method) if method else None,
lambda: get_file_content(file) if file else None,
]:
res = attempt()
if res and res.ok: # 找到一个真实代码单元
return res
return None # 全落空
# 重点看:模型给的位置只是「线索」,真正的代码单元由确定性的搜索兜底取回

真实实现是 get_bug_loc_snippets_new(search_backend.py:759-881)里一串 if (not call_ok) and ...

2.7 检索的产物:BugLocation

降级解析成功后,每个 SearchResult 被转成 BugLocation(data_structures.py:272),它带:相对/绝对路径、类名、方法名、重新读取的源码(search_utils.get_code_snippets,data_structures.py:309——不信任之前传递的代码,按行号再读一遍保证准确)、以及那段 intended_behaviorBugLocation.__eq__ / __hash__(文件, 起, 止) 去重(data_structures.py:325-333),所以同一处被多次定位只留一份。这串 BugLocation 加上整个检索对话历史,就是交给补丁阶段的全部输入。

→ 下一章:模型怎么写补丁,以及补丁怎么可靠落回真实代码

2.8 代码地图

主题文件符号
检索主循环app/search/search_manage.pySearchManager.search_iterative
search agent 生成器app/agents/agent_search.pygeneratorSELECT_PROMPTANALYZE_AND_SELECT_PROMPT
issue 文本清洗app/agents/agent_search.pyprepare_issue_prompt
散文→JSONapp/agents/agent_proxy.pyrun_with_retriesrun
六级降级定位app/search/search_backend.pyget_bug_loc_snippets_new
父类覆写方法app/search/search_backend.py_get_inherited_methods
bug 位置数据结构app/data_structures.pyBugLocation