Add Your Own Provider¶
A provider AnyInfer does not ship is not a fork: a provider is a small installable
package, and once it is installed, yourprovider:model targets resolve everywhere: the
Python API, the command line, the sidecar, and any config UI built on the setup spec.
Do You Need an Adapter at All?¶
If your endpoint speaks /chat/completions and differs only by URL, authentication
spelling, and a few quirks, you do not. Point the built-in
OpenAI-compatible provider at it:
client = ai.Client(
[
ai.ProviderSettings.of(
"openai-compat",
base_url="https://llm.internal.example/v1",
api_key="env://INTERNAL_LLM_KEY",
)
]
)
Write an adapter when there is real protocol translation to do: a different request shape, a different streaming framing, or discovery that reports something the OpenAI listing cannot express.
Scaffold It¶
anyinfer conform acme --scaffold ./acme-anyinfer
That writes a package that already imports, registers, and resolves:
acme-anyinfer/
acme_anyinfer/__init__.py the descriptor and its entry point
acme_anyinfer/adapter.py the four methods to fill in
contracts/acme.md the protocol snapshot to record what you depend on
tests/test_conformance.py certification, ready to point at your endpoint
pyproject.toml the entry point and your capability declarations
README.md
Write the Four Methods¶
An adapter exposes exactly list_models, health, generate, and aclose. generate
yields normalized events; everything else is the core's job:
Retry, fallback, health gating, schema validation and repair, first-token timing, usage normalization, cost, telemetry, and redaction live in AnyInfer's core. If you find yourself adding control flow to an adapter, it belongs in the core instead, and it is probably already there.
That constraint is what makes an adapter small. It is also what makes your provider behave identically to every built-in one without you implementing any of it. The adapter walkthrough covers each method's contract in detail, and what your models support flows through the same capabilities system the built-ins use; declare what you know and leave the rest unknown.
Declare What Your Provider Needs¶
The descriptor's ProviderSetupSpec is what allows a configuration UI to render your
provider without knowing which provider it is: which fields to prompt for, which have
sensible defaults, which are credentials, and which environment variable each
conventionally comes from. Fill it in and every AnyInfer-based application can configure your provider.
Declare SetupField.env_var on any field with a conventional variable: the bare name,
"ACME_API_KEY", not the env:// reference form. It is the machine-readable half of what
placeholder says in prose, and it is what allows anyinfer init to find your provider
already usable on somebody's machine, and a config UI to say "we found this in your
environment", without either of them parsing an example sentence.
Certify It¶
anyinfer conform acme --model acme-large
acme
✅ list_models
✅ health
✅ non_streaming
✅ streaming
❌ usage usage must report output tokens
➖ reasoning declared unsupported
This is the same conformance suite the built-in adapters
run, and their results are published as the
conformance matrix. Cases your provider genuinely
cannot support are declared in your pyproject.toml, where they show as ➖ rather than as
failures:
[tool.anyinfer.conformance]
reasoning = false # no reasoning channel on this API
retry_after = false # rate limiting cannot be provoked on demand
Declaring them in the project file rather than on the command line keeps the claim reviewable: "what we do not support" is checked in, not typed once on a bad day.
The command exits non-zero on any failure, so your own CI can gate on it. Add
--markdown-row for a pasteable conformance-matrix row, or --json for a machine-readable
report.
Record What You Depend On¶
contracts/acme.md is the snapshot of exactly which upstream details your adapter relies
on: endpoints, auth headers, version pins, fields sent and read, streaming framing, and
error-mapping inputs. It exists so that when the provider changes something, you can tell —
by comparing the snapshot against their current documentation rather than by waiting for a
production failure. The in-tree adapters keep the same snapshots under contracts/,
written by the procedure in
contracts/NEW-PROVIDER.md.
Install and Use It¶
pip install -e ./acme-anyinfer
client = ai.Client([ai.ProviderSettings.of("acme", api_key="env://ACME_API_KEY")])
client.generate("hello", target="acme:acme-large")
Nothing in the application changed. If your package fails to load (a bad import, an id
that collides with a built-in), anyinfer doctor says so by name, rather than leaving your
provider mysteriously absent.
Key Takeaways
- An endpoint that speaks
/chat/completionsneeds no adapter;openai-compatwith abase_urlcovers it. - An adapter is four methods that translate protocol. Retry, validation, timing, cost, telemetry, and redaction stay in the core, which is why your provider behaves like every built-in one.
- Conformance cases you cannot support are declared in
pyproject.toml, so the claim is checked in and reviewable, and CI can gate on the suite's exit code. - The contract snapshot records the upstream details you depend on, so provider drift is detectable by comparison instead of by production failure.