Skip to content

feat: add first-class OpenAI-compatible custom endpoint path - #1198

Merged
tbille merged 5 commits into
mainfrom
feat/openai-compatible-custom-path
Aug 3, 2026
Merged

tbille merged 5 commits into
mainfrom
feat/openai-compatible-custom-path

Conversation

@njbrake

@njbrake njbrake commented Jul 23, 2026 •

Copy link
Copy Markdown
Contributor

Description

Adds a first-class, supported way to point any-llm at any OpenAI-compatible endpoint that does not have a dedicated provider, via a new factory:

from any_llm import AnyLLM

llm = AnyLLM.create_openai_compatible(
    name="mygateway",
    api_base="https://mygateway.example/v1",
    api_key="your-key",  # optional for keyless local servers
)
resp = llm.completion(model="some-model", messages=[{"role": "user", "content": "Hello!"}])

Previously the only way to reach an unlisted gateway was AnyLLM.create("openai", api_base=...), which misreports the provider identity as openai, and LLMProvider.from_string rejects any name not in the enum, so users could not represent their gateway under its own name. The returned provider reports the caller's chosen name and is usable exactly like any other provider instance.

Design notes:

  • Instance-based, not enum routed. The custom endpoint is represented by a provider instance you hold and call methods on. The provider enum stays the identity registry; named provider:model routing for community gateways is the follow-up registry work in the parent issue.
  • Keyless tolerated (mirrors vllm/llamafile): api_key falls back to OPENAI_COMPATIBLE_API_KEY, then to a placeholder, so local servers are never blocked by MissingApiKeyError.
  • Capability flags follow the OpenAI-compatible defaults from BaseOpenAIProvider; calling a capability the endpoint does not implement surfaces the endpoint's own error. Explicit per-provider flags are the registry's job in a later PR.

Implementation:

  • src/any_llm/providers/openai/custom.py: OpenAICompatibleProvider(BaseOpenAIProvider)
  • src/any_llm/any_llm.py: AnyLLM.create_openai_compatible(name, api_base, api_key=None, **kwargs)
  • tests/unit/providers/test_openai_compatible_provider.py: 11 tests (identity, base-URL binding, empty-api_base guard, keyless placeholder, explicit/env key precedence, capability defaults, kwargs forwarding, enum exclusion)
  • docs/quickstart.md: "Custom OpenAI-compatible Endpoints" section

Testing:

  • pytest tests/unit/providers/test_openai_compatible_provider.py: 11 passed
  • pre-commit run --files <changed>: ruff, ruff-format, mypy strict, codespell all pass

PR Type

  • 🆕 New Feature

Relevant issues

Part of #1197 (first-class OpenAI-compatible custom path). Deliberately scoped to that one step; does not close the issue.

Checklist

  • I understand the code I am submitting.
  • I have added unit tests that prove my fix/feature works
  • I have run this code locally and verified it fixes the issue.
  • New and existing tests pass locally
  • Documentation was updated where necessary
  • I have read and followed the contribution guidelines
  • AI Usage:
    • No AI was used.
    • AI was used for drafting/refactoring.
    • This is fully AI-generated.

AI Usage Information

  • AI Model used: Claude Opus 4.8

  • AI Developer Tool used: Claude Code

  • Any other info you'd like to share: Implemented by Claude via back-and-forth with @njbrake, who directed the scope (this is PR 1 of the Provider policy: two tiers, a config registry, and a first-class OpenAI-compatible path #1197 provider-policy rollout) and the design decisions (instance-based custom path, zero-knob registry to follow). Verified locally with unit tests and the pre-commit gate.

  • I am an AI Agent filling out this form (check box if true)

Summary by CodeRabbit

  • New Features

    • Added support for connecting to arbitrary OpenAI-compatible endpoints via AnyLLM.create_openai_compatible.
    • You can now supply a custom gateway name, base URL, and optional API key.
    • Keyless endpoints are supported using a safe placeholder when no key is provided.
    • Capability flags follow OpenAI-compatible defaults; unsupported calls surface appropriate errors.
  • Documentation

    • Updated the quickstart with a new “Custom OpenAI-compatible Endpoints” section and examples.
  • Tests

    • Expanded unit, documentation, and integration coverage for custom OpenAI-compatible flows.

@njbrake
njbrake had a problem deploying to integration-tests July 23, 2026 19:26 — with GitHub Actions Failure
@coderabbitai

coderabbitai Bot commented Jul 23, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

The change adds configurable OpenAI-compatible endpoint support through a factory and provider, including keyless authentication, capability defaults, integration coverage, unit tests, and quickstart documentation.

Changes

OpenAI-compatible endpoint support

Layer / File(s) Summary
Configurable provider implementation
src/any_llm/providers/openai/custom.py
Adds provider identity, API-base configuration, API-key fallbacks, and OpenAI-compatible capability settings.
Factory integration and identity
src/any_llm/any_llm.py, tests/unit/providers/test_openai_compatible_provider.py
Adds AnyLLM.create_openai_compatible, validates names, creates per-name provider subclasses, and forwards endpoint configuration.
Validation and documentation
tests/unit/providers/test_openai_compatible_provider.py, tests/docs/test_all.py, docs/quickstart.md
Covers configuration, authentication, capabilities, client options, documentation execution, and custom endpoint usage.
OpenAI endpoint integration flow
tests/integration/test_openai_compatible.py
Tests non-streaming completion, streaming completion, and model listing against an OpenAI-compatible endpoint.

Suggested labels: 1.22.0

Suggested reviewers: tbille

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and clearly summarises the main change: adding a first-class OpenAI-compatible custom endpoint path.
Description check ✅ Passed The description follows the template well, covering purpose, PR type, related issue, checklist, tests, docs, and AI usage details.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/openai-compatible-custom-path

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 23, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
src/any_llm/any_llm.py 74.25% <100.00%> (-0.86%) ⬇️
src/any_llm/providers/openai/custom.py 100.00% <100.00%> (ø)

... and 35 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@njbrake
njbrake force-pushed the feat/openai-compatible-custom-path branch from bdb9c19 to d985692 Compare July 23, 2026 19:29
@njbrake
njbrake had a problem deploying to integration-tests July 23, 2026 19:29 — with GitHub Actions Failure

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/any_llm/any_llm.py`:
- Line 207: The public factory create_openai_compatible in
src/any_llm/any_llm.py:207 and the provider constructor in
src/any_llm/providers/openai/custom.py:39 both trigger ANN401 through open-ended
kwargs annotations. Update both sites to use the repository’s accepted typed
kwargs alias, or add narrowly scoped ANN401 suppressions with rationale that
these client options are intentionally open-ended.

In `@src/any_llm/providers/openai/custom.py`:
- Around line 20-23: Clarify the capability-error wording in
src/any_llm/providers/openai/custom.py lines 20-23: disabled capabilities are
rejected locally by AnyLLM, while enabled OpenAI-compatible defaults are passed
through to the endpoint, where endpoint errors may occur. Apply the same
distinction in docs/quickstart.md line 119; update both sites without changing
the capability flags or provider identity behavior.

In `@tests/unit/providers/test_openai_compatible_provider.py`:
- Around line 66-70: Update test_client_kwargs_are_forwarded to construct the
provider through AnyLLM.create_openai_compatible, passing timeout as a keyword
argument and retaining the timeout assertion. Ensure the test validates the
public factory’s kwargs-forwarding contract rather than directly instantiating
OpenAICompatibleProvider.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: deaa0461-20ab-4900-9e7e-11f1aa09cdc2

📥 Commits

Reviewing files that changed from the base of the PR and between a5a86c0 and bdb9c19.

📒 Files selected for processing (4)
  • docs/quickstart.md
  • src/any_llm/any_llm.py
  • src/any_llm/providers/openai/custom.py
  • tests/unit/providers/test_openai_compatible_provider.py

Comment thread src/any_llm/providers/openai/custom.py Outdated
Comment thread tests/unit/providers/test_openai_compatible_provider.py
Point any-llm at any OpenAI-compatible gateway that does not have a
dedicated provider, via AnyLLM.create_openai_compatible(name, api_base,
api_key). The returned provider reports the caller's chosen name as its
identity instead of masquerading as `openai`, and is usable exactly like
any other provider instance.

Previously the only way to reach an unlisted gateway was
AnyLLM.create("openai", api_base=...), which misreports the provider as
openai, and LLMProvider.from_string rejects any name not in the enum, so
users could not represent their gateway under its own name. This makes
"bring your own endpoint" a supported, documented feature so nobody is
blocked from using any-llm with their endpoint.

The endpoint may be keyless (local servers) or keyed (hosted gateways):
api_key falls back to the env var then to a placeholder, never raising
MissingApiKeyError. Capability flags follow the OpenAI-compatible
defaults from BaseOpenAIProvider.

Part of #1197.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@njbrake
njbrake force-pushed the feat/openai-compatible-custom-path branch from d985692 to 7ec2136 Compare July 23, 2026 19:33
@njbrake
njbrake temporarily deployed to integration-tests July 23, 2026 19:34 — with GitHub Actions Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/any_llm/any_llm.py`:
- Line 207: Update create_openai_compatible to reject name values that are empty
or contain only whitespace before creating the dynamic subclass or assigning
PROVIDER_NAME, raising the established validation error type. Add a focused
error-case test covering blank and whitespace-only names.

In `@src/any_llm/providers/openai/custom.py`:
- Around line 33-42: Update OpenAICompatibleProvider.__init__ and
ENV_API_BASE_NAME consistently: either remove the unused advertised
environment-variable constant, or allow api_base to be optional so the base
resolver can honor OPENAI_COMPATIBLE_API_BASE before enforcing a
missing-endpoint error, and add coverage for the environment-based configuration
path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c783d476-f5f8-4d84-879d-a64a366487c2

📥 Commits

Reviewing files that changed from the base of the PR and between bdb9c19 and d985692.

📒 Files selected for processing (4)
  • docs/quickstart.md
  • src/any_llm/any_llm.py
  • src/any_llm/providers/openai/custom.py
  • tests/unit/providers/test_openai_compatible_provider.py

Comment thread src/any_llm/any_llm.py
return cls._create_provider(provider, api_key=api_key, api_base=api_base, **kwargs)

@classmethod
def create_openai_compatible(cls, name: str, api_base: str, api_key: str | None = None, **kwargs: Any) -> AnyLLM:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject empty custom provider names.

An empty or whitespace-only name is accepted and copied to PROVIDER_NAME, producing blank provider metadata and error messages. Validate the identifier before creating the dynamic subclass and add an error-case test.

Proposed validation
     def create_openai_compatible(cls, name: str, api_base: str, api_key: str | None = None, **kwargs: Any) -> AnyLLM:
+        if not name.strip():
+            raise ValueError("name must not be empty")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def create_openai_compatible(cls, name: str, api_base: str, api_key: str | None = None, **kwargs: Any) -> AnyLLM:
def create_openai_compatible(cls, name: str, api_base: str, api_key: str | None = None, **kwargs: Any) -> AnyLLM:
if not name.strip():
raise ValueError("name must not be empty")
🧰 Tools
🪛 Ruff (0.15.21)

[warning] 207-207: Dynamically typed expressions (typing.Any) are disallowed in **kwargs

(ANN401)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/any_llm/any_llm.py` at line 207, Update create_openai_compatible to
reject name values that are empty or contain only whitespace before creating the
dynamic subclass or assigning PROVIDER_NAME, raising the established validation
error type. Add a focused error-case test covering blank and whitespace-only
names.

Comment thread src/any_llm/providers/openai/custom.py Outdated
Reject blank names in create_openai_compatible, drop the unused
ENV_API_BASE_NAME constant so metadata no longer advertises an env var
the constructor never consults, correct the capability-error wording
(flag-gated capabilities such as batch raise NotImplementedError
locally; forwarded calls surface the endpoint's own error), and route
the kwargs-forwarding test through the public factory.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@njbrake
njbrake temporarily deployed to integration-tests July 24, 2026 12:55 — with GitHub Actions Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/any_llm/providers/openai/custom.py (2)

37-39: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject whitespace-only API bases.

if not api_base accepts values such as " ", deferring an invalid endpoint failure to client initialisation. Use if not api_base.strip() and extend the existing error-case test accordingly.

Suggested validation
-        if not api_base:
+        if not api_base.strip():
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/any_llm/providers/openai/custom.py` around lines 37 - 39, Update the
api_base validation in OpenAICompatibleProvider to reject whitespace-only
strings by checking the stripped value, while preserving the existing
explicit-endpoint error. Extend the existing error-case test to cover a
whitespace-only api_base.

36-36: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add @override to the constructor.

OpenAICompatibleProvider.__init__ overrides AnyLLM.__init__, and overloaded methods in the provider Python code require @override. Add it immediately above the constructor definition.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/any_llm/providers/openai/custom.py` at line 36, Add the required
`@override` decorator immediately above OpenAICompatibleProvider.__init__,
preserving its existing signature and implementation.

Source: Coding guidelines

docs/quickstart.md (1)

101-110: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document the OPENAI_COMPATIBLE_API_KEY fallback.

When api_key is omitted, the provider reads OPENAI_COMPATIBLE_API_KEY before using the keyless placeholder. The section currently documents only an inline key or keyless mode, so hosted users may miss the supported environment configuration. As per the PR objectives, environment-based key selection is part of the supported contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/quickstart.md` around lines 101 - 110, Update the
AnyLLM.create_openai_compatible documentation example or surrounding text to
state that omitted api_key values use OPENAI_COMPATIBLE_API_KEY before falling
back to the keyless placeholder. Preserve the existing inline-key and keyless
usage guidance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@docs/quickstart.md`:
- Around line 101-110: Update the AnyLLM.create_openai_compatible documentation
example or surrounding text to state that omitted api_key values use
OPENAI_COMPATIBLE_API_KEY before falling back to the keyless placeholder.
Preserve the existing inline-key and keyless usage guidance.

In `@src/any_llm/providers/openai/custom.py`:
- Around line 37-39: Update the api_base validation in OpenAICompatibleProvider
to reject whitespace-only strings by checking the stripped value, while
preserving the existing explicit-endpoint error. Extend the existing error-case
test to cover a whitespace-only api_base.
- Line 36: Add the required `@override` decorator immediately above
OpenAICompatibleProvider.__init__, preserving its existing signature and
implementation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 57206973-f603-49b5-8d0e-6515feb0c93d

📥 Commits

Reviewing files that changed from the base of the PR and between 7ec2136 and 77fc0bf.

📒 Files selected for processing (4)
  • docs/quickstart.md
  • src/any_llm/any_llm.py
  • src/any_llm/providers/openai/custom.py
  • tests/unit/providers/test_openai_compatible_provider.py

… path

Reaches OpenAI's real endpoint through create_openai_compatible using the
key CI already holds, covering the community-provider verification bar
from #1197: completion, streaming, and list_models. Skips when
OPENAI_API_KEY is absent. Verified live locally: 3 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@njbrake njbrake added the run-integration-tests Put this label on a PR to trigger the integration test suite: works with forks label Jul 24, 2026
@njbrake
njbrake temporarily deployed to integration-tests July 24, 2026 13:12 — with GitHub Actions Inactive
@github-actions github-actions Bot removed the run-integration-tests Put this label on a PR to trigger the integration test suite: works with forks label Jul 24, 2026
…karound

Point users at the custom path from the three places they look when a
gateway is not listed: the generated providers page preamble, the
README install section, and a rationale note in the quickstart section
itself. The note names the concrete problems with create("openai",
api_base=...): misreported identity, OPENAI_API_KEY silently sent to
the custom endpoint, and MissingApiKeyError on keyless local servers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@njbrake
njbrake temporarily deployed to integration-tests July 24, 2026 15:15 — with GitHub Actions Inactive
Keep the quickstart section focused on how to use the custom path; the
recommendation against the openai api_base workaround stays on the
providers page and README.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@njbrake
njbrake temporarily deployed to integration-tests July 24, 2026 15:15 — with GitHub Actions Inactive
@njbrake
njbrake requested a review from tbille July 24, 2026 15:17
@njbrake

njbrake commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Note: this comment was drafted by Claude via back-and-forth with @njbrake. The reasoning and decisions are his; the prose is Claude's.

@tbille this one is ready for you. Please review and merge whenever you're comfortable with it. Heads up that #1201 (the config registry) is stacked on this branch, so merging this one first keeps that diff clean.

champ18ion pushed a commit to champ18ion/otari that referenced this pull request Jul 27, 2026
…ts (mozilla-ai#423)

* fix(providers): honor the optional API key for keyless custom endpoints

The dashboard's "Custom endpoint" form labels the API key optional, but any-llm
rejects a keyless call to most providers (openai, anthropic, ...) with
MissingApiKeyError when no key and no native env var are present. So a keyless
local backend (vLLM, llama.cpp, Ollama) added through the form could neither be
tested nor used, contradicting the "optional" label.

Supply a harmless placeholder key when an instance points at a custom api_base
and has no key anywhere (config and the provider's native env var both empty).
The injection is purely additive: it only affects the case any-llm would already
reject, and it never overrides a real key or the documented env-var fallback. A
local server ignores the placeholder; a real one that needs a key rejects it,
the same failure the operator would already get. Applied at both the dispatch
path (get_provider_kwargs) and the pre-save "Test connection" path so they
agree. Mirrors any-llm's own keyless tolerance (mozilla-ai/any-llm#1198).

Fixes mozilla-ai#421

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(providers): detect compound env-key labels for keyless placeholder

any-llm exposes ENV_API_KEY_NAME as a "/"-joined list of alternatives for
some providers (gemini: "GEMINI_API_KEY/GOOGLE_API_KEY"), so os.getenv on the
whole label always missed, letting the placeholder shadow a real env-var key.
Split on "/" and treat each as a candidate. Also rename the redundant
TestKeylessProviderConnectionTest class.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@tbille
tbille merged commit 7557a4e into main Aug 3, 2026
12 checks passed
@tbille
tbille deleted the feat/openai-compatible-custom-path branch August 3, 2026 08:58
njbrake added a commit that referenced this pull request Aug 5, 2026
## Description

Writes down the provider policy from #1197 in CONTRIBUTING.md, so
config-only gateway PRs have a documented path and the acceptance bar is
consistent from one review to the next. Docs only, no code change.

The `Adding a New Provider` section now leads with a decision step
rather than assuming every provider needs a folder:

- **No PR needed.** If the endpoint speaks the OpenAI API,
`AnyLLM.create_openai_compatible(...)` (added in #1198) already covers
it. That is the right answer for private gateways and self-hosted
servers, and it means nobody is ever blocked.
- **A registry row.** Config-only OpenAI-compatible gateways get one row
in `src/any_llm/providers/registry.py` (added in #1201), with no folder,
no `pyproject.toml` extra, and no `tests/conftest.py` model maps.
- **A code folder.** Reserved for providers whose protocol requires
behavior: custom auth, non-OpenAI request or response shapes, param
translation, or model-list quirks.

The rule is stated as the reviewer judging whether the protocol
*requires* the code. Shipping an official SDK is not by itself a reason
for a folder, and adding an override that is not needed does not turn a
config-only gateway into a code provider.

`Provider Tiers` documents verified vs community as a support promise
rather than a statement about code shape, so a config-only provider we
hold keys for stays verified. Community entries are verified live by the
contributor at PR time and excluded from the integration matrix; CI
cannot use repository secrets on fork PRs, so contribution-time
verification is the only bar that is actually enforceable. The removal
policy is written down too, on the grounds that cheap addition is only
sustainable if removal is equally cheap.

Section 2a gives the concrete row, flag discipline (do not set a flag
you have not exercised against the live endpoint), and a copy-pasteable
verification script covering completion, streaming, and `list_models`.

Two details worth flagging for review:

- **The enum caveat is real, and verified.** A registry-only row
resolves through `AnyLLM.create("name")` and
`get_provider_class("name")`, but the `"name:model"` string form raises
`UnsupportedProviderError`, because `split_model_provider` returns
`LLMProvider`. I confirmed this by injecting a row with no enum member.
The checklist therefore tells contributors to add the `LLMProvider`
entry as well. Widening that type is still a follow-up on #1197.
- **Tier labeling in the docs is not claimed as done.** An earlier draft
said community providers are labeled in the generated docs. They are not
yet, so the text points at #1197 for the remaining rollout instead.

Also repairs stale references in the existing checklist, since they sit
in the section being rewritten:

- `ProviderName` in `src/any_llm/provider.py` is now `LLMProvider` in
`src/any_llm/constants.py` (that file does not exist).
- Providers inherit `AnyLLM` from `any_llm.any_llm`, not a `Provider`
class from `any_llm.provider`.
- The `__init__.py` snippet used a non-existent import path; it is the
provider package's own `__init__.py`.
- Renaming the old `2. Implementation Checklist` heading to `2b` broke
an in-page anchor further up the file, which is repointed.

Happy to split the stale-reference cleanup into its own commit if you
would rather review it separately.

## PR Type

- 📚 Documentation

## Relevant issues

Completes the `Update CONTRIBUTING.md with the acceptance rule` item of
#1197. Does not close the issue; the two-tier docs listing,
`provider:model` routing for registry-only names, and the open questions
about registry knobs and shim lifetime remain.

## Checklist
- [x] I understand the code I am submitting.
- [ ] I have added unit tests that prove my fix/feature works
- [x] I have run this code locally and verified it fixes the issue.
- [x] New and existing tests pass locally
- [x] Documentation was updated where necessary
- [x] I have read and followed the [contribution
guidelines](https://github.com/mozilla-ai/any-llm/blob/main/CONTRIBUTING.md)
- [x] **AI Usage:**
    - [ ] No AI was used.
    - [x] AI was used for drafting/refactoring.
    - [ ] This is fully AI-generated.

Notes on the checklist: no unit tests, since this is a
documentation-only change. `tests/docs` only globs `docs/**/*.md`, so
the Python snippets in CONTRIBUTING.md are not executed by the suite; I
validated them by hand against the real API instead
(`create_openai_compatible` signature, `AnyLLM.create` with an injected
registry row, and `Model.id` for the `list_models` output). `pre-commit`
and `pytest tests/docs` are clean.

## AI Usage Information

- AI Model used: Claude Opus 5
- AI Developer Tool used: Claude Code
- Any other info you'd like to share: Drafted by Claude through back and
forth with @njbrake. The policy decisions and the reasoning are his; the
prose is Claude's. The behavioral claims in the text were checked
against the code rather than assumed.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Documentation**
- Updated provider contribution guidance with clearer support tiers,
eligibility criteria, and decision rules.
- Added instructions for configuration-only gateways, custom-code
providers, and providers that do not require a pull request.
- Clarified registry updates, capability flags, enum and package
registration, implementation paths, and override requirements.
- Expanded integration-testing and live-verification expectations for
each support tier.
- Added guidance on provider removal and refreshed project structure
references.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added the 1.24.0 Included in release 1.24.0 label Aug 5, 2026

This branch was previously deployed

1 inactive deployment
integration-tests — 701d473d Deployed Jul 24, 2026 by njbrake via run-docs-tests #2212
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

1.24.0 Included in release 1.24.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants