Skip to content
Open
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
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,23 @@ uncommon-route provider add google AIza...
uncommon-route serve
```

MiniMax uses the global OpenAI-compatible endpoint by default. Pass the China
endpoint explicitly when needed:

```bash
uncommon-route provider add minimax eyJ-...
uncommon-route provider add minimax eyJ-... --url https://api.minimaxi.com/v1
```

| Region | OpenAI-compatible base URL | Anthropic-compatible base URL |
|---|---|---|
| Global | `https://api.minimax.io/v1` | `https://api.minimax.io/anthropic` |
| China | `https://api.minimaxi.com/v1` | `https://api.minimaxi.com/anthropic` |

Provider configuration stores the OpenAI-compatible base URL. Anthropic
requests use the corresponding `/anthropic` base and append `/v1/messages`
internally.

> UncommonRoute doesn't automatically read `OPENAI_API_KEY` or `ANTHROPIC_API_KEY`. Use `init`, a saved connection, or one of the manual setup paths above.

### Routing Modes
Expand Down
60 changes: 47 additions & 13 deletions tests/test_anthropic_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -1931,9 +1931,18 @@ def handler(request: httpx.Request) -> httpx.Response:
finally:
asyncio.run(async_client.aclose())

@pytest.mark.parametrize(
("base_url", "expected_url"),
[
("https://api.minimax.io/v1", "https://api.minimax.io/anthropic/v1/messages"),
("https://api.minimaxi.com/v1", "https://api.minimaxi.com/anthropic/v1/messages"),
],
)
def test_messages_use_minimax_anthropic_endpoint_for_direct_provider(
self,
monkeypatch: pytest.MonkeyPatch,
base_url: str,
expected_url: str,
) -> None:
captured: dict[str, object] = {}

Expand All @@ -1947,7 +1956,7 @@ def handler(request: httpx.Request) -> httpx.Response:
"id": "msg_minimax_byok",
"type": "message",
"role": "assistant",
"model": "minimax/minimax-m2.1",
"model": "MiniMax-M3",
"content": [{"type": "text", "text": "ok"}],
"stop_reason": "end_turn",
"stop_sequence": None,
Expand All @@ -1964,32 +1973,44 @@ def handler(request: httpx.Request) -> httpx.Response:
"minimax": ProviderEntry(
name="minimax",
api_key="mm-key-123",
base_url="https://api.minimax.io/v1",
models=["minimax/minimax-m2.1"],
base_url=base_url,
models=["minimax/minimax-m3"],
),
})
app = create_app(
upstream="https://api.commonstack.ai/v1",
upstream="https://primary.example/v1",
providers_config=providers,
)
client = TestClient(app, raise_server_exceptions=False)
resp = client.post("/v1/messages", json={
"model": "minimax/minimax-m2.1",
"model": "minimax/minimax-m3",
"max_tokens": 32,
"messages": [{"role": "user", "content": "hello"}],
})

assert resp.status_code == 200
assert captured["url"] == "https://api.minimax.io/anthropic/v1/messages"
assert captured["url"] == expected_url
headers = captured["headers"]
assert isinstance(headers, dict)
assert headers["x-api-key"] == "mm-key-123"
body = captured["body"]
assert isinstance(body, dict)
assert body["model"] == "MiniMax-M3"
finally:
asyncio.run(async_client.aclose())

def test_chat_completions_keep_openai_transport_for_minimax_models(
@pytest.mark.parametrize(
("base_url", "expected_url"),
[
("https://api.minimax.io/v1", "https://api.minimax.io/v1/chat/completions"),
("https://api.minimaxi.com/v1", "https://api.minimaxi.com/v1/chat/completions"),
],
)
def test_chat_completions_use_minimax_openai_endpoint_for_direct_provider(
self,
monkeypatch: pytest.MonkeyPatch,
base_url: str,
expected_url: str,
) -> None:
captured: dict[str, object] = {}

Expand All @@ -2003,7 +2024,7 @@ def handler(request: httpx.Request) -> httpx.Response:
"id": "chatcmpl_minimax",
"object": "chat.completion",
"created": 1,
"model": "minimax/minimax-m2.1",
"model": "MiniMax-M3",
"choices": [{
"index": 0,
"message": {"role": "assistant", "content": "done"},
Expand All @@ -2016,13 +2037,23 @@ def handler(request: httpx.Request) -> httpx.Response:

async_client = httpx.AsyncClient(transport=httpx.MockTransport(handler))
monkeypatch.setattr("uncommon_route.proxy._get_client", lambda: async_client)
monkeypatch.setenv("UNCOMMON_ROUTE_API_KEY", "env-key-123")

try:
app = create_app(upstream="https://api.commonstack.ai/v1")
providers = ProvidersConfig(providers={
"minimax": ProviderEntry(
name="minimax",
api_key="mm-key-123",
base_url=base_url,
models=["minimax/minimax-m3"],
),
})
app = create_app(
upstream="https://primary.example/v1",
providers_config=providers,
)
client = TestClient(app, raise_server_exceptions=False)
resp = client.post("/v1/chat/completions", json={
"model": "minimax/minimax-m2.1",
"model": "minimax/minimax-m3",
"max_tokens": 32,
"messages": [{"role": "user", "content": "hello"}],
})
Expand All @@ -2031,10 +2062,13 @@ def handler(request: httpx.Request) -> httpx.Response:
assert resp.headers["x-uncommon-route-requested-transport"] == "openai-chat"
assert resp.headers["x-uncommon-route-transport"] == "openai-chat"
assert resp.headers["x-uncommon-route-transport-source"] == "ingress-policy"
assert captured["url"] == "https://api.commonstack.ai/v1/chat/completions"
assert captured["url"] == expected_url
headers = captured["headers"]
assert isinstance(headers, dict)
assert headers["authorization"] == "Bearer mm-key-123"
body = captured["body"]
assert isinstance(body, dict)
assert body["model"] == "minimax/minimax-m2.1"
assert body["model"] == "MiniMax-M3"
finally:
asyncio.run(async_client.aclose())

Expand Down
25 changes: 23 additions & 2 deletions tests/test_cache_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import json

from uncommon_route.cache_support import apply_anthropic_cache_breakpoints, parse_stream_usage_metrics
from uncommon_route.router.types import ModelPricing
from uncommon_route.cache_support import apply_anthropic_cache_breakpoints, estimate_usage_cost, parse_stream_usage_metrics
from uncommon_route.router.types import ModelPricing, ModelPricingTier


def test_anthropic_cache_breakpoints_do_not_upgrade_after_existing_5m() -> None:
Expand Down Expand Up @@ -88,3 +88,24 @@ def test_stream_usage_falls_back_to_reasoning_content_tokens_when_usage_missing(
assert usage.output_tokens > 0
assert usage.total_tokens == usage.output_tokens
assert usage.actual_cost is not None


def test_usage_cost_applies_long_context_pricing_tier() -> None:
pricing = ModelPricing(
0.30,
1.20,
pricing_tiers=(
ModelPricingTier("standard", 0.30, 1.20, input_tokens_lte=512_000),
ModelPricingTier("standard", 0.60, 2.40, input_tokens_gt=512_000),
),
)

cost = estimate_usage_cost(
input_tokens_uncached=600_000,
output_tokens=100_000,
cache_read_input_tokens=0,
cache_write_input_tokens=0,
pricing=pricing,
)

assert cost == 0.60
39 changes: 38 additions & 1 deletion tests/test_providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
ProviderEntry,
add_provider,
load_providers,
resolve_upstream_model,
remove_provider,
select_preferred_model,
)
from uncommon_route.router.config import PROVIDER_MODEL_CAPABILITIES, PROVIDER_MODEL_PRICING
from uncommon_route.router.types import RoutingFeatures, Tier


Expand Down Expand Up @@ -41,12 +43,47 @@ def test_add_provider_with_plan(self) -> None:
cfg = add_provider("minimax", "eyJ-test", plan="coding-plan")
entry = cfg.providers["minimax"]
assert entry.plan == "coding-plan"
assert "minimax/minimax-m2.5" in entry.models
assert entry.models == [
"minimax/minimax-m2.5",
"minimax/minimax-m3",
"minimax/minimax-m2.7",
]

def test_add_provider_custom_url(self) -> None:
cfg = add_provider("openai", "sk-openai", base_url="https://my-proxy.com/v1")
assert cfg.providers["openai"].base_url == "https://my-proxy.com/v1"

def test_resolve_minimax_upstream_model_ids(self) -> None:
assert resolve_upstream_model("minimax", "minimax/minimax-m2.5") == "MiniMax-M2.5"
assert resolve_upstream_model("minimax", "minimax/minimax-m3") == "MiniMax-M3"
assert resolve_upstream_model("minimax", "minimax/minimax-m2.7") == "MiniMax-M2.7"
assert resolve_upstream_model("minimax", "minimax/unknown") == "minimax/unknown"

def test_minimax_provider_model_metadata(self) -> None:
m3_pricing = PROVIDER_MODEL_PRICING["minimax/minimax-m3"]
assert m3_pricing.for_usage(512_000).input_price == 0.30
assert m3_pricing.for_usage(512_001).input_price == 0.60
assert m3_pricing.for_usage(512_000, "priority").input_price == 0.45
assert m3_pricing.for_usage(512_001, "priority").input_price == 0.90

m27_pricing = PROVIDER_MODEL_PRICING["minimax/minimax-m2.7"]
assert m27_pricing.cached_input_price == 0.06
assert m27_pricing.cache_write_price == 0.375

m3_capabilities = PROVIDER_MODEL_CAPABILITIES["minimax/minimax-m3"]
assert m3_capabilities.context_window == 1_000_000
assert m3_capabilities.input_modalities == ("text", "image", "video")
assert m3_capabilities.thinking_modes == ("adaptive", "disabled")
assert m3_capabilities.vision is True
assert m3_capabilities.reasoning is True

m27_capabilities = PROVIDER_MODEL_CAPABILITIES["minimax/minimax-m2.7"]
assert m27_capabilities.context_window == 204_800
assert m27_capabilities.input_modalities == ("text",)
assert m27_capabilities.thinking_modes == ("always_on",)
assert m27_capabilities.vision is False
assert m27_capabilities.reasoning is True

def test_add_provider_custom_models(self) -> None:
cfg = add_provider(
"custom",
Expand Down
118 changes: 118 additions & 0 deletions tests/test_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from uncommon_route.model_experience import InMemoryModelExperienceStorage, ModelExperienceStore
from uncommon_route.providers import ProviderEntry, ProvidersConfig
from uncommon_route.proxy import (
UpstreamSemanticCompressor,
_extract_current_message,
_extract_prompt,
_normalize_reasoning_content_chunk,
Expand Down Expand Up @@ -77,6 +78,123 @@ def _build_test_mapper(*model_ids: str) -> ModelMapper:
return mapper


class TestUpstreamSemanticCompressor:
def test_direct_minimax_provider_uses_upstream_model_id(self) -> None:
providers = ProvidersConfig(providers={
"minimax": ProviderEntry(
name="minimax",
api_key="test-key",
base_url="https://api.minimax.io/v1",
models=["minimax/minimax-m3"],
),
})
compressor = UpstreamSemanticCompressor(
upstream_chat="https://primary.example/v1/chat/completions",
primary_api_key="primary-test-key",
providers_config=providers,
model_mapper=ModelMapper("https://primary.example/v1"),
composition_policy=CompositionPolicy(),
)

resolved = compressor._resolve_request(
"minimax/minimax-m3",
httpx.Request("POST", "https://local.example/v1/chat/completions"),
)

assert resolved is not None
target_url, headers, upstream_model = resolved
assert target_url == "https://api.minimax.io/v1/chat/completions"
assert headers["authorization"] == "Bearer test-key"
assert upstream_model == "MiniMax-M3"


class TestProviderModelMetadata:
def test_minimax_byok_metadata_is_available_to_virtual_routing(
self,
monkeypatch: pytest.MonkeyPatch,
) -> None:
routed: dict[str, object] = {}

def fake_route(*_args, **kwargs) -> RoutingDecision:
routed["pricing"] = kwargs["pricing"]
routed["capabilities"] = kwargs["model_capabilities"]
return RoutingDecision(
model="minimax/minimax-m3",
tier=Tier.COMPLEX,
capability_lane=CapabilityLane.VISION,
served_quality=ServedQuality.PREMIUM,
served_quality_target=ServedQuality.PREMIUM,
served_quality_floor=ServedQuality.PREMIUM,
continuity_quality_floor=kwargs["routing_features"].continuity_quality_floor,
mode=RoutingMode.AUTO,
confidence=0.9,
method="provider metadata test",
reasoning="provider metadata test",
cost_estimate=0.001,
baseline_cost=0.002,
savings=0.5,
routing_features=kwargs["routing_features"],
)

captured: dict[str, object] = {}

def handler(request: httpx.Request) -> httpx.Response:
captured["body"] = json.loads(request.content.decode("utf-8"))
return httpx.Response(
200,
json={
"id": "chatcmpl_minimax_metadata",
"object": "chat.completion",
"created": 1,
"model": "MiniMax-M3",
"choices": [{
"index": 0,
"message": {"role": "assistant", "content": "ok"},
"finish_reason": "stop",
}],
"usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2},
},
headers={"content-type": "application/json"},
)

providers = ProvidersConfig(providers={
"minimax": ProviderEntry(
name="minimax",
api_key="test-key",
base_url="https://api.minimax.io/v1",
models=["minimax/minimax-m3", "minimax/minimax-m2.7"],
),
})
async_client = httpx.AsyncClient(transport=httpx.MockTransport(handler))
monkeypatch.setattr("uncommon_route.proxy._get_client", lambda: async_client)
monkeypatch.setattr("uncommon_route.proxy.route", fake_route)

try:
app = create_app(
upstream="https://primary.example/v1",
providers_config=providers,
spend_control=SpendControl(storage=InMemorySpendControlStorage()),
)
client = TestClient(app, raise_server_exceptions=False)
resp = client.post("/v1/chat/completions", json={
"model": "uncommon-route/auto",
"messages": [{"role": "user", "content": "describe this image"}],
})

assert resp.status_code == 200
pricing = routed["pricing"]
capabilities = routed["capabilities"]
assert isinstance(pricing, dict)
assert isinstance(capabilities, dict)
assert pricing["minimax/minimax-m3"].cached_input_price == 0.06
assert pricing["minimax/minimax-m2.7"].cache_write_price == 0.375
assert capabilities["minimax/minimax-m3"].vision is True
assert capabilities["minimax/minimax-m2.7"].thinking_modes == ("always_on",)
assert captured["body"]["model"] == "MiniMax-M3"
finally:
asyncio.run(async_client.aclose())


class TestPromptExtraction:
def test_extract_prompt_ignores_claude_code_wrapper_blocks(self) -> None:
prompt, system_prompt, max_tokens = _extract_prompt({
Expand Down
Loading