Skip to content
教程閱讀約需 11 分鐘

面向 AI 編碼智慧體的 GitHub PR 風格程式碼評審

在 Potato 中搭建 GitHub PR 風格的程式碼評審標註:diff 內聯評論、檔案級品質評分,以及對編碼智慧體產出的通過或打回結論。

Potato Team

測試通過率與程式碼品質

大多數編碼智慧體基準把評估壓縮成一個二元問題:測試過了沒有?SWE-bench 報告 issue 的解決率,HumanEval 報告 pass@k。這些指標拿來排榜有用,但對理解程式碼品質沒什麼幫助。

一個智慧體可以通過全部測試,同時寫出沒人願意維護的程式碼:帶安全漏洞的、跑得慢的,或者風格和整個程式碼庫對著幹的。就算測試全綠,人類評審也會在那個 PR 上要求修改。如果你想要的是能被人真正合並的程式碼,就得去評審程式碼本身,而不只是跑測試。

Potato 的 code_review 標註方案把 GitHub PR 評審的體驗搬進了標註工具。標註者看到帶語法高亮的 unified diff,點選 diff 行新增內聯評論,在兩個品質維度上給檔案打分,最後給出 approve、request changes 或 comment 的結論,和評審一個真實的 pull request 一樣。完整的方案參考見編碼智慧體標註文件和智慧體評估指南。

這是 Potato 裡的程式碼評審介面,展示了內聯 diff 評論和檔案級評分:

帶內聯 diff 評論和檔案評分的程式碼評審標註Potato 的程式碼評審介面,包含內聯 diff 評論和檔案級品質評分


程式碼評審方案概覽

code_review 方案分三層:

  1. 內聯 diff 評論:標註者點選 diff 中的任意一行,附上一條帶分類的評論(bug、風格、效能、安全、邏輯、建議、疑問)
  2. 檔案級評分:每個被修改的檔案都單獨獲得正確性(1-5)和程式碼品質(1-5)兩項評分
  3. 總體結論:標註者給出最終判斷——通過、要求修改,或只留評論

這套流程和真實的程式碼評審對應,產出的結構化資料可以直接對接程式碼評審模型的訓練。


CodingTraceDisplay:diff 是怎麼渲染的

CodingTraceDisplay 元件把編碼智慧體的 trace 渲染成一串工具呼叫及其輸出,並對檔案編輯做了專門處理。當智慧體編輯檔案時,介面顯示 unified diff,其中包含:

  • 紅色行:被刪除的行(字首 -)
  • 綠色行:新增的行(字首 +)
  • 灰色行:上下文行(未改動)
  • 行號:行號槽中同時給出舊行號和新行號
  • 語法高亮:根據副檔名做語言感知的高亮
  • 點選評論:點選任意一行會開啟一個錨定到該行的評論表單

diff 由智慧體的編輯操作自動算出。如果智慧體用的是搜尋替換類工具,Potato 會重建改動前後的狀態並生成 unified diff。

對於一條 trace 中修改了多個檔案的智慧體(現實中的 bug 修復很常見),每個檔案有自己的可摺疊 diff 區塊,類似 GitHub PR 的 “Files changed” 標籤頁。

CodingTraceDisplay 渲染程式碼改動時帶有正確的語法高亮:

帶 diff 渲染和檔案樹的編碼 trace 展示CodingTraceDisplay 渲染帶語法高亮的 unified diff 和檔案樹側邊欄


評論分類

標註者點選 diff 行新增評論時,需要選一個分類:

分類顏色說明示例
bug紅色程式碼存在功能性錯誤“如果 user 是 None,這裡會拋 NullPointerException”
style藍色程式碼風格或約定問題“本項目函式用 snake_case,不是 camelCase”
performance橙色程式碼效率低“這是在迴圈裡查資料庫,應該用批次查詢”
security紫色安全漏洞“使用者輸入沒有做淨化就直接拼進了 SQL 查詢”
logic黃色邏輯問題,未必立刻導致失敗“這個條件應該是 >= 而不是 >,邊界上差一”
suggestion綠色改進建議,不算錯誤“這裡可以考慮用 context manager,資源處理更乾淨”
question灰色需要澄清“為什麼加了這個 import?看起來並沒有被用到”

每條評論還有一個自由文本正文,標註者在這裡詳細說明問題,就像寫真實的 PR 評論一樣。


檔案級評分

看完某個檔案的 diff 之後,標註者從兩個維度給它打分:

正確性(1-5):

  • 1:不能工作,還引入了新 bug
  • 2:部分可用,存在明顯問題
  • 3:主路徑可用,但漏掉了邊界情況
  • 4:基本正確,只有小問題
  • 5:完全正確,邊界情況也處理得當

程式碼品質(1-5):

  • 1:無法維護,毫無結構
  • 2:品質差,風格或設計問題嚴重
  • 3:可以接受,遵循了基本約定
  • 4:品質好,乾淨可讀
  • 5:優秀,寫法地道,文件齊全

結論選項

看完所有檔案後,標註者從三種結論中選一個:

  • Approve:程式碼可以原樣合併,或者只需要無關緊要的小改動
  • Request Changes:合併之前需要做實質性修改
  • Comment Only:只給反饋,不做合併決定

這三項與 GitHub 的三種 PR 評審狀態一一對應。


分步搭建

第 1 步:轉換編碼智慧體 trace

編碼智慧體的 trace 格式五花八門。下面是三種常見智慧體的例子。

從 Claude Code(JSON 匯出):

bash
python -m potato.trace_converter \
  --input claude_code_sessions/ \
  --output data/code_traces.jsonl \
  --input-format claude_code

從 Aider(聊天記錄):

bash
python -m potato.trace_converter \
  --input aider_logs/ \
  --output data/code_traces.jsonl \
  --input-format aider

從 SWE-Agent(trajectories 目錄):

bash
python -m potato.trace_converter \
  --input swe_agent_trajectories/ \
  --output data/code_traces.jsonl \
  --input-format swe_agent_trajectory

轉換器輸出一種標準化的 JSONL 格式。每一行是一條 trace,包含任務、智慧體的各個步驟,以及檔案 diff:

json
{
  "trace_id": "swe_bench_123",
  "task": "Fix the TypeError in django/db/models/query.py when calling .values() on an empty QuerySet",
  "repo": "django/django",
  "base_commit": "a1b2c3d",
  "steps": [
    {
      "type": "thought",
      "content": "Let me look at the QuerySet.values() method to understand the TypeError."
    },
    {
      "type": "action",
      "tool": "search",
      "input": "def values",
      "file": "django/db/models/query.py"
    },
    {
      "type": "observation",
      "content": "Found at line 234: def values(self, *fields):\n    ..."
    },
    {
      "type": "action",
      "tool": "edit",
      "file": "django/db/models/query.py",
      "old_content": "    def values(self, *fields):\n        clone = self._clone()\n        clone.query.set_values(fields)",
      "new_content": "    def values(self, *fields):\n        clone = self._clone()\n        if not fields and not self.query.values_select:\n            return clone\n        clone.query.set_values(fields)"
    },
    {
      "type": "action",
      "tool": "edit",
      "file": "tests/queries/test_values.py",
      "old_content": "class ValuesTests(TestCase):",
      "new_content": "class ValuesTests(TestCase):\n    def test_empty_queryset_values(self):\n        \"\"\"Calling .values() on empty QuerySet should not raise TypeError.\"\"\"\n        qs = Author.objects.none()\n        result = list(qs.values())\n        self.assertEqual(result, [])"
    }
  ],
  "files_changed": [
    {
      "path": "django/db/models/query.py",
      "diff": "--- a/django/db/models/query.py\n+++ b/django/db/models/query.py\n@@ -234,6 +234,8 @@\n     def values(self, *fields):\n         clone = self._clone()\n+        if not fields and not self.query.values_select:\n+            return clone\n         clone.query.set_values(fields)"
    },
    {
      "path": "tests/queries/test_values.py",
      "diff": "--- a/tests/queries/test_values.py\n+++ b/tests/queries/test_values.py\n@@ -1,4 +1,10 @@\n class ValuesTests(TestCase):\n+    def test_empty_queryset_values(self):\n+        \"\"\"Calling .values() on empty QuerySet should not raise TypeError.\"\"\"\n+        qs = Author.objects.none()\n+        result = list(qs.values())\n+        self.assertEqual(result, [])"
    }
  ]
}

