Add provider catalog runtime support

This commit is contained in:
whit3rabbit
2026-06-07 10:19:50 -05:00
parent dd82b01799
commit 1b902c5310
9 changed files with 1157 additions and 8 deletions
Generated
+3
View File
@@ -89,7 +89,10 @@ dependencies = [
name = "anyllm_providers"
version = "0.9.6"
dependencies = [
"reqwest",
"serde",
"serde_json",
"tokio",
]
[[package]]
+10
View File
@@ -6,5 +6,15 @@ edition.workspace = true
license.workspace = true
repository.workspace = true
[features]
default = []
runtime-catalog = ["dep:serde_json"]
remote-catalog = ["runtime-catalog", "dep:reqwest"]
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = { version = "1", optional = true }
reqwest = { version = "0.12", optional = true, default-features = false, features = ["native-tls", "stream"] }
[dev-dependencies]
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
+40 -1
View File
@@ -98,9 +98,48 @@ Other public items (re-exported from the crate root):
- Single-model lookup: `get_model(provider_id, model_id)`.
- Migration helper: `canonical_provider_id(provider_id)`.
## Runtime LiteLLM updates
The default crate remains static and does no I/O. If your app needs newer
LiteLLM provider/model rows without waiting for an `anyllm_providers` release,
enable the opt-in runtime catalog features:
```toml
[dependencies]
anyllm_providers = { version = "0.9", features = ["remote-catalog"] }
```
`runtime-catalog` adds owned catalog types and a parser for LiteLLM's
`model_prices_and_context_window.json`. `remote-catalog` also adds explicit
fetch/cache helpers using a caller-provided `reqwest::Client`:
```rust
use anyllm_providers::{ProviderCatalog, RemoteCatalogOptions};
let client = reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::none())
.timeout(std::time::Duration::from_secs(30))
.build()?;
let cache_dir = std::env::temp_dir().join("anyllm-provider-catalog");
let options = RemoteCatalogOptions::default()
.with_cache_dir(&cache_dir)
.with_stale_on_error(true);
let catalog = ProviderCatalog::fetch_litellm_with_options(&client, &options).await?;
for p in catalog.all_providers() {
println!("{}: {} models", p.id, catalog.list_models(&p.id).len());
}
```
Known providers keep the bundled auth, protocol, env-var, and base-url metadata.
Brand-new LiteLLM providers are exposed with `ProviderStatus::Stub`, a guessed
API-key env var, and an empty default base URL so callers do not silently route
to the wrong endpoint.
## Integrating into a TUI (or any client app)
This crate gives you the catalog. It does **not** ship an HTTP client, an async runtime, or a key store, so a TUI integrates it as a read-only data source and supplies its own transport (typically `reqwest`) and config (typically `std::env` or a config file).
In its default mode this crate gives you the static catalog. It does **not** ship an HTTP client, an async runtime, or a key store, so a TUI integrates it as a read-only data source and supplies its own transport (typically `reqwest`) and config (typically `std::env` or a config file).
The recommended local integration can use **Ollama** and **LM Studio** as built-in providers. Both are local, OpenAI-compatible, require no API key, and their catalog entries (`auth: AuthKind::None`, `protocol: ProviderProtocol::OpenAICompat`) reflect that. Ollama is part of the LiteLLM snapshot; LM Studio is a legacy-only local provider that still resolves through `get_provider("lm_studio")` but is not returned by `all_providers()`.
File diff suppressed because it is too large Load Diff
+8
View File
@@ -1,8 +1,16 @@
#[cfg(feature = "runtime-catalog")]
pub mod catalog;
pub mod model;
pub mod provider;
pub mod providers;
pub mod registry;
#[cfg(feature = "runtime-catalog")]
pub use catalog::{
CatalogError, CatalogMetadata, OwnedModelDef, OwnedProviderDef, ProviderCatalog,
};
#[cfg(feature = "remote-catalog")]
pub use catalog::{RemoteCatalogOptions, DEFAULT_MAX_CATALOG_BYTES, LITELLM_CATALOG_URL};
pub use model::{ModelCapabilities, ModelDef, ModelStatus};
pub use provider::{AuthKind, ProviderCapabilities, ProviderDef, ProviderProtocol, ProviderStatus};
pub use registry::{
+2 -2
View File
@@ -1,5 +1,5 @@
/// Features a specific model supports.
#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ModelCapabilities {
pub streaming: bool,
pub tool_use: bool,
@@ -9,7 +9,7 @@ pub struct ModelCapabilities {
}
/// Availability of a model.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ModelStatus {
Available,
Deprecated,
+4 -4
View File
@@ -1,6 +1,6 @@
/// Wire format / HTTP client strategy used to communicate with a provider.
/// Maps to proxy's `BackendKind` variants.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ProviderProtocol {
/// Standard OpenAI Chat Completions (Groq, Together, Mistral, etc.)
OpenAICompat,
@@ -21,7 +21,7 @@ pub enum ProviderProtocol {
}
/// How the provider expects authentication.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum AuthKind {
/// `Authorization: Bearer <key>`
Bearer,
@@ -36,7 +36,7 @@ pub enum AuthKind {
}
/// Implementation maturity of a provider.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ProviderStatus {
/// HTTP client exists and has been live-tested.
Implemented,
@@ -48,7 +48,7 @@ pub enum ProviderStatus {
}
/// Capabilities advertised for a provider (endpoint-level, not model-level).
#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ProviderCapabilities {
pub chat_completions: bool,
pub streaming: bool,
+20
View File
@@ -93,6 +93,26 @@ static LEGACY_ONLY_MODELS: &[(&str, &[ModelDef])] = &[
("xinference", providers::xinference::MODELS),
];
#[cfg(feature = "runtime-catalog")]
pub(crate) fn advertised_provider_defs() -> &'static [&'static ProviderDef] {
ALL_PROVIDERS
}
#[cfg(feature = "runtime-catalog")]
pub(crate) fn legacy_only_provider_defs() -> &'static [&'static ProviderDef] {
LEGACY_ONLY_PROVIDERS
}
#[cfg(feature = "runtime-catalog")]
pub(crate) fn advertised_model_groups() -> &'static [(&'static str, &'static [ModelDef])] {
ALL_MODELS
}
#[cfg(feature = "runtime-catalog")]
pub(crate) fn legacy_only_model_groups() -> &'static [(&'static str, &'static [ModelDef])] {
LEGACY_ONLY_MODELS
}
/// Return the LiteLLM-canonical provider id for a local legacy provider id.
pub fn canonical_provider_id(id: &str) -> &str {
PROVIDER_ALIASES
+1 -1
View File
@@ -10,7 +10,7 @@ repository.workspace = true
anyllm_translate = { path = "../translator", version = "0.9.6" }
anyllm_client = { path = "../client", version = "0.9.6" }
anyllm_batch_engine = { path = "../batch_engine", version = "0.9.6" }
anyllm_providers = { path = "../providers", version = "0.9.6" }
anyllm_providers = { path = "../providers", version = "0.9.6", features = ["runtime-catalog"] }
axum = { version = "0.8", features = ["ws", "multipart"] }
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.12", default-features = false, features = ["json", "stream", "native-tls", "http2", "multipart"] }