File: test_messages.py

package info (click to toggle)
anthropic-sdk-python 0.79.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 4,588 kB
  • sloc: python: 32,024; sh: 186; makefile: 5
file content (199 lines) | stat: -rw-r--r-- 8,775 bytes parent folder | download
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
from typing import List

import pytest
from respx import MockRouter
from pydantic import BaseModel
from inline_snapshot import external, snapshot

from anthropic import Anthropic, AsyncAnthropic, _compat
from anthropic.types.message import Message
from anthropic.types.parsed_message import ParsedMessage

from ..snapshots import make_snapshot_request, make_async_snapshot_request, make_async_stream_snapshot_request


class OrderItem(BaseModel):
    product_name: str
    price: float
    quantity: int


class OrderDetails(BaseModel):
    items: List[OrderItem]
    total: float


@pytest.mark.skipif(_compat.PYDANTIC_V1, reason="structured outputs not supported with pydantic v1")
class TestSyncParse:
    @pytest.mark.respx(base_url="http://127.0.0.1:4010")
    def test_parse_with_pydantic_model(self, client: Anthropic, respx_mock: MockRouter) -> None:
        """Test sync messages.parse() with a Pydantic model output_format."""

        def parse_message(c: Anthropic) -> ParsedMessage[OrderItem]:
            return c.messages.parse(
                model="claude-sonnet-4-5",
                messages=[
                    {
                        "role": "user",
                        "content": "Extract: I want to order 2 Green Tea at $5.50 each",
                    }
                ],
                output_format=OrderItem,
                max_tokens=1024,
            )

        response = make_snapshot_request(
            parse_message,
            content_snapshot=snapshot(
                '{"model":"claude-sonnet-4-5","id":"msg_01ParseTest001","type":"message","role":"assistant","content":[{"type":"text","text":"{\\"product_name\\":\\"Green Tea\\",\\"price\\":5.5,\\"quantity\\":2}"}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":50,"output_tokens":20}}'
            ),
            respx_mock=respx_mock,
            mock_client=client,
            path="/v1/messages",
        )

        assert response.parsed_output is not None
        assert isinstance(response.parsed_output, OrderItem)
        assert response.parsed_output.product_name == "Green Tea"
        assert response.parsed_output.price == 5.5
        assert response.parsed_output.quantity == 2

    @pytest.mark.respx(base_url="http://127.0.0.1:4010")
    def test_parse_with_nested_pydantic_model(self, client: Anthropic, respx_mock: MockRouter) -> None:
        """Test sync messages.parse() with nested Pydantic models."""

        def parse_message(c: Anthropic) -> ParsedMessage[OrderDetails]:
            return c.messages.parse(
                model="claude-sonnet-4-5",
                messages=[
                    {
                        "role": "user",
                        "content": "Extract order: 2 Green Tea at $5.50 and 1 Coffee at $3.00. Total $14.",
                    }
                ],
                output_format=OrderDetails,
                max_tokens=1024,
            )

        response = make_snapshot_request(
            parse_message,
            content_snapshot=snapshot(
                '{"model":"claude-sonnet-4-5","id":"msg_01ParseTest002","type":"message","role":"assistant","content":[{"type":"text","text":"{\\"items\\":[{\\"product_name\\":\\"Green Tea\\",\\"price\\":5.5,\\"quantity\\":2},{\\"product_name\\":\\"Coffee\\",\\"price\\":3.0,\\"quantity\\":1}],\\"total\\":14.0}"}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":60,"output_tokens":40}}'
            ),
            respx_mock=respx_mock,
            mock_client=client,
            path="/v1/messages",
        )

        assert response.parsed_output is not None
        assert isinstance(response.parsed_output, OrderDetails)
        assert len(response.parsed_output.items) == 2
        assert response.parsed_output.items[0].product_name == "Green Tea"
        assert response.parsed_output.total == 14.0


@pytest.mark.skipif(_compat.PYDANTIC_V1, reason="structured outputs not supported with pydantic v1")
class TestAsyncParse:
    async def test_parse_with_pydantic_model(self, async_client: AsyncAnthropic, respx_mock: MockRouter) -> None:
        """Test async messages.parse() with a Pydantic model output_format."""

        async def parse_message(c: AsyncAnthropic) -> ParsedMessage[OrderItem]:
            return await c.messages.parse(
                model="claude-sonnet-4-5",
                messages=[
                    {
                        "role": "user",
                        "content": "Extract: I want to order 2 Green Tea at $5.50 each",
                    }
                ],
                output_format=OrderItem,
                max_tokens=1024,
            )

        response = await make_async_snapshot_request(
            parse_message,
            content_snapshot=snapshot(
                '{"model":"claude-sonnet-4-5","id":"msg_01AsyncParseTest001","type":"message","role":"assistant","content":[{"type":"text","text":"{\\"product_name\\":\\"Green Tea\\",\\"price\\":5.5,\\"quantity\\":2}"}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":50,"output_tokens":20}}'
            ),
            respx_mock=respx_mock,
            mock_client=async_client,
            path="/v1/messages",
        )

        assert response.parsed_output is not None
        assert isinstance(response.parsed_output, OrderItem)
        assert response.parsed_output.product_name == "Green Tea"
        assert response.parsed_output.price == 5.5
        assert response.parsed_output.quantity == 2

    async def test_parse_with_nested_pydantic_model(self, async_client: AsyncAnthropic, respx_mock: MockRouter) -> None:
        """Test async messages.parse() with nested Pydantic models."""

        async def parse_message(c: AsyncAnthropic) -> ParsedMessage[OrderDetails]:
            return await c.messages.parse(
                model="claude-sonnet-4-5",
                messages=[
                    {
                        "role": "user",
                        "content": "Extract order: 2 Green Tea at $5.50 and 1 Coffee at $3.00. Total $14.",
                    }
                ],
                output_format=OrderDetails,
                max_tokens=1024,
            )

        response = await make_async_snapshot_request(
            parse_message,
            content_snapshot=snapshot(
                '{"model":"claude-sonnet-4-5","id":"msg_01AsyncParseTest002","type":"message","role":"assistant","content":[{"type":"text","text":"{\\"items\\":[{\\"product_name\\":\\"Green Tea\\",\\"price\\":5.5,\\"quantity\\":2},{\\"product_name\\":\\"Coffee\\",\\"price\\":3.0,\\"quantity\\":1}],\\"total\\":14.0}"}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":60,"output_tokens":40}}'
            ),
            respx_mock=respx_mock,
            mock_client=async_client,
            path="/v1/messages",
        )

        assert response.parsed_output is not None
        assert isinstance(response.parsed_output, OrderDetails)
        assert len(response.parsed_output.items) == 2
        assert response.parsed_output.items[0].product_name == "Green Tea"
        assert response.parsed_output.total == 14.0


@pytest.mark.skipif(_compat.PYDANTIC_V1, reason="structured outputs not supported with pydantic v1")
class TestAsyncStream:
    async def test_stream_with_raw_schema(self, async_client: AsyncAnthropic, respx_mock: MockRouter) -> None:
        """Test async messages.stream() with raw JSON schema via output_config."""

        async def async_stream_parse(client: AsyncAnthropic) -> Message:
            async with client.messages.stream(
                model="claude-sonnet-4-5",
                messages=[
                    {
                        "role": "user",
                        "content": "Extract order IDs from the following text:\n\nOrder 12345\nOrder 67890",
                    }
                ],
                output_config={
                    "format": {
                        "type": "json_schema",
                        "schema": {
                            "type": "array",
                            "items": {"type": "integer"},
                        },
                    },
                },
                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:b2c4d6e8-f012-4a56-8b90-1234567890ab.json"),
            respx_mock=respx_mock,
            mock_client=async_client,
            path="/v1/messages",
        )

        content_block = response.content[0]
        assert content_block.type == "text"
        assert content_block.text == snapshot("[12345,67890]")