← ブログに戻る

AIレビューをローカルLLMで動かしてみた

結論

Alibaba の AIコードレビューツール open-code-review は、向き先を手元の GPU で動くローカルLLM に替えても、そのまま動きました。ツール呼び出しも壊れず、1回のレビューが9〜25分、料金は0円です。

ただ、同じ PR を3回ずつレビューさせると、後で直された取りこぼしを指摘できたのはクラウドの Claude Sonnet 5 で3回中3回、ローカルの Qwen3.6 で3回中0回でした。今回のレベルの取りこぼしには、ローカルの Qwen3.6 では対処できませんでした。

ローカルの Qwen は、問題のある関数を読みに行きませんでした。そこで、コードグラフで出した「読むべき関数の一覧」を渡すと、3回中2回はその関数を読みました。それでも、読んだうえで「ここは問題ない」と判断しました。ローカルLLMで足りなかったのは、読みに行く粘りと、読んだコードを正しく判断する力の両方でした。

では、小さいモデルだから外れたのか。クラウドの小型モデルでも同じことを試すと、Claude Haiku 4.5 は3回中0回、新しい Claude Haiku 5.5 は1回2円前後・1分足らずで3回中2回当てました。ローカルかクラウドか、モデルが大きいか小さいかでは、結果は分かれませんでした。分けたのは、どこを読むかの見立てと、読んだあとの判断です。

左のコマでは、図書館の長い通路の途中で小さなロボットが懐中電灯を持ったまま床に倒れ込み、通路の奥では1冊の本が光っている。右のコマでは、同じロボットが本棚の前で本を開いて読んでいて、開いたページの上に小さな虫が1匹いる

やったこと

open-code-review は、差分を渡すとエージェントがリポジトリを検索・読み込みしながら指摘を作るレビューツールです。題材は、open-code-review 自身の PR #1248 です。この PR には、約1か月後の #1745 で直された取りこぼしがあります。ファイル単位の検査 (scan) 側の executeSubtask が、新しい停止理由を受け取っても「予算超過」として記録しません。

前回の記事では、この取りこぼしがコードグラフの1ホップ目に出ることを確かめました。今回は、AIレビューがこれを見つけられるか、クラウドとローカル、大きいモデルと小さいモデルで比べます。

項目クラウドローカル
モデルClaude Sonnet 5、Claude Haiku 4.5、Claude Haiku 5.5Qwen3.6-35B-A3B (4ビット量子化、約22GB)
動かす場所Anthropic APIRTX 4070 (12GB) の PC。llama.cpp の llama-server
open-code-review2026-11-11 時点の main (db5db6b) をビルド同じ
回数各3回素のまま3回、読むべき関数の一覧つき3回

ローカルLLM に向ける手順

ローカル側は llama.cpp の llama-server で立てます。Ollama を使わず llama-server にしたのは、以前に別のコーディングエージェントで試したとき、Ollama 経由だとツール呼び出しが壊れたからです (モデルがツールを呼ばずに、呼び出しの文字列をそのまま書き出した)。--jinja を付けると、モデルに同梱されたテンプレートでツール呼び出しを組み立ててくれます。

llama-server -m ~/qwen36.gguf \
  -ngl 99 --cpu-moe -c 65536 --parallel 1 \
  --cache-type-k q8_0 --cache-type-v q8_0 \
  --jinja --alias qwen36 --host 0.0.0.0 --port 18080

35B のモデルは 12GB の GPU に載りきらないので、--cpu-moe で専門家層 (MoE の expert) を CPU 側に置きます。--parallel 1 と KV キャッシュの8ビット化で、文脈を 65,536 トークンまで広げています。

open-code-review 側は、OpenAI 互換の独自プロバイダとして登録します。

ocr config set provider local-qwen
ocr config set custom_providers.local-qwen.url      http://<GPU PC>:18080/v1
ocr config set custom_providers.local-qwen.protocol openai
ocr config set custom_providers.local-qwen.model    qwen36
ocr config set custom_providers.local-qwen.api_key  local
ocr config set max_tokens 45000
ocr llm test

max_tokens は、1つのサブタスクに渡す入力の上限です。既定は20万トークンなので、そのままだとローカルの文脈 (65,536) からあふれます。出力の上限 (16,384) を引いた範囲に収まるよう、45,000 にしました。ocr llm test で「Tool-call round trip verified」と出れば、ツール呼び出しまで通っています。

もう1つ、つまずいた点があります。open-code-review のコード検索は git grep --max-count を使うので、git 2.38 以上が要ります。手元の git 2.34 では検索がすべて失敗し、エラーにならないままレビューが進んでいました。

結果

構成問題の関数を読んだ回取りこぼしを指摘した回1回の所要1回の費用
Claude Sonnet 53/33/3約4分約110〜130円
Claude Haiku 5.52/32/3 (1回は一部)40〜56秒約1〜2.5円
Claude Haiku 4.50/30/3約2分約45〜60円
Qwen3.6 (素のまま)0/30/39〜25分0円
Qwen3.6 (読むべき関数の一覧つき)2/30/313〜18分0円

「問題の関数を読んだ回」は、エージェントのファイル読み込みが executeSubtask の行範囲 (791〜838行目) と重なった回です。

Sonnet 5 は、MaxTokensBudget や RunMainTask で scan 側を検索して executeSubtask を読み、3回とも「scan 側は新しい停止理由を受けても予算超過を記録しない」と指摘しました。これは #1745 の修正内容とほぼ同じです。

Qwen3.6 も、budgetExceeded や StopTokenBudget を検索し、scan 側のファイルまでは読みに行っています。ただ、読んだのは同じファイルの別の箇所で、executeSubtask には届きませんでした。1回目の記録では、LLM の呼び出しが Sonnet 5 の約47回に対して約10回で、1ラウンド目で「新しい指摘なし」として打ち切っています。open-code-review は指摘が0件だとそこでラウンドを止めるので、2ラウンド目の読み直しも起きませんでした。

読む場所を教えたら、読んで外した

足りないのが「読む場所」なら、教えればよいはずです。前回の記事で、Go 公式の callgraph から「この PR が変更した関数を直接呼んでいるのに、PR では触っていない関数」を6個出していました。この6個を、open-code-review の --background-file (要件や背景を Markdown で渡すオプション) に入れて渡しました。答えを教えないよう、6個は同じ書式でパッケージ順に並べ、「バグがあるとは限らない、確かめる場所の一覧」と書いています。

すると、3回中2回は executeSubtask を読みに行きました。読む場所は当たるようになりました。それでも指摘は0件でした。セッションの記録には、Qwen3.6 がこう判断したことが残っています。

For the scan path, there’s no corresponding StopTokenBudget handling needed since the scan agent’s executeSubtask already has its own dispatch gate with the look-ahead check

scan 側には、次のファイルを始める前に予算を確かめる仕組み (先読みのゲート) があるから、新しい停止理由の扱いは要らない、という判断です。実際には、その先読みは「次のファイルを始める前」にしか効かず、1つのファイルの会話が途中で止まった場合は記録されません。#1745 が直したのは、まさにその場合でした。

読む場所を教えると、ローカルLLMはそこを読みます。そのうえで正しく判断できるかは、モデルの力の問題として残りました。

小さいモデルなら外れる、とは限らない

外れた理由が「小さいモデルだから」なのかを確かめるため、クラウドの小型モデル2つでも同じレビューを3回ずつ回しました。

Claude Haiku 4.5 は、ファイルの読み込みを1回あたり31〜53回と、Sonnet 5 より多く重ねました。それでも executeSubtask には一度も届かず、指摘はフラグの説明文や比較演算子といった別の箇所でした。

Claude Haiku 5.5 は逆で、読み込みは1回あたり3〜6回しかしていません。それでも3回中2回は executeSubtask を読み、3回目には次のように書いています。

Passing the budget into the shared Runner lets scan stop a file mid-conversation with StopTokenBudget, but the scan agent never records that stop. Only the dispatch gate sets budgetExceeded and emits a warning, so a run where files fail with ‘reached the aggregate token budget’ still reports BudgetExceeded()==false and has no token_budget_reached warning.

#1745 の修正内容とほぼ同じです。2回目は、警告が記録されないことまでは指摘しましたが、フラグが立たない点には触れていないので「一部」と数えました。

読む量を増やしても、読む場所が外れていれば当たりません。少なく読んでも、見立てが合っていれば当たります。今回の5つの構成の差は、この見立てと、読んだあとの判断で説明がつきます。

ローカルLLMで使うなら

今回の1件の PR から言えるのは、次の範囲です。

  • open-code-review は、ローカルLLM でもそのまま動く。ツール呼び出しも、差分の外の検索も働く
  • コードを外に出さずに、0円でレビューを回せる。その代わり、1回あたりの時間はクラウドの2〜6倍かかった
  • 差分の外を読み、そこで細かい挙動を判断する種類の取りこぼしは、今回の Qwen3.6 では拾えなかった。読む場所を教えても、判断で外した
  • 同じ取りこぼしを、クラウドの Haiku 5.5 は1回2円前後で拾えた。費用を抑えたいだけなら、ローカルより新しい小型モデルのほうが今回は確実だった

「ローカルLLMのレビューは使えない」とまでは言えません。今回の取りこぼしは、差分には無い関数の、条件分岐の抜けでした。差分の中で完結する不具合や、もっと素直な取りこぼしなら、結果は違うかもしれません。ここは、自分のリポジトリの過去の PR で試すのが確実です。

限界

  • PR は1件、各構成3回です
  • ローカルは Qwen3.6-35B-A3B の4ビット量子化だけです。Haiku との比較は、同じ open-code-review の設定でモデルだけを替えたもので、ローカル側は文脈を 65,536 トークンに絞っている点が違います。Qwen3.5 も試すつもりでしたが、手元では Ollama の中にしかファイルが無く、権限の都合で今回は使えませんでした
  • open-code-review は2026-11-11 時点の版を、2026-09-17 時点のコードに当てています
  • クラウドの費用は、トークン数と公式単価からの計算で、1ドル159円で円にしています。Haiku 5.5 は、1回の入力が10万トークン以下のときの料金です。ローカルの電気代は測っていません
  • 素のままの Qwen3.6 は、2回目に別の指摘を1件出しました (「最後のラウンドが予算を超えたまま正常に終わると、予算超過のフラグが立たない」)。動作の説明は正しいのですが、このフラグは説明上「予算のチェックが処理を止めたときに立てる」ものなので、仕様どおりとも読めます。正しいとも誤りとも決めていません

この記事の元になった本

コードをグラフにして、変更の影響をホップ数でたどる考え方は、拙著『ナレッジグラフ活用大全』の「第8章: コードをグラフで理解する」にまとめています。グラフで出した一覧を AIレビューに渡す使い方は、本には入っていない追試です。

本の紹介ページを見る

ナレッジグラフ活用大全 関連書籍 ナレッジグラフ活用大全 ナレッジグラフ 活用大全 | GraphRAG・Neo4j・RDF・Property Graph・Emotion AI の実践書 書籍ページを見る →