最初はかなり雑でした。

Claude Codeが利用上限になったら、Macで動かしているQwenへそのまま仕事を渡せばよいかな、と考えていました。クラウドが使えなければローカルを使う。仕組みとしては分かりやすいです。

ただ、実際に98回の切替を検知したログを見ていると、モデルの精度より前で止まっている仕事がかなりありました。

そもそもローカルへ渡してよいデータなのか。小さいモデルで判断できる仕事なのか。終わったことを誰が確認するのか。このあたりを決めずに、空いているモデルへ順番に流すのは無理がありました・・・!

なので、いまはモデル名から決めていません。仕事をどこに置いてよいかを先に決めています。

「ローカルかクラウドか」だけでは足りなかった

自分が守りたかったのは、だいたい次の3つです。

  1. 端末の外へ出せないデータを出さない
  2. 小さいモデルへ無理な仕事を渡して、終わった雰囲気だけ出させない
  3. 毎回人がモデルを選ばなくても、同じ基準で動く

ここで「ローカルなら安全、クラウドなら高性能」と分けると、すぐに例外が出ます。

ローカルで動いていても、途中で外部APIを呼べばデータはMacの外へ出ます。逆に公開情報だけを扱うなら、ブラウザの中で済む短い分類へファイル操作権限まで渡す必要はありません。

一旦、判断する順番を次の4つに固定しました。

データ境界、ブラウザ実行可否、推論難易度、完了条件の順に実行先を決める判断フロー

図1: モデルの空き状況を見る前に、その仕事を置いてよい場所を絞っています。

実行先は3つに分けています

いまは、ブラウザ、ローカルPC、クラウドLLMの3層で考えています。

実行先 置きやすい仕事 ここへ置く理由 気にすること
ブラウザ マスキング、短文分類、定型変換 入力を端末外へ出さず、権限も小さくできる モデル容量、初回ロード、ブラウザのメモリ
ローカルPC 社内文書の要約、ログ分類、差分の一次確認 端末内のファイルを直接読める 端末差、起動時間、推論品質、資源競合
クラウドLLM 複雑な設計、複数ファイルの実装、曖昧な判断 推論とツール利用が比較的安定している 利用上限、費用、送ってよいデータか

ブラウザを別にしたのは、短いテキストへラベルを付けるだけなら、シェルもファイルシステムもいらないためです。

ローカルPCも「秘密の仕事を全部置く場所」ではありません。Macの中で完結できて、モデルの能力と空きメモリが足りる仕事だけです。

クラウドも同じで、難しいから送るのではなく、まずそのデータを送ってよいことが前提になります。

ルーターで実際に見ていること

データはどこまで動いてよいか

ここは依頼文だけ見ても足りません。

たとえば「公開情報を要約して」という依頼でも、エージェントが同じ作業ディレクトリにある未公開メモを探索できるなら、扱うデータは公開情報だけではなくなります。

なので、入力本文に付けたラベルだけでなく、読んでよいディレクトリ、使ってよいツール、生成物の送り先までセットで見ています。

ブラウザだけで終わるか

短文分類や伏字化のように、入力も出力も小さく、追加のファイルを読まない仕事ならブラウザで十分なことがあります。

ただ、ブラウザ実行だから軽いとは限りません。モデルの初回ダウンロードが大きかったり、ページを開くたびに待たされたりすると普通に使われなくなります。

ここは速度の理論値より、対象端末で初回から最後まで動くかを見ています。

小さいモデルで判断できるか

文章が短いかどうかだけでは決まりません。

次のような仕事は、いまのところクラウド側へ残すことが多いです。

逆に、候補抽出、既知パターンの分類、定型修正、決まったテストの実行はローカルへ置きやすいです。

このあたりはモデルの優劣というより、仕事の切り方の話かなと思っています。

終わったことを機械で確認できるか

ここが一番効きました。

小さいモデルでも、自然な返事はかなり返してくれます。ただ「対応しました」と返ってきても、ファイルを読んでいなかったり、テストを動かしていなかったりします。

ローカルへ任せやすいのは、次のように終点を書ける仕事です。

「良い設計にする」「適切にレビューする」のような仕事は、終点をそのまま機械判定できません。調査だけローカルへ分けるか、最初からクラウド側へ残します。

最終的には、モデル名ではなく「できること」を返したい

ルーターがlocalだけ返すと、その後ろの処理が「ローカルならこれくらいできるだろう」と勝手に期待し始めます。

いまのルーターが返しているのは、実行先、profile、選んだ理由、hop数、判定結果までです。

ただ、これだけだと後段が持つ前提を消しきれません。最終的には、使ってよい道具、データ境界、完了条件、失敗時の戻し先まで含めた契約にしたいと思っています。

目標にしている形はこんな感じです。

{
  "route": "local",
  "reason": "社内ログを端末外へ出さず、既知パターンだけを分類する",
  "allowedTools": ["read", "edit", "bash"],
  "dataBoundary": "device-only",
  "completion": {
    "command": "npm test -- log-classifier",
    "expectedExitCode": 0
  },
  "fallback": "human-review"
}

reasonは地味ですが残した方がよいです。

後から失敗を見たとき、モデルが弱かったのか、そもそもルーティングを間違えたのかを分けられます。

利用上限も作業ディレクトリ単位ではなく、実際に上限がかかるアカウント単位で持っています。別ディレクトリから同じアカウントへ投げ直しても、だいたい同じところで止まるためです。

やってみてダメだった配置

「ローカルなら安い」で全部渡す

推論単価がほぼゼロでも、長い試行錯誤のあとに人が直し、Macまで重くなったら安くありません。

自分はモデルの利用量だけでなく、終わるまでの時間と手直し時間も見るようにしました。

人にモデルを毎回選んでもらう

モデル名や量子化方式を利用者へ選んでもらうと、同じ仕事でも人によって安全基準が変わります。

利用者には仕事の目的とデータ区分を出してもらい、実行先はルーター側で決める方が運用しやすかったです。

一度決めた配置をずっと使う

Macの負荷も、使えるモデルも、クラウド側の残量も変わります。

ルーティング結果には期限を持たせて、実行直前にもう一度確認します。ただし、途中でローカルとクラウドを何度も往復すると別の事故が起きるので、経路変更には上限を置いています。

何を数字で見ているか

ローカル利用率はKPIにしていません。高ければよい数字ではないためです。

再ルーティングが多い仕事は、モデルを大きくする前に分け方を見直します。

分類と修正を分ける。調査と判断を分ける。そこまで小さくすると、いまのローカルモデルでも普通に終わる仕事が見つかります。

いまの確認項目

最初はモデルの使い分けを作っているつもりでした。

実際に必要だったのは、モデルを選ぶ機能というより「この仕事をここへ置いて大丈夫か」を毎回同じ順番で確認する仕組みでした。いまはこの順番にしてから、ローカルへ任せる範囲をかなり決めやすくなっています。

ローカルLLM設計・全8回

MacでローカルLLMを実際に動かして、うまくいかなかったところを8つに分けて書いています。

  1. PART 01仕事の配置
  2. PART 02コンテキストの削減
  3. PART 03handoff
  4. PART 04冪等性
  5. PART 05プロセス管理
  6. PART 06復帰監視
  7. PART 07安全ゲート
  8. PART 08完了率の計測