Repository navigation
fix(gateway): apply Vertex Anthropic request fields in adapter_class so judges work - #25556
Conversation
Vertex rejects Anthropic partner-model requests that omit anthropic_version. That field was injected only by _VertexAIClaudeProvider._prepare_payload(), a provider hook reached exclusively from AnthropicProvider._chat() and ._chat_stream(). The judge path does not go through either. GatewayAdapter._invoke_and_handle_tools() builds its request from adapter_class.chat_to_model() and posts it with send_chat_request(), so _prepare_payload() never runs and every vertex_ai:/claude-* judge invocation fails with "anthropic_version: Field required". The failure is silent to the caller: it is stored as a SCORER_ERROR with value=None, so mlflow.genai.evaluate() completes and renders a full table with an empty judge column. Bedrock does not have this problem because AmazonBedrockAnthropicAdapter applies the equivalent field in chat_to_model(), one layer up. Vertex placed the same required transformation on the provider instead, which is the layer the judge path skips. Move it to a _VertexAIClaudeAdapter and point _VertexAIClaudeProvider.adapter_class at it, so both paths are covered from one definition. _chat()/._chat_stream() format through AnthropicAdapter directly rather than self.adapter_class, so _prepare_payload() is kept for the gateway path and now delegates to the adapter rather than repeating the transformation. test_adapter_class_matches_the_active_delegate is updated: Claude now resolves to the Vertex-specific adapter, which is the point of the change. Fixes mlflow#25543 Signed-off-by: Yash-Chindam <[email protected]>
Install mlflow from this PR
Install mlflow from this PR# mlflow
pip install git+https://github.com/mlflow/mlflow.git@refs/pull/25556/merge
# mlflow-skinny
pip install git+https://github.com/mlflow/mlflow.git@refs/pull/25556/merge#subdirectory=libs/skinnyFor Databricks, use the following command: %sh curl -LsSf https://raw.githubusercontent.com/mlflow/mlflow/HEAD/dev/install-skinny.sh | sh -s pull/25556/mergePR author's recent activityIn the last 14 days, @Yash-Chindam opened 6 PRs across 3 repos:
|
…aude-judge-anthropic-version
There was a problem hiding this comment.
Thanks for the fix and the detailed write-up. The approach looks right, and I verified both the judge path and the gateway path locally (anthropic_version set once, model dropped in both). A few small suggestions inline.
-- Claude-drafted, reviewed before posting.
| return cls._apply_vertex_fields(super().chat_to_model(payload, config)) | ||
|
|
||
| @classmethod | ||
| def chat_streaming_to_model(cls, payload, config): |
There was a problem hiding this comment.
AnthropicAdapter.chat_streaming_to_model already delegates to cls.chat_to_model, so the inherited method would pick up your chat_to_model override and apply the Vertex fields once. As written, this override applies them a second time. It's idempotent today so not a bug, but the redundant pass isn't desirable, and it becomes a latent trap if _apply_vertex_fields ever stops being idempotent. It also differs from AmazonBedrockAnthropicAdapter, which overrides only chat_to_model. Could we drop this override and let the base delegation carry it? The streaming assertion in the test should still pass.
-- Claude-drafted, reviewed before posting.
| assert "model" not in call_kwargs[1]["json"] | ||
|
|
||
|
|
||
| def test_claude_adapter_applies_vertex_fields_without_provider_hooks(): |
There was a problem hiding this comment.
Nice that this formats through adapter_class directly. One gap: it stops at the adapter, whereas the bug surfaced through the judge integration (GatewayAdapter._invoke_and_handle_tools -> _get_provider_instance("vertex_ai", "claude-*") -> adapter_class.chat_to_model). Could we add a case that resolves the provider through that path too, so a future break in the resolution wiring is also caught? Optional, but it would pin the exact regression.
-- Claude-drafted, reviewed before posting.
| return payload | ||
|
|
||
| @classmethod | ||
| def chat_to_model(cls, payload, config): |
There was a problem hiding this comment.
nit: _apply_vertex_fields just above is fully typed, but this override isn't. The base signatures are untyped too, so this is optional. Could we add payload: dict[str, Any], config -> dict[str, Any] to keep the new class internally consistent?
-- Claude-drafted, reviewed before posting.
|
Documentation preview for c37e9ec is available at: More info
|
kriscon-db's review on mlflow#25556 flagged that _VertexAIClaudeAdapter's chat_streaming_to_model override applies the Vertex fields a second time, since AnthropicAdapter.chat_streaming_to_model already delegates to chat_to_model, which the adapter also overrides. Drop the redundant override and let base-class delegation carry it, matching how AmazonBedrockAnthropicAdapter only overrides chat_to_model. Also add a test that resolves the provider via _get_provider_instance, the same seam mlflow.genai.judges.adapters.gateway_adapter uses, so a future regression in that resolution wiring is caught too - not just the adapter-level formatting the existing test already covers. Co-Authored-By: Claude Sonnet 5 <[email protected]> Signed-off-by: Yash-Chindam <[email protected]>
|
Pushed 9858353 addressing all three review comments:
|
Co-Authored-By: Claude Fable 5.1 <[email protected]> Signed-off-by: Aaron Teo <[email protected]>
aaronteo-db
left a comment
There was a problem hiding this comment.
LGTM! @Yash-Chindam Thanks for addressing the comments from @kriscon-db .
I made some minor shortening on some verbose comments. Thanks for your contribution!
Related Issues/PRs
Closes #25543
What changes are proposed in this pull request?
Vertex rejects Anthropic partner-model requests that omit
anthropic_version. That field was injected only by_VertexAIClaudeProvider._prepare_payload(), a provider hook reached exclusively fromAnthropicProvider._chat()and._chat_stream().The judge path does not go through either.
GatewayAdapter._invoke_and_handle_tools()builds its request fromadapter_class.chat_to_model()and posts it withsend_chat_request()(gateway_adapter.py:581-590), so_prepare_payload()never runs and everyvertex_ai:/claude-*judge invocation fails withanthropic_version: Field required.Bedrock does not have this problem because
AmazonBedrockAnthropicAdapterapplies the equivalent field inchat_to_model()(bedrock.py:28-35), one layer up. Vertex placed the same required transformation on the provider instead — the layer the judge path skips.This adds
_VertexAIClaudeAdapter(AnthropicAdapter)and overridesadapter_classon_VertexAIClaudeProvider, as agreed on the issue.On the double-execution concern raised in review:
_prepare_payload()deliberately does not call_VertexAIClaudeAdapter.chat_to_model. It calls a small_apply_vertex_fields()classmethod that only popsmodeland setsanthropic_version, so message conversion andmax_tokensdefaulting still run exactly once on the gateway path. Verified by running both sequences on a payload that exercises system-message conversion andmax_tokensdefaulting:Behaviour of the judge path, which is the bug:
One existing test is updated, which is a review decision rather than a mechanical change:
test_adapter_class_matches_the_active_delegateasserted_make_claude_provider().adapter_class is AnthropicAdapter. That assertion encodes the buggy behaviour — the plain adapter, with noanthropic_version— so it now asserts_VertexAIClaudeAdapter, with a comment explaining why.How is this PR tested?
New:
test_claude_adapter_applies_vertex_fields_without_provider_hooksformats throughadapter_class.chat_to_modeland.chat_streaming_to_modelby themselves — the judge path — and assertsanthropic_versionis set andmodelis absent. Nothing covered that route before.A note on my own verification, in the interest of transparency: I could not run the suite locally. Every test in
tests/gateway/providers/test_vertex_ai.pyerrors at setup withRuntimeError: Failed to start server— the session fixture attests/conftest.py:812-853spawns anmlflow serversubprocess viauv runand times out after 60s in my environment. It affects all tests in the file, including ones this PR does not touch (test_with_credentials,test_project_required, thetest_maas_*family), so it is environmental. I verified the assertions by executing the test bodies directly against both the patched and unpatched source, which is where the before/after output above comes from. CI is the real check.ruff checkandruff format --checkboth pass under the pinnedruff==0.16.4fromlint-requirements.txt. Commits are DCO signed off.Does this PR require documentation update?
Does this PR require updating the MLflow Skills repository?
Release Notes
Is this a user-facing change?
Fixed
mlflow.genaijudges failing withanthropic_version: Field requiredwhen using Anthropic Claude models hosted on Vertex AI (vertex_ai:/claude-*). The failure was silent — it surfaced as aSCORER_ERRORwithvalue=None, somlflow.genai.evaluate()completed and rendered a full table with an empty judge column.What component(s), interfaces, languages, and integrations does this PR affect?
Components
area/evaluation: MLflow model evaluation features, evaluation metrics, and evaluation workflowsarea/gateway: MLflow AI Gateway client APIs, server, and third-party integrationsHow should the PR be classified in the release notes? Choose one:
rn/bug-fix- A user-facing bug fix worth mentioning in the release notesIs this PR a critical bugfix or security fix that should go into the next patch release?
Disclosure: I used an AI assistant (Claude) to help trace the call paths, implement the fix, and draft this description. Every file/line reference and every result above was produced by running the code, and I reviewed the change before submitting.