从 0 学习 Alibaba Open Code Review(九):为 Code Review Agent 增加风险分级

📅 2026/7/9 9:59:06
从 0 学习 Alibaba Open Code Review(九):为 Code Review Agent 增加风险分级
一、前言第八篇已经把 Code Review Agent MVP 的 V1 跑通了。上一版完成的链路是Git diff ↓ OpenCodeReview / LLM ↓ review-result.json ↓ review-report.md也就是说第八篇解决的是“能不能跑起来”的问题。但是只生成报告还不够。真实代码审查里开发者更关心的是哪些问题最严重 哪些问题应该优先修 哪些只是建议所以第九篇开始给 Agent MVP 增加一个新能力风险分级。本篇代码放在D:\agent\open-code-review-main\ocr-practice-demo\s09对应项目目录是D:\agent\open-code-review-main\ocr-practice-demo\s09\open-code-review-agent-mvp二、本篇目标本篇要完成 5 件事新增risk_tools.py风险分级模块。给每条 OCR comment 添加severity。生成顶层risk_summary。在 Markdown 报告中展示 Risk Summary。在每条 Finding 标题中显示[Critical]、[Warning]、[Suggestion]。升级后的流程是Git changes ↓ Run OpenCodeReview ↓ Parse JSON ↓ Classify Risk ↓ Generate Markdown Report相比第八篇多了一个步骤Classify Risk三、为什么需要风险分级第八篇的报告已经可以展示审查结果例如Findings 1. SQL Injection Vulnerability 2. Function not exported但它还没有告诉我这两个问题哪个更严重。从开发者视角看SQL 注入明显比“函数没有导出”更紧急。所以报告最好能变成这样[Critical] SQL Injection Vulnerability [Warning] Function not exported这就是风险分级要解决的问题。加入风险分级后报告不只是“列出问题”而是开始具备“处理优先级”的能力。这也是 Agent 项目从 Demo 走向工程化的重要一步。四、项目结构变化第九篇的代码结构如下open-code-review-agent-mvp ├── agent_graph.py ├── tools │ ├── __init__.py │ ├── git_tools.py │ ├── ocr_tools.py │ ├── report_tools.py │ └── risk_tools.py ├── README.md ├── requirements.txt └── .gitignore相比第八篇新增了tools/risk_tools.py同时改造了agent_graph.py report_tools.py git_tools.py ocr_tools.py每个文件的变化如下文件本篇变化risk_tools.py新增风险分级逻辑agent_graph.py在 OCR JSON 后加入风险分级步骤report_tools.py展示 Risk Summary 和 severitygit_tools.py增加 changed files 的 exclude 过滤ocr_tools.pyWindows 下优先解析ocr.cmd五、风险等级设计第一版风险等级先设计成三类等级含义Critical高风险问题例如 SQL 注入、权限绕过、密钥泄露Warning中风险问题例如错误处理、参数校验、导出缺失Suggestion一般建议例如命名、可读性、重构建议为什么第一版只做三类因为 MVP 阶段不需要过度复杂。如果一开始就设计 5 到 7 个等级会增加解释成本也不一定更准确。三类已经足够支撑第一版报告Critical必须优先处理 Warning建议处理 Suggestion可以后续优化六、为什么第一版用关键词规则风险分级有很多做法再调用一次 LLM 判断风险。使用规则库匹配。结合静态扫描器结果。使用人工标注数据训练分类器。第一版我选择关键词规则。原因是不额外消耗 LLM token。逻辑透明容易解释。后续可以自然升级成 RAG 规则库或 LLM classifier。所以第九篇先做一个简单但可用的规则分类器。七、新增 risk_tools.pyrisk_tools.py是本篇新增的核心文件。它定义了三组关键词CRITICAL_KEYWORDS[sql injection,command injection,remote code execution,rce,xss,csrf,hardcoded secret,secret key,api key,password,token,authentication,authorization,permission,privilege,prepared statement,parameterized,drop table,]WARNING_KEYWORDS[error handling,exception,null,undefined,validation,not exported,missing,async,timeout,resource leak,race condition,]SUGGESTION_KEYWORDS[style,readability,naming,comment,documentation,refactor,simplify,]这三组关键词分别对应Critical Warning Suggestion八、classify_comment给单条评论分级核心函数是defclassify_comment(comment:dict[str,Any])-dict[str,Any]:resultdict(comment)textbuild_comment_text(comment)severity,reason,matched_keywordsmatch_severity(text)result[severity]severity result[risk_reason]reason result[matched_keywords]matched_keywordsreturnresult它做了几件事复制原始 comment。把 comment 里的文本拼成一个待匹配字符串。调用match_severity判断风险等级。给 comment 添加三个新字段。新增字段是字段含义severity风险等级risk_reason为什么判成这个等级matched_keywords命中的关键词这样做的好处是原始 OCR comment 不丢失只是在它上面增加风险信息。九、build_comment_text为什么要拼多个字段风险分级不能只看content。因为有些风险关键词可能出现在content existing_code suggestion_code所以代码里这样处理defbuild_comment_text(comment:dict[str,Any])-str:parts[str(comment.get(content,)),str(comment.get(existing_code,)),str(comment.get(suggestion_code,)),]return .join(parts).lower()这样做的目的很简单扩大匹配范围。例如content里可能没有明确写 “password”但existing_code里有db.query(UPDATE users SET password ...)这种情况下也应该提高风险等级。十、match_severity按优先级匹配风险匹配函数是defmatch_severity(text:str)-tuple[str,str,list[str]]:matchedfind_keywords(text,CRITICAL_KEYWORDS)ifmatched:returnCritical,Security-sensitive or>,matched matchedfind_keywords(text,WARNING_KEYWORDS)ifmatched:returnWarning,Reliability, correctness, or maintainability keyword matched.,matched matchedfind_keywords(text,SUGGESTION_KEYWORDS)ifmatched:returnSuggestion,Code quality or readability keyword matched.,matchedreturnSuggestion,No high-risk keyword matched; treat as a general suggestion.,[]这里的顺序很重要Critical - Warning - Suggestion为什么 Critical 要放在最前面因为同一条评论里可能同时出现多个关键词。例如SQL Injection missing validation如果先匹配 Warning就可能把 SQL 注入误判成 Warning。所以应该优先匹配高风险关键词。十一、classify_review_risks处理整份 JSON单条评论分级之后还需要处理整份 OCR JSON。代码是defclassify_review_risks(review_data:dict[str,Any])-dict[str,Any]:resultdeepcopy(review_data)commentsresult.get(comments)or[]classified_comments[classify_comment(comment)forcommentincomments]result[comments]classified_comments result[risk_summary]summarize_risks(classified_comments)returnresult这里做了两件事给comments里的每条评论加风险字段。在顶层新增risk_summary。为什么要用deepcopy因为不希望直接修改原始review_data对象。这样函数更像一个纯转换原始 OCR JSON - 带风险分级的 JSON十二、risk_summary生成风险统计风险统计函数是defsummarize_risks(comments:list[dict[str,Any]])-dict[str,int]:summary{severity:0forseverityinSEVERITY_ORDER}forcommentincomments:severitycomment.get(severity,Suggestion)ifseveritynotinsummary:severitySuggestionsummary[severity]1returnsummary生成结果类似risk_summary:{Critical:1,Warning:1,Suggestion:0}这个字段很适合展示在报告顶部。因为读者一打开报告就能马上知道高危问题有几个 中风险问题有几个 普通建议有几个十三、修改 agent_graph.py加入风险分级节点第八篇的流程是OCR JSON ↓ Generate Markdown Report第九篇改成OCR JSON ↓ Classify Risk ↓ Generate Markdown Report在agent_graph.py中新增导入fromtools.risk_toolsimportclassify_review_risks然后在run_workflow中加入state.review_dataclassify_review_risks(review_data)state.review_json_path.write_text(json.dumps(state.review_data,ensure_asciiFalse,indent2)\n,encodingutf-8,)这说明review-result.json保存的不再只是 OCR 原始结果而是增强后的结果。增强后的结果包含原始 OCR 字段 severity risk_reason matched_keywords risk_summary这一步可以理解成一个新的 Agent 节点risk_classification_node虽然现在还没有用 LangGraph但节点思想已经很清楚了。十四、修改 report_tools.py展示风险信息report_tools.py主要改了两处。第一处增加 Risk Summary。lines.extend([,## Risk Summary,,| Severity | Count |,|---|---:|])forseverityinSEVERITY_ORDER:lines.append(f|{severity}|{risk_summary.get(severity,0)}|)生成报告后会显示## Risk Summary | Severity | Count | |---|---:| | Critical | 1 | | Warning | 1 | | Suggestion | 0 |第二处Finding 标题增加 severity。lines[f###{index}. [{severity}] {path}:{start_line}-{end_line},,]生成后会变成### 1. [Critical] s01/src/user.js:37-37相比第八篇这个变化很明显。开发者不需要读完整段评论就能先看到风险等级。十五、修复第八篇发现的问题第八篇真实运行时发现一个问题OCR 的 --exclude 生效了但 Markdown 报告里的 Changed Files 仍然显示被排除文件。原因是changed_files 来自 git status --porcelain 没有按 exclude 过滤第九篇在git_tools.py里加入了deffilter_paths(paths:list[str],excludes:list[str]|NoneNone)-list[str]:patterns[patternforpatternin(excludesor[])ifpattern]return[pathforpathinpathsifnotis_excluded(path,patterns)]并在agent_graph.py中使用all_changed_filesget_changed_files(repo_root)state.changed_filesfilter_paths(all_changed_files,effective_excludes)这样报告里的 Changed Files 会和 exclude 规则保持一致。测试时加了--exclude outputs/**--exclude outputs-review-result.json--exclude s09/**最终报告里的 Changed Files 只显示s01/src/user.js十六、顺手优化 Windows 下 ocr.cmd 解析第八篇还遇到一个问题PowerShell 能运行 ocr但 Python subprocess 找不到 ocr。原因是 PowerShell 找到的是ocr.ps1而 Python 更适合调用ocr.cmd。第九篇在ocr_tools.py里新增了defresolve_ocr_executable(ocr_bin:strocr)-str:candidatePath(ocr_bin)ifcandidate.exists():returnstr(candidate)ifos.namentandnotPath(ocr_bin).suffix:cmd_pathshutil.which(f{ocr_bin}.cmd)ifcmd_path:returncmd_path resolvedshutil.which(ocr_bin)ifresolved:returnresolvedraiseRuntimeError(...)这样在 Windows 下如果传入默认的ocr代码会优先查找ocr.cmd这比第八篇要求用户手动传--ocr-bin更稳。十七、运行语法检查先做语法检查python-B-cfrom pathlib import Path; files[rD:\agent\open-code-review-main\ocr-practice-demo\s09\open-code-review-agent-mvp\agent_graph.py,rD:\agent\open-code-review-main\ocr-practice-demo\s09\open-code-review-agent-mvp\tools\git_tools.py,rD:\agent\open-code-review-main\ocr-practice-demo\s09\open-code-review-agent-mvp\tools\ocr_tools.py,rD:\agent\open-code-review-main\ocr-practice-demo\s09\open-code-review-agent-mvp\tools\risk_tools.py,rD:\agent\open-code-review-main\ocr-practice-demo\s09\open-code-review-agent-mvp\tools\report_tools.py]; [compile(Path(f).read_text(encodingutf-8), f, exec) for f in files]; print(syntax ok:, len(files), files)输出syntax ok: 5 files说明 5 个核心 Python 文件语法正常。十八、离线运行测试这里继续用已有 OCR JSON 做离线测试python-B D:\agent\open-code-review-main\ocr-practice-demo\s09\open-code-review-agent-mvp\agent_graph.py--repo D:\agent\open-code-review-main\ocr-practice-demo--output-dirC:\Users\ad\Documents\New project\s09-final-output-filtered--use-existing-json D:\agent\open-code-review-main\ocr-practice-demo\outputs-review-result.json--exclude outputs/**--exclude outputs-review-result.json--exclude s09/**输出Code Review Agent MVP finished. Repo: D:\agent\open-code-review-main\ocr-practice-demo Changed files: 1 JSON: C:\Users\ad\Documents\New project\s09-final-output-filtered\review-result.json Report: C:\Users\ad\Documents\New project\s09-final-output-filtered\review-report.md这里的Changed files: 1说明 exclude 过滤生效了。因为当前仓库里有M s01/src/user.js ?? outputs-review-result.json ?? s09/但加了 exclude 后报告里只展示业务代码s01/src/user.js十九、查看增强后的 review-result.json生成的 JSON 里每条 comment 已经带上风险字段。第一条 SQL 注入评论{path:s01/src/user.js,content:**SQL Injection Vulnerability**: ...,start_line:37,end_line:37,severity:Critical,risk_reason:Security-sensitive or>,matched_keywords:[sql injection,prepared statement,parameterized]}第二条函数未导出评论{path:s01/src/user.js,content:The updateUserEmail function is defined but not exported...,start_line:36,end_line:39,severity:Warning,risk_reason:Reliability, correctness, or maintainability keyword matched.,matched_keywords:[not exported]}顶层新增了risk_summary:{Critical:1,Warning:1,Suggestion:0}这说明风险分级模块已经生效。二十、查看增强后的 Markdown 报告报告里新增了 Risk Summary## Risk Summary | Severity | Count | |---|---:| | Critical | 1 | | Warning | 1 | | Suggestion | 0 |Changed Files 也只显示业务代码## Changed Files - s01/src/user.jsFindings 标题现在带风险等级### 1. [Critical] s01/src/user.js:37-37并且会展示分级原因**Risk Reason** Security-sensitive or>### 2. [Warning] s01/src/user.js:36-39命中的关键词是not exported这样报告的可读性明显比第八篇更好。二十一、这版代码的工程价值第八篇的报告只能说明发现了哪些问题第九篇的报告可以进一步说明哪些问题更严重 为什么判定为这个等级 命中了哪些关键词这就让报告从“结果展示”升级成了“处理优先级建议”。这也是一个 Code Review Agent 应该具备的能力。因为审查结果如果很多开发者不可能逐条从头读到尾。风险分级可以帮助开发者先处理Critical再处理Warning最后再看Suggestion二十二、当前规则方案的不足当前风险分级仍然是一个 MVP 版本有明显不足关键词规则比较粗糙。可能出现误判。不能理解复杂上下文。不同语言、不同框架的风险关键词不一样。不能判断某个问题是否真的可利用。例如password这个词出现在代码里不一定一定是 Critical。但如果它和 SQL 拼接、token、secret、authorization 等词一起出现风险就明显更高。所以后续可以升级成规则库 权重打分或者LLM Risk Classifier二十三、后续怎么升级成 LangGraph 节点现在的风险分级是在run_workflow里直接调用state.review_dataclassify_review_risks(review_data)如果后续引入 LangGraph可以把它变成一个独立节点check_git_changes ↓ run_ocr_review ↓ parse_review_json ↓ classify_risk ↓ generate_report其中classify_risk就是第九篇新增的能力。也就是说第九篇虽然还没有引入 LangGraph但已经为 LangGraph 的节点拆分做准备了。二十四、本篇总结本篇完成了 Code Review Agent MVP 的风险分级能力。核心成果如下新增tools/risk_tools.py。为每条 OCR comment 增加severity。为每条 OCR comment 增加risk_reason。为每条 OCR comment 增加matched_keywords。在 JSON 顶层增加risk_summary。在 Markdown 报告中增加Risk Summary。在 Findings 标题中展示[Critical]、[Warning]、[Suggestion]。修复 Changed Files 未按 exclude 过滤的问题。优化 Windows 下ocr.cmd解析问题。到这里项目已经从能生成报告升级成能生成带风险优先级的报告二十五、下一篇计划下一篇可以正式进入 LangGraph从 0 学 OpenCodeReview引入 LangGraph 管理 Agent 工作流下一篇可以把当前线性流程run_workflow拆成check_git_changes run_ocr_review classify_risk generate_report save_outputs这样项目会更像一个真正的 Agent Workflow。