0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Bot毎に推論エンジンを分けたい! Ollama × OpenRouter 混在運用

0
Last updated at Posted at 2026-05-31

Hermes Agent で bot ごとに推論エンジンを設定する — Ollama × OpenRouter 混在運用

はじめに

Mac Studio 1 台で複数の AI bot を動かしていると、すぐにこういう要求にぶつかる。

  • 軽い案件はローカル LLM(Ollama)で回してコストゼロにしたい
  • 重い案件・本番運用だけクラウド(OpenRouter)に投げたい
  • でも会話履歴・記憶・人格・触っていいファイルは bot ごとに完全に分けたい

Hermes Agent はこれを profile という単位で素直に解ける。本記事では、bot A をローカル LLM、bot B を OpenRouter にルーティングする構成を、実際の現場設定の手順としてまとめる。

結論を先に言うと、profile を「LLM provider + bot identity + memory + skill + 制約」のひとまとまりとして扱う設計が、複数 bot 運用の正解だった。bot を増やすことと profile を増やすことを同義にする、という考え方だ。


1. 結論先出し

Hermes Agent では profile という単位で bot を独立稼働でき、それぞれ別の LLM プロバイダにルーティングできる。~/.hermes/<profile>/config.yaml 内の model.provider を書き換えるだけで切り替わる。

最小構成:

  • bot A(default profile)→ Ollama gemma4:26b-mxfp8(local)
  • bot B(qi_bidding profile)→ OpenRouter openai/gpt-5.4-mini

注: モデル名(gemma4:26b-mxfp8 / openai/gpt-5.4-mini)は環境ごとに読み替えてほしい。ollama list や OpenRouter のモデル一覧で実在するタグに置き換えること。


2. ファイル配置の全体図

profile の独立性を理解する一番の近道は、ディレクトリ構造を眺めることだ。

~/.hermes/
├── config.yaml                    ← bot A (default profile) のメイン config
├── .env                           ← bot A の secrets
├── auth.json                      ← 全 profile 共有の OAuth credential 池
├── webhook_subscriptions.json     ← 全 profile 共有 (webhook adapter は default のみ)
├── hermes-agent/                  ← Python package 本体 (update で書き換わる)
├── kanban.db (+ boards/*)         ← 全 profile 共有 SQLite
├── sessions/                      ← bot A の会話履歴
├── memories/                      ← bot A の MEMORY.md / USER.md
├── workspace/                     ← bot A の作業領域
├── logs/                          ← bot A のログ
├── skills/                        ← bot A の skill 群
├── profiles/
│   └── qi_bidding/                ← bot B のサンドボックス (default と独立)
│       ├── config.yaml            ← bot B のメイン config
│       ├── .env                   ← bot B の secrets
│       ├── sessions/
│       ├── memories/
│       ├── workspace/
│       ├── logs/
│       ├── skills/
│       └── SOUL.md                ← bot B 専用の人格 / 制約 prompt
└── _pre_update_backup-<ts>/       ← 更新前バックアップ (運用上の保険)

~/Library/LaunchAgents/
├── ai.hermes.gateway.plist                ← bot A の launchd entry
└── ai.hermes.gateway-qi_bidding.plist     ← bot B の launchd entry (別 service)

ポイントは、sessions/ memories/ SOUL.md は profile ごとに独立している一方、auth.json kanban.db は全 profile 共有になっていること。この「何が混ざって何が分かれるか」の線引きが運用設計の肝になる(詳細は §8)。


3. profile を新規作成する

# bot B 用 profile 作成
# (内部で <profile>/ ディレクトリ + デフォルト config を雛形展開)
hermes profile create qi_bidding

# wrapper script が ~/.local/bin/qi_bidding として作られる
# 以降 `hermes -p qi_bidding ...` または `qi_bidding ...` で呼べる

これだけで ~/.hermes/profiles/qi_bidding/ 以下に config の雛形が展開される。


4. config.yaml の model 部だけ書き換える

bot A — Ollama (local LLM) にルーティング

~/.hermes/config.yaml の冒頭:

model:
  default: gemma4:26b-mxfp8                # ollama list で出てくる tag そのまま
  provider: ollama
  base_url: http://127.0.0.1:11434/v1      # Ollama の OpenAI 互換 endpoint

providers:
  ollama:
    api_key: ollama                        # 任意の文字列で OK (Ollama は検証しない)
    base_url: http://127.0.0.1:11434/v1

事前準備:

brew install --cask ollama
open -a Ollama                              # daemon 起動
ollama pull gemma4:26b-mxfp8                # モデル取得 (~26GB)

ローカル LLM 並列の罠回避(重要)

launchctl setenv OLLAMA_NUM_PARALLEL 1
launchctl setenv OLLAMA_MAX_LOADED_MODELS 1
launchctl setenv OLLAMA_MAX_QUEUE 10
# Ollama.app を再起動して反映

並列推論時の KV cache 競合で 30B 級モデルが詰まる挙動への対処。OLLAMA_NUM_PARALLEL=1 で推論を順次化する。launchd plist で永続化しておくのが安全(~/Library/LaunchAgents/ai.ollama.envsetter.plist)。

この「ローカル LLM の擬似並列を直列化する」パターンは、別途 Hermes Agent + Ollama の並列処理アーキテクチャの記事で深掘りしているので、そちらも参照してほしい。本記事では「並列の罠があるので潰しておく」程度に留める。

bot B — OpenRouter にルーティング

~/.hermes/profiles/qi_bidding/config.yaml の冒頭:

model:
  default: openai/gpt-5.4-mini             # OpenRouter のモデル ID 形式
  provider: openrouter
  base_url: https://openrouter.ai/api/v1

providers:
  openrouter:
    api_key: ${OPENROUTER_API_KEY}         # .env から展開される
    base_url: https://openrouter.ai/api/v1

~/.hermes/profiles/qi_bidding/.env に:

OPENROUTER_API_KEY=sk-or-v1-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

default の .env にも同名キーを置けば共有できるが、profile ごとに別契約・別 key にする方が安全。コスト管理も切り分けやすくなる。


5. Discord bot token を profile ごとに分ける

Discord は 1 bot token = 1 WebSocket session なので、bot 2 つ = bot token 2 つ必要。Discord Developer Portal で 2 つの Application を作り、それぞれの token を割り当てる。

# ~/.hermes/.env  (bot A 用)
DISCORD_BOT_TOKEN=MT...bot-A-token...
DISCORD_HOME_CHANNEL=<CHANNELID_1>
DISCORD_ALLOW_ALL_USERS=true

# ~/.hermes/profiles/qi_bidding/.env  (bot B 用)
DISCORD_BOT_TOKEN=MT...bot-B-token...
DISCORD_HOME_CHANNEL=<CHANNELID_2>
DISCORD_ALLOWED_USERS=<USERID_1>

注意点:

  • Privileged Gateway Intents で Message Content Intent を ON にしないと、bot 起動時に PrivilegedIntentsRequired で死ぬ。
  • DISCORD_ALLOWED_USERS=<username> のように ユーザ名で書くと Hermes が name resolution のため Server Members Intent を要求し、admin 承認待ちで詰む。数値 User ID(Discord 開発者モードでコピー)を入れること。

6. 各 bot を launchd で常駐化

~/Library/LaunchAgents/ai.hermes.gateway.plist(bot A 用、雛形):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key><string>ai.hermes.gateway</string>
    <key>ProgramArguments</key>
    <array>
        <string>/Users/llm/.hermes/hermes-agent/venv/bin/python</string>
        <string>-m</string><string>hermes_cli.main</string>
        <string>gateway</string><string>run</string>
        <string>--replace</string>
    </array>
    <key>EnvironmentVariables</key>
    <dict>
        <key>HERMES_HOME</key><string>/Users/llm/.hermes</string>
    </dict>
    <key>RunAtLoad</key><true/>
    <key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>
    <key>StandardOutPath</key><string>/Users/llm/.hermes/logs/gateway.log</string>
    <key>StandardErrorPath</key><string>/Users/llm/.hermes/logs/gateway.error.log</string>
    <key>SoftResourceLimits</key>
    <dict><key>NumberOfFiles</key><integer>8192</integer></dict>
    <key>HardResourceLimits</key>
    <dict><key>NumberOfFiles</key><integer>16384</integer></dict>
</dict>
</plist>

bot B 用は --profile qi_biddingProgramArguments に挟むだけ:

<array>
    <string>/Users/llm/.hermes/hermes-agent/venv/bin/python</string>
    <string>-m</string><string>hermes_cli.main</string>
    <string>--profile</string><string>qi_bidding</string>  <!-- ここだけ追加 -->
    <string>gateway</string><string>run</string>
    <string>--replace</string>
</array>

Label は別名(ai.hermes.gateway-qi_bidding)にすること。

FD 上限は両 plist とも 8192 に上げるのが鉄則。 macOS LaunchAgent default の 256 のままだと、長時間稼働で file descriptor が枯渇し、bot がサイレント切断される。

起動:

launchctl bootstrap gui/501 ~/Library/LaunchAgents/ai.hermes.gateway.plist
launchctl bootstrap gui/501 ~/Library/LaunchAgents/ai.hermes.gateway-qi_bidding.plist

7. 動作確認

# 両 gateway が独立プロセスで動いていることを確認
ps -ax | grep "hermes_cli.main" | grep -v grep

# 出力イメージ:
#  PID    args
#  AAAAA  hermes_cli.main gateway run --replace             ← bot A (default)
#  BBBBB  hermes_cli.main --profile qi_bidding gateway run  ← bot B

それぞれの model 設定確認:

hermes status               | grep -iE "Model|Provider"
hermes -p qi_bidding status | grep -iE "Model|Provider"

# 出力例:
#  [bot A]  Model: gemma4:26b-mxfp8 / Provider: Custom endpoint (Ollama)
#  [bot B]  Model: openai/gpt-5.4-mini / Provider: OpenRouter

smoke test:

hermes -z "Say pong" --yolo                 # bot A の Ollama 経由
hermes -p qi_bidding -z "Say pong" --yolo   # bot B の OpenRouter 経由

両 bot が独立して別エンジンで応答すれば完成。


8. 共有リソースと分離リソース(運用注意)

profile 設計でいちばん事故りやすいのが、「何が共有で何が分離か」の取り違えだ。

リソース 範囲 コメント
~/.hermes/auth.json(OAuth pool) 全 profile 共有 API key も OAuth も 1 個の pool。profile-specific にしたいなら別アカウントで key 発行
~/.hermes/kanban.db + boards/* 全 profile 共有 bot 間でタスク受け渡しが自然にできる設計。完全分離したいなら HERMES_KANBAN_DB env で別パスへ
sessions/ / memories/ / SOUL.md profile ごとに独立 会話履歴・記憶・人格は混じらない
Discord/Teams credentials profile ごとに独立 別 bot token 必要
LLM provider profile ごとに独立 この記事の主題
webhook adapter(:8644) port 競合するので片方だけ enable default に置き、qi_bidding は platforms.webhook.enabled: false で抑制
Teams adapter(:3978) 同じく片方 qi_bidding profile に置く運用例
Cloudflare tunnel(named) 全 profile が同じ tunnel を共有 hostname 別 ingress で振り分け

9. SOUL.md で bot 個性 / 制約を分離

profile の独立性がいちばん効くのが、bot ごとの「触っていい領域」の制約だ。~/.hermes/profiles/qi_bidding/SOUL.md に bot ごとのシステムプロンプトと境界を書く。

You are Hermes_For_QI_Bidding, the QI Bidding agent.
You assist bid/proposal preparation.

## Workspace boundaries (STRICT)
- Allowed Obsidian vault: /Users/llm/Documents/obsidian/bidding/
- Forbidden: /Users/llm/Documents/obsidian/yoshiya/ (個人 vault、別 profile 担当)

## Tool conduct
- Use `read_file` / `write_file` over shell `cat` / `find`.
- Never commit code without explicit user instruction.

これで bot B は bidding vault しか触らない。bot A は default の SOUL.md で別境界(または制限なし)にする。業務 bot から個人 vault を物理的に守れるのは、profile を分けた副次効果として大きい。


10. ハマりどころ(失敗事例)

症状 原因 対処
Bot 1 つだけ Discord 反応しない DISCORD_BOT_TOKEN を間違って同じ値にして 2 つ目が kick される bot 単位に別 token
PrivilegedIntentsRequired で起動失敗 Developer Portal で Message Content Intent OFF Portal で ON
Server Members Intent privileged… DISCORD_ALLOWED_USERS にユーザ名(非数値) 数値 User ID に置換
ローカル LLM が 11 分も応答しない OLLAMA_NUM_PARALLEL > 1 で KV cache 競合 NUM_PARALLEL=1 で順次化
30 時間稼働後 bot サイレント切断 macOS LaunchAgent FD 上限 256 枯渇 plist の SoftResourceLimits=8192
OpenRouter で 504 タイムアウト provider_routing.only で限定したら全滅 provider_routing: {} で OpenRouter デフォルトに
片方の bot だけ webhook 立ち上がらない 両 profile で :8644 取り合い qi_bidding 側で platforms.webhook.enabled: false
update 後 DB schema mismatch kanban DB に新カラム未追加 手動 ALTER TABLE tasks ADD COLUMN session_id TEXT

11. on-the-fly でモデル切替する

Discord 内で /model <name> slash command を打てば、session 単位で切り替わる(gateway restart 不要)。

/model openrouter/anthropic/claude-sonnet-4
/model ollama/qwen3.6:35b-a3b-coding-mxfp8

session のスコープなので他 thread / 他 bot には影響しない。「重い案件だけ強いモデルに切替」「軽い雑談は Ollama で」という運用ができる。


12. fallback_model で provider 不調時の自動 failover

~/.hermes/config.yaml に追記:

fallback_model:
  provider: openrouter
  model: google/gemma-4-31b-it

primary が rate limit(429)/ overload(529)/ 5xx / 接続失敗で落ちた時、自動で fallback に切り替わる。クラウド側の動的枠超過にも有効。


13. 記事の核(主張)

profile =「LLM provider + bot identity + memory + skill + 制約」のまとまった単位として扱う設計が、複数 bot 運用の正解。bot を増やすことと profile を増やすことを同義にする。

「bot ごとに違う engine」という機能を実現するために、Hermes は config を profile スコープに切る。その結果として会話履歴も人格も制約も独立になる。これが副次効果として、

  • 個人 vault を業務 bot から守る
  • 片方の provider 不調時にもう片方は無傷
  • 片方を Ollama 検証用、もう片方を本番運用

といった運用に効いてくる。


関連記事

本記事では profile 単位で LLM provider を分ける設計を扱ったが、bot を Discord ではなく Microsoft Teams で動かす場合、「プライベートチャンネルで bot が反応しない」という Teams 固有の落とし穴がある。これは Hermes の問題ではなく Teams の設計仕様で、profile 設計とは別レイヤーの話になる。

詳細は別記事「Microsoft Teams のプライベートチャンネルで bot が反応しない問題 — 設計仕様と回避策」にまとめた。Teams adapter の chat_id 形式制約(semicolon 不可)もそちらで扱っている。


タグ

HermesAgent Ollama OpenRouter Discord LLM AIエージェント macOS LaunchAgent

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?