Span attributes make one run explainable. Metrics make spend and capacity visible across runs. Story-1440 adds the missing metrics layer for Atlas model usage without turning tenant, run, matter, user, trace, or span identifiers into metric dimensions.
The mental model: traces answer "what happened to this run"; metrics answer "what is the fleet doing by model, provider, agent, and environment". Those are different jobs, so their labels are different.
runtime.handle_request()
final input/output token totals
final computed USD cost
|
v
observability.record_genai_token_usage_and_cost()
gen_ai.client.token.usage histogram
pramaan.gen_ai.cost counter
|
v
dedicated Logfire OTLP metrics exporter
https://logfire-us.pydantic.dev/v1/metrics
| Metric | Type | Unit | Allowed labels |
|---|---|---|---|
gen_ai.client.token.usage | histogram | {token} | gen_ai.token.type, model_id, provider, agent.key, deployment.environment.name |
pramaan.gen_ai.cost | counter | USD | model_id, provider, agent.key, deployment.environment.name |
firm_id, run_id, matter_id, user_id, trace_id, and span_id.| Standard OTel keys | PRAMAAN product keys |
|---|---|
gen_ai.client.token.usage, gen_ai.token.type, gen_ai.agent.name=Atlas, service.name=pramaan-agents-atlas, deployment.environment.name | agent.key=atlas, model_id. run_id, firm_id, and form_id stay trace/log context, not metric labels. |
The runtime pins OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental before Pydantic AI instrumentation.
-- Logfire / OTLP metrics view
SELECT
date_trunc('day', timestamp) AS day,
attributes['deployment.environment.name'] AS env,
attributes['agent.key'] AS agent_key,
attributes['provider'] AS provider,
attributes['model_id'] AS model_id,
sum(value) AS daily_cost_usd
FROM metrics
WHERE metric_name = 'pramaan.gen_ai.cost'
GROUP BY 1, 2, 3, 4, 5
ORDER BY day DESC, daily_cost_usd DESC;
The unit tests assert the metric registry contract directly, the Logfire export path, the semconv opt-in, and the runtime call from final token totals. They also plant a forbidden run_id label and prove the metric-label guard rejects it.
ruff check tests/ scripts/ agents/atlas, Atlas focused pytest, root guard tests, remote metric registry guard for the merged pramaan-docs registry, and git diff --check.