coderouter-cli 1.8.0__tar.gz → 1.8.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/CHANGELOG.md +133 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/PKG-INFO +1 -1
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/config/loader.py +27 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/data/model-capabilities.yaml +126 -8
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/doctor.py +88 -6
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/docs/hf-ollama-models.md +2 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/docs/troubleshooting.en.md +4 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/docs/troubleshooting.md +80 -1
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/examples/providers.nvidia-nim.yaml +27 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/examples/providers.yaml +26 -7
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/pyproject.toml +1 -1
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_capability_registry.py +46 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_config.py +63 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_doctor.py +168 -1
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/.gitignore +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/LICENSE +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/README.en.md +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/README.md +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/__init__.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/__main__.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/adapters/__init__.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/adapters/anthropic_native.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/adapters/base.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/adapters/openai_compat.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/adapters/registry.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/cli.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/cli_stats.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/config/__init__.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/config/capability_registry.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/config/env_file.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/config/schemas.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/data/__init__.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/doctor_apply.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/env_security.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/errors.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/ingress/__init__.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/ingress/anthropic_routes.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/ingress/app.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/ingress/dashboard_routes.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/ingress/metrics_routes.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/ingress/openai_routes.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/logging.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/metrics/__init__.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/metrics/collector.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/metrics/prometheus.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/output_filters.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/routing/__init__.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/routing/auto_router.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/routing/capability.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/routing/fallback.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/translation/__init__.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/translation/anthropic.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/translation/convert.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/coderouter/translation/tool_repair.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/docs/assets/dashboard-demo.png +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/docs/designs/v1.5-dashboard-mockup.html +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/docs/designs/v1.6-auto-router-verification.md +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/docs/designs/v1.6-auto-router.md +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/docs/free-tier-guide.en.md +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/docs/free-tier-guide.md +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/docs/openrouter-roster/README.md +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/docs/openrouter-roster/latest.json +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/docs/quickstart.en.md +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/docs/quickstart.md +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/docs/retrospectives/v0.4.md +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/docs/retrospectives/v0.5-verify.md +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/docs/retrospectives/v0.5.md +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/docs/retrospectives/v0.6.md +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/docs/retrospectives/v0.7.md +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/docs/retrospectives/v1.0-verify.md +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/docs/retrospectives/v1.0.md +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/docs/security.en.md +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/docs/security.md +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/docs/usage-guide.en.md +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/docs/usage-guide.md +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/docs/when-do-i-need-coderouter.en.md +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/docs/when-do-i-need-coderouter.md +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/examples/.env.example +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/examples/providers.auto-custom.yaml +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/examples/providers.auto.yaml +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/examples/providers.note-2026.yaml +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/scripts/demo_traffic.sh +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/scripts/openrouter_roster_diff.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/scripts/verify_v0_5.sh +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/scripts/verify_v1_0.sh +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/__init__.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/conftest.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_adapter_anthropic.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_auto_router.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_capability.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_capability_degraded_payload.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_claude_code_suitability.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_cli.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_cli_stats.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_dashboard_endpoint.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_doctor_apply.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_env_file.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_env_security.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_errors.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_examples_yaml.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_fallback.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_fallback_anthropic.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_fallback_cache_control.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_fallback_misconfig_warn.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_fallback_paid_gate.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_fallback_thinking.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_ingress_anthropic.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_ingress_profile.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_metrics_collector.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_metrics_endpoint.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_metrics_jsonl.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_metrics_prometheus.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_openai_compat.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_openrouter_roster_diff.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_output_filters.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_output_filters_adapters.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_reasoning_strip.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_setup_sh.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_tool_repair.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_translation_anthropic.py +0 -0
- {coderouter_cli-1.8.0 → coderouter_cli-1.8.2}/tests/test_translation_reverse.py +0 -0
|
@@ -6,6 +6,139 @@ versioning follows [SemVer](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## [v1.8.2] — 2026-04-26 (doctor probe を thinking モデル対応に — Gemma 4 偽陽性の解消)
|
|
10
|
+
|
|
11
|
+
**Theme: v1.8.1 リリース直後の深掘りで `doctor` の `num_ctx` / `streaming` probe が thinking モデルに対して偽陽性 NEEDS_TUNING を出していた事実を発見、probe の `max_tokens` バジェットを reasoning トークン消費分込みで設計し直した patch。**
|
|
12
|
+
|
|
13
|
+
v1.8.1 で `coding` profile primary に置いた Gemma 4 26B の doctor 結果が `tool_calls [OK]` + `num_ctx [NEEDS TUNING]` + `streaming [NEEDS TUNING]` で「中途半端に動く」と判定されていたが、実機で curl 直叩きすると **Ollama OpenAI-compat 経由でも 5K トークンの canary echo-back に成功** することが判明。原因切り分けの結果、Gemma 4 が emit する非標準 `reasoning` フィールドが doctor probe の `max_tokens=32` (num_ctx) / `max_tokens=128` (streaming) を**思考トークンで食い切って `content=""` で `finish_reason='length'`** を返していた偽陽性と確定。実機検証 (M3 Max 64GB / Ollama 0.21.2) で Anthropic 互換 `/v1/messages` 経由 Gemma 4 26B が "Hello." を 2 秒で返すことも確認、**Gemma 4 26B は実用 OK** と最終判定。
|
|
14
|
+
|
|
15
|
+
- Tests: 730 → **733** (+3: thinking provider declaration / registry-based / streaming の 3 件)
|
|
16
|
+
- Runtime deps: 5 → 5 (20 sub-release 連続据え置き)
|
|
17
|
+
- Backward compat: 完全互換、`providers.yaml` / `~/.coderouter/model-capabilities.yaml` 編集不要
|
|
18
|
+
|
|
19
|
+
### Changes
|
|
20
|
+
|
|
21
|
+
#### Doctor probe: thinking モデル対応バジェット選択
|
|
22
|
+
|
|
23
|
+
- **`coderouter/doctor.py`**: `_probe_num_ctx` / `_probe_streaming` の `max_tokens` を thinking 検出付きの動的選択に変更。新 helper `_is_reasoning_model(provider, resolved)` が provider declaration / registry resolved の両方から `thinking` / `reasoning_passthrough` の真偽を見て、reasoning モデルのときだけ大きいバジェットを選ぶ。
|
|
24
|
+
- `_NUM_CTX_PROBE_MAX_TOKENS_DEFAULT = 256` (旧 32)、`_NUM_CTX_PROBE_MAX_TOKENS_THINKING = 1024`
|
|
25
|
+
- `_STREAMING_PROBE_MAX_TOKENS_DEFAULT = 512` (旧 128)、`_STREAMING_PROBE_MAX_TOKENS_THINKING = 1024`
|
|
26
|
+
- 非 thinking モデルは natural stop で早期終了するので無駄消費なし、thinking モデルは reasoning trace + 答えが収まる headroom
|
|
27
|
+
|
|
28
|
+
#### Registry: 既知 thinking モデルに `thinking: true` 宣言
|
|
29
|
+
|
|
30
|
+
- **`coderouter/data/model-capabilities.yaml`**: `gemma4:*` / `google/gemma-4*` / `qwen3.6:*` / `qwen/qwen3.6-*` に `thinking: true` を追加。これらは Ollama 経由で `reasoning` フィールドにかなりのトークンを吐く設計と確認済み。registry 経由で渡るので user は `providers.yaml` を触らなくても doctor の thinking バジェットが効く
|
|
31
|
+
- **Qwen3.6 セクションのコメント更新**: v1.8.1 時点で「Ollama silent cap」と書いていた part を「**v1.8.2 で num_ctx / streaming は doctor 偽陽性と判明、tool_calls [NEEDS TUNING] が真の課題として残る**」に整理。`claude_code_suitability` 撤回判断は維持 (Qwen3.6 の tool_calls 不全は thinking 起因ではない別の真の課題)
|
|
32
|
+
|
|
33
|
+
#### Tests
|
|
34
|
+
|
|
35
|
+
- **`tests/test_doctor.py`**: 3 件追加
|
|
36
|
+
- `test_num_ctx_max_tokens_bumped_for_thinking_provider_declaration`: `provider.capabilities.thinking=True` → 1024
|
|
37
|
+
- `test_num_ctx_max_tokens_bumped_when_registry_says_thinking`: provider 宣言なし + registry 宣言あり → 1024
|
|
38
|
+
- `test_streaming_max_tokens_bumped_for_thinking_provider`: streaming probe も同経路で 1024 になる
|
|
39
|
+
- 既存 `test_num_ctx_request_body_merges_extra_body_options` の `max_tokens == 32` assertion を `== 256` に更新 (新 baseline)
|
|
40
|
+
- 既存 `test_streaming_request_body_carries_stream_true_and_merges_extra_body` に `max_tokens == 512` assertion を追加 (streaming baseline)
|
|
41
|
+
|
|
42
|
+
### Why
|
|
43
|
+
|
|
44
|
+
v1.8.1 article 執筆中に「note の流行モデル → ollama pull → 動かない」のうち Gemma 4 だけ `tool_calls [OK]` の **逆転勝利** だったはずが、`num_ctx [NEEDS TUNING]` も出ていて記事として煮え切らない状態だった。深掘りの結果、`/v1/chat/completions` 経由でも options は効く / `ollama ps` で context length 262144 が出る / **でも doctor は失敗** という矛盾を観測。`.choices[0].message.reasoning` フィールドに思考トークンが流れて `max_tokens=32` を消費していた事実を確認、**doctor 側の probe 設計が thinking モデル時代に追いついていない**ことが判明。
|
|
45
|
+
|
|
46
|
+
これは「実機 evidence first」原則 (plan.md §5.4) の更に一段下のメタ教訓:**diagnostic ツール自身も diagnostic され続ける必要がある**。
|
|
47
|
+
|
|
48
|
+
### Migration
|
|
49
|
+
|
|
50
|
+
`pyproject.toml version 1.8.1 → 1.8.2`、`coderouter --version` は 1.8.2 を返す。**手元の `~/.coderouter/providers.yaml` は触らない限り完全に変化なし**。
|
|
51
|
+
|
|
52
|
+
v1.8.1 で Gemma 4 26B を `claude_code_suitability` 抑え目に運用していたユーザーは v1.8.2 で doctor 再実行すると `num_ctx [OK]` + `streaming [OK]` まで通るはず。Qwen3.6 系の `tool_calls [NEEDS TUNING]` は本物 (thinking 起因ではない) なので引き続き coding chain primary には推奨しない。
|
|
53
|
+
|
|
54
|
+
### Files touched
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
M CHANGELOG.md
|
|
58
|
+
M coderouter/data/model-capabilities.yaml
|
|
59
|
+
M coderouter/doctor.py
|
|
60
|
+
M pyproject.toml
|
|
61
|
+
M plan.md
|
|
62
|
+
M docs/troubleshooting.md
|
|
63
|
+
M docs/articles/note-v1-8-1-reality-check.md (or new file v1-8-2)
|
|
64
|
+
M tests/test_doctor.py
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## [v1.8.1] — 2026-04-26 (実機検証反映 patch — mode_aliases 解決 + Gemma 4 第一候補化 + Ollama 既知問題ドキュメント化)
|
|
70
|
+
|
|
71
|
+
**Theme: v1.8.0 出荷直後の実機検証 (M3 Max 32GB / Ollama 0.21.2) で踏んだ問題 3 件を patch で解消。**
|
|
72
|
+
|
|
73
|
+
v1.8.0 の用途別 4 プロファイルが、NIM example yaml ベースで運用しているユーザーで `coderouter serve --mode coding` が **`default_profile 'coding' is not declared in profiles`** エラーで起動失敗する loader bug が判明。あわせて `coding` profile の primary に置いていた Qwen3.6:27b/35b が Ollama 経由で実用厳しい (num_ctx silent cap / tool_calls 0 / streaming 0 chars) ことも実機検証で確認。**「note 記事や HF 評判が高くても Ollama 経由ですぐ動くとは限らない」現実**を troubleshooting.md §4-2 として明文化。
|
|
74
|
+
|
|
75
|
+
- Tests: 729 → **730** (+1: loader の mode_aliases 解決テスト)
|
|
76
|
+
- Runtime deps: 5 → 5 (19 sub-release 連続据え置き)
|
|
77
|
+
- Backward compat: 完全互換、`providers.yaml` 編集不要 (loader が alias 経由で解決)
|
|
78
|
+
|
|
79
|
+
### Changes
|
|
80
|
+
|
|
81
|
+
#### Bug fixes (実機検証で踏んだもの)
|
|
82
|
+
|
|
83
|
+
- **`coderouter/config/loader.py`**: `CODEROUTER_MODE` env (= `--mode` CLI) が **`mode_aliases` を解決せず直接 `default_profile` に代入** していた v0.6-A の素朴実装を修正。runtime の `X-CodeRouter-Mode` ヘッダ (v0.6-D) は alias 解決していたので、startup と runtime で semantic 非対称だった。v1.8.1 で env_mode を `mode_aliases` 経由で解決してから `default_profile` 代入する流れに揃え、両者を symmetric に。これで `cr serve --mode coding` が NIM example yaml (profiles=`[claude-code-nim, ...]`、mode_aliases=`{coding: claude-code-nim}`) でも validation エラーにならず起動する
|
|
84
|
+
- **`examples/providers.nvidia-nim.yaml`**: v1.8.0 で main `providers.yaml` に追加した `mode_aliases` (default/coding/general/multi/reasoning/fast/cheap/think/vision) を NIM example yaml にも追加。NIM ユーザーも `--mode coding|general|reasoning|multi` を canonical な短縮 alias として使えるように
|
|
85
|
+
|
|
86
|
+
#### `coding` profile primary を実機検証反映に調整
|
|
87
|
+
|
|
88
|
+
- **`examples/providers.yaml`**: `coding` profile の providers リスト先頭を Qwen3.6:35b/27b → **`ollama-qwen-coder-14b` / `ollama-gemma4-26b` / `ollama-qwen-coder-7b` / `ollama-qwen3-coder-30b`** の順に変更。Qwen3.6 系は末尾退避線にコメントアウトで降格 (LM/llama.cpp が後日対応強化されたら primary に戻す候補として残置)。順序原則「枯れて確実に動くもの」を上に、note 推奨の新しいものは安定確認後に昇格、を反映
|
|
89
|
+
- **`coderouter/data/model-capabilities.yaml`**: `qwen3.6:*` / `qwen/qwen3.6-*` の `claude_code_suitability: ok` を**撤回**。v1.7-B 追加時は note 記事の伝聞ベースで先回り宣言していたが、v1.8.1 実機検証で num_ctx / tool_calls / streaming すべて NEEDS_TUNING 確認、確証ない以上 `tools` 宣言だけ残して suitability は出さない方針に。実機で動いた人は `~/.coderouter/model-capabilities.yaml` で `claude_code_suitability: ok` を user-side override 可能 (registry の first-match-per-flag walk が user → bundled の順序なので)
|
|
90
|
+
|
|
91
|
+
#### Documentation: 実機 Ollama 運用の Known Issues 追加
|
|
92
|
+
|
|
93
|
+
- **`docs/troubleshooting.md` §4-2 新設「ローカル Ollama 経由で踏みやすい既知問題」**:
|
|
94
|
+
- **§4-2-A**: Qwen3.6:27b/35b が Ollama 0.21.2 経由で実用厳しい (num_ctx silent cap / tool_calls 0 / streaming 0)、`/no_think` でも改善せず。回避は Gemma 4 / Qwen2.5-Coder を上位に
|
|
95
|
+
- **§4-2-B**: Qwen3.5 系 HF 蒸留モデル (Qwopus3.5 等) は llama.cpp が `qwen35` architecture (hybrid Transformer-SSM) 未対応で `unable to load model` 500 エラー。フレームワーク本体の対応待ち
|
|
96
|
+
- **§4-2-C**: Gemma 4 26B が無加工で tool_calls OK 確認、note 記事「日常の王者」評価が裏付け
|
|
97
|
+
- **§4-2-D**: ベスト実践「枯れたモデル + 観測ツール (doctor)」、HF で見つけた新モデルは `ollama run` → server log で `unknown model architecture` 確認、出たら今は諦め
|
|
98
|
+
|
|
99
|
+
### Why
|
|
100
|
+
|
|
101
|
+
v1.8.0 出荷で「用途別 4 プロファイルで `--mode coding` が使える」と謳ったが、NIM example yaml ベースのユーザーが踏むことが分かった loader bug は**最初の実プロンプト到達前に validation で死ぬ**ので最重要修正。あわせて、v1.8.0 example の primary に置いていた Qwen3.6 系列が実機で 3 つの probe NEEDS_TUNING を出すこと、Qwen3.5 ベース HF 蒸留が llama.cpp 未対応であることは、**「先回り実装より実機 evidence」原則** (plan.md §5.4) を再確認させる結果。
|
|
102
|
+
|
|
103
|
+
### Migration
|
|
104
|
+
|
|
105
|
+
`pyproject.toml version 1.8.0 → 1.8.1`、`coderouter --version` は 1.8.1 を返す。**手元の `~/.coderouter/providers.yaml` は触らない限り完全に変化なし**。
|
|
106
|
+
|
|
107
|
+
NIM example ベースで `cr serve --mode coding` が動かなかったユーザーは、
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# 最新 example をコピー (v1.8.1 で mode_aliases 追加済み)
|
|
111
|
+
cp examples/providers.nvidia-nim.yaml ~/.coderouter/providers.yaml
|
|
112
|
+
# あるいは手で mode_aliases セクションを既存ファイルに追加
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
または、`cr` をローカル開発版から再 install:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
uv tool install --reinstall --force --from /path/to/CodeRouter coderouter-cli --with ruamel.yaml
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Real-machine verification
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
$ pytest -q
|
|
125
|
+
730 passed, 1 skipped in 1.86s
|
|
126
|
+
|
|
127
|
+
$ ruff check coderouter/ tests/
|
|
128
|
+
All checks passed!
|
|
129
|
+
|
|
130
|
+
$ cr serve --port 8088 --mode coding # NIM example yaml でも起動成功
|
|
131
|
+
$ cr doctor --check-model ollama-gemma4-26b --apply # tool_calls OK 確認済み
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Out of scope / 次回送り
|
|
135
|
+
|
|
136
|
+
- Qwen3.5 系 HF 蒸留 (Qwopus / 類似): llama.cpp が `qwen35` architecture を実装したら再評価
|
|
137
|
+
- Qwen3.6:27b/35b の Ollama 経由動作: Ollama / llama.cpp 側の改善があれば再評価、`claude_code_suitability` を再付与の検討
|
|
138
|
+
- v1.7-C 候補 (network audit / launcher / 起動時 update check) は引き続き需要待ち
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
9
142
|
## [v1.8.0] — 2026-04-26 (用途別 4 プロファイル + GLM/Gemma 4/Qwen3.6 公式化 + apply 自動化)
|
|
10
143
|
|
|
11
144
|
**Theme: 「Claude Code で意味合いがズレない代替モデル」を operator に渡す minor。** plan.md §11.B (v1.7-B umbrella) を 6 タスクで一気に消化:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coderouter-cli
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.2
|
|
4
4
|
Summary: Local-first, free-first, fallback-built-in LLM router. Claude Code / OpenAI compatible.
|
|
5
5
|
Project-URL: Homepage, https://github.com/zephel01/CodeRouter
|
|
6
6
|
Project-URL: Repository, https://github.com/zephel01/CodeRouter
|
|
@@ -49,8 +49,35 @@ def load_config(path: str | os.PathLike[str] | None = None) -> CodeRouterConfig:
|
|
|
49
49
|
# fail can be rescued by an explicit env-set mode, and (b) the model-
|
|
50
50
|
# validator's "default_profile must exist in profiles" check applies to the
|
|
51
51
|
# *effective* mode the engine will see, not the pre-override YAML value.
|
|
52
|
+
#
|
|
53
|
+
# v1.8.0+: also resolve env_mode through ``mode_aliases`` before assigning,
|
|
54
|
+
# so that startup-time ``--mode coding`` (env CODEROUTER_MODE=coding)
|
|
55
|
+
# behaves symmetrically with the runtime ``X-CodeRouter-Mode: coding``
|
|
56
|
+
# header — both should accept short intent names like ``coding`` /
|
|
57
|
+
# ``general`` / ``reasoning`` and resolve them to the underlying profile
|
|
58
|
+
# (e.g. ``claude-code-nim`` in providers.nvidia-nim.yaml). Without this,
|
|
59
|
+
# users on the NIM example yaml hit
|
|
60
|
+
# "default_profile 'coding' is not declared in profiles:
|
|
61
|
+
# known=['claude-code-nim', ...]"
|
|
62
|
+
# because mode_aliases only fired at request time, not at startup.
|
|
52
63
|
env_mode = os.environ.get("CODEROUTER_MODE", "").strip()
|
|
53
64
|
if env_mode:
|
|
65
|
+
# Pre-validation alias resolution: if env_mode isn't directly a
|
|
66
|
+
# profile name but matches an entry in raw["mode_aliases"], swap it
|
|
67
|
+
# for the underlying profile name. This avoids forcing every example
|
|
68
|
+
# yaml to mirror the v1.8.0 four-profile names (multi/coding/general
|
|
69
|
+
# /reasoning) just to accept the canonical short --mode flags.
|
|
70
|
+
raw_profiles = raw.get("profiles", []) or []
|
|
71
|
+
profile_names = {
|
|
72
|
+
p.get("name") for p in raw_profiles if isinstance(p, dict)
|
|
73
|
+
}
|
|
74
|
+
raw_aliases = raw.get("mode_aliases", {}) or {}
|
|
75
|
+
if (
|
|
76
|
+
env_mode not in profile_names
|
|
77
|
+
and isinstance(raw_aliases, dict)
|
|
78
|
+
and env_mode in raw_aliases
|
|
79
|
+
):
|
|
80
|
+
env_mode = raw_aliases[env_mode]
|
|
54
81
|
raw["default_profile"] = env_mode
|
|
55
82
|
|
|
56
83
|
config = CodeRouterConfig.model_validate(raw)
|
|
@@ -168,45 +168,67 @@ rules:
|
|
|
168
168
|
claude_code_suitability: ok
|
|
169
169
|
|
|
170
170
|
# ------------------------------------------------------------------
|
|
171
|
-
# Qwen3.6 family (v1.7-B
|
|
171
|
+
# Qwen3.6 family (v1.7-B 追加、v1.8.1 で suitability 撤回)
|
|
172
172
|
#
|
|
173
173
|
# 2026-04 リリースの Qwen3.6 シリーズ。Ollama 公式 tag は
|
|
174
|
-
# qwen3.6:27b / qwen3.6:35b
|
|
175
|
-
# 256K context
|
|
176
|
-
# 「Claude Code 代替として最高」「local champ
|
|
177
|
-
#
|
|
174
|
+
# qwen3.6:27b / qwen3.6:35b、metadata 上は tools+vision+thinking 対応、
|
|
175
|
+
# 256K context を宣言。note 記事 (r/LocalLLaMA 2026-04 Megathread) で
|
|
176
|
+
# 「Claude Code 代替として最高」「local champ」と評価されている。
|
|
177
|
+
#
|
|
178
|
+
# ただし v1.8.0 までで `claude_code_suitability: ok` を declare していた
|
|
179
|
+
# のは note 記事の伝聞ベースの先回り宣言で、v1.8.1 〜 v1.8.2
|
|
180
|
+
# (2026-04-26) の実機検証 (M3 Max 64GB / Ollama 0.21.2) で:
|
|
181
|
+
# - num_ctx と streaming の NEEDS_TUNING は v1.8.2 で thinking モデル
|
|
182
|
+
# 用 probe バジェット拡大により偽陽性と判明 (doctor 側の課題)
|
|
183
|
+
# - tool_calls probe が native tool_calls / 修復可能 JSON のいずれも
|
|
184
|
+
# 返さない真の課題が残る (Qwen3.6 系の Ollama 経由 tool 仕様未成熟)
|
|
185
|
+
# tool_calls 不全が解消されるまで `claude_code_suitability` は撤回。
|
|
186
|
+
# 実機で動いたユーザーは `~/.coderouter/model-capabilities.yaml` で
|
|
187
|
+
# `claude_code_suitability: ok` を上書きできる。
|
|
178
188
|
# ------------------------------------------------------------------
|
|
179
189
|
|
|
190
|
+
# v1.8.2: thinking: true は doctor probe (num_ctx / streaming) が reasoning
|
|
191
|
+
# トークン消費分の max_tokens 余裕を確保するためのヒント。Qwen3 系は
|
|
192
|
+
# /think モードで thinking トークンを吐く設計なので true 宣言。
|
|
180
193
|
- match: "qwen3.6:*"
|
|
181
194
|
kind: openai_compat
|
|
182
195
|
capabilities:
|
|
183
196
|
tools: true
|
|
184
|
-
|
|
197
|
+
thinking: true
|
|
185
198
|
|
|
186
199
|
- match: "qwen/qwen3.6-*"
|
|
187
200
|
kind: openai_compat
|
|
188
201
|
capabilities:
|
|
189
202
|
tools: true
|
|
190
|
-
|
|
203
|
+
thinking: true
|
|
191
204
|
|
|
192
205
|
# ------------------------------------------------------------------
|
|
193
|
-
# Gemma 4 family (v1.7-B
|
|
206
|
+
# Gemma 4 family (v1.7-B 追加、v1.8.2 で thinking: true 宣言)
|
|
194
207
|
#
|
|
195
208
|
# Google 公式 Gemma 4。Ollama 公式 tag は gemma4:e2b / e4b / 26b / 31b、
|
|
196
209
|
# 全 variant が tools+vision+thinking 対応、E2B/E4B は audio もサポート。
|
|
197
210
|
# MoE (26b は active 3.8B / total 25.2B)。note 記事で「日常・バランスの
|
|
198
211
|
# 王者」と評価。Claude Haiku 互換性に近い簡潔な応答スタイル。
|
|
212
|
+
#
|
|
213
|
+
# v1.8.2 (2026-04-26): 実機検証 (M3 Max 64GB / Ollama 0.21.2 / gemma4:26b)
|
|
214
|
+
# で `reasoning` フィールドにかなりの量のトークンを吐く thinking モデル
|
|
215
|
+
# と確認。doctor probe の max_tokens=32 / 128 が thinking トークンに
|
|
216
|
+
# 食い切られて偽陽性 NEEDS_TUNING を出していた。registry で
|
|
217
|
+
# `thinking: true` を宣言すると doctor が probe バジェットを 1024 まで
|
|
218
|
+
# 引き上げて偽陽性を回避する。
|
|
199
219
|
# ------------------------------------------------------------------
|
|
200
220
|
|
|
201
221
|
- match: "gemma4:*"
|
|
202
222
|
kind: openai_compat
|
|
203
223
|
capabilities:
|
|
204
224
|
tools: true
|
|
225
|
+
thinking: true
|
|
205
226
|
|
|
206
227
|
- match: "google/gemma-4*"
|
|
207
228
|
kind: openai_compat
|
|
208
229
|
capabilities:
|
|
209
230
|
tools: true
|
|
231
|
+
thinking: true
|
|
210
232
|
|
|
211
233
|
# ------------------------------------------------------------------
|
|
212
234
|
# GLM family (Z.AI / Zhipu AI、v1.7-B 追加)
|
|
@@ -233,3 +255,99 @@ rules:
|
|
|
233
255
|
kind: openai_compat
|
|
234
256
|
capabilities:
|
|
235
257
|
tools: true
|
|
258
|
+
|
|
259
|
+
# ------------------------------------------------------------------
|
|
260
|
+
# Kimi K2 family (Moonshot AI、v1.8.0 追加)
|
|
261
|
+
#
|
|
262
|
+
# NVIDIA NIM 経由で実機検証済み (2026-04-23) の tool-capable モデル。
|
|
263
|
+
# examples/providers.nvidia-nim.yaml の `nim-kimi-k2` / `nim-kimi-k2-thinking`
|
|
264
|
+
# で運用実績あり。Unsloth tool-calling guide にも tool calling 対応モデル
|
|
265
|
+
# として掲載 (Kimi K2.5 / K2 Thinking)。providers.yaml 側で個別の
|
|
266
|
+
# `capabilities.tools: true` 宣言を省略可能にするのが目的。
|
|
267
|
+
# ------------------------------------------------------------------
|
|
268
|
+
|
|
269
|
+
- match: "moonshotai/kimi-k2*"
|
|
270
|
+
kind: openai_compat
|
|
271
|
+
capabilities:
|
|
272
|
+
tools: true
|
|
273
|
+
|
|
274
|
+
- match: "moonshotai/Kimi-K2*"
|
|
275
|
+
kind: openai_compat
|
|
276
|
+
capabilities:
|
|
277
|
+
tools: true
|
|
278
|
+
|
|
279
|
+
# ------------------------------------------------------------------
|
|
280
|
+
# gpt-oss family (OpenAI 117B MoE オープンウェイト、v1.8.0 追加)
|
|
281
|
+
#
|
|
282
|
+
# OpenRouter free 経由で実機検証済み (`openai/gpt-oss-120b:free` を
|
|
283
|
+
# examples/providers.yaml の `openrouter-gpt-oss-free` で運用)。
|
|
284
|
+
# native tool calling 設計、131K context、Unsloth tool-calling guide
|
|
285
|
+
# にも tool calling 対応モデルとして掲載 (gpt-oss)。
|
|
286
|
+
# ------------------------------------------------------------------
|
|
287
|
+
|
|
288
|
+
- match: "openai/gpt-oss-*"
|
|
289
|
+
kind: openai_compat
|
|
290
|
+
capabilities:
|
|
291
|
+
tools: true
|
|
292
|
+
|
|
293
|
+
- match: "gpt-oss-*"
|
|
294
|
+
kind: openai_compat
|
|
295
|
+
capabilities:
|
|
296
|
+
tools: true
|
|
297
|
+
|
|
298
|
+
# ------------------------------------------------------------------
|
|
299
|
+
# 先回り宣言 family (Unsloth tool-calling guide 掲載、v1.8.0 追加)
|
|
300
|
+
#
|
|
301
|
+
# Unsloth のローカル LLM tool-calling ガイド
|
|
302
|
+
# (https://unsloth.ai/docs/jp/ji-ben/tool-calling-guide-for-local-llms)
|
|
303
|
+
# で tool-calling 対応モデルとして掲載されているが、CodeRouter 側で
|
|
304
|
+
# 実機検証は未実施。tools=true の事前宣言だけ入れて、providers.yaml で
|
|
305
|
+
# これらを使う際の `capabilities.tools: true` 明示宣言を不要にする。
|
|
306
|
+
# claude_code_suitability は実機検証後に追加判断 — それまでは "意見なし"。
|
|
307
|
+
# 不具合があれば user-side の `~/.coderouter/model-capabilities.yaml` で
|
|
308
|
+
# `tools: false` を declare して上書き可能 (first-match-per-flag)。
|
|
309
|
+
# ------------------------------------------------------------------
|
|
310
|
+
|
|
311
|
+
# DeepSeek-V3.x — DeepSeek-AI の主力 (V3.1 / V3.2 等)
|
|
312
|
+
- match: "deepseek-ai/DeepSeek-V3*"
|
|
313
|
+
kind: openai_compat
|
|
314
|
+
capabilities:
|
|
315
|
+
tools: true
|
|
316
|
+
|
|
317
|
+
- match: "deepseek/deepseek-v3*"
|
|
318
|
+
kind: openai_compat
|
|
319
|
+
capabilities:
|
|
320
|
+
tools: true
|
|
321
|
+
|
|
322
|
+
# MiniMax — MiniMaxAI の MoE 系
|
|
323
|
+
- match: "MiniMaxAI/MiniMax-*"
|
|
324
|
+
kind: openai_compat
|
|
325
|
+
capabilities:
|
|
326
|
+
tools: true
|
|
327
|
+
|
|
328
|
+
- match: "minimax/minimax-*"
|
|
329
|
+
kind: openai_compat
|
|
330
|
+
capabilities:
|
|
331
|
+
tools: true
|
|
332
|
+
|
|
333
|
+
# NVIDIA Nemotron 3 — Nano 系の小型モデル
|
|
334
|
+
- match: "nvidia/nemotron-3-*"
|
|
335
|
+
kind: openai_compat
|
|
336
|
+
capabilities:
|
|
337
|
+
tools: true
|
|
338
|
+
|
|
339
|
+
- match: "nvidia/Nemotron-3-*"
|
|
340
|
+
kind: openai_compat
|
|
341
|
+
capabilities:
|
|
342
|
+
tools: true
|
|
343
|
+
|
|
344
|
+
# Devstral 2 — Mistral AI の coding 特化 fine-tune
|
|
345
|
+
- match: "mistralai/Devstral-*"
|
|
346
|
+
kind: openai_compat
|
|
347
|
+
capabilities:
|
|
348
|
+
tools: true
|
|
349
|
+
|
|
350
|
+
- match: "mistral/devstral*"
|
|
351
|
+
kind: openai_compat
|
|
352
|
+
capabilities:
|
|
353
|
+
tools: true
|
|
@@ -433,6 +433,31 @@ _STREAMING_PROBE_USER_PROMPT = (
|
|
|
433
433
|
# truncated". "1\n2\n...\n30" is ~80 chars; 40 chars covers the halfway
|
|
434
434
|
# mark (1..20) which is already obviously-truncated territory.
|
|
435
435
|
_STREAMING_PROBE_MIN_EXPECTED_CHARS = 40
|
|
436
|
+
|
|
437
|
+
# v1.8.2: probe response budgets.
|
|
438
|
+
#
|
|
439
|
+
# Both num_ctx and streaming probes ask the model for a *short* answer
|
|
440
|
+
# (the canary token / "1..30"). The original budgets (32 / 128 tokens)
|
|
441
|
+
# assumed a non-thinking model that emits the answer immediately. On a
|
|
442
|
+
# thinking model — Gemma 4 26B, Qwen3.6, gpt-oss, deepseek-r1 — the
|
|
443
|
+
# upstream burns the entire budget on a hidden ``reasoning`` field
|
|
444
|
+
# *before* emitting any visible ``content``, producing a false-positive
|
|
445
|
+
# NEEDS_TUNING (canary missing / 0 chars streamed). Bumping the budget
|
|
446
|
+
# is the cleanest fix: non-thinking models stop early at their natural
|
|
447
|
+
# stop token (no waste), thinking models get headroom for the reasoning
|
|
448
|
+
# trace plus the actual answer.
|
|
449
|
+
#
|
|
450
|
+
# Numbers picked from the v1.8.1 reality-check session
|
|
451
|
+
# (docs/articles/note-v1-8-1-reality-check.md):
|
|
452
|
+
# * Gemma 4 26B reasoning prefix observed at ~150-300 tokens before
|
|
453
|
+
# content starts → 1024 covers reasoning + 30-line count comfortably.
|
|
454
|
+
# * Non-thinking baseline kept conservative-but-non-tight (256/512) to
|
|
455
|
+
# absorb stylistic preambles ("Sure, the answer is...") without
|
|
456
|
+
# burning extra cloud quota when the operator probes a paid endpoint.
|
|
457
|
+
_NUM_CTX_PROBE_MAX_TOKENS_DEFAULT = 256
|
|
458
|
+
_NUM_CTX_PROBE_MAX_TOKENS_THINKING = 1024
|
|
459
|
+
_STREAMING_PROBE_MAX_TOKENS_DEFAULT = 512
|
|
460
|
+
_STREAMING_PROBE_MAX_TOKENS_THINKING = 1024
|
|
436
461
|
# Default ``num_predict`` suggested in the emitted patch. -1 would be
|
|
437
462
|
# optimal (uncapped) but "4096" communicates intent more clearly to
|
|
438
463
|
# operators unfamiliar with Ollama's sentinel value, and covers Claude
|
|
@@ -475,6 +500,42 @@ def _declared_num_ctx(provider: ProviderConfig) -> int | None:
|
|
|
475
500
|
return val if isinstance(val, int) else None
|
|
476
501
|
|
|
477
502
|
|
|
503
|
+
def _is_reasoning_model(
|
|
504
|
+
provider: ProviderConfig, resolved: ResolvedCapabilities
|
|
505
|
+
) -> bool:
|
|
506
|
+
"""v1.8.2: True iff the model is known to emit a hidden reasoning trace.
|
|
507
|
+
|
|
508
|
+
Thinking models (Gemma 4, Qwen3-with-/think, gpt-oss, deepseek-r1,
|
|
509
|
+
Claude Sonnet 4.5+ in extended-thinking mode) burn output tokens on a
|
|
510
|
+
``reasoning`` field before any visible ``content`` is produced. The
|
|
511
|
+
num_ctx / streaming probes use small response budgets that get fully
|
|
512
|
+
consumed by the reasoning prefix, producing a false-positive
|
|
513
|
+
NEEDS_TUNING. Callers use this to choose a generous probe budget.
|
|
514
|
+
|
|
515
|
+
Three signals fire:
|
|
516
|
+
* provider declared ``capabilities.thinking: true`` in providers.yaml
|
|
517
|
+
* provider declared ``capabilities.reasoning_passthrough: true``
|
|
518
|
+
(the operator opted in to passing the raw reasoning to the client,
|
|
519
|
+
which is only meaningful for models that emit it)
|
|
520
|
+
* registry resolved ``thinking: true`` for this (kind, model) pair
|
|
521
|
+
|
|
522
|
+
Conservative bias — when both provider declaration and registry are
|
|
523
|
+
silent, treat as non-reasoning. The probe still completes for thinking
|
|
524
|
+
models in that case (they just hit ``finish_reason='length'`` like
|
|
525
|
+
they did pre-v1.8.2), but at least the new generous default budget
|
|
526
|
+
(256 / 512) gives more headroom than the old 32 / 128.
|
|
527
|
+
"""
|
|
528
|
+
if provider.capabilities.thinking is True:
|
|
529
|
+
return True
|
|
530
|
+
if provider.capabilities.reasoning_passthrough is True:
|
|
531
|
+
return True
|
|
532
|
+
if resolved.thinking is True:
|
|
533
|
+
return True
|
|
534
|
+
if resolved.reasoning_passthrough is True:
|
|
535
|
+
return True
|
|
536
|
+
return False
|
|
537
|
+
|
|
538
|
+
|
|
478
539
|
_PROBE_BASIC_USER_PROMPT = "Reply with exactly the single word: PONG"
|
|
479
540
|
_PROBE_TOOLS_USER_PROMPT = (
|
|
480
541
|
"You have one tool named `echo`. Call it with the argument "
|
|
@@ -617,7 +678,9 @@ def _extract_openai_assistant_choice(
|
|
|
617
678
|
return msg if isinstance(msg, dict) else None
|
|
618
679
|
|
|
619
680
|
|
|
620
|
-
async def _probe_num_ctx(
|
|
681
|
+
async def _probe_num_ctx(
|
|
682
|
+
provider: ProviderConfig, resolved: ResolvedCapabilities
|
|
683
|
+
) -> ProbeResult:
|
|
621
684
|
"""v1.0-B Probe — direct detection of Ollama ``num_ctx`` truncation.
|
|
622
685
|
|
|
623
686
|
Addresses plan.md §9.4 symptom #1 (空応答 / 意味不明応答). Prior to
|
|
@@ -683,11 +746,21 @@ async def _probe_num_ctx(provider: ProviderConfig) -> ProbeResult:
|
|
|
683
746
|
# whatever ``options.num_ctx`` the operator has declared. Request
|
|
684
747
|
# fields win over extra_body, matching the adapter's merge order.
|
|
685
748
|
body: dict[str, Any] = dict(provider.extra_body)
|
|
749
|
+
# v1.8.2: thinking models burn output tokens on a hidden ``reasoning``
|
|
750
|
+
# trace before emitting any ``content``. The pre-v1.8.2 default of 32
|
|
751
|
+
# was tight for any preamble at all; on Gemma 4 26B it caused
|
|
752
|
+
# ``finish_reason='length'`` with content="" before the canary could
|
|
753
|
+
# surface, producing a false-positive NEEDS_TUNING.
|
|
754
|
+
max_tokens = (
|
|
755
|
+
_NUM_CTX_PROBE_MAX_TOKENS_THINKING
|
|
756
|
+
if _is_reasoning_model(provider, resolved)
|
|
757
|
+
else _NUM_CTX_PROBE_MAX_TOKENS_DEFAULT
|
|
758
|
+
)
|
|
686
759
|
body.update(
|
|
687
760
|
{
|
|
688
761
|
"model": provider.model,
|
|
689
762
|
"messages": [{"role": "user", "content": user_prompt}],
|
|
690
|
-
"max_tokens":
|
|
763
|
+
"max_tokens": max_tokens,
|
|
691
764
|
"temperature": 0,
|
|
692
765
|
}
|
|
693
766
|
)
|
|
@@ -799,7 +872,9 @@ async def _probe_num_ctx(provider: ProviderConfig) -> ProbeResult:
|
|
|
799
872
|
)
|
|
800
873
|
|
|
801
874
|
|
|
802
|
-
async def _probe_streaming(
|
|
875
|
+
async def _probe_streaming(
|
|
876
|
+
provider: ProviderConfig, resolved: ResolvedCapabilities
|
|
877
|
+
) -> ProbeResult:
|
|
803
878
|
"""v1.0-C Probe — streaming completion path integrity.
|
|
804
879
|
|
|
805
880
|
Addresses plan.md §9.4 symptom #1 from the **output** side. The v1.0-B
|
|
@@ -868,11 +943,18 @@ async def _probe_streaming(provider: ProviderConfig) -> ProbeResult:
|
|
|
868
943
|
# probing. Top-level probe fields win on collision, matching adapter
|
|
869
944
|
# merge order.
|
|
870
945
|
body: dict[str, Any] = dict(provider.extra_body)
|
|
946
|
+
# v1.8.2: same thinking-model rationale as num_ctx probe — give
|
|
947
|
+
# reasoning a budget so the visible content has a chance to surface.
|
|
948
|
+
max_tokens = (
|
|
949
|
+
_STREAMING_PROBE_MAX_TOKENS_THINKING
|
|
950
|
+
if _is_reasoning_model(provider, resolved)
|
|
951
|
+
else _STREAMING_PROBE_MAX_TOKENS_DEFAULT
|
|
952
|
+
)
|
|
871
953
|
body.update(
|
|
872
954
|
{
|
|
873
955
|
"model": provider.model,
|
|
874
956
|
"messages": [{"role": "user", "content": _STREAMING_PROBE_USER_PROMPT}],
|
|
875
|
-
"max_tokens":
|
|
957
|
+
"max_tokens": max_tokens,
|
|
876
958
|
"temperature": 0,
|
|
877
959
|
"stream": True,
|
|
878
960
|
}
|
|
@@ -1506,11 +1588,11 @@ async def check_model(
|
|
|
1506
1588
|
# declaration probes (tool_calls / thinking / reasoning-leak) should
|
|
1507
1589
|
# dominate the report — streaming is the output-side sibling of
|
|
1508
1590
|
# num_ctx and its NEEDS_TUNING verdict is orthogonal to the others.
|
|
1509
|
-
report.results.append(await _probe_num_ctx(provider))
|
|
1591
|
+
report.results.append(await _probe_num_ctx(provider, resolved))
|
|
1510
1592
|
report.results.append(await _probe_tool_calls(provider, resolved))
|
|
1511
1593
|
report.results.append(await _probe_thinking(provider, resolved))
|
|
1512
1594
|
report.results.append(await _probe_reasoning_leak(provider, resolved))
|
|
1513
|
-
report.results.append(await _probe_streaming(provider))
|
|
1595
|
+
report.results.append(await _probe_streaming(provider, resolved))
|
|
1514
1596
|
return report
|
|
1515
1597
|
|
|
1516
1598
|
|
|
@@ -237,6 +237,8 @@ ollama cp hf.co/unsloth/Qwen3-Coder-30B-A3B-Instruct-GGUF:Q4_K_M qwen3-coder:30b
|
|
|
237
237
|
|
|
238
238
|
- Ollama HF integration: <https://huggingface.co/docs/hub/en/ollama>
|
|
239
239
|
- Unsloth (高速量子化版の代表的アップローダー): <https://huggingface.co/unsloth>
|
|
240
|
+
- **Unsloth: Tool calling guide for local LLMs (日本語)**: <https://unsloth.ai/docs/jp/ji-ben/tool-calling-guide-for-local-llms>
|
|
241
|
+
— Qwen / Llama / Gemma など local LLM で tool-call が動かない/壊れる原因と対策をモデル別に整理。CodeRouter で `tool_calls: NEEDS_TUNING` が出たときの背景理解にちょうど良い。
|
|
240
242
|
- bartowski (品質重視の量子化版): <https://huggingface.co/bartowski>
|
|
241
243
|
- Qwen3-Coder (Alibaba 公式): <https://huggingface.co/collections/Qwen/qwen3-coder>
|
|
242
244
|
- Gemma 4 (Google 公式): <https://huggingface.co/collections/google/gemma-4>
|
|
@@ -187,6 +187,8 @@ coderouter doctor --check-model <provider>
|
|
|
187
187
|
|
|
188
188
|
With `tools: false` the chain moves on to the next provider when a tool-heavy request arrives. Pair this with a stronger model later in the chain (e.g. qwen2.5-coder:14b or a cloud fallback).
|
|
189
189
|
|
|
190
|
+
> **Further reading**: Unsloth's [Tool calling guide for local LLMs](https://unsloth.ai/docs/jp/ji-ben/tool-calling-guide-for-local-llms) (Japanese) walks through per-model tool-call quirks (Qwen / Llama / Gemma) — a useful background read when CodeRouter's doctor flags `tool_calls: NEEDS_TUNING` and you want to understand why.
|
|
191
|
+
|
|
190
192
|
**3. `<think>...</think>` tags leak into the UI.** Qwen3-distilled models, DeepSeek-R1 distills, and some HF GGUF variants emit chain-of-thought inside the regular content channel (not an Anthropic `thinking` block). The tags land in Claude Code's terminal verbatim.
|
|
191
193
|
|
|
192
194
|
```bash
|
|
@@ -278,6 +280,8 @@ profiles:
|
|
|
278
280
|
|
|
279
281
|
`examples/providers.nvidia-nim.yaml` (v1.6.2 onwards) ships with the Qwen-first ordering. Llama-3.3-70B works fine for many things, but for Claude Code chat traffic specifically, Qwen3-Coder-480B / Kimi-K2 are operationally more stable.
|
|
280
282
|
|
|
283
|
+
> **Background on per-model tool-call behavior**: Llama-3.3-70B's tendency to rewrite plain text into tool calls comes from its aggressive agentic-tuning RLHF signal interacting with Claude Code's system prompt. Unsloth's [Tool calling guide for local LLMs](https://unsloth.ai/docs/jp/ji-ben/tool-calling-guide-for-local-llms) (Japanese) covers this and other model-specific quirks well — useful background for the v1.8.0 `claude_code_suitability` heuristic.
|
|
284
|
+
|
|
281
285
|
### 4-2. `UserPromptSubmit hook error` (third-party Claude Code plugins)
|
|
282
286
|
|
|
283
287
|
```
|
|
@@ -187,6 +187,8 @@ coderouter doctor --check-model <provider>
|
|
|
187
187
|
|
|
188
188
|
`tools: false` にすると、ツール要求リクエスト到来時にチェーンは次のプロバイダに進みます。強いモデル (qwen2.5-coder:14b やクラウドフォールバック) と組み合わせて使ってください。
|
|
189
189
|
|
|
190
|
+
> **補足資料**: モデル別の tool-call 対応状況や、量子化 / システムプロンプト / chat template の落とし穴は Unsloth の [Tool calling guide for local LLMs (日本語)](https://unsloth.ai/docs/jp/ji-ben/tool-calling-guide-for-local-llms) にきれいにまとまっています。Qwen / Llama / Gemma 各系列で tool-call が動かない原因を踏み込んで知りたい人向け。
|
|
191
|
+
|
|
190
192
|
**3. UI に `<think>...</think>` タグが漏れる。** Qwen3 蒸留モデル、DeepSeek-R1 蒸留、一部の HF GGUF 変種は chain-of-thought を Anthropic の `thinking` ブロックではなく通常のコンテンツチャネルに吐きます。タグが Claude Code のターミナルにそのまま出ます。
|
|
191
193
|
|
|
192
194
|
```bash
|
|
@@ -278,7 +280,84 @@ profiles:
|
|
|
278
280
|
|
|
279
281
|
`examples/providers.nvidia-nim.yaml` (v1.6.2 以降) は Qwen-first の順序になっています。Llama-3.3-70B 自体は動作確認済みですが、Claude Code 単独の対話用途では Qwen3-Coder-480B / Kimi-K2 のほうが運用上は安定します。
|
|
280
282
|
|
|
281
|
-
|
|
283
|
+
> **モデル別 tool-call 挙動の深掘り**: Llama-3.3-70B 系の「自然文を tool 呼び出しに変換しがち」性質は、agentic tuning の RLHF signal とシステムプロンプトの相性に起因します。各モデルの傾向と回避策は Unsloth の [Tool calling guide for local LLMs (日本語)](https://unsloth.ai/docs/jp/ji-ben/tool-calling-guide-for-local-llms) が読みやすく、CodeRouter v1.8.0 で導入した `claude_code_suitability` 判定の背景理解にも役立ちます。
|
|
284
|
+
|
|
285
|
+
### 4-2. ローカル Ollama 経由で踏みやすい既知問題 (v1.8.1 追記、v1.8.2 改訂)
|
|
286
|
+
|
|
287
|
+
2026-04-26 の実機検証 (M3 Max 64GB / Ollama 0.21.2 / CodeRouter v1.8.0 → v1.8.2) で、**note 記事や HF で評価が高いモデルでも Ollama 経由では動かないケース**が判明したのでまとめます。
|
|
288
|
+
|
|
289
|
+
> **v1.8.2 重要更新**: 当初 v1.8.1 で「Qwen3.6 / Gemma 4 ともに num_ctx silent cap」「streaming 0 chars 打ち切り」と判定していた問題は、深掘りの結果 **doctor の `num_ctx` / `streaming` probe が thinking モデルの reasoning トークン消費分を見ていない `max_tokens=32` / `128` バジェットで偽陽性 NEEDS_TUNING を出していた** ことが判明。v1.8.2 で probe バジェットを reasoning モデル時に 1024 まで拡大、registry で `gemma4:*` / `qwen3.6:*` に `thinking: true` 宣言を追加。**Gemma 4 26B は実機で完全動作確定** (`/v1/messages` Anthropic 互換で "Hello." を 2 秒応答)、**Qwen3.6 系の `tool_calls [NEEDS TUNING]` だけが真の課題として残る** (thinking 起因とは別の Ollama tool 仕様未成熟)。
|
|
290
|
+
|
|
291
|
+
#### 4-2-A. **Qwen3.6:27b / 35b** が Claude Code で実用厳しい
|
|
292
|
+
|
|
293
|
+
v1.8.2 の偽陽性除去後、`coderouter doctor --check-model ollama-qwen3-6-27b` の結果:
|
|
294
|
+
|
|
295
|
+
| Probe | 結果 | 症状 |
|
|
296
|
+
|---|---|---|
|
|
297
|
+
| auth+basic-chat | OK | 短い chat なら動く |
|
|
298
|
+
| num_ctx | OK or NEEDS_TUNING (model依存) | thinking バジェット 1024 で偽陽性は解消 |
|
|
299
|
+
| **tool_calls** | **NEEDS_TUNING** ← 残る真の課題 | native tool_calls / 修復可能 JSON のいずれも返さず |
|
|
300
|
+
| streaming | OK or NEEDS_TUNING | thinking バジェット 1024 で偽陽性は解消 |
|
|
301
|
+
|
|
302
|
+
`/no_think` を `append_system_prompt` に入れても tool_calls は改善せず。Ollama 0.21.2 / llama.cpp 側の Qwen3.6 family の **tool 仕様** がまだ完全でない (chat template / tool スキーマ整合) 可能性が高い。
|
|
303
|
+
|
|
304
|
+
**回避**: `claude-code-nim` profile の primary に Qwen3.6 を置かず、**Gemma 4 26B または Qwen2.5-Coder 14b を上位に**。bundled `model-capabilities.yaml` も v1.8.1 で `qwen3.6:*` の `claude_code_suitability: ok` を撤回 (declaration 過信の例)、v1.8.2 で `thinking: true` 追加 (doctor probe 偽陽性除去のため)。
|
|
305
|
+
|
|
306
|
+
#### 4-2-B. **Qwen3.5 系の HF 蒸留モデル** (Qwopus3.5 等) は llama.cpp 未対応
|
|
307
|
+
|
|
308
|
+
例えば `Jackrong/Qwopus3.5-9B-v3-GGUF` (Qwen3.5-VL ベース + Claude Opus 蒸留、Apache-2.0、Vision) を `ollama pull` すると **blob は完全に落ちてくる**が `ollama run` で:
|
|
309
|
+
|
|
310
|
+
```
|
|
311
|
+
Error: 500 Internal Server Error: unable to load model:
|
|
312
|
+
...sha256-19d52ddc.../...
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Ollama server log を見ると:
|
|
316
|
+
|
|
317
|
+
```
|
|
318
|
+
llama_model_load: error loading model: error loading model architecture:
|
|
319
|
+
unknown model architecture: 'qwen35'
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
**原因**: Qwen3.5 は新アーキテクチャ (hybrid Transformer-SSM、`qwen35.ssm.*` 系のキーが GGUF metadata に含まれる) で、llama.cpp / Ollama に **`qwen35` architecture 実装が未マージ**。Ollama version の問題ではなく、フレームワーク本体の対応待ち。
|
|
323
|
+
|
|
324
|
+
**回避**: 現状は HF / Transformers / vLLM 直接ロード経由で使うしかない (CodeRouter は Ollama OpenAI-compat 経由なので非対応)。llama.cpp が `qwen35` を実装したら再評価。
|
|
325
|
+
|
|
326
|
+
> **教訓**: HF で「Qwen3.5 + Opus 蒸留」のような新しい組み合わせは note / r/LocalLLaMA で評判が立っていても、**Ollama 経由ですぐ使えるとは限らない**。`ollama pull` → `ollama run` で 500 が出たら、まず Ollama server log で `unknown model architecture` を確認。出たら今は諦めて他のモデルに行くのが時間効率的に正解。
|
|
327
|
+
|
|
328
|
+
#### 4-2-C. **Gemma 4 26B** は実機で完全動作 (v1.8.2 で確定)
|
|
329
|
+
|
|
330
|
+
`coderouter doctor --check-model ollama-gemma4-26b` の `tool_calls` probe が無加工で `[OK]`。v1.8.2 で thinking モデル対応 probe バジェット (1024) を入れた後は **`num_ctx` / `streaming` も `[OK]`** で 6 probe 全クリア。`/v1/messages` Anthropic 互換経由で "Hello." を 2 秒応答 (M3 Max 64GB)、`tool_calls native OK`、`reasoning strip` 動作。
|
|
331
|
+
|
|
332
|
+
ただし **interactive UX は若干重い** — Gemma 4 は thinking モデルなので `reasoning` フィールドにも応答時間を使う + 26B サイズ + Claude Code の agent loop (1 プロンプトで 3〜6 round-trip) で総応答時間が 30〜90 秒 / プロンプトになる。daily driver には `qwen2.5-coder:14b` のほうが速い。**Gemma 4 は tool_calls native + 高品質が要るときの選択肢**。
|
|
333
|
+
|
|
334
|
+
note 記事の「Gemma 4 が日常の王者」評価は **Claude Code agentic 用途でも裏付けられた**形。v1.8.1 で `coding` profile primary を Gemma 4 / Qwen-Coder 14b へ調整、v1.8.2 で registry に `thinking: true` を宣言。
|
|
335
|
+
|
|
336
|
+
#### 4-2-D. ベスト実践 — 「枯れたモデル + 観測ツール」
|
|
337
|
+
|
|
338
|
+
実機運用での提案:
|
|
339
|
+
|
|
340
|
+
1. **第一候補は枯れたもの**: `qwen2.5-coder:14b` / `qwen2.5-coder:7b` / `gemma4:26b` / `gemma4:e4b`
|
|
341
|
+
2. **doctor で確認**: `cr doctor --check-model <provider>` で 6 probe を回す
|
|
342
|
+
3. **`--apply` で patch 適用**: NEEDS_TUNING の YAML パッチを非破壊書き戻し (`pip install 'coderouter-cli[doctor]'` 必要)
|
|
343
|
+
4. **新興モデルは慎重に**: HF で見つけた新モデルは Ollama 0.20+ でも未対応のことあり、`ollama run` → server log で確認
|
|
344
|
+
5. **fallback chain で守る**: ローカル primary が落ちても NIM / OpenRouter free に流れるように chain を厚く
|
|
345
|
+
|
|
346
|
+
#### 4-2-E. doctor probe 自体の限界 — thinking モデル対応 (v1.8.2)
|
|
347
|
+
|
|
348
|
+
v1.8.2 までの doctor `num_ctx` / `streaming` probe は **`max_tokens=32` / `128`** で出力を要求していた。これは canary token (~5 tokens) や "1..30" (~40 tokens) を返すには十分だったが、**thinking モデル** (Gemma 4、Qwen3 系、gpt-oss、deepseek-r1) は `reasoning` フィールドに思考トークンを吐く設計のため、可視 `content` が出る前に max_tokens に到達して `finish_reason='length'` で打ち切られる **偽陽性 NEEDS_TUNING** を出していた。
|
|
349
|
+
|
|
350
|
+
v1.8.2 で:
|
|
351
|
+
|
|
352
|
+
- `_NUM_CTX_PROBE_MAX_TOKENS_DEFAULT = 256` (旧 32)、`_NUM_CTX_PROBE_MAX_TOKENS_THINKING = 1024`
|
|
353
|
+
- `_STREAMING_PROBE_MAX_TOKENS_DEFAULT = 512` (旧 128)、`_STREAMING_PROBE_MAX_TOKENS_THINKING = 1024`
|
|
354
|
+
- `provider.capabilities.thinking` / `provider.capabilities.reasoning_passthrough` / registry の `thinking` / `reasoning_passthrough` のいずれかが true なら thinking バジェットを採用
|
|
355
|
+
|
|
356
|
+
つまり **provider 宣言不要**: bundled `model-capabilities.yaml` で `gemma4:*` / `qwen3.6:*` などに `thinking: true` を宣言してあれば、user の providers.yaml には何も書かなくても doctor が自動的に正しいバジェットを使う。
|
|
357
|
+
|
|
358
|
+
**メタ教訓**: diagnostic ツール自身も diagnostic され続ける必要がある (plan.md §5.4 「実機 evidence first」原則の補強)。
|
|
359
|
+
|
|
360
|
+
### 4-3. `UserPromptSubmit hook error` が出る (第三者 Claude Code プラグイン)
|
|
282
361
|
|
|
283
362
|
```
|
|
284
363
|
❯ こんにちは
|