第 2 步:配置程式碼評審方案

建立你的 config.yaml:

yaml
annotation_task_name: "Coding Agent Code Review"
 
data_files:
  - "data/code_traces.jsonl"
 
item_properties:
  id_key: "trace_id"
  text_key: "task"
 
# Display coding agent traces with diff rendering
instance_display:
  fields:
    - key: task
      type: text
      label: "Task"
 
    - key: steps
      type: coding_trace
      label: "Agent changes"
      display_options:
        diff_view: unified
        show_file_tree: true
        show_reasoning: true
        terminal_theme: dark
        collapse_long_outputs: true
        max_output_lines: 200
 
annotation_schemes:
  - annotation_type: code_review
    name: review
    description: "Review the agent's code changes as you would a pull request"
    comment_categories: [bug, style, performance, security, logic, suggestion, question]
    file_rating_dimensions: [correctness, quality]
    verdict_options: [approve, request_changes, comment_only]
 
output_annotation_dir: "annotation_output/"
export_annotation_format: jsonl

第 3 步:啟動標註服務

bash
potato start config.yaml -p 8000

開啟 http://localhost:8000。你會看到第一條編碼智慧體 trace,包括任務描述、智慧體的推理步驟,以及帶語法高亮的檔案 diff。

第 4 步:標註者的工作流

典型的評審流程是這樣的:

  1. 讀任務:搞清楚智慧體被要求做什麼(比如“修復 django/db/models/query.py 中的 TypeError”)
  2. 看 trace:翻一遍智慧體的推理步驟,理解它的思路
  3. 逐個檔案看 diff:
    • 讀帶語法高亮的 diff
    • 點選任意一行新增內聯評論
    • 選擇評論分類(bug、風格、效能等)
    • 寫下評論正文說明問題
    • 給這個檔案的正確性(1-5)和程式碼品質(1-5)打分
  4. 給出結論:選擇通過、要求修改,或只留評論
  5. 提交:點選 “Submit” 或按 Ctrl+Enter

鍵盤快捷鍵可以加快流程:

快捷鍵操作
j / k在檔案之間切換
c在選中行上開啟評論
1-5為當前維度打分
a結論設為通過
r結論設為要求修改
Ctrl+Enter提交評審

匯出格式

每份提交的評審會產出一個結構化的 JSON 物件:

json
{
  "instance_id": "swe_bench_123",
  "annotator": "reviewer_01",
  "verdict": "request_changes",
  "comments": [
    {
      "category": "logic",
      "file": "django/db/models/query.py",
      "line": 236,
      "text": "This early return skips set_values entirely, but if fields are provided later via .values('name'), the previous empty .values() call will have returned a clone that never went through set_values. Consider checking if this clone is still valid downstream."
    },
    {
      "category": "suggestion",
      "file": "tests/queries/test_values.py",
      "line": 5,
      "text": "Consider adding a test case for .values() followed by .values('name') to verify the chaining behavior after your fix."
    }
  ],
  "file_ratings": {
    "django/db/models/query.py": { "correctness": 3, "quality": 4 },
    "tests/queries/test_values.py": { "correctness": 4, "quality": 4 }
  }
}

這種結構化格式可以直接用於訓練程式碼評審模型,也便於做彙總分析。coding_eval 匯出器把它寫成 code_reviews.jsonl:

bash
python -m potato.export -c config.yaml -f coding_eval -o ./export/

分析:處理評審資料

載入評審結果

python
import json
import pandas as pd
from pathlib import Path
 
reviews = []
for f in Path("output/").glob("*.jsonl"):
    with open(f) as fh:
        for line in fh:
            reviews.append(json.loads(line))
 
print(f"Loaded {len(reviews)} code reviews")

評論分類分佈

python
from collections import Counter
 
all_comments = []
for rev in reviews:
    all_comments.extend(rev["comments"])
 
category_counts = Counter(c["category"] for c in all_comments)
print("Comment categories:")
for cat, count in category_counts.most_common():
    print(f"  {cat}: {count}")

檔案平均評分

python
ratings = []
for rev in reviews:
    for path, dims in rev["file_ratings"].items():
        ratings.append({"file": path, **dims})
 
ratings_df = pd.DataFrame(ratings)
print("Average ratings by file:")
print(
    ratings_df.groupby("file")[["correctness", "quality"]]
    .mean()
    .round(2)
    .to_string()
)

結論分佈

python
verdict_counts = Counter(rev["verdict"] for rev in reviews)
total = sum(verdict_counts.values())
print("Verdict distribution:")
for verdict, count in verdict_counts.most_common():
    print(f"  {verdict}: {count} ({count/total*100:.1f}%)")

按智慧體統計 bug 率

如果你的 trace 裡帶 agent 欄位,就可以比較不同智慧體的 bug 率:

python
agent_bugs = {}
for rev in reviews:
    agent = rev.get("agent", "unknown")
    bug_count = sum(
        1 for c in rev["comments"]
        if c["category"] == "bug"
    )
    if agent not in agent_bugs:
        agent_bugs[agent] = []
    agent_bugs[agent].append(bug_count)
 
print("Average bugs per review by agent:")
for agent, bugs in sorted(agent_bugs.items()):
    print(f"  {agent}: {sum(bugs)/len(bugs):.2f} (n={len(bugs)})")

應用場景

訓練程式碼評審模型

Potato 程式碼評審標註產出的結構化內聯評論、檔案評分和結論,很適合作為自動程式碼評審模型的訓練資料。每份評審提供:

  • 定位到具體 diff 行的區域性反饋
  • 已分類的問題(bug 還是風格還是效能)
  • 多個粒度上的品質訊號(行、檔案、整體)

CodeRabbit 或 Graphite 這類自動評審器用的就是同一種形狀的資料,區別在於這裡的資料由人類專家寫出,而不是從 LLM 蒸餾出來的。

在 SWE-bench 上評估編碼智慧體

SWE-bench 告訴你智慧體有沒有解決 issue(測試是否通過),但不告訴你程式碼能不能合併。對 SWE-bench 的解法跑一遍程式碼評審標註,你就能區分出哪些智慧體是用乾淨的程式碼解決問題,哪些是靠 hack 矇混過關。這樣得到的排名反映的是程式碼能不能合併,而不只是測試有沒有變綠。

構建程式碼品質資料集

把大量 trace 的程式碼評審資料彙總起來,可以建立 AI 生成程式碼中常見品質問題的資料集。這類資料集可以用於:

  • 微調程式碼生成模型,避開常見錯誤
  • 針對 AI 生成程式碼的模式構建專門的 linter
  • 訓練分類器,在人工評審之前先標出智慧體輸出中可能有問題的地方

小結

Potato 的 code_review 方案把 GitHub PR 評審流程放進了智慧體評估。內聯評論、檔案評分和結論給你的是結構化的程式碼品質資料:可以拿來訓練程式碼評審模型,可以把乾淨的 SWE-bench 解法和糙活區分開,也可以給自己的智慧體定一條品質基線。