from collections.abc import Iterator from unittest.mock import AsyncMock, patch import pytest from opensquilla.engine.pricing import ( PriceEntry, PricingCache, lookup_price, reset_live_price_cache_for_tests, seed_live_price_cache_for_tests, ) @pytest.fixture(autouse=True) def reset_pricing_cache() -> Iterator[None]: reset_live_price_cache_for_tests() yield reset_live_price_cache_for_tests() def test_deepseek_v4_pro_static_price_matches_current_official( monkeypatch: pytest.MonkeyPatch, ) -> None: """Official price since 2026-05-31: $0.435/M in (miss), $0.87/M out, $0.003625/M cache hit.""" monkeypatch.setenv("OPENSQUILLA_OPENROUTER_LIVE_PRICING", "0") price = lookup_price("deepseek/deepseek-v4-pro") assert price.input_per_m == pytest.approx(0.435) assert price.output_per_m == pytest.approx(0.87) assert price.cache_read_per_m == pytest.approx(0.003625) @pytest.mark.asyncio async def test_pricing_cache_refresh_adds_openrouter_app_attribution() -> None: import httpx as _httpx cache = PricingCache(api_key="test-key", ttl_seconds=60) captured: dict[str, object] = {} mock_response = _httpx.Response( 200, json={ "data": [ { "id": "openai/gpt-4o", "pricing": {"prompt": "0.0000025", "completion": "0.00001"}, } ] }, request=_httpx.Request("GET", "https://openrouter.ai/api/v1/models"), ) with patch("opensquilla.engine.pricing.httpx.AsyncClient") as mock_client: mock_instance = AsyncMock() async def capture_get(url, *, headers): captured["url"] = url captured["headers"] = headers return mock_response mock_instance.get = AsyncMock(side_effect=capture_get) mock_client.return_value.__aenter__ = AsyncMock(return_value=mock_instance) mock_client.return_value.__aexit__ = AsyncMock(return_value=False) await cache.refresh() assert captured["url"] == "https://openrouter.ai/api/v1/models" assert captured["headers"] == { "Authorization": "Bearer test-key", "Content-Type": "application/json", "HTTP-Referer": "https://opensquilla.ai", "X-Title": "OpenSquilla", } price = cache.get_price_sync("openai/gpt-4o") assert price is not None assert price.input_per_token == 0.0000025 assert price.output_per_token == 0.00001 def test_deepseek_v4_pro_live_price_wins_over_static( monkeypatch: pytest.MonkeyPatch, ) -> None: """The hardcoded pin must no longer block live-price self-correction.""" monkeypatch.setenv("OPENSQUILLA_OPENROUTER_LIVE_PRICING", "1") reset_live_price_cache_for_tests() seed_live_price_cache_for_tests("deepseek/deepseek-v4-pro", PriceEntry(0.5, 1.0)) price = lookup_price("deepseek/deepseek-v4-pro") assert price.input_per_m == pytest.approx(0.5) assert price.output_per_m == pytest.approx(1.0) def test_versioned_deepseek_id_prefix_matches_static_entry( monkeypatch: pytest.MonkeyPatch, ) -> None: monkeypatch.setenv("OPENSQUILLA_OPENROUTER_LIVE_PRICING", "0") price = lookup_price("deepseek/deepseek-v4-pro-20260423") assert price.input_per_m == pytest.approx(0.435) def test_price_entry_cache_fields_default_none() -> None: entry = PriceEntry(3.0, 15.0) assert entry.cache_read_per_m is None assert entry.cache_write_per_m is None @pytest.mark.parametrize( ("model", "input_per_m", "output_per_m"), [ ("moonshotai/kimi-k2.7-code-20260612", 0.95, 4.0), ("kimi-k2.7-code", 0.95, 4.0), ("claude-haiku-4-5-20251001", 1.0, 5.0), ("deepseek-chat", 0.14, 0.28), ("deepseek-reasoner", 0.26, 0.38), ("glm-5.2", 1.40, 4.40), ("qwen3.7-max", 1.25, 3.75), ("qwen3.7-plus", 0.40, 1.60), ], ) def test_previously_missing_ids_now_have_static_entries( monkeypatch: pytest.MonkeyPatch, model: str, input_per_m: float, output_per_m: float, ) -> None: monkeypatch.setenv("OPENSQUILLA_OPENROUTER_LIVE_PRICING", "0") price = lookup_price(model) assert price.input_per_m == pytest.approx(input_per_m) assert price.output_per_m == pytest.approx(output_per_m) @pytest.mark.parametrize("model", ["z-ai/glm-5.1", "z-ai/glm-5.2"]) def test_glm_5_static_price_matches_openrouter_native_provider( monkeypatch: pytest.MonkeyPatch, model: str, ) -> None: monkeypatch.setenv("OPENSQUILLA_OPENROUTER_LIVE_PRICING", "0") price = lookup_price(model) assert price.input_per_m == pytest.approx(1.40) assert price.output_per_m == pytest.approx(4.40) @pytest.mark.parametrize( ("model", "input_per_m", "output_per_m"), [ ("qwen/qwen3.7-plus-20260602", 0.40, 1.60), ("qwen/qwen3.7-max", 1.25, 3.75), ("google/gemini-3-flash-preview-20251217", 0.50, 3.0), ("mistralai/mistral-large-2512", 0.50, 1.50), ("meta-llama/llama-4-maverick", 0.15, 0.60), ], ) def test_g8_ensemble_static_fallback_prices_do_not_use_generic_default( monkeypatch: pytest.MonkeyPatch, model: str, input_per_m: float, output_per_m: float, ) -> None: monkeypatch.setenv("OPENSQUILLA_OPENROUTER_LIVE_PRICING", "0") price = lookup_price(model) assert price.input_per_m == pytest.approx(input_per_m) assert price.output_per_m == pytest.approx(output_per_m) def test_claude_opus_4_8_static_price_matches_openrouter_model_catalog( monkeypatch: pytest.MonkeyPatch, ) -> None: monkeypatch.setenv("OPENSQUILLA_OPENROUTER_LIVE_PRICING", "0") price = lookup_price("anthropic/claude-opus-4.8") assert price.input_per_m == pytest.approx(5.0) assert price.output_per_m == pytest.approx(25.0) @pytest.mark.parametrize( ("model", "input_per_m", "output_per_m"), [ ("qwen-plus", 0.115, 0.287), ("qwen-flash", 0.022, 0.216), ("qwen-turbo", 0.044, 0.087), ("qwen-max", 0.345, 1.377), ], ) def test_dashscope_beijing_qwen_static_prices_match_official_model_studio_pricing( monkeypatch: pytest.MonkeyPatch, model: str, input_per_m: float, output_per_m: float, ) -> None: monkeypatch.setenv("OPENSQUILLA_OPENROUTER_LIVE_PRICING", "0") price = lookup_price(model) assert price.input_per_m == pytest.approx(input_per_m) assert price.output_per_m == pytest.approx(output_per_m) def test_dashscope_beijing_qwen_plus_smoke_usage_estimates_cost_from_tokens( monkeypatch: pytest.MonkeyPatch, ) -> None: monkeypatch.setenv("OPENSQUILLA_OPENROUTER_LIVE_PRICING", "0") price = lookup_price("qwen-plus") estimated_cost = (31 * price.input_per_m + 6 * price.output_per_m) / 1_000_000 assert estimated_cost == pytest.approx(0.000005287) def test_provider_profile_models_do_not_use_default_pricing( monkeypatch: pytest.MonkeyPatch, ) -> None: monkeypatch.setenv("OPENSQUILLA_OPENROUTER_LIVE_PRICING", "0") default = PriceEntry(3.0, 15.0) models = [ "qwen3.6-flash", "qwen3.7-plus", "qwen3.7-max", "deepseek-v4-flash", "deepseek-v4-pro", "gemini-3.1-flash-lite", "gemini-3.5-flash", "gemini-3.1-pro-preview", "gpt-4.1-nano", "gpt-4.1-mini", "gpt-4.1", "glm-5-turbo", "glm-5.2", "kimi-k2.6", "kimi-k2.7-code", "doubao-seed-1-6-flash-250828", "doubao-seed-1-6-251015", "doubao-seed-1-6-thinking-250715", "doubao-seed-2-0-mini-260215", "doubao-seed-2-0-lite-260215", "doubao-seed-2-0-pro-260215", "doubao-seed-2-0-code-preview-260215", ] for model in models: assert lookup_price(model) != default, model def test_local_embedding_model_does_not_fetch_openrouter_pricing( monkeypatch: pytest.MonkeyPatch, ) -> None: monkeypatch.setenv("OPENSQUILLA_OPENROUTER_LIVE_PRICING", "1") def fail_fetch(*_args, **_kwargs): raise AssertionError("local embedding models should not hit OpenRouter pricing") monkeypatch.setattr("opensquilla.engine.pricing._fetch_openrouter_json_sync", fail_fetch) price = lookup_price("BAAI/bge-small-zh-v1.5") assert price.input_per_m == 0 assert price.output_per_m == 0 @pytest.mark.parametrize( ("model", "input_per_m", "output_per_m"), [ ("gpt-4.1-nano", 0.10, 0.40), ("gpt-4.1-mini", 0.40, 1.60), ("gpt-4.1", 2.0, 8.0), ("gpt-5.4-nano", 0.20, 1.25), ("gpt-5.4-mini", 0.75, 4.50), ("gpt-5.5", 5.0, 30.0), ("glm-5", 1.0, 3.20), ("glm-5-turbo", 1.20, 4.0), ("glm-5.1", 1.40, 4.40), ("kimi-k2.6", 0.95, 4.0), ("kimi-k2.7-code", 0.95, 4.0), ("gemini-3.1-flash-lite", 0.25, 1.50), ("gemini-3.5-flash", 1.50, 9.0), ("gemini-3.1-pro-preview", 2.0, 12.0), ], ) def test_direct_provider_profile_estimate_prices_match_approved_static_entries( monkeypatch: pytest.MonkeyPatch, model: str, input_per_m: float, output_per_m: float, ) -> None: monkeypatch.setenv("OPENSQUILLA_OPENROUTER_LIVE_PRICING", "0") price = lookup_price(model) assert price.input_per_m == pytest.approx(input_per_m) assert price.output_per_m == pytest.approx(output_per_m) @pytest.mark.parametrize( ("model", "input_per_m", "output_per_m"), [ ("doubao-seed-2-0-mini-260215", 0.029, 0.287), ("doubao-seed-2-0-lite-260215", 0.086, 0.516), ("doubao-seed-2-0-pro-260215", 0.459, 2.294), ("doubao-seed-2-0-code-preview-260215", 0.459, 2.294), ], ) def test_volcengine_seed_2_static_prices_match_under_32k_online_inference_pricing( monkeypatch: pytest.MonkeyPatch, model: str, input_per_m: float, output_per_m: float, ) -> None: monkeypatch.setenv("OPENSQUILLA_OPENROUTER_LIVE_PRICING", "0") price = lookup_price(model) assert price.input_per_m == pytest.approx(input_per_m) assert price.output_per_m == pytest.approx(output_per_m) @pytest.mark.parametrize( ("model", "input_per_m", "output_per_m"), [ ("gpt-4.1", 2.0, 8.0), ("glm-4.5", 0.115, 0.287), ("kimi-k2.6", 0.95, 4.0), ("MiniMax-M2.7", 0.118, 0.99), ], ) def test_direct_openai_zhipu_kimi_and_minimax_prices_do_not_fall_back_to_default( monkeypatch: pytest.MonkeyPatch, model: str, input_per_m: float, output_per_m: float, ) -> None: monkeypatch.setenv("OPENSQUILLA_OPENROUTER_LIVE_PRICING", "0") price = lookup_price(model) assert price.input_per_m == pytest.approx(input_per_m) assert price.output_per_m == pytest.approx(output_per_m) def test_local_provider_is_free_even_for_unqualified_model( monkeypatch: pytest.MonkeyPatch, ) -> None: monkeypatch.setenv("OPENSQUILLA_OPENROUTER_LIVE_PRICING", "0") # Bare ollama model id misses the "ollama/" table entry -> cloud default. assert lookup_price("qwen3:4b").input_per_m == pytest.approx(3.0) # Passing the local provider makes it free regardless of the model id. free = lookup_price("qwen3:4b", provider="ollama") assert free.input_per_m == 0.0 assert free.output_per_m == 0.0 def test_local_provider_case_insensitive_and_covers_local_runtimes() -> None: for prov in ("ollama", "OLLAMA", "lm_studio", "vllm", "ovms", "local"): price = lookup_price("some-model", provider=prov) assert (price.input_per_m, price.output_per_m) == (0.0, 0.0) def test_cloud_provider_arg_does_not_zero_priced_model( monkeypatch: pytest.MonkeyPatch, ) -> None: monkeypatch.setenv("OPENSQUILLA_OPENROUTER_LIVE_PRICING", "0") price = lookup_price("claude-sonnet-4", provider="anthropic") assert price.input_per_m == pytest.approx(3.0)