Skip to content

Add update_genai_prices flag to logfire.configure()#1960

Open
JonathanTsen wants to merge 5 commits into
pydantic:mainfrom
JonathanTsen:feat/genai-prices-auto-update
Open

Add update_genai_prices flag to logfire.configure()#1960
JonathanTsen wants to merge 5 commits into
pydantic:mainfrom
JonathanTsen:feat/genai-prices-auto-update

Conversation

@JonathanTsen
Copy link
Copy Markdown

@JonathanTsen JonathanTsen commented May 23, 2026

Problem

Pydantic AI uses genai-prices to populate the operation.cost attribute on LLM spans. The catalogue mirrors the upstream provider pricing pages — for example Google Gemini API pricing and OpenAI API pricing — and is shipped as a snapshot inside the package. So when a brand-new model is released (e.g. gemini-3.5-flash) calc_price raises LookupError, which pydantic-ai then swallows silently in pydantic_ai/_instrumentation.py. The result is that newly released models show up in Logfire with correct token counts but no operation.cost, until a new genai-prices release is published, pulled in transitively, and reinstalled.

genai-prices itself already exposes an opt-in UpdatePrices background updater that refreshes the catalogue from the upstream repo. This PR makes that updater available through a single Logfire configure flag.

Change

A new opt-in kwarg update_genai_prices: bool | None = None on logfire.configure().

  • Default: False (no behaviour change for existing users).
  • When True, Logfire instantiates genai_prices.update_prices.UpdatePrices and starts the daemon thread that polls upstream every hour.
  • Lifecycle is fully managed: stopped automatically on reconfigure and on interpreter exit via atexit.
  • Skipped under emscripten (no threads).
  • If genai-prices is not installed (or fails to import — e.g. on pydantic < 2.5 environments missing pydantic.Tag), the flag emits a one-shot UserWarning and is a no-op.
  • Also exposed via the LOGFIRE_UPDATE_GENAI_PRICES=1 environment variable, following the existing ConfigParam pattern.

Usage

import logfire

logfire.configure(update_genai_prices=True)
logfire.instrument_pydantic_ai()

Or via env var:

LOGFIRE_UPDATE_GENAI_PRICES=1

Pricing source of truth

The model prices used to compute operation.cost are maintained in pydantic/genai-prices and mirror the official provider catalogues:

This PR doesn't change how the prices are derived — it only lets Logfire users pick up upstream updates without waiting for a genai-prices release.

Files changed

  • logfire/_internal/config_params.py — new UPDATE_GENAI_PRICES ConfigParam and registration in CONFIG_PARAMS.
  • logfire/_internal/config.py — kwarg threaded through configure(), _LogfireConfigData, _load_configuration, LogfireConfig.__init__/configure; lifecycle in _initialize via two helpers (_maybe_start_genai_prices_updater / _stop_genai_prices_updater); new field surfaced in emit_configuration_span.
  • logfire-api/logfire_api/_internal/config.pyi and config_params.pyi — regenerated stubs.
  • docs/integrations/llms/pydanticai.md — new "Keep model pricing up to date" section.
  • tests/test_configure.py — 6 new tests covering default-off, enabled-starts, env var, graceful ImportError, reconfigure-stops-old, and emscripten-skip; existing test_configuration_span_emitted_when_opted_in snapshot updated to include the new field.

Tests

All new tests mock genai_prices.update_prices.UpdatePrices, so no network or real threads are needed in CI.

$ uv run pytest tests/test_configure.py tests/test_logfire_api.py tests/test_logfire.py
199 passed, 2 skipped
$ make lint
All checks passed
$ make typecheck
0 errors, 0 warnings, 0 informations

Adds a new opt-in flag (default False) that, when enabled, starts a daemon
thread refreshing the genai-prices model pricing snapshot from upstream every
hour. This keeps operation.cost attributes correct for newly released models
(e.g. gemini-3.5-flash) without requiring a package upgrade.

The thread is gracefully a no-op (with a one-shot warning) when genai-prices
is not installed, and is properly torn down on reconfigure or interpreter exit.

Also exposed via the LOGFIRE_UPDATE_GENAI_PRICES=1 environment variable.
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 6 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Re-trigger cubic

The `genai-prices` package depends on `pydantic.Tag`, which was introduced in
pydantic 2.5. On the `pydantic 2.4` CI matrix entry, importing
`genai_prices.update_prices` raises `AttributeError: module 'pydantic._migration'
has no attribute 'Tag'` — not `ImportError`. Three fixes:

- `config.py`: broaden the lazy-import guard from `except ImportError` to also
  catch generic import-time exceptions, emitting an informative warning instead
  of crashing `logfire.configure()` over a non-critical feature.
- `tests/test_configure.py`: skip the 4 `patch('genai_prices.update_prices...')`
  tests via `pytest.importorskip` when the module cannot be imported in the
  current environment (e.g. the pydantic 2.4 matrix). The graceful-when-not-
  installed test stays unconditional because it exercises exactly that path.
- `docs/integrations/llms/pydanticai.md`: mark the new example block as
  `skip-run="true" skip-reason="external-connection"`, matching the other LLM
  integration docs which also need a real network connection at runtime.
`pytest.importorskip()` only catches `ImportError`. When importing
`genai_prices.update_prices` on the pydantic 2.4 CI matrix, the failure mode is
`AttributeError: module 'pydantic._migration' has no attribute 'Tag'`, which
escapes the guard and surfaces as a test failure.

Replace the four `pytest.importorskip` calls with a local helper that calls
`importlib.import_module` inside a broad `try/except` and `pytest.skip`s on any
exception during import. The helper sits near the affected tests so the intent
stays local.
@codecov
Copy link
Copy Markdown

codecov Bot commented May 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

The `if not emscripten` guard around `_maybe_start_genai_prices_updater()` had
no test exercising the emscripten=True path on non-pyodide CI, so the branch
coverage check failed by exactly one branch (1430->exit, 99.99% total).

Add `test_update_genai_prices_skipped_under_emscripten` which monkeypatches
`platform_is_emscripten` to return True and verifies the updater is not
instantiated even when the flag is enabled.
Reviewers shouldn't need to dig through the `genai-prices` repo to confirm where
the model costs come from. Reference the two upstream sources of truth directly
in the "Keep model pricing up to date" section.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant