Skip to content

Configuration API

The versioned JSON loader and what it produces: load_config and loads_config parse and validate a file into an AnyInferConfig, and dump_config and dumps_config write the same format back. The prose reference for the file format lives in Shared configuration.

anyinfer.CONFIG_FORMAT_VERSION module-attribute

CONFIG_FORMAT_VERSION = 1

The configuration format version written and understood by this release.

anyinfer.MAX_CONFIG_BYTES module-attribute

MAX_CONFIG_BYTES = 1024 * 1024

Maximum accepted configuration size.

anyinfer.AnyInferConfig dataclass

AnyInferConfig(
    providers: tuple[ProviderSettings, ...] = (),
    route: Route | None = None,
    format_version: int = CONFIG_FORMAT_VERSION,
    context: ContextTuning = DEFAULT_TUNING,
    history: HistoryPolicy | None = None,
    cache: CachePolicy | None = None,
    repair: Repair | None = None,
    observers: tuple[ObserverSpec, ...] = (),
    arena: ArenaPolicy | None = None,
    arenas: Mapping[str, ArenaPolicy] = dict(),
    mcp: tuple[MCPServer, ...] = (),
    operation_routes: Mapping[str, Route] = dict(),
)

Validated configuration shared by every AnyInfer integration surface.

Pass providers and route directly to Client or AsyncClient. The same object is used by the command-line runner and OpenAI-compatible sidecar.

Attributes:

Name Type Description
providers tuple[ProviderSettings, ...]

Configured provider instances, in declaration order.

route Route | None

Default fallback route, when one was configured.

format_version int

Parsed file-format version.

context ContextTuning

Advanced context-reduction settings from the optional context block. Pass to anyinfer.context.select as tuning=. Defaults reproduce the library's plain behaviour, so a file without the block behaves as before.

history HistoryPolicy | None

Conversation-compaction policy from the optional history block, or None when the file does not ask for one. Pass to Client or AsyncClient as history=; every frontend built on that client then behaves identically.

cache CachePolicy | None

Prompt-cache placement from the optional cache block, or None when the file does not ask for one. Pass to Client or AsyncClient as cache=. Absent means no placement — caching changes what a provider bills, so it is never turned on by a file that did not name it.

mcp tuple[MCPServer, ...]

Model Context Protocol servers described by the optional mcp block. These are inert descriptions: loading a file never spawns a process or opens a socket. Pass them to anyinfer.mcp.MCPToolset.connect when tools are wanted.

observers tuple[ObserverSpec, ...]

Telemetry sinks described by the optional observers block, as ObserverSpecs. Inert: loading a file never opens a log. Call build_observers to construct them, then pass the result to Client or AsyncClient as observers=. This is how a sidecar deployment gets an access log at all — it has no constructor for a caller to reach, so a sink it cannot name is a sink it cannot have.

repair Repair | None

Bounded schema-repair budget from the optional repair block, or None when the file does not ask for one. Pass to Client or AsyncClient as repair=. Without it, structured output validates and fails rather than repairing — which is why a sidecar deployment could not reach the repair loop at all before this block existed.

operation_routes Mapping[str, Route]

Per-operation default routes from the optional operation_routes block, keyed "embedding"/"rerank". An embedding route can never be selected for generation or vice versa — generation's default stays default_route. Pass to Client or AsyncClient as operation_routes=.

anyinfer.load_config

load_config(
    path: str | Path,
    *,
    registry: ProviderRegistry | None = None,
) -> AnyInferConfig

Read and validate an AnyInfer JSON configuration file.

Parameters:

Name Type Description Default
path str | Path

File to read.

required
registry ProviderRegistry | None

Provider registry used to validate setup-field names. Defaults to the process-wide registry, including installed third-party providers.

None

Raises:

Type Description
ConfigError

If the file cannot be read or does not match the shared format.

anyinfer.loads_config

loads_config(
    text: str,
    *,
    source: str = "<string>",
    registry: ProviderRegistry | None = None,
) -> AnyInferConfig

Parse and validate AnyInfer configuration from a JSON string.

Parameters:

Name Type Description Default
text str

UTF-8 JSON text.

required
source str

Human-readable source name included in validation errors.

'<string>'
registry ProviderRegistry | None

Provider registry used to validate setup-field names.

None

Raises:

Type Description
ConfigError

If the text is too large, invalid JSON, or has invalid fields.

anyinfer.dumps_config

dumps_config(
    config: AnyInferConfig, *, comments: bool = False
) -> str

Render a configuration as the JSON text loads_config accepts.

The other half of the shared format. Three frontends could read this file and none could write it, which left every example of the format as prose and left anyinfer init with no way to produce one but string templating.

Round-tripping is the contract: loads_config(dumps_config(c)) == c for every configuration the loader accepts. What that costs is verbosity in one place — a provider instance carrying an opt-in policy emits that policy even when every field in it is standard, because an omitted block and a default-valued block mean different things to the loader and only one of them is what the caller had.

Credential values are written exactly as configured. References such as env:// and credential:// remain references, while a literal credential remains literal. This function never resolves a reference, but callers must still review configurations that they constructed with literal secrets before writing or committing them. Discovery and anyinfer init produce references so their generated files contain no key material.

Parameters:

Name Type Description Default
config AnyInferConfig

The configuration to render.

required
comments bool

Write a leading COMMENT_KEY note explaining what the file is. Still JSON, and still accepted by the loader.

False

Returns:

Type Description
str

UTF-8 JSON text, two-space indented, ending in a newline.

Raises:

Type Description
ConfigError

If a provider's options or headers hold a value JSON cannot represent. Settings built in Python may carry anything; a file cannot.

anyinfer.dump_config

dump_config(
    config: AnyInferConfig,
    path: str | Path,
    *,
    force: bool = False,
) -> None

Write a configuration to a file, refusing to replace one that exists.

Destructive-by-default is not acceptable for a file a user may have hand-tuned, and a configuration file is exactly that kind of file. Overwriting is available and has to be asked for.

Parameters:

Name Type Description Default
config AnyInferConfig

The configuration to write.

required
path str | Path

Where to write it. Parent directories must already exist.

required
force bool

Replace an existing file instead of refusing.

False

Raises:

Type Description
ConfigError

If the path exists and force is false, if the configuration cannot be rendered, or if the file cannot be written.

anyinfer.config.COMMENT_KEY module-attribute

COMMENT_KEY = '_comment'

Root key carrying a human-readable note, accepted and ignored by the loader.

The format is JSON, not JSONC, so a generated file cannot explain itself in // lines without becoming something this loader would reject. A string under this key is the version of that idea the format can actually carry: dumps_config(..., comments=True) writes one, and reading it back changes nothing.

Telemetry Sinks From Configuration

An observers block names sinks; it does not build them. Loading a configuration file has no side effects, so nothing opens a log file until a frontend decides to observe. See the observers block for the file format and observability for what to do with them.

anyinfer.config.ObserverSpec dataclass

ObserverSpec(
    name: str, options: Mapping[str, Any] = dict()
)

A telemetry sink named in configuration, not yet built.

Inert by design, exactly as MCPServer is: reading a configuration file must not open a log file any more than it should spawn a subprocess. build_observers turns these into live sinks when a frontend actually wants them.

Attributes:

Name Type Description
name str

"logging", "jsonl", or a name published under the anyinfer.observers entry-point group.

options Mapping[str, Any]

Keyword arguments for the sink's constructor.

anyinfer.config.build_observers

build_observers(
    specs: Sequence[ObserverSpec],
) -> tuple[Any, ...]

Construct live telemetry sinks from configured specs.

Separate from loading so that reading a file has no side effects — a jsonl sink opens and holds a file, and that should happen when a frontend decides to observe, not when a config file is parsed.

Parameters:

Name Type Description Default
specs Sequence[ObserverSpec]

Usually AnyInferConfig.observers.

required

Returns:

Type Description
tuple[Any, ...]

The constructed sinks, ready to pass as observers=.

Raises:

Type Description
ConfigError

A sink rejected its options, could not open its file, or its plugin failed to build.

anyinfer.config.BUILTIN_OBSERVERS module-attribute

BUILTIN_OBSERVERS = ('logging', 'jsonl')

Sink names that need no plugin installed.

Plugin Entry Points

Two entry-point groups exist so a configuration file can name an extension that no constructor call could reach — which is the sidecar's only route to one. Discovery never raises: a broken third-party package is recorded as a PluginLoadIssue and skipped, the same discipline anyinfer.registry.ProviderRegistry applies to provider plugins.

Credential-store plugins carry a trust delta worth reading before publishing one; see custom schemes the sidecar can reach.

anyinfer.plugins.OBSERVER_GROUP module-attribute

OBSERVER_GROUP = 'anyinfer.observers'

Entry-point group for config-nameable telemetry sinks.

anyinfer.plugins.CREDENTIAL_STORE_GROUP module-attribute

CREDENTIAL_STORE_GROUP = 'anyinfer.credential_stores'

Entry-point group for credential resolvers adding a reference scheme.

anyinfer.plugins.load_observers

load_observers() -> tuple[
    dict[str, Any], list[PluginLoadIssue]
]

Discover telemetry sinks published under OBSERVER_GROUP.

Returns:

Type Description
dict[str, Any]

A (factories, issues) pair. Each factory is either an Observer instance or

list[PluginLoadIssue]

a callable that builds one from keyword options; the caller decides which by

tuple[dict[str, Any], list[PluginLoadIssue]]

whether it has options to pass.

Note

Nothing is instantiated here. A sink that opens a file should not do so merely because a package that provides it happens to be installed.

anyinfer.plugins.load_credential_stores

load_credential_stores() -> tuple[
    dict[str, Any], list[PluginLoadIssue]
]

Discover credential resolvers published under CREDENTIAL_STORE_GROUP.

Discovered resolvers are placed ahead of the built-ins in default_resolver, so first refusal is exactly what makes a custom scheme work — and exactly what would let an installed distribution interpose on the built-in ones. Any resolver claiming to handle env:// or credential:// is therefore dropped with a scheme-reserved issue, mirroring the id/alias-collision refusal the anyinfer.providers group has enforced since it shipped. A plugin is trusted to add a scheme, never to redefine one.

That guard bounds interposition, not code execution: an installed package already runs arbitrary code at interpreter startup by other means, and this group's entry points are imported and instantiated like any other. What it removes is the specific case where a compromised transitive dependency silently becomes the resolver for every credential in the process without the operator having named it anywhere.

Returns:

Type Description
dict[str, Any]

A (resolvers, issues) pair. Entries that are callable are called with no

list[PluginLoadIssue]

arguments to build the resolver; anything failing the CredentialResolver

tuple[dict[str, Any], list[PluginLoadIssue]]

protocol, or claiming a built-in scheme, is dropped with an issue rather than

tuple[dict[str, Any], list[PluginLoadIssue]]

reaching the chain.