この記事は tk3.biz のブログ からの転載です。
はじめに
前回は、dev-orchestra のレビュアーとロールの組み替え方を書きました。今回は、Claude Code・Codex・Antigravity 以外の AI を、自分で書いたアダプタで繋ぐ方法です。例として、手元で動かしているローカル LLM をレビュアーに加えます。
ローカル LLM を繋ぐ話は、これまでにも 2 本書いています。
- ローカルLLMをdev-orchestraのコードレビュアーに追加してみた: 繋ぐまでの試行錯誤と、0.9.0 で公式の仕組みが入った経緯
- 機密コードをクラウドに出さずにAIでレビューする: ローカル LLM だけでレビューする運用
この記事では経緯は省いて、dev-orchestra 1.0.0 で一からアダプタを書く手順に絞ります。
dev-orchestra は GitHub で公開しています(MIT ライセンス)。役に立ちそうと思ったら、スターを付けてもらえるとうれしいです。
仕組み:アダプタは「コマンドを組み立てる」だけ
dev-orchestra は、AI を直接呼びません。CLI を起動して、プロンプトを標準入力で渡し、答えを標準出力から読むだけです。その CLI のコマンドを組み立てるのがアダプタ(provider)です。
| 部品 | 役目 |
|---|---|
| dev-orchestra | プロンプトを作り、アダプタが返したコマンドを起動する |
| アダプタ | 「どのコマンドを、どの引数で起動するか」を返す |
| CLI | 標準入力のプロンプトを AI に渡し、答えを標準出力に書く |
つまり、標準入力で受けて標準出力で返す CLI さえあれば、どんな AI でも繋げます。
手順
1. プロンプトを受けて答えを返す CLI を用意する
ローカル LLM(llama.cpp の llama-server など)は、OpenAI 互換の API を出しています。そこにプロンプトを投げて、答えを出力するだけの CLI を書きます。標準ライブラリだけで書けます。
"""Send a prompt from stdin to a local OpenAI-compatible server and print the answer."""
import json
import os
import sys
import urllib.request
URL = os.environ.get("LOCAL_LLM_URL", "http://127.0.0.1:8080") + "/v1/chat/completions"
def main():
# dev-orchestra writes the prompt as UTF-8, whatever the OS locale is
sys.stdin.reconfigure(encoding="utf-8")
prompt = sys.stdin.read()
body = json.dumps({"messages": [{"role": "user", "content": prompt}]}).encode("utf-8")
request = urllib.request.Request(URL, data=body, headers={"Content-Type": "application/json"})
# Ignore proxy settings so the prompt never leaves this machine
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
with opener.open(request, timeout=1800) as response:
answer = json.load(response)["choices"][0]["message"]["content"]
sys.stdout.reconfigure(encoding="utf-8")
sys.stdout.write(answer)
if __name__ == "__main__":
main()
これを local_llm_review.py として、好きな場所に保存しておきます。
この CLI で気をつけたのは 2 つです。
| 書いたこと | 理由 |
|---|---|
sys.stdin.reconfigure(encoding="utf-8") |
dev-orchestra はプロンプトを UTF-8 で書き込む。日本語の Windows は既定で cp932 で読むので、日本語のコメントを含む差分が化けたり、読み込みで落ちたりする |
ProxyHandler({}) で作った opener を使う |
urlopen は HTTP_PROXY などの環境変数や Windows のプロキシ設定に従う。127.0.0.1 宛てでも、プロキシ経由でコードが外に出ることがある |
ローカル LLM のサーバーは、
127.0.0.1(localhost)だけで待ち受けるようにしておきます。0.0.0.0で待ち受けると、同じネットワークの誰からでも使えてしまいます。
2. アダプタを書く
次にアダプタです。dev-orchestra のドキュメントにある最小の例をもとに、ローカル LLM 用に書きました。
"""Adapter for a local LLM behind an OpenAI-compatible server. Review only."""
from __future__ import annotations
import sys
from typing import Any, Dict, List, Optional, Sequence
from orchestrator.providers.base import (
READ_ONLY_MODES,
ModelCandidate,
ModelResolutionError,
Provider,
ResolvedModel,
)
# The small CLI that sends the prompt to the local server
CLI = r"C:\path\to\local_llm_review.py"
class LocalLLMProvider(Provider):
name = "localllm"
display_name = "Local LLM"
executable = sys.executable
fallback_models = (ModelCandidate("", "default", "server default", "builtin-fallback"),)
fallback_updated = "2026-10-12"
option_keys = ("args",)
def resolve_latest(self, family: str) -> ResolvedModel:
if family in ("", "default"):
return ResolvedModel(self.name, "default", "latest", None, "server default", "cli-default")
raise ModelResolutionError("localllm: only the default model is supported, not %r" % family)
def build_command(
self,
mode: str,
resolved: ResolvedModel,
cwd: str,
extra_args: Sequence[str] = (),
options: Optional[Dict[str, Any]] = None,
) -> List[str]:
# The CLI cannot read or write files, so it can only review a diff it is given.
if mode not in READ_ONLY_MODES:
raise ModelResolutionError("localllm cannot implement: it has no file access")
return [self.executable, CLI] + self.option_args(options)
def build_provider(executable: Optional[str] = None) -> LocalLLMProvider:
return LocalLLMProvider(executable)
ポイントは 4 つです。
| ポイント | 理由 |
|---|---|
from orchestrator.providers.base import ... |
このファイルはプラグインのパッケージの外から読み込まれるので、相対 import(from .base)は使えない
|
name = "localllm" |
設定の provider: に書く名前。claude・codex・agy・mock は使えない |
resolve_latest は default だけ受け付ける |
確かめられないモデル名では推測せずに止める。dev-orchestra 全体の決まりに合わせる |
| 読み取り専用でない実行(実装)は断る | この CLI はファイルを読み書きできない。実装役に置かれても、何も変えずに成功したふりをしないように |
CLI には、手順 1 で保存したファイルの本当の場所に書き換えます。例の r"..." は、Windows のパスの \ をそのまま書くためのものです。macOS / Linux なら CLI = "/home/you/local_llm_review.py" のように書きます。
executable = sys.executableなので、dev-orchestra がインストールを確かめるときに動かすのはpython --versionです。CLI の側に--versionを用意する必要はありません。
3. 設定ディレクトリの providers/ に置く
アダプタは、設定ディレクトリの providers/ に置きます。
| OS | 置き場所 |
|---|---|
| Windows | %APPDATA%\dev-orchestra\providers\ |
| macOS / Linux | ~/.config/dev-orchestra/providers/ |
ファイル名は localllm.py のように、識別子として使える名前にします。
プラグインの中(
~/.claude/plugins/cache/...)に置かないでください。そこはプラグインを更新すると消えます。設定ディレクトリなら、設定ファイル(config.yaml)と同じ場所なので、更新しても消えません。
Windows で Microsoft Store 版の Python を使っていると、
%APPDATA%に置いたファイルが Python からは見えないことがあります(以前の記事で半日はまりました)。doctorがこれを見つけると、本当の置き場所と対処のしかたを表示してくれます。
4. doctor で読み込まれたか確かめる
アダプタを置いたら、何よりも先に dev-orchestra doctor を実行します(エージェントに頼んでも同じです)。こう出ました(抜粋、パスは省略)。
Local LLM (localllm)
Installed: yes
Source: user module <設定ディレクトリ>\providers\localllm.py
Model selection: supported
Models (builtin-fallback): server default
Read-only runs: not reported by this adapter
Resume: NOT SUPPORTED -- localllm does not resume sessions
User providers
Directory: <設定ディレクトリ>\providers
Code in this directory is imported at startup, from outside the plugin.
Imported: localllm <- <設定ディレクトリ>\providers\localllm.py
どのファイルから読み込んだのかまで表示されます。最後に No problems found. と出れば成功です。書き間違いがあっても dev-orchestra 全体は止まらず、doctor がそのファイルとエラーを教えてくれます。
5. レビュアーとして登録する
レビュアーに加えます。--model default を忘れずに付けます。
dev-orchestra reviewer add --provider localllm --model default --role general
私は最初
--modelを付け忘れました。すると系列名がrecommended-codingで登録され、レビューのときに自作のアダプタがonly the default model is supported, not 'recommended-coding'と断りました。手順 2 で「確かめられない名前では止める」と書いたとおりの動きです。
6. レビューさせてみる
前回の記事で使った、バグを仕込んだサンプルで試しました。ただし、この確認で見たいのは仕組みが通るかです。そこで、ローカル LLM の代わりに、決まった答えを返すダミーのサーバーを使いました。
ok localllm-general localllm server default 1 finding(s)
1 successful, 0 failed
自作のアダプタを通して、ダミーのサーバーの答えが返ってきました。確かめられたのは、アダプタの繋ぎ込みと、答えの書式の読み取りまでです。答えが dev-orchestra の決まった書式(## Finding …)になっていれば、ほかのレビュアーの指摘と同じように重複の統合やトリアージにかかります。
実際のローカル LLM のレビューの実力は、以前の記事(商用 2 つと比べて、6 件中 1 件が採用)や機密コードの記事に書いています。
7. 実装を任せようとすると、断ることを確かめる
念のため、実装役をこのアダプタにして実行してみました。
localllm cannot implement: it has no file access
CLI を起動する前に止まりました。うっかり実装役に置いても、「成功したのに何も変わっていない」という気づきにくい失敗にはなりません。
できる役・できない役
| 役 | このアダプタで | 理由 |
|---|---|---|
| レビュー | ○ | 差分はプロンプトで渡されるので、ファイルを読めなくても成り立つ |
| 設計 | △ | 動くが、渡されたプロンプトしか見えない。関連するファイルを自分で調べられない |
| 実装・修正 | × | ファイルを書けないので、アダプタで断っている |
Claude Code や Codex は、関連するファイルを自分で追いかけて調べられます。この CLI は渡されたものしか見えないので、別の視点を足すレビュアーとして使うのが向いています。
注意すること
-
providers/のファイルは、dev-orchestra を起動するたびに自分の権限で実行されます。サンドボックスではありません。自分で書いたものか、中身を読んで信頼できるものだけを置きます - ローカル LLM のサーバーは localhost だけで待ち受けます(手順 1 の注意)
- CLI の側では、プロキシを通さずに
127.0.0.1へ送ります(手順 1 のProxyHandler({}))。LOCAL_LLM_URLを変えるときも、手元のアドレスにします - コードを外に出さないつもりなら、Claude Code などのクラウドの AI から dev-orchestra を呼ばないようにします。呼び出し元の AI がコードを読むからです。ターミナルから直接実行し、
--onlyでローカル LLM に絞ります(機密コードの記事) - 自作のアダプタは、プリセットには自動で入りません。設定で名前を挙げたときだけ動きます
-
doctorのRead-only runs: not reported by this adapterは、このアダプタが「読み取り専用をどう守るか」を報告していないという意味です。この CLI はそもそもファイルに触れないので実害はありませんが、ファイルを触れる CLI を繋ぐときは、読み取り専用を本当に守れるかを自分で確かめる必要があります - アダプタが頼る部分(この記事の例で使っているもの)は、1.0.0 の互換性の約束に入っています。それでも、dev-orchestra を更新したら
doctorを一度実行しておくと安心です
まとめ
- dev-orchestra は CLI を起動するだけ。標準入力で受けて標準出力で返す CLI があれば、どんな AI でも繋げる
- アダプタは 50 行ほど。
orchestrator.providers.baseから import し、resolve_latestとbuild_commandを書く - 置き場所は設定ディレクトリの
providers/。プラグインを更新しても消えない - 置いたら
doctorで、どのファイルから読み込んだかを確かめる - 登録は
reviewer add --provider localllm --model default --role general - ファイルに触れない CLI はレビュー専用にして、実装はアダプタで断る
-
providers/のコードは自分の権限で実行される。信頼できるものだけを置く
細かな決まりは providers のドキュメント にあります。
GitHub: istb16/dev-orchestra(スターを付けてもらえると励みになります)