instructor
251 строка · 6.0 Кб
1from enum import Enum
2from typing import Literal
3
4import anthropic
5import pytest
6from pydantic import BaseModel, field_validator
7
8import instructor
9from instructor.retry import InstructorRetryException
10
11client = instructor.from_anthropic(
12anthropic.Anthropic(), mode=instructor.Mode.ANTHROPIC_TOOLS
13)
14
15
16def test_simple():
17class User(BaseModel):
18name: str
19age: int
20
21@field_validator("name")
22def name_is_uppercase(cls, v: str):
23assert v.isupper(), "Name must be uppercase, please fix"
24return v
25
26resp = client.messages.create(
27model="claude-3-haiku-20240307",
28max_tokens=1024,
29max_retries=2,
30messages=[
31{
32"role": "user",
33"content": "Extract John is 18 years old.",
34}
35],
36response_model=User,
37) # type: ignore
38
39assert isinstance(resp, User)
40assert resp.name == "JOHN" # due to validation
41assert resp.age == 18
42
43
44def test_nested_type():
45class Address(BaseModel):
46house_number: int
47street_name: str
48
49class User(BaseModel):
50name: str
51age: int
52address: Address
53
54resp = client.messages.create(
55model="claude-3-haiku-20240307",
56max_tokens=1024,
57max_retries=0,
58messages=[
59{
60"role": "user",
61"content": "Extract John is 18 years old and lives at 123 First Avenue.",
62}
63],
64response_model=User,
65) # type: ignore
66
67assert isinstance(resp, User)
68assert resp.name == "John"
69assert resp.age == 18
70
71assert isinstance(resp.address, Address)
72assert resp.address.house_number == 123
73assert resp.address.street_name == "First Avenue"
74
75
76def test_list_str():
77class User(BaseModel):
78name: str
79age: int
80family: list[str]
81
82resp = client.messages.create(
83model="claude-3-haiku-20240307",
84max_tokens=1024,
85max_retries=0,
86messages=[
87{
88"role": "user",
89"content": "Create a user for a model with a name, age, and family members.",
90}
91],
92response_model=User,
93)
94
95assert isinstance(resp, User)
96assert isinstance(resp.family, list)
97for member in resp.family:
98assert isinstance(member, str)
99
100
101@pytest.mark.skip("Just use Literal!")
102def test_enum():
103class Role(str, Enum):
104ADMIN = "admin"
105USER = "user"
106
107class User(BaseModel):
108name: str
109role: Role
110
111resp = client.messages.create(
112model="claude-3-haiku-20240307",
113max_tokens=1024,
114max_retries=1,
115messages=[
116{
117"role": "user",
118"content": "Create a user for a model with a name and role of admin.",
119}
120],
121response_model=User,
122)
123
124assert isinstance(resp, User)
125assert resp.role == Role.ADMIN
126
127
128def test_literal():
129class User(BaseModel):
130name: str
131role: Literal["admin", "user"]
132
133resp = client.messages.create(
134model="claude-3-haiku-20240307",
135max_tokens=1024,
136max_retries=2,
137messages=[
138{
139"role": "user",
140"content": "Create a admin user for a model with a name and role.",
141}
142],
143response_model=User,
144) # type: ignore
145
146assert isinstance(resp, User)
147assert resp.role == "admin"
148
149
150def test_nested_list():
151class Properties(BaseModel):
152key: str
153value: str
154
155class User(BaseModel):
156name: str
157age: int
158properties: list[Properties]
159
160resp = client.messages.create(
161model="claude-3-haiku-20240307",
162max_tokens=1024,
163max_retries=0,
164messages=[
165{
166"role": "user",
167"content": "Create a user for a model with a name, age, and properties.",
168}
169],
170response_model=User,
171)
172
173assert isinstance(resp, User)
174for property in resp.properties:
175assert isinstance(property, Properties)
176
177
178def test_system_messages_allcaps():
179class User(BaseModel):
180name: str
181age: int
182
183resp = client.messages.create(
184model="claude-3-haiku-20240307",
185max_tokens=1024,
186max_retries=0,
187messages=[
188{"role": "system", "content": "EVERYTHING MUST BE IN ALL CAPS"},
189{
190"role": "user",
191"content": "Create a user for a model with a name and age.",
192},
193],
194response_model=User,
195)
196
197assert isinstance(resp, User)
198assert resp.name.isupper()
199
200
201def test_retry_error():
202class User(BaseModel):
203name: str
204
205@field_validator("name")
206def validate_name(cls, _):
207raise ValueError("Never succeed")
208
209try:
210client.messages.create(
211model="claude-3-haiku-20240307",
212max_tokens=1024,
213max_retries=2,
214messages=[
215{
216"role": "user",
217"content": "Extract John is 18 years old",
218},
219],
220response_model=User,
221)
222except InstructorRetryException as e:
223assert e.total_usage.input_tokens > 0 and e.total_usage.output_tokens > 0
224
225
226@pytest.mark.asyncio
227async def test_async_retry_error():
228client = instructor.from_anthropic(anthropic.AsyncAnthropic())
229
230class User(BaseModel):
231name: str
232
233@field_validator("name")
234def validate_name(cls, _):
235raise ValueError("Never succeed")
236
237try:
238await client.messages.create(
239model="claude-3-haiku-20240307",
240max_tokens=1024,
241max_retries=2,
242messages=[
243{
244"role": "user",
245"content": "Extract John is 18 years old",
246},
247],
248response_model=User,
249)
250except InstructorRetryException as e:
251assert e.total_usage.input_tokens > 0 and e.total_usage.output_tokens > 0
252