# v1.6 `auto_router` 実機確認手順書

Spec: [v1.6-auto-router.md](./v1.6-auto-router.md) / CHANGELOG: v1.6.0 / Target: `default_profile: auto` を書いた状態で、内蔵ルールとカスタムルールの双方が想定通りに profile を振り分けるかを 1 セッションで確認する。

---

## 目的

v1.6.0 のリリース直後に **実機上で** `auto_router` が以下を満たしていることを確認する:

1. 起動時 validator が `default_profile: auto` を受け入れ、3 bundled profile の存在をチェックしている
2. 画像添付 / コードフェンス密集 / 平文の 3 入力が、それぞれ `multi` / `coding` / `writing` profile に振り分けられる
3. `auto-router-resolved` / `auto-router-fallthrough` の 2 ログイベントが適切に発火し、`coderouter_auto_router_fallthrough_total` Prometheus counter が増分する
4. v0.6-D 以来の precedence chain (`body.profile` > `X-CodeRouter-Profile` > `X-CodeRouter-Mode` > `auto_router` > `default_profile`) が壊れていない
5. `auto_router:` ブロックを書いた場合、内蔵ルールは完全置換され、ユーザー定義ルールのみが発火する
6. Claude Code / codex CLI 経由で自然な会話を投げた時も、同じルールで振り分けが起きる

## 前提条件

- Python **3.12 以上** (`python3 --version` で確認。Homebrew の古い 3.10 を踏まないよう注意)
- `git` / `jq` / `curl` がインストール済み
- Ollama がローカルで稼働中 (`curl -s http://localhost:11434/api/version` で JSON が返る)
- テスト用の小さい PNG 1 枚 (スクリーンショット等で OK)

CodeRouter 本体とモデルはステップ 0 でインストール・pull する。

## 所要時間

インストール (clone + venv + pip) に 5〜10 分、モデル pull (合計 ~15GB) に 10〜15 分、確認手順自体に 20〜30 分。合計 **40〜60 分** を見ておく。

---

## チェックリスト

### 0. 事前準備

#### 0-A. CodeRouter v1.6 をインストール

clone + venv 経路を推奨 (`auto_router:` ルールを手元でいじる前提のため)。既に v1.5 以前を入れている場合も一度 venv から作り直す方が環境差を拾わない。

- [ ] repo を clone (既に clone 済みなら `git pull && git checkout v1.6.0`)

```bash
git clone https://github.com/zephel01/CodeRouter.git
cd CodeRouter
git checkout v1.6.0   # tag push 後。まだなら main の先端で HEAD 運用
```

- [ ] Python 3.12 で **プロジェクト直下に** venv を作って activate

> **注意**: `uv pip install` は cwd の `.venv` と `$VIRTUAL_ENV` が両方空だと、**ホーム直下の `~/.venv` を黙って掴む**。必ず先に venv を作って activate してから `uv pip install` を打つ。

```bash
# uv 派 (推奨) — cwd に必ず .venv を作る
uv venv --python 3.12
source .venv/bin/activate        # Windows: .venv\Scripts\activate

# 素の python 派でも同等
# python3 -m venv .venv
# source .venv/bin/activate
```

- [ ] venv がプロジェクト直下の `.venv` を指していることを確認 (**ここは必ず目で見る**)

```bash
which python
# 期待: /Users/xxx/works/.../CodeRouter/.venv/bin/python
# ↑ $HOME/.venv/bin/python だったら venv が activate できていない、もしくは誤った venv を掴んでいる

echo "$VIRTUAL_ENV"
# 期待: .../CodeRouter/.venv

python --version
# 期待: Python 3.12.x
```

- [ ] dev extras 込みで editable install

```bash
# uv 派 (推奨、解決が速い)
uv pip install -e ".[dev]"

# 素の pip 派でも同等
# pip install -e ".[dev]"
```

実行時に `Using Python 3.12.x environment at: ...CodeRouter/.venv` のような行が出ていれば OK。`at: /Users/xxx/.venv` (プロジェクト外) と出たら即中断し、前ステップの `which python` からやり直す。

- [ ] インストール確認 (v1.6 の実装が実体として入っていること)

```bash
python -c "from coderouter.routing.auto_router import classify, RESERVED_PROFILE_NAME; print(RESERVED_PROFILE_NAME)"
# 期待: "auto"

coderouter --version
# 期待: coderouter 1.6.0 (pyproject.toml 未 bump の場合は 1.5.0 表示だが機能は入っている)
```

