公司动态

SHERLOC框架:基于结构化诊断的代码修复精准定位实践

📅 2026/8/19 22:21:33
SHERLOC框架:基于结构化诊断的代码修复精准定位实践
1. 项目概述当代码修复遇上“福尔摩斯”最近在搞大语言模型LLM驱动的代码修复代理Code Repair Agent时我遇到了一个非常典型且棘手的问题模型生成的补丁Patch常常“跑偏”。它可能准确地识别出了代码库中存在一个bug但给出的修复方案却修改了完全无关的文件或者在一个正确的函数里塞入了错误的逻辑。这感觉就像医生诊断对了病症“病人发烧”却给错了药甚至给错了病人。这种“定位不准”的问题严重制约了自动化代码修复的实用性和可靠性。正是在这种背景下“SHERLOC”这个项目引起了我的强烈兴趣。这个名字本身就很有意思它借用了著名侦探“夏洛克·福尔摩斯”的名号其全称是“Structured Diagnostic Localization for Code Repair Agents”——为代码修复代理设计的结构化诊断定位框架。它的核心使命非常明确不是直接生成修复代码而是先像一个侦探一样在庞大的代码库中精准地“定位”出需要修复的具体位置哪个文件、哪个函数、哪几行代码并给出结构化的诊断证据。这相当于为后续的修复LLM提供了一个精确的“手术坐标”和“病情报告”极大地提升了修复动作的准确性。简单来说SHERLOC要解决的是代码修复流程中的“第一步”也是最关键的一步精准定位。传统的端到端修复模型或者简单的基于问题描述检索代码片段的方法在复杂的真实世界项目如SWE-Bench基准测试中的任务中表现很不稳定。SHERLOC通过引入一个结构化的、多步骤的推理定位流程将模糊的自然语言问题描述逐步收敛到具体的代码变更集Change Set上。我深入研究并实践了这套方法后发现它确实将代码修复的“命中率”提升了一个档次。下面我就结合自己的实操经验拆解一下SHERLOC的设计思路、核心模块以及如何将其集成到你自己的智能体工作流中。2. 核心思路拆解从“盲人摸象”到“外科手术”在深入细节之前我们得先理解为什么“定位”如此困难以及SHERLOC是如何结构化地解决这个问题的。2.1 传统方法的痛点信息过载与上下文幻觉当你把一个庞大的代码库比如一个拥有几百个文件的Python项目和一个模糊的Issue描述例如“用户登录时偶尔会报500错误”一起扔给一个LLM并让它“修复这个bug”时会发生什么信息过载LLM的上下文窗口是有限的。即使是最新的128K模型也很难无损地塞入整个项目的代码。你不得不进行剪裁而剪裁的过程本身就可能导致丢失关键线索。缺乏推理结构LLM可能会基于问题描述中的关键词如“login”、“500”进行简单的全文检索然后对检索到的几个相关文件进行“猜测式”修复。这个过程是黑盒的没有明确的推理链条极易受到代码库中相似但不相关模式的干扰。上下文幻觉与错误传播LLM可能会“幻想”出一些不存在的代码结构或者将其他模块的正确模式错误地应用到当前问题上。更糟糕的是如果第一步定位错了文件那么后续无论生成多么精巧的修复代码都是南辕北辙。这就像让一个侦探在没有勘察现场、没有询问证人、没有分析物证的情况下直接指认凶手。成功率可想而知。2.2 SHERLOC的“侦探办案”流程SHERLOC的聪明之处在于它没有让LLM一次性完成所有工作而是设计了一个结构化的、循序渐进的定位流程。这个流程模仿了人类开发者的调试思路接案与初步侦查问题理解与范围划定首先系统需要理解用户提交的Issue到底在说什么。它不仅仅是提取关键词而是尝试理解问题涉及的模块、功能和行为。例如从“登录500错误”中可以推断出可能涉及auth/目录下的文件、用户会话处理、数据库查询等。搜集线索检索相关代码片段基于初步理解在代码库中检索可能与问题相关的文件、函数、类或代码块。这里的关键是多路召回即使用不同的“检索器”如基于文本相似度、基于代码结构图、基于调用关系尽可能多地搜集潜在线索避免遗漏。分析线索与建立关联结构化诊断这是SHERLOC的核心。它不会直接使用检索到的原始代码片段而是让LLM扮演“侦探分析师”的角色对每一段检索到的代码进行诊断性分析。分析任务包括相关性判断这段代码与当前Issue有多相关是完全相关、部分相关还是无关影响分析如果这段代码有问题它会导致Issue中描述的现象吗变更类型推测要修复这个问题这段代码可能需要怎样的修改是修改条件判断、修复函数参数、还是处理异常证据链生成用自然语言或结构化数据如JSON给出分析的理由形成逻辑链条。综合研判与锁定目标生成变更集将所有代码片段的诊断分析结果进行汇总和综合推理。LLM需要像侦探开会一样权衡所有证据的强弱和关联性最终判断出最有可能需要修改的一组文件及其具体的修改位置行号和修改类型。输出是一个结构化的“变更集”Change Set例如{file: “auth/login.py”, location: “lines 45-50”, change_type: “fix_condition”, reason: “...“}。移交“手术刀”为修复代理提供输入这个精确的、带有丰富诊断证据的变更集被传递给后续的代码修复代理。此时修复代理的任务被大大简化了它不再需要从海量代码中寻找目标而是专注于在给定的“手术区域”变更集内根据诊断证据生成具体的代码修改即补丁。这相当于外科医生拿到了精确的CT扫描片和手术方案只需要执行精细操作即可。这套流程的核心思想是“分离关注点”。让一个模块SHERLOC专精于“找哪里错了以及为什么”另一个模块修复代理专精于“怎么改才对”。通过结构化诊断作为中间桥梁整个系统的可解释性和可靠性都得到了质的提升。3. 核心模块深度解析与实操要点理解了宏观流程我们再来拆解SHERLOC内部的几个关键模块以及在实际实现中需要注意的细节。3.1 诊断定位器从检索到诊断的质变单纯的代码检索例如用BM25或Embedding做语义搜索在代码修复中是不够的。因为代码bug的表述Issue和代码实现之间往往存在巨大的语义鸿沟。SHERLOC的“诊断定位器”是检索的升级版。实操要点构建多维度检索与诊断管道混合检索策略文本语义检索使用代码专用的嵌入模型如codebert、unixcoder将Issue和代码片段向量化进行相似度匹配。这能抓住功能描述上的关联。代码结构检索利用抽象语法树AST提取函数名、类名、变量名、API调用等信息构建索引。当Issue中提到“send_email函数失败”时能直接定位到该函数定义。动态上下文检索分析代码的调用图Call Graph或导入关系。如果Issue关于“登录失败”除了看login函数本身还要检索调用login的函数以及login内部调用的函数如validate_password、create_session。这能捕捉到bug的影响链。 我通常会使用ChromaDB或Weaviate来分别构建这几个索引在查询时进行融合Fusion或重排序Reranking。诊断提示词工程 这是让LLM从“检索结果阅读者”变为“诊断分析师”的关键。提示词必须精心设计。你是一个资深的代码调试专家。请分析以下代码片段与给定问题的相关性。 [问题描述开始] {issue_description} [问题描述结束] [检索到的代码片段开始] 文件路径{file_path} 代码 python {code_snippet}[检索到的代码片段结束]请按以下结构输出你的分析相关性评分 (0-5分): 0完全无关5直接导致该问题)理由: 详细解释你的评分。这段代码如何可能导致问题或者为什么它无关潜在修改类型 (可选): 如果相关指出这里可能需要哪种修改例如逻辑错误、空值未处理、条件边界错误、API调用错误等。关键证据行号: 指出片段中最可疑的一行或几行代码的行号相对于片段起始行。通过强制结构化的输出我们得到了机器可读的诊断元数据为后续的综合研判提供了标准化的输入。3.2 证据整合与推理引擎从线索到结论单个代码片段的诊断是零散的证据。推理引擎的任务是扮演“侦探组长”综合所有证据做出最终判断。实操要点实现基于图的推理与排序构建证据图 将每个诊断后的代码片段视为一个节点。节点属性包括诊断得分、理由、修改类型、文件路径、行号。 在节点之间建立边。边的权重可以由以下因素决定共现关系两个片段是否在同一个文件、同一个函数或相邻行调用关系一个片段中的函数是否调用了另一个片段中的函数需要静态分析工具如tree-sitter支持语义相似性两个诊断理由在语义上是否指向同一个根本原因 这样我们就得到了一个带权重的证据图。图算法进行聚类与排序社区发现使用Louvain或Leiden算法对证据图进行社区检测。同一个社区内的节点很可能指向同一个bug的核心位置。节点重要性排序在社区内部或全局图上使用PageRank或中心性算法找出连接最紧密、诊断得分最高的“核心证据”节点。这些节点对应的代码位置就是最有可能的修复点。 通过图算法我们能够将零散的、可能矛盾的诊断信息整合成几个清晰的、有内部逻辑支持的“嫌疑目标群”。LLM作为最终仲裁者 将图算法筛选出的Top-K个核心证据节点例如每个社区的前2个节点及其完整的诊断信息再次喂给一个更强的LLM如GPT-4。这次的任务是进行最终的综合推理“基于以下多份诊断证据请推断出修复问题{issue}所必须修改的最小文件集合和具体位置。请以JSON格式输出变更集。”让LLM在图算法提供的“候选名单”基础上做最终裁定结合了算法的客观性和LLM的深层语义理解能力效果通常比单独使用任何一种都要好。3.3 与修复代理的接口设计传递结构化上下文SHERLOC的输出不是终点而是修复代理的起点。如何将定位信息高效地传递给修复代理至关重要。实操要点设计信息丰富的变更集描述一个糟糕的接口可能只传递file_path和line_number。一个优秀的接口应该像一份完整的手术简报{ issue_summary: 用户登录时偶发500内部服务器错误。, change_sets: [ { file_path: backend/auth/session_manager.py, target_location: { start_line: 78, end_line: 85, function_name: _create_session_token }, change_type: fix_null_handling, confidence: 0.9, diagnostic_evidence: [ { source: 代码语义分析, description: 函数第82行 user_id payload.get(‘sub’)当JWT解码失败或格式不当时payload 可能为 None调用 .get 会抛出 AttributeError导致500错误。这与‘偶发’特性吻合可能源于畸变的客户端令牌。 }, { source: 调用链分析, description: 此函数由 login 函数直接调用是登录流程的关键一环。 } ], context_code_snippet: def _create_session_token(payload):\n # ...\n try:\n user_id payload.get(sub) # -- 疑似问题行\n if not user_id:\n return None\n # ...\n except Exception as e:\n logger.error(f\Session creation failed: {e}\)\n return None # 此处返回None可能导致上层未处理空值 } ], global_context: [ { file_path: backend/auth/views.py, purpose: 调用方上下文展示 _create_session_token 如何被使用。, snippet: ... } ] }修复代理收到这样一份详细的变更集后它的提示词就可以非常聚焦“你需要在session_manager.py文件的_create_session_token函数中第78-85行附近解决一个空值处理问题。具体证据表明……。请生成一个补丁来修复它。”这极大地降低了修复代理的认知负荷将生成高质量补丁的成功率最大化。4. 实战集成在SWE-Bench任务上构建SHERLOC工作流SWE-Bench是一个评估LLM解决真实世界GitHub Issue能力的权威基准。将SHERLOC思路应用于SWE-Bench任务是检验其效果的绝佳场景。下面是我构建的一个简易可复现的工作流。4.1 环境准备与工具链# 1. 创建环境 conda create -n sherloc_agent python3.10 conda activate sherloc_agent # 2. 安装核心依赖 pip install openai anthropic chromadb pydantic tree-sitter tree-sitter-languages # 安装代码分析工具 pip install libcst pytest # 安装SWE-Bench官方工具包用于加载任务 pip install swe-bench工具选型理由openai/anthropic用于调用GPT、Claude等LLM API作为诊断和推理引擎。chromadb轻量级向量数据库用于存储和检索代码片段嵌入。tree-sitter高效的增量解析器用于生成AST、提取代码结构、分析调用关系比传统正则或简单字符串匹配可靠得多。libcst另一个优秀的Python源码解析和修改库在后续生成补丁时可能用到。swe-bench提供标准化的任务加载和评估接口。4.2 分步实现SHERLOC管道4.2.1 步骤一代码库索引与预处理在处理一个SWE-Bench任务对应一个Git仓库的某个版本时首先需要为这个代码库建立索引。import os from tree_sitter import Language, Parser import chromadb from chromadb.utils import embedding_functions import hashlib class CodebaseIndexer: def __init__(self, repo_path): self.repo_path repo_path self.parser Parser() # 加载Python语法需提前编译tree-sitter语言库 PYTHON_LANGUAGE Language(‘./tree-sitter-python.so‘, ‘python‘) self.parser.set_language(PYTHON_LANGUAGE) # 初始化ChromaDB集合 self.chroma_client chromadb.PersistentClient(path“./chroma_db”) # 使用一个通用的文本嵌入模型例如sentence-transformers self.embedding_fn embedding_functions.SentenceTransformerEmbeddingFunction(model_name“all-MiniLM-L6-v2”) self.collection self.chroma_client.get_or_create_collection( namerepo_path.replace(‘/‘, ‘_‘), embedding_functionself.embedding_fn ) def extract_functions_from_file(self, file_path): 使用tree-sitter提取文件中的所有函数/方法 with open(file_path, ‘r‘, encoding‘utf-8‘) as f: code f.read() tree self.parser.parse(bytes(code, ‘utf-8‘)) root_node tree.root_node functions [] # 查询所有函数定义节点 query PYTHON_LANGUAGE.query(“”” (function_definition name: (identifier) func_name body: (block) func_body) func_def (class_definition body: (block (function_definition name: (identifier) method_name body: (block) method_body) method_def)) class_def “””) captures query.captures(root_node) # 处理捕获的节点组织成结构化数据 # ... (具体实现略需将节点位置转换为代码字符串) return functions def build_index(self): 遍历代码库为每个函数/类建立索引 for root, dirs, files in os.walk(self.repo_path): for file in files: if file.endswith(‘.py‘): # 以Python为例 full_path os.path.join(root, file) functions self.extract_functions_from_file(full_path) for func in functions: # 构建文档包含代码、路径、函数名等元数据 doc { “code”: func[‘body‘], “file_path”: full_path, “func_name”: func[‘name‘], “start_line”: func[‘start_line‘], “end_line”: func[‘end_line‘], } doc_id hashlib.md5(f“{full_path}:{func[‘name‘]}“.encode()).hexdigest() # 将代码文本和元数据存入向量库 self.collection.add( documents[func[‘body‘]], metadatas[{“file_path”: full_path, “func_name”: func[‘name‘], “lines”: f“{func[‘start_line‘]}-{func[‘end_line‘]}“}], ids[doc_id] ) print(f“索引构建完成共索引 {self.collection.count()} 个代码片段。”)注意事项索引粒度选择函数/方法级别作为索引粒度是平衡点。太粗整个文件信息混杂太细单行丢失上下文。对于类可以同时索引整个类和其内部方法。过滤文件忽略__pycache__,.git,node_modules, 测试文件(test_*.py)等专注于业务代码。处理大文件对于超长函数可以考虑按逻辑块如大的if-else分支进一步分割但需保持块内语义完整。4.2.2 步骤二基于Issue的混合检索与初步诊断import openai from typing import List, Dict class DiagnosticLocator: def __init__(self, indexer: CodebaseIndexer, llm_client): self.indexer indexer self.llm_client llm_client def hybrid_retrieve(self, issue_text: str, top_k: int 20) - List[Dict]: 混合检索语义检索 关键词检索 results [] # 1. 语义检索 (通过向量库) semantic_results self.indexer.collection.query( query_texts[issue_text], n_resultstop_k // 2 ) for i, doc in enumerate(semantic_results[‘documents‘][0]): results.append({ “code”: doc, “metadata”: semantic_results[‘metadatas‘][0][i], “score”: semantic_results[‘distances‘][0][i], # 注意是距离 “retrieval_method”: “semantic“ }) # 2. 关键词检索 (简化版在元数据中搜索) # 可以从issue_text中提取名词、动词作为关键词在metadata的func_name和file_path中匹配 # 这里简化为使用ChromaDB的where过滤进行示例 keywords self._extract_keywords(issue_text) for kw in keywords[:3]: # 取前几个关键词 keyword_results self.indexer.collection.get( where{“$or”: [{“func_name”: {“$contains”: kw}}, {“file_path”: {“$contains”: kw}}]}, limittop_k // 4 ) for i, doc in enumerate(keyword_results[‘documents‘]): # 去重 if doc not in [r[‘code‘] for r in results]: results.append({ “code”: doc, “metadata”: keyword_results[‘metadatas‘][i], “score”: 0.0, # 关键词检索无分数 “retrieval_method”: “keyword“ }) return results[:top_k] # 返回Top-K个结果 def diagnose_snippet(self, issue: str, code_snippet: str, metadata: Dict) - Dict: 调用LLM对单个代码片段进行诊断 prompt f“““ 你是一个资深的代码调试专家。请分析以下代码片段与给定问题的相关性。 [问题描述开始] {issue} [问题描述结束] [检索到的代码片段开始] 文件路径{metadata[‘file_path‘]} 函数名{metadata.get(‘func_name‘, ‘N/A‘)} 代码 python {code_snippet} [检索到的代码片段结束] 请按以下结构输出你的分析 1. 相关性评分 (0-5分): 0完全无关5直接导致该问题) 2. 理由: 详细解释你的评分。这段代码如何可能导致问题或者为什么它无关 3. 潜在修改类型 (可选): 如果相关指出这里可能需要哪种修改例如逻辑错误、空值未处理、条件边界错误、API调用错误、权限校验缺失等。 4. 关键证据行号: 指出片段中最可疑的一行或几行代码的行号相对于片段起始行。 “““ response self.llm_client.chat.completions.create( model“gpt-4-turbo“, messages[{“role”: “user”, “content”: prompt}], temperature0.1, # 低温度保证输出稳定 response_format{“type”: “json_object”} # 强制JSON输出 ) diagnosis json.loads(response.choices[0].message.content) diagnosis.update({ “file_path”: metadata[‘file_path‘], “func_name”: metadata.get(‘func_name‘), “code_snippet”: code_snippet, “lines”: metadata.get(‘lines‘), “retrieval_method”: metadata.get(‘retrieval_method‘, ‘unknown‘) }) return diagnosis实操心得LLM选择诊断步骤对推理能力要求高建议使用能力较强的模型如GPT-4、Claude 3 Opus。虽然成本较高但这是保证定位精度的关键投资。对于简单项目可以尝试deepseek-coder等开源模型。并行处理检索到的Top-K个片段可以并行调用LLM进行诊断利用异步请求asyncioaiohttp可以大幅缩短整体耗时。结果缓存相同的代码片段和Issue组合的诊断结果可以缓存起来避免重复调用LLM节省成本和时间。4.2.3 步骤三证据整合与变更集生成import networkx as nx class EvidenceIntegrator: def __init__(self): self.graph nx.Graph() def build_evidence_graph(self, diagnoses: List[Dict]): 基于诊断结果构建证据图 for i, diag in enumerate(diagnoses): node_id f“{diag[‘file_path‘]}:{diag.get(‘func_name‘, ‘global‘)}“ self.graph.add_node(node_id, **diag) # 添加边基于文件路径和代码行号的邻近性 for i in range(len(diagnoses)): for j in range(i1, len(diagnoses)): diag_i, diag_j diagnoses[i], diagnoses[j] weight 0.0 # 规则1同一个文件内权重更高 if diag_i[‘file_path‘] diag_j[‘file_path‘]: weight 2.0 # 规则2行号接近权重额外增加 try: lines_i list(map(int, diag_i[‘lines‘].split(‘-‘))) lines_j list(map(int, diag_j[‘lines‘].split(‘-‘))) if abs(lines_i[0] - lines_j[0]) 20: # 20行以内视为接近 weight 1.0 except: pass # 规则3诊断的修改类型相似权重增加 if diag_i.get(‘potential_change_type‘) diag_j.get(‘potential_change_type‘): weight 0.5 if weight 0: self.graph.add_edge(f“{diag_i[‘file_path‘]}:{diag_i.get(‘func_name‘, ‘global‘)}“, f“{diag_j[‘file_path‘]}:{diag_j.get(‘func_name‘, ‘global‘)}“, weightweight) def rank_and_cluster(self) - List[List[Dict]]: 对证据图进行社区发现和节点排序 if len(self.graph) 0: return [] # 使用Louvain算法进行社区发现 import community as community_louvain partition community_louvain.best_partition(self.graph.to_undirected(), weight‘weight‘) # 按社区分组 communities {} for node, comm_id in partition.items(): communities.setdefault(comm_id, []).append(node) ranked_communities [] for comm_id, nodes in communities.items(): # 计算社区内节点的平均诊断评分 avg_score sum(self.graph.nodes[n].get(‘relevance_score‘, 0) for n in nodes) / len(nodes) # 计算社区内节点的PageRank中心性 subgraph self.graph.subgraph(nodes) try: pagerank nx.pagerank(subgraph, weight‘weight‘) # 选择社区内PageRank最高的节点作为代表 central_node max(pagerank, keypagerank.get) except: central_node nodes[0] ranked_communities.append({ “community_id”: comm_id, “nodes”: nodes, “avg_relevance”: avg_score, “central_node”: central_node, “central_node_data”: self.graph.nodes[central_node] }) # 按社区平均相关性排序 ranked_communities.sort(keylambda x: x[‘avg_relevance‘], reverseTrue) return ranked_communities def generate_change_set(self, top_communities: List, issue_text: str, llm_client) - Dict: 调用LLM基于排名靠前的社区证据生成最终的变更集 # 准备给LLM的综合证据文本 evidence_summary “” for i, comm in enumerate(top_communities[:3]): # 取前3个社区 evidence_summary f“\n\n[证据群 {i1}] - 平均相关性: {comm[‘avg_relevance‘]:.2f}\n” for node in comm[‘nodes‘][:3]: # 每个社区取前3个节点 data self.graph.nodes[node] evidence_summary f“文件: {data[‘file_path‘]}, 函数: {data.get(‘func_name‘, ‘N/A‘)}\n” evidence_summary f“诊断评分: {data.get(‘relevance_score‘, ‘N/A‘)}, 理由: {data.get(‘reason‘, ‘N/A‘)[:200]}...\n” evidence_summary f“可疑代码行(片段内): {data.get(‘key_evidence_lines‘, ‘N/A‘)}\n” prompt f“““ 你是一个高级软件工程师正在定位一个bug。以下是针对问题“{issue_text}”的多个诊断证据群。 {evidence_summary} 请基于以上所有证据进行综合推理判断修复此问题最需要修改的**核心位置**。 输出一个JSON对象包含以下字段 - primary_change_sets: 一个列表每个元素描述一个最有可能的修改位置。每个元素包含 * file_path: 文件路径 * function_name: 函数名如可推断 * target_lines: 目标行号范围如 “45-52” * confidence: 你的置信度 (0.0-1.0) * summary_of_evidence: 支持此判断的证据摘要1-2句话 * expected_change_nature: 预期的修改性质例如“修复空指针解引用”、“修正条件逻辑”、“添加输入验证” - reasoning: 一段话解释你是如何从证据得出这些结论的。 “““ response llm_client.chat.completions.create( model“gpt-4-turbo“, messages[{“role”: “user”, “content”: prompt}], temperature0.1, response_format{“type”: “json_object”} ) final_judgment json.loads(response.choices[0].message.content) return final_judgment注意事项图构建的启发式规则上述边的权重计算规则比较简单可以根据实际效果调整。更复杂的规则可以包括基于调用图的真实依赖关系。社区数量不一定所有社区都相关。通常只关注平均相关性分数超过某个阈值例如2.5分的社区。最终裁决的提示词给LLM的提示词要清晰指示输出格式并强调基于证据进行推理。可以提供几个示例Few-shot来引导其输出更规范。4.3 步骤四与修复代理的对接假设我们已经有一个现成的代码修复代理例如一个封装了LLM API专门接收代码上下文和问题描述并输出补丁的函数generate_patch。集成SHERLOC后调用方式变为def enhanced_code_repair_agent(issue_text: str, repo_path: str): # 1. 初始化并构建索引如果尚未构建 indexer CodebaseIndexer(repo_path) if not index_collection_exists: # 检查索引是否已存在 indexer.build_index() # 2. 定位诊断 locator DiagnosticLocator(indexer, openai_client) retrieved_snippets locator.hybrid_retrieve(issue_text, top_k20) diagnoses [] for snippet_info in retrieved_snippets: diagnosis locator.diagnose_snippet(issue_text, snippet_info[‘code‘], snippet_info[‘metadata‘]) diagnoses.append(diagnosis) # 3. 整合证据生成变更集 integrator EvidenceIntegrator() integrator.build_evidence_graph(diagnoses) top_communities integrator.rank_and_cluster() change_set integrator.generate_change_set(top_communities, issue_text, openai_client) # 4. 为每个变更集生成补丁 patches [] for change in change_set[‘primary_change_sets‘]: # 提取目标代码的完整上下文例如目标函数及其前后若干行 target_code_with_context read_code_with_context( change[‘file_path‘], change[‘target_lines‘], context_lines50 ) # 构建给修复代理的详细指令 repair_prompt f“““ 问题{issue_text} 经过分析问题很可能源于以下代码位置 文件{change[‘file_path‘]} 函数{change.get(‘function_name‘, ‘N/A‘)} 关键区域{change[‘target_lines‘]} 诊断证据摘要{change[‘summary_of_evidence‘]} 预期修改性质{change[‘expected_change_nature‘]} 以下是相关代码的上下文 python {target_code_with_context} 请生成一个具体的、最小化的代码补丁diff格式来修复这个问题。只修改必要的部分并确保修改后的代码逻辑正确。 “““ patch generate_patch(repair_prompt) # 调用你的修复代理 if patch: patches.append({ “file”: change[‘file_path‘], “patch”: patch }) return { “issue”: issue_text, “diagnostic_summary”: change_set[‘reasoning‘], “produced_patches”: patches }通过这个流程修复代理generate_patch接收到的上下文信息质量极高它不再需要大海捞针而是进行定点修复成功率自然会显著提升。5. 常见问题、避坑指南与效果评估在实际部署和测试SHERLOC类方案时我踩过不少坑也总结了一些提升效果的关键点。5.1 典型问题与排查技巧问题现象可能原因排查与解决思路检索结果完全无关1. Issue描述太模糊或与代码语义差距大。2. 嵌入模型不适合代码。3. 索引粒度不合适如文件太大。1.预处理Issue用LLM对Issue进行重写或扩展生成更技术化的描述。例如“用户登录失败” - “用户认证过程中/api/login端点可能因会话令牌生成函数_create_session_token中的空值处理不当而返回500错误”。2.更换嵌入模型尝试代码专用模型如microsoft/codebert-base、Salesforce/codet5-base。3.调整索引尝试以“函数其直接调用者/被调用者”为单位进行索引。诊断评分普遍偏低或模糊1. 诊断提示词不够明确LLM“不敢”给高分。2. LLM温度设置过高输出不稳定。1.优化提示词在提示词中提供高分和低分的具体示例Few-shot Learning明确评分标准。例如“5分这段代码中的bug是导致问题的直接且唯一原因。4分这段代码的问题极有可能导致所述现象。”2.降低温度并启用JSON模式设置temperature0.1或0并使用API的response_format{“type”: “json_object”}确保输出结构稳定。证据图社区过多且分散1. 检索结果本身噪声大。2. 构建边的规则太宽松将不相关的节点连接在了一起。1.提高检索质量在诊断步骤前增加一个“快速过滤”层。用一个快速的、便宜的LLM如GPT-3.5-Turbo先对检索结果做一次二分类相关/不相关只对“相关”的进行深度诊断。2.收紧建边规则只对同一文件内且诊断修改类型相同的节点建立边提高社区内聚性。最终变更集指向了正确文件但错误行号1. 代码片段上下文不足LLM无法精确定位到行。2. 行号信息在传递中出错如使用片段内相对行号而非文件绝对行号。1.提供更宽上下文在最终生成变更集时不仅提供诊断出的代码片段也提供该片段在源文件中的前后各20-30行代码帮助LLM理解上下文。2.严格校验行号在输出变更集前用脚本检查target_lines是否在文件有效行号范围内并尝试将片段内相对行号准确映射回文件绝对行号。流程耗时太长1. 串行调用LLM诊断。2. 索引构建慢。1.并行化使用asyncio和aiohttp并发调用诊断API。对于20个片段并行可将延迟从分钟级降到秒级。2.增量索引与缓存对于同一代码库的不同Issue复用索引。对诊断结果进行缓存键为issue_hash code_snippet_hash避免重复分析。5.2 效果评估与迭代如何知道SHERLOC是否真的提升了你的修复代理水平不能只靠感觉需要量化评估。评估指标定位准确率在SWE-Bench等有标准答案的数据集上判断SHERLOC输出的primary_change_sets是否包含了真实需要修改的文件和行号范围允许一定误差如±5行。这是最核心的指标。修复成功率提升对比“原始修复代理端到端”和“SHERLOC修复代理”两个流程在同一个测试集上的补丁生成成功率即生成的补丁能否通过测试。这是终极业务指标。诊断相关性评分分布分析诊断步骤输出的评分分布。理想情况下应有少数几个片段获得高分4-5分大部分是低分0-2分。如果分布平均说明诊断没有区分度。A/B测试 在SWE-Bench上选取一批任务分别用两种流程运行记录定位准确率和最终修复成功率。使用统计检验如McNemar检验判断提升是否显著。迭代优化点检索器尝试加入基于AST或调用图的检索器看是否能召回更多关键但语义不明显的代码如配置文件、常量定义。诊断提示词持续通过少量样本10-20个进行提示词微调让LLM的评分标准更贴近你的需求。图算法权重根据验证集效果手动调整或使用简单学习算法优化证据图中边的权重计算规则。5.3 个人实操心得与成本考量成本与效率的平衡SHERLOC的管道涉及多次LLM调用检索后诊断 最终整合成本比直接端到端修复要高。我的经验是对于复杂、模糊或大型代码库的Issue这个成本是值得的因为它能显著避免生成大量无用的、甚至有害的补丁从整体上提升了开发效率。对于简单明了的bug可以设置一个阈值例如当Issue描述非常具体包含文件名和函数名时绕过SHERLOC直接修复。不是银弹而是增强组件SHERLOC极大地改善了定位问题但并不能保证100%生成正确补丁。修复代理本身的能力代码理解、生成质量同样关键。SHERLOC是一个强大的“前置过滤器”和“上下文增强器”它让修复代理的工作变得更简单但并不能替代修复代理。可解释性的巨大价值SHERLOC流程中产生的诊断理由、证据链和最终推理提供了宝贵的可解释性。当修复建议被提出时开发者可以看到“为什么系统认为这里有问题”这大大增加了对自动化修复的信任度也便于人工复核和干预。适用于CI/CD流水线可以将SHERLOC集成到CI/CD中作为自动化的代码审查或bug分诊的第一步。当新的Issue被创建或CI测试失败时自动运行SHERLOC定位并将诊断报告附加到Issue中为开发者提供强有力的线索加速排查过程。将SHERLOC的结构化诊断定位思想融入你的代码修复智能体本质上是在模仿优秀开发者的核心技能系统性调试。它把模糊的问题拆解成可验证的假设在代码的迷宫中一步步搜集证据、推理排查最终锁定目标。这个过程本身或许比单纯生成一个补丁更能体现AI向“智能”迈出的一步。