Repository navigation
fix(azure_ai): bill FLUX.2-flex edit reference megapixels at Azure's rate - #42448
devin-ai-integration[bot] wants to merge 20 commits into
Conversation
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Azure meters FLUX.2 Flex image edits per generated megapixel and per reference megapixel, but the proxy priced only the generated image. The shared image edit handler now reads the PNG, JPEG or WebP header of each reference before the request transform consumes the stream, sums the pixels and stores one integer under hidden_params["reference_pixels"] on the ImageResponse. The Azure AI image cost calculator multiplies that value by the new input_cost_per_reference_pixel rate when no provider usage block is present, so token reporting models keep their existing cost. The rate on azure_ai/FLUX.2-flex comes from the Azure retail prices API Flex Ref Megapixel row (0.05 USD per megapixel)
70f338c to
f6e7920
Compare
|
There was a problem hiding this comment.
✅ 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 6a1bf0e. Configure here.
|
Superseded by #42829 |
TLDR
Problem this solves:
How it solves it:
input_cost_per_reference_pixel, read from the resolved deployment model info first and the shared catalog second, the same lookup the generated pixels use, so custom priced and unlisted deployments bill their own reference rateazure_ai/FLUX.2-flexgets that rate, and both rates become 0.05 / 1048576 per pixellitellm_modelresource gainsinput_cost_per_reference_pixelnext toinput_cost_per_pixel, forwarded whenever it is set in config so an explicit 0 override reaches the deployment, and the dashboardschema.d.tsis regeneratedModelGroupInfocarriesinput_cost_per_reference_pixelnext toinput_cost_per_pixel, so/model_group/inforeports a configured reference rate instead of dropping itUser Flow
Before: a developer editing an image through FLUX.2 Flex sees the same spend whether they send one reference or two, and Azure bills them two or three times what the proxy shows
model=azure_ai/FLUX.2-flex,prompt=make it a watercolor,size=1024x1024and one 1024x1024 PNG asimagex-litellm-response-cost: 0.0524288image[]and getx-litellm-response-cost: 0.0524288againspend: 0.0524288for both calls, while Azure Cost Management shows 1.0 output MP plus 1.0 and 2.0 reference MP at $0.05 each ($0.10 and $0.15)After: the same requests come back with the reference megapixels priced in at Azure's megapixel definition, so spend matches the Azure meter to the cent
model=azure_ai/FLUX.2-flex,prompt=make it a watercolor,size=1024x1024and one 1024x1024 PNG asimagex-litellm-response-cost: 0.1image[]and getx-litellm-response-cost: 0.15spend: 0.1andspend: 0.15, matching the Azure metersRelevant issues
Affected release
Linear ticket
Resolves LIT-8140
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
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 morerust-wheel, fails ontests/test_litellm_rust/ocr/test_callbacks.py::test_native_aocr_state_stashed_before_a_blocking_hook_raises_reaches_failure_callbackswith the identicalDID NOT RAISEon the merge baseb0ac23d3(main run 35807522715); nothing here touches OCR@greptileaito 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
Two proxies ran side by side against the real Azure AI Foundry FLUX.2-flex deployment with
LITELLM_LOCAL_MODEL_COST_MAP=True: the merge base on port 4100 and this branch on port 4200. Every scenario is one curl, for exampleThe
x-litellm-response-costheader from each call, base then branch. Expected is0.05 * (output MP + reference MP)with 1 MP = 1024x1024The whole matrix was rerun on the final head
1eaddc47e9against the same merge base with byte identical fixtures; every branch value matched the expected column and every base value reproduced the under billing. Azure itself rejects the malformed BMP, and on the branch that request no longer produced a negative reference total on the way outThe base column shows both bugs at once: every edit costs the same as a generation because references were never counted, and 1024x1024 is priced at 0.0524288 because the old rate divided by 1,000,000 instead of 1,048,576. The base JPEG baseline 400 was Azure rejecting that one upload ("Unable to extract dimensions from input image"); the identical file was accepted on the branch run and on a retry, so it is provider side flakiness, not a difference in the proxy. The trailing digits in 0.15000000000000002 and 0.08750000000000001 are the usual float rendering of
0.05 * 3and0.05 * 1.75; spend is already stored as a floatAzure Cost Management for these calls posts the next day. The earlier run on 2026-09-22 (4 calls, all 1024x1024) reconciled exactly:
Flex Megapixel4.0 MP at $0.20 andFlex Ref Megapixel6.0 MP at $0.30, for $0.50 against $0.21 of proxy spend on the basePricing source
Both
input_cost_per_pixelandinput_cost_per_reference_pixelonazure_ai/FLUX.2-flexare4.76837158203125e-08, which is 0.05 / 1048576. The Azure retail prices API (filterproductName eq 'Azure BFL Flux Models', checked 2026-09-23) listsmeterName: Flex MegapixelandmeterName: Flex Ref MegapixelatretailPrice: 0.05per megapixel. Azure Cost Management usage for the QA Foundry resources on 2026-09-22 records whole 1024x1024 images as whole megapixels (UsageQuantity 4.0 onFlex Megapixeland 6.0 onFlex Ref Megapixel), which is only consistent with 1 MP = 1,048,576 pixels, the definition BFL also publishes. The previous5e-08treated a megapixel as 1,000,000 pixels and over-billed every Flex generation by 4.86%Regression tests and mutation
On the merge base the extended edit tests fail with
KeyError: 'reference_pixels'because the hidden key does not exist, and the schema invariant fails becauseazure_ai/FLUX.2-flexhas noinput_cost_per_reference_pixelMutations at the final head, each run against the mapped test files and then restored and verified with
cmp -s: removing the outerexceptinmeasure_reference_imageturnsclosed_and_read_only_streamsandrestores_position_on_read_errorred; dropping the<= 0dimension guard turnsbmp-negative-width,bmp-zero-widthandinvalid_dimensionsred; putting_MAX_JPEG_SEGMENTSback to 64 turnsjpeg-300-app2-segmentsred; counting fill bytes as hops turnsjpeg-1100-fill-bytes-before-sofred; skipping past a marker byte after a lone fill byte (the old1 + int(marker[2] == 0xFF) + int(marker[3] == 0xFF)skip) turnsjpeg-fill-before-large-segmentred; skipping at most 3 fill bytes per hop instead of the whole run turnsjpeg-200k-fill-bytes-before-sofred; consuming a whole 64 KiB fill chunk instead of leaving the last byte as the marker prefix turnsjpeg-65535-fill-bytes-before-sofred; removing the 16 MiB offset cap turnsjpeg-sof-beyond-16mibred; narrowing_JPEG_SOF_MARKERSturns 11 of the 13every_jpeg_sof_markercases red; dropping the C8 and CC exclusions turns bothskips_non_sof_c_range_markerscases red; changingis not Noneto truthiness in_pixel_rateturnsexplicit_zero_deployment_ratered; weakeningisinstance(pixels, int)turns both non integerreference_pixelscases red; replacingor 0.0with.get(key, 0.0)turnscatalog_rate_is_unsetredEarlier mutations, still killed: removing the
_with_reference_pixels(...)wrapper from both handler return sites turns the edit tests red; removing the JPEG0xFFpadding skip turnsjpeg-sof-after-0xff-paddingred; lowering_MAX_JPEG_SEGMENTSto 63 turnsjpeg-sof-at-segment-64-boundaryred; droppingabs()on the BMP height turnsbmp-negative-heightred; swapping the GIF width and height offsets turnsgif89aandgif87ared; dropping the 12 byte BITMAPCOREHEADER branch turnsbmp-coreheaderred. Reverting_reference_costto read the shared catalog instead of the resolved model info turnstest_flux2_flex_reference_cost_prefers_deployment_rateandtest_unlisted_azure_ai_model_bills_deployment_reference_ratered. Restoring each gives a green run. Thesegment_length < 2guard cannot be killed by any input because a sub 2 length can never land the next read on a valid0xFFmarker; it stays as a loop safety netAdversarial review and formal check
A fresh context review council (six investigation lenses, three taxonomy auditors, four independent reviewers, and reproduce, falsify and artifact check verifiers on every verdict flipping finding) ran against the pre push head. It refuted the universal safety claim with three executable counterexamples that the final head fixes and pins with the tests above: a closed
BytesIOraisedValueErrorout of the measurement path, a JPEG with 64 or more pre SOF segments fell to the fallback, and a BMP with a negative width produced a negative pixel total. It also flagged the Terraform provider drift and the tests that pinned the reference term toinput_cost_per_pixel, both fixed hereThe billing model is also checked in Lean 4 (v4.15.0, core only, 34 theorems, no
sorry): the 2^20 megapixel constant and the 48576 pixel gap to 10^6, the Azure invoice reconciliation (4 MP plus 6 MP at 5 cents is 50 cents), additivity of the reference charge over any list of images, pro rata billing of non square and fractional sizes, the fallback only ever under billing by exactly the reference term, provider usage taking precedence over any pixel value, the deployment then catalog then zero rate chain honouring an explicit zero, rejection of non positive dimensions, and the bounds of the JPEG walkType
🐛 Bug Fix
Caveats (if any)
Medium
Low
reference_pixelsin hidden params for image edits on the shared handler; a typed usage field is the follow-upModelInfoand the inline cost map schema intests/test_litellm/test_utils.pygained the same optional key so both schema checks accept itazure_ai/flux.2-prois metered by Azure as first MP, additional MP and reference MP tiers; that pricing shape is left for a follow-upinput_cost_per_pixel,output_cost_per_pixel, per second and per million token rates) keep their pre-existing> 0guard, so an explicit 0 for those is still dropped; that is a follow-upFLUX.1-Kontext-pro, so it is left as per imageFinal Attestation
Note
Medium Risk
Changes spend calculation for Azure AI image edits and adjusts FLUX.2-flex per-megapixel rates; incorrect header parsing could under-bill reference images.
Overview
Azure AI FLUX.2 Flex image edit billing now counts reference image megapixels separately from generated output, and uses Azure’s 1 MP = 1024×1024 definition for per-pixel rates.
Before upload streams are consumed, the shared image-edit path measures reference dimensions from binary headers (PNG, JPEG, WebP, GIF, BMP) and stores the summed pixel count on the response as
reference_pixels. The Azure AI cost calculator addsreference_pixels × input_cost_per_reference_pixelto existing generated-pixel or per-image charges; provider-reported token usage still overrides pixel math when present.Catalog and types gain
input_cost_per_reference_pixel;azure_ai/FLUX.2-flexsets both generated and reference rates to 0.05 / 1,048,576 (replacing the old 1,000,000-pixel megapixel assumption). Unreadable references fall back to billing generated pixels only.Reviewed by Cursor Bugbot for commit 6db3435. Bugbot is set up for automated code reviews on this repo. Configure here.
Link to Devin session: https://app.devin.ai/sessions/70b30e9f289d49e08561526f9110a933
Open in Devin Desktop: https://app.devin.ai/desktop/session/70b30e9f289d49e08561526f9110a933?variant=devin
Requested by: @shreshth-berri