Coverage for tests/models/test_anthropic.py: 98.31%
227 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-28 17:27 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-28 17:27 +0000
1from __future__ import annotations as _annotations
3import json
4import os
5from collections.abc import Sequence
6from dataclasses import dataclass, field
7from datetime import timezone
8from functools import cached_property
9from typing import Any, TypeVar, Union, cast
11import httpx
12import pytest
13from inline_snapshot import snapshot
15from pydantic_ai import Agent, ModelHTTPError, ModelRetry
16from pydantic_ai.messages import (
17 BinaryContent,
18 DocumentUrl,
19 ImageUrl,
20 ModelRequest,
21 ModelResponse,
22 RetryPromptPart,
23 SystemPromptPart,
24 TextPart,
25 ToolCallPart,
26 ToolReturnPart,
27 UserPromptPart,
28)
29from pydantic_ai.result import Usage
30from pydantic_ai.settings import ModelSettings
32from ..conftest import IsDatetime, IsNow, IsStr, TestEnv, raise_if_exception, try_import
33from .mock_async_stream import MockAsyncStream
35with try_import() as imports_successful:
36 from anthropic import NOT_GIVEN, APIStatusError, AsyncAnthropic
37 from anthropic.types import (
38 ContentBlock,
39 InputJSONDelta,
40 Message as AnthropicMessage,
41 MessageDeltaUsage,
42 RawContentBlockDeltaEvent,
43 RawContentBlockStartEvent,
44 RawContentBlockStopEvent,
45 RawMessageDeltaEvent,
46 RawMessageStartEvent,
47 RawMessageStopEvent,
48 RawMessageStreamEvent,
49 TextBlock,
50 ToolUseBlock,
51 Usage as AnthropicUsage,
52 )
53 from anthropic.types.raw_message_delta_event import Delta
55 from pydantic_ai.models.anthropic import AnthropicModel, AnthropicModelSettings
56 from pydantic_ai.providers.anthropic import AnthropicProvider
58 # note: we use Union here so that casting works with Python 3.9
59 MockAnthropicMessage = Union[AnthropicMessage, Exception]
60 MockRawMessageStreamEvent = Union[RawMessageStreamEvent, Exception]
62pytestmark = [
63 pytest.mark.skipif(not imports_successful(), reason='anthropic not installed'),
64 pytest.mark.anyio,
65]
67# Type variable for generic AsyncStream
68T = TypeVar('T')
71def test_init():
72 m = AnthropicModel('claude-3-5-haiku-latest', provider=AnthropicProvider(api_key='foobar'))
73 assert m.client.api_key == 'foobar'
74 assert m.model_name == 'claude-3-5-haiku-latest'
75 assert m.system == 'anthropic'
76 assert m.base_url == 'https://api.anthropic.com'
79@dataclass
80class MockAnthropic:
81 messages_: MockAnthropicMessage | Sequence[MockAnthropicMessage] | None = None
82 stream: Sequence[MockRawMessageStreamEvent] | Sequence[Sequence[MockRawMessageStreamEvent]] | None = None
83 index = 0
84 chat_completion_kwargs: list[dict[str, Any]] = field(default_factory=list)
85 base_url: str | None = None
87 @cached_property
88 def messages(self) -> Any:
89 return type('Messages', (), {'create': self.messages_create})
91 @classmethod
92 def create_mock(cls, messages_: MockAnthropicMessage | Sequence[MockAnthropicMessage]) -> AsyncAnthropic:
93 return cast(AsyncAnthropic, cls(messages_=messages_))
95 @classmethod
96 def create_stream_mock(
97 cls, stream: Sequence[MockRawMessageStreamEvent] | Sequence[Sequence[MockRawMessageStreamEvent]]
98 ) -> AsyncAnthropic:
99 return cast(AsyncAnthropic, cls(stream=stream))
101 async def messages_create(
102 self, *_args: Any, stream: bool = False, **kwargs: Any
103 ) -> AnthropicMessage | MockAsyncStream[MockRawMessageStreamEvent]:
104 self.chat_completion_kwargs.append({k: v for k, v in kwargs.items() if v is not NOT_GIVEN})
106 if stream:
107 assert self.stream is not None, 'you can only use `stream=True` if `stream` is provided'
108 if isinstance(self.stream[0], Sequence): 108 ↛ 111line 108 didn't jump to line 111 because the condition on line 108 was always true
109 response = MockAsyncStream(iter(cast(list[MockRawMessageStreamEvent], self.stream[self.index])))
110 else:
111 response = MockAsyncStream(iter(cast(list[MockRawMessageStreamEvent], self.stream)))
112 else:
113 assert self.messages_ is not None, '`messages` must be provided'
114 if isinstance(self.messages_, Sequence):
115 raise_if_exception(self.messages_[self.index])
116 response = cast(AnthropicMessage, self.messages_[self.index])
117 else:
118 raise_if_exception(self.messages_)
119 response = cast(AnthropicMessage, self.messages_)
120 self.index += 1
121 return response
124def completion_message(content: list[ContentBlock], usage: AnthropicUsage) -> AnthropicMessage:
125 return AnthropicMessage(
126 id='123',
127 content=content,
128 model='claude-3-5-haiku-123',
129 role='assistant',
130 stop_reason='end_turn',
131 type='message',
132 usage=usage,
133 )
136async def test_sync_request_text_response(allow_model_requests: None):
137 c = completion_message([TextBlock(text='world', type='text')], AnthropicUsage(input_tokens=5, output_tokens=10))
138 mock_client = MockAnthropic.create_mock(c)
139 m = AnthropicModel('claude-3-5-haiku-latest', provider=AnthropicProvider(anthropic_client=mock_client))
140 agent = Agent(m)
142 result = await agent.run('hello')
143 assert result.data == 'world'
144 assert result.usage() == snapshot(Usage(requests=1, request_tokens=5, response_tokens=10, total_tokens=15))
146 # reset the index so we get the same response again
147 mock_client.index = 0 # type: ignore
149 result = await agent.run('hello', message_history=result.new_messages())
150 assert result.data == 'world'
151 assert result.usage() == snapshot(Usage(requests=1, request_tokens=5, response_tokens=10, total_tokens=15))
152 assert result.all_messages() == snapshot(
153 [
154 ModelRequest(parts=[UserPromptPart(content='hello', timestamp=IsNow(tz=timezone.utc))]),
155 ModelResponse(
156 parts=[TextPart(content='world')],
157 model_name='claude-3-5-haiku-123',
158 timestamp=IsNow(tz=timezone.utc),
159 ),
160 ModelRequest(parts=[UserPromptPart(content='hello', timestamp=IsNow(tz=timezone.utc))]),
161 ModelResponse(
162 parts=[TextPart(content='world')],
163 model_name='claude-3-5-haiku-123',
164 timestamp=IsNow(tz=timezone.utc),
165 ),
166 ]
167 )
170async def test_async_request_text_response(allow_model_requests: None):
171 c = completion_message(
172 [TextBlock(text='world', type='text')],
173 usage=AnthropicUsage(input_tokens=3, output_tokens=5),
174 )
175 mock_client = MockAnthropic.create_mock(c)
176 m = AnthropicModel('claude-3-5-haiku-latest', provider=AnthropicProvider(anthropic_client=mock_client))
177 agent = Agent(m)
179 result = await agent.run('hello')
180 assert result.data == 'world'
181 assert result.usage() == snapshot(Usage(requests=1, request_tokens=3, response_tokens=5, total_tokens=8))
184async def test_request_structured_response(allow_model_requests: None):
185 c = completion_message(
186 [ToolUseBlock(id='123', input={'response': [1, 2, 3]}, name='final_result', type='tool_use')],
187 usage=AnthropicUsage(input_tokens=3, output_tokens=5),
188 )
189 mock_client = MockAnthropic.create_mock(c)
190 m = AnthropicModel('claude-3-5-haiku-latest', provider=AnthropicProvider(anthropic_client=mock_client))
191 agent = Agent(m, result_type=list[int])
193 result = await agent.run('hello')
194 assert result.data == [1, 2, 3]
195 assert result.all_messages() == snapshot(
196 [
197 ModelRequest(parts=[UserPromptPart(content='hello', timestamp=IsNow(tz=timezone.utc))]),
198 ModelResponse(
199 parts=[
200 ToolCallPart(
201 tool_name='final_result',
202 args={'response': [1, 2, 3]},
203 tool_call_id='123',
204 )
205 ],
206 model_name='claude-3-5-haiku-123',
207 timestamp=IsNow(tz=timezone.utc),
208 ),
209 ModelRequest(
210 parts=[
211 ToolReturnPart(
212 tool_name='final_result',
213 content='Final result processed.',
214 tool_call_id='123',
215 timestamp=IsNow(tz=timezone.utc),
216 )
217 ]
218 ),
219 ]
220 )
223async def test_request_tool_call(allow_model_requests: None):
224 responses = [
225 completion_message(
226 [ToolUseBlock(id='1', input={'loc_name': 'San Francisco'}, name='get_location', type='tool_use')],
227 usage=AnthropicUsage(input_tokens=2, output_tokens=1),
228 ),
229 completion_message(
230 [ToolUseBlock(id='2', input={'loc_name': 'London'}, name='get_location', type='tool_use')],
231 usage=AnthropicUsage(input_tokens=3, output_tokens=2),
232 ),
233 completion_message(
234 [TextBlock(text='final response', type='text')],
235 usage=AnthropicUsage(input_tokens=3, output_tokens=5),
236 ),
237 ]
239 mock_client = MockAnthropic.create_mock(responses)
240 m = AnthropicModel('claude-3-5-haiku-latest', provider=AnthropicProvider(anthropic_client=mock_client))
241 agent = Agent(m, system_prompt='this is the system prompt')
243 @agent.tool_plain
244 async def get_location(loc_name: str) -> str:
245 if loc_name == 'London':
246 return json.dumps({'lat': 51, 'lng': 0})
247 else:
248 raise ModelRetry('Wrong location, please try again')
250 result = await agent.run('hello')
251 assert result.data == 'final response'
252 assert result.all_messages() == snapshot(
253 [
254 ModelRequest(
255 parts=[
256 SystemPromptPart(content='this is the system prompt', timestamp=IsNow(tz=timezone.utc)),
257 UserPromptPart(content='hello', timestamp=IsNow(tz=timezone.utc)),
258 ]
259 ),
260 ModelResponse(
261 parts=[
262 ToolCallPart(
263 tool_name='get_location',
264 args={'loc_name': 'San Francisco'},
265 tool_call_id='1',
266 )
267 ],
268 model_name='claude-3-5-haiku-123',
269 timestamp=IsNow(tz=timezone.utc),
270 ),
271 ModelRequest(
272 parts=[
273 RetryPromptPart(
274 content='Wrong location, please try again',
275 tool_name='get_location',
276 tool_call_id='1',
277 timestamp=IsNow(tz=timezone.utc),
278 )
279 ]
280 ),
281 ModelResponse(
282 parts=[
283 ToolCallPart(
284 tool_name='get_location',
285 args={'loc_name': 'London'},
286 tool_call_id='2',
287 )
288 ],
289 model_name='claude-3-5-haiku-123',
290 timestamp=IsNow(tz=timezone.utc),
291 ),
292 ModelRequest(
293 parts=[
294 ToolReturnPart(
295 tool_name='get_location',
296 content='{"lat": 51, "lng": 0}',
297 tool_call_id='2',
298 timestamp=IsNow(tz=timezone.utc),
299 )
300 ]
301 ),
302 ModelResponse(
303 parts=[TextPart(content='final response')],
304 model_name='claude-3-5-haiku-123',
305 timestamp=IsNow(tz=timezone.utc),
306 ),
307 ]
308 )
311def get_mock_chat_completion_kwargs(async_anthropic: AsyncAnthropic) -> list[dict[str, Any]]:
312 if isinstance(async_anthropic, MockAnthropic):
313 return async_anthropic.chat_completion_kwargs
314 else: # pragma: no cover
315 raise RuntimeError('Not a MockOpenAI instance')
318@pytest.mark.parametrize('parallel_tool_calls', [True, False])
319async def test_parallel_tool_calls(allow_model_requests: None, parallel_tool_calls: bool) -> None:
320 responses = [
321 completion_message(
322 [ToolUseBlock(id='1', input={'loc_name': 'San Francisco'}, name='get_location', type='tool_use')],
323 usage=AnthropicUsage(input_tokens=2, output_tokens=1),
324 ),
325 completion_message(
326 [TextBlock(text='final response', type='text')],
327 usage=AnthropicUsage(input_tokens=3, output_tokens=5),
328 ),
329 ]
331 mock_client = MockAnthropic.create_mock(responses)
332 m = AnthropicModel('claude-3-5-haiku-latest', provider=AnthropicProvider(anthropic_client=mock_client))
333 agent = Agent(m, model_settings=ModelSettings(parallel_tool_calls=parallel_tool_calls))
335 @agent.tool_plain
336 async def get_location(loc_name: str) -> str:
337 if loc_name == 'London': 337 ↛ 338line 337 didn't jump to line 338 because the condition on line 337 was never true
338 return json.dumps({'lat': 51, 'lng': 0})
339 else:
340 raise ModelRetry('Wrong location, please try again')
342 await agent.run('hello')
343 assert get_mock_chat_completion_kwargs(mock_client)[0]['tool_choice']['disable_parallel_tool_use'] == (
344 not parallel_tool_calls
345 )
348@pytest.mark.vcr
349async def test_multiple_parallel_tool_calls(allow_model_requests: None):
350 async def retrieve_entity_info(name: str) -> str:
351 """Get the knowledge about the given entity."""
352 data = {
353 'alice': "alice is bob's wife",
354 'bob': "bob is alice's husband",
355 'charlie': "charlie is alice's son",
356 'daisy': "daisy is bob's daughter and charlie's younger sister",
357 }
358 return data[name.lower()]
360 system_prompt = """
361 Use the `retrieve_entity_info` tool to get information about a specific person.
362 If you need to use `retrieve_entity_info` to get information about multiple people, try
363 to call them in parallel as much as possible.
364 Think step by step and then provide a single most probable concise answer.
365 """
367 # If we don't provide some value for the API key, the anthropic SDK will raise an error.
368 # However, we do want to use the environment variable if present when rewriting VCR cassettes.
369 api_key = os.environ.get('ANTHROPIC_API_KEY', 'mock-value')
370 agent = Agent(
371 AnthropicModel('claude-3-5-haiku-latest', provider=AnthropicProvider(api_key=api_key)),
372 system_prompt=system_prompt,
373 tools=[retrieve_entity_info],
374 )
376 result = await agent.run('Alice, Bob, Charlie and Daisy are a family. Who is the youngest?')
377 assert 'Daisy is the youngest' in result.data
379 all_messages = result.all_messages()
380 first_response = all_messages[1]
381 second_request = all_messages[2]
382 assert first_response.parts == [
383 TextPart(
384 content="I'll retrieve the information about each family member to determine their ages.",
385 part_kind='text',
386 ),
387 ToolCallPart(
388 tool_name='retrieve_entity_info', args={'name': 'Alice'}, tool_call_id=IsStr(), part_kind='tool-call'
389 ),
390 ToolCallPart(
391 tool_name='retrieve_entity_info', args={'name': 'Bob'}, tool_call_id=IsStr(), part_kind='tool-call'
392 ),
393 ToolCallPart(
394 tool_name='retrieve_entity_info', args={'name': 'Charlie'}, tool_call_id=IsStr(), part_kind='tool-call'
395 ),
396 ToolCallPart(
397 tool_name='retrieve_entity_info', args={'name': 'Daisy'}, tool_call_id=IsStr(), part_kind='tool-call'
398 ),
399 ]
400 assert second_request.parts == [
401 ToolReturnPart(
402 tool_name='retrieve_entity_info',
403 content="alice is bob's wife",
404 tool_call_id=IsStr(),
405 timestamp=IsDatetime(),
406 part_kind='tool-return',
407 ),
408 ToolReturnPart(
409 tool_name='retrieve_entity_info',
410 content="bob is alice's husband",
411 tool_call_id=IsStr(),
412 timestamp=IsDatetime(),
413 part_kind='tool-return',
414 ),
415 ToolReturnPart(
416 tool_name='retrieve_entity_info',
417 content="charlie is alice's son",
418 tool_call_id=IsStr(),
419 timestamp=IsDatetime(),
420 part_kind='tool-return',
421 ),
422 ToolReturnPart(
423 tool_name='retrieve_entity_info',
424 content="daisy is bob's daughter and charlie's younger sister",
425 tool_call_id=IsStr(),
426 timestamp=IsDatetime(),
427 part_kind='tool-return',
428 ),
429 ]
431 # Ensure the tool call IDs match between the tool calls and the tool returns
432 tool_call_part_ids = [part.tool_call_id for part in first_response.parts if part.part_kind == 'tool-call']
433 tool_return_part_ids = [part.tool_call_id for part in second_request.parts if part.part_kind == 'tool-return']
434 assert len(set(tool_call_part_ids)) == 4 # ensure they are all unique
435 assert tool_call_part_ids == tool_return_part_ids
438async def test_anthropic_specific_metadata(allow_model_requests: None) -> None:
439 c = completion_message([TextBlock(text='world', type='text')], AnthropicUsage(input_tokens=5, output_tokens=10))
440 mock_client = MockAnthropic.create_mock(c)
441 m = AnthropicModel('claude-3-5-haiku-latest', provider=AnthropicProvider(anthropic_client=mock_client))
442 agent = Agent(m)
444 result = await agent.run('hello', model_settings=AnthropicModelSettings(anthropic_metadata={'user_id': '123'}))
445 assert result.data == 'world'
446 assert get_mock_chat_completion_kwargs(mock_client)[0]['metadata']['user_id'] == '123'
449async def test_stream_structured(allow_model_requests: None):
450 """Test streaming structured responses with Anthropic's API.
452 This test simulates how Anthropic streams tool calls:
453 1. Message start
454 2. Tool block start with initial data
455 3. Tool block delta with additional data
456 4. Tool block stop
457 5. Update usage
458 6. Message stop
459 """
460 stream = [
461 RawMessageStartEvent(
462 type='message_start',
463 message=AnthropicMessage(
464 id='msg_123',
465 model='claude-3-5-haiku-latest',
466 role='assistant',
467 type='message',
468 content=[],
469 stop_reason=None,
470 usage=AnthropicUsage(input_tokens=20, output_tokens=0),
471 ),
472 ),
473 # Start tool block with initial data
474 RawContentBlockStartEvent(
475 type='content_block_start',
476 index=0,
477 content_block=ToolUseBlock(type='tool_use', id='tool_1', name='my_tool', input={'first': 'One'}),
478 ),
479 # Add more data through an incomplete JSON delta
480 RawContentBlockDeltaEvent(
481 type='content_block_delta',
482 index=0,
483 delta=InputJSONDelta(type='input_json_delta', partial_json='{"second":'),
484 ),
485 RawContentBlockDeltaEvent(
486 type='content_block_delta',
487 index=0,
488 delta=InputJSONDelta(type='input_json_delta', partial_json='"Two"}'),
489 ),
490 # Mark tool block as complete
491 RawContentBlockStopEvent(type='content_block_stop', index=0),
492 # Update the top-level message with usage
493 RawMessageDeltaEvent(
494 type='message_delta',
495 delta=Delta(
496 stop_reason='end_turn',
497 ),
498 usage=MessageDeltaUsage(
499 output_tokens=5,
500 ),
501 ),
502 # Mark message as complete
503 RawMessageStopEvent(type='message_stop'),
504 ]
506 done_stream = [
507 RawMessageStartEvent(
508 type='message_start',
509 message=AnthropicMessage(
510 id='msg_123',
511 model='claude-3-5-haiku-latest',
512 role='assistant',
513 type='message',
514 content=[],
515 stop_reason=None,
516 usage=AnthropicUsage(input_tokens=0, output_tokens=0),
517 ),
518 ),
519 # Text block with final data
520 RawContentBlockStartEvent(
521 type='content_block_start',
522 index=0,
523 content_block=TextBlock(type='text', text='FINAL_PAYLOAD'),
524 ),
525 RawContentBlockStopEvent(type='content_block_stop', index=0),
526 RawMessageStopEvent(type='message_stop'),
527 ]
529 mock_client = MockAnthropic.create_stream_mock([stream, done_stream])
530 m = AnthropicModel('claude-3-5-haiku-latest', provider=AnthropicProvider(anthropic_client=mock_client))
531 agent = Agent(m)
533 tool_called = False
535 @agent.tool_plain
536 async def my_tool(first: str, second: str) -> int:
537 nonlocal tool_called
538 tool_called = True
539 return len(first) + len(second)
541 async with agent.run_stream('') as result:
542 assert not result.is_complete
543 chunks = [c async for c in result.stream(debounce_by=None)]
545 # The tool output doesn't echo any content to the stream, so we only get the final payload once when
546 # the block starts and once when it ends.
547 assert chunks == snapshot(
548 [
549 'FINAL_PAYLOAD',
550 'FINAL_PAYLOAD',
551 ]
552 )
553 assert result.is_complete
554 assert result.usage() == snapshot(Usage(requests=2, request_tokens=20, response_tokens=5, total_tokens=25))
555 assert tool_called
558@pytest.mark.vcr()
559async def test_image_url_input(allow_model_requests: None, anthropic_api_key: str):
560 m = AnthropicModel('claude-3-5-haiku-latest', provider=AnthropicProvider(api_key=anthropic_api_key))
561 agent = Agent(m)
563 result = await agent.run(
564 [
565 'What is this vegetable?',
566 ImageUrl(url='https://t3.ftcdn.net/jpg/00/85/79/92/360_F_85799278_0BBGV9OAdQDTLnKwAPBCcg1J7QtiieJY.jpg'),
567 ]
568 )
569 assert result.data == snapshot("""\
570This is a potato. It's a yellow-skinned potato with a somewhat oblong or oval shape. The surface is covered in small eyes or dimples, which is typical of potato skin. The color is a golden-yellow, and the potato appears to be clean and fresh, photographed against a white background.
572Potatoes are root vegetables that are staple foods in many cuisines around the world. They can be prepared in numerous ways such as boiling, baking, roasting, frying, or mashing. This particular potato looks like it could be a Yukon Gold or a similar yellow-fleshed variety.\
573""")
576@pytest.mark.vcr()
577async def test_image_url_input_invalid_mime_type(allow_model_requests: None, anthropic_api_key: str):
578 m = AnthropicModel('claude-3-5-haiku-latest', provider=AnthropicProvider(api_key=anthropic_api_key))
579 agent = Agent(m)
581 result = await agent.run(
582 [
583 'What animal is this?',
584 ImageUrl(
585 url='https://lh3.googleusercontent.com/proxy/YngsuS8jQJysXxeucAgVBcSgIdwZlSQ-HvsNxGjHS0SrUKXI161bNKh6SOcMsNUGsnxoOrS3AYX--MT4T3S3SoCgSD1xKrtBwwItcgexaX_7W-qHo-VupmYgjjzWO-BuORLp9-pj8Kjr'
586 ),
587 ]
588 )
589 assert result.data == snapshot(
590 'This is a Great Horned Owl (Bubo virginianus), a large and powerful owl species. It has distinctive ear tufts (the "horns"), large yellow eyes, and a mottled gray-brown plumage that provides excellent camouflage. In this image, the owl is perched on a branch, surrounded by soft yellow and green vegetation, which creates a beautiful, slightly blurred background that highlights the owl\'s sharp features. Great Horned Owls are known for their adaptability, wide distribution across the Americas, and their status as powerful nocturnal predators.'
591 )
594@pytest.mark.parametrize('media_type', ('audio/wav', 'audio/mpeg'))
595async def test_audio_as_binary_content_input(allow_model_requests: None, media_type: str):
596 c = completion_message([TextBlock(text='world', type='text')], AnthropicUsage(input_tokens=5, output_tokens=10))
597 mock_client = MockAnthropic.create_mock(c)
598 m = AnthropicModel('claude-3-5-haiku-latest', provider=AnthropicProvider(anthropic_client=mock_client))
599 agent = Agent(m)
601 base64_content = b'//uQZ'
603 with pytest.raises(RuntimeError, match='Only images and PDFs are supported for binary content'):
604 await agent.run(['hello', BinaryContent(data=base64_content, media_type=media_type)])
607def test_model_status_error(allow_model_requests: None) -> None:
608 mock_client = MockAnthropic.create_mock(
609 APIStatusError(
610 'test error',
611 response=httpx.Response(status_code=500, request=httpx.Request('POST', 'https://example.com/v1')),
612 body={'error': 'test error'},
613 )
614 )
615 m = AnthropicModel('claude-3-5-sonnet-latest', provider=AnthropicProvider(anthropic_client=mock_client))
616 agent = Agent(m)
617 with pytest.raises(ModelHTTPError) as exc_info:
618 agent.run_sync('hello')
619 assert str(exc_info.value) == snapshot(
620 "status_code: 500, model_name: claude-3-5-sonnet-latest, body: {'error': 'test error'}"
621 )
624@pytest.mark.vcr()
625async def test_document_binary_content_input(
626 allow_model_requests: None, anthropic_api_key: str, document_content: BinaryContent
627):
628 m = AnthropicModel('claude-3-5-sonnet-latest', provider=AnthropicProvider(api_key=anthropic_api_key))
629 agent = Agent(m)
631 result = await agent.run(['What is the main content on this document?', document_content])
632 assert result.data == snapshot(
633 'The document appears to be a simple PDF file with only the text "Dummy PDF file" displayed at the top. It appears to be mostly blank otherwise, likely serving as a template or placeholder document.'
634 )
637@pytest.mark.vcr()
638async def test_document_url_input(allow_model_requests: None, anthropic_api_key: str):
639 m = AnthropicModel('claude-3-5-sonnet-latest', provider=AnthropicProvider(api_key=anthropic_api_key))
640 agent = Agent(m)
642 document_url = DocumentUrl(url='https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf')
644 result = await agent.run(['What is the main content on this document?', document_url])
645 assert result.data == snapshot(
646 'The document appears to be a simple PDF file with only the text "Dummy PDF file" displayed at the top. It seems to be a blank or template document with minimal content.'
647 )
650@pytest.mark.vcr()
651async def test_text_document_url_input(allow_model_requests: None, anthropic_api_key: str):
652 m = AnthropicModel('claude-3-5-sonnet-latest', provider=AnthropicProvider(api_key=anthropic_api_key))
653 agent = Agent(m)
655 text_document_url = DocumentUrl(url='https://example-files.online-convert.com/document/txt/example.txt')
657 result = await agent.run(['What is the main content on this document?', text_document_url])
658 assert result.data == snapshot("""\
659This document is a TXT test file that primarily contains information about the use of placeholder names, specifically focusing on "John Doe" and its variants. The main content explains how these placeholder names are used in legal contexts and popular culture, particularly in English-speaking countries. The text describes:
6611. The various placeholder names used:
662- "John Doe" for males
663- "Jane Doe" or "Jane Roe" for females
664- "Jonnie Doe" and "Janie Doe" for children
665- "Baby Doe" for unknown children
6672. The usage of these names in different English-speaking countries, noting that while common in the US and Canada, they're less used in the UK, where "Joe Bloggs" or "John Smith" are preferred.
6693. How these names are used in legal contexts, forms, and popular culture.
671The document is formatted as a test file with metadata including its purpose, file type, and version. It also includes attribution information indicating the content is from Wikipedia and is licensed under Attribution-ShareAlike 4.0.\
672""")
675def test_init_with_provider():
676 provider = AnthropicProvider(api_key='api-key')
677 model = AnthropicModel('claude-3-opus-latest', provider=provider)
678 assert model.model_name == 'claude-3-opus-latest'
679 assert model.client == provider.client
682def test_init_with_provider_string(env: TestEnv):
683 env.set('ANTHROPIC_API_KEY', 'env-api-key')
684 model = AnthropicModel('claude-3-opus-latest', provider='anthropic')
685 assert model.model_name == 'claude-3-opus-latest'
686 assert model.client is not None