从 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 和 severity |
git_tools.py |
增加 changed files 的 exclude 过滤 |
ocr_tools.py |
Windows 下优先解析 ocr.cmd |
五、风险等级设计
第一版风险等级先设计成三类:
| 等级 | 含义 |
|---|---|
Critical |
高风险问题,例如 SQL 注入、权限绕过、密钥泄露 |
Warning |
中风险问题,例如错误处理、参数校验、导出缺失 |
Suggestion |
一般建议,例如命名、可读性、重构建议 |
为什么第一版只做三类?
因为 MVP 阶段不需要过度复杂。
如果一开始就设计 5 到 7 个等级,会增加解释成本,也不一定更准确。
三类已经足够支撑第一版报告:
Critical:必须优先处理
Warning:建议处理
Suggestion:可以后续优化
六、为什么第一版用关键词规则
风险分级有很多做法:
- 再调用一次 LLM 判断风险。
- 使用规则库匹配。
- 结合静态扫描器结果。
- 使用人工标注数据训练分类器。
第一版我选择关键词规则。
原因是:
- 不额外消耗 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:给单条评论分级
核心函数是:
def classify_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_keywords
return result
它做了几件事:
- 复制原始 comment。
- 把 comment 里的文本拼成一个待匹配字符串。
- 调用
match_severity判断风险等级。 - 给 comment 添加三个新字段。
新增字段是:
| 字段 | 含义 |
|---|---|
severity |
风险等级 |
risk_reason |
为什么判成这个等级 |
matched_keywords |
命中的关键词 |
这样做的好处是:原始 OCR comment 不丢失,只是在它上面增加风险信息。
九、build_comment_text:为什么要拼多个字段
风险分级不能只看 content。
因为有些风险关键词可能出现在:
content
existing_code
suggestion_code
所以代码里这样处理:
def build_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:按优先级匹配
风险匹配函数是:
def match_severity(text: str) -> tuple[str, str, list[str]]:
matched = find_keywords(text, CRITICAL_KEYWORDS)
if matched:
return "Critical", "Security-sensitive or data-risk keyword matched.", matched
matched = find_keywords(text, WARNING_KEYWORDS)
if matched:
return "Warning", "Reliability, correctness, or maintainability keyword matched.", matched
matched = find_keywords(text, SUGGESTION_KEYWORDS)
if matched:
return "Suggestion", "Code quality or readability keyword matched.", matched
return "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。
代码是:
def classify_review_risks(review_data: dict[str, Any]) -> dict[str, Any]:
result = deepcopy(review_data)
comments = result.get("comments") or []
classified_comments = [classify_comment(comment) for comment in comments]
result["comments"] = classified_comments
result["risk_summary"] = summarize_risks(classified_comments)
return result
这里做了两件事:
- 给
comments里的每条评论加风险字段。 - 在顶层新增
risk_summary。
为什么要用 deepcopy?
因为不希望直接修改原始 review_data 对象。
这样函数更像一个纯转换:
原始 OCR JSON -> 带风险分级的 JSON
十二、risk_summary:生成风险统计
风险统计函数是:
def summarize_risks(comments: list[dict[str, Any]]) -> dict[str, int]:
summary = {severity: 0 for severity in SEVERITY_ORDER}
for comment in comments:
severity = comment.get("severity", "Suggestion")
if severity not in summary:
severity = "Suggestion"
summary[severity] += 1
return summary
生成结果类似:
"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 中新增导入:
from tools.risk_tools import classify_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 |", "|---|---:|"])
for severity in SEVERITY_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 里加入了:
def filter_paths(paths: list[str], excludes: list[str] | None = None) -> list[str]:
patterns = [pattern for pattern in (excludes or []) if pattern]
return [path for path in paths if not is_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 里新增了:
def resolve_ocr_executable(ocr_bin: str = "ocr") -> str:
candidate = Path(ocr_bin)
if candidate.exists():
return str(candidate)
if os.name == "nt" and not Path(ocr_bin).suffix:
cmd_path = shutil.which(f"{ocr_bin}.cmd")
if cmd_path:
return cmd_path
resolved = shutil.which(ocr_bin)
if resolved:
return resolved
raise RuntimeError(...)
这样在 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 data-risk keyword matched.",
"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 data-risk keyword matched.
**Matched Keywords**
`sql injection`, `prepared statement`, `parameterized`
第二条问题是:
### 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_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 的风险分级能力。
核心成果如下:
- 新增
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。
更多推荐



所有评论(0)