- [ ] 回帰テストを 1 回通しておく

```bash
pytest -q
# 期待: 596 passed
```

もしここで `ImportError: cannot import name 'model_validator' from 'pydantic'` が出たら、`pytest` バイナリが venv 外 (`/opt/homebrew/bin/pytest` 等) に解決されている可能性が高い。`which pytest` が venv 内を指さない場合は `python -m pytest -q` で venv 経由に強制するか、`uv pip install -e ".[dev]"` を再実行する (トラブルシュート節参照)。

#### 0-B. 既存の providers.yaml を退避

- [ ] 既存設定がある場合は退避する

```bash
cp ~/.coderouter/providers.yaml ~/.coderouter/providers.yaml.bak 2>/dev/null || true
```

#### 0-C. Ollama モデルを pull

- [ ] 3 モデルを pull (既に入っていれば飛ばす)

```bash
ollama pull qwen2.5-coder:7b    # coding   (~4.7 GB)
ollama pull qwen2.5:7b          # writing  (~4.7 GB)
ollama pull qwen2.5vl:7b        # multi    (~6 GB、画像テストを飛ばすなら省略可)
```

#### 0-D. auto_router サンプルを本番位置に配置

- [ ] `examples/providers.auto.yaml` を `~/.coderouter/` にコピー

```bash
mkdir -p ~/.coderouter
cp examples/providers.auto.yaml ~/.coderouter/providers.yaml
```

### 1. 起動時 validator の確認

- [ ] CodeRouter を前面で起動

```bash
coderouter serve --port 8088
```

- [ ] 別ターミナルで `/healthz` が 200 を返し、`allow_paid: false` / providers に `ollama-qwen-coder-7b` / `ollama-qwen-7b` / `ollama-qwen-vl-7b` の 3 本が載っていること

```bash
curl -s http://localhost:8088/healthz | jq .
```

- [ ] `/metrics.json` の `config.default_profile` が `"auto"` であること

```bash
curl -s http://localhost:8088/metrics.json | jq '.config.default_profile'
# 期待: "auto"
```

- [ ] 起動ログに `coderouter-startup` が 1 行出て、`default_profile=auto` / `mode_source=config` が読み取れること (stderr を確認)

**失敗時の典型**: `bundled auto_router requires profiles ['multi','coding','writing']` で起動が落ちる → `providers.yaml` の profiles 節に 3 profile が宣言されているか確認。

### 2. 3 つの内蔵ルールを 1 本ずつ踏む

ここで使う実験用コマンドは全て `/v1/chat/completions` に直接 curl で打つ。serve 側 stderr のログと `/metrics.json` counter を都度確認する。

#### 2-a. 平文 → `writing` (fall-through)

- [ ] 以下を実行し、200 応答 + `qwen2.5:7b` からの返答が返ること

```bash
curl -s http://localhost:8088/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "ignored",
    "messages": [{"role": "user", "content": "今日の天気を英訳してください。"}]
  }' | jq '.choices[0].message.content'
```

- [ ] serve 側 stderr に `auto-router-fallthrough` イベントが 1 本出ていること (`resolved_profile: "writing"` が payload に載っている)
- [ ] `auto-router-resolved` イベントは **出ていない** こと

#### 2-b. コードフェンス密集 → `coding`

- [ ] 以下を実行し、200 応答 + `qwen2.5-coder:7b` からの返答が返ること

```bash
curl -s http://localhost:8088/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "ignored",
    "messages": [{"role": "user", "content": "このバグ見つけて:\n```python\ndef foo():\n    return 1/0\nfoo()\n```\nなぜゼロ除算?"}]
  }' | jq '.choices[0].message.content'
```

- [ ] serve 側 stderr に `auto-router-resolved` が出て、payload の `rule_id: "builtin:code-fence-dense"` / `resolved_profile: "coding"` / `signals.code_fence_ratio >= 0.3` が確認できること

#### 2-c. 画像添付 → `multi`

- [ ] テスト用 PNG を base64 化して画像付きリクエストを送る

```bash
IMG_B64=$(base64 -w0 < ~/Downloads/test.png 2>/dev/null || base64 < ~/Downloads/test.png)

curl -s http://localhost:8088/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d "{
    \"model\": \"ignored\",
    \"messages\": [{
      \"role\": \"user\",
      \"content\": [
        {\"type\": \"text\", \"text\": \"この画像は何?\"},
        {\"type\": \"image_url\", \"image_url\": {\"url\": \"data:image/png;base64,$IMG_B64\"}}
      ]
    }]
  }" | jq '.choices[0].message.content'
```

- [ ] `auto-router-resolved` が出て、`rule_id: "builtin:image-attachment"` / `resolved_profile: "multi"` / `signals.has_image: true` が確認できること
- [ ] 応答が `qwen2.5vl:7b` から返っていること (Ollama 側ログ or `coderouter stats --once` の Providers パネル)

**注意**: VL モデルの初回ロードは 10〜30 秒以上かかる場合があるため、timeout を体感しても慌てず `ollama ps` で読み込み中か確認する。

### 3. Prometheus counter の増分を確認

- [ ] `/metrics` で auto_router 関連の counter が想定通り増えていること

```bash
curl -s http://localhost:8088/metrics | grep -E 'auto_router|requests_total'
```

- [ ] `coderouter_auto_router_fallthrough_total` が 1 (ステップ 2-a の fall-through 分)
- [ ] `coderouter_requests_total` が 3 (ステップ 2-a/b/c の合計)

**注意**: 他プロセス (Claude Code 等) から追加のリクエストが飛んでいると counter が想定より増える。その場合は serve 再起動で counter を初期化して再計測。

### 4. per-request 上書きが auto より優先されることを確認

- [ ] `body.profile: "coding"` を明示指定して画像なしリクエストを送り、auto router が発火しないこと

```bash
curl -s http://localhost:8088/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "ignored",
    "profile": "coding",
    "messages": [{"role": "user", "content": "こんにちは"}]
  }' > /dev/null
```

- [ ] serve 側 stderr に `auto-router-*` イベントが **1 本も出ていない** こと (auto slot より前で `body.profile` が確定しているため `classify()` が呼ばれない)

### 5. カスタムルールセットに差し替えて 1 本踏む

- [ ] `providers.auto-custom.yaml` に差し替え、serve を再起動する

```bash
cp examples/providers.auto-custom.yaml ~/.coderouter/providers.yaml
# serve を Ctrl+C で止めて再起動
coderouter serve --port 8088
```

- [ ] 翻訳意図の regex ルールが発火すること

```bash
curl -s http://localhost:8088/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "ignored",
    "messages": [{"role": "user", "content": "Please translate this to French."}]
  }' > /dev/null
```

- [ ] serve 側 stderr に `auto-router-resolved` が出て、`rule_id: "user:translate-intent"` / `resolved_profile: "writing"` が確認できること
- [ ] `builtin:` prefix のイベントが **1 本も出ていない** こと (= 内蔵ルールが完全置換されている証拠)

### 6. Claude Code 経由で 3 種の会話を通す

- [ ] `providers.auto.yaml` に戻して serve 再起動

```bash
cp examples/providers.auto.yaml ~/.coderouter/providers.yaml
# serve 再起動
coderouter serve --port 8088
```

- [ ] Claude Code を CodeRouter に向けて起動

```bash
export ANTHROPIC_BASE_URL="http://localhost:8088"
export ANTHROPIC_AUTH_TOKEN="dummy"
claude
```

- [ ] Claude Code 内で以下 3 ターンを順に投げ、serve 側 stderr を監視:
  1. `こんにちは、元気ですか` → `auto-router-fallthrough` / `resolved_profile: writing`
  2. ```` fix this: ```python\nprint(1/0)\n``` why? ```` → `auto-router-resolved` / `builtin:code-fence-dense` / `coding`
  3. 画像を 1 枚ドラッグ&ドロップして `これ何?` → `auto-router-resolved` / `builtin:image-attachment` / `multi`

- [ ] 各ターンで想定通りの profile に振り分けられていること (stderr ログ + `coderouter stats --once` の Providers パネルで応答元モデルを確認)

### 7. 後片付け

- [ ] 検証前の `providers.yaml` に戻す (auto-router を本採用しない場合)

```bash
mv ~/.coderouter/providers.yaml.bak ~/.coderouter/providers.yaml
```

- [ ] auto-router を本採用する場合は退避ファイルを削除

```bash
rm ~/.coderouter/providers.yaml.bak
```

---

## トラブルシュート

### VL モデルが応答を返さない

`qwen2.5vl:7b` は画像推論の初回ロードが GPU 非搭載環境で数十秒〜分単位かかる。`providers.auto.yaml` の `timeout_s: 120` を延ばすか、`ollama ps` で立ち上げ状態を確認してから再試行する。

### `auto-router-fallthrough` が想定より多い

Claude Code は起動時に複数の health-check 的な空リクエストを送る。counter の絶対値ではなく「実験リクエストの前後差分」で確認する、または serve を再起動して counter を初期化してから測る。

### `builtin:code-fence-dense` に落ちない

短いプロンプト (全体 100 文字未満) だと ``` ```python``` ``` のフェンスを含めても比率が 0.3 に届かないケースがある。30〜50 行の本格的なコード塊を貼ると確実に閾値を超える。閾値を緩めたい場合は `providers.auto-custom.yaml` を参考に `code_fence_ratio_min: 0.15` 相当のカスタムルールを書く。

### 起動が `bundled auto_router requires profiles [...]` で落ちる

`providers.yaml` の `profiles:` 節に `multi` / `coding` / `writing` の 3 名が揃っていない。(a) 3 profile を全て宣言する、(b) 独自 `auto_router:` ブロックで内蔵ルールを上書きする、(c) `default_profile: auto` を別 profile 名に戻す、のいずれかで解決する。

### `pip install` が `externally-managed-environment` で落ちる

macOS (Homebrew Python) / Ubuntu 23+ / Debian bookworm+ で PEP 668 によりシステム Python への素の `pip install` が拒否されているパターン。ステップ 0-A の venv 経由インストールに乗り換える (`python3 -m venv .venv && source .venv/bin/activate` を先に実行してから `pip install -e ".[dev]"`)。`--break-system-packages` での強行は OS 管理の Python 環境を壊す原因になるため非推奨。

### `pytest -q` が `ImportError: cannot import name 'model_validator' from 'pydantic'` で落ちる

venv 内の pydantic v2 ではなく、OS 側の Homebrew pydantic v1 を拾っている。原因は `pytest` バイナリが venv 外 (`/opt/homebrew/bin/pytest` 等) に解決されていること。`which pytest` で確認し、venv 内を指していなければ以下のいずれかで解決:

- `python -m pytest -q` で明示的に venv の Python 経由にする (即席)
- `uv pip install -e ".[dev]"` を再実行して venv 内に `pytest` / `pytest-asyncio` / `pytest-httpx` を入れ直す (恒久)

---

## 結果記録

実施者が下記を埋める:

| 項目 | 値 |
| --- | --- |
| 実施日 | YYYY-MM-DD |
| 実施者 | |
| CodeRouter version | (`coderouter --version` もしくは `/healthz` の version フィールド) |
| Ollama version | (`ollama --version`) |
| OS / Python | (例: macOS 15 / Python 3.12.x) |

| ステップ | 結果 (OK / NG) | メモ |
| --- | --- | --- |
| 1. 起動時 validator |  |  |
| 2-a. 平文 → writing |  |  |
| 2-b. コードフェンス → coding |  |  |
| 2-c. 画像 → multi |  |  |
| 3. Prometheus counter |  |  |
| 4. per-request 上書き |  |  |
| 5. カスタムルール |  |  |
| 6. Claude Code 経由 3 ターン |  |  |

NG があった場合は本手順書とは別に issue / PR を起票し、ここには参照リンクを残す。

---

## 実施ログ

### 2026-04-22 v1.6.0 release verify

| 項目 | 値 |
| --- | --- |
| 実施日 | 2026-04-22 |
| 実施者 | zephel01 (Hideki) |
| CodeRouter version | 1.6.0 (`/healthz` の `version` フィールドで確認) |
| Ollama version | (未記録) |
| OS / Python | macOS / Python 3.12.x |
| Release tag | `v1.6.0` (commit `a6ac84b`) — verify 通過後に `009b2b1` (`fix coderouter/__init__.py`、`importlib.metadata` 経由への切り替え) を main に追積 |

| ステップ | 結果 | メモ |
| --- | --- | --- |
| 1. 起動時 validator | OK | `coderouter-startup` で `profiles: ["coding","writing","multi"]` / `default_profile: "auto"` / `mode_source: "config"` 確認 |
| 2-a. 平文 → writing | OK | `auto-router-fallthrough` / `resolved_profile: "writing"` / `code_fence_ratio: 0.0` / `content_len: 15` / provider `ollama-qwen-7b` |
| 2-b. コードフェンス → coding | OK | `builtin:code-fence-dense` / `code_fence_ratio: 0.714` / `has_image: false` / provider `ollama-qwen-coder-7b` |
| 2-c. 画像 → multi | OK | `builtin:image-attachment` / `has_image: true` / provider `ollama-qwen-vl-7b`。VL モデル初回ロードに 10〜30 秒程度かかる |
| 3. Prometheus counter | OK | `coderouter_requests_total 1` / `coderouter_auto_router_fallthrough_total 0` を step 4 単独で観測 (※ 2-c と step 3 の間で serve 再起動があり、2-a/b/c 分の counter は一度 reset されたため step 4 単独でカウント再計測) |
| 4. per-request 上書き | OK | `body.profile: "coding"` 付きリクエストに `auto-router-*` イベントが **一本も出なかった** → auto slot より手前で確定している証拠。provider は `ollama-qwen-coder-7b` 直行 |
| 5. カスタムルール | OK | `providers.auto-custom.yaml` に切り替え、`Please translate this to French.` で `rule_id: "user:translate-intent"` 発火 / `builtin:*` の rule_id は 0 本 (= 内蔵ルールが完全置換されている証拠) |
| 6. Claude Code 経由 | OK (1 ターンのみ) | option C で平文 1 ターン (`こんにちは、元気ですか`) のみ実施。Claude Code は 1 ターンで 2 リクエスト (system prompt bundle: `content_len=6361`/`code_fence_ratio=0.071`、user msg: `content_len=11`/`code_fence_ratio=0.0`) を投げる構造で、両方とも fall-through で `writing` に着地。コード/画像系は step 2-b/2-c の curl 経路でカバー済みのため省略 |

#### 検出した運用上の留意点

- **cwd の `providers.yaml` が `~/.coderouter/providers.yaml` より優先される** (`coderouter/config/loader.py` の探索順: `--config` > `$CODEROUTER_CONFIG` > `./providers.yaml` > `~/.coderouter/providers.yaml`)。repo 直下に手元の `providers.yaml` を置いたまま `coderouter serve` を走らせると `~/.coderouter/` の auto.yaml が無視される。検証時は `--config examples/providers.auto.yaml` で明示指定するか、repo 直下の `providers.yaml` を一時的に退避するのが確実。手順書 0-D に次の revision で追記する。
- **in-memory counter は serve 再起動で 0 リセットされる**。`/metrics` を観測する際は最後の `coderouter-startup` 以降のリクエスト数とだけ突き合わせる。
- **`ollama-qwen-7b` (writing profile) は日本語プロンプトに対して中国語が混入することがある** (qwen2.5:7b のマルチリンガル癖)。router 側の問題ではなく model 側の特性。日本語用途では system prompt に `Reply in Japanese only.` を仕込むか、`auto_router:` のカスタムルールで和文を `coding` 側 (qwen2.5-coder は日本語出力が安定気味) に振る回避策あり。
- **Anthropic ingress 経由 (Claude Code) では `capability-degraded` / `dropped: ["cache_control"]` が出る**。v0.7 以来の既存仕様 (Anthropic → OpenAI-compat 形式変換で cache_control を落とす) で、auto_router の挙動とは独立。
- **`coderouter --version` が 1.5.0 のままだった問題**: `coderouter/__init__.py` で `__version__ = "1.5.0"` とハードコードされていたのが原因で、v1.6.0 tag 付与後の `009b2b1` で `importlib.metadata.version("coderouter")` 経由に切り替え。以降は `pyproject.toml` の `version` 1 行が single source of truth。

**結論**: v1.6.0 の `auto_router` は仕様通りに動作している。release tag `v1.6.0` は有効。

---

## 付録: 本手順を自動化する場合のメモ

2-a / 2-b / 2-c / 4 は curl + jq で完結するので、シェルスクリプト化して `scripts/verify_auto_router.sh` に置く余地がある。v1.7 以降で CI に乗せる場合:

- VL モデル (`qwen2.5vl:7b`) のロード時間が CI ランナーでは厳しいので、画像テスト (2-c) は skip するかモック provider に差し替える想定
- 2-a / 2-b / 3 / 4 / 5 は既に `tests/test_auto_router.py` で FastAPI TestClient 経由のユニットテストが存在するため、実機確認の目的は「Ollama 実体が応答するか」の回線確認に絞る
- Claude Code 経由 (6) は自動化が困難で、リリースごとの手動 smoke にとどめる
