2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

dev-orchestra に自作のエージェントを繋いでみた(ローカルLLM編)

2
Posted at

この記事は tk3.biz のブログ からの転載です。

はじめに

前回は、dev-orchestra のレビュアーとロールの組み替え方を書きました。今回は、Claude Code・Codex・Antigravity 以外の AI を、自分で書いたアダプタで繋ぐ方法です。例として、手元で動かしているローカル LLM をレビュアーに加えます。

ローカル LLM を繋ぐ話は、これまでにも 2 本書いています。

この記事では経緯は省いて、dev-orchestra 1.0.0 で一からアダプタを書く手順に絞ります。

dev-orchestra は GitHub で公開しています(MIT ライセンス)。役に立ちそうと思ったら、スターを付けてもらえるとうれしいです。

→ github.com/istb16/dev-orchestra

仕組み:アダプタは「コマンドを組み立てる」だけ

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(スターを付けてもらえると励みになります)

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?