ローカル大規模モデルはどのような支援ができるのか —— 境界、ユースケース、自動化
このページの目次
モデルを起動して常駐させることはスタート地点に過ぎません。真の問題は、消費向けGPU上の35B小モデルで、いったい何をするのかということです。本稿の答えは控えめです——プロダクション環境の複雑なロジックは処理できません(それはClaude Code + Opusの役割です)、その位置づけは非常に狭いものです:日常的で、複雑なロジックを必要とせず、エラー許容可能な雑用です。しかし、この狭い位置づけにおいて、クラウドでは代替できない2つの本質的な特徴があります。オフラインでの自立、プライバシーの端末内完結。この境界線を明確にすることで、どのような作業にリソースを投じる価値があるかが見えてきます。
1. まず境界線を引く:ローカルモデルがやらないこと
ローカルモデルをインストールした直後の第一反応は往々にして「ChatGPT / Copilotを代替する」——そして失望します。純粋な能力において、消費向けGPUで動作する35Bモデルは最先端のクラウドモデルに勝てないからです。特に、多段階推論、長距離のエージェント連携、正確なコード生成といった「プロダクション環境」の作業において顕著です。
一言で位置づけると:
複雑なロジック、プロダクション納品、品質が求められる作業 → Claude Code + Opus; ローカルモデルは日常的で、複雑でないロジック、許容可能なエラーのある雑用だけを引き受ける。
なぜローカルでプロダクション作業を行ってはならないのか:小パラメータモデルは、正確性と多段階推論が求められる場面で失敗する可能性が高く、プロダクション環境での失敗の代償は甚だしい(誤ったコード、逸脱したエージェント)。ローカルによるコスト削減やプライバシーの確保は、この代償を相殺するには不十分です。複雑な作業をOpusに任せることは、ローカルモデルに境界線を引く第一歩であり、最も重要なルールです。
2. ローカルモデルの存立基盤: オフライン + プライバシー
能力面でクラウドに勝てないのに、なぜローカルなのか? 2つのことは、クラウドがどれだけ優れていても実現できないからです:
- オフラインでの自立: ネットワーク切断、クラウドへのアクセス不可、クラウドによるブロック、クラウドの価格上昇やレートリミット——ローカルでは問題なく動作します。外部サービスのオンライン依存はありません。
- プライバシーの端末内完結: 個人データをモデルに与えても、アップロードされず、第三者のログや学習データセットに入りません。
これら2つこそが、「ある種の作業はローカルで行うべきだ」という真の理由です。
クラウドフォールバックに関する矛盾
一般的なアプローチとして「ローカル優先、クラウドでフォールバック」があります:ローカルが停止した場合(例:ゲームモード時にgamemodeがサービスを停止)、自動的にクラウドに切り替わり、使用が中断されません。一見美しく聞こえますが、冷静にならなければなりません——
クラウドフォールバックを設定すると、ローカルはもはやオフラインでもなく、純粋なプライバシーでもなくなります。 一度でもフォールバックが発生すれば、その時のデータはクラウドに送信されます。フォールバックとローカルの2つの本質的な特徴は本質的に矛盾しています。
したがって、すべてのローカルタスクに無条件でフォールバックを割り当てることはできません。誠実なアプローチは、シナリオに応じて2種類に分類することです:
| シナリオの性質 | ローカルが選ばれる理由 | フォールバックの設定? | 例 |
|---|---|---|---|
| 可用性優先 | ローカルは単なる「コスト削減のためのデフォルト」であり、オフライン/プライバシーは必須要件ではない | ✅ 可 | openclaw日常アシスタント: 情報検索、スケジュール調整。ローカル停止時はクラウドにフォールバックし、アシスタントのダウンを防ぐ |
| 純粋性優先 | ローカルは「オフライン/プライバシーであるため」に選ばれる | ❌ 不可 | daily-harvest: 個人コードやキーを含むセッションをスキャン。クラウドにフォールバック = これらのデータを外部に送信することになる |
この線引きが、本環境の設定で明確に区別されています:openclawの primary = ローカル / fallbacks = deepseek(可用性優先)、一方、llm-jobsのharvestはローカル使用不可時に直接 exit 3 して再実行を待ち、クラウドには決して送信しない(純粋性優先)。判断基準はシンプルです:「このタスクをクラウドに送信することが、ローカルを利用する初衷に反する場合、フォールバックを持つべきではありません。」
3. では、具体的にどのような支援ができるのか
「日常雑用 + オフライン/プライバシー」という狭い位置づけにおいて、ローカルモデルの3つの属性がそれぞれ異なるユースケースを解放します。以下、✅ は本環境で実際に稼働しているもの、💡 は拡張可能なアイデアです。
限界費用ゼロ → 日常雑用の常態化を恐れない
従量課金のクラウドモデルでは、無意識に「計算して使う」ようになりますが、ローカルはコストゼロであるため、低価値だが高頻度の雑用を毎日実行することを恐れる必要がありません。
- ✅ openclaw日常Q&A: 情報検索、スケジュール調整、ちょっとした質問。何度聞いても痛くありません。この種の「1回あたりの価値は大きくないが、頻度が高い」作業は、ゼロコストで維持できるからこそ可能です。
- ✅ 日報の毎日合成:
daily-reportは毎日09:00に、前日の断片的なエントリを重複排除し、人間が読める文章に統合し、システムインスペクション、継続作業、金曜日の「今週の視点」と合わせて4つのセクションにまとめ、本環境のメールliz@liz6.comに送信します。毎日必ず実行され、1回の呼び出しで複数の調整を行うため、クラウドの従量課金では痛手です。 - ✅ 今週の視点: 金曜日の日報に1セクション追加——モデルが過去7日間の完了エントリを読み、精力の配分を総括し、停滞している事項を指摘し、来週への提案を1つ示します。ドキュメント横断的な総合作業は人手では煩雑ですが、モデルならゼロコストで毎週実行できます。
- 💡 拡張: 收藏した記事/RSSに対して自動で1文のサマリーを生成;月報のローリング集計。
プライバシーの端末内完結 → 個人データを恐れずに与えられる
「AIで処理したいが、クラウドには預けたくない」というデータがあります。ローカルは、そのようなデータを安心して与えられる唯一の場所です。
- ✅ daily-harvest: 当日のClaude Code / Codex / Grokのセッション(個人プロジェクト、コード、潜在的なキーを含む)+ openclawの記憶をスキャンし、「実際に何をしたか」を抽出して日報に回填します。これらのデータはクラウドに送信してはならず、ローカルだからこそこのような与え方が可能です。
- 💡 拡張: 個人日記 / チャット履歴 / 財務レシートに対するローカルRAG検索;写真のOCRアーカイブ。
常駐して埋め込み可能 → 自動化に組み込むことを恐れない
モデルは localhost:18080 で常駐しており、スクリプトやタイマーからいつでも呼び出し可能で、クラウドサービスの起動やネットワーク往復の懸念がありません。
- ✅ llm-jobs: systemd timer が期限になるとローカルモデルを呼び出し、収集→抽出→配信をローカル完結で実行(次節で詳述)。
- ✅ openclawデフォルトモデル: 日常エージェントの
primaryがローカルを指しています。 - ✅ システムインスペクションのノイズ低減:
health-snapshotは生のシグナル(失敗したユニット、バックアップ結果、ZFS水位、24時間分の万単位のerrレベルログをソース別に集約、SSH認証失敗)をリードオンリーで収集し、モデルがノイズ低減と優先度付けを行い、日報の「🩺 システム」セクションを生成します。「radicaleがINFOをerrとして記録しているのは長期的なノイズである」「SSHは2FAあり、ブルートフォース未遂は報告不要」といった判断は、ルールでは記述できませんが、モデルなら一言で処理できます;すべて正常な場合は「✅ すべて正常」とだけ出力します。 - 💡 拡張: 記憶の定期的な蒸留とメンテナンス。
4. アイデアを常駐タスクとして実装する: llm-jobs フレームワーク
上記の自動化は、すべて同じ形をしています:データの収集 → ローカルモデルの呼び出し → ファイルへの書き込み / 配信。各タスクごとに個別に実装する(それぞれが独自にcurlでモデルを叩き、メールを送信する)のではなく、統一フレームワークとして抽出します。 ~/.local/lib/llm-jobs/ がそのフレームワークです——「定期的にローカルモデルを呼び出す」という類のタスク(日報、harvest、将来の週報/月報/記憶メンテナンス)の統一された着地点であり、新しいタスクを追加する際は収集スクリプトのみを記述すれば十分です。
なぜこの抽象化レイヤーが必要なのか
日報は原本孤立したスクリプトであり、唯一のデータソースは ~/doc/daily/ ですが、このディレクトリはClaude Codeとopenclaw(Miko)という2つのエージェントが「思い出したときに doc-daily-log を呼び出す」という尽力ベースで管理されています——誰かが記録を忘れると、完了した作業全体が日報から消失します(例:2026-07-03の日報は原本空欄でした)。問題は2つの側面に分解できます:
- 網羅性: 行った作業が確実にdailyに入らない。→
daily-harvestで実際の作業痕跡(会話履歴)から回収する。 - 品質: 純粋な文字列解析では、重複項目や粒度のばらつきがそのまま透過される。→ ローカルモデルで合成する。
これら2つは本質的に「定期的にローカルモデルを呼び出す」ものであるため、統一フレームワークとして抽出しました。
アーキテクチャの意思決定: systemd vs openclaw cron
どちらも「定期実行」ですが、2つのアプローチには大きな違いがあります:
| systemd timer + スクリプト | openclaw cron | |
|---|---|---|
| タスクモデル | 決定論的パイプライン(収集→モデル呼び出し→配信) | エージェントセッションを起動(ツール/記憶/channel コンテキスト付き) |
| 観測性 | journald → Loki、Grafanaアラート可 | openclaw内部ログ、監視スタックから外れる |
| 依存関係 | llama-server のみ(これもsystemd) | openclawゲートウェイがオンラインであること |
| デバッグ | --dry-run、systemctl start、純粋なスクリプトでバージョン管理可能 | エージェントの文脈で再現する必要があり、非決定論的 |
ルール: パイプライン系はsystemdを使い、エージェント系のみopenclaw cronを使用する。 日報とharvestは「ファイルの読み取り→モデルの1回呼び出し→ファイルへの書き込み/メッセージ送信」のパイプラインであり、ツールループは不要で、systemdを使用する。openclaw cronは「Mikoが本当に頭を使って手を動かす」必要がある定期タスクのみに残す(現状ではなし)。
構造: 3層の疎結合
~/.local/lib/llm-jobs/
bin/llm-call 統一モデルエントリ: POST localhost:18080; デフォルトはリクエスト側でthinkingをオフ (--think でオン)、
空のcontentは失敗として扱い、トークン数を増やして再試行、ローカルダウン時は exit 3
bin/notify-email 統一配信: 本環境のJMAPで liz@liz6.com の受信箱に書き込み(SMTP/トンネルを使用しない)
bin/health-snapshot リードオンリーでインスペクションシグナルを収集(失敗ユニット/バックアップ結果/ZFS/journal err集約/SSH認証)、
daily-reportの「システム」セクションにノイズ低減のためにモデルに与える
jobs/daily-harvest 深夜: 前日のセッションをスキャン → map-reduce で完了項目 + 継続作業を抽出 → dailyに追加
jobs/daily-report 朝: daily + リアルタイムインスペクションを読み取り → 4セクションを合成 → 本環境メールへ
README.md 規約 + タスク登録表
bin/llm-callが唯一のモデルエントリ —— 「定期的にローカルモデルを呼び出す」処理は、テスト済みの実装が1箇所のみで、モデル名/エンドポイント/フォールバック戦略が一元管理されています。モデルを変更する場合はここだけを変更すれば十分です。bin/notify-emailの配信は独立 —— 配信チャネルを変更する場合はこのレイヤーのみをいじれば十分です。配信失敗はGrafana Barkでアラート通知され、アラートチャネルはメールに依存しません。- テンプレートユニット
llm-job@.service——ExecStart=%h/.local/lib/llm-jobs/jobs/%i、タスクを追加する際に新しいserviceファイルを作成する必要はありません。
規約: タスクスクリプトは**bin/ 内の共有プリミティブのみを呼び出し**、各自でcurlを叩いてモデルにアクセスしたりメールを送信したりしてはいけません。
完全なクローズドループ
harvestの定期インスタンスはsystemd drop-inを通じて --date 昨日 を渡す(手動実行時はデフォルトで今日);04:00の時点で昨日は完全に終了しており、補完されるのはちょうど09:00に報告すべき内容である。マシンが夜間にスリープしてトリガーを逃しても問題ない、Persistent=true により、起動後に追実行される。
daily-harvest(04:00): 昨日までに登録されたローカルエージェントのセッションをスキャン——Claude Code ~/.claude/projects/**/*.jsonl、Codex ~/.codex/sessions/**/rollout-*.jsonl、Grok ~/.grok/sessions/*/chat_history.jsonl——それぞれを USER:/AI: の純粋なテキストに圧縮(思考やツールのノイズを除去し、制限まで切り捨て)してモデルに与え、map で2つのカテゴリを抽出する——「実際に完了した有意義な作業」と「明確に行うと言ったが未完成のもの」——およびopenclaw自身が蒸留した記憶。ソースはスクリプトで登録されており、未知のツールを魔法のように発見するものではない;新しいエージェントを追加する場合は、finder+compact のセクションを1つ追加する必要がある。完了候補 + 当日のdailyに既に存在する手動エントリを一緒に reduce し、本当に完了し、かつ手動で記録されていないもののみを選択し、JSONを出力し、hv_* タグで1行ずつ書き戻す。毎回、物理的に古い hv_ ブロックを削除してから再計算する(冪等、繰り返し実行しても重複しない)。継続作業候補は完了候補で重複排除した後に daily/<日付>.pending に書き込む(全体チェーンはbest-effort、失敗してもこのセクションが失われるのみ)。全体を通じてローカルモデル + ローカル書き込み。
daily-report(09:00): 4セクションを合成して本環境メール liz@liz6.com に書き込む——①「🩺 システム」: health-snapshot がリアルタイムのインスペクションシグナルを収集し、モデルがノイズ低減して簡報を作成(モデル使用不可時は失敗したユニットのみを列挙するにフォールバック、インスペクションは報告送信をブロックしない);②「📝 昨日の完了」: 前日のdailyの全エントリを読み取り、モデルが重複排除して人間が読める文章に書き換え、モデル使用不可時は「元のグループ化」にフォールバック(クラウドには送信せず、LLM合成を行わないのみ)、日報は必ず送信される;③「🔜 継続作業」: .pending ファイルを直接読み取り、存在しない場合はセクション自体が表示されない;④「📊 今週の視点」: 金曜日のみ、モデルが過去7日間のエントリを総括。日付/天気(wttr.in)を付加し、すべてのセクションは統一された決定論的な整形後処理(エントリ間の空行、箇条書きの全角インデント——モデルの整形の安定性を期待しない)を経て、notify-email が本環境のJMAPを通じて受信箱に書き込む。配信失敗時にはsystemdの失敗がLokiに入り、Grafana Barkのルール [llm-jobs] 日報メール配信失敗 がiPhoneにプッシュ通知する。
フォールバック(ローカル処理を優先する方針): harvest完了後に ~/.local/state/llm-jobs/harvest-<日付>.ok のマークファイルを作成;もしローカルモデルが使用不可の場合(gamemodeによって停止されている場合)、.deferred を書き込み exit 3 を実行——偽の空結果を書き込まず、クラウドにも送信せず、systemdが失敗としてマークしてLokiに記録する。09:00のreport送信前に、前日の .ok マークファイルが見つからない場合、まず daily-harvest --date <昨日> を追実行(この時点でモデルは回復している)してからdailyを読み取る。これにより、「夜間にマシンがスリープ中 / モデルがgamemodeによって停止されている」という全シナリオをカバーする。このフォールバックチェーンの前提は、.ok マークファイルが誠実であること——2026-07-10の事故(次節「血みどろの教訓」参照)は、空結果が誤って .ok と書き込まれ、フォールバックチェーン全体をショートサーキットさせたものである。
新しいタスクの追加
~/.local/lib/llm-jobs/jobs/<name>を記述(実行可能、--dry-run対応、bin/内の共有プリミティブのみを呼び出す)~/.config/systemd/user/llm-job@<name>.timerを作成(daily-reportをコピーし、OnCalendarを変更)systemctl --user daemon-reload && systemctl --user enable --now llm-job@<name>.timer
運用
# 手動実行(送信/書き込みなし)
# 手動で実際のタスクをトリガー
# ログ確認(journald → Lokiにも流入)
|
配信は本環境のJMAPを使用し、WeChat bot tokenに依存しなくなった。notify-weixin / notify-flush は停止済み;失敗はGrafana Barkアラートに切り替えた。
5. ローカルモデルを効果的に使用するいくつかの実践的アドバイス
これらはすべて上記の実際のタスクから得られたもので、あらゆる「ローカルモデルの呼び出し」のシナリオに移植可能です:
- 日常タスクではthinkingをオフにし、かつリクエスト側でオフにする: 整理、まとめ、分類などの作業には思考チェーンは不要であり、オフにすることでレイテンシを大幅に削減できる。重要なのは、リクエストレベルの
chat_template_kwargs: {"enable_thinking": false}(サーバー側のデフォルトを上書き可能)を使用することであり、サーバー側のデフォルトに依存してはいけない——デフォルトは共有されており、いつか他のシナリオのために変更される可能性がある(以下の血みどろの教訓を参照)。bin/llm-callは現在デフォルトでリクエスト側で思考をオフにしており、必要に応じて--thinkで明示的にオンにできる。 - 長いコンテキストを善用し、自分で分割しない: ローカルでは非常に長いウィンドウ(本環境では64K–192K)まで対応可能であり、セッション全体やドキュメント全体を与えるだけで十分である。harvestでは、セッションの切り捨て上限を10kから40kに引き上げた——切り捨てはセッションの中間部分の完了サマリーを切断し、見逃しを引き起こす。
- 構造化出力には検証 + 再試行を設ける: モデルにJSONを出力させる場合は、解析のフォールトトレランスと失敗時の再試行(
bin/llm-callは内部で2回再試行)を設ける。小モデルは温度パラメータ下でフォーマットから外れることがある。 - 判断系のプロンプトではポジティブな抽出を使用し、「ない場合は『なし』を出力」という逃げ道を簡単に与えない(血みどろの教訓): harvestのmapでは当初、否定フレームワーク(「議論ではない場合は『なし』を出力」)を使用していたが、温度パラメータ下でランダムに生産的なセッション全体を「なし」と判断し、1日分の作業を失った(同じ入力でも、1回目は6件出力、2回目は「なし」)。その後、「プログラミングセッションには通常生産性があるため、全文を読み通してから抽出し、純粋な相談のみで『なし』を出力する」に変更し、5/5で安定。LLMの判断系プロンプトでは、「何も出力しない」という便利な逃げ道を絶対に与えてはならない。それはサンプリングにおいて不安定にデータを消失させる。
- 過度なマージを避ける: reduce段階での「類似項目のマージ」が攻撃的すぎると、同じ入力でもあるときは19件、別のときは4件の曖昧な大見出しに崩壊する。粒度を保つ、マージは下流(日報生成)のステップに任せる。
- HTTP 200も偽りの成功である可能性がある——空のcontentは失敗として扱うべき(血みどろの教訓、2026-07-10): codexのフリーズ修正のために、サーバー側のデフォルト
qwen3.6-mtp-thinkingをenable_thinking:falseからreasoning = onに変更したところ、思考チェーンがreasoning_contentに分離され、バッチタスクの小さいmax_tokens(512)がすべて思考に消費された——200を返すがcontent=""、finish_reason=length。harvestは空の出力を「そのセッションには作業がない」と判断し、17件の実際の作業があるセッションをすべて「なし」と判断し、.okの成功マークファイルまで書き込んでしまい、朝のフォールバックがショートサーキットされ、日報に「昨日の記録なし」と送信された。さらに、思考に予算を追加しても勝てない——実測では512→2048にしても、より長く思考して結局使い切った。修正は二管併用:①llm-callで空のcontentを失敗として扱い(lengthの場合はmax_tokensを倍にして即時再試行、使い切ったらexit 3でdeferredに進む);②バッチタスクはデフォルトでリクエスト側で思考をオフにする(上記)。より深い教訓:共有推論インスタンスのサーバー側のデフォルトは、すべての呼び出し元の暗黙の契約である——1つのシナリオのためにデフォルトを変更する場合、他の呼び出し元をすべて確認する必要がある。 - モデルの整形の安定性を期待しない、表示層では決定論的な後処理を使用する: プロンプトに「各エントリを空行で区切る」といった整形の指示を書いても、小モデルの実行は不安定である。日報のアプローチでは、モデルは内容のみを担当し、スクリプトが一括で再整形する(エントリ間の空行、箇条書きの全角スペースインデント——モデルの出力フォーマットがどれほど揺らいでも、読者が見る整形は一定である)。
- 構造化出力には十分なトークン数を割り当て、かつ失敗を成功として偽装してはならない(血みどろの教訓): 忙しい日の候補は100件を超える可能性があり(54セッション→163候補)、reduceの出力JSONは非常に長くなり、
max_tokens=1500では直接切り捨てられ→ JSONが無効→ 解析失敗。当初、解析失敗を静かに「新規追加なし」と見なし、.okの成功マークファイルまで書き込んでいたため、朝のフォールバックもトリガーされず、1日分の作業が静かに消滅した(2026-07-06)。3つの鉄則:①出力トークン数を十分に確保し、昇格して再試行する(6000→9000);②空の判定ではNone(失敗)と[](真に新規追加なし)を区別する;③失敗にはフォールバックを設け、破棄しない——本環境のharvestでは、reduceが繰り返し失敗した場合、「プロジェクト別にグループ化して生の候補を直接書き出す」にフォールバックし(下流の日報でマージ)、モデルが真に使用不可な場合のみ.deferred+exit 3を実行。「失敗が空結果として扱われる」経路は、自動化においてデータを静かに飲み込む。 - エージェント / ツール呼び出しでは
--jinjaをオンにする: そうしないと、モデルは「toolcallタグを吐けるが実行しない」——llama-serverのネイティブなツール解析(出力を構造化されたtool_callsとして解析)は、--jinjaがオンの時のみ有効である。純粋な対話には影響しないが、エージェント使用時には必須。
関連ドキュメント
- local-llm.md — デプロイ: メモリ予算、量子化の選択、MTP、常駐の潮汐スケジューリング、完全なsystemd設定
- timers-and-crons.md — 全systemd timerの概要(llm-job@ 2つを含む)
- monitoring.md — journald → Lokiログ集約(llm-jobsの失敗はここでアラート可能)