1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
|
import json
import pytest
from respx import MockRouter
from pydantic import BaseModel
from inline_snapshot import external, snapshot
from anthropic import AnthropicError, AsyncAnthropic, _compat
from anthropic._legacy_response import LegacyAPIResponse
from anthropic.types.beta.parsed_beta_message import ParsedBetaMessage
from ..snapshots import make_async_stream_snapshot_request
@pytest.mark.skipif(_compat.PYDANTIC_V1, reason="tool runner not supported with pydantic v1")
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
class TestAsyncMessages:
async def test_stream_with_raw_schema(self, async_client: AsyncAnthropic, respx_mock: MockRouter) -> None:
async def async_stream_parse(client: AsyncAnthropic) -> ParsedBetaMessage[None]:
async with client.beta.messages.stream(
model="claude-sonnet-4-5",
messages=[
{
"role": "user",
"content": "Extract order IDs from the following text:\n\nOrder 12345\nOrder 67890",
}
],
output_format={
"type": "json_schema",
"schema": {
"type": "array",
"items": {"type": "integer"},
},
},
betas=["structured-outputs-2025-12-15"],
max_tokens=1024,
) as stream:
return await stream.get_final_message()
response = await make_async_stream_snapshot_request(
async_stream_parse,
content_snapshot=external("uuid:48aac7c3-f271-47b3-854b-af4ed31e10bb.json"),
respx_mock=respx_mock,
mock_client=async_client,
path="/v1/messages?beta=true",
)
assert response.content[0].text == snapshot("[12345,67890]")
async def test_parse_uses_output_config(self, async_client: AsyncAnthropic, respx_mock: MockRouter) -> None:
class User(BaseModel):
name: str
age: int
async def simple_parse(client: AsyncAnthropic) -> LegacyAPIResponse[ParsedBetaMessage[User]]:
return await client.beta.with_raw_response.messages.parse(
model="claude-sonnet-4-5",
messages=[
{
"role": "user",
"content": "Extract the user's name and age from the following text:\n\nMy name is John Doe and I am 30 years old.",
}
],
output_format=User,
max_tokens=1024,
)
response = await make_async_stream_snapshot_request(
simple_parse,
content_snapshot=external("uuid:044ce19d-3e9c-42d2-90e7-759c978cd94b.json"),
respx_mock=respx_mock,
mock_client=async_client,
path="/v1/messages?beta=true",
)
request_json = json.loads(response.http_request.content)
assert request_json == snapshot(
{
"max_tokens": 1024,
"messages": [
{
"role": "user",
"content": """\
Extract the user's name and age from the following text:
My name is John Doe and I am 30 years old.\
""",
}
],
"model": "claude-sonnet-4-5",
"output_config": {
"format": {
"schema": {
"type": "object",
"title": "User",
"properties": {
"name": {"type": "string", "title": "Name"},
"age": {"type": "integer", "title": "Age"},
},
"additionalProperties": False,
"required": ["name", "age"],
},
"type": "json_schema",
}
},
}
)
async def test_rejects_both_output_format_and_config(self, async_client: AsyncAnthropic) -> None:
class User(BaseModel):
name: str
age: int
with pytest.raises(AnthropicError, match="Both output_format and output_config.format were provided"):
await async_client.beta.messages.parse(
model="claude-sonnet-4-5",
messages=[
{
"role": "user",
"content": "Extract the user's name and age.",
}
],
output_format=User,
output_config={
"format": {
"type": "json_schema",
"schema": {"type": "object"},
}
},
max_tokens=1024,
)
|