Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ on:

jobs:
test:
runs-on: ubuntu-latest
# ubuntu-latest moved to 26.04 (2026-10-20), which has no Python 3.9 build.
runs-on: ubuntu-24.04
strategy:
matrix:
python-version: ['3.9', '3.11', '3.12']
Expand Down
38 changes: 38 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,44 @@

All notable changes to blockrun-llm will be documented in this file.

## Unreleased

### Changed
- **Router Core re-synced to upstream `2cd5a10`** (was `e38958b`, five
upstream commits behind; the TypeScript SDK bundles `65a92c8`, which differs
from `2cd5a10` only in the capability snapshot). Auto routing now reads only
the user turn for stakes, latency and language cues, and structured output
is taken only from `requires_structured_output` (`response_format`): a
system prompt that lists tool schemas or says "be quick" no longer lifts a
request off the cheapest tier. CJK input is sized at about one token per
character (`router_core.estimate_tokens`, also the default for
`router.classify_by_rules`), so a long Chinese document reaches the
long-context models instead of overflowing a smaller window. Keyword scoring
matches whole words ("classic" is no longer code, "history" is no longer
creative, "prefix" is no longer agentic) while keeping ordinary inflections,
ASCII German/Romance endings, Cyrillic stems and CJK substrings. A stack
trace or CamelCase error class (`TypeError`, `NullPointerException`) now
floors a debug or code-edit turn at MEDIUM. A stale speed probe fades toward
neutral instead of toward zero, and a model with no catalog price no longer
reports 100% savings.

- **Tool routing admits eleven newly probe-verified models as availability
fallbacks:** Claude Fable 5.1 / Opus 5.5 / Sonnet 5.5, Gemini 3.8 Flash,
GPT-5.1, GPT-6 Astra / Luna / Sol, Grok 4.6 / 4.7 and MiMo V2.5 now carry
`supports_tools=True` (all eleven were fail-closed here before). They join a
tool-required turn's chain after the configured and benchmark-backed models,
only when the live catalog prices them, cheapest first outside `premium`.
Primary selection is unchanged.

- **Capability snapshot regenerated from the 2026-10-20 catalog: 89 text
models** (was 82). New: DeepSeek V4.1 Flash, GPT-6.1 Sol, Mistral Large 4,
Muse Glimmer 30B, Laguna S 2.1, Qwen 3.8 Max and Hunyuan 4 Preview, all
without probe evidence yet, so they serve plain chat but never a
tool-required turn. DeepSeek V4 Flash Vision Experimental and Qwen 3.8 Flash
failed the function-call probe and stay fail-closed. The cross-language
decision snapshot (`tests/unit/router_core_decisions.snapshot.json`) is
re-copied from upstream, and the port reproduces all 88 decisions exactly.

## 1.20.0 — 2026-10-11

### Added
Expand Down
9 changes: 4 additions & 5 deletions blockrun_llm/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
ResolvedRoutingDecision,
route_with_catalog,
)
from .router_core import DEFAULT_ROUTING_CONFIG
from .router_core import DEFAULT_ROUTING_CONFIG, estimate_tokens
from .router_core import classify_by_rules as _classify_by_rules
from .router_core.types import ModelPricing, ScoringResult, Tier, TierConfig
from .types import RoutingProfile
Expand Down Expand Up @@ -66,12 +66,11 @@ def classify_by_rules(
) -> ScoringResult:
"""Classify a prompt into a tier with the shared 15-dimension scorer.

``estimated_tokens`` defaults to the ~4-chars-per-token estimate the router
itself uses.
``estimated_tokens`` defaults to the estimate the router itself uses (about
four characters per token, one per CJK character).
"""
if estimated_tokens is None:
full_text = f"{system_prompt or ''} {prompt}"
estimated_tokens = -(-len(full_text) // 4) # ceil
estimated_tokens = estimate_tokens(f"{system_prompt or ''} {prompt}")
return _classify_by_rules(
prompt, system_prompt, estimated_tokens, DEFAULT_ROUTING_CONFIG["scoring"]
)
Expand Down
4 changes: 3 additions & 1 deletion blockrun_llm/router_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Router Core — deterministic, constraint-first model routing.

Python port of `@blockrun/router-core <https://github.com/BlockRunAI/router-core>`_
(upstream commit ``e38958b``), the same routing engine the TypeScript SDK and
(upstream commit ``2cd5a10``), the same routing engine the TypeScript SDK and
the BlockRun gateway use. The package is deliberately product-neutral: task
classification, hard capability filtering, portfolio scoring, ordered
fallbacks, and routing configuration. It contains no wallet, gateway client,
Expand Down Expand Up @@ -51,6 +51,7 @@
get_strategy,
register_strategy,
)
from .tokens import estimate_tokens
from .tool_intent import infer_tool_requirement
from .types import (
Capacity,
Expand Down Expand Up @@ -108,6 +109,7 @@ def route(
"calculate_model_cost",
"classify_by_rules",
"classify_task",
"estimate_tokens",
"filter_by_exclude_list",
"filter_by_tool_calling",
"filter_by_vision",
Expand Down
5 changes: 3 additions & 2 deletions blockrun_llm/router_core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
Default Routing Config

Python port of ``@blockrun/router-core`` ``config.ts`` (upstream commit
``e38958b``, 2026-09-29 — the same pin the TypeScript SDK
bundles). V3.5: every tier rung is a public catalog id.
``2cd5a10``; the file is unchanged since ``e38958b``, 2026-09-29, so it is the
same table the TypeScript SDK bundles). V3.5: every tier rung is a public
catalog id.

All routing parameters as a module constant. Hosts override by passing their
own ``RoutingConfig`` in ``RouterOptions["config"]``.
Expand Down
84 changes: 60 additions & 24 deletions blockrun_llm/router_core/model_capabilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
product catalog is temporarily unavailable, without importing product code.

GENERATED upstream by ``scripts/sync-model-capabilities.mjs`` from the public
catalog (GET https://blockrun.ai/api/v1/models) on 2026-10-02; ``supports_tools``
catalog (GET https://blockrun.ai/api/v1/models) on 2026-10-20; ``supports_tools``
comes from a live function-calling probe. Re-sync from ``model-capabilities.ts``
rather than editing by hand — a hand edit is lost on the next sync.
"""
Expand All @@ -28,11 +28,10 @@
"supports_tools": True,
"supports_vision": True,
},
# supportsTools: not probed — fails closed
"anthropic/claude-fable-5.1": {
"context_window": 1_000_000,
"max_output_tokens": 128_000,
"supports_tools": False,
"supports_tools": True,
"supports_vision": True,
},
# override: The public catalog's `categories` omit "vision" for this Anthropic model even
Expand Down Expand Up @@ -69,11 +68,10 @@
"supports_tools": True,
"supports_vision": True,
},
# supportsTools: not probed — fails closed
"anthropic/claude-opus-5.5": {
"context_window": 1_000_000,
"max_output_tokens": 128_000,
"supports_tools": False,
"supports_tools": True,
"supports_vision": True,
},
"anthropic/claude-sonnet-4.5": {
Expand All @@ -98,11 +96,10 @@
"supports_tools": True,
"supports_vision": True,
},
# supportsTools: not probed — fails closed
"anthropic/claude-sonnet-5.5": {
"context_window": 1_000_000,
"max_output_tokens": 128_000,
"supports_tools": False,
"supports_tools": True,
"supports_vision": True,
},
# supportsTools: not probed — fails closed
Expand All @@ -124,7 +121,6 @@
"supports_tools": True,
"supports_vision": False,
},
# supportsTools: not probed — fails closed
"deepseek/deepseek-v4-flash-vision-exp": {
"context_window": 1_048_576,
"max_output_tokens": 65_536,
Expand All @@ -137,6 +133,13 @@
"supports_tools": True,
"supports_vision": False,
},
# supportsTools: not probed — fails closed
"deepseek/deepseek-v4.1-flash": {
"context_window": 1_048_576,
"max_output_tokens": 65_536,
"supports_tools": False,
"supports_vision": False,
},
"google/gemini-2.5-flash": {
"context_window": 1_048_576,
"max_output_tokens": 65_536,
Expand Down Expand Up @@ -191,11 +194,10 @@
"supports_tools": True,
"supports_vision": True,
},
# supportsTools: not probed — fails closed
"google/gemini-3.8-flash": {
"context_window": 1_048_576,
"max_output_tokens": 65_536,
"supports_tools": False,
"supports_tools": True,
"supports_vision": True,
},
"minimax/minimax-m2.7": {
Expand All @@ -210,6 +212,13 @@
"supports_tools": True,
"supports_vision": False,
},
# supportsTools: not probed — fails closed
"mistral/mistral-large-4": {
"context_window": 524_288,
"max_output_tokens": 262_144,
"supports_tools": False,
"supports_vision": True,
},
"moonshot/kimi-k3": {
"context_window": 1_048_576,
"max_output_tokens": 65_536,
Expand All @@ -223,6 +232,13 @@
"supports_tools": False,
"supports_vision": True,
},
# supportsTools: not probed — fails closed
"nvidia/muse-glimmer-30b": {
"context_window": 131_072,
"max_output_tokens": 16_384,
"supports_tools": False,
"supports_vision": False,
},
# override: The catalog tags this model "vision", but a correctly sized probe does not
# hold up (ClawRouter, 2026-08-31): a 64x64 solid-red PNG was named correctly 1 of 4
# times on Base, and on Solana the image was silently dropped and a text model answered
Expand Down Expand Up @@ -292,11 +308,10 @@
"supports_tools": True,
"supports_vision": False,
},
# supportsTools: not probed — fails closed
"openai/gpt-5.1": {
"context_window": 400_000,
"max_output_tokens": 128_000,
"supports_tools": False,
"supports_tools": True,
"supports_vision": True,
},
"openai/gpt-5.2": {
Expand Down Expand Up @@ -398,22 +413,26 @@
"supports_tools": True,
"supports_vision": True,
},
# supportsTools: not probed — fails closed
"openai/gpt-6-astra": {
"context_window": 1_050_000,
"max_output_tokens": 128_000,
"supports_tools": False,
"supports_tools": True,
"supports_vision": True,
},
# supportsTools: not probed — fails closed
"openai/gpt-6-luna": {
"context_window": 1_050_000,
"max_output_tokens": 128_000,
"supports_tools": False,
"supports_tools": True,
"supports_vision": True,
},
# supportsTools: not probed — fails closed
"openai/gpt-6-sol": {
"context_window": 1_050_000,
"max_output_tokens": 128_000,
"supports_tools": True,
"supports_vision": True,
},
# supportsTools: not probed — fails closed
"openai/gpt-6.1-sol": {
"context_window": 1_050_000,
"max_output_tokens": 128_000,
"supports_tools": False,
Expand Down Expand Up @@ -444,6 +463,13 @@
"supports_vision": False,
},
# supportsTools: not probed — fails closed
"poolside/laguna-s-2.1": {
"context_window": 1_048_576,
"max_output_tokens": 131_072,
"supports_tools": False,
"supports_vision": False,
},
# supportsTools: not probed — fails closed
"poolside/laguna-xs-2.1": {
"context_window": 131_072,
"max_output_tokens": 16_384,
Expand All @@ -468,13 +494,26 @@
"supports_tools": True,
"supports_vision": False,
},
# supportsTools: not probed — fails closed
"qwen/qwen3.8-flash": {
"context_window": 1_000_000,
"max_output_tokens": 131_072,
"supports_tools": False,
"supports_vision": True,
},
# supportsTools: not probed — fails closed
"qwen/qwen3.8-max": {
"context_window": 1_000_000,
"max_output_tokens": 131_072,
"supports_tools": False,
"supports_vision": False,
},
# supportsTools: not probed — fails closed
"tencent/hy4-preview": {
"context_window": 1_048_576,
"max_output_tokens": 64_000,
"supports_tools": False,
"supports_vision": False,
},
"xai/grok-4.3": {
"context_window": 1_000_000,
"max_output_tokens": 16_384,
Expand All @@ -487,18 +526,16 @@
"supports_tools": True,
"supports_vision": True,
},
# supportsTools: not probed — fails closed
"xai/grok-4.6": {
"context_window": 500_000,
"max_output_tokens": 16_384,
"supports_tools": False,
"supports_tools": True,
"supports_vision": True,
},
# supportsTools: not probed — fails closed
"xai/grok-4.7": {
"context_window": 500_000,
"max_output_tokens": 16_384,
"supports_tools": False,
"supports_tools": True,
"supports_vision": True,
},
"xai/grok-build-0.1": {
Expand All @@ -507,11 +544,10 @@
"supports_tools": True,
"supports_vision": False,
},
# supportsTools: not probed — fails closed
"xiaomi/mimo-v2.5": {
"context_window": 1_048_576,
"max_output_tokens": 131_072,
"supports_tools": False,
"supports_tools": True,
"supports_vision": True,
},
"xiaomi/mimo-v2.5-pro": {
Expand Down
Loading
Loading