Multi-source LLM model catalog with cost-accurate pricing. Fetches model metadata from LiteLLM and truefoundry/models, merges them with cost-accurate pricing, filters deprecated models, and returns a unified catalog keyed by TensorZero provider/model.
Covers text, embedding, image and voice models — speech-to-text and text-to-speech entries from Deepgram, ElevenLabs, AssemblyAI, AWS Polly and Groq are included, priced per second or per character.
pip install bud-model-catalogfrom bud_model_catalog import CatalogClient
client = CatalogClient()
result = client.fetch_catalog_sync()
# Inspect merge statistics
print(result.stats)
# MergeStats(total_litellm=847, total_output=812, matched=342, ...)
# Look up a specific model's pricing
model = result.models["openai/gpt-4o"]
print(f"Input: ${model['input_cost_per_token']}/token")
print(f"Output: ${model['output_cost_per_token']}/token")Speech models are keyed like any other entry and carry mode plus duration- or
character-based costs:
result = client.fetch_catalog_sync()
stt = result.models["deepgram/nova-3"]
print(stt["mode"]) # audio_transcription
print(stt["input_cost_per_second"]) # 7.167e-05
tts = result.models["elevenlabs/eleven_multilingual_v2"]
print(tts["mode"]) # audio_speech
print(tts["input_cost_per_character"]) # 0.0001
# All voice models across providers
voice = {
key: m for key, m in result.models.items()
if m.get("mode") in ("audio_transcription", "audio_speech")
}Only providers that upstream actually publishes are included. Vendors served elsewhere (Cartesia, LMNT, Murf, PlayHT, Speechmatics, …) have no entries in either source and so do not appear in the catalog.
import asyncio
from bud_model_catalog import CatalogClient, CatalogConfig
async def main():
config = CatalogConfig(include_deprecated=True, timeout=60)
client = CatalogClient(config)
result = await client.fetch_catalog()
# Filter to a single provider
anthropic_models = {
key: model for key, model in result.models.items()
if key.startswith("anthropic/")
}
for key, model in list(anthropic_models.items())[:3]:
print(f"{key}: ${model.get('input_cost_per_token', 'N/A')}/token")
asyncio.run(main())fetch_catalog_sync() works inside Jupyter's running event loop:
# In a Jupyter notebook cell:
from bud_model_catalog import CatalogClient
client = CatalogClient()
result = client.fetch_catalog_sync() # works inside Jupyter's event loop
print(f"{len(result.models)} models loaded")
# Or use await directly in a notebook cell:
result = await client.fetch_catalog()from bud_model_catalog import fetch_catalog
# One-shot fetch (no ETag caching across calls)
result = await fetch_catalog()All options are passed via CatalogConfig:
| Field | Type | Default | Description |
|---|---|---|---|
litellm_url |
str |
GitHub raw URL | URL to the LiteLLM model prices JSON |
ai_models_url |
str |
GitHub archive URL | URL to the truefoundry/models ZIP archive |
timeout |
int |
30 |
HTTP request timeout in seconds (must be > 0) |
include_deprecated |
bool |
False |
Whether to include deprecated models in output |
max_retries |
int |
2 |
Maximum retry attempts per HTTP request (with exponential backoff) |
cache |
bool |
True |
Enable ETag-based conditional GET caching across calls |
from bud_model_catalog import CatalogConfig
config = CatalogConfig(
timeout=60,
include_deprecated=True,
max_retries=3,
cache=True,
)Validation is enforced at construction time:
CatalogConfig(timeout=-1) # ValueError: timeout must be positive
CatalogConfig(litellm_url="not-a-url") # ValueError: must be an HTTP(S) URLfrom bud_model_catalog import CatalogClient, CatalogConfig, SourceFetchError
try:
result = CatalogClient().fetch_catalog_sync()
except SourceFetchError as e:
print(f"Failed to fetch data: {e}")SourceFetchError— raised when LiteLLM fetch fails (HTTP error, invalid JSON, timeout)- ai-models failures are handled gracefully — the SDK falls back to LiteLLM-only costs
Main entry point for fetching the catalog.
CatalogClient(config=None)— create a client with optionalCatalogConfigawait client.fetch_catalog()— async fetch, returnsCatalogResultclient.fetch_catalog_sync()— blocking sync wrapper (works in Jupyter, async frameworks, and plain scripts)
Pydantic model returned from fetch operations.
models: dict[str, dict]— merged model catalog keyed by{provider}/{model}stats: MergeStats— merge statisticslitellm_fetched_at: datetime— timestamp of LiteLLM fetchai_models_fetched_at: datetime | None— timestamp of ai-models fetch (None if failed/skipped)
total_litellm— total models from LiteLLM sourcetotal_output— models in final outputmatched— models matched with ai-models dataunmatched— models without ai-models matchdeprecated_removed— models filtered as deprecatedcost_fields_updated— individual cost field values updated from ai-models
The SDK uses Python's logging module. Enable output to see fetch/merge details:
import logging
logging.basicConfig(level=logging.INFO)Key log messages:
INFO— fetch counts, merge statistics, cache hitsWARNING— ai-models fallback, malformed YAML files skipped, retry attempts
# Install dev dependencies
pip install -e ".[dev]"
# Run tests
pytest -v
# Lint
ruff check src/ tests/