coderouter-cli 2.6.0__py3-none-any.whl → 2.6.1__py3-none-any.whl

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.
@@ -184,6 +184,21 @@ _DASHBOARD_HTML = r"""<!doctype html>
184
184
  </div>
185
185
  </div>
186
186
  <div id="language-tax-by-provider" class="text-xs text-slate-400 tabnum mt-3"></div>
187
+ <!-- Token Savings: trim (core budget guard) + compress (plugin) -->
188
+ <div class="grid grid-cols-3 gap-3 mt-3">
189
+ <div class="rounded-md bg-slate-800/50 p-3">
190
+ <div class="text-xs text-slate-400">Tokens saved &middot; trim</div>
191
+ <div class="text-2xl font-semibold tabnum text-sky-400" data-bind="tokens_saved_trim">0</div>
192
+ </div>
193
+ <div class="rounded-md bg-slate-800/50 p-3">
194
+ <div class="text-xs text-slate-400">Tokens saved &middot; compress</div>
195
+ <div class="text-2xl font-semibold tabnum text-sky-400" data-bind="tokens_saved_compress">0</div>
196
+ </div>
197
+ <div class="rounded-md bg-slate-800/50 p-3">
198
+ <div class="text-xs text-slate-400">Tokens saved &middot; total</div>
199
+ <div class="text-2xl font-semibold tabnum text-green-400" data-bind="tokens_saved_total">0</div>
200
+ </div>
201
+ </div>
187
202
  </section>
188
203
  <section class="bg-slate-900/60 border border-slate-800 rounded-lg p-4">
189
204
  <h2 class="text-sm font-semibold uppercase tracking-wider text-slate-400 mb-3">Usage Mix</h2>
@@ -474,6 +489,14 @@ _DASHBOARD_HTML = r"""<!doctype html>
474
489
  rows.map(([n, v]) =>
475
490
  '<span class="mr-4"><span class="text-slate-500">' + escapeHTML(n) +
476
491
  '</span> ' + usd(v) + '</span>').join("");
492
+ // token-savings tiles. trim comes from the core budget guard;
493
+ // compress from the optional plugin. Collector zero-fills, so an
494
+ // install with neither still renders "0" cleanly.
495
+ const ts = c.tokens_saved_by_mechanism || {};
496
+ const intfmt = (x) => (Number(x) || 0).toLocaleString();
497
+ setBind("tokens_saved_trim", intfmt(ts.trim));
498
+ setBind("tokens_saved_compress", intfmt(ts.compress));
499
+ setBind("tokens_saved_total", intfmt(c.tokens_saved_total));
477
500
  };
478
501
 
479
502
  const renderSnapshot = (snap) => {
@@ -197,6 +197,16 @@ class MetricsCollector(logging.Handler):
197
197
  self._language_tax_usd: dict[str, float] = {}
198
198
  self._language_tax_usd_aggregate: float = 0.0
199
199
 
200
+ # token-savings accounting. Tokens that never reached the backend
201
+ # because a reduction mechanism shrank or dropped them. Two
202
+ # mechanisms feed this: ``trim`` (core context-budget guard, derived
203
+ # from the existing ``context-budget-trimmed`` event) and
204
+ # ``compress`` (the optional compress plugin, via the neutral
205
+ # ``tokens-saved`` event). Owned by core so the figure exists even
206
+ # when no plugin is installed.
207
+ self._tokens_saved_total: int = 0
208
+ self._tokens_saved_by_mechanism: Counter[str] = Counter()
209
+
200
210
  # v2.0-F (L1): context budget guard counters. Per-profile counts
201
211
  # of warnings (over warn threshold) and trims (messages removed).
202
212
  # The ``latest_usage_ratio`` dict records the most recent ratio
@@ -425,6 +435,41 @@ class MetricsCollector(logging.Handler):
425
435
  profile = _str(extras.get("profile"))
426
436
  self._context_budget_trims_total += 1
427
437
  self._context_budget_trims_by_profile[profile] += 1
438
+ # token-savings: the trim already carries the estimated
439
+ # before/after token counts, so fold the delta into the
440
+ # shared savings buckets (mechanism="trim").
441
+ before = extras.get("estimated_tokens_before")
442
+ after = extras.get("estimated_tokens_after")
443
+ if isinstance(before, int | float) and isinstance(
444
+ after, int | float
445
+ ):
446
+ saved = max(0, int(before) - int(after))
447
+ if saved:
448
+ self._tokens_saved_total += saved
449
+ self._tokens_saved_by_mechanism["trim"] += saved
450
+ elif event == "tokens-saved":
451
+ # Neutral token-savings event emitted by non-core reduction
452
+ # mechanisms (e.g. the compress plugin). Carries a
453
+ # ``mechanism`` label plus either a precomputed
454
+ # ``tokens_saved`` or a ``tokens_before`` / ``tokens_after``
455
+ # pair. Negative deltas clamp to zero. No core import needed
456
+ # by the emitter — it just logs this record name.
457
+ mechanism = _str(extras.get("mechanism")) or "unknown"
458
+ saved_raw = extras.get("tokens_saved")
459
+ before = extras.get("tokens_before")
460
+ after = extras.get("tokens_after")
461
+ if isinstance(saved_raw, int | float):
462
+ saved = max(0, int(saved_raw))
463
+ elif isinstance(before, int | float) and isinstance(
464
+ after, int | float
465
+ ):
466
+ saved = max(0, int(before) - int(after))
467
+ else:
468
+ saved = 0
469
+ if saved:
470
+ self._tokens_saved_total += saved
471
+ self._tokens_saved_by_mechanism[mechanism] += saved
472
+ self._push_recent(event, extras, record)
428
473
  elif event == "drift-detected":
429
474
  # v2.0-G (L4): drift detection fired.
430
475
  provider = _str(extras.get("provider"))
@@ -624,6 +669,11 @@ class MetricsCollector(logging.Handler):
624
669
  "language_tax_usd_aggregate": round(
625
670
  self._language_tax_usd_aggregate, 6
626
671
  ),
672
+ # token-savings: total + per-mechanism (trim / compress).
673
+ "tokens_saved_total": self._tokens_saved_total,
674
+ "tokens_saved_by_mechanism": dict(
675
+ self._tokens_saved_by_mechanism
676
+ ),
627
677
  # v2.0-F (L1): context budget guard aggregate counters.
628
678
  "context_budget_warnings_total": self._context_budget_warnings_total,
629
679
  "context_budget_trims_total": self._context_budget_trims_total,
@@ -684,6 +734,10 @@ class MetricsCollector(logging.Handler):
684
734
  "chain_budget_exceeded_total": self._chain_budget_exceeded_total,
685
735
  "chain_memory_pressure_blocked_total": self._chain_memory_pressure_blocked_total,
686
736
  "chain_uniform_auth_failure_total": self._chain_uniform_auth_failure_total,
737
+ "tokens_saved_total": self._tokens_saved_total,
738
+ "tokens_saved_by_mechanism": dict(
739
+ self._tokens_saved_by_mechanism
740
+ ),
687
741
  "probe_rounds_total": self._probe_rounds_total,
688
742
  }
689
743
 
@@ -724,6 +778,9 @@ class MetricsCollector(logging.Handler):
724
778
  self._language_tax_usd_aggregate += float(
725
779
  state.get("language_tax_usd_aggregate", 0.0)
726
780
  )
781
+ self._tokens_saved_total += int(state.get("tokens_saved_total", 0))
782
+ for k, v in (state.get("tokens_saved_by_mechanism") or {}).items():
783
+ self._tokens_saved_by_mechanism[k] += int(v)
727
784
  self._chain_paid_gate_blocked_total += int(
728
785
  state.get("chain_paid_gate_blocked_total", 0)
729
786
  )
@@ -782,6 +839,9 @@ class MetricsCollector(logging.Handler):
782
839
  # v2.6
783
840
  self._language_tax_usd.clear()
784
841
  self._language_tax_usd_aggregate = 0.0
842
+ # token-savings
843
+ self._tokens_saved_total = 0
844
+ self._tokens_saved_by_mechanism.clear()
785
845
  # v2.0-H (L6)
786
846
  self._partial_stitch_surfaced_total = 0
787
847
  # v2.0-I
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coderouter-cli
3
- Version: 2.6.0
3
+ Version: 2.6.1
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
@@ -161,6 +161,36 @@ ANTHROPIC_BASE_URL=http://localhost:8088 ANTHROPIC_AUTH_TOKEN=dummy claude
161
161
  | **`coderouter replay`** | provider 切替の効果を統計比較 (A/B 分析) / `--suggest-rules` でルール最適化提案 |
162
162
  | **Continuous Probe** | idle 時も定期的に backend を監視 |
163
163
 
164
+ ### 言語税トラッキング — v2.6.0
165
+
166
+ 日本語などの CJK テキストは、クラウドのトークナイザだと「同じ意味の英語」より多くのトークンを消費します(**実測: GPT-4o 系 o200k で平均 1.6 倍、GPT-4 系 cl100k で平均 2.0 倍**)。ローカル LLM は課金されないので、この「言語税」はクラウド利用時だけ効いてきます。CodeRouter v2.6.0 はこれを **計測・ルーティング回避・可視化** します。
167
+
168
+ | 機能 | 何をしてくれるか |
169
+ |---|---|
170
+ | **言語税の計測** | プロバイダに `tokenizer_path`(ローカルの `tokenizer.json`)を指定すると、char/4 ヒューリスティック比の実トークン倍率と割増 USD を算出(ネットワーク不要・未設定なら無効) |
171
+ | **`cjk_ratio_min` ルーティング** | CJK 比率が高いリクエストを自動でローカル LLM(課金ゼロ)へ。コードや英語はクラウドへ |
172
+ | **ダッシュボード可視化** | `/dashboard` の「Cost & Language Tax」パネルで総支出・キャッシュ節約・言語税をリアルタイム表示 |
173
+
174
+ ```yaml
175
+ # providers.yaml — CJK 多めのターンはローカルへ自動回避
176
+ auto_router:
177
+ rules:
178
+ - match: { cjk_ratio_min: 0.3 } # 日本語が3割以上 → ローカル
179
+ profile: local
180
+ - match: { has_tools: true } # ツール使用 → クラウド
181
+ profile: cloud
182
+ default_rule_profile: cloud
183
+
184
+ providers:
185
+ - name: cloud-sonnet
186
+ kind: anthropic
187
+ base_url: https://api.anthropic.com
188
+ model: claude-sonnet-4-6
189
+ tokenizer_path: ~/.coderouter/tokenizers/sonnet.json # 言語税の正確計測(任意)
190
+ ```
191
+
192
+ 詳細 → [言語税ガイド](./docs/guides/language-tax.md)
193
+
164
194
  ### Launcher — llama.cpp / vllm 起動 UI
165
195
 
166
196
  `http://localhost:8088/launcher` で開けるブラウザ UI。llama.cpp や vllm を GUI で起動・管理できます。
@@ -229,6 +259,7 @@ providers:
229
259
  | 使いこなす | [利用ガイド](./docs/guides/usage-guide.md) |
230
260
  | 無料で回す | [無料枠ガイド](./docs/guides/free-tier-guide.md) |
231
261
  | llama.cpp / vllm を GUI で起動 | [Launcher ガイド](./docs/backends/launcher.md) |
262
+ | 言語税を計測・回避する | [言語税ガイド](./docs/guides/language-tax.md) |
232
263
  | 詰まった | [トラブルシューティング](./docs/guides/troubleshooting.md) |
233
264
  | 設計を知りたい | [アーキテクチャ詳細](./docs/concepts/architecture.md) |
234
265
  | 全リリース履歴 | [CHANGELOG](./CHANGELOG.md) |
@@ -40,12 +40,12 @@ coderouter/guards/tool_loop.py,sha256=EzeMcmU7BLeTW2jsRVevU81l5rhWcn1oUr7EpzgXjV
40
40
  coderouter/ingress/__init__.py,sha256=WQsCH2CGJCAhy0mS6GSEdeYZRkkQu2OHDsP4CJWTLug,155
41
41
  coderouter/ingress/anthropic_routes.py,sha256=It2f7XGe3fgKQX01J2F5JOCoZr96t_Tx_kY2om99MVo,16894
42
42
  coderouter/ingress/app.py,sha256=PcuTvUFNjr04EbsUOu8qdyKTdBzxkIJYB4xpz8dFfMo,12635
43
- coderouter/ingress/dashboard_routes.py,sha256=tEIayMHxCzlmpnLyKHgpqrE4W24DTJM97ewTlYvkKqI,24238
43
+ coderouter/ingress/dashboard_routes.py,sha256=1SqNNrVTfQiZcx5n2MvjAzWirittX0e61tCt-MKp4Ag,25617
44
44
  coderouter/ingress/launcher_routes.py,sha256=Jh-E6qFmHnr7ON4W6QanafxQIoojT4F034mybLvhTyQ,47548
45
45
  coderouter/ingress/metrics_routes.py,sha256=M22dwOGn24P05Ge4W3c7d7mYytSGWjIR-pPSPOAiHJY,3965
46
46
  coderouter/ingress/openai_routes.py,sha256=Zw1efPw9DI6GgV8ZcLrzS6Cda0KLrFkKn2GBZWSe6Vo,6322
47
47
  coderouter/metrics/__init__.py,sha256=7Es351DPS7yLM0yVF_F0eesmiD83n7Zzhie44chht38,1465
48
- coderouter/metrics/collector.py,sha256=9lKnaFpdlu8R9mRUeyAeJWXR1urRCKt_6sUFn_9ybss,49657
48
+ coderouter/metrics/collector.py,sha256=F6C19DgZDqEKq8Hidlo99cfev5vRQO63p4WMOrvUeqQ,52975
49
49
  coderouter/metrics/prometheus.py,sha256=YRqyT931s40zVkIj07D-M2UNfDhIEElVFRz3izdJcnQ,24419
50
50
  coderouter/plugins/__init__.py,sha256=76hMLe5dV_ilripHXzWn3HSYoIALjzlw4EJVyI-GyIM,1974
51
51
  coderouter/plugins/base.py,sha256=n9hsck2NCSqi6oeHIumKC5zhQ8JGwCXUz7J5AZQCQss,5772
@@ -67,8 +67,8 @@ coderouter/translation/__init__.py,sha256=PYXN7XVEwpG1uC8RLy6fvnGbzEZhhrEuUapH8I
67
67
  coderouter/translation/anthropic.py,sha256=aZkcYH4x82b0x7efJgJb9RWn9Hbyc9pEOthXe4vjUdU,11113
68
68
  coderouter/translation/convert.py,sha256=-qyzFzmmr9hhQV6_Sg75kJnvCZvHe3n7vRdaZtk_JqQ,47269
69
69
  coderouter/translation/tool_repair.py,sha256=Ok2PF947Liegc5oaytfptv5MWMkpfJYQie-zdP1y3cY,9946
70
- coderouter_cli-2.6.0.dist-info/METADATA,sha256=us2o2_EtIlzd2EjQqAqtKIX1ocpAD3YcaDiZKOG6ktE,11674
71
- coderouter_cli-2.6.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
72
- coderouter_cli-2.6.0.dist-info/entry_points.txt,sha256=-dnLfD1YZ2WjH2zSdNCvlO65wYltM9bsHt9Fhg3yGss,51
73
- coderouter_cli-2.6.0.dist-info/licenses/LICENSE,sha256=wkEzoR86jFw33jvfOHjULqmkGEfxTFMgMaJnpR8mPRw,1065
74
- coderouter_cli-2.6.0.dist-info/RECORD,,
70
+ coderouter_cli-2.6.1.dist-info/METADATA,sha256=3zw3ZZGT71GYD8F9qeYIyMQB-ZN4jmRRoYOB4XD8eIo,13534
71
+ coderouter_cli-2.6.1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
72
+ coderouter_cli-2.6.1.dist-info/entry_points.txt,sha256=-dnLfD1YZ2WjH2zSdNCvlO65wYltM9bsHt9Fhg3yGss,51
73
+ coderouter_cli-2.6.1.dist-info/licenses/LICENSE,sha256=wkEzoR86jFw33jvfOHjULqmkGEfxTFMgMaJnpR8mPRw,1065
74
+ coderouter_cli-2.6.1.dist-info/RECORD,,