Skip to content

feat(meta): add Meta Model API provider (OpenAI + Anthropic dual protocol) - #1250

Merged
tbille merged 3 commits into
mainfrom
feat/meta-provider
Aug 7, 2026
Merged

tbille merged 3 commits into
mainfrom
feat/meta-provider

Conversation

@tbille

@tbille tbille commented Aug 7, 2026 •

Copy link
Copy Markdown
Member

Description

Adds a new meta provider for Meta's Model API (muse-spark-* models). This API is unusual in that it serves the same models through both protocols on the same account/key:

  • Chat Completions + Responses via https://api.meta.ai/v1, OpenAI-SDK compatible.
  • Messages via https://api.meta.ai, Anthropic-SDK compatible (native content blocks, thinking, tool_use, stop_reason).

MetaProvider(BaseOpenAIProvider) inherits Chat Completions and Responses unmodified (no translation needed, they're wire-compatible with the OpenAI SDK), and stands up a second AsyncAnthropic client to serve .messages()/.amessages() as a genuine native pass-through to /v1/messages, rather than any-llm's default Messages↔Completions bridge — this preserves thinking blocks, native tool_use blocks, and cache_control that a bridge conversion would otherwise drop. Two auth details worth calling out for review:

  • The Anthropic client is constructed with auth_token= (not api_key=), since Meta expects Authorization: Bearer rather than Anthropic's native x-api-key header.
  • The Anthropic SDK's base URL is derived from the OpenAI one by stripping a trailing /v1 (https://api.meta.ai/v1 → https://api.meta.ai), since the Anthropic SDK appends /v1/messages itself.

context_management and betas are rejected with NotImplementedError on .messages(), since Meta's translation layer doesn't document support for Anthropic's context-management/beta primitives.

This lands as community tier: added to the LLMProvider enum and a pyproject.toml extra, but not to VERIFIED_PROVIDERS or the tests/conftest.py model maps, since I don't hold a MODEL_API_KEY for CI. Capability flags were set conservatively from the docs alone (no live key to verify against):

  • SUPPORTS_COMPLETION_REASONING=False — docs state reasoning_content on Chat Completions is redacted to empty for external callers.
  • SUPPORTS_EMBEDDING, SUPPORTS_BATCH, SUPPORTS_IMAGE_GENERATION, SUPPORTS_RERANK all False — none of these endpoints are documented.
  • PROMPT_CACHE_KEY_SUPPORT="supported" — docs confirm prompt_cache_key on Chat Completions + Responses (not explicitly confirmed on Messages).

I could not run this against the live endpoint (no MODEL_API_KEY). Whoever has one, the live-verification snippet from CONTRIBUTING.md would be:

from any_llm import AnyLLM
llm = AnyLLM.create("meta", api_key="...")
print(llm.completion(model="muse-spark-1.2", messages=[{"role": "user", "content": "Say hi"}]).choices[0].message.content)
print(llm.messages(model="muse-spark-1.2", max_tokens=64, messages=[{"role": "user", "content": "Say hi"}]))
print([m.id for m in llm.list_models()][:5])

That run would also be the moment to correct any of the conservative flags above if the live behavior differs from the docs.

PR Type

  • 🆕 New Feature

Relevant issues

N/A

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. — unit tests pass locally; the live endpoint is not verified (no MODEL_API_KEY), see note above.
  • New and existing tests pass locally (uv run pytest tests/unit — 1889 passed; the only failures are a pre-existing local otari SDK version mismatch unrelated to this change, reproduced identically on unmodified main)
  • Documentation was updated where necessary (docs/providers.md is generated from provider metadata; no manual docs needed)
  • 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 Sonnet 5

  • AI Developer Tool used: Claude Code

  • Any other info you'd like to share: Implementation, tests, and this PR description were drafted by Claude Code from the published Meta Model API docs, following the existing otari/anthropic provider patterns in this repo. Live-endpoint verification is still needed from someone with a MODEL_API_KEY.

  • I am an AI Agent filling out this form

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added support for the Meta provider.
    • Supports OpenAI-compatible completions and responses, as well as Anthropic-compatible messaging.
    • Added standard and structured outputs, streaming responses, authentication, and model listing.
    • Included the Meta provider in the complete optional installation.
  • Tests

    • Added comprehensive coverage for Meta provider configuration, requests, streaming, authentication, and output handling.

…ocol)

Meta's Model API (https://dev.meta.ai/docs) serves the same muse-spark-*
models through both an OpenAI-compatible Chat Completions/Responses
endpoint and a native Anthropic-compatible Messages endpoint on the same
account/key.

MetaProvider(BaseOpenAIProvider) inherits Chat Completions and Responses
unmodified, since they are wire-compatible with the OpenAI SDK. It stands
up a second AsyncAnthropic client (authenticated via auth_token, since
Meta expects Authorization: Bearer rather than Anthropic's native x-api-key
header) and overrides _amessages for a genuine native pass-through to
/v1/messages, instead of any-llm's default Messages<->Completions bridge,
so thinking blocks, native tool_use blocks, and cache_control survive the
round trip. context_management and betas are rejected explicitly since
Meta's translation layer does not document support for them.

Lands as community tier (no repo-held key yet): added to the LLMProvider
enum and a pyproject extra, but not to VERIFIED_PROVIDERS or the
tests/conftest.py model maps.
@tbille
tbille temporarily deployed to integration-tests August 7, 2026 09:48 — with GitHub Actions Inactive
@coderabbitai

coderabbitai Bot commented Aug 7, 2026 •

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This PR registers the Meta provider, adds OpenAI-compatible and Anthropic-compatible clients, implements native Messages handling and streaming conversion, supports structured outputs, and adds unit tests.

Changes

Meta provider

Layer / File(s) Summary
Provider registration and client setup
pyproject.toml, src/any_llm/constants.py, src/any_llm/providers/meta/*, tests/unit/providers/test_meta_provider.py
Registers the meta extra and LLMProvider.META. Exports MetaProvider, derives the Anthropic base URL, initialises both clients, and tests provider metadata and authentication.
Completion and native message handling
src/any_llm/providers/meta/meta.py, tests/unit/providers/test_meta_provider.py
Delegates completions and model listing to the OpenAI client. Handles native Anthropic Messages requests, rejects unsupported options, converts responses, and tests these paths.
Streaming and structured output
src/any_llm/providers/meta/meta.py, tests/unit/providers/test_meta_provider.py
Converts recognised Anthropic stream events into typed any-llm events. Supports Pydantic and dictionary output formats and tests event conversion and output configuration.

Possibly related PRs

  • mozilla-ai/any-llm#1147: Adds a provider using the same dependency, enum, export, implementation, and test pattern.
  • mozilla-ai/any-llm#1179: Adds another provider using the same registration and testing pattern.
  • mozilla-ai/any-llm#1215: Aligns with the provider-folder, enum, export, implementation, and testing requirements used by this change.

Suggested labels: 1.21.0

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the new Meta Model API provider and its OpenAI and Anthropic protocol support.
Description check ✅ Passed The description includes all template sections, explains the implementation and limitations, records testing status, and completes the AI usage information.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/meta-provider

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 Aug 7, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
src/any_llm/constants.py 100.00% <100.00%> (ø)
src/any_llm/providers/meta/__init__.py 100.00% <100.00%> (ø)
src/any_llm/providers/meta/meta.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.

@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: 5

🤖 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/providers/meta/meta.py`:
- Around line 68-80: Run the Meta provider integration suite with MODEL_API_KEY
covering the Chat Completions, Responses, image-input, PDF-input, and Messages
request paths before enabling their corresponding SUPPORTS_* flags. Until those
paths are verified, disable the affected capability flags in the Meta provider
and preserve only flags backed by passing integration coverage.
- Around line 111-121: Update the output_format handling in the provider method
containing _anthropic_client.messages.parse/create so params.stream is not
silently removed when native structured output is requested. Either route the
combination through a supported streaming path or reject it with a clear error
before invoking the SDK, and add a test covering output_format with stream
enabled.
- Around line 41-48: Update _derive_anthropic_base to strip trailing slashes
from openai_base before removing the /v1 suffix, so both /v1 and /v1/ overrides
produce the bare host. Add coverage for an API-base override ending in /v1/.

In `@tests/unit/providers/test_meta_provider.py`:
- Around line 239-251: Strengthen the test around
provider._stream_messages_async by asserting each collected event is the
expected any-llm event class and validating its key converted fields, not only
event.type. Add an unrecognised Anthropic SDK event to the input stream and
assert it is omitted from the yielded events, covering the ignored-event branch
while preserving the existing expected event sequence.
- Around line 254-303: Extend the MetaProvider structured-output tests to cover
the OpenAI-compatible Responses flow: add one test for dataclass or dictionary
output using parse_responses_output and another for Pydantic output using the
Responses client’s parse method. Assert each path returns the mocked result and
forwards the expected output format through the appropriate Responses API call.
🪄 Autofix

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: 1fe589ac-9557-42d7-9a50-2a84ebc672be

📥 Commits

Reviewing files that changed from the base of the PR and between 430d968 and 12f21c5.

📒 Files selected for processing (5)
  • pyproject.toml
  • src/any_llm/constants.py
  • src/any_llm/providers/meta/__init__.py
  • src/any_llm/providers/meta/meta.py
  • tests/unit/providers/test_meta_provider.py

Comment thread src/any_llm/providers/meta/meta.py Outdated
Comment thread src/any_llm/providers/meta/meta.py
Comment thread src/any_llm/providers/meta/meta.py
Comment thread tests/unit/providers/test_meta_provider.py Outdated
Comment thread tests/unit/providers/test_meta_provider.py
@HareeshBahuleyan
HareeshBahuleyan self-requested a review August 7, 2026 09:55
…output_format

- _derive_anthropic_base: strip a trailing slash before removing the /v1
  suffix, so a ".../v1/" override doesn't leave a base URL that would
  double up to ".../v1/v1/messages".
- _amessages: raise explicitly if output_format and stream are both set,
  instead of silently dropping stream when called directly (the public
  amessages() entry point already blocks this combination, but _amessages
  can be invoked directly, e.g. in tests).
- Strengthen the streaming test to assert converted event types/fields
  (not just event.type) and to cover an unrecognized SDK event (ping)
  being dropped rather than yielded.
- Add coverage for the new trailing-slash and stream+output_format cases.
@tbille
tbille temporarily deployed to integration-tests August 7, 2026 09:57 — with GitHub Actions Inactive
msg = "stream is not supported for output_format"
raise ValueError(msg)
native_kwargs = params.model_dump(
exclude_none=True, exclude={"output_format", "stream", "betas", "context_management"}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P1] prompt_cache_key breaks the Messages path

The provider advertises PROMPT_CACHE_KEY_SUPPORT = "supported", but _amessages() forwards prompt_cache_key into the Anthropic SDK. Anthropic SDK 0.83 does not accept that argument:

TypeError: AsyncMessages.create() got an unexpected keyword argument 'prompt_cache_key'

Meta documents prompt_cache_key for Chat Completions and Responses, but not for Messages. The Messages implementation should exclude it and raise a clear endpoint-specific unsupported-parameter error. It should remain supported for the OpenAI-compatible paths.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in fb3e44c: _amessages now rejects prompt_cache_key client-side with UnsupportedParameterError before it ever reaches the Anthropic SDK call, instead of letting messages.create() raise a raw TypeError. It remains supported (unchanged) on the OpenAI-compatible Chat Completions/Responses paths, since `PROMPT_CACHE_KEY_SUPPORT = "supported"" only gates the generic base-layer check, not this provider's Messages-specific handling. Added parametrized test coverage.

)
return self._convert_native_message_to_response(message)

api_kwargs = params.model_dump(exclude_none=True, exclude={"betas", "context_management"})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P2] Unsupported Messages parameters are passed through

The native pass-through forwards stop_sequences, top_k, and top-level cache_control.

Meta's Messages documentation explicitly says:

  • stop_sequences and top_k return HTTP 400.
  • Unknown top-level fields return HTTP 400.
  • Top-level cache_control is not documented.

Therefore, the claim that cache_control “survives the round trip” is misleading. These parameters should be explicitly rejected, or the documentation should clearly describe the limited Messages compatibility.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in fb3e44c: stop_sequences and top_k are now rejected the same way as prompt_cache_key above, with UnsupportedParameterError, matching the existing context_management/betas guard.

On cache_control: you're right that the docstring overclaimed it. Meta's docs don't explicitly confirm or reject it for Messages (unlike stop_sequences/top_k, which are called out by name), so I reworded the docstring to stop asserting it survives, rather than adding a rejection I can't back with a docs citation either way. Given it's a real top-level kwarg on the Anthropic SDK's own messages.create() (used for prompt caching, not something Meta specifically excluded in its documented field list), blocking it outright felt like the wrong kind of guess in the other direction. Whoever runs the live-verification pass can confirm either way and I'll follow up.

@HareeshBahuleyan

HareeshBahuleyan commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

Maybe we could simplify this provider by keeping the inherited Chat Completions and Responses support, while removing the native Messages implementation?

Meta documents Responses as the full-featured interface for agent workflows, including reasoning replay, tool loops, search grounding, files, and server-managed history. Chat Completions and Responses use the same AsyncOpenAI client and require little provider-specific code.

Most of the maintenance complexity comes from the second AsyncAnthropic client, base URL derivation, unsupported-parameter filtering, streaming event conversion, and additional tests. Basic .messages() usage could still rely on any-llm’s existing Messages-to-Completions bridge.

The tradeoff is losing native Anthropic fidelity and direct Claude Code compatibility. Unless Claude Code or native Anthropic blocks are explicit requirements, keeping only the OpenAI-compatible paths would retain advanced agent support while substantially reducing maintenance.

…es path

Meta's Messages docs say stop_sequences and top_k return HTTP 400, and
document prompt_cache_key only for Chat Completions/Responses, not
Messages. _amessages was forwarding all three straight into the
Anthropic SDK: prompt_cache_key isn't even a valid messages.create
kwarg (raises a raw TypeError from the SDK), and the other two would
have surfaced as an opaque 400 from Meta's API. Reject all three
client-side with UnsupportedParameterError instead, matching how
context_management/betas are already handled.

Also corrected the class docstring's claim that cache_control "survives
the round trip" on Messages - Meta doesn't document that field either
way (unlike stop_sequences/top_k, which it explicitly 400s on), so the
docstring shouldn't have implied it's confirmed to work.
@tbille
tbille temporarily deployed to integration-tests August 7, 2026 10:10 — 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: 1

🤖 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/providers/meta/meta.py`:
- Around line 131-133: Update src/any_llm/providers/meta/meta.py:131-133 in
_amessages() to validate each unsupported parameter from both the typed params
fields and merged kwargs before calling the client, using direct MessagesParams
attribute access instead of getattr(). Add tests in
tests/unit/providers/test_meta_provider.py:190-213 covering every unsupported
parameter passed via _amessages(params, **kwargs), asserting
UnsupportedParameterError is raised and the client is not called.
🪄 Autofix

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: 23a7aee9-d543-431f-9e4c-339d91ffc9cc

📥 Commits

Reviewing files that changed from the base of the PR and between de41596 and fb3e44c.

📒 Files selected for processing (2)
  • src/any_llm/providers/meta/meta.py
  • tests/unit/providers/test_meta_provider.py

Comment on lines +131 to +133
for param_name in _MESSAGES_UNSUPPORTED_PARAMS:
if getattr(params, param_name) is not None:
raise UnsupportedParameterError(param_name, self.PROVIDER_NAME)

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 | 🟠 Major | ⚡ Quick win

Validate unsupported parameters after merging kwargs.

_amessages() checks only params, then api_kwargs.update(kwargs) forwards unsupported names supplied through kwargs. This can again cause the raw SDK TypeError or Meta API HTTP 400 that this validation intends to prevent.

  • src/any_llm/providers/meta/meta.py#L131-L133: reject unsupported values from both params and kwargs. Use direct typed access for the known MessagesParams fields instead of getattr().
  • tests/unit/providers/test_meta_provider.py#L190-L213: add cases that pass each unsupported parameter through _amessages(params, **kwargs) and assert that the client is not called.

As per coding guidelines, “Prefer direct typed attribute access such as obj.field over `getattr(obj, "field")”.

📍 Affects 2 files
  • src/any_llm/providers/meta/meta.py#L131-L133 (this comment)
  • tests/unit/providers/test_meta_provider.py#L190-L213
🤖 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/meta/meta.py` around lines 131 - 133, Update
src/any_llm/providers/meta/meta.py:131-133 in _amessages() to validate each
unsupported parameter from both the typed params fields and merged kwargs before
calling the client, using direct MessagesParams attribute access instead of
getattr(). Add tests in tests/unit/providers/test_meta_provider.py:190-213
covering every unsupported parameter passed via _amessages(params, **kwargs),
asserting UnsupportedParameterError is raised and the client is not called.

Source: Coding guidelines

@HareeshBahuleyan
HareeshBahuleyan self-requested a review August 7, 2026 10:40
@tbille
tbille merged commit 455ebe1 into main Aug 7, 2026
12 checks passed
@tbille
tbille deleted the feat/meta-provider branch August 7, 2026 13:00
@github-actions github-actions Bot added the 1.25.0 Included in release 1.25.0 label Aug 11, 2026

This branch was previously deployed

1 inactive deployment
integration-tests — fb3e44c4 Deployed Aug 7, 2026 by tbille via run-docs-tests #2340
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

1.25.0 Included in release 1.25.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants