2026-09-20 The Modifier Was Part of the Slug

What I set out to do

Check whether one agy conversation was instrumented properly. It was, and the control query I ran to prove it turned up a cost gap that had nothing to do with agy.

What I actually did

Grouping gen_ai.cost.total_cost by span name over 24h showed OpenRouter models carrying cost and Code Assist models not, which is correct: gemini-3.8-flash-high bills against a subscription quota, so there is no per-token price to attach. That is the split the quota gauges exist to cover.

What I got wrong was the leftover. I saw deepseek/deepseek-v4-flash-0731:nitro at null next to z-ai/glm-5.3-flash:nitro at 0.00014 and called the deepseek one anomalous, reasoning that the glm row proved nitro variants get costed. Adam pushed back that the parser has a notion of modifiers, and the cost map settled it:

openrouter/deepseek/deepseek-v4-flash-0731         4e-08 / 8e-08
openrouter/deepseek/deepseek-v4-flash-0731:nitro   MISS
openrouter/z-ai/glm-5.3-flash                      9e-08 / 3e-07
openrouter/z-ai/glm-5.3-flash:nitro                MISS
nitro keys in map: []

No :nitro key exists at all, so the glm row was never proof of anything. Per span it was 12 null out of 14, and the deepseek one was 11 null out of 11 on successful 5-7k token calls. Summing a mostly-null group still prints a number, and that number was two spans.

The cause is structural. litellm_model_resolver re-attaches the modifier to the wire slug because the modifier is what selects the routing, and the cost map is read at that exact spelling. Neither OpenRouter catalog lists modifier spellings, so every :nitro, :floor and :batch request missed.

Fixed in litellm_openrouter_enrichment.py by mirroring each refreshed base entry onto its modifier spellings, after both catalog passes so a dual-catalog model keeps its per-image rate at the modifier spelling. Mirroring walks the models the catalog named rather than everything owned, because an alias is itself owned and would otherwise grow ...:nitro:nitro on the next refresh. :free is excluded: it is a real catalog entry with its own zero price, so deriving it from the paid base would invent a charge.

Six tests first, three of which failed against the old code. Verified live: fresh :nitro requests, streaming and not, now export total_cost 0.00000655 with input_cost and output_cost populated. Committed as 28f32ce.

What was striking

The blast radius was smaller than it looked but pointed at the wrong place. response_cost comes from the provider’s reported usage, not the map, so actual spend accounting was always right. Only the gen_ai.cost.* span breakdown was blind, and that is precisely what the cost dashboard reads. The number that was correct and the number I would have looked at were different numbers.

Two process notes. A sum over a grouped aggregation is not evidence about any individual row, and I used one to manufacture a contrast between two models that were behaving identically. And the correction landed as four words about our own parser, which was enough, because the answer was in code I could read rather than in anything I needed to reason out.