从 0 学习 Alibaba Open Code Review(九):为 Code Review Agent 增加风险分级
2026/7/9 9:58:55 网站建设 项目流程

一、前言

第八篇已经把 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 件事:

  1. 新增risk_tools.py风险分级模块。
  2. 给每条 OCR comment 添加severity
  3. 生成顶层risk_summary
  4. 在 Markdown 报告中展示 Risk Summary。
  5. 在每条 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 和 severity
git_tools.py增加 changed files 的 exclude 过滤
ocr_tools.pyWindows 下优先解析ocr.cmd

五、风险等级设计

第一版风险等级先设计成三类:

等级含义
Critical高风险问题,例如 SQL 注入、权限绕过、密钥泄露
Warning中风险问题,例如错误处理、参数校验、导出缺失
Suggestion一般建议,例如命名、可读性、重构建议

为什么第一版只做三类?

因为 MVP 阶段不需要过度复杂。

如果一开始就设计 5 到 7 个等级,会增加解释成本,也不一定更准确。

三类已经足够支撑第一版报告:

Critical:必须优先处理 Warning:建议处理 Suggestion:可以后续优化

六、为什么第一版用关键词规则

风险分级有很多做法:

  1. 再调用一次 LLM 判断风险。
  2. 使用规则库匹配。
  3. 结合静态扫描器结果。
  4. 使用人工标注数据训练分类器。

第一版我选择关键词规则。

原因是:

  • 不额外消耗 LLM token。
  • 逻辑透明,容易解释。
  • 后续可以自然升级成 RAG 规则库或 LLM classifier。

所以第九篇先做一个简单但可用的规则分类器。

七、新增 risk_tools.py

risk_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]:result=dict(comment)text=build_comment_text(comment)severity,reason,matched_keywords=match_severity(text)result["severity"]=severity result["risk_reason"]=reason result["matched_keywords"]=matched_keywordsreturnresult

它做了几件事:

  1. 复制原始 comment。
  2. 把 comment 里的文本拼成一个待匹配字符串。
  3. 调用match_severity判断风险等级。
  4. 给 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]]:matched=find_keywords(text,CRITICAL_KEYWORDS)ifmatched:return"Critical","Security-sensitive or>,matched matched=find_keywords(text,WARNING_KEYWORDS)ifmatched:return"Warning","Reliability, correctness, or maintainability keyword matched.",matched matched=find_keywords(text,SUGGESTION_KEYWORDS)ifmatched:return"Suggestion","Code quality or readability keyword matched.",matchedreturn"Suggestion","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]:result=deepcopy(review_data)comments=result.get("comments")or[]classified_comments=[classify_comment(comment)forcommentincomments]result["comments"]=classified_comments result["risk_summary"]=summarize_risks(classified_comments)returnresult

这里做了两件事:

  1. comments里的每条评论加风险字段。
  2. 在顶层新增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:severity=comment.get("severity","Suggestion")ifseveritynotinsummary:severity="Suggestion"summary[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_data=classify_review_risks(review_data)state.review_json_path.write_text(json.dumps(state.review_data,ensure_ascii=False,indent=2)+"\n",encoding="utf-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]|None=None)->list[str]:patterns=[patternforpatternin(excludesor[])ifpattern]return[pathforpathinpathsifnotis_excluded(path,patterns)]

并在agent_graph.py中使用:

all_changed_files=get_changed_files(repo_root)state.changed_files=filter_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:str="ocr")->str:candidate=Path(ocr_bin)ifcandidate.exists():returnstr(candidate)ifos.name=="nt"andnotPath(ocr_bin).suffix:cmd_path=shutil.which(f"{ocr_bin}.cmd")ifcmd_path:returncmd_path resolved=shutil.which(ocr_bin)ifresolved:returnresolvedraiseRuntimeError(...)

这样在 Windows 下,如果传入默认的:

ocr

代码会优先查找:

ocr.cmd

这比第八篇要求用户手动传--ocr-bin更稳。

十七、运行语法检查

先做语法检查:

python-B-c"from pathlib import Path; files=[r'D:\agent\open-code-review-main\ocr-practice-demo\s09\open-code-review-agent-mvp\agent_graph.py',r'D:\agent\open-code-review-main\ocr-practice-demo\s09\open-code-review-agent-mvp\tools\git_tools.py',r'D:\agent\open-code-review-main\ocr-practice-demo\s09\open-code-review-agent-mvp\tools\ocr_tools.py',r'D:\agent\open-code-review-main\ocr-practice-demo\s09\open-code-review-agent-mvp\tools\risk_tools.py',r'D:\agent\open-code-review-main\ocr-practice-demo\s09\open-code-review-agent-mvp\tools\report_tools.py']; [compile(Path(f).read_text(encoding='utf-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-dir"C:\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.js`

Findings 标题现在带风险等级:

### 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 版本,有明显不足:

  1. 关键词规则比较粗糙。
  2. 可能出现误判。
  3. 不能理解复杂上下文。
  4. 不同语言、不同框架的风险关键词不一样。
  5. 不能判断某个问题是否真的可利用。

例如:

password

这个词出现在代码里,不一定一定是 Critical。

但如果它和 SQL 拼接、token、secret、authorization 等词一起出现,风险就明显更高。

所以后续可以升级成:

规则库 + 权重打分

或者:

LLM Risk Classifier

二十三、后续怎么升级成 LangGraph 节点

现在的风险分级是在run_workflow里直接调用:

state.review_data=classify_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 的风险分级能力。

核心成果如下:

  1. 新增tools/risk_tools.py
  2. 为每条 OCR comment 增加severity
  3. 为每条 OCR comment 增加risk_reason
  4. 为每条 OCR comment 增加matched_keywords
  5. 在 JSON 顶层增加risk_summary
  6. 在 Markdown 报告中增加Risk Summary
  7. 在 Findings 标题中展示[Critical][Warning][Suggestion]
  8. 修复 Changed Files 未按 exclude 过滤的问题。
  9. 优化 Windows 下ocr.cmd解析问题。

到这里,项目已经从:

能生成报告

升级成:

能生成带风险优先级的报告

二十五、下一篇计划

下一篇可以正式进入 LangGraph:

从 0 学 OpenCodeReview:引入 LangGraph 管理 Agent 工作流

下一篇可以把当前线性流程:

run_workflow

拆成:

check_git_changes run_ocr_review classify_risk generate_report save_outputs

这样项目会更像一个真正的 Agent Workflow。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询