Rust项目LLM规则驱动代码审查实践

📅 2026/8/27 5:02:15
Rust项目LLM规则驱动代码审查实践
代码审查是 Rust 项目里最贵但也最值钱的环节。Rust 编译器已经挡掉了大量内存安全问题但unsafe块、unwrap/expect、错误处理策略、并发模型、trait 设计这些问题仍然要靠人逐行判断。最近关于“多个 Rust 团队采用 LLM 规则”的讨论本质上是这一趋势的缩影让 LLM 先按固定规则扫一遍 diff把人从重复性检查里解放出来让 reviewer 集中精力做真正需要经验的事情。这里的关键不是“让 AI 替代人做 review”而是用一套可配置、可解释、可拦截的规则把 review 流程变成“机器先做初筛人做最终裁决”。这篇文章会围绕这套模式讲清楚几件事为什么 Rust 项目特别适合引入 LLM 评审规则在落地时应该怎样划分人、规则引擎和 LLM 的职责如何从零搭一个最小可运行的系统规则文件、模型参数、CI 集成怎么设计以及最容易被忽略的误报、超时、输出解析和安全问题。读完以后你能直接把这套流程套到自己维护的 Rust 仓库里而不是只停留在“听说过”的层面。1. 为什么 Rust 代码审查需要 LLM 规则辅助在深入写代码之前先想清楚一个问题Rust 项目里的人类 reviewer 到底被什么消耗掉了。1.1 人类 reviewer 的注意力被重复问题分散一个中等规模的 Rust 仓库PR 密度不会低。每个 PR 里都会有类似的问题有人在新代码里写了unwrap()有人把Result吞掉了有人在错误处理上直接expect(cannot fail)还有一些 unsafe 块没有写// SAFETY:注释。这些问题单独看都不复杂但累积起来非常消耗评审者。人类的短期记忆和工作注意力是有限的。reviewer 在一个 PR 上花 40 分钟其中 25 分钟花在机械性问题上那么真正用于评估并发模型、API 设计、性能取舍和语义边界的时间就被挤占了。更麻烦的是一旦 reviewer 觉得“这个 PR 全是小问题”就容易形成惯性把更深层的设计问题也忽略掉。LLM 规则要解决的正是这种注意力结构的问题。1.2 传统 lint 和 clippy 已经解决了一部分但不够Rust 项目通常已经接入了rustfmt和clippy。前者解决格式后者解决一大批 lint 规则比如未使用的变量、不必要的克隆、可推导的 lifetime。这些工具是确定性的速度快结果稳定适合在 CI 里直接作为硬性检查。但 clippy 能覆盖的边界并不是完整的 code review 范围。比如下面这些问题clippy 通常不会管在核心业务路径里调用了unwrap()但调用者的错误处理策略明显不一致。一个unsafe块虽然有注释但注释没有解释不变量成立的依据。错误处理把ResultT, E到处用map_err(|e| format!({}, e))展开导致类型信息丢失。新加的 trait 实现和现有语义冲突可能影响后续维护。这类问题依赖上下文语义需要理解“这段代码在项目里承担什么职责”而不是只看局部语法。传统静态分析用规则匹配很难写清楚“关键路径”到底是什么而 LLM 的优势恰好是能结合规则描述和代码上下文做判断。1.3 Rust 项目不适合“AI 直接批准”的模式有一种错误的理解是接入了 LLM 规则就可以让模型直接给 PR 打“通过”或“不通过”。这种做法在 Rust 项目里风险很高。Rust 的语法表达力强同样的功能可以写出截然不同的实现性能特征和安全性差异很大。一个unsafe块可能完全安全也可能在修改三行代码后变成未定义行为。模型即使上下文能力再强也无法替维护者对项目的不变量、产品的业务约束和性能目标负责。所以更合理的做法是LLM 只负责“找证据”不负责“下结论”。规则决定找什么LLM 负责在 diff 里找到对应代码人类 reviewer 根据证据做最终判断。这也是后面规则文件设计的核心出发点每条规则都要求模型输出file、line、message、evidence缺一不可。没有证据的建议不会被直接展示给 review 者而是进入待确认列表。2. 先分清人、规则引擎和 LLM 的职责这套模式不是一个单独的 AI 服务而是一条由多个组件组成的流水线。如果职责划分不清楚最后一定会变成“模型说了一堆话人不知道要不要信”。2.1 四个角色的分工可以把整条链路拆成四个角色。角色负责内容不负责内容人类 reviewer最终裁决、设计评审、安全性判断、性能取舍逐行找格式问题、重复提醒同类缺陷规则引擎触发事件、拉取 diff、过滤文件、组装 prompt、解析模型输出编写业务规则、解释业务语义LLM按规则在 diff 中找证据、生成结构化建议直接批准或拒绝 PRCI 系统在 PR 生命周期里自动调用评审任务、发布报告决定规则是否合理规则引擎建议用一张表来定义“哪种类型的问题需要被自动扫描”。它不关心规则内容本身只负责把规则描述、代码 diff、项目上下文拼接成请求再把模型返回的 JSON 转成评审意见。2.2 人类 reviewer 必须保留的职责自动规则可以提示“这里出现了unwrap()”但它回答不了“这个unwrap()是否真的会 panic”。反过来说即便模型给出了一个很具体的替代建议也不代表这个建议适合当前项目的错误处理约定。所以每条自动评审意见都必须显式标注“这只是一个候选建议”。规则引擎可以把它显示在 PR 评论里但只有人工 reviewer 选择了“同意”或“稍后处理”它才进入最终评审记录。这个设计可能看起来多一步但它保证了人类 reviewer 始终掌握最终话语权。2.3 LLM 规则的输入输出设计LLM 规则的核心是让模型做“受约束的判断”。约束来自两部分一是规则的描述文本二是结构化输出格式。输入侧至少包含四部分系统指令说明模型的身份和输出约束。规则描述说明要检查什么、什么情况下算违反。代码 diff新增、修改、删除的具体行。额外上下文比如Cargo.toml、被修改文件的相关片段。输出侧统一使用 JSON建议结构如下{ comments: [ { rule_id: rust/no-unwrap-critical, severity: warning, file: src/api/handler.rs, line: 52, message: 该函数处于请求处理路径调用 unwrap() 可能因为输入数据缺失直接 panic建议改为返回错误。, evidence: pub async fn handle(req: Request) - Response {\n let token req.header(\Authorization\).unwrap(); } ] }这里的evidence很重要。它让人类 reviewer 不用再切回 PR 页面反复找代码也能防止 LLM 编造不存在的代码行。凡是无法提供明确证据的输出规则引擎都应该丢弃或标为低置信度。3. 搭建一个最小可用的 LLM 评审服务理解了职责接下来从零搭建一个最小闭环系统。目标很简单一个 Rust 项目在提交 PR 时CI 自动拉取 diff按照规则文件用 LLM 扫描然后把结果输出成 JSON最后由脚本发布到 PR 评论。3.1 环境准备本文示例在一个 Rust 仓库里运行。需要准备以下环境。工具用途检查方式Rust 工具链编译示例中的评审服务和测试项目rustc --version cargo --versiongit获取 diff 和分支信息git --versionLLM API提供 OpenAI 兼容的 chat completions 接口确认接口地址和 API key 可用GitHub Actions 或 GitLab CI自动触发评审任务确认仓库能运行 CI如果安装 Rust 时网络较慢可以配置国内镜像源。具体方式以镜像站文档为准例如使用清华大学 tuna 或中科大 USTC 提供的 crates 镜像。配置完成后执行cargo build验证依赖拉取是否正常。3.2 项目结构为了不污染正式业务代码评审服务可以放在仓库根目录的tools/review-cli/子项目里。rust-project/ ├── Cargo.toml ├── rules/ │ └── code-review.yaml ├── tools/ │ └── review-cli/ │ ├── Cargo.toml │ └── src/ │ ├── main.rs │ ├── diff.rs │ └── llm.rs └── .github/ └── workflows/ └── llm-review.ymlrules/code-review.yaml放评审规则tools/review-cli/放实现代码.github/workflows/放 CI 配置。3.3 定义第一个规则文件新建rules/code-review.yaml内容如下rules: - id: rust/no-unwrap-critical severity: warning scope: added_lines paths: - src/** - !**/tests/** description: 核心业务路径中不允许直接调用 unwrap 或 expect。 prompt: | 检查 diff 中所有新增或修改的代码行。 如果发现 unwrap() 或 expect() 出现在库代码、请求处理函数或核心业务路径中 输出一条 comment包含 file、line、message、evidence。 message 需要说明为什么这里可能有 panic 风险并给出替代写法。 evidence 必须从输入 diff 中复制完整代码行。scope: added_lines表示只检查新增行避免对未修改的旧代码反复报警。paths用来限定扫描目录!**/tests/**表示排除测试代码因为测试里出现unwrap()通常是合理的。3.4 编写 diff 收集脚本评审服务需要拿到 PR 相对基准分支的 diff。在 CI 里可以使用git fetch origin $BASE_REF git diff origin/$BASE_REF...HEAD -- . :(exclude)Cargo.lock /tmp/review.diff这里的三个点表示使用三点 diff只比较从合并基到 HEAD 的变更避免把基准分支上已有的代码全部带进来。排除Cargo.lock是因为锁文件变更大且通常不需要逐行审查。在本地调试时可以用类似命令模拟git diff origin/main...HEAD -- src/ /tmp/review.diff wc -l /tmp/review.diff确认 diff 内容存在后再进入下一步。3.5 用 Rust 实现规则调用核心在tools/review-cli/Cargo.toml中加入以下依赖。[package] name review-cli version 0.1.0 edition 2021 [dependencies] reqwest { version 0.12, features [json] } serde { version 1, features [derive] } serde_json 1 tokio { version 1, features [macros, rt-multi-thread] } serde_yaml 0.9以下代码是核心调用的最小示例实际项目需要结合自己的路径和错误处理扩展。use serde::{Deserialize, Serialize}; use serde_json::Value; #[derive(Debug, Deserialize)] struct ReviewRule { id: String, severity: String, description: String, prompt: String, } #[derive(Debug, Serialize)] struct ReviewComment { rule_id: String, severity: String, file: String, line: Optionu32, message: String, evidence: String, } async fn run_rule( rule: ReviewRule, diff: str, ) - ResultVecReviewComment, Boxdyn std::error::Error { let client reqwest::Client::new(); let endpoint std::env::var(LLM_ENDPOINT)?; let api_key std::env::var(LLM_API_KEY)?; let messages vec![ serde_json::json!({ role: system, content: 你是一个 Rust 代码审查助手。只根据用户提供的规则和 diff 输出结构化 JSON不要输出额外内容。 }), serde_json::json!({ role: user, content: format!( 规则 ID{}\n规则描述{}\n\n代码 diff\n{}, rule.id, rule.prompt, diff ) }), ]; let body serde_json::json!({ model: gpt-4o-mini, messages: messages, temperature: 0, response_format: {type: json_object} }); let resp: Value client .post(endpoint) .bearer_auth(api_key) .json(body) .send() .await? .json() .await?; let content resp[choices][0][message][content] .as_str() .unwrap_or({}); let parsed: Value serde_json::from_str(content)?; let comments: VecReviewComment serde_json::from_value(parsed[comments].clone())?; Ok(comments) } #[tokio::main] async fn main() - Result(), Boxdyn std::error::Error { let diff std::fs::read_to_string(/tmp/review.diff)?; let rules_file std::fs::read_to_string(rules/code-review.yaml)?; let config: Value serde_yaml::from_str(rules_file)?; let rules: VecReviewRule serde_json::from_value(serde_json::to_value(config[rules])?)?; for rule in rules { match run_rule(rule, diff).await { Ok(comments) { for comment in comments { println!({}, serde_json::to_string(comment)?); } } Err(err) { eprintln!(rule {} failed: {}, rule.id, err); } } } Ok(()) }这段代码有几处值得说明。response_format要求模型返回 JSON 对象这能显著提高解析稳定性。temperature设置为 0避免生成建议时产生随机波动。单个规则调用失败时不能中断整个评审流程应该记录错误后继续处理下一条规则。注意代码里resp[choices][0][message][content].as_str().unwrap_or({})只是一个原型写法。在实际工具中这里要处理网络超时、JSON 解析失败、响应字段缺失等情况不能直接把unwrap_or带进生产环境否则这条规则服务的稳定性会很差。3.6 接入 GitHub Actions在.github/workflows/llm-review.yml中新增 workflow。name: llm-code-review on: pull_request: types: [opened, synchronize] jobs: review: runs-on: ubuntu-latest permissions: contents: read pull-requests: write steps: - name: Checkout uses: actions/checkoutv4 with: fetch-depth: 0 - name: Setup Rust uses: dtolnay/rust-toolchainstable - name: Collect diff env: BASE_REF: ${{ github.event.pull_request.base.ref }} run: | git fetch origin $BASE_REF --depth 1 git diff origin/$BASE_REF...HEAD -- . :(exclude)Cargo.lock /tmp/review.diff - name: Run review-cli env: LLM_ENDPOINT: ${{ secrets.LLM_ENDPOINT }} LLM_API_KEY: ${{ secrets.LLM_API_KEY }} run: | cargo run --manifest-path tools/review-cli/Cargo.toml -- /tmp/review.diff review.json - name: Upload review result uses: actions/upload-artifactv4 with: name: review-result path: review.jsonpull-requests: write权限用于后续把评审结果发布到 PR 评论区。示例中先上传 artifact避免把整个发布逻辑写得太复杂。如果你用 GitLab CI可以换成对应的rules配置和artifacts字段。3.7 验证整个链路提交一个测试 PR在src/api/handler.rs中故意加入一段包含unwrap()的代码例如pub async fn handle(req: Request) - Response { let token req.header(Authorization).unwrap(); // ... }等待 workflow 运行完成后打开review.json检查输出。正常情况下至少会出现一条规则 ID 为rust/no-unwrap-critical的建议并且evidence字段里包含unwrap()所在的原始行。如果没有输出先按第 6 节的排查路径检查。4. 规则设计从“让 LLM 挑错”到“让 LLM 按规则工作”接入一个演示规则只是第一步。真正决定这套体系价值的是规则文件写得好不好。规则文件如果写得太泛LLM 会产生大量无用建议写得太死又会漏掉真正的问题。这里需要掌握平衡。4.1 规则字段应该怎样组织一份可维护的规则至少包含以下字段。字段作用示例id唯一标识用于统计和去重rust/unsafe-without-safetyseverity提示级别决定是否阻塞合并error、warning、infoscope扫描范围added_lines、modified_hunks、whole_filepaths文件路径过滤src/**,!**/tests/**conditions前置条件例如必须包含某个函数名fn handleprompt描述要查找的问题和输出约束见前文suppressions抑制规则例如允许带注释的行# allow(no_unwrap)severity不一定直接映射到 CI 结果。初期建议error只用于“确定性问题”例如 unsafe 块缺少 SAFETY 注释warning和info只作为提示不阻塞合并。否则规则误报一个warning就会阻塞整个 PR团队成员很快会失去耐心。4.2 示例规则检查 unsafe 块是否缺少 SAFETY 注释Rust 社区对unsafe代码有比较明确的约定每个 unsafe 块都应该解释为什么它是安全的。这是静态工具很难完全自动化的因为“安全依据”是语义信息。规则文件片段如下- id: rust/unsafe-without-safety severity: error scope: added_lines paths: - src/** prompt: | 检查 diff 中所有新增的 unsafe 块。 如果 unsafe 块上方没有以 // SAFETY: 开头的注释 或者 SAFETY 注释没有解释内存安全不变量成立的理由 输出一条 comment。 evidence 必须包含 unsafe 关键字所在行及前面的注释行。这个规则非常契合 Rust 项目的实际需求。人类 reviewer 看到unsafe时依然要读代码但这个规则可以保证代码作者至少在提交前认真写过 SAFETY 注释减少“unsafe 裸奔”进入 review 流程的概率。4.3 示例规则发现被静默忽略的 Result- id: rust/ignored-result severity: warning scope: added_lines paths: - src/** prompt: | 检查 diff 中新增的代码行。 如果 Result 类型返回值被直接丢弃例如 let _ do_something(); 或者整条表达式没有使用 Result说明可能忽略了错误处理。 对每处输出一条 comment并在 message 中建议处理错误或显式声明忽略原因。要注意这个规则容易误报因为有些let _ 是作者刻意表示“我知道这里有错误但我现在不处理”。所以规则里要要求 message 中提示“显式声明忽略原因”比如let _ do_something().map_err(|e| warn!({}, e));。这样可以引导作者写出有意图的代码而不是机械地补一个let _。4.4 误报抑制机制的三种常见做法LLM 规则一定会产生误报。设计规则时就要考虑怎么抑制。第一种是路径排除。测试代码、构建脚本、examples 目录通常可以放宽规则。第二种是行内忽略标记。在 Rust 代码里定义类似#[allow(llm_rule::no_unwrap)]的注释或自定义 lint 标记规则引擎在拿到 diff 后先做过滤如果发现目标行上方有忽略标记就不再交给 LLM 分析。第三种是重复抑制。同一个规则对同一个文件、同一个行号只报告一次不重复刷屏。// 代码作者可以这样显式声明某个 unwrap 是安全的 let token req.header(Authorization) .map(|s| s.to_string()) .unwrap(); // llm-ignore: rust/no-unwrap-critical规则引擎在预处理 diff 时可以先用正则找出llm-ignore: rule-id标记命中后直接从候选列表里移除这一行。这样真正的豁免权仍然留在代码作者手里。5. 模型参数、上下文预算与质量保障LLM 评审服务的质量不完全取决于模型本身prompt 结构、上下文裁剪和参数设置同样重要。下面这些参数在落地时都要仔细调。5.1 模型选择不应该一开始就追最强模型评审任务有两个场景。第一个是快速预筛对每个 diff 跑一遍目的是找出明显违反规则的地方。这个场景可以选择速度快、成本低的模型即使漏掉一些深层问题也不致命因为后面还有人类 reviewer。第二个是深度审查对高风险文件或疑似 unsafe 代码做更详细的分析这时可以使用上下文窗口更大、推理能力更强的模型。使用两级模型可以控制成本也让团队对“自动化能做到什么程度”形成预期。初期不要直接让最贵的模型参与所有扫描先看误报率能不能接受。5.2 temperature 参数应该固定为低值代码审查任务和创意写作不同。你不需要模型发挥想象力只需要它稳定地按规则描述做判断。因此temperature建议设置为 0top_p也可以设置为 0.1 到 0.3减少输出随机性。如果同一个 PR 连续跑两次结果差异很大那这套规则服务很难被团队信任。稳定性比单次准确性更影响使用体验。5.3 上下文裁剪diff 太大怎么办真实项目的 PR 很容易超过模型上下文窗口。如果把整个 diff 塞进 prompt结果通常是截断、遗漏或模型忽略规则。这里推荐按文件拆分处理。流程是规则引擎先读取完整 diff按文件路径分组再通过paths规则过滤然后对每个文件单独调用 LLM 分析。如果单个文件 diff 仍然过大可以只处理新增行和修改行并限制为前 200 行变更。git diff origin/main...HEAD -- src/api/handler.rs | head -n 400在 prompt 中直接说明“以下是文件 src/api/handler.rs 的 diff只分析新增行”模型会更容易聚焦。5.4 防止 LLM 幻觉建议幻觉是 LLM 评审服务最大的风险之一。模型可能在证据不充分时凭印象给出不存在的行号或 API。这里有三种可行的防护手段。第一强制要求evidence必须从输入 diff 中逐字复制。第二在规则引擎里做行号校验将模型输出的line与 diff 中的新增行集合比对如果行号不在新增范围内直接丢弃。第三对于包含unsafe、并发、unsafe trait 等高风险建议必须在评审报告中标记“需要人类 reviewer 二次验证”不能直接显示为最终结论。// 伪代码行号校验 let added_lines: std::collections::HashSetu32 diff.added_line_numbers(); if !added_lines.contains(comment.line) { log::warn!(drop comment because line {} is not in added lines, comment.line); continue; }这种“宁可少报不能错报”的原则是自动化评审工具进入生产环境的前提。5.5 建立人工复核闭环模型输出不应该是最终结果。推荐在 CI 里生成一个review.json报告然后由团队成员在 PR 评论或独立页面里逐条确认。只有被人工标记为“有效”的建议才进入统计指标。记录每次评审的指标很有价值总建议数。有效建议比例。被采纳并修改代码的比例。规则 ID 维度的误报率。用这些数据定期调整规则描述。你会发现某个规则连续两周误报率超过 50%就该改 prompt 或者把触发范围收窄。6. 常见问题与排查路径自建 LLM 评审服务时问题通常集中在这几个地方规则不生效、解析失败、误报过高、CI 超时、密钥泄露。6.1 问题速查表问题现象常见原因检查方式处理建议PR 评论没有出现评审结果workflow 没有触发查看 Actions 运行记录确认on.pull_request配置正确规则完全不匹配diff 路径与paths不匹配确认 diff 文件是否存在检查路径 glob 规则LLM 返回内容解析失败返回了非 JSON 文本查看原始 response增加重试和 fallback 逻辑误报过多规则描述过泛抽样统计有效比例收窄paths或补充约束CI 等待时间过长diff 过大或模型响应慢观察每次调用耗时按文件拆分、限制行数API key 泄露环境变量未加密检查 secrets 配置使用 CI secrets不要写进代码6.2 规则不生效先检查 diff 本身最常遇到的坑是“规则文件写好了CI 也跑了但没有任何输出”。这时不要先去怀疑模型先看 diff 文件是否正常。cat /tmp/review.diff | head -n 50如果文件是空说明git diff命令里的分支引用有问题。在 GitHub Actions 中github.event.pull_request.base.ref是目标分支名但要先git fetch origin $BASE_REF才能拿到本地引用。检查fetch-depth: 0是否配置否则只能拿到浅克隆无法执行三点 diff。另一个常见问题是路径过滤写错。paths: [src/**]只匹配src/下的文件如果代码在crates/api/src/就需要改成crates/**/src/**或直接用**/*.rs。6.3 LLM 输出不稳定加 retry 和 fallback模型返回格式不稳定时最简单的方案是加入一次重试。第一次解析失败后把原始内容重新作为输入交给模型让它修正。async fn parse_with_retry( client: reqwest::Client, body: Value, ) - ResultVecReviewComment, Boxdyn std::error::Error { for attempt in 0..2 { let resp: Value client.post(std::env::var(LLM_ENDPOINT)?) .bearer_auth(std::env::var(LLM_API_KEY)?) .json(body) .send().await? .json().await?; let content resp[choices][0][message][content] .as_str().unwrap_or({}); if let Ok(parsed) serde_json::from_str::Value(content) { if let Ok(comments) serde_json::from_value::VecReviewComment(parsed[comments].clone()) { return Ok(comments); } } eprintln!(retry attempt {} because JSON parse failed, attempt 1); tokio::time::sleep(std::time::Duration::from_secs(2)).await; } Ok(vec![]) }注意 retry 不能无限循环最多两次即可。还要区分“网络错误”和“解析错误”网络错误可以等更久再重试解析错误说明 prompt 本身有问题重试不一定有用需要回到规则描述上调整。6.4 误报过高从规则描述找原因误报通常不是模型“乱说”而是规则描述给模型留下了太大的解释空间。比如“检查代码中是否有不安全写法”这种描述模型几乎一定会报一堆不相关的问题。更合理的写法是给模型一个正反示例。在 prompt 里加入“以下是符合规则的例子”和“以下是不符合规则的例子”能显著降低误报率。规则描述 检查新增代码中是否包含 unwrap() 调用。 以下情况不算违反 1. 在测试代码中。 2. 在 main() 函数中作为程序退出点。 3. 代码上方有 // llm-ignore: rust/no-unwrap-critical 标记。 以下情况算违反 1. 在请求处理函数中。 2. 在库的公共 API 中。这种正反示例直接用规则文件维护。每条规则都可以带examples字段规则引擎把它拼到 prompt 尾部。6.5 安全与成本密钥永远放 CI secretsLLM 评审服务需要 API key这是最需要保护的内容。绝对不要把 key 写在main.rs或 workflow 文件里。GitHub 项目要放在Settings - Secrets - Actions中然后在 workflow 里通过${{ secrets.LLM_API_KEY }}传给环境变量。本地调试时可以使用本地的.env文件读取 key但不要提交到 git。项目根目录的.gitignore里应加入.env、review.json、/tmp/review.diff等临时文件。成本控制方面每次调用都会消耗 token建议在 workflow 里限制 diff 大小。如果 PR 变更超过一定行数可以只扫描新增行或者在汇总里标记“本次评审为抽样分析未覆盖全部变更”。7. 落地检查清单与扩展方向最后给出可以照着执行的清单也聊聊这套体系下一步能往哪里走。7.1 上线前检查清单检查项状态规则文件能按paths正确过滤文件是 / 否本地可以生成非空 diff是 / 否LLM API 接口、模型名、key 配置正确是 / 否模型输出能够稳定解析成 JSON是 / 否evidence缺少或行号不在变更范围的意见会被丢弃是 / 否workflow 只在 PR 上输出报告不直接合并是 / 否API key 只存在于 CI secrets是 / 否测试 PR 能产生至少一条结构化建议是 / 否7.2 学习环境与生产环境的差异在学习环境里直接在本地运行 CLI把review.json打印到终端就足够。生产环境则需要额外补齐几个能力。场景学习环境生产环境触发方式手动运行PR 生命周期自动触发报告输出终端打印PR 评论或独立页面误报处理人工看指标统计 规则迭代模型选择单模型多级模型、按文件风险选择审计无所有评审记录可回溯异常恢复失败就重新跑重试、限流、熔断7.3 扩展方向从代码评审到仓库知识评审这套“规则 LLM 人工复核”的模式不只在代码 diff 上有价值。它能复用到很多仓库场景。依赖变更评审Cargo.toml变化时用 LLM 分析新增依赖是否引入大量 unsafe 代码或维护风险。文档评审README、内部设计文档提交时检查是否包含过期命令、错误链接。安全敏感变更评审当 diff 包含unsafe、process::Command、网络监听、文件删除等高风险片段时自动要求额外的人类 review。团队规模小的时候先跑通第一条代码评审链路就足够。不要一开始就铺开太多场景否则规则维护成本会把自动化带来的收益吃掉。7.4 给新手团队的练习建议如果你刚接触这个方向不要直接上复杂工具。建议按三步走。第一步把规则文件设计好加上 3 到 5 个与当前项目最痛点相关的规则比如“unsafe 必须有 SAFETY 注释”和“关键路径禁止 unwrap”。第二步本地手动跑通 CLI生成报告。第三步接入 CI但只做非阻塞的报告观察两周。两周后统计误报率比如有效建议比例是否超过 50%。高于这个比例就继续补充规则低于这个比例就收窄触发范围。等团队信任度提高了再把规则从warning转变为 CI 阻塞项但建议仍然保留允许代码作者通过显式标记豁免的通道。这套系统真正改变的不是“有没有 AI”而是让机器把 repeatable 的检查从人的工作台里移走让人类 reviewer 把注意力放回真正需要推理和判断的地方。对一个 Rust 项目而言这种注意力重新分配带来的质量收益通常比费尽心思找一个“更强模型”更明显。