Track LLM cost in Datadog LLM Obs for the BiggiePockets review - #54
Merged
Conversation
bockets
force-pushed
the
datadog-llm-cost-tracking
branch
from
September 10, 2026 14:02
947a99e to
a09ff71
Compare
bockets
force-pushed
the
datadog-llm-cost-tracking
branch
from
September 10, 2026 14:18
a09ff71 to
a4b8d5e
Compare
Each LLM span in the review trace now carries what the pass actually used and,
where it is known, what it actually cost, so spend is attributed per model on the
same trace as the review-quality metrics.
No rate table lives in this repository. A list price committed to a file goes
stale silently and would be reported with the same confidence as a real one.
Instead each span carries whichever of the two things Datadog needs to price it:
token counts for a model in its catalog, and a `total_cost` metric for one it does
not carry. That cost is the amount OpenRouter reports charging, from the `cost`
field it returns on every response.
Datadog's catalog keys on the bare model and the provider that originated it, so
the OpenRouter slug is split ("openai/gpt-5.6-sol" -> gpt-5.6-sol + openai) and the
routing is recorded as a gateway tag. Without that split nothing would match and no
model would be priced at all.
The two passes record usage differently. Claude Code writes a `usage` object to its
execution output. Codex writes running token counters to a session rollout on its
own runner and its action exposes no usage output, so the Codex job reads that
rollout and hands the totals to the reporting job through the existing handoff.
Claude Code's own total_cost_usd is ignored: it is computed against Anthropic's
list prices while these passes are billed by OpenRouter for a non-Anthropic model.
The Datadog site and ml_app move to the DD_SITE and DD_LLMOBS_ML_APP org-level
variables, so no Datadog endpoint or org identifier is committed here. Both fall
back to public defaults.
Only usage objects are read — never message content, transcripts, prompts, or
diffs. Reporting stays best-effort and cannot fail a review.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
bockets
force-pushed
the
datadog-llm-cost-tracking
branch
from
September 10, 2026 14:47
a4b8d5e to
0f24174
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this does
Attributes the cost of a BiggiePockets review per model, on the same Datadog LLM Obs
trace as the review-quality metrics. Both LLM spans in the trace —
codex.reviewandclaude.synthesize— now carry what the pass used and, where it is known, what itcost.
Where the money comes from
No rate table lives in this repository. A list price committed to a file goes stale
silently and would be reported with the same confidence as a real one. Each span
instead carries whichever of the two things Datadog needs:
itself, from rates it maintains.
total_costmetric, for a model it does not carry — the amount OpenRouterreports charging, from the
costfield it returns on every response.Today that splits cleanly:
gpt-5.6-solis in the catalog, anddeepseek-v4.1-flashis not.Making the catalog match
Datadog keys its catalog on the bare model and the provider that originated it, not
on a gateway slug. The workflow names models the way OpenRouter routes them, so
openai/gpt-5.6-solis split intomodel_name: gpt-5.6-sol/model_provider: openai, and the routing is kept as agateway:openroutertag.Without that split nothing matches the catalog and no model gets priced at all.
Reading usage from each pass
The two tools record it differently:
usageobject to its execution output.openai/codex-action@v1declares exactly one output (final-message) — no usage.So the Codex job reads that rollout itself and hands the totals to the reporting job
through the handoff artifact that already exists.
Claude Code's own
total_cost_usdis deliberately ignored. It is computed againstAnthropic's list prices, while these passes are billed by OpenRouter for a
non-Anthropic model, so it describes a bill nobody was sent.
Configuration
Two organization-level variables, both optional. No Datadog endpoint or org
identifier is committed to this repository.
DD_SITEdatadoghq.comDD_LLMOBS_ML_APPml_appthe trace is grouped under.biggiepockets-reviewCost reporting rides on the existing
DATADOG_API_KEYsecret; with no key the wholestep is skipped.
Safety
Only usage objects are read — never message content, transcripts, prompts, or diffs.
The helper never raises and always exits 0, the reporting step is
continue-on-error, and unusable usage data falls back to a valid empty object so acost problem cannot cost us the quality metrics in the same payload.
Testing
tests/test_llm_usage.py; 38 total across the suite, all passing.jqspan program and ran it against real helperoutput over a Codex rollout fixture and a Claude execution fixture. Result:
codex.reviewcarriesinput_tokens: 31751 / output_tokens: 2367 / cache_read_input_tokens: 14720 / total_tokens: 34118asgpt-5.6-sol+openai;claude.synthesizecarries its counts plustotal_cost: 0.0031asdeepseek-v4.1-flash+deepseek, with the bogustotal_cost_usd: 9.99in thefixture correctly ignored.
gpt-5.6-solis priced anddeepseek-v4.1-flashreturnsskipped_unsupported_model_pricing, which is whatmakes the two paths necessary.
Not verified: an end-to-end run against Datadog itself, and whether OpenRouter's
costfield survives the Anthropic-compatible round trip into what Claude Codewrites to disk. Both need a real review run with the org variables configured. If
that field does not come through, the Claude span reports tokens and no cost rather
than a guess.