llmshim 0.6.0__tar.gz → 0.7.0__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.
- {llmshim-0.6.0 → llmshim-0.7.0}/Cargo.lock +1 -1
- {llmshim-0.6.0 → llmshim-0.7.0}/Cargo.toml +1 -1
- {llmshim-0.6.0 → llmshim-0.7.0}/PKG-INFO +1 -1
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/proxy/scaling.md +12 -5
- {llmshim-0.6.0 → llmshim-0.7.0}/src/cost.rs +57 -11
- {llmshim-0.6.0 → llmshim-0.7.0}/src/proxy/types.rs +6 -1
- {llmshim-0.6.0 → llmshim-0.7.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/.github/workflows/catalog-refresh.yml +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/.github/workflows/pages.yml +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/.github/workflows/release.yml +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/.gitignore +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/CLAUDE.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/CODE_OF_CONDUCT.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/CONTRIBUTING.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/LICENSE-APACHE +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/LICENSE-MIT +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/NOTICE +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/README.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/SECURITY.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/benchmarks/bench.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/benchmarks/bench_python.py +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/benchmarks/gateway_loadtest.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/benchmarks/loadtest.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/crates/llmshim-catalog/Cargo.toml +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/crates/llmshim-catalog/LICENSE-APACHE +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/crates/llmshim-catalog/LICENSE-MIT +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/crates/llmshim-catalog/README.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/crates/llmshim-catalog/data/LICENSE.models.dev +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/crates/llmshim-catalog/data/README.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/crates/llmshim-catalog/data/models.dev.json +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/crates/llmshim-catalog/src/aliases.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/crates/llmshim-catalog/src/builtin.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/crates/llmshim-catalog/src/capabilities.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/crates/llmshim-catalog/src/lib.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/crates/llmshim-catalog/src/merge.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/crates/llmshim-catalog/src/parse.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/crates/llmshim-catalog/src/refresh.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/crates/llmshim-catalog/src/types.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/crates/llmshim-catalog/tests/catalog.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/crates/llmshim-catalog/tests/refresh.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/.gitignore +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/book.toml +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/mermaid-init.js +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/mermaid.min.js +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/SUMMARY.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/concepts/contracts.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/concepts/conversations.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/concepts/portability.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/concepts/routing.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/concepts/translation-flow.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/guides/caching.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/guides/capabilities.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/guides/fallbacks.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/guides/images.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/guides/native-controls.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/guides/reasoning.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/guides/schemas.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/guides/streaming.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/guides/tools.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/introduction.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/proxy/deployment.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/proxy/http-api.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/proxy/native-apis.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/reference/api.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/reference/cli.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/reference/configuration.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/reference/errors.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/reference/models.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/reference/providers.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/reference/request-fields.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/reference/surfaces.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/start/choose.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/start/cli.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/start/clients.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/start/configure.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/start/proxy.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/docs/src/start/rust.md +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/examples/chat.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/examples/stream.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/llmshim/__init__.py +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/llmshim/_client.py +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/llmshim/_server.py +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/llmshim/types.py +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/pyproject.toml +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/breaker.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/cache.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/cli.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/client.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/config.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/env.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/error/normalize.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/error.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/fallback.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/gateway/auth.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/gateway/distributed.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/gateway/http.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/gateway/idempotency.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/gateway/metrics.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/gateway/mod.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/gateway/quota.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/lib.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/log.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/main.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/models.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/provider.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/providers/anthropic.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/providers/anthropic_reasoning.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/providers/anthropic_signature.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/providers/chatgpt/auth.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/providers/chatgpt/mod.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/providers/chatgpt/streaming.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/providers/gemini.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/providers/mod.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/providers/openai.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/providers/openai_compat.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/providers/openrouter.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/providers/xai.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/proxy/convert.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/proxy/error.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/proxy/handlers.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/proxy/health.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/proxy/mod.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/proxy/ratelimit.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/proxy/wire/mod.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/proxy/wire/receipts.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/reasoning/normalize.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/reasoning.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/router.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/schema/memo.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/schema/mod.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/schema/validate.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/schema/walk.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/shim.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/streaming.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/toolcall/streaming.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/toolcall.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/usage.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/src/vision.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/fixtures/chatgpt-red.png +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/integration.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/integration_chatgpt.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/integration_chatgpt_proxy.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/integration_current_models.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/integration_fallback.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/integration_gemini.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/integration_gemini_tools.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/integration_long_context.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/integration_multimodel.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/integration_openrouter.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/integration_proxy.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/integration_sglang.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/integration_thinking.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/integration_tool_roundtrip.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/integration_vision.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/integration_xai.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/support/completion_status.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_advertised_models.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_anthropic.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_cache.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_chatgpt.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_cli.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_fable.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_fallback.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_fast_mode.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_gemini.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_log.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_models.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_multimodel.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_openai.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_openai_compat.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_openrouter.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_provider_contracts.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_proxy.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_proxy_convert.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_reasoning.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_reasoning_profile.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_router.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_schema.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_shim.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_signature.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_sse.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_toolcall.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_tools.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_usage.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_vision.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_wire.rs +0 -0
- {llmshim-0.6.0 → llmshim-0.7.0}/tests/unit_xai.rs +0 -0
|
@@ -106,11 +106,18 @@ buckets. A gateway key's identity may carry `budget_usd` and an optional
|
|
|
106
106
|
{"sk-example": {"tenant": "acme", "tier": 1, "budget_usd": 100, "budget_window_secs": 86400}}
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
-
Cost is only knowable after a response, so the cap is checked before dispatch
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
109
|
+
Cost is only knowable after a response, so the cap is checked before dispatch and
|
|
110
|
+
charged after. Everything admitted between the last charge and the next check
|
|
111
|
+
passes, so the overshoot bound is **admitted concurrency × the most expensive
|
|
112
|
+
request**, multiplied again across replicas that have not yet shared their
|
|
113
|
+
ledger. Size a cap with that headroom in mind rather than as a hard ceiling.
|
|
114
|
+
|
|
115
|
+
A response the catalog cannot price at all is **not** charged — recording zero
|
|
116
|
+
would let an unpriced model run forever under a budget. A model that prices only
|
|
117
|
+
*some* token classes is charged at its highest published rate for the rest, so a
|
|
118
|
+
partial price bounds the charge from above instead of voiding it: 2,537 of the
|
|
119
|
+
7,461 priced models in the catalog publish no `cache_read` rate, and voiding
|
|
120
|
+
those would have reopened this same hole one layer down. So that a cap cannot silently stop
|
|
114
121
|
binding, a request whose target has **no catalog price is refused before it runs**
|
|
115
122
|
when a budget is set:
|
|
116
123
|
|
|
@@ -29,6 +29,27 @@ fn charge(tokens: u64, rate: Option<f64>) -> Option<f64> {
|
|
|
29
29
|
Some(tokens as f64 * rate? / PER_MILLION)
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/// The highest rate this model publishes, used where a class has none of its own.
|
|
33
|
+
///
|
|
34
|
+
/// Catalogs price partially and often: **2,537 of 7,461 priced models in the
|
|
35
|
+
/// vendored snapshot carry no `cache_read` rate and 5,892 no `cache_write`**,
|
|
36
|
+
/// including `gpt-5-pro` and `o3-pro`, priced `{input, output}` only. Returning
|
|
37
|
+
/// `None` for those made the whole response unpriceable the moment it reported a
|
|
38
|
+
/// cached token — and a discarded charge is a spend cap that silently stops
|
|
39
|
+
/// binding, which is the failure this crate exists to prevent.
|
|
40
|
+
///
|
|
41
|
+
/// Substituting the highest published rate can only over-estimate, never under.
|
|
42
|
+
/// For a cap that is the safe direction: it spends a budget slightly early, where
|
|
43
|
+
/// under-estimating spends it forever.
|
|
44
|
+
fn highest_published_rate(cost: &Cost) -> Option<f64> {
|
|
45
|
+
[cost.input, cost.output, cost.cache_read, cost.cache_write]
|
|
46
|
+
.into_iter()
|
|
47
|
+
.flatten()
|
|
48
|
+
.fold(None, |best: Option<f64>, rate| {
|
|
49
|
+
Some(best.map_or(rate, |b| b.max(rate)))
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
32
53
|
/// Price one normalized usage object.
|
|
33
54
|
///
|
|
34
55
|
/// Input is charged on `uncached_input_tokens` — the prompt minus whatever
|
|
@@ -53,11 +74,17 @@ pub fn price(usage: &Value, cost: &Cost) -> Option<f64> {
|
|
|
53
74
|
return None;
|
|
54
75
|
}
|
|
55
76
|
|
|
77
|
+
// A class the model does not price falls back to its highest published rate
|
|
78
|
+
// rather than voiding the total. See `highest_published_rate`: the result is
|
|
79
|
+
// an upper bound, which is the only safe direction for a budget.
|
|
80
|
+
let ceiling = highest_published_rate(cost);
|
|
81
|
+
let rate_for = |own: Option<f64>| own.or(ceiling);
|
|
82
|
+
|
|
56
83
|
Some(
|
|
57
|
-
charge(input, cost.input)?
|
|
58
|
-
+ charge(count(usage, "completion_tokens"), cost.output)?
|
|
59
|
-
+ charge(cache_read, cost.cache_read)?
|
|
60
|
-
+ charge(cache_write, cost.cache_write)?,
|
|
84
|
+
charge(input, rate_for(cost.input))?
|
|
85
|
+
+ charge(count(usage, "completion_tokens"), rate_for(cost.output))?
|
|
86
|
+
+ charge(cache_read, rate_for(cost.cache_read))?
|
|
87
|
+
+ charge(cache_write, rate_for(cost.cache_write))?,
|
|
61
88
|
)
|
|
62
89
|
}
|
|
63
90
|
|
|
@@ -221,14 +248,21 @@ mod tests {
|
|
|
221
248
|
}
|
|
222
249
|
|
|
223
250
|
#[test]
|
|
224
|
-
fn
|
|
251
|
+
fn a_missing_cache_rate_is_charged_at_the_models_highest_rate() {
|
|
252
|
+
// This replaced an assertion that a used-but-unpriced class voids the
|
|
253
|
+
// total. That instinct was right — never under-report — but the remedy
|
|
254
|
+
// was wrong: `None` is discarded by `SpendCap::record`, so a spend cap
|
|
255
|
+
// silently stopped binding for the 2,537 catalogued models that publish
|
|
256
|
+
// no `cache_read` rate. An upper bound honours the same principle
|
|
257
|
+
// without the hole.
|
|
225
258
|
let partial = Cost {
|
|
226
259
|
input: Some(3.0),
|
|
227
260
|
output: Some(15.0),
|
|
228
261
|
cache_read: None,
|
|
229
262
|
cache_write: None,
|
|
230
263
|
};
|
|
231
|
-
|
|
264
|
+
|
|
265
|
+
// No cache tokens: the missing rates never come into play.
|
|
232
266
|
close(
|
|
233
267
|
price(
|
|
234
268
|
&json!({"uncached_input_tokens": 1_000_000, "completion_tokens": 0, "cache_read_tokens": 0}),
|
|
@@ -236,14 +270,26 @@ mod tests {
|
|
|
236
270
|
),
|
|
237
271
|
3.0,
|
|
238
272
|
);
|
|
239
|
-
|
|
240
|
-
|
|
273
|
+
|
|
274
|
+
// Cache tokens used with no rate of their own: charged at 15.0, the
|
|
275
|
+
// highest rate this model publishes — an over-estimate, never an under.
|
|
276
|
+
close(
|
|
241
277
|
price(
|
|
242
|
-
&json!({"uncached_input_tokens": 1_000_000, "cache_read_tokens":
|
|
278
|
+
&json!({"uncached_input_tokens": 1_000_000, "cache_read_tokens": 1_000_000}),
|
|
243
279
|
&partial,
|
|
244
280
|
),
|
|
245
|
-
|
|
246
|
-
|
|
281
|
+
18.0,
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
// The bound must never fall below what a complete price would charge.
|
|
285
|
+
let complete = Cost {
|
|
286
|
+
cache_read: Some(0.3),
|
|
287
|
+
..partial
|
|
288
|
+
};
|
|
289
|
+
let usage = json!({"uncached_input_tokens": 1_000_000, "cache_read_tokens": 1_000_000});
|
|
290
|
+
assert!(
|
|
291
|
+
price(&usage, &partial).unwrap() >= price(&usage, &complete).unwrap(),
|
|
292
|
+
"a fallback rate must bound the real one from above, or a cap under-charges"
|
|
247
293
|
);
|
|
248
294
|
}
|
|
249
295
|
|
|
@@ -119,7 +119,12 @@ pub struct Usage {
|
|
|
119
119
|
pub cache_read_tokens: u64,
|
|
120
120
|
pub cache_write_tokens: u64,
|
|
121
121
|
/// USD charged for this response. `null` means the catalog carries no price
|
|
122
|
-
/// for the model — it never means free.
|
|
122
|
+
/// for the model at all — it never means free.
|
|
123
|
+
///
|
|
124
|
+
/// Where a model prices some token classes and not others, the unpriced ones
|
|
125
|
+
/// are charged at its highest published rate, so this is an **upper bound**
|
|
126
|
+
/// rather than `null`. Under-reporting would let a spend cap stop binding;
|
|
127
|
+
/// over-reporting merely spends a budget slightly early. See `llmshim::cost`.
|
|
123
128
|
pub cost_usd: Option<f64>,
|
|
124
129
|
}
|
|
125
130
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|