Registry, catalog, and credentials¶
How providers describe themselves (frozen descriptors, declarative setup specs), how targets and aliases resolve, and how credential references become secrets. Concepts: targets and aliases · credentials.
Provider registry¶
anyinfer.ProviderRegistry ¶
ProviderRegistry(
*,
load_builtins: bool = True,
load_entry_points: bool = True,
)
Maps provider ids and aliases to descriptors, rejecting collisions.
Built-in providers are registered on first use. Entry-point providers are discovered lazily on the first lookup that misses, so installing a provider package is enough to make it resolvable.
register ¶
register(
descriptor: ProviderDescriptor, *, replace: bool = False
) -> None
Register a descriptor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
descriptor
|
ProviderDescriptor
|
The provider to register. |
required |
replace
|
bool
|
Allow replacing an existing registration of the same id. Aliases are still checked against other providers. |
False
|
Raises:
| Type | Description |
|---|---|
ConfigError
|
On a duplicate id or an alias already claimed by another provider. |
unregister ¶
unregister(provider_id: str) -> None
Remove a provider and its aliases. Unknown ids are ignored.
resolve_alias ¶
resolve_alias(name: str) -> str
Resolve a provider name or alias to a canonical provider id.
Raises:
| Type | Description |
|---|---|
ConfigError
|
If no provider claims the name. |
get ¶
get(provider_id: str) -> ProviderDescriptor
Look up a descriptor by id or alias.
Raises:
| Type | Description |
|---|---|
ConfigError
|
If no provider claims the name. |
anyinfer.ProviderDescriptor
dataclass
¶
ProviderDescriptor(
id: str,
display_name: str,
factory: AdapterFactory,
aliases: tuple[str, ...] = (),
locality: Literal[
"hosted", "local", "remote"
] = "hosted",
default_base_url: str | None = None,
requires_base_url: bool = False,
setup: ProviderSetupSpec = ProviderSetupSpec(),
reasoning_translator: ReasoningTranslator = _no_reasoning,
static_capabilities: Mapping[
str, ModelCapabilities
] = dict(),
default_capabilities: ModelCapabilities = ModelCapabilities(),
supports_sessions: bool = False,
grammar_needs_prompt_injection: bool = False,
ignored_parameters: tuple[str, ...] = (),
derived_from: str | None = None,
)
Declarative facts about a provider and how to instantiate its adapter.
id
instance-attribute
¶
id: str
Canonical provider id — the provider half of a provider:model target.
Normalized for lookup: lowercased, stripped, underscores to hyphens.
factory
instance-attribute
¶
factory: AdapterFactory
Builds this provider's adapter instance from its resolved configuration.
aliases
class-attribute
instance-attribute
¶
aliases: tuple[str, ...] = ()
Alternative names that resolve to this provider; each must be globally unique across the registry.
locality
class-attribute
instance-attribute
¶
locality: Literal['hosted', 'local', 'remote'] = 'hosted'
Where inference physically happens.
local means "on this machine" and carries two consequences — genuine zero pricing,
and hardware detection that describes the right computer. remote is the third case
a descriptor can never state on its own: an engine that is normally local, reached over
a network. A client downgrades local to remote when the configured base URL is
not loopback, because stamping zero cost on someone else's metered proxy, or sizing
models against the wrong machine's RAM, are both silent wrong answers.
default_base_url
class-attribute
instance-attribute
¶
default_base_url: str | None = None
Endpoint used when settings supply no base URL; None when there is no sensible
default, as with per-tenant or supervised endpoints.
requires_base_url
class-attribute
instance-attribute
¶
requires_base_url: bool = False
Whether the adapter cannot be built without a configured base URL.
setup
class-attribute
instance-attribute
¶
setup: ProviderSetupSpec = ProviderSetupSpec()
Declarative description of the configuration this provider needs, which is what a config UI renders.
reasoning_translator
class-attribute
instance-attribute
¶
reasoning_translator: ReasoningTranslator = _no_reasoning
Maps normalized reasoning effort onto this provider's wire fields. The default translates every effort to nothing, for providers without a reasoning control.
static_capabilities
class-attribute
instance-attribute
¶
static_capabilities: Mapping[str, ModelCapabilities] = (
field(default_factory=dict)
)
Per-model capabilities declared ahead of time, keyed by model id; layered over
default_capabilities when capabilities are assembled.
default_capabilities
class-attribute
instance-attribute
¶
default_capabilities: ModelCapabilities = (
ModelCapabilities()
)
Capabilities assumed for any model without a more specific source.
supports_sessions
class-attribute
instance-attribute
¶
supports_sessions: bool = False
Whether the provider can keep state between requests — a session API, or keep-alive model residency — rather than treating every request as independent.
grammar_needs_prompt_injection
class-attribute
instance-attribute
¶
grammar_needs_prompt_injection: bool = False
Whether grammar mode also requires the schema in the prompt.
True for engines that compile the schema to a decoding grammar without conditioning
the model on it (llama.cpp, and Ollama's format): the grammar guarantees
well-formed JSON but not meaningful JSON unless the model was told the shape.
ignored_parameters
class-attribute
instance-attribute
¶
ignored_parameters: tuple[str, ...] = ()
Request parameters this provider accepts and silently discards.
Distinct from "rejects with an error" and from "supported": a silently-ignored
parameter looks like success while doing nothing, so the core reports it as a
ParameterDropped event instead of letting it pass
unnoticed.
derived_from
class-attribute
instance-attribute
¶
derived_from: str | None = None
The engine this descriptor is an instance of, when it is one.
An application that configures two Azure tenants or two OpenAI-compatible endpoints
gives each instance its own id (work-azure, ollama-local); each becomes a
descriptor derived from the underlying engine's, differing only in identity. None
means this descriptor is an engine, which is the ordinary case.
anyinfer.ProviderSetupSpec
dataclass
¶
ProviderSetupSpec(
fields: tuple[SetupField, ...] = (),
model_selection: Literal[
"discover-or-manual", "manual-only"
] = "discover-or-manual",
host_shorthand: HostShorthand | None = None,
any_of: tuple[tuple[str, ...], ...] = (),
requirement_note: str = "",
)
Everything a config UI needs to configure a provider without knowing which it is.
fields
class-attribute
instance-attribute
¶
fields: tuple[SetupField, ...] = ()
The provider's configurable fields, in the order a UI should present them.
model_selection
class-attribute
instance-attribute
¶
model_selection: Literal[
"discover-or-manual", "manual-only"
] = "discover-or-manual"
Whether a UI may offer models discovered from the endpoint, or must let the user type a model id because the provider cannot enumerate what it serves.
host_shorthand
class-attribute
instance-attribute
¶
host_shorthand: HostShorthand | None = None
Expansion rule applied when a bare hostname is entered as the base URL, or None
when only full URLs make sense for this provider.
any_of
class-attribute
instance-attribute
¶
any_of: tuple[tuple[str, ...], ...] = ()
Groups of field keys of which at least one must be supplied.
Some providers accept a choice of credential rather than a fixed one — Anthropic takes
either an API key or a claude.ai OAuth token. That is a constraint over a group, so
no per-field required flag can express it: marking both required demands both, and
marking neither required lets an unconfigured instance save cleanly. Each inner tuple
names the keys in one such group.
requirement_note
class-attribute
instance-attribute
¶
requirement_note: str = ''
One line explaining the spec's requirements, shown beneath the fields.
Carried here rather than assembled by the UI because only the provider knows why its
any_of groups exist; a generated sentence would say what is required without ever
saying what the alternatives mean.
essential_fields
property
¶
essential_fields: tuple[SetupField, ...]
The fields a UI should put in front of the user, in declared order.
Everything the provider cannot supply a sensible answer for: credentials, and the endpoints and identifiers that vary per account. This is the short list an application prompts for.
advanced_fields
property
¶
advanced_fields: tuple[SetupField, ...]
The fields that already have a standard value, in declared order.
Offer them — a base URL override is what makes a proxy or a mirror usable — but offer them folded away, since changing one is the rare case rather than the setup path.
__post_init__ ¶
__post_init__() -> None
Reject a spec that hides a field a user cannot skip.
Marking a field both mandatory and advanced asks a UI to do two contradictory
things, and the resolution it usually picks — honor the disclosure — produces the
one failure mode worth designing against: a save that refuses, naming a field that
is not on screen. Caught here, at import time, so it is a provider-authoring error
rather than a user's dead end.
unsatisfied_groups ¶
unsatisfied_groups(
values: Mapping[str, str],
) -> tuple[tuple[str, ...], ...]
Return the any_of groups no value satisfies.
Empty when every group has at least one non-blank value, which is the case a UI needs to allow a save.
label_for ¶
label_for(key: str) -> str
The declared label for a field key, falling back to the key itself.
anyinfer.SetupField
dataclass
¶
SetupField(
key: str,
label: str,
kind: SetupFieldKind,
required: bool = False,
help_text: str = "",
placeholder: str = "",
advanced: bool = False,
default_value: str = "",
)
One configurable field a provider needs, described declaratively.
key
instance-attribute
¶
key: str
Machine name the entered value is saved under in provider settings — a well-known
key such as api_key or api_version, or a provider-specific options entry.
kind
instance-attribute
¶
kind: SetupFieldKind
Semantic role of the field, which tells a UI how to render and validate it.
required
class-attribute
instance-attribute
¶
required: bool = False
Whether saving needs a non-blank value for this field on its own. Either-or
alternatives between fields are expressed via ProviderSetupSpec.any_of instead.
help_text
class-attribute
instance-attribute
¶
help_text: str = ''
Explanatory sentence shown alongside the field; empty when the label suffices.
placeholder
class-attribute
instance-attribute
¶
placeholder: str = ''
Example value a UI shows in the empty editor.
Declared per field because the right example is provider knowledge: the environment
variable an Anthropic key conventionally lives in is not the one an OpenAI key does,
and a UI that guesses picks one provider's convention and is wrong for all the others.
Empty means the UI falls back to whatever generic hint suits the kind.
advanced
class-attribute
instance-attribute
¶
advanced: bool = False
Whether this field has a standard value that is right for almost every user.
The split this expresses is prominence, not optionality. required already says
"saving fails without a value"; plenty of fields are neither required nor worth
showing — an Ollama base URL, an Anthropic API version, a Bedrock signing profile.
Presented as equals they read as five questions where there is really one, and every
consuming application then has to rediscover which is which from prose help text.
So the provider says it here: advanced fields are the ones a UI may fold behind a
disclosure, leaving the fields a user genuinely has to answer in front of them. A
required field is never advanced — hiding something that blocks saving is exactly the
trap this exists to avoid — and ProviderSetupSpec rejects that combination.
default_value
class-attribute
instance-attribute
¶
default_value: str = ''
The value the provider falls back to when this field is left blank.
What a UI shows as "standard: …" beside a hidden field, so folding one away never hides what it will do. Empty when the field has no default — a credential has none, and neither does an endpoint the user must supply.
A UI should render this rather than pre-filling the editor with it: a saved copy of today's default is a value frozen at the moment someone opened a dialog, and it keeps overriding the real default long after that default has moved on.
anyinfer.HostShorthand
dataclass
¶
HostShorthand(scheme: str, default_port: int)
anyinfer.default_registry
module-attribute
¶
default_registry = ProviderRegistry()
The process-wide provider registry used when a client is given no other.
Deliberately not named registry: a module-level name equal to the module's own name
shadows the module itself on the package, which breaks introspection, documentation
generation, and patching in tests.
anyinfer.providers.builtin_descriptors ¶
builtin_descriptors() -> Iterator[ProviderDescriptor]
Yield every built-in provider descriptor.
A module that fails to import — typically because its optional extra is absent — is skipped rather than breaking discovery for the others. The resulting "unknown provider" error, raised only if that provider is actually requested, carries the install hint.
Catalog¶
Two shapes over one body of data: the alias ladder ("just give me a good default") and the
logical model table ("let me browse and pick"). Catalog.with_alias_target bridges them.
See the model catalog.
anyinfer.Catalog
dataclass
¶
Catalog(
aliases: Mapping[str, AliasEntry] = dict(),
artifacts: Mapping[str, GgufArtifact] = dict(),
models: Mapping[str, ModelEntry] = dict(),
default_alias: str = "medium",
format_version: int = FORMAT_VERSION,
)
A parsed alias catalog.
Attributes:
| Name | Type | Description |
|---|---|---|
aliases |
Mapping[str, AliasEntry]
|
The tier ladder, keyed by lowercase alias name. |
artifacts |
Mapping[str, GgufArtifact]
|
Pinned GGUF artifacts, keyed by artifact id. Includes artifacts derived from the model table's GGUF variants as well as explicitly declared ones. |
models |
Mapping[str, ModelEntry]
|
The logical model table, keyed by model id. |
default_alias |
str
|
The alias resolution falls back to when a caller does not pick one. |
format_version |
int
|
Schema version of the parsed document. |
alias ¶
alias(name: str) -> AliasEntry
targets_for_alias ¶
targets_for_alias(name: str) -> Mapping[str, TargetEntry]
Every provider realization of an alias.
artifact ¶
artifact(artifact_id: str) -> GgufArtifact
model ¶
model(model_id: str) -> ModelEntry
models_for ¶
models_for(
provider_id: str | None = None,
*,
best_at: str | None = None,
) -> tuple[ModelEntry, ...]
Logical models filtered by serving channel and category, id-ordered.
with_alias_target ¶
with_alias_target(
alias: str, provider_id: str, model_id: str
) -> Catalog
Point one alias's provider target at a catalog model.
This is the bridge between browsing and the tier ladder: an app implements "use my
catalog pick as medium" in one call, and the result resolves through the ordinary
alias machinery with no resolver changes.
Raises:
| Type | Description |
|---|---|
ConfigError
|
If the alias or model is unknown, or the model has no artifact for the named provider. |
overlay ¶
overlay(other: Catalog) -> Catalog
Merge other on top of this catalog, entry by entry.
Application entries win over bundled ones at the alias, artifact, and model level — an overridden alias replaces the bundled one wholesale rather than merging its target map, so an app can remove a provider from a tier it does not want used. The same wholesale rule applies per model id.
from_mapping
classmethod
¶
from_mapping(data: Mapping[str, Any]) -> Catalog
Parse a catalog document.
Raises:
| Type | Description |
|---|---|
ConfigError
|
On an unsupported format version or malformed entries. |
from_files
classmethod
¶
from_files(*paths: Path) -> Catalog
Load several catalog documents and overlay them left to right.
The bundled catalog is split this way on purpose: default.json stays a small,
human-editable alias policy file while models.json is machine-maintained data
with its own refresh cadence.
Raises:
| Type | Description |
|---|---|
ConfigError
|
If any file is missing, malformed, or empty of catalogs. |
anyinfer.ModelEntry
dataclass
¶
ModelEntry(
id: str,
family: str = "",
display_name: str = "",
parameter_size: str | None = None,
quantization: str | None = None,
context_window: int | None = None,
license: str = "",
best_at: tuple[str, ...] = (),
est_file_bytes: int | None = None,
est_ram_bytes: int | None = None,
est_vram_bytes: int | None = None,
last_verified: str = "",
source: str = "",
variants: tuple[ModelVariant, ...] = (),
ollama: OllamaChannel | None = None,
description: str = "",
)
One logical model in the catalog.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
str
|
Stable catalog id ( |
family |
str
|
Model family, for grouping in a UI. |
display_name |
str
|
Human-facing name. |
parameter_size |
str | None
|
Parameter class ( |
quantization |
str | None
|
The default quantization the headline estimates assume. |
context_window |
int | None
|
Native context length, when known. |
license |
str
|
License id; gated against the download allowlist. |
best_at |
tuple[str, ...]
|
Categories from |
est_file_bytes |
int | None
|
Download size of the default variant. |
est_ram_bytes |
int | None
|
Memory needed on the CPU-only path. |
est_vram_bytes |
int | None
|
Memory needed when fully offloaded. |
last_verified |
str
|
ISO date the entry was actually checked against upstream. |
source |
str
|
URL of the upstream repository or registry page. |
variants |
tuple[ModelVariant, ...]
|
The quantization ladder, best quality first is not assumed — sort by
|
ollama |
OllamaChannel | None
|
The Ollama channel, when the model is published there. |
description |
str
|
Free text for display. |
gguf_artifact_id
property
¶
gguf_artifact_id: str | None
The artifact id of this model's headline GGUF variant, when there is one.
"Headline" means the quantization the entry's own memory estimates describe — the rung a browsing user is being shown — not the highest rung in the repository. Picking the largest would quietly hand someone a Q8_0 download after they read a Q4_K_M size.
variants_for ¶
variants_for(
engine: str | None = None,
) -> tuple[ModelVariant, ...]
Variants for one engine, best quality first.
variant ¶
variant(variant_id: str) -> ModelVariant
matches_best_at ¶
matches_best_at(category: str | None) -> bool
Whether this entry carries a category tag (case-insensitively).
anyinfer.ModelVariant
dataclass
¶
ModelVariant(
id: str,
engine: str = "llama.cpp",
kind: str = "gguf",
quantization: str = "",
quality_rank: int = 0,
est_file_bytes: int | None = None,
est_ram_bytes: int | None = None,
est_vram_bytes: int | None = None,
min_compute_capability: str | None = None,
source: SourceRef = SourceRef(),
artifact_id: str | None = None,
)
One (model, quantization, engine) rung of a model's ladder.
A variant carries its own source reference, because a quantized vLLM variant is usually a different repository while a quantized GGUF variant is usually a different file in the same repository. One schema covers both only because the reference is per variant rather than per model.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
str
|
Stable variant id, unique within the catalog. |
engine |
str
|
|
kind |
str
|
|
quantization |
str
|
The quantization this rung ships ( |
quality_rank |
int
|
Ladder position; higher is better quality. |
est_file_bytes |
int | None
|
On-disk size of the weights. |
est_ram_bytes |
int | None
|
Memory needed on the CPU-only path. |
est_vram_bytes |
int | None
|
Memory needed when fully offloaded. |
min_compute_capability |
str | None
|
NVIDIA compute capability this variant's kernels need,
as a string ( |
source |
SourceRef
|
Where the bytes come from. |
artifact_id |
str | None
|
For GGUF variants, the id under which the derived |
anyinfer.OllamaChannel
dataclass
¶
OllamaChannel(tag: str, digest: str | None = None)
How a logical model is packaged in the Ollama registry.
We never download these — the daemon owns its own blob store. The tag is what we
recommend, and digest is what drift checking compares against, because registry tags
are mutable by design.
anyinfer.BEST_AT
module-attribute
¶
BEST_AT: frozenset[str] = frozenset(
{
"agentic",
"code-completion",
"coding",
"drafting",
"embeddings",
"general-chat",
"long-context",
"low-resource",
"math",
"multilingual",
"rag",
"reasoning",
"tool-use",
"vision",
}
)
The closed vocabulary of "best at" categories.
Closed on purpose: a free-text tag set drifts into synonyms nobody can filter on. Adding a category is a deliberate edit here, and the catalog validator enforces the set.
anyinfer.load_default_catalog ¶
load_default_catalog() -> Catalog
Load the catalog bundled with this AnyInfer build.
Two documents, overlaid: default.json carries the hand-edited alias policy, and
models.json carries the machine-maintained logical model table with its own refresh
cadence — the same split the bundled pricing table uses.
Browsing the local catalog¶
What Client.local_catalog() returns: every catalog model annotated with whether it fits,
and why.
anyinfer.CatalogView
dataclass
¶
CatalogView(
entries: tuple[CatalogEntryFit, ...] = (),
hardware: HardwareProfile | None = None,
hardware_source: HardwareSource = "unavailable",
backend: Backend | None = None,
notes: tuple[str, ...] = (),
)
A filtered, fit-annotated view of the local model catalog.
Attributes:
| Name | Type | Description |
|---|---|---|
entries |
tuple[CatalogEntryFit, ...]
|
Models, best-fit-first. |
hardware |
HardwareProfile | None
|
The profile fits were judged against, when there was one. |
hardware_source |
HardwareSource
|
|
backend |
Backend | None
|
The llama.cpp runtime variant that would actually drive these, when one is installed. |
notes |
tuple[str, ...]
|
View-level remarks, such as the runtime a machine should install. |
anyinfer.CatalogEntryFit
dataclass
¶
CatalogEntryFit(
model: ModelEntry,
fit: ModelFit,
channels: tuple[str, ...] = (),
)
One catalog model, judged against a machine.
Attributes:
| Name | Type | Description |
|---|---|---|
model |
ModelEntry
|
The catalog entry. |
fit |
ModelFit
|
How it fits, with reasons. |
channels |
tuple[str, ...]
|
Provider ids that can serve it. |
Credentials¶
anyinfer.CredentialResolver ¶
Bases: Protocol
Resolves credential references of one scheme.
resolve ¶
resolve(reference: str) -> str
Resolve reference to a secret.
Raises:
| Type | Description |
|---|---|
CredentialError
|
If the reference is recognized but cannot be resolved. |
anyinfer.ResolverChain ¶
ResolverChain(resolvers: list[CredentialResolver])
Tries each resolver in order, returning the first match's result.
The chain — not the individual resolvers — is responsible for registering resolved secrets for redaction, so a third-party resolver cannot forget to.
add ¶
add(
resolver: CredentialResolver, *, first: bool = True
) -> None
Register an additional resolver, by default ahead of the built-ins.
resolve ¶
resolve(reference: str | None) -> str | None
Resolve a credential reference.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
reference
|
str | None
|
The reference string, or |
required |
Returns:
| Type | Description |
|---|---|
str | None
|
The resolved secret, or |
Raises:
| Type | Description |
|---|---|
CredentialError
|
If no resolver handles the reference, or resolution fails. |
anyinfer.default_resolver ¶
default_resolver() -> ResolverChain
Build the standard resolver chain: keyring, env, then literal.
Literal is last because it accepts anything; the scheme-specific resolvers must get first refusal.