"""Live e2e: Together AI through the gateway on /chat/completions and /v1/messages. The reasoning and tool-calling backend is the cheapest live ``together_ai/`` chat row in the proxy's own cost map that carries both capability flags; the structured-output and cache-pricing backends are likewise the cheapest rows carrying ``supports_response_schema`` and a ``cache_read_input_token_cost``. Two backends are pinned because the registry has no flag for what they prove: ``enable_thinking`` and the ``{"reasoning": {"enabled": false}}`` toggle that ``reasoning_effort="none"`` maps to are Qwen hybrid-model contracts, and MiniMax-M3 is the serverless model whose template renders a replayed ``reasoning_content`` back into the prompt (Qwen and DeepSeek silently drop it). MiniMax-M3 honors that replayed field on nearly every call, not every call (one miss in dozens of otherwise identical calls), so the replay case asks up to ``REPLAY_ATTEMPTS`` times and fails only when no answer carries the secret, which a proxy that strips the field guarantees. Requires TOGETHER_API_KEY on the proxy; no skip gate. """ from __future__ import annotations from collections.abc import Iterator, Mapping from dataclasses import dataclass from datetime import date from typing import Final import pytest from e2e_config import unique_marker from e2e_http import StreamingResponse, require_successful_call, unwrap from lifecycle import ResourceManager from models import ( AnthropicAssistantTurn, AnthropicContentBlock, AnthropicCustomTool, AnthropicMessagesBody, AnthropicToolResultBlock, AnthropicToolResultTurn, ChatAssistantTurn, ChatBody, ChatMessage, ChatResponse, ChatTool, ChatToolFunction, ChatToolResultTurn, CostMapEntry, JsonSchemaProperty, LiteLLMParamsBody, OutMessage, SpendLogRow, ToolCall, ToolInputSchema, ) from passthrough_client import PassthroughClient from pydantic import BaseModel pytestmark = pytest.mark.e2e HYBRID_REASONING_BACKEND = "together_ai/Qwen/Qwen3.5-9B" REASONING_REPLAY_BACKEND = "together_ai/MiniMaxAI/MiniMax-M3" SECRET_PROMPT = "Remember this for later and reply with just OK." SECRET_REASONING = "The user told me their favorite color is chartreuse. I must remember it." SECRET_QUESTION = "What is my favorite color? Answer with one word." REPLAY_ATTEMPTS: Final = 3 ARITHMETIC_PROMPT = "What is 17 + 26? Answer with just the number." PERSON_PROMPT = "Invent a fictional person." CACHE_PREFIX_FACTS: Final = 600 CACHE_ATTEMPTS: Final = 3 PERSON_RESPONSE_FORMAT: dict[str, object] = { "type": "json_schema", "json_schema": { "name": "person", "strict": True, "schema": { "type": "object", "properties": {"name": {"type": "string"}, "age": {"type": "integer"}}, "required": ["name", "age"], "additionalProperties": False, }, }, } WEATHER_PROMPT = "What is the weather in Paris? Use the tool." WEATHER_REPORT = "Paris: 22 degrees Celsius, clear skies, wind from the northwest at 9 km/h" COUNTING_PROMPT = "Count from 1 to 20, one number per line." WEATHER_TOOL = ChatTool( function=ChatToolFunction( name="get_weather", description="Get the current weather for a location.", parameters={ "type": "object", "properties": {"location": {"type": "string"}}, "required": ["location"], }, ) ) MESSAGES_WEATHER_TOOL = AnthropicCustomTool( name="get_weather", description="Get the current weather for a location.", input_schema=ToolInputSchema( properties={"location": JsonSchemaProperty(type="string")}, required=["location"], ), ) @dataclass(frozen=True, slots=True) class _Needs: function_calling: bool = False reasoning: bool = False response_schema: bool = False cache_read_pricing: bool = False class _Person(BaseModel): name: str age: int class _WeatherArgs(BaseModel): location: str class _StreamToolCallFunction(BaseModel): name: str | None = None arguments: str | None = None class _StreamToolCall(BaseModel): function: _StreamToolCallFunction | None = None class _StreamDelta(BaseModel): content: str | None = None reasoning_content: str | None = None tool_calls: list[_StreamToolCall] | None = None class _StreamChoice(BaseModel): delta: _StreamDelta | None = None class _StreamChunk(BaseModel): choices: list[_StreamChoice] = [] class _MessagesEventDelta(BaseModel): type: str | None = None text: str = "" class _MessagesStreamEvent(BaseModel): type: str delta: _MessagesEventDelta | None = None def _approx_equal(actual: float, expected: float) -> bool: return abs(actual - expected) <= max(1e-9, abs(expected) * 1e-2) def _cheapest_together_chat_model(registry: Mapping[str, CostMapEntry], needs: _Needs) -> str: today = date.today().isoformat() def qualifies(name: str, entry: CostMapEntry) -> bool: return ( name.startswith("together_ai/") and entry.litellm_provider == "together_ai" and entry.mode == "chat" and (entry.deprecation_date is None or entry.deprecation_date > today) and (entry.input_cost_per_token or 0.0) > 0 and (entry.output_cost_per_token or 0.0) > 0 and (not needs.function_calling or bool(entry.supports_function_calling)) and (not needs.reasoning or bool(entry.supports_reasoning)) and (not needs.response_schema or bool(entry.supports_response_schema)) and (not needs.cache_read_pricing or (entry.cache_read_input_token_cost or 0.0) > 0) ) candidates = sorted( (name for name, entry in registry.items() if qualifies(name, entry)), key=lambda name: ( registry[name].input_cost_per_token or 0.0, registry[name].output_cost_per_token or 0.0, name, ), ) assert candidates, f"no live together_ai chat model in the proxy's cost map satisfies {needs}" return candidates[0] @pytest.fixture(scope="module") def registry(client: PassthroughClient) -> dict[str, CostMapEntry]: return client.proxy.model_cost_map() @pytest.fixture(scope="module") def reasoning_tool_backend(registry: dict[str, CostMapEntry]) -> str: return _cheapest_together_chat_model(registry, _Needs(function_calling=True, reasoning=True)) def _register(client: PassthroughClient, resources: ResourceManager, backend: str) -> tuple[str, str]: model = f"e2e-together-{unique_marker()}" model_id = client.proxy.create_model( model, LiteLLMParamsBody(model=backend, api_key="os.environ/TOGETHER_API_KEY") ) resources.defer(lambda: client.proxy.delete_model(model_id)) return model, resources.key() def _message(response: ChatResponse) -> OutMessage: assert response.choices, f"Together returned no choices: {response}" message = response.choices[0].message assert message is not None, f"Together choice has no message: {response}" return message def _carries_secret(answer: OutMessage) -> bool: return answer.content is not None and "chartreuse" in answer.content.lower() def _answers_until_secret(client: PassthroughClient, key: str, body: ChatBody) -> Iterator[OutMessage]: answers: Final = (_message(unwrap(client.proxy.chat(key, body))) for _ in range(REPLAY_ATTEMPTS)) for answer in answers: yield answer if _carries_secret(answer): return def _deltas(result: StreamingResponse) -> list[_StreamDelta]: require_successful_call(result) assert result.is_streaming, f"response was not streamed: {result.headers}" assert not result.stream_error, f"stream errored: {result.stream_error}" assert result.stream_done, f"stream never reached [DONE]: {result.stream_events[-3:]}" return [ choice.delta for event in result.stream_events for choice in _StreamChunk.model_validate_json(event).choices if choice.delta is not None ] def _validated_weather_call_id(call: ToolCall) -> str: assert call.id, f"tool call carries no id, so a tool result cannot answer it: {call}" assert call.function.name == "get_weather", f"wrong tool called: {call}" assert call.function.arguments, f"tool call carries no arguments: {call}" args = _WeatherArgs.model_validate_json(call.function.arguments) assert "paris" in args.location.lower(), f"tool arguments lost the location: {args}" return call.id def _weather_call_ids(message: OutMessage) -> tuple[str, ...]: """The id of every tool call the model made, each one checked for the fields a caller needs to answer it. The backend is whichever together_ai row is cheapest with tools and reasoning, and those rows carry supports_parallel_function_calling, so one weather prompt can legitimately come back as several get_weather calls. What the gateway owes us is that each call survives translation intact; how many the model chose to make is the model's business.""" assert message.tool_calls, f"Together dropped the tool call: {message}" return tuple(_validated_weather_call_id(call) for call in message.tool_calls) def _cache_prefix(marker: str) -> str: facts = " ".join(f"Fact {i}: the {marker} ledger row {i} holds value {i * 7}." for i in range(CACHE_PREFIX_FACTS)) return f"Reference document {marker}:\n{facts}" def _cached_tokens(response: ChatResponse) -> int: usage = response.usage if usage is None or usage.prompt_tokens_details is None: return 0 return usage.prompt_tokens_details.cached_tokens or 0 def _primed_calls_until_cache_hit(client: PassthroughClient, key: str, model: str) -> Iterator[StreamingResponse]: """Together's prefix cache is best-effort, so each attempt primes a brand-new prefix (fresh marker = fresh cache identity) and re-asks with a different trailing question; a new marker per attempt keeps a stale attempt's prefix from polluting the next one.""" for _ in range(CACHE_ATTEMPTS): prefix = _cache_prefix(unique_marker()) _ = _message( unwrap( client.proxy.chat( key, ChatBody( model=model, messages=[ChatMessage(role="user", content=f"{prefix}\n\nReply with just OK.")], max_tokens=16, ), ) ) ) result = client.proxy.transport.send( "/chat/completions", headers=client.proxy.transport.bearer(key), json=ChatBody( model=model, messages=[ ChatMessage(role="user", content=f"{prefix}\n\nWhat is the marker id? Answer with one word.") ], max_tokens=32, ), ) require_successful_call(result) yield result if _cached_tokens(ChatResponse.model_validate_json(result.body)) > 0: return def _weather_call(client: PassthroughClient, key: str, model: str) -> OutMessage: return _message( unwrap( client.proxy.chat( key, ChatBody( model=model, messages=[ChatMessage(role="user", content=WEATHER_PROMPT)], tools=[WEATHER_TOOL], max_tokens=512, ), ) ) ) class TestTogetherChatCompletions: @pytest.mark.covers("llm.chat_completions.together_ai.thinking.nonstream.works") def test_reasoning_surfaces_as_reasoning_content( self, client: PassthroughClient, resources: ResourceManager, reasoning_tool_backend: str ) -> None: model, key = _register(client, resources, reasoning_tool_backend) message = _message( unwrap( client.proxy.chat( key, ChatBody( model=model, messages=[ChatMessage(role="user", content=ARITHMETIC_PROMPT)], max_tokens=1024, ), ) ) ) assert message.reasoning_content, ( f"{reasoning_tool_backend} reasons, but no reasoning_content came back: {message}" ) assert message.content and "43" in message.content, f"answer lost: {message}" @pytest.mark.covers("llm.chat_completions.together_ai.thinking.stream.works") def test_reasoning_streams_as_reasoning_content_deltas( self, client: PassthroughClient, resources: ResourceManager, reasoning_tool_backend: str ) -> None: model, key = _register(client, resources, reasoning_tool_backend) deltas = _deltas( client.proxy.chat_stream( key, ChatBody( model=model, messages=[ChatMessage(role="user", content=ARITHMETIC_PROMPT)], max_tokens=1024, stream=True, ), ) ) reasoning = "".join(delta.reasoning_content or "" for delta in deltas) content = "".join(delta.content or "" for delta in deltas) assert reasoning, f"stream carried no reasoning_content deltas: {deltas[:5]}" assert "43" in content, f"streamed answer lost: {content!r}" @pytest.mark.covers("llm.chat_completions.together_ai.tool_use.nonstream.works") def test_tool_call_is_returned( self, client: PassthroughClient, resources: ResourceManager, reasoning_tool_backend: str ) -> None: model, key = _register(client, resources, reasoning_tool_backend) _ = _weather_call_ids(_weather_call(client, key, model)) @pytest.mark.covers("llm.chat_completions.together_ai.tool_use.stream.works") def test_tool_call_is_streamed( self, client: PassthroughClient, resources: ResourceManager, reasoning_tool_backend: str ) -> None: model, key = _register(client, resources, reasoning_tool_backend) deltas = _deltas( client.proxy.chat_stream( key, ChatBody( model=model, messages=[ChatMessage(role="user", content=WEATHER_PROMPT)], tools=[WEATHER_TOOL], max_tokens=512, stream=True, ), ) ) calls = [ call.function for delta in deltas for call in delta.tool_calls or [] if call.function is not None ] assert calls, f"stream carried no tool call deltas: {deltas[:5]}" names = {call.name for call in calls if call.name} assert names == {"get_weather"}, f"unexpected streamed tool names: {names}" arguments = "".join(call.arguments or "" for call in calls) args = _WeatherArgs.model_validate_json(arguments) assert "paris" in args.location.lower(), f"streamed tool arguments lost the location: {args}" @pytest.mark.covers("llm.chat_completions.together_ai.multi_turn.nonstream.works") def test_tool_result_round_trip( self, client: PassthroughClient, resources: ResourceManager, reasoning_tool_backend: str ) -> None: model, key = _register(client, resources, reasoning_tool_backend) first = _weather_call(client, key, model) call_ids = _weather_call_ids(first) answer = _message( unwrap( client.proxy.chat( key, ChatBody( model=model, messages=[ ChatMessage(role="user", content=WEATHER_PROMPT), ChatAssistantTurn( content=first.content, reasoning_content=first.reasoning_content, tool_calls=first.tool_calls, ), *( ChatToolResultTurn(tool_call_id=call_id, content=WEATHER_REPORT) for call_id in call_ids ), ], tools=[WEATHER_TOOL], max_tokens=512, ), ) ) ) assert answer.content and "22" in answer.content, ( f"the model never saw the tool result: {answer}" ) @pytest.mark.covers("llm.chat_completions.together_ai.thinking.nonstream.template_kwargs_forwarded") def test_chat_template_kwargs_reach_together( self, client: PassthroughClient, resources: ResourceManager ) -> None: model, key = _register(client, resources, HYBRID_REASONING_BACKEND) def ask(chat_template_kwargs: dict[str, bool] | None) -> OutMessage: return _message( unwrap( client.proxy.chat( key, ChatBody( model=model, messages=[ChatMessage(role="user", content=ARITHMETIC_PROMPT)], max_tokens=1024, chat_template_kwargs=chat_template_kwargs, ), ) ) ) control = ask(None) assert control.reasoning_content, ( f"control: {HYBRID_REASONING_BACKEND} returned no reasoning_content by default, " f"so the disable assertion below cannot be trusted: {control}" ) treatment = ask({"enable_thinking": False}) assert not treatment.reasoning_content, ( "chat_template_kwargs={'enable_thinking': False} did not reach Together: " f"reasoning_content is still present: {treatment}" ) assert treatment.content and "43" in treatment.content, f"answer lost: {treatment}" @pytest.mark.covers("llm.chat_completions.together_ai.thinking.nonstream.replayed_reasoning_forwarded") def test_replayed_reasoning_content_reaches_together( self, client: PassthroughClient, resources: ResourceManager ) -> None: model, key = _register(client, resources, REASONING_REPLAY_BACKEND) body: Final = ChatBody( model=model, messages=[ ChatMessage(role="user", content=SECRET_PROMPT), ChatAssistantTurn(content="OK.", reasoning_content=SECRET_REASONING), ChatMessage(role="user", content=SECRET_QUESTION), ], max_tokens=512, ) answers: Final = tuple(_answers_until_secret(client, key, body)) assert any(_carries_secret(answer) for answer in answers), ( f"the replayed reasoning_content never reached Together in {len(answers)} attempts: {answers}" ) @pytest.mark.covers("llm.chat_completions.together_ai.basic.nonstream.cost_logged") def test_cost_header_and_spend_row_match_the_registry_price( self, client: PassthroughClient, resources: ResourceManager, registry: dict[str, CostMapEntry], reasoning_tool_backend: str, ) -> None: model, key = _register(client, resources, reasoning_tool_backend) result = client.proxy.transport.send( "/chat/completions", headers=client.proxy.transport.bearer(key), json=ChatBody( model=model, messages=[ChatMessage(role="user", content=f"{ARITHMETIC_PROMPT} {unique_marker()}")], max_tokens=1024, ), ) require_successful_call(result) response = ChatResponse.model_validate_json(result.body) usage = response.usage assert usage is not None and usage.prompt_tokens and usage.completion_tokens, ( f"response carries no usage, so the cost cannot be real: {result.body[:300]}" ) header_cost = result.response_cost assert header_cost is not None and header_cost > 0, ( f"x-litellm-response-cost header missing or non-positive: {result.headers}" ) price = registry[reasoning_tool_backend] assert price.input_cost_per_token and price.output_cost_per_token cached = (usage.prompt_tokens_details.cached_tokens or 0) if usage.prompt_tokens_details else 0 expected = ( (usage.prompt_tokens - cached) * price.input_cost_per_token + cached * (price.cache_read_input_token_cost or 0.0) + usage.completion_tokens * price.output_cost_per_token ) assert _approx_equal(header_cost, expected), ( f"header cost {header_cost} disagrees with the registry price for " f"{reasoning_tool_backend} at {usage}: expected {expected}" ) def _priced(rows: list[SpendLogRow]) -> bool: return any(row.spend is not None and row.spend > 0 for row in rows) rows = client.proxy.poll_logs_for_key(key, predicate=_priced) priced = [row for row in rows if row.spend is not None and row.spend > 0] assert priced, f"no priced spend row landed for key {key}; got {rows}" row = priced[0] assert row.custom_llm_provider == "together_ai", f"spend row misattributed: {row}" assert row.spend is not None and _approx_equal(row.spend, header_cost), ( f"logged spend {row.spend} disagrees with the x-litellm-response-cost header {header_cost}" ) @pytest.mark.covers("llm.chat_completions.together_ai.thinking.nonstream.effort_none_disables") def test_reasoning_effort_none_reaches_together( self, client: PassthroughClient, resources: ResourceManager ) -> None: model, key = _register(client, resources, HYBRID_REASONING_BACKEND) def ask(reasoning_effort: str | None) -> OutMessage: return _message( unwrap( client.proxy.chat( key, ChatBody( model=model, messages=[ChatMessage(role="user", content=ARITHMETIC_PROMPT)], max_tokens=1024, reasoning_effort=reasoning_effort, ), ) ) ) control = ask(None) assert control.reasoning_content, ( f"control: {HYBRID_REASONING_BACKEND} returned no reasoning_content by default, " f"so the disable assertion below cannot be trusted: {control}" ) treatment = ask("none") assert not treatment.reasoning_content, ( "reasoning_effort='none' never reached Together as {'reasoning': {'enabled': false}}: " f"reasoning_content is still present: {treatment}" ) assert treatment.content and "43" in treatment.content, f"answer lost: {treatment}" @pytest.mark.covers("llm.chat_completions.together_ai.structured_output.nonstream.works") def test_response_format_json_schema_shapes_the_reply( self, client: PassthroughClient, resources: ResourceManager, registry: dict[str, CostMapEntry] ) -> None: backend = _cheapest_together_chat_model(registry, _Needs(response_schema=True)) model, key = _register(client, resources, backend) message = _message( unwrap( client.proxy.chat( key, ChatBody( model=model, messages=[ChatMessage(role="user", content=PERSON_PROMPT)], max_tokens=1024, response_format=PERSON_RESPONSE_FORMAT, ), ) ) ) assert message.content, f"{backend} returned no content: {message}" person = _Person.model_validate_json(message.content) assert person.name, f"schema-shaped reply carries an empty name: {message.content!r}" @pytest.mark.covers("llm.chat_completions.together_ai.prompt_cache_5m.nonstream.cost_logged") def test_cache_read_tokens_bill_at_the_cache_read_rate( self, client: PassthroughClient, resources: ResourceManager, registry: dict[str, CostMapEntry], ) -> None: backend = _cheapest_together_chat_model(registry, _Needs(cache_read_pricing=True)) model, key = _register(client, resources, backend) price = registry[backend] assert price.input_cost_per_token and price.output_cost_per_token cache_read_rate = price.cache_read_input_token_cost assert cache_read_rate, f"{backend} lost its cache-read price mid-test: {price}" results = tuple(_primed_calls_until_cache_hit(client, key, model)) result = results[-1] response = ChatResponse.model_validate_json(result.body) cached = _cached_tokens(response) assert cached > 0, ( f"Together reported no cached tokens on {backend} in {len(results)} primed attempts, " f"so cache-read billing cannot be proven: {response.usage}" ) usage = response.usage assert usage is not None and usage.prompt_tokens and usage.completion_tokens, ( f"response carries no usage, so the cost cannot be real: {result.body[:300]}" ) assert cached <= usage.prompt_tokens, f"cached tokens exceed the prompt: {usage}" header_cost = result.response_cost assert header_cost is not None and header_cost > 0, ( f"x-litellm-response-cost header missing or non-positive: {result.headers}" ) expected = ( (usage.prompt_tokens - cached) * price.input_cost_per_token + cached * cache_read_rate + usage.completion_tokens * price.output_cost_per_token ) discount = cached * (price.input_cost_per_token - cache_read_rate) assert discount > abs(expected) * 1e-2, ( f"the cache-read discount {discount} sits inside the cost tolerance, so this test " f"could not tell discounted from full-price billing: {usage}" ) assert _approx_equal(header_cost, expected), ( f"header cost {header_cost} disagrees with the cache-read-discounted registry price for " f"{backend} at {usage}: expected {expected}" ) assert response.id, f"response carries no id, so its spend row cannot be found: {result.body[:200]}" def _priced(rows: list[SpendLogRow]) -> bool: return any(row.spend is not None for row in rows) rows = client.proxy.poll_logs_for_request_id(response.id, predicate=_priced) row = rows[0] assert row.spend is not None and _approx_equal(row.spend, header_cost), ( f"logged spend {row.spend} disagrees with the x-litellm-response-cost header {header_cost}" ) def _tool_use_blocks(content: list[AnthropicContentBlock] | None) -> list[AnthropicContentBlock]: assert content, f"/v1/messages returned no content blocks: {content}" return [block for block in content if block.type == "tool_use"] def _validated_tool_use_id(block: AnthropicContentBlock) -> str: assert block.name == "get_weather", f"wrong tool called: {block}" assert block.id, f"tool_use block carries no id, so a tool_result cannot answer it: {block}" assert block.input is not None, f"tool_use block carries no input: {block}" args = _WeatherArgs.model_validate(block.input) assert "paris" in args.location.lower(), f"tool input lost the location: {args}" return block.id def _messages_weather_call( client: PassthroughClient, key: str, model: str ) -> tuple[list[AnthropicContentBlock], tuple[str, ...]]: """The blocks /v1/messages returned and the id of every tool_use among them. The count is the model's choice (see _weather_call_ids); what this surface owes us is that each tool_use arrives named and addressable.""" response = unwrap( client.proxy.messages( key, AnthropicMessagesBody( model=model, max_tokens=512, tools=[MESSAGES_WEATHER_TOOL], messages=[ChatMessage(role="user", content=WEATHER_PROMPT)], ), ) ) tool_uses = _tool_use_blocks(response.content) assert tool_uses, f"/v1/messages carried no tool_use block: {response.content}" assert response.content is not None return response.content, tuple(_validated_tool_use_id(block) for block in tool_uses) class TestTogetherMessages: @pytest.mark.covers("llm.messages.together_ai.tool_use.nonstream.works") def test_tool_use_block_is_returned( self, client: PassthroughClient, resources: ResourceManager, reasoning_tool_backend: str ) -> None: model, key = _register(client, resources, reasoning_tool_backend) _messages_weather_call(client, key, model) @pytest.mark.covers("llm.messages.together_ai.multi_turn.nonstream.works") def test_tool_result_round_trip( self, client: PassthroughClient, resources: ResourceManager, reasoning_tool_backend: str ) -> None: model, key = _register(client, resources, reasoning_tool_backend) first_content, tool_use_ids = _messages_weather_call(client, key, model) response = unwrap( client.proxy.messages( key, AnthropicMessagesBody( model=model, max_tokens=512, tools=[MESSAGES_WEATHER_TOOL], messages=[ ChatMessage(role="user", content=WEATHER_PROMPT), AnthropicAssistantTurn(content=first_content), AnthropicToolResultTurn( content=[ AnthropicToolResultBlock(tool_use_id=tool_use_id, content=WEATHER_REPORT) for tool_use_id in tool_use_ids ] ), ], ), ) ) assert response.content, f"/v1/messages returned no content blocks: {response}" text = "".join(block.text or "" for block in response.content if block.type == "text") assert "22" in text, f"the model never saw the tool result: {response.content}" @pytest.mark.covers("llm.messages.together_ai.basic.stream.works") def test_streams_text_deltas( self, client: PassthroughClient, resources: ResourceManager, reasoning_tool_backend: str ) -> None: model, key = _register(client, resources, reasoning_tool_backend) result = client.proxy.messages_stream( key, AnthropicMessagesBody( model=model, max_tokens=512, stream=True, messages=[ChatMessage(role="user", content=COUNTING_PROMPT)], ), ) require_successful_call(result) assert result.is_streaming, f"response was not streamed: {result.headers}" assert not result.stream_error, f"stream errored: {result.stream_error}" events = [_MessagesStreamEvent.model_validate_json(event) for event in result.stream_events] types = [event.type for event in events] text_deltas = [ event.delta.text for event in events if event.type == "content_block_delta" and event.delta is not None and event.delta.text ] assert len(text_deltas) >= 2, f"stream was not incremental: {types}" assert "20" in "".join(text_deltas), f"streamed text lost the answer: {text_deltas}" assert "message_stop" in types, f"stream never reached message_stop: {types}"