Skip to content

feat(azure): price GPT-6 Sol and GPT-6 Luna on the azure route - #42704

Closed
marty-sullivan wants to merge 1 commit into
BerriAI:mainfrom
cu-aaii:litellm_azure_gpt6_sol_luna_pricing_main
Closed

marty-sullivan wants to merge 1 commit into
BerriAI:mainfrom
cu-aaii:litellm_azure_gpt6_sol_luna_pricing_main

Conversation

@marty-sullivan

@marty-sullivan marty-sullivan commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • Azure serves gpt-6-sol and gpt-6-luna today
  • Neither has an azure/ cost map row
  • Real billed calls log $0.00 spend, silently
  • No x-litellm-response-cost header comes back either
  • The proxy offers reasoning_effort: max, which Azure rejects

How it solves it:

  • Adds 6 keys: global, dated snapshot, US data zone, per model
  • Field set follows the merged azure/gpt-6-astra rows
  • Global rows sit at parity with the OpenAI rows
  • US data zone rows sit at the standard 1.1x
  • reasoning_effort max marked unsupported, from a live probe
  • No EU row, because Microsoft's gen-6 EU meter is 1.2x

User Flow

Before: a developer calling an Azure gpt-6-sol deployment through the gateway gets no spend data at
all, and nothing in the response says so

  1. The proxy admin adds a deployment with model: azure/gpt-6-sol and an api_base pointing at their
    Azure resource, then restarts the proxy
  2. The developer sends POST https://litellm-domain/v1/chat/completions with "model": "gpt-6-sol"
    and a short prompt
  3. A normal 200 comes back with real content and a usage object carrying real token counts
  4. No x-litellm-response-cost header arrives at all, so a client reading that header to track cost
    silently gets nothing
  5. They open https://litellm-domain/ui/?page=logs, find the request, and the row reads $0
  6. The admin switches the deployment to model: azure/us/gpt-6-sol for the US data zone and sends the
    same request. It behaves the same way, no cost header and a $0 log row
  7. Their team's budget never moves no matter how much traffic they send, so a budget cap on either
    deployment cannot fire
  8. Asking GET https://litellm-domain/model_group/info which reasoning efforts the deployment offers
    lists max, so the developer tries "reasoning_effort": "max" and Azure answers 400

After: both deployments bill, the data zone one bills more than the global one, and the effort list
matches what Azure accepts

  1. The proxy admin adds a deployment with model: azure/gpt-6-sol and an api_base pointing at their
    Azure resource, then restarts the proxy
  2. The developer sends POST https://litellm-domain/v1/chat/completions with "model": "gpt-6-sol"
    and a short prompt
  3. A normal 200 comes back with real content and a usage object carrying real token counts
  4. x-litellm-response-cost now reads a dollar amount matching the token counts at $2.00 per million
    input and $10.00 per million output
  5. They open https://litellm-domain/ui/?page=logs, find the request, and it shows that same non-zero
    spend
  6. The admin switches to model: azure/us/gpt-6-sol and sends the same request. The cost comes back
    10% higher, which is the US data zone uplift
  7. Their team's budget advances with usage, so a budget cap on either deployment fires when it should
  8. GET https://litellm-domain/model_group/info no longer lists max, so the developer sees the five
    efforts Azure actually accepts before sending anything

Relevant issues

No issue asks for these entries. Related but not duplicates: #42515 added the two bare OpenAI rows and
nothing Azure, #39827 added the azure/gpt-6-astra rows this follows, and #42594 syncs Azure prices
without touching either of these models

Affected release

Linear ticket

Pre-Submission checklist

  • I have added meaningful tests
  • The handful of test files covering my change pass locally, e.g. uv run pytest tests/test_litellm/<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)

Screenshots / Proof of Fix

Captured 2026-09-23 against a real Azure resource serving both models, so every call below is a real
billed call. Nothing is mocked and no pytest run stands in for any of it

Shared setup, used for both Before and After.

One note on how the six cost map rows get exercised, because it changes the config. The real Azure
deployments are named gpt-6-sol and gpt-6-luna, and Azure's deployment path is literal: a
deployment string of us/gpt-6-sol or gpt-6-sol-2026-09-22 makes the proxy call
/openai/deployments/us/gpt-6-sol/..., which Azure answers 404 Resource not found. So the data zone
and dated rows are priced by pointing base_model at them while model keeps the deployment that
exists. That is the documented way to steer an Azure cost lookup and the repo's own docs name this exact
case: terraform/provider/docs/resources/model.md describes an Azure deployment routed as
azure/gpt-4.1 whose real tier is Data Zone, priced by setting the key to us/gpt-4.1-2025-04-14. It
is also what a real operator does, since an Azure deployment is not named after its data zone either

config.yaml:

model_list:
  - model_name: gpt-6-sol
    litellm_params:
      model: azure/gpt-6-sol
      api_base: os.environ/AZURE_API_BASE
      api_key: os.environ/AZURE_API_KEY
      api_version: 2025-04-01-preview
  - model_name: gpt-6-sol-dated
    litellm_params:
      model: azure/gpt-6-sol
      base_model: azure/gpt-6-sol-2026-09-22
      api_base: os.environ/AZURE_API_BASE
      api_key: os.environ/AZURE_API_KEY
      api_version: 2025-04-01-preview
  - model_name: gpt-6-sol-us
    litellm_params:
      model: azure/gpt-6-sol
      base_model: azure/us/gpt-6-sol
      api_base: os.environ/AZURE_API_BASE
      api_key: os.environ/AZURE_API_KEY
      api_version: 2025-04-01-preview
  - model_name: gpt-6-luna
    litellm_params:
      model: azure/gpt-6-luna
      api_base: os.environ/AZURE_API_BASE
      api_key: os.environ/AZURE_API_KEY
      api_version: 2025-04-01-preview
  - model_name: gpt-6-luna-dated
    litellm_params:
      model: azure/gpt-6-luna
      base_model: azure/gpt-6-luna-2026-09-22
      api_base: os.environ/AZURE_API_BASE
      api_key: os.environ/AZURE_API_KEY
      api_version: 2025-04-01-preview
  - model_name: gpt-6-luna-us
    litellm_params:
      model: azure/gpt-6-luna
      base_model: azure/us/gpt-6-luna
      api_base: os.environ/AZURE_API_BASE
      api_key: os.environ/AZURE_API_KEY
      api_version: 2025-04-01-preview

general_settings:
  master_key: os.environ/LITELLM_MASTER_KEY

Env vars the proxy ran with: AZURE_API_BASE (the resource endpoint), AZURE_API_KEY (redacted),
LITELLM_MASTER_KEY (redacted, shown as sk-REDACTED in the curls below), DATABASE_URL (a local
Postgres, so the spend rows are readable), STORE_MODEL_IN_DB=False, and
LITELLM_LOCAL_MODEL_COST_MAP=True. That last one matters: without it the proxy pulls the cost map off
the network and the Before run would read the published map rather than the checked-out one, which
would prove nothing

The driver loop, reused verbatim at both commits. It sends the same prompt to all six model groups and
prints the status line, the cost header, and the token counts:

for g in gpt-6-sol gpt-6-sol-dated gpt-6-sol-us gpt-6-luna gpt-6-luna-dated gpt-6-luna-us; do
  echo "===== $g ====="
  curl -s -D /tmp/h_$g.txt -X POST http://localhost:4000/v1/chat/completions \
    -H "Authorization: Bearer sk-REDACTED" -H 'Content-Type: application/json' \
    -d "{\"model\":\"$g\",\"messages\":[{\"role\":\"user\",\"content\":\"Reply with exactly one word: ok\"}]}" \
    -o /tmp/b_$g.json
  grep -iE "^HTTP/|^x-litellm-response-cost:" /tmp/h_$g.txt
  jq -c '{in: .usage.prompt_tokens, out: .usage.completion_tokens, content: .choices[0].message.content}' /tmp/b_$g.json
done

Before (721d39f476)

Spend on all six shapes

  1. Start the proxy at the merge base:

    git checkout 721d39f476
    LITELLM_LOCAL_MODEL_COST_MAP=True python litellm/proxy/proxy_cli.py --config config.yaml --detailed_debug 2>&1 | tee litellm.log
    
    LiteLLM: Proxy initialized with Config, Set models:
        gpt-6-sol
        gpt-6-sol-dated
        gpt-6-sol-us
        gpt-6-luna
        gpt-6-luna-dated
        gpt-6-luna-us
    INFO:     Application startup complete.
    INFO:     Uvicorn running on http://0.0.0.0:4000 (Press CTRL+C to quit)
    
  2. Run the driver loop. Every call returns 200 with real content and real token counts, and not one
    of them carries an x-litellm-response-cost header:

    ===== gpt-6-sol =====
    HTTP/1.1 200 OK
    {"in":13,"out":4,"content":"ok"}
    ===== gpt-6-sol-dated =====
    HTTP/1.1 200 OK
    {"in":13,"out":4,"content":"ok"}
    ===== gpt-6-sol-us =====
    HTTP/1.1 200 OK
    {"in":13,"out":4,"content":"ok"}
    ===== gpt-6-luna =====
    HTTP/1.1 200 OK
    {"in":13,"out":17,"content":"ok"}
    ===== gpt-6-luna-dated =====
    HTTP/1.1 200 OK
    {"in":13,"out":15,"content":"ok"}
    ===== gpt-6-luna-us =====
    HTTP/1.1 200 OK
    {"in":13,"out":17,"content":"ok"}
    

    The grep prints no x-litellm-response-cost line for any of the six, so those lines are genuinely
    absent rather than trimmed from this paste. The full header block on the first call shows what does
    come back, a breakdown of zeros with the total missing:

    HTTP/1.1 200 OK
    x-litellm-call-id: 734ed736-ee8e-4a35-9475-95cb7ad1ae39
    x-litellm-model-name: azure/gpt-6-sol
    x-litellm-response-cost-original: 0.0
    x-litellm-response-cost-discount-amount: 0.0
    x-litellm-response-cost-margin-amount: 0.0
    x-litellm-response-cost-margin-percent: 0.0
    x-litellm-response-cost-input: 0.0
    x-litellm-response-cost-output: 0.0
    x-litellm-response-cost-tool-usage: 0.0
    x-litellm-model-group: gpt-6-sol
    
  3. Show the proxy log naming the models it could not price:

    grep -o "This model isn't mapped yet. model=gpt-6-[a-z]*, custom_llm_provider=azure." litellm.log | sort -u
    
    This model isn't mapped yet. model=gpt-6-luna, custom_llm_provider=azure.
    This model isn't mapped yet. model=gpt-6-sol, custom_llm_provider=azure.
    

    Six such lines in total, one per model group

  4. The proxy also warns that it billed a real request at zero, which is the clearest single line in the
    log:

    grep -o "Billable request priced at \$0.*missing_pricing_key\"}" litellm.log
    
    Billable request priced at $0 and logged as such (model_group=gpt-6-sol model=azure/gpt-6-sol
    provider=azure prompt_tokens=13 completion_tokens=4): pricing entry 'azure/gpt-6-sol' has no
    input_cost_per_token, output_cost_per_token. Set the missing rate in the deployment's model_info or
    in the model cost map, or set every rate to 0 to mark the model free. Counted in
    litellm_zero_cost_requests_total{reason="missing_pricing_key"}
    

    Four such warnings fire, for gpt-6-sol, gpt-6-sol-dated, gpt-6-luna and gpt-6-luna-dated.
    Worth flagging that the two us shapes get no warning at all despite billing $0 the same way,
    because the diagnostic only speaks up when the pricing entry it found declares some non-zero rate,
    and the data zone spelling resolves to a synthesized entry that is all zeros. So on the data zone
    rows the $0 is silent in the log as well as in the response

  5. Read the spend rows back:

    curl -s "http://localhost:4000/spend/logs/v2?start_date=$(date -u +%F)&end_date=$(date -u -v+1d +%F)" \
      -H 'Authorization: Bearer sk-REDACTED' \
      | jq -r '.data[] | "\(.model_group)\t\(.spend)\t\(.prompt_tokens)in/\(.completion_tokens)out"'
    
    gpt-6-luna-us     0.0   13in/17out
    gpt-6-luna-dated  0.0   13in/15out
    gpt-6-luna        0.0   13in/17out
    gpt-6-sol-us      0.0   13in/4out
    gpt-6-sol-dated   0.0   13in/4out
    gpt-6-sol         0.0   13in/4out
    

    Six real billed calls, six rows, $0.00 on every one

Reasoning efforts the proxy advertises

  1. Ask the proxy what reasoning efforts it offers:

    curl -s http://localhost:4000/model_group/info -H 'Authorization: Bearer sk-REDACTED' \
      | jq -r '.data[] | "\(.model_group) \(.supported_reasoning_efforts)"'
    
    gpt-6-sol ["none","low","medium","high","xhigh","max"]
    gpt-6-sol-dated ["none","low","medium","high","xhigh","max"]
    gpt-6-sol-us ["none","low","medium","high","xhigh","max"]
    gpt-6-luna ["none","low","medium","high","xhigh","max"]
    gpt-6-luna-dated ["none","low","medium","high","xhigh","max"]
    gpt-6-luna-us ["none","low","medium","high","xhigh","max"]
    

    With no azure/ row of their own these fall back to the bare OpenAI rows, which do support max,
    so all six advertise it. The Azure probe in the next case shows Azure answering 400 to exactly
    that value

Azure's own answer on reasoning_effort max

This case probes Azure directly rather than the proxy, so it reads the same at either commit and the
output is identical under Before and After. It is shown in full under After, since that is where it
lines up with the flag this PR sets. The short version: gpt-6-sol and gpt-6-luna both answer 400
to reasoning_effort: max and 200 to xhigh, at this commit and at the tip alike

After (d07d31c8e1)

Spend on all six shapes

  1. Start the proxy at the PR tip, same config, same env:

    git checkout d07d31c8e1
    LITELLM_LOCAL_MODEL_COST_MAP=True python litellm/proxy/proxy_cli.py --config config.yaml --detailed_debug 2>&1 | tee litellm.log
    
    LiteLLM: Proxy initialized with Config, Set models:
        gpt-6-sol
        gpt-6-sol-dated
        gpt-6-sol-us
        gpt-6-luna
        gpt-6-luna-dated
        gpt-6-luna-us
    INFO:     Application startup complete.
    INFO:     Uvicorn running on http://0.0.0.0:4000 (Press CTRL+C to quit)
    
  2. Run the same driver loop. Every call now carries a cost header, and the model answered with the
    same 13 in / 4 out on all six, which makes the six numbers directly comparable:

    ===== gpt-6-sol =====
    HTTP/1.1 200 OK
    x-litellm-response-cost: 6.6e-05
    {"in":13,"out":4,"content":"ok"}
    ===== gpt-6-sol-dated =====
    HTTP/1.1 200 OK
    x-litellm-response-cost: 6.6e-05
    {"in":13,"out":4,"content":"ok"}
    ===== gpt-6-sol-us =====
    HTTP/1.1 200 OK
    x-litellm-response-cost: 7.26e-05
    {"in":13,"out":4,"content":"ok"}
    ===== gpt-6-luna =====
    HTTP/1.1 200 OK
    x-litellm-response-cost: 3.2999999999999997e-06
    {"in":13,"out":4,"content":"ok"}
    ===== gpt-6-luna-dated =====
    HTTP/1.1 200 OK
    x-litellm-response-cost: 3.2999999999999997e-06
    {"in":13,"out":4,"content":"ok"}
    ===== gpt-6-luna-us =====
    HTTP/1.1 200 OK
    x-litellm-response-cost: 3.63e-06
    {"in":13,"out":4,"content":"ok"}
    

    The mapping errors are gone as well. The same grep that printed six lines before prints none:

    grep -c "isn't mapped yet. model=gpt-6-sol\|isn't mapped yet. model=gpt-6-luna" litellm.log
    
    0
    
  3. The zero-cost warning is gone too. The same grep that matched four times before matches nothing:

    grep -c "Billable request priced at \$0" litellm.log
    
    0
    
  4. Reconcile each cost against its row's rates and the reported 13 in / 4 out. Expected is
    13 * input_cost_per_token + 4 * output_cost_per_token:

    model group cost map key rates per 1M expected observed
    gpt-6-sol azure/gpt-6-sol $2.00 / $10.00 6.6e-05 6.6e-05
    gpt-6-sol-dated azure/gpt-6-sol-2026-09-22 $2.00 / $10.00 6.6e-05 6.6e-05
    gpt-6-sol-us azure/us/gpt-6-sol $2.20 / $11.00 7.26e-05 7.26e-05
    gpt-6-luna azure/gpt-6-luna $0.10 / $0.50 3.2999999999999997e-06 3.2999999999999997e-06
    gpt-6-luna-dated azure/gpt-6-luna-2026-09-22 $0.10 / $0.50 3.2999999999999997e-06 3.2999999999999997e-06
    gpt-6-luna-us azure/us/gpt-6-luna $0.11 / $0.55 3.63e-06 3.63e-06

    Every one matches to the last bit, with no rounding applied on either side. The two luna figures
    carry a float tail because 13 * 1e-07 + 4 * 5e-07 is not exactly representable in binary; the
    expected value carries the identical tail, which is why they compare equal rather than approximately
    equal. The uplift lands where it should too: 7.26e-05 / 6.6e-05 and 3.63e-06 / 3.3e-06 are both
    1.1, so each data zone row is exactly 1.1x its global twin on the same token counts

    The per-direction headers reconcile independently, which separates the input rate from the output
    rate rather than only checking their sum. On gpt-6-sol:

    x-litellm-response-cost-input: 2.6e-05      13 * 2e-06  = 2.6e-05
    x-litellm-response-cost-output: 4e-05        4 * 1e-05  = 4e-05
    
  5. Read the spend rows back with the same command as Before:

    gpt-6-sol-us      7.26e-05   13in/4out
    gpt-6-luna-us     3.63e-06   13in/4out
    gpt-6-luna-dated  3.3e-06    13in/4out
    gpt-6-luna        3.3e-06    13in/4out
    gpt-6-sol-dated   6.6e-05    13in/4out
    gpt-6-sol         6.6e-05    13in/4out
    

    Each row equals its cost header, so the number a client reads and the number a budget spends against
    agree

Reasoning efforts the proxy advertises

  1. Ask the proxy what reasoning efforts it offers, same command as Before:

    curl -s http://localhost:4000/model_group/info -H 'Authorization: Bearer sk-REDACTED' \
      | jq -r '.data[] | "\(.model_group) \(.supported_reasoning_efforts)"'
    
    gpt-6-sol ["none","low","medium","high","xhigh"]
    gpt-6-sol-dated ["none","low","medium","high","xhigh"]
    gpt-6-sol-us ["none","low","medium","high","xhigh"]
    gpt-6-luna ["none","low","medium","high","xhigh"]
    gpt-6-luna-dated ["none","low","medium","high","xhigh"]
    gpt-6-luna-us ["none","low","medium","high","xhigh"]
    

    max is gone from all six and the remaining five match Azure's own list in the 400 below, word
    for word

Azure's own answer on reasoning_effort max

This one runs straight at the Azure endpoint rather than through the proxy, because it establishes a
vendor behaviour rather than a gateway one, so it reads the same at either commit and is where the
supports_max_reasoning_effort: false flag comes from. Run on 2026-09-23 against a real deployment of
each model.

  1. Ask each deployment for reasoning_effort: max:

    for m in gpt-6-sol gpt-6-luna gpt-6-astra; do
      curl -s -o /dev/stderr -w "$m -> %{http_code}\n" \
        "$AZURE_API_BASE/openai/deployments/$m/chat/completions?api-version=2025-04-01-preview" \
        -H "api-key: $AZURE_API_KEY" -H 'Content-Type: application/json' \
        -d '{"messages":[{"role":"user","content":"hi"}],"reasoning_effort":"max"}'
    done
    
    gpt-6-sol -> 400
    gpt-6-luna -> 400
    gpt-6-astra -> 400
    

    gpt-6-sol and gpt-6-luna both return:

    Unsupported value: 'reasoning_effort' does not support 'max' with this model.
    Supported values are: 'none', 'low', 'medium', 'high', and 'xhigh'.
    

    gpt-6-astra returns the same rejection, with 'low', 'medium', 'high', and 'xhigh' as its list.
    The lists differ on whether none appears, which is a real difference between the models rather
    than noise, and no conclusion about none support is drawn from it here. What matters for this PR
    is that all three refuse max

  2. Ask each for reasoning_effort: xhigh, the next level down:

    for m in gpt-6-sol gpt-6-luna gpt-6-astra; do
      curl -s -o /dev/null -w "$m -> %{http_code}\n" \
        "$AZURE_API_BASE/openai/deployments/$m/chat/completions?api-version=2025-04-01-preview" \
        -H "api-key: $AZURE_API_KEY" -H 'Content-Type: application/json' \
        -d '{"messages":[{"role":"user","content":"hi"}],"reasoning_effort":"xhigh"}'
    done
    
    gpt-6-sol -> 200
    gpt-6-luna -> 200
    gpt-6-astra -> 200
    

    So xhigh stays flagged and only max comes off

Type

🆕 New Feature

Caveats (if any)

Severe

  • The rates are derived, not published. That is why this is a draft

    Microsoft publishes no retail meter for either model as of 2026-09-23, and I checked that
    positively rather than assuming it. The Azure Retail Prices API under serviceName eq 'Foundry Models' returns 34,448 rows over 1,863 distinct meter names, and exactly 16 of those names belong
    to generation 6. All 16 are astra, and none is sol or luna. Two controls say the query works: 6-astra ShortCo Inp Std Gl 1M Tokens reads $10.00, matching azure/gpt-6-astra, and 5.6 sol ShortCo Inp Std Gl 1M Tokens reads $4.00, matching azure/gpt-5.6-sol. Worth noting for anyone re-checking two
    ways to get a false zero here: the service was renamed from Cognitive Services to Foundry Models, and no meter name in this family contains the substring gpt, so a contains(meterName, 'gpt-6') filter returns nothing

    So the numbers here are OpenAI's published direct price at parity on the global shape, plus the
    standard 1.1x on the US data zone shape. I am not presenting that as a citation. It is the same
    derivation azure/gpt-6-astra already encodes, and it matches the map's dominant convention: of the
    88 azure/<model> rows that have a bare OpenAI twin, 83 sit at exact parity on every shared rate,
    and of the 108 data-zone rows with a global twin, 91 sit at exactly 1.1x with the rest differing only
    by rounding. The deployments serve live today, which is what motivated this. Reconcile against
    whatever Microsoft publishes before this is marked ready

Medium

  • No EU data zone row, on purpose, and copying astra's would be wrong

    Microsoft's generation-6 data zone meter is not one price, it splits by region. 6-astra ShortCo Inp Std DZ 1M Tokens reads $11.00 across 7 regions, none of them EU, and $12.00 across 14 regions of
    which 10 are EU. So gen-6 US data zone is 1.1x and the bucket holding the EU regions is 1.2x.
    azure/eu/gpt-6-astra carries 1.1e-05, the US bucket's number, which reads low against its own
    meter. That row came from the automated price-sync bot in 41caaa301c, not from either astra PR,
    which is some evidence it was never weighed by hand

    With no published sol or luna meter there is nothing to cite at 1.1x or at 1.2x, so shipping an EU
    row would mean either guessing a second number or propagating astra's. Note this also differs from
    generation 5.6, whose DZ meter is a flat 1.1x for US and EU alike, so the multiplier cannot be
    carried forward from that generation either. An operator on an EU data zone deployment gets no
    pricing from this PR and still needs one

  • Fixing azure/eu/gpt-6-astra is left out to keep the scope to one problem

    The finding above is actionable on its own and I would rather not fold a repricing of a merged row
    into a PR that adds new ones. Happy to send it separately

Low

  • Flex, priority and batch rates are deliberately absent, and /v1/batch with them

    All 16 generation-6 meters are Std. There is no Fl, no PP and no batch SKU anywhere in the
    family, where 5.6 does have PP. Carrying OpenAI's flex, priority and batch numbers would price a
    tier Azure does not sell, so these rows match azure/gpt-6-astra's reduced field set rather than
    the OpenAI row's full one. A test asserts that, so a later sync cannot quietly re-add them

  • The dated snapshot rows are a spelling, not a requirement

    azure/gpt-6-astra-2026-09-03 is one of 89 dated azure/ keys on this branch, so these follow an
    established convention and specifically the one the sibling gen-6 model already has. Being straight
    about what they buy, though: _strip_dated_snapshot_suffix (67778cfe26) already resolves a dated
    model string back to its undated row, so a call would price correctly without them. They are belt
    and braces plus consistency with the astra precedent, and the test on them asserts the only thing
    that can go wrong, which is the dated row drifting from the undated one

  • supports_prompt_cache_breakpoint is kept on all six rows, which is a small divergence

    None of the 277 pre-existing azure/ rows carries this flag, and azure/gpt-6-astra does not,
    while both bare gen-6 rows and azure_ai/gpt-6-astra do. Keeping it makes the six rows agree with
    each other, because the flag resolves through a bare-twin fallback that only fires for the exact
    azure/<model> spelling: with the flag dropped, azure/gpt-6-sol reads true while
    azure/us/gpt-6-sol and the dated key read false, which is the state astra is in today. Dropping
    it here would be closer to the azure/ convention at the price of three rows disagreeing about one
    model's capability

  • No azure_ai/ rows

    azure_ai/gpt-6-astra exists, so there is a precedent, but it arrived in its own later PR (feat(cost-map): add azure_ai/gpt-6-astra Foundry pricing #39983)
    rather than in either astra PR, it cites the Foundry catalog rather than the retail price sheet, and
    only 16 of the 131 azure/<model> rows have an azure_ai/ twin at all. Adding one here would mean
    inventing a second unpublished citation for the same unpublished rate. Straightforward to add if
    someone is serving these through a Foundry project

  • No source field on any of the six rows

    Every azure/ gen-6 sibling points source at a retail-prices query that contains no sol or luna
    meter, so reusing it would dress a derivation up as a citation. 97 of the 277 azure/ rows carry no
    source at all, so omitting it is within convention. Add the real URL when Microsoft publishes one

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

An Azure deployment of either model had no azure/ entry, so the cost map answered
two different wrong ways. azure/gpt-6-sol and the dated snapshot raised
ModelNotMappedError, and azure/us/gpt-6-sol fell through to 0.0, which a proxy
records as a real $0.00 spend row.

Adds six keys, the Standard Global, dated snapshot and US data zone shapes for
each model, following the azure/gpt-6-astra rows PR BerriAI#39827 landed. Rates are
derived from the OpenAI direct rows: parity on the global shape, since Foundry
resells generation 6 at OpenAI's list price, and the standard 1.1x on the US data
zone. Flex, priority and batch rates are dropped along with /v1/batch, because
Azure publishes Std meters only for this generation.

supports_max_reasoning_effort is false rather than the true the OpenAI rows carry.
A live call to a real deployment of each model answers reasoning_effort max with a
400, "Unsupported value: 'reasoning_effort' does not support 'max' with this
model. Supported values are: 'none', 'low', 'medium', 'high', and 'xhigh'.", and
xhigh returns 200. That is the same evidence e79f3ec acted on for
azure/gpt-6-astra, so all three shapes per model now match those rows.

No EU data zone row. Microsoft's own DZ meter splits by region for generation 6,
$11.00 across seven US regions against $12.00 across fourteen that include nine
EU ones, so an EU row is 1.2x rather than the 1.1x azure/eu/gpt-6-astra carries.
With no published sol or luna meter there is nothing to cite at either value.

Azure's Retail Prices API publishes no meter for either model as of 2026-09-23,
so these rates are derived rather than cited, and the PR stays a draft until they
can be reconciled against published figures.
@codspeed

codspeed Bot commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing cu-aaii:litellm_azure_gpt6_sol_luna_pricing_main (d07d31c) with main (40ec84c)

Open in CodSpeed

@codecov

codecov Bot commented Sep 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@devin-ai-integration

Copy link
Copy Markdown
Contributor

Closing: these rows already landed on main with the same prices, and the rolling registry PR #42951 tracks the remaining unverified fields. Thanks!

@marty-sullivan
marty-sullivan deleted the litellm_azure_gpt6_sol_luna_pricing_main branch September 28, 2026 16:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant