v0.1.20 - Cost Calculation Architecture Overhaul
Pricing architecture overhaul
The SDK's cost estimation subsystem has been rewritten from a stub into a proper 4-level resolution chain. Previously, pricing_config.py held a small hard-coded DEFAULT_PRICING dict and a placeholder fetch_remote_pricing() that just returned it. Now:
- Bundled snapshot — BUNDLED_PRICING is no longer a hard-coded dict. It is loaded at import time from data/pricing_snapshot.json, a 2,500+ model snapshot generated by scripts/build_pricing_snapshot.py at release time by fetching LiteLLM's canonical pricing file and normalizing per-token rates to per-1K-token. The CI pipeline (publish.yml) runs this script before every wheel build, so every release ships a fresh snapshot. The old DEFAULT_PRICING name is kept as an alias.
- Local cache — traccia pricing refresh writes to ~/.cache/traccia/pricing.json (overridable via TRACCIA_PRICING_CACHE_PATH). load_pricing_with_source() layers this on top of the bundled snapshot, not replacing the whole table but using the cached table as the base.
- Env var override — TRACCIA_PRICING_OVERRIDE_JSON merges on top. AGENT_DASHBOARD_PRICING_JSON is still accepted with a one-time WARNING deprecation log. Previously, only the legacy variable name existed.
- Programmatic override — init(pricing_override={…}) merges on top of everything. Previously this existed but only merged onto the stub default table; now it merges onto whichever of the above layers resolved.
load_pricing_with_source() now returns a 3-tuple (table, source, generated_at) instead of 2. The generated_at ISO timestamp travels through the entire stack.
Per-span pricing provenance attributes
CostAnnotatingProcessor now writes four new span attributes on every LLM call that resolves a cost:
- llm.pricing.generated_at — ISO timestamp of the snapshot used
- llm.pricing.age_days — integer age at emit time
- llm.pricing.snapshot_version — stable identifier for the snapshot (same as generated_at for bundled/cached; distinct for programmatic overrides)
- llm.pricing.model_key — the exact key matched in the pricing table
The processor also logs a staleness warning once per process: INFO at >7 days, WARNING at >30 days, guarded by a threading.Event so it fires exactly once regardless of call volume.
CostResolver singleton — trace/metrics parity fix
A new cost_resolver.py introduces a thread-safe process-level CostResolver singleton. Previously, the OpenAI Agents SDK integration's metrics recorder called load_pricing() independently, meaning a pricing_override passed via init() was reflected in span attributes but NOT in the gen_ai.client.operation.cost histogram. Now both paths share the same resolver. start_tracing() calls set_resolver() with the resolved table; the background pricing refresh thread calls get_resolver().update(). The resolver uses a per-instance threading.Lock with an atomic snapshot() method.
traccia pricing CLI command group
Three new subcommands:
- traccia pricing status — shows bundled snapshot age/count and local cache info side by side, including the active resolution source
- traccia pricing refresh — fetches from https://api.traccia.ai/v1/pricing/latest first (with ETag/If-None-Match caching and correct urllib 304-as-HTTPError handling), falls back to LiteLLM GitHub raw JSON automatically. --source upstream forces the fallback. Platform URL is hardcoded; TRACCIA_API_URL exists as an undocumented internal escape hatch only.
- traccia pricing clear — removes the local cache
_load_dotenv_if_present() now passes dotenv_path=os.path.join(os.getcwd(), ".env") explicitly to python-dotenv, fixing a bug where the default search started from the SDK package tree rather than the process CWD.
Prefix match fix in cost_engine.py
_lookup_price() previously could match gpt-4o against the gpt-4 key. Keys are now sorted by descending length before prefix matching, so gpt-4o beats gpt-4 when both are present.
Tests
New tests/test_pricing.py covers: bundled snapshot loading, DEFAULT_PRICING alias, 4-level precedence (each level beats the one below), deprecated env var alias + warning, snapshot_age_days, CostAnnotatingProcessor new attributes, staleness warning fires exactly once, CostResolver thread safety under concurrent reads and writes, and pricing_override trace/metrics parity.