test(e2e): cover Anthropic /chat/completions streaming and tool calls

Adds TestAnthropicChatCompletions to the chat completions regression suite,
registering a claude-haiku-4-5 deployment via /model/new and asserting the
streamed call delivers real content deltas and a tool-forced call returns a
well-formed get_weather tool_call on both the non-streamed and streamed paths.
Covers three P0 registry cells that had no e2e test.
This commit is contained in:
Yuneng Jiang 2026-09-05 10:37:14 -07:00
parent 7672399c26
commit 98784360e8
No known key found for this signature in database

View file

@ -11,7 +11,7 @@ fails that provider's row here.
The per-provider classes below cover the OpenAI-compatible /chat/completions
translation for providers customers reach by registering their own deployment
via /model/new (Cohere, Gemini, hosted_vllm), each deleted on teardown.
via /model/new (Cohere, Gemini, hosted_vllm, Anthropic), each deleted on teardown.
"""
from __future__ import annotations
@ -46,6 +46,7 @@ pytestmark = pytest.mark.e2e
COHERE_BACKEND = "cohere/command-r-08-2024"
GEMINI_BACKEND = "gemini/gemini-2.5-flash"
OPENAI_BACKEND = "openai/gpt-5.6"
ANTHROPIC_BACKEND = "anthropic/claude-haiku-4-5-20251001"
BEDROCK_CONVERSE_BACKEND = "bedrock/us.anthropic.claude-haiku-4-5-20251001-v1:0"
@ -746,3 +747,98 @@ class TestBedrockConverseChatCompletions:
response = unwrap(client.proxy.chat(key, ChatBody(model=model, messages=_vision_messages(), max_tokens=32)))
_assert_describes_cat(response)
class TestAnthropicChatCompletions:
"""Anthropic via the OpenAI-compatible /chat/completions path, the translation
customers on the OpenAI SDK rely on when they route to Claude. The streamed call
must deliver real content deltas, and a tool-forced call must come back as a
well-formed tool_call on both the non-streamed and streamed paths.
"""
def _register(self, client: PassthroughClient, resources: ResourceManager, prefix: str) -> str:
model = f"{prefix}-{unique_marker()}"
model_id = client.proxy.create_model(
model, LiteLLMParamsBody(model=ANTHROPIC_BACKEND, api_key="os.environ/ANTHROPIC_API_KEY")
)
resources.defer(lambda: client.proxy.delete_model(model_id))
return model
@pytest.mark.covers(
"llm.chat_completions.anthropic.basic.stream.works",
exercised_on=["chat_completions"],
)
def test_anthropic_chat_streams_real_content(
self, client: PassthroughClient, resources: ResourceManager
) -> None:
model = self._register(client, resources, "e2e-anthropic-stream")
key = resources.key()
result = client.proxy.chat_stream(
key,
ChatBody(
model=model,
messages=[
ChatMessage(role="user", content=f"Count from 1 to 5, one number per line. {unique_marker()}")
],
max_tokens=64,
stream=True,
),
)
_assert_streamed_completion(result)
@pytest.mark.covers(
"llm.chat_completions.anthropic.tool_use.nonstream.works",
exercised_on=["chat_completions"],
)
def test_anthropic_chat_returns_tool_call(
self, client: PassthroughClient, resources: ResourceManager
) -> None:
model = self._register(client, resources, "e2e-anthropic-tool")
key = resources.key()
response = unwrap(
client.proxy.chat(
key,
ChatBody(
model=model,
messages=[
ChatMessage(role="user", content="What is the weather in San Francisco? Use the get_weather tool.")
],
tools=[_WEATHER_TOOL],
tool_choice="required",
max_tokens=128,
),
)
)
_assert_weather_tool_call(response)
@pytest.mark.covers(
"llm.chat_completions.anthropic.tool_use.stream.works",
exercised_on=["chat_completions"],
)
def test_anthropic_chat_streams_tool_call(
self, client: PassthroughClient, resources: ResourceManager
) -> None:
model = self._register(client, resources, "e2e-anthropic-tool-stream")
key = resources.key()
result = client.proxy.chat_stream(
key,
ChatBody(
model=model,
messages=[
ChatMessage(role="user", content="What is the weather in San Francisco? Use the get_weather tool.")
],
tools=[_WEATHER_TOOL],
tool_choice="required",
max_tokens=128,
stream=True,
),
)
assert result.ok and result.is_streaming, f"tool stream was not established: {result}"
assert result.stream_error is None, f"tool stream carried an error event: {result.stream_error}"
name, arguments = _streamed_tool_call(result.stream_events)
assert name == "get_weather", f"streamed tool call named {name!r}: {result.stream_events[:5]}"
args = _WeatherArgs.model_validate_json(arguments)
assert args.location.strip(), f"streamed tool call arguments missing location: {arguments!r}"