Toolkit Cost Optimizer: LLM spend and routing analyzer
toolkit-opt answers one question from the logs you already have: what would this LLM traffic cost at the same quality if it were routed differently?
It reads your gateway spend logs, OpenTelemetry GenAI traces or provider usage exports, prices every request from its token classes (input, cache reads and writes, output, reasoning) against a dated price table, joins per-request quality scores, and finds the cheapest tier-to-model routing that keeps quality above a floor. The result is a report you can sign and gate in CI, a cost/quality Pareto frontier, and a LiteLLM proxy config you can deploy.
It is an offline analyzer with no runtime dependencies. It does not call any LLM provider, proxy traffic, or collect logs for you.
Capabilities
| Command | Status | What it does |
|---|---|---|
ingest |
Working | Converts LiteLLM spend logs and logging payloads, OpenTelemetry GenAI spans (OTLP/JSON) and OpenAI/Anthropic usage exports into log rows. Reads files you export; it does not connect to a gateway, collector or provider API. |
route |
Working | Counterfactual routing under a quality floor: joins per-request quality scores (from log rows or eval reports) to your traffic, prices every tier-to-model policy, and reports the cheapest policy that keeps quality (optionally at a confidence level), the savings against today's routing, and the cost/quality Pareto frontier. Writes the chosen policy and a LiteLLM proxy config with budgets. |
simulate |
Working (cost only) | Re-prices each request under a given tier-to-model policy from its token classes and a dated price table covering 35 current Anthropic, OpenAI and Gemini models. It cannot predict latency, success or output quality on the target model, so it does not report them; route handles quality. |
summarize |
Working | Per model: request count, success rate, total logged cost, p50/p95 latency, and how many rows each figure rests on. Measurements a row does not carry are reported as null, never as zero. |
validate |
Working | Checks each row against the row schema and reports issues by field. Every other command also validates every row and refuses invalid input. |
recommend |
Working (simple) | Picks the model with the lowest average logged cost_usd among models that meet --max-p95-ms, --min-success and --min-samples. No quality signal beyond success; prefer route. |
| Report envelope output | Working | Every command prints an in-toto Statement v1 report (canonical JSON with input digests and a pass/fail/error verdict). See Output. |
Human-readable output (--format table/markdown) |
Planned | Reports are JSON only; pipe them through python -m json.tool or jq. |
| Live ingestion (streaming from a gateway or collector) | Planned | Export to a file and run ingest. |
| Latency prediction for a target model | Planned | Not modeled: route optimizes cost under a quality floor only. |
Install
Python 3.10 or newer; no runtime dependencies. There is no PyPI release yet, so install from source:
git clone https://github.com/AKIVA-AI/toolkit-cost-optimizer.git
cd toolkit-cost-optimizer
pip install -e .
Five-minute example
The examples/ folder holds a synthetic but realistic week of traffic for a support assistant: 160 LiteLLM spend-log rows (examples/spend_logs.json), all served by claude-opus-5, tagged tier:faq or tier:escalation, with prompt caching on a shared system prompt and a few failures. examples/quality.jsonl holds 30 graded replays per tier for four candidate models. examples/generate.py regenerates both.
1. Ingest the spend logs into normalized rows, taking the tier from the tier: request tag:
toolkit-opt ingest --format litellm-spendlogs --input examples/spend_logs.json \
--rows rows.jsonl --tier-from tier
The report says "records":160,"rows_written":160,"skipped":0.
2. See what you spend today:
toolkit-opt summarize --input rows.jsonl
claude-opus-5: 160 requests, success rate 0.975, logged spend $3.575365, p50 5.2 s.
3. Find the cheapest routing at the same quality, and write the policy and a LiteLLM config:
toolkit-opt route --input rows.jsonl --quality-rows examples/quality.jsonl \
--policy-out chosen.json --litellm-config litellm.yaml --out route.json
From route.json (predicate.summary):
{"baseline": {"cost_usd": 3.62312, "quality": 0.924198, "logged_cost_usd": 3.575365},
"quality_floor": 0.924198, "quality_floor_source": "baseline",
"chosen": {"policy": {"escalation": "claude-sonnet-5", "faq": "claude-sonnet-5"},
"cost_usd": 1.449248, "quality": 0.925427, "quality_se": 0.009051},
"savings_usd": 2.173872, "savings_pct": 60.0,
"policies_evaluated": 16, "frontier_size": 9}
The baseline reprices today's routing at list price, including the prompt tokens of failed requests, which is why it is slightly above the logged spend. Moving both tiers to claude-sonnet-5 keeps the estimated quality (0.925 against 0.924) for 60% less. predicate.details.frontier lists the nine cost/quality trade-offs, from all-gemini-2.5-flash ($0.28, quality 0.83) to claude-opus-5 on escalations ($3.05, 0.93). Add --confidence 0.95 to require the lower confidence bound, not the mean, to meet the floor.
4. Check the chosen policy request by request, and deploy it:
toolkit-opt simulate --input rows.jsonl --policy chosen.json # total_cost_usd 1.449248
cat litellm.yaml # LiteLLM proxy config with 30-day budgets
Command reference
toolkit-opt ingest --format FORMAT --input EXPORT --rows rows.jsonl [--tier-from KEY]
toolkit-opt validate --input rows.jsonl
toolkit-opt summarize --input rows.jsonl
toolkit-opt simulate --input rows.jsonl --policy policy.json [--prices prices.json]
toolkit-opt route --input rows.jsonl [--quality-rows q.jsonl] [--eval MODEL=report.json]
[--min-quality Q] [--confidence 0.95] [--candidates a,b]
[--policy-out policy.json] [--litellm-config litellm.yaml]
toolkit-opt recommend --input rows.jsonl --max-p95-ms 3000 --min-success 0.99 --min-samples 50
Every command accepts --out report.json (write the report envelope to a file as well), --coerce-numeric-strings and --legacy-json (deprecated pre-1.0 output). Use --verbose for debug logging and --json-log for structured JSON logs on stderr.
Where the cost numbers come from
This matters, because the commands use different inputs:
summarizeandrecommendusecost_usdfrom your log rows. Whatever you logged is what gets summed and compared. If your logger computed cost from an out-of-date price, these numbers inherit that error.simulateandrouteignore the loggedcost_usdwhen pricing. They price each request's token classes (see Token classes) at list price for the model a policy routes it to. A row that cannot be priced is counted as unpriced with a reason, never priced from its logged cost. The logged cost is reported separately aslogged_cost_usd, so you can compare the two.
Route: what would this cost at the same quality?
route answers the question the rest of the tool builds up to: given your traffic and how well each model does on each kind of request, which routing is cheapest without losing quality, and how much would it save?
toolkit-opt route --input rows.jsonl --quality-rows shadow_scores.jsonl \
--eval gpt-5.4-mini=evals/mini.json --min-quality-samples 30 --policy-out chosen.json
Inputs
- Traffic (
--input): log rows withtokens_in/tokens_outand a known request count. Each row'stier(ordefault) is a traffic segment the router can send to a different model. - Quality: per-request scores in [0, 1] for (tier, model) pairs, from any of:
- a
qualityfield on traffic rows (the model that served them); --quality-rows FILE.jsonl: rows used only for quality, for example the same prompts replayed on candidate models (they are not counted as traffic);--eval MODEL=REPORT.json: an eval report envelope whosepredicate.details.cases[].scoremeasureMODEL, such as aneval.runreport from an evaluation harness. It is read as JSON by that shape; no other package is needed. Cases taggedtier:NAMEcount for that tier; untagged cases count for every tier that has no data of its own. Anerrorreport is refused.
- a
--candidates a,b,climits the models considered (default: every model with quality data). A model is a candidate for a tier only if it is in the price table, has at least--min-quality-samplesscores (default 30) for that tier (or for all tiers), and can price every row of the tier.
What it computes
- For each tier and candidate: the tier's cost if every request went to that model (token classes at list price), and the model's mean quality there with its standard error.
- For every policy (one model per tier): cost = sum of tier costs; quality = request-weighted mean of tier qualities; standard error from the stratified-sampling variance
sum (w_t^2 s^2 / n)(Cochran, Sampling Techniques, ch. 5). An estimate shared by several tiers enters once with their summed weight, because their errors are correlated. - Baseline: the traffic as actually routed, repriced at list price, and its quality from the same estimates.
- Floor:
--min-quality Q, or by default the baseline's quality ("same quality"). With--confidence 0.95, the one-sided 95% lower bound of a policy's quality must meet the floor, not just its mean. - Result: the cheapest policy meeting the floor, the savings against the baseline, and the cost/quality Pareto frontier (every policy that no other policy beats on both cost and quality).
--policy-outwrites the chosen routing as a policy file thatsimulatereads.
predicate.summary: baseline (cost_usd, quality, logged_cost_usd), quality_floor and its source, chosen (policy, cost_usd, quality, quality_se, quality_lower_bound with --confidence), savings_usd, savings_pct, policies_evaluated, frontier_size, feasible. predicate.details: per-tier options, excluded (tier, model) pairs with reasons, the frontier, and the chosen policy file. Exit 4 when no policy meets the floor or a tier has no candidate.
LiteLLM config: --litellm-config litellm.yaml writes a LiteLLM proxy config for the chosen policy, so the result can be deployed as is:
model_list:
- model_name: "complex" # clients request the tier name
litellm_params:
model: "anthropic/claude-sonnet-5"
api_key: "os.environ/ANTHROPIC_API_KEY"
max_budget: 1.56 # per-tier budget (USD)
budget_duration: "30d"
- model_name: "simple"
litellm_params:
model: "gemini/gemini-2.5-flash"
api_key: "os.environ/GEMINI_API_KEY"
max_budget: 0.9
budget_duration: "30d"
litellm_settings:
max_budget: 2.45 # proxy-wide budget
budget_duration: "30d"
- The provider prefix and key variable come from the price table's
provider(openai,anthropic,geminimap toOPENAI_API_KEY,ANTHROPIC_API_KEY,GEMINI_API_KEY). A model without a provider is written bare and listed inmodels_without_provider. - Budgets are the chosen policy's cost over the log window, scaled to
--budget-duration(default30d;s,m,h,dunits) and multiplied by--budget-headroom(default 1.2), rounded up to the cent. With a single-timestamp log there is no window, so budgets are left out. - The shape follows the LiteLLM docs. A config written by
routewas loaded intolitellm.Router(litellm 1.85.0) with its deployment budgets to confirm LiteLLM accepts it; the test suite checks the structure with a YAML parser and does not depend on LiteLLM.
Limits: quality estimates are only as good as the scores you supply, and they assume the scored requests represent the tier's traffic. Token counts are reused across models; different tokenizers produce different counts for the same text (Anthropic notes about 30% more tokens on its newer tokenizer), so cross-provider savings are estimates. Latency on the target model is not predicted. Policies are enumerated exhaustively up to --max-policies (default 100,000).
Ingest: from your gateway, traces or provider bill to log rows
ingest converts an export you already have into version 2 log rows that every other command reads:
toolkit-opt ingest --format litellm-spendlogs --input spend_logs.json --rows rows.jsonl --tier-from tier
toolkit-opt summarize --input rows.jsonl
--format |
Input | Where to get it | What a row is |
|---|---|---|---|
litellm-spendlogs |
JSON list of LiteLLM_SpendLogs rows, or {"data": [...]} |
LiteLLM proxy GET /spend/logs?summarize=false or GET /spend/logs/v2 |
one request |
litellm-payload |
StandardLoggingPayload objects, JSON array or NDJSON |
LiteLLM s3_v2, gcs_bucket or generic_api logging callbacks |
one request |
otel |
OTLP/JSON trace data, one {"resourceSpans": ...} per line |
OpenTelemetry Collector file exporter (JSON), spans with GenAI semantic-convention gen_ai.* attributes |
one inference span (chat, text_completion, generate_content) |
openai-usage |
Usage API completions page(s) | GET /v1/organization/usage/completions, grouped by model |
one time bucket per model, with requests |
anthropic-usage |
Usage report page(s) | GET /v1/organizations/usage_report/messages, grouped by model |
one time bucket per model, request count unknown |
How fields are mapped:
- Tokens follow the row schema:
tokens_inincludes cache reads and writes. LiteLLM'sprompt_tokensalready does (for Anthropic it adds cache reads and cache creation toinput_tokens); OpenAI's usageinput_tokensdoes per its API spec; for OpenTelemetry the GenAI conventions saygen_ai.usage.input_tokensSHOULD include cached tokens; for Anthropic's usage report the tool addsuncached_input_tokens,cache_read_input_tokensand bothcache_creationcounts. Reasoning comes fromcompletion_tokens_details.reasoning_tokens(LiteLLM) orgen_ai.usage.reasoning.output_tokens(OpenTelemetry). - OpenTelemetry: model from
gen_ai.response.model, elsegen_ai.request.model; provider fromgen_ai.provider.name, else the oldergen_ai.system;gen_ai.usage.prompt_tokens/completion_tokensandgen_ai.usage.cache_creation.input_tokens(older names) are accepted; latency is end minus start time; a span with status code 2 (ERROR) or anerror.typeattribute issuccess: false. Spans withoutgen_ai.operation.nameand non-inference operations (tools, agents, embeddings) are ignored and counted. - LiteLLM:
spend/response_costbecomescost_usd(LiteLLM's own calculation),statusbecomessuccess, naive timestamps are read as UTC. - Usage exports produce aggregate rows (
"aggregate": true) without latency or success. Batch and non-standard service tiers (flex, priority) are skipped, because the price table models standard pricing only. --tier-from KEYfillstier: a LiteLLM request tagKEY:value(or a metadata fieldKEY, such asuser_api_key_team_alias), an OpenTelemetry span or resource attributeKEY, or a usage-export result field such asproject_idorworkspace_id.
A record that cannot become a valid row (no model, no timestamp, cache counts larger than the input total, a batch bucket) is skipped and counted by reason, and the command exits 4 so a pipeline notices. Nothing is filled in. The report's details.rows_file records the SHA-256 of the rows it wrote.
Input log schema (JSONL)
Each line is a JSON object. Two schema versions are accepted:
- Version 1: one row per request, with logged cost, latency and success. Required:
schema_version(1),created_ts,model,latency_ms,cost_usd,success. - Version 2: the normalized row that different sources can fill in part. Required:
schema_version(2),created_ts,model. Everything else is optional.
Fields:
| Field | Type | Meaning |
|---|---|---|
schema_version |
int | 1 or 2 (a JSON true or "1" is rejected) |
created_ts |
number >= 0 | request time, Unix seconds |
model |
non-empty string | the model that served the request |
latency_ms |
number >= 0 | end-to-end latency |
cost_usd |
number >= 0 | the cost you recorded for the request |
success |
bool | only JSON true/false |
tier |
non-empty string | traffic segment used by routing policies; rows without one use the policy's default_model |
tokens_in |
int >= 0 | all input tokens, including cached reads and cache writes |
tokens_cache_read |
int >= 0 | the part of tokens_in read from a prompt cache |
tokens_cache_write |
int >= 0 | the part of tokens_in written to a prompt cache |
tokens_cache_write_1h |
int >= 0 | the part of tokens_cache_write written with a 1-hour lifetime (Anthropic prices these apart) |
tokens_out |
int >= 0 | all output tokens, including reasoning/thinking tokens |
tokens_reasoning |
int >= 0 | the part of tokens_out spent on reasoning |
requests |
int >= 1 | number of requests the row stands for (default 1) |
aggregate |
bool | true for a row that sums several requests, such as a usage-export bucket. A row with aggregate: true or requests > 1 cannot carry latency_ms, success or quality; without requests its request count is unknown and is reported as unknown_request_rows |
quality |
number in [0, 1] | a per-request quality score |
request_id, provider, source |
non-empty string | identifiers kept for traceability |
Numbers must be finite JSON numbers: NaN, Infinity, booleans and numeric strings are rejected. An optional field set to null is treated as absent. Unknown extra fields are allowed. Cache and reasoning counts must not exceed the totals they are part of.
Example (version 1):
{"schema_version": 1, "created_ts": 1700000000.0, "model": "gpt-4o", "latency_ms": 1200, "cost_usd": 0.0045, "success": true, "tier": "premium", "tokens_in": 1200, "tokens_out": 150}
Every command validates every row. validate reports the issues (exit 4 when any row is invalid). summarize, recommend and simulate refuse to analyze a file with invalid rows: they exit 2 with an error report whose details.issues lists each problem and its count. recommend also needs latency_ms, success and cost_usd on every row, including version 2 rows.
If your logger writes numbers as strings ("cost_usd": "0.0045"), pass --coerce-numeric-strings to convert strings that parse completely as numbers (integers for token counts). Nothing else is converted; the report's summary.coerced_values says how many values were changed.
Policy file (JSON)
A default model plus optional tier overrides:
{
"default_model": "gpt-4o-mini",
"tiers": {
"premium": "gpt-4o"
}
}
Price table (JSON)
simulate needs a price for every model the policy can route to. If any is missing it stops with exit code 2 rather than guessing.
Built-in table
The package ships a dated table at src/toolkit_cost_latency_opt/data/model_prices.json (as_of: 2026-09-26) with 35 current models from Anthropic (Claude Fable, Opus, Sonnet and Haiku), OpenAI (GPT-6, GPT-5.x, GPT-4.1/4o, o3/o4-mini) and Google (Gemini 3.x and 2.5). Every price was checked against the provider's official pricing page on that date; the page URLs and the billing rules used are recorded in the file's sources. The prices are the standard tier in the default region: batch, flex, priority/fast mode and data-residency surcharges are not included. Provider prices change, so pass your own table for anything that matters.
Token classes
A request is priced from its token classes (see the row schema):
cost = (tokens_in - tokens_cache_read - tokens_cache_write) x input
+ tokens_cache_read x cached_input
+ (tokens_cache_write - tokens_cache_write_1h) x cache_write
+ tokens_cache_write_1h x cache_write_1h
+ (tokens_out - tokens_reasoning) x output
+ tokens_reasoning x reasoning (defaults to output)
- Reasoning tokens are billed as output tokens by Anthropic, OpenAI and Google (each page is cited in the table), so they use the output rate unless a model sets
reasoning_per_1m. - Cache writes: Anthropic prices 5-minute and 1-hour writes separately. OpenAI charges 1.25x input for GPT-5.6 and later and "no additional cache-write charge" for earlier models, so those models'
cache_write_per_1mequals their input price. Gemini has no per-token cache-write price. - Long context: some OpenAI and Gemini models charge more for the whole request when the prompt is over 272K (OpenAI) or 200K (Gemini) tokens. A per-request row above the threshold is priced at the long-context rates. An aggregate row (
requests> 1) cannot show each prompt's size, so it is priced at the standard rate and counted inbase_tier_assumed_rows. - Fail closed: a row that uses a token class its model has no price for (for example cache writes on a Gemini model) is reported as unpriced with a reason in
unpriced_reasons(missing_token_counts,no_cached_input_price,no_cache_write_price,no_cache_write_1h_price), never priced at a guessed rate. - Model names match exactly, by an alias listed in the table (
claude-haiku-4-5-20251001), or without aprovider/prefix (openai/gpt-4o). There is no fuzzy matching.
Your own table
To use negotiated rates, other providers or self-hosted models, pass --prices with the same format. Prices are USD per one million tokens and as_of is required; everything except input_per_1m and output_per_1m is optional:
{
"as_of": "2026-09-26",
"models": {
"gpt-5.4": {
"input_per_1m": 2.5, "cached_input_per_1m": 0.25, "cache_write_per_1m": 2.5,
"output_per_1m": 15.0,
"long_context": {"threshold_tokens": 272000, "input_per_1m": 5.0,
"cached_input_per_1m": 0.5, "cache_write_per_1m": 5.0, "output_per_1m": 22.5},
"provider": "openai", "aliases": ["prod-gpt-5.4"]
},
"claude-sonnet-5": {
"input_per_1m": 2.0, "cached_input_per_1m": 0.2, "cache_write_per_1m": 2.5,
"cache_write_1h_per_1m": 4.0, "output_per_1m": 10.0, "provider": "anthropic"
},
"my-local-model": {"input_per_1m": 0.0, "output_per_1m": 0.0}
}
}
Output: report envelope
Every command prints one report envelope to stdout: an in-toto Statement v1 written as canonical JSON (UTF-8, sorted keys, no insignificant whitespace, trailing newline), so the same result always has the same SHA-256. --out report.json also writes it to a file. The format is specified in docs/report-envelope.md and schemas/report-envelope.v1.json.
{"_type":"https://in-toto.io/Statement/v1",
"subject":[{"name":"logs.jsonl","digest":{"sha256":"..."}}],
"predicateType":"https://github.com/AKIVA-AI/toolkit-cost-optimizer/report/v1",
"predicate":{"tool":{"name":"toolkit-cost-optimizer","version":"..."},
"kind":"cost.simulate","created_at":"2026-09-26T18:00:00Z",
"verdict":"pass","exit_code":0,
"inputs":[{"name":"policy.json","digest":{"sha256":"..."}}],
"summary":{...},"details":{...}}}
subjectis the log file analyzed;inputsare the policy, price table and any other file the result depends on (the built-in price table is recorded asbuilt-in:model_prices.jsonwith its digest).verdictfollows the exit code:0ispass,4isfail,2/3areerror. When a command fails after it has read its input (for example an invalid policy), it still prints anerrorenvelope with the message indetails.message. When the input itself cannot be read, nothing is printed to stdout.created_atis the current UTC time, orSOURCE_DATE_EPOCHwhen that is set, which makes reports byte-for-byte reproducible.
predicate.summary per command (the numbers a CI gate reads):
kind |
summary |
details |
|---|---|---|
cost.ingest |
format, records, rows_written, skipped, skipped_reasons, ignored, ignored_reasons |
rows_file (name and SHA-256 of the rows written) |
cost.validate |
ok, total, invalid_rows (coerced_values with --coerce-numeric-strings) |
issues (kind, field, message, count) |
cost.summarize |
total_rows, total_requests, model_count, total_cost_usd |
models (per model: count, rows, success_rate, success_samples, total_cost_usd, cost_rows, p50_ms, p95_ms, latency_samples, unknown_request_rows) |
cost.recommend |
ok, recommended_model, avg_cost_usd, p95_ms, success_rate, count, thresholds (or ok: false, reason) |
models (every model considered, with eligible) |
cost.simulate |
prices (source, as_of), complete, total_rows, total_requests, priced_requests, unpriced_requests, unpriced_reasons, base_tier_assumed_rows, unknown_request_rows, total_cost_usd, logged_cost_usd |
models (per target model) |
cost.route |
see Route | per-tier options, excluded pairs, frontier, chosen policy file |
A command that refuses invalid rows reports summary.invalid_rows, summary.total_rows and details.issues in its error envelope.
--legacy-json prints the pre-1.0 output (described in schemas/cli-output.schema.json) instead. It is deprecated and will be removed in the next minor release; --out still writes the envelope.
Signing a report
Signing is not built in. Any report can be signed and verified with the optional toolkit-ml-provenance CLI:
toolkit-opt route --input rows.jsonl --quality-rows q.jsonl --out report.json
toolkit-mlsbom sign-file report.json --key signing.pem # or --sigstore (keyless)
toolkit-mlsbom verify-file report.json --public-key signing.pub
Exit codes
0success2CLI or input error: bad arguments, unreadable file, invalid rows, invalid policy, price table or eval report, policy model missing from the price table3unexpected error4result not usable as-is:validatefound invalid rows,ingestskipped records,recommendfound no model meeting the thresholds,simulatecould not price some rows (complete: false), orroutefound no policy meeting the floor
Safety notes
- Input files must be regular files (no symlinks).
- Log inputs must be
.jsonl; policy, price and eval-report inputs must be.json;ingestreads.jsonor.jsonl. - Outputs:
--outand--policy-outmust be.json,--rows.jsonl,--litellm-config.yaml/.yml; none may be a symlink. - Maximum file size is 1 GB.
- Error messages written to logs are redacted for common secret patterns.
Development
pip install -e ".[dev]"
pytest
ruff check .
pyright
Contributing and security
Contributions are welcome: see CONTRIBUTING.md and the Code of Conduct. Please report security problems privately, as described in SECURITY.md.
Releasing
Releases are cut by pushing a vX.Y.Z tag. CI runs the tests, builds the
sdist and wheel, checks them, attaches them to a GitHub Release and publishes
them to PyPI with Trusted Publishing. RELEASING.md describes
the process and how to verify a release.
License
Apache License 2.0. See LICENSE and NOTICE.
Releases before the relicensing remain available under the MIT License.
Metadata
Release files for toolkit-cost-optimizer 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| toolkit_cost_optimizer-1.0.0.tar.gz | 91.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| toolkit_cost_optimizer-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 150.4 kB
Release files / toolkit_cost_optimizer-1.0.0.tar.gz
| Download URL | toolkit_cost_optimizer-1.0.0.tar.gz |
|---|---|
| Size | 91.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
50c4be1cce3e0dc2bf6cc4a72f2af06d64e657cca3820aa25c2c60cee96ce683
|
|
BLAKE2b-256 checksum How to use checksums |
6fd954faa8741211fb099c3555545799493f5442d1ec24118e273b0fd527b6b3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.
Transparency logRelease files / toolkit_cost_optimizer-1.0.0-py3-none-any.whl
| Download URL | toolkit_cost_optimizer-1.0.0-py3-none-any.whl |
|---|---|
| Size | 58.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0b6d2a19ef750dc83d12bf68752750fa068c97cb7b9af4ba68a1c09de5b62afa
|
|
BLAKE2b-256 checksum How to use checksums |
83b4fbf4ab784bc716385193c79db85e15e1b3aca80d3339ec3469f6dda822e1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.
Transparency log