同じ8Bの重みを、同じマシンで動かす。なのにエージェントのツール呼び出し成功率が、一方では7%、もう一方では83%になる。この差はモデルの賢さではなく、モデルを「配信する側」の作りで生まれている。ローカルLLMでエージェントを組んだことがある人なら、途中でツール呼び出しのJSONが壊れて全体が止まる、あの徒労感に覚えがあるはずだ。forge-guardrails(通称Forge)は、まさにそこを埋めにきたPythonフレームワークで、自前ホストの8Bクラスを26シナリオのベンチで84%まで押し上げたと公表している。
なぜ小さいモデルはエージェントで転ぶのか
前提を一つ。いまのAIエージェントは「モデルにツール一覧を渡す→モデルが呼び出したいツールと引数をJSONで返す→こちらが実行して結果を戻す」というループを何十回も回す。フロンティアモデルはこの往復をかなり正確にこなすが、8B前後のローカルモデルは違う。
失敗の中身は、モデルが問題を解けないからではないことが多い。多いのは形式のズレだ。OpenAI形式のtool_callsで返してほしいのに、Mistral系は[TOOL_CALLS]、Qwen系はXMLで返してくる。ツールを使うべき場面でただの地の文を吐く。手順の途中を飛ばす。ループが長くなってコンテキスト上限に当たる。どれも一手ごとの小さな綻びだが、多段のエージェントでは1ステップ落ちれば全体が失敗するので、成功率は掛け算で急落する。
Forgeの発想は、モデルを賢くするのではなく、モデルとアプリの間に検証と修復の層を差し込むことにある。壊れたツール呼び出しを各方言から拾い直してOpenAIのtool_callsスキーマに整形し(rescue parsing)、検証に落ちたら最大3回まで訂正メッセージ付きで再試行し、ツールがある場面で地の文を出させないために合成のrespondツールを注入する。手順を縛りたいときはrequired_stepsやterminal_toolで順序を強制できる。要は、モデルの気まぐれをアプリに届く前に握り潰す。
84%か、99%か。数字が割れる理由
ここは意図的に整理して書く。ソースによって数字が食い違うからだ。
| 出典 | 対象モデル | 素の状態 | Forge適用後 |
|---|---|---|---|
| GitHub README / PyPI(v0.7.0) | ローカル8B (Ministral-3 8B) | 一桁% | 84%(26シナリオ) |
| GitHub README(v0.6.0計測) | Claude Sonnet 4.6 | 85% | 98% |
| 第三者レビュー | Ministral-3 8B Q8 | 約53% | 86.5%(基本93%+高度推論76%) |
Show HN では「53%→99%」という見出しが目立ったが、README・PyPIという一次ソースが公式に掲げる数字は8Bで84%だ。99.3%はツール呼び出しの厳密性だけを見た狭いスライスであって、26シナリオ全体の値ではない。素の状態が「一桁%」なのか「53%」なのかが割れているのも、実は今回の一番おもしろい発見に直結している。
重みより配信スタックが効く
その発見が冒頭の数字だ。レビューは、同じMistral-Nemo 12Bの重みでも、llama-serverのネイティブfunction callingでは精度7%、Llamafileのプロンプト注入モードでは83%になると報告している。モデルは1バイトも変えていない。ツール定義をどう渡し、出力をどう受け取るかという「サービング層」の作りだけで、10倍以上ひっくり返る。
ここに、ローカルエージェントがなぜか動かない問題の核心がある。私たちはつい「8Bだから無理」とモデルサイズのせいにするが、実際にはサービング設定の相性で成功率の大半が決まっていて、その上に薄いガードレールを乗せると一気に実用域に入る。Forgeが示したのは新しい魔法ではなく、「取りこぼしを拾えば小さいモデルでも十分戦える」という、地味だが再現性のある工学だ。フロンティアモデル(Claude Sonnet 4.6)ですら85%→98%と伸びる点も示唆的で、形式ズレやコンテキスト管理は大きいモデルにも一定確率で起きているということになる。
触ってみる
インストールは素直だ。Python 3.12以上が要る(執筆時点の最新は0.8.3)。
pip install forge-guardrails
# Anthropicクライアント同梱版
pip install "forge-guardrails[anthropic]"
プログラムから使う場合は、Workflowでツールと制約を定義し、WorkflowRunnerでループを回す。バックエンドはOllama、llama-server、Llamafile、vLLM、Anthropicに対応する。
from forge import Workflow, WorkflowRunner, ToolDef, LlamafileClient
workflow = Workflow(
name="weather",
tools={"get_weather": ToolDef(spec=..., callable=get_weather)},
terminal_tool="get_weather",
)
runner = WorkflowRunner(client=LlamafileClient(...), context_manager=ctx)
await runner.run(workflow, "What's the weather in Paris?")
既存コードを書き換えたくないなら、ドロップインのHTTPプロキシとして挟むほうが早い。OpenAI/Anthropic両APIを喋るので、Continue・aider・Cline・Claude Codeのようなクライアントの向き先を差し替えるだけでガードレールが効く。
python -m forge.proxy --backend-url http://localhost:8080 --port 8081
使う前に知っておくこと
万能ではない。一次情報とレビューを突き合わせると、正直な弱点が見える。まず遅延データが公表されていない。最大3回のリトライは成功率を上げる代わりに壁時計時間を食うはずで、「完了率とレイテンシのトレードオフ」が定量化されていないのはHNでも突っ込まれていた点だ。合成respondツールも、効くのはデータで裏づけられているものの、本番で常用するにはやや行儀の悪い回避策ではある。検証が厚いのはMinistralとMistral-Nemo系で、Qwen・Llama 3.x・Gemmaは相対的に手薄。そして事実上のワンオペ開発というメンテナンスリスクもMITライセンスの個人プロジェクトである以上つきまとう。
それでも、ローカルでエージェントを回したい実務者にとって試す価値は高いと思う。プロキシとして1行差し込めば、いま動かしている8Bの成功率がどれだけ変わるかを自分のワークロードで即測れる。そのとき見るべきは「モデルを大きくすべきか」ではなく、「サービング設定を変えるだけで何%戻るか」だ。Forgeの一番の貢献は84%という数字そのものより、ローカルエージェントの信頼性が重みではなく配信層で決まりうる、と数字で突きつけたことにある。