Repository navigation
feat(meta): add Meta Model API provider (OpenAI + Anthropic dual protocol) - #1250
Conversation
…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.
WalkthroughThis 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. ChangesMeta provider
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests.
... and 35 files with indirect coverage changes 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
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
📒 Files selected for processing (5)
pyproject.tomlsrc/any_llm/constants.pysrc/any_llm/providers/meta/__init__.pysrc/any_llm/providers/meta/meta.pytests/unit/providers/test_meta_provider.py
…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.
| 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"} |
There was a problem hiding this comment.
[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.
There was a problem hiding this comment.
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"}) |
There was a problem hiding this comment.
[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_sequencesandtop_kreturn HTTP 400.- Unknown top-level fields return HTTP 400.
- Top-level
cache_controlis 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.
There was a problem hiding this comment.
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.
|
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 Most of the maintenance complexity comes from the second 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.
There was a problem hiding this comment.
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
📒 Files selected for processing (2)
src/any_llm/providers/meta/meta.pytests/unit/providers/test_meta_provider.py
| for param_name in _MESSAGES_UNSUPPORTED_PARAMS: | ||
| if getattr(params, param_name) is not None: | ||
| raise UnsupportedParameterError(param_name, self.PROVIDER_NAME) |
There was a problem hiding this comment.
🎯 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 bothparamsandkwargs. Use direct typed access for the knownMessagesParamsfields instead ofgetattr().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
Description
Adds a new
metaprovider 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:https://api.meta.ai/v1, OpenAI-SDK compatible.https://api.meta.ai, Anthropic-SDK compatible (nativecontentblocks,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 secondAsyncAnthropicclient to serve.messages()/.amessages()as a genuine native pass-through to/v1/messages, rather than any-llm's default Messages↔Completions bridge — this preservesthinkingblocks, nativetool_useblocks, andcache_controlthat a bridge conversion would otherwise drop. Two auth details worth calling out for review:auth_token=(notapi_key=), since Meta expectsAuthorization: Bearerrather than Anthropic's nativex-api-keyheader./v1(https://api.meta.ai/v1→https://api.meta.ai), since the Anthropic SDK appends/v1/messagesitself.context_managementandbetasare rejected withNotImplementedErroron.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
LLMProviderenum and apyproject.tomlextra, but not toVERIFIED_PROVIDERSor thetests/conftest.pymodel maps, since I don't hold aMODEL_API_KEYfor CI. Capability flags were set conservatively from the docs alone (no live key to verify against):SUPPORTS_COMPLETION_REASONING=False— docs statereasoning_contenton Chat Completions is redacted to empty for external callers.SUPPORTS_EMBEDDING,SUPPORTS_BATCH,SUPPORTS_IMAGE_GENERATION,SUPPORTS_RERANKallFalse— none of these endpoints are documented.PROMPT_CACHE_KEY_SUPPORT="supported"— docs confirmprompt_cache_keyon 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 fromCONTRIBUTING.mdwould be: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
Relevant issues
N/A
Checklist
MODEL_API_KEY), see note above.uv run pytest tests/unit— 1889 passed; the only failures are a pre-existing localotariSDK version mismatch unrelated to this change, reproduced identically on unmodifiedmain)docs/providers.mdis generated from provider metadata; no manual docs needed)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/anthropicprovider patterns in this repo. Live-endpoint verification is still needed from someone with aMODEL_API_KEY.I am an AI Agent filling out this form
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Tests