Pricing model deployments in Langfuse
Prerequisites: 03-tracing-generation.md and 05-tracing-frameworks.md (Token usage and cost).
Langfuse puts a cost on a generation only when one of the project's model definitions
matches it. An Azure deployment usually does not match one, or matches one at the wrong price,
and nothing reports it: the generation shows its tokens and no cost, or a cost lower than the
bill. Everything that reads cost from the trace reads that figure, including the cost_usd
trace metric of an offline evaluation run and the experiment report that compares runs on it.
An unpriced model then reads as free.
This page explains how Langfuse prices a generation, where an Azure deployment's price comes
from, and the command that writes the definition: python -m semantic_tracing.pricing.
How Langfuse prices a generation
Definition: model definition A Langfuse record, per project, that prices generations: a
matchPattern(a regular expression tested against the generation'smodel), one or more pricing tiers, and per tier a price per token for each usage key. Langfuse ships its own, called managed definitions, at each provider's list price. A project adds its own under Settings, Models, or through the API. A project's own definition wins over a managed one that matches the same name.
For each generation, Langfuse:
- finds the definition whose pattern matches the generation's
model, the project's own before a managed one (a project holds one definition per name); - picks the tier whose conditions hold (a tier for priority processing applies when
model_parameters.service_tierispriority, a tier for large context when the input exceeds a token count), or the default tier; - multiplies each usage key in the generation's
usage_detailsby that tier's price for the key, and adds them up.
Three consequences matter:
- A usage key without a price costs 0, without a warning. The keys are the buckets
semantic_tracingwrites (input,input_cached_tokens,output,output_reasoning_tokens, see 05-tracing-frameworks.md), and a definition must price every one of them, and the aliases other clients send. - Cost is fixed when the generation is ingested. A new or changed definition prices only generations logged after it. Runs from before and after a price change are not comparable on cost unless that is said.
- A cost the client sends wins. A project that ingests
cost_detailsitself needs no definition.
Where an Azure deployment's price comes from
An Azure deployment is priced by its deployment type and region, not by the model
alone. Data Zone Standard costs more than Global Standard: for gpt-5-mini in Sweden Central,
input is 0.275 USD per million tokens in a Data Zone deployment and 0.25 in a Global one.
Langfuse's managed definitions hold OpenAI's list price, which equals the Global price. So a
managed definition prices a Global Standard deployment correctly, and a Data Zone or regional
deployment too low.
| Question | Answer |
|---|---|
| Where the prices come from | Azure's Retail Prices API, which is public and returns exact retail prices in USD. Not the pricing web page, which fills its table in with script and rounds it |
| What they are | Retail list prices in USD. A negotiated discount or an invoice in another currency is not reflected, and the pricing record says "retail" |
| Where the deployment type and region come from | Read from the deployment, never assumed: az cognitiveservices account deployment show gives the type (sku.name), az cognitiveservices account show the region. Pricing a region does not approve it; where model calls may go is decided elsewhere |
Provisioned throughput (ProvisionedManaged and its zone variants) |
Billed per hour, not per token, so it has no token definition. It stays unpriced, and the record says why |
| Batch deployments | Priced from their own batch meters |
| Tool sessions, such as a code interpreter | Billed per session, not per token; not covered here |
| Other providers | The provider's own price list. When a managed definition already matches the name the project reports and holds that price, there is nothing to do |
The API's meter names are irregular (GPT 5 Mini Inpt DZone, 5.6 luna ShortCo Inp Std DZ,
5 mini pp cd Inp Dz), so the command reads them word by word against a vocabulary:
| In the meter name | Means |
|---|---|
Inp, Inpt, in |
Input |
Cd, cchd with input |
Cached input |
Cd Wr |
Cache write |
Opt, outpt, Outp, out |
Output, which also prices reasoning tokens |
No usage word (Microsoft Decision 1) |
All tokens alike: Langfuse's total key |
Dz, DZone, DZn / Gl, Glbl / none |
Data Zone / Global / regional |
Std / pp / Flex, Fl |
Standard / priority processing / flex |
ShortCo / LongCo |
Standard context / large context |
Batch |
A batch deployment |
A meter with any other word is shown as one the command cannot read, and is not used.
Hyphens count as spaces (6.1-sol is gpt-6.1-sol). A model with a similar name is another
model at other prices (6-sol is not gpt-6.1-sol), so when no meter names the model the
command lists the related names it found and stops; none of them stands in for it. The
price is divided by the meter's unit (1M or 1K tokens), and of two meters for the same
thing the one with the latest effective date wins.
What name to match
Match the model value the deployment's generations actually report: unpriced lists them.
Azure clients report either the deployment name (gpt-5-mini-DZ) or the dated model name
(gpt-5-mini-2025-08-07), so the pattern matches both, anchored and case-insensitive:
(?i)^(gpt-5-mini-DZ|gpt-5-mini-2025-08-07)$. Add the bare model name (gpt-5-mini) only
when no other deployment of that model, at another price, reports to the same Langfuse
project: two definitions that match one name make the price depend on which wins.
How the definition is built
- From Langfuse's managed definition of the model, never by editing it. The command copies its tokenizer and every tier (name, priority, conditions), so priority processing, flex and large context keep working.
- Each price from its Azure meter, for that tier and usage. Where Azure has no meter (Flex
for a Data Zone deployment, say), the managed price is scaled by the model's ratio: Azure's
standard input price over the managed one. The ratio differs between models (1.1 for
gpt-5-miniin a Data Zone, 1.2 forgpt-6.1-sol), so it is computed per model, and the plan says which prices came from it. - Without a managed definition (a model Langfuse does not know), one default tier is built from the standard meters.
unitisTOKENS, prices are per token to six significant digits, and the definition is named after the deployment.- One definition per Langfuse project. Each project the deployment reports to (dev, prod, a pod's own) needs its own.
The command
uv run --env-file .env python -m semantic_tracing.pricing <subcommand> ...
It reads LANGFUSE_BASE_URL, LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY from the
environment and works on that Langfuse project; it names a missing variable and never prints
the keys. It needs a project key pair: an organisation key gets 403.
| Subcommand | What it does |
|---|---|
unpriced [--days 7] [--limit 500] |
Recent generations: the models no definition matches, the usage keys the matched definition does not price, and which definition priced the rest, managed or the project's own. Writes nothing |
azure-prices --account A --deployment D [--resource-group RG] |
Reads the deployment with az and lists the meters it is billed by, and any meter of the model it cannot read |
azure-prices --model M --model-version V --region R --deployment-type T |
The same, stated by hand, for a resource az cannot see |
plan <deployment> [--name N] [--match NAME ...] [--include-bare-model] |
The definition it would write, each price with its source (meter or ratio), any other definition whose pattern also matches, and how it differs from the project's own definition of that name. Writes nothing |
apply <same flags> [--record telemetry/model_prices.json] |
Writes the definition and the pricing record. With no definition of that name it creates one; when the project's differs, it replaces it in place, since Langfuse allows one definition per name; when they are the same it writes nothing to Langfuse. Provisioned throughput is recorded as unpriced |
check [--record ...] |
Re-plans every deployment in the record against today's meters and today's managed definition, and reports what changed. Writes nothing; exits 1 when anything differs |
verify --trace-id ID |
For each generation in the trace, Langfuse's cost beside the cost recomputed from its usage and its definition's current prices, so it applies to a trace ingested after the last apply. Exits 1 when they disagree or a generation is unpriced |
<deployment> is either --account and --deployment (with --name defaulting to the
deployment) or the stated form with --name. Errors exit 2.
The pricing record
apply writes telemetry/model_prices.json, to be committed with the project. Per deployment it
holds the region, deployment type, model and version, the matched names and pattern, every meter
used with its effective date, the date the prices were read, the ratio and the prices it set,
and per Langfuse project the definition's id. It is the evidence behind any cost figure the
project reports.
Keeping prices current
A copied definition does not follow Langfuse's updates to its managed definitions (new tiers,
new usage keys), and Azure changes prices. Run check:
- when a deployment is added, renamed, or moved to another deployment type or region;
- when
unpricedreports anything; - before an experiment compares configurations on cost.
When it reports a change, plan shows the whole definition and apply replaces it. Earlier
generations keep the cost they were ingested with, and the committed record's history keeps
the prices they were ingested at.