Skip to content

fix(cost): bill OpenRouter and Perplexity v1.1 decisions models - #45476

Open
devin-ai-integration[bot] wants to merge 2 commits into
mainfrom
litellm_decisions_cost_rows_openrouter_perplexity
Open

devin-ai-integration[bot] wants to merge 2 commits into
mainfrom
litellm_decisions_cost_rows_openrouter_perplexity

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • 15 of OpenRouter's 16 decisions models have no cost map row
  • Perplexity's pplx-decider-v1.1-27b has none either
  • Calls to them return no x-litellm-response-cost and log $0 spend
  • openrouter/togethercomputer/... is re-priced as a Together size bucket

How it solves it:

  • Adds the 15 openrouter/ rows from OpenRouter's decisions catalog (read 2026-10-08)
  • Adds perplexity/pplx-decider-v1.1-27b from Perplexity's pricing page (read 2026-10-08)
  • Refreshes perplexity/pplx-decider-v1-27b to the same page's $0.02/1M input
  • A model's own cost map row now wins over the Together size bucket

Intentional product change: perplexity/pplx-decider-v1-27b bills $0.02/1M input instead of $0.04/1M, matching Perplexity's current pricing page (the page lists v1 and v1.1 at the same price), so spend on that model halves

User Flow

Before: a team routing classification calls to an OpenRouter or Perplexity decisions model sees every one of them logged at $0, so budgets never move

  1. The admin adds openrouter/openai/gpt-6-luna-decisions (or perplexity/pplx-decider-v1.1-27b) to config.yaml and restarts the proxy
  2. A developer sends POST https://litellm-domain/v1/decisions with that model, a ticket text, and three questions, or the same through POST https://litellm-domain/v1/systemone
  3. The 200 response carries the answers and usage.input_tokens: 378, but no x-litellm-response-cost header
  4. GET https://litellm-domain/spend/logs?request_id= (and https://litellm-domain/ui/?page=logs) shows the request with 378 prompt tokens and spend: 0.0

After: the same calls bill at the vendor's price, so the team's keys and budgets reflect them

  1. The admin adds openrouter/openai/gpt-6-luna-decisions (or perplexity/pplx-decider-v1.1-27b) to config.yaml and restarts the proxy
  2. A developer sends POST https://litellm-domain/v1/decisions with that model, a ticket text, and three questions, or the same through POST https://litellm-domain/v1/systemone
  3. The 200 response carries the answers, usage.input_tokens: 378, and x-litellm-response-cost: 3.78e-05 (378 tokens at $0.10/1M)
  4. GET https://litellm-domain/spend/logs?request_id= (and https://litellm-domain/ui/?page=logs) shows the request with spend: 3.78e-05

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • The handful of test files covering my change pass locally, e.g. uv run pytest tests/unit/<your_test_file>.py -v. Leave the suites (make test-unit-*, make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more
  • My PR passes all required CI/CD checks (e.g., lint, schema.d.ts sync check, etc.)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

Last updated: 939ce3f. QA and /live-pr-risk ran on c71b972, the last commit with a product diff

Both legs boot the same config on the same Postgres with two uvicorn workers, real OpenRouter and Perplexity keys, and LITELLM_LOCAL_MODEL_COST_MAP=True so the proxy prices from the checkout's cost map. Before runs the merge base, After runs this PR's tip. Each case sends the same request through /v1/decisions (OpenAI shape) and /v1/systemone (TypeSafe shape), then reads the request back through GET /spend/logs. openrouter/typesafe/jev-1.13 already had a row and is the control: it must bill the same on both legs

Shared setup:

model_list:
  - model_name: openrouter-gpt-6-luna-decisions
    litellm_params:
      model: openrouter/openai/gpt-6-luna-decisions
      api_key: os.environ/OPENROUTER_API_KEY
  - model_name: openrouter-pplx-decider
    litellm_params:
      model: openrouter/perplexity/pplx-decider-v1.1-27b
      api_key: os.environ/OPENROUTER_API_KEY
  - model_name: openrouter-clef-flash
    litellm_params:
      model: openrouter/cloudflare/clef-flash
      api_key: os.environ/OPENROUTER_API_KEY
  - model_name: perplexity-decider-v1-1
    litellm_params:
      model: perplexity/pplx-decider-v1.1-27b
      api_key: os.environ/PERPLEXITY_API_KEY
  - model_name: openrouter-jev
    litellm_params:
      model: openrouter/typesafe/jev-1.13
      api_key: os.environ/OPENROUTER_API_KEY

general_settings:
  master_key: os.environ/LITELLM_MASTER_KEY
  database_url: os.environ/DATABASE_URL
LITELLM_LOCAL_MODEL_COST_MAP=True python litellm/proxy/proxy_cli.py --config config.yaml --port 36454 --num_workers 2 --detailed_debug

STATE='Ticket (billing): The export job hangs at 99% and never finishes'
oai() { printf '{"model": "%s", "input": "%s", "questions": [{"type": "predicate", "name": "defect", "instructions": "Is this a defect report?"}, {"type": "choice", "name": "severity", "instructions": "How severe is it?", "choices": [{"value": "low", "description": "cosmetic"}, {"value": "high", "description": "blocks users"}]}, {"type": "score", "name": "confidence", "instructions": "How sure are you?", "levels": [{"label": "unsure"}, {"label": "sure"}]}]}' "$1" "$STATE"; }
s1() { printf '{"model": "%s", "state": "%s", "questions": {"defect": {"type": "noul", "instructions": "Is this a defect report?"}, "severity": {"type": "choice", "instructions": "How severe is it?", "criteria": {"low": "cosmetic", "high": "blocks users"}}, "confidence": {"type": "score", "instructions": "How sure are you?", "criteria": ["unsure", "sure"]}}}' "$1" "$STATE"; }

The response bodies below are trimmed to id, model, and usage; the answers to the three questions came back on every call

Before (b1eae88)

openrouter-gpt-6-luna-decisions -> openrouter/openai/gpt-6-luna-decisions

  1. curl -s -D - -X POST http://127.0.0.1:36454/v1/decisions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "$(oai openrouter-gpt-6-luna-decisions)" | tee /dev/stderr | jq -c '{id, model, usage}'
    HTTP 200  x-litellm-response-cost=absent  x-litellm-call-id=2dd39dca-d1ec-4f03-9bf7-06af9da9c0b6
    {"model": "openai/gpt-6-luna-decisions-20261006", "usage": {"input_tokens": 378, "input_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0}, "output_tokens": 0, "output_tokens_details": {"reasoning_tokens": 0}, "total_tokens": 378}}
    
  2. curl -s "http://127.0.0.1:36454/spend/logs?request_id=2dd39dca-d1ec-4f03-9bf7-06af9da9c0b6" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c '.[] | {request_id, model, prompt_tokens, completion_tokens, spend}'
    {"request_id":"2dd39dca-d1ec-4f03-9bf7-06af9da9c0b6","model":"openrouter/openai/gpt-6-luna-decisions","prompt_tokens":378,"completion_tokens":0,"spend":0.0}
    
  3. curl -s -D - -X POST http://127.0.0.1:36454/v1/systemone -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "$(s1 openrouter-gpt-6-luna-decisions)" | tee /dev/stderr | jq -c '{id, model, usage}'
    HTTP 200  x-litellm-response-cost=absent  x-litellm-call-id=5abe2cb3-4aa7-42a1-afde-b37154633ef0
    {"id": "gen-dec-1791507579-W47sBWX3V38wxK7PtPwB", "model": "openai/gpt-6-luna-decisions-20261006", "usage": {"input_tokens": 378, "output_tokens": 0, "cost": 3.78e-05}}
    
  4. curl -s "http://127.0.0.1:36454/spend/logs?request_id=gen-dec-1791507579-W47sBWX3V38wxK7PtPwB" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c '.[] | {request_id, model, prompt_tokens, completion_tokens, spend}'
    {"request_id":"gen-dec-1791507579-W47sBWX3V38wxK7PtPwB","model":"openrouter/openai/gpt-6-luna-decisions","prompt_tokens":378,"completion_tokens":0,"spend":0.0}
    

openrouter-pplx-decider -> openrouter/perplexity/pplx-decider-v1.1-27b

  1. curl -s -D - -X POST http://127.0.0.1:36454/v1/decisions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "$(oai openrouter-pplx-decider)" | tee /dev/stderr | jq -c '{id, model, usage}'
    HTTP 200  x-litellm-response-cost=absent  x-litellm-call-id=a9cc5cd8-fa52-40ac-a0fb-cf7d1fa70baa
    {"model": "perplexity/pplx-decider-v1.1-27b-20261006", "usage": {"input_tokens": 298, "input_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0}, "output_tokens": 3, "output_tokens_details": {"reasoning_tokens": 0}, "total_tokens": 301}}
    
  2. curl -s "http://127.0.0.1:36454/spend/logs?request_id=a9cc5cd8-fa52-40ac-a0fb-cf7d1fa70baa" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c '.[] | {request_id, model, prompt_tokens, completion_tokens, spend}'
    {"request_id":"a9cc5cd8-fa52-40ac-a0fb-cf7d1fa70baa","model":"openrouter/perplexity/pplx-decider-v1.1-27b","prompt_tokens":298,"completion_tokens":3,"spend":0.0}
    
  3. curl -s -D - -X POST http://127.0.0.1:36454/v1/systemone -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "$(s1 openrouter-pplx-decider)" | tee /dev/stderr | jq -c '{id, model, usage}'
    HTTP 200  x-litellm-response-cost=absent  x-litellm-call-id=20e19770-9c6a-4b24-adc0-791a5323791a
    {"id": "gen-dec-1791507591-dSacfBpPQ3Sb4aYQowID", "model": "perplexity/pplx-decider-v1.1-27b-20261006", "usage": {"input_tokens": 298, "output_tokens": 3, "cost": 5.96e-06}}
    
  4. curl -s "http://127.0.0.1:36454/spend/logs?request_id=gen-dec-1791507591-dSacfBpPQ3Sb4aYQowID" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c '.[] | {request_id, model, prompt_tokens, completion_tokens, spend}'
    {"request_id":"gen-dec-1791507591-dSacfBpPQ3Sb4aYQowID","model":"openrouter/perplexity/pplx-decider-v1.1-27b","prompt_tokens":298,"completion_tokens":3,"spend":0.0}
    

openrouter-clef-flash -> openrouter/cloudflare/clef-flash

  1. curl -s -D - -X POST http://127.0.0.1:36454/v1/decisions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "$(oai openrouter-clef-flash)" | tee /dev/stderr | jq -c '{id, model, usage}'
    HTTP 200  x-litellm-response-cost=absent  x-litellm-call-id=436b03ab-b206-441f-bba0-b4670050a359
    {"model": "cloudflare/clef-flash", "usage": {"input_tokens": 287, "input_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0}, "output_tokens": 0, "output_tokens_details": {"reasoning_tokens": 0}, "total_tokens": 287}}
    
  2. curl -s "http://127.0.0.1:36454/spend/logs?request_id=436b03ab-b206-441f-bba0-b4670050a359" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c '.[] | {request_id, model, prompt_tokens, completion_tokens, spend}'
    {"request_id":"436b03ab-b206-441f-bba0-b4670050a359","model":"openrouter/cloudflare/clef-flash","prompt_tokens":287,"completion_tokens":0,"spend":0.0}
    
  3. curl -s -D - -X POST http://127.0.0.1:36454/v1/systemone -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "$(s1 openrouter-clef-flash)" | tee /dev/stderr | jq -c '{id, model, usage}'
    HTTP 200  x-litellm-response-cost=absent  x-litellm-call-id=8348c3c2-dc57-415e-86ab-e65004356f2d
    {"id": "gen-dec-1791507595-3xcXFDYigXxMb3ngVR7L", "model": "cloudflare/clef-flash", "usage": {"input_tokens": 287, "output_tokens": 0, "cost": 6.027e-06}}
    
  4. curl -s "http://127.0.0.1:36454/spend/logs?request_id=gen-dec-1791507595-3xcXFDYigXxMb3ngVR7L" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c '.[] | {request_id, model, prompt_tokens, completion_tokens, spend}'
    {"request_id":"gen-dec-1791507595-3xcXFDYigXxMb3ngVR7L","model":"openrouter/cloudflare/clef-flash","prompt_tokens":287,"completion_tokens":0,"spend":0.0}
    

perplexity-decider-v1-1 -> perplexity/pplx-decider-v1.1-27b

  1. curl -s -D - -X POST http://127.0.0.1:36454/v1/decisions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "$(oai perplexity-decider-v1-1)" | tee /dev/stderr | jq -c '{id, model, usage}'
    HTTP 200  x-litellm-response-cost=absent  x-litellm-call-id=bf1af6d0-d999-46bd-905b-572c73335d30
    {"model": "pplx-decider-v1.1-27b", "usage": {"input_tokens": 298, "input_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0}, "output_tokens": 3, "output_tokens_details": {"reasoning_tokens": 0}, "total_tokens": 301}}
    
  2. curl -s "http://127.0.0.1:36454/spend/logs?request_id=bf1af6d0-d999-46bd-905b-572c73335d30" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c '.[] | {request_id, model, prompt_tokens, completion_tokens, spend}'
    {"request_id":"bf1af6d0-d999-46bd-905b-572c73335d30","model":"perplexity/pplx-decider-v1.1-27b","prompt_tokens":298,"completion_tokens":3,"spend":0.0}
    
  3. curl -s -D - -X POST http://127.0.0.1:36454/v1/systemone -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "$(s1 perplexity-decider-v1-1)" | tee /dev/stderr | jq -c '{id, model, usage}'
    HTTP 200  x-litellm-response-cost=absent  x-litellm-call-id=dbb281d1-b45a-465d-8b89-78abfe967c9e
    {"model": "pplx-decider-v1.1-27b", "usage": {"input_tokens": 298, "output_tokens": 3}}
    
  4. curl -s "http://127.0.0.1:36454/spend/logs?request_id=dbb281d1-b45a-465d-8b89-78abfe967c9e" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c '.[] | {request_id, model, prompt_tokens, completion_tokens, spend}'
    {"request_id":"dbb281d1-b45a-465d-8b89-78abfe967c9e","model":"perplexity/pplx-decider-v1.1-27b","prompt_tokens":298,"completion_tokens":3,"spend":0.0}
    

openrouter-jev -> openrouter/typesafe/jev-1.13 (control, priced before this PR)

  1. curl -s -D - -X POST http://127.0.0.1:36454/v1/decisions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "$(oai openrouter-jev)" | tee /dev/stderr | jq -c '{id, model, usage}'
    HTTP 200  x-litellm-response-cost=1.5456e-05  x-litellm-call-id=b1820e8c-3b76-4a64-8497-ec652dd8e88f
    {"model": "typesafe/jev-1.13-20260917", "usage": {"input_tokens": 368, "input_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0}, "output_tokens": 62, "output_tokens_details": {"reasoning_tokens": 0}, "total_tokens": 430}}
    
  2. curl -s "http://127.0.0.1:36454/spend/logs?request_id=b1820e8c-3b76-4a64-8497-ec652dd8e88f" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c '.[] | {request_id, model, prompt_tokens, completion_tokens, spend}'
    {"request_id":"b1820e8c-3b76-4a64-8497-ec652dd8e88f","model":"openrouter/typesafe/jev-1.13","prompt_tokens":368,"completion_tokens":62,"spend":0.000015456}
    
  3. curl -s -D - -X POST http://127.0.0.1:36454/v1/systemone -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "$(s1 openrouter-jev)" | tee /dev/stderr | jq -c '{id, model, usage}'
    HTTP 200  x-litellm-response-cost=1.5456e-05  x-litellm-call-id=49e06f69-8981-470b-9f30-563b1b6b5a87
    {"id": "gen-dec-1791507602-hFKU15G3hYbjX3jyppea", "model": "typesafe/jev-1.13-20260917", "usage": {"input_tokens": 368, "output_tokens": 62, "cost": 1.5456e-05}}
    
  4. curl -s "http://127.0.0.1:36454/spend/logs?request_id=gen-dec-1791507602-hFKU15G3hYbjX3jyppea" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c '.[] | {request_id, model, prompt_tokens, completion_tokens, spend}'
    {"request_id":"gen-dec-1791507602-hFKU15G3hYbjX3jyppea","model":"openrouter/typesafe/jev-1.13","prompt_tokens":368,"completion_tokens":62,"spend":0.000015456}
    

After (c71b972)

openrouter-gpt-6-luna-decisions -> openrouter/openai/gpt-6-luna-decisions

  1. curl -s -D - -X POST http://127.0.0.1:36454/v1/decisions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "$(oai openrouter-gpt-6-luna-decisions)" | tee /dev/stderr | jq -c '{id, model, usage}'
    HTTP 200  x-litellm-response-cost=3.78e-05  x-litellm-call-id=7d4673cd-533b-4c4d-9187-38421b7998b2
    {"model": "openai/gpt-6-luna-decisions-20261006", "usage": {"input_tokens": 378, "input_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0}, "output_tokens": 0, "output_tokens_details": {"reasoning_tokens": 0}, "total_tokens": 378}}
    
  2. curl -s "http://127.0.0.1:36454/spend/logs?request_id=7d4673cd-533b-4c4d-9187-38421b7998b2" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c '.[] | {request_id, model, prompt_tokens, completion_tokens, spend}'
    {"request_id":"7d4673cd-533b-4c4d-9187-38421b7998b2","model":"openrouter/openai/gpt-6-luna-decisions","prompt_tokens":378,"completion_tokens":0,"spend":0.0000378}
    
  3. curl -s -D - -X POST http://127.0.0.1:36454/v1/systemone -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "$(s1 openrouter-gpt-6-luna-decisions)" | tee /dev/stderr | jq -c '{id, model, usage}'
    HTTP 200  x-litellm-response-cost=3.78e-05  x-litellm-call-id=afd3d243-fcc1-4434-b53f-bb50f214a708
    {"id": "gen-dec-1791507930-nPIH11Qv6Q5zQm95QBd7", "model": "openai/gpt-6-luna-decisions-20261006", "usage": {"input_tokens": 378, "output_tokens": 0, "cost": 3.78e-05}}
    
  4. curl -s "http://127.0.0.1:36454/spend/logs?request_id=gen-dec-1791507930-nPIH11Qv6Q5zQm95QBd7" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c '.[] | {request_id, model, prompt_tokens, completion_tokens, spend}'
    {"request_id":"gen-dec-1791507930-nPIH11Qv6Q5zQm95QBd7","model":"openrouter/openai/gpt-6-luna-decisions","prompt_tokens":378,"completion_tokens":0,"spend":0.0000378}
    

openrouter-pplx-decider -> openrouter/perplexity/pplx-decider-v1.1-27b

  1. curl -s -D - -X POST http://127.0.0.1:36454/v1/decisions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "$(oai openrouter-pplx-decider)" | tee /dev/stderr | jq -c '{id, model, usage}'
    HTTP 200  x-litellm-response-cost=5.9600000000000005e-06  x-litellm-call-id=0b6e6376-6045-432a-a757-c6a90a9037b6
    {"model": "perplexity/pplx-decider-v1.1-27b-20261006", "usage": {"input_tokens": 298, "input_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0}, "output_tokens": 3, "output_tokens_details": {"reasoning_tokens": 0}, "total_tokens": 301}}
    
  2. curl -s "http://127.0.0.1:36454/spend/logs?request_id=0b6e6376-6045-432a-a757-c6a90a9037b6" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c '.[] | {request_id, model, prompt_tokens, completion_tokens, spend}'
    {"request_id":"0b6e6376-6045-432a-a757-c6a90a9037b6","model":"openrouter/perplexity/pplx-decider-v1.1-27b","prompt_tokens":298,"completion_tokens":3,"spend":0.000005960000000000001}
    
  3. curl -s -D - -X POST http://127.0.0.1:36454/v1/systemone -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "$(s1 openrouter-pplx-decider)" | tee /dev/stderr | jq -c '{id, model, usage}'
    HTTP 200  x-litellm-response-cost=5.9600000000000005e-06  x-litellm-call-id=04b71db6-c2a0-4341-ac6f-69dbdd50ed7f
    {"id": "gen-dec-1791507932-oNdzE1sb6pDqefO0JZeK", "model": "perplexity/pplx-decider-v1.1-27b-20261006", "usage": {"input_tokens": 298, "output_tokens": 3, "cost": 5.96e-06}}
    
  4. curl -s "http://127.0.0.1:36454/spend/logs?request_id=gen-dec-1791507932-oNdzE1sb6pDqefO0JZeK" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c '.[] | {request_id, model, prompt_tokens, completion_tokens, spend}'
    {"request_id":"gen-dec-1791507932-oNdzE1sb6pDqefO0JZeK","model":"openrouter/perplexity/pplx-decider-v1.1-27b","prompt_tokens":298,"completion_tokens":3,"spend":0.000005960000000000001}
    

openrouter-clef-flash -> openrouter/cloudflare/clef-flash

  1. curl -s -D - -X POST http://127.0.0.1:36454/v1/decisions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "$(oai openrouter-clef-flash)" | tee /dev/stderr | jq -c '{id, model, usage}'
    HTTP 200  x-litellm-response-cost=6.027e-06  x-litellm-call-id=d49c1ef0-986c-4b14-825e-d2aff436c079
    {"model": "cloudflare/clef-flash", "usage": {"input_tokens": 287, "input_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0}, "output_tokens": 0, "output_tokens_details": {"reasoning_tokens": 0}, "total_tokens": 287}}
    
  2. curl -s "http://127.0.0.1:36454/spend/logs?request_id=d49c1ef0-986c-4b14-825e-d2aff436c079" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c '.[] | {request_id, model, prompt_tokens, completion_tokens, spend}'
    {"request_id":"d49c1ef0-986c-4b14-825e-d2aff436c079","model":"openrouter/cloudflare/clef-flash","prompt_tokens":287,"completion_tokens":0,"spend":0.000006027}
    
  3. curl -s -D - -X POST http://127.0.0.1:36454/v1/systemone -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "$(s1 openrouter-clef-flash)" | tee /dev/stderr | jq -c '{id, model, usage}'
    HTTP 200  x-litellm-response-cost=6.027e-06  x-litellm-call-id=6a6d26c8-2532-4483-b590-a78f90615289
    {"id": "gen-dec-1791507934-N8F740szw0kbf3i70yDD", "model": "cloudflare/clef-flash", "usage": {"input_tokens": 287, "output_tokens": 0, "cost": 6.027e-06}}
    
  4. curl -s "http://127.0.0.1:36454/spend/logs?request_id=gen-dec-1791507934-N8F740szw0kbf3i70yDD" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c '.[] | {request_id, model, prompt_tokens, completion_tokens, spend}'
    {"request_id":"gen-dec-1791507934-N8F740szw0kbf3i70yDD","model":"openrouter/cloudflare/clef-flash","prompt_tokens":287,"completion_tokens":0,"spend":0.000006027}
    

perplexity-decider-v1-1 -> perplexity/pplx-decider-v1.1-27b

  1. curl -s -D - -X POST http://127.0.0.1:36454/v1/decisions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "$(oai perplexity-decider-v1-1)" | tee /dev/stderr | jq -c '{id, model, usage}'
    HTTP 200  x-litellm-response-cost=5.9600000000000005e-06  x-litellm-call-id=a1d6c7a4-8373-4ba8-82c6-3e438e31fdbe
    {"model": "pplx-decider-v1.1-27b", "usage": {"input_tokens": 298, "input_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0}, "output_tokens": 3, "output_tokens_details": {"reasoning_tokens": 0}, "total_tokens": 301}}
    
  2. curl -s "http://127.0.0.1:36454/spend/logs?request_id=a1d6c7a4-8373-4ba8-82c6-3e438e31fdbe" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c '.[] | {request_id, model, prompt_tokens, completion_tokens, spend}'
    {"request_id":"a1d6c7a4-8373-4ba8-82c6-3e438e31fdbe","model":"perplexity/pplx-decider-v1.1-27b","prompt_tokens":298,"completion_tokens":3,"spend":0.000005960000000000001}
    
  3. curl -s -D - -X POST http://127.0.0.1:36454/v1/systemone -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "$(s1 perplexity-decider-v1-1)" | tee /dev/stderr | jq -c '{id, model, usage}'
    HTTP 200  x-litellm-response-cost=5.9600000000000005e-06  x-litellm-call-id=62289c4e-7a57-4cb8-b5c5-453ccf41a59e
    {"model": "pplx-decider-v1.1-27b", "usage": {"input_tokens": 298, "output_tokens": 3}}
    
  4. curl -s "http://127.0.0.1:36454/spend/logs?request_id=62289c4e-7a57-4cb8-b5c5-453ccf41a59e" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c '.[] | {request_id, model, prompt_tokens, completion_tokens, spend}'
    {"request_id":"62289c4e-7a57-4cb8-b5c5-453ccf41a59e","model":"perplexity/pplx-decider-v1.1-27b","prompt_tokens":298,"completion_tokens":3,"spend":0.000005960000000000001}
    

openrouter-jev -> openrouter/typesafe/jev-1.13 (control, priced before this PR)

  1. curl -s -D - -X POST http://127.0.0.1:36454/v1/decisions -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "$(oai openrouter-jev)" | tee /dev/stderr | jq -c '{id, model, usage}'
    HTTP 200  x-litellm-response-cost=1.5456e-05  x-litellm-call-id=85ad7fb7-95b8-4b07-aa19-8d87695dff96
    {"model": "typesafe/jev-1.13-20260917", "usage": {"input_tokens": 368, "input_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0}, "output_tokens": 62, "output_tokens_details": {"reasoning_tokens": 0}, "total_tokens": 430}}
    
  2. curl -s "http://127.0.0.1:36454/spend/logs?request_id=85ad7fb7-95b8-4b07-aa19-8d87695dff96" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c '.[] | {request_id, model, prompt_tokens, completion_tokens, spend}'
    {"request_id":"85ad7fb7-95b8-4b07-aa19-8d87695dff96","model":"openrouter/typesafe/jev-1.13","prompt_tokens":368,"completion_tokens":62,"spend":0.000015456}
    
  3. curl -s -D - -X POST http://127.0.0.1:36454/v1/systemone -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" -d "$(s1 openrouter-jev)" | tee /dev/stderr | jq -c '{id, model, usage}'
    HTTP 200  x-litellm-response-cost=1.5456e-05  x-litellm-call-id=273cdeee-dcfa-420b-9b3f-6527b77850d5
    {"id": "gen-dec-1791507941-hje3LsaH3b0BULgOmHIL", "model": "typesafe/jev-1.13-20260917", "usage": {"input_tokens": 368, "output_tokens": 62, "cost": 1.5456e-05}}
    
  4. curl -s "http://127.0.0.1:36454/spend/logs?request_id=gen-dec-1791507941-hje3LsaH3b0BULgOmHIL" -H "Authorization: Bearer $LITELLM_MASTER_KEY" | jq -c '.[] | {request_id, model, prompt_tokens, completion_tokens, spend}'
    {"request_id":"gen-dec-1791507941-hje3LsaH3b0BULgOmHIL","model":"openrouter/typesafe/jev-1.13","prompt_tokens":368,"completion_tokens":62,"spend":0.000015456}
    

Observations from the legs:

  • OpenRouter's /v1/systemone body carries usage.cost; Before still logged $0. Fixed here
  • OpenRouter echoes date-suffixed model names; spend rows keep the configured id. Unchanged
  • Spend log request_id is OpenRouter's gen-dec-... id on /v1/systemone. Unchanged
  • The control openrouter/typesafe/jev-1.13 bills 1.5456e-05 on both legs

/live-pr-risk (c71b972 on main 79c3de4, merged tree 9aecc5355d)

Rig: base is main 79c3de4, head is main merged with c71b972, two uvicorn workers each on a shared Postgres, real OpenRouter, Perplexity, and Together keys, and a logging forwarder in front of each vendor on both legs so the outbound requests could be diffed. main had moved 24 commits past the branch point (6 touch the cost map, #45469 among them); the merge is clean with no duplicate keys

Verdict: no breaking change, one intentional backward-incompatible price change named in the TLDR

Breaking: none. Every /v1/decisions, /v1/systemone, and Together /v1/chat/completions cell answered 200 on both legs with byte-identical recorded outbound requests (11 OpenRouter, 5 Perplexity, 2 Together per leg, one request per cell, same keys and sizes). The control openrouter/typesafe/jev-1.13 bills 1.5456e-05 on both legs; together_ai/Qwen/Qwen3.5-9B bills 3.97e-06 and together_ai/together/Tev1-4B-experimental 7.56e-07 on both

Backward incompatible: perplexity/pplx-decider-v1-27b bills 2e-08 per input token instead of 4e-08 (the same 298-token call: base 1.192e-05, head 5.96e-06). The TLDR names it and the approval on this PR is its sign-off

Changes the PR intends, observed: the new rows bill (base 0.0; head openrouter/openai/gpt-6-luna-decisions 3.78e-05, openrouter/perplexity/pplx-decider-v1.1-27b 5.96e-06, perplexity/pplx-decider-v1.1-27b 5.96e-06, openrouter/cloudflare/clef-flash 6.027e-06, openrouter/togethercomputer/tev1-4b-experimental 1.3356e-05, which is 318 x 4.2e-08 and no longer a Together size bucket), GET /model/info and GET /model_group/info carry mode, the costs, and max_input_tokens for them (null before), GET /health?model=openrouter-tev1-4b goes from 503 (a chat probe OpenRouter rejects) to 200 (a decisions probe), and PUT /lens/signals accepts the model (400 "Choose a System 1 model" before)

Regression risk: the Together size-bucket path (a together_ai/ model with no cost map row) could not run live, since every chat model Together serves serverless today has a row. test_completion_cost_prefers_a_direct_row_over_the_together_size_bucket covers it and an SDK probe on both trees prices together_ai/togethercomputer/CodeLlama-34b-Instruct identically, since the guard reads the same key twice for a together_ai/-prefixed name

Dependency graph: cost map rows read by completion_cost through /v1/decisions, /v1/systemone, and /spend/logs (verified live), by /model/info and /model_group/info (verified live), by the /health mode dispatch (verified live), by Lens PUT /lens/signals (verified live), and by get_model_info in the SDK (tested); has_together_registry_pricing has one caller in cost_calculator.py (tested and verified live); the bare pplx-decider-v1.1-27b name resolves to the Perplexity provider identically on both trees (SDK probe)

Not verified: the Together size-bucket path live (no serverless Together model without a row exists today); the Admin UI models page render (its data route /model/info was verified)

Type

🐛 Bug Fix

Caveats (if any)

Low

Each row below stays as is because fixing it costs more than it buys; the judgment follows the caveat

  • feat(decisions): accept decisions as a model mode alongside evaluation #45474 (renaming mode: evaluation to decisions) closed unmerged, so the 16 rows keep evaluation, the mode /health and Lens read today. Switching them to a mode nothing reads would break both for these models
  • Rows carry OpenRouter's prices, not the upstream vendor's (clef-flash 2.1e-08 vs Cloudflare's 9e-08). OpenRouter is who bills the call, so its price is the spend; the vendor's would overbill
  • max_input_tokens comes from the catalog's context_length; the three respan/ rows omit it because the catalog says 0. Inventing a limit the catalog does not publish would reject valid prompts
  • New rows set no max_output_tokens. The catalog's top_provider.max_completion_tokens (8 for tev1-4b, 945000 for gpt-6-luna) was left out: the decisions API takes no output cap and no decisions code path reads the field, so pinning it only changes /model/info, and a product commit for that re-runs the whole proof loop
  • openrouter/~typesafe/jev-latest keeps the catalog's tilde id, like the 18 existing openrouter/~ keys. It is the id OpenRouter routes by, so a cleaner key would never match a request
  • The existing openrouter/typesafe/jev-1.13 row is untouched (max_input_tokens 32000 vs catalog 64000, max_output_tokens 28800 vs 57600). It bills correctly today and widening its limits is a separate one-row change outside this billing fix
  • The Together guard change applies to every togethercomputer model name with its own row. That is the intended behavior, and openrouter/togethercomputer/tev1-4b-experimental is the only such key in the map

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR
  • c71b972 passes /live-pr-risk

Every decisions model OpenRouter serves except typesafe/jev-1.13, and
Perplexity's pplx-decider-v1.1-27b, had no cost map row, so /v1/decisions
and /v1/systemone calls through them returned no x-litellm-response-cost
header and logged $0 spend. Add the 15 openrouter/ rows from OpenRouter's
decisions catalog (prompt price, free output, max_input_tokens from
context_length) and the perplexity/pplx-decider-v1.1-27b row from
Perplexity's pricing page, which also lists v1 at $0.02/1M, so that row
moves from $0.04/1M to $0.02/1M.

The Together size-bucket branch of the cost calculator rewrote any model
whose name contains "togethercomputer" to a bucket name even when the model
had its own cost map row, which left openrouter/togethercomputer/tev1-4b-experimental
unpriced; the guard now honours the model's own row too.
@devin-ai-integration

devin-ai-integration Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@greptile-apps

greptile-apps Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[Critical impact] The PR appears safe to merge, with both previous findings addressed and no new blocking issues

Summary

Adds prices for 15 OpenRouter decisions models and Perplexity v1.1, updates Perplexity v1 pricing, and lets a model's own price row win over Together's size-based price

  • OpenRouter and Perplexity decision calls now use their listed prices.
  • Directly priced togethercomputer models use their own token rates.

Reviews (2) · Last reviewed commit: "test(cost): type the monkeypatch fixture..." · Reviewed by Greptile

Comment thread tests/unit/decisions/test_main.py
Comment thread tests/unit/test_cost_calculator.py Outdated
@codspeed

codspeed Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_decisions_cost_rows_openrouter_perplexity (939ce3f) with main (0ae62c5)

Open in CodSpeed

@codecov

codecov Bot commented Oct 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@mateo-berri

Copy link
Copy Markdown
Contributor

@greptileai

@mateo-berri

Copy link
Copy Markdown
Contributor

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 939ce3f. Configure here.

@mateo-berri

Copy link
Copy Markdown
Contributor

@litellm-stampbot

@litellm-stampbot

litellm-stampbot Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

litellm-stampbot: risk low

Approved at 939ce3f.

Rules (stampbot/risk_rules.yml):

  • low: no rule flagged it

Judge (gpt-6-luna): low, probability of high 0.69 (high at or above 0.90)

  • risk: high (probability of high 0.69)

Pushes do not re-run this check. Comment @litellm-stampbot again to re-check the new head.

@litellm-stampbot litellm-stampbot Bot added the risk:high Sensitive, broad, or high-risk change label Oct 14, 2026
@mateo-berri

Copy link
Copy Markdown
Contributor

@litellm-stampbot

@litellm-stampbot litellm-stampbot Bot added risk:low Small, isolated, low-risk change and removed risk:high Sensitive, broad, or high-risk change labels Oct 15, 2026

@litellm-stampbot litellm-stampbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low risk by rule and by judgement at 939ce3f

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:low Small, isolated, low-risk change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants