diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 30c8c4584..3977bf1cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -154,7 +154,7 @@ jobs: - run: mkdir coverage # run tests with just `pydantic-ai-slim` dependencies - - run: uv run --package pydantic-ai-slim coverage run -m pytest + - run: uv run --package pydantic-ai-slim --extra google coverage run -m pytest env: COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}-slim diff --git a/pydantic_ai_slim/pydantic_ai/_agent_graph.py b/pydantic_ai_slim/pydantic_ai/_agent_graph.py index 4515d18bc..32e69edda 100644 --- a/pydantic_ai_slim/pydantic_ai/_agent_graph.py +++ b/pydantic_ai_slim/pydantic_ai/_agent_graph.py @@ -14,6 +14,7 @@ from pydantic_ai._function_schema import _takes_ctx as is_takes_ctx # type: ignore from pydantic_ai._utils import is_async_callable, run_in_executor +from pydantic_ai.builtin_tools import AbstractBuiltinTool from pydantic_graph import BaseNode, Graph, GraphRunContext from pydantic_graph.nodes import End, NodeRunEndT @@ -109,6 +110,7 @@ class GraphAgentDeps(Generic[DepsT, OutputDataT]): history_processors: Sequence[HistoryProcessor[DepsT]] function_tools: dict[str, Tool[DepsT]] = dataclasses.field(repr=False) + builtin_tools: list[AbstractBuiltinTool] = dataclasses.field(repr=False) mcp_servers: Sequence[MCPServer] = dataclasses.field(repr=False) default_retries: int @@ -301,6 +303,7 @@ async def add_mcp_server_tools(server: MCPServer) -> None: return models.ModelRequestParameters( function_tools=function_tool_defs, + builtin_tools=ctx.deps.builtin_tools, output_mode=output_schema.mode, output_tools=output_tools, output_object=output_object, @@ -475,6 +478,10 @@ async def _run_stream() -> AsyncIterator[_messages.HandleResponseEvent]: texts.append(part.content) elif isinstance(part, _messages.ToolCallPart): tool_calls.append(part) + elif isinstance(part, _messages.ServerToolCallPart): + yield _messages.ServerToolCallEvent(part) + elif isinstance(part, _messages.ServerToolReturnPart): + yield _messages.ServerToolResultEvent(part) elif isinstance(part, _messages.ThinkingPart): # We don't need to do anything with thinking parts in this tool-calling node. # We need to handle text parts in case there are no tool calls and/or the desired output comes diff --git a/pydantic_ai_slim/pydantic_ai/_utils.py b/pydantic_ai_slim/pydantic_ai/_utils.py index 4fa5a6d78..1666959b0 100644 --- a/pydantic_ai_slim/pydantic_ai/_utils.py +++ b/pydantic_ai_slim/pydantic_ai/_utils.py @@ -220,7 +220,13 @@ def now_utc() -> datetime: return datetime.now(tz=timezone.utc) -def guard_tool_call_id(t: _messages.ToolCallPart | _messages.ToolReturnPart | _messages.RetryPromptPart) -> str: +def guard_tool_call_id( + t: _messages.ToolCallPart + | _messages.ToolReturnPart + | _messages.RetryPromptPart + | _messages.ServerToolCallPart + | _messages.ServerToolReturnPart, +) -> str: """Type guard that either returns the tool call id or generates a new one if it's None.""" return t.tool_call_id or generate_tool_call_id() diff --git a/pydantic_ai_slim/pydantic_ai/agent.py b/pydantic_ai_slim/pydantic_ai/agent.py index 5342daa1b..35ccb984a 100644 --- a/pydantic_ai_slim/pydantic_ai/agent.py +++ b/pydantic_ai_slim/pydantic_ai/agent.py @@ -15,6 +15,7 @@ from pydantic.json_schema import GenerateJsonSchema from typing_extensions import Literal, Never, Self, TypeIs, TypeVar, deprecated +from pydantic_ai.builtin_tools import AbstractBuiltinTool, WebSearchTool from pydantic_ai.profiles import ModelProfile from pydantic_graph import End, Graph, GraphRun, GraphRunContext from pydantic_graph._utils import get_event_loop @@ -176,6 +177,7 @@ def __init__( retries: int = 1, output_retries: int | None = None, tools: Sequence[Tool[AgentDepsT] | ToolFuncEither[AgentDepsT, ...]] = (), + builtin_tools: Sequence[Literal['web-search'] | AbstractBuiltinTool] = (), prepare_tools: ToolsPrepareFunc[AgentDepsT] | None = None, mcp_servers: Sequence[MCPServer] = (), defer_model_check: bool = False, @@ -206,6 +208,7 @@ def __init__( result_tool_description: str | None = None, result_retries: int | None = None, tools: Sequence[Tool[AgentDepsT] | ToolFuncEither[AgentDepsT, ...]] = (), + builtin_tools: Sequence[Literal['web-search'] | AbstractBuiltinTool] = (), prepare_tools: ToolsPrepareFunc[AgentDepsT] | None = None, mcp_servers: Sequence[MCPServer] = (), defer_model_check: bool = False, @@ -231,6 +234,7 @@ def __init__( retries: int = 1, output_retries: int | None = None, tools: Sequence[Tool[AgentDepsT] | ToolFuncEither[AgentDepsT, ...]] = (), + builtin_tools: Sequence[Literal['web-search'] | AbstractBuiltinTool] = (), prepare_tools: ToolsPrepareFunc[AgentDepsT] | None = None, mcp_servers: Sequence[MCPServer] = (), defer_model_check: bool = False, @@ -261,6 +265,8 @@ def __init__( output_retries: The maximum number of retries to allow for result validation, defaults to `retries`. tools: Tools to register with the agent, you can also register tools via the decorators [`@agent.tool`][pydantic_ai.Agent.tool] and [`@agent.tool_plain`][pydantic_ai.Agent.tool_plain]. + builtin_tools: The builtin tools that the agent will use. This depends on the model, as some models may not + support certain tools. On models that don't support certain tools, the tool will be ignored. prepare_tools: custom method to prepare the tool definition of all tools for each step. This is useful if you want to customize the definition of multiple tools or you want to register a subset of tools for a given step. See [`ToolsPrepareFunc`][pydantic_ai.tools.ToolsPrepareFunc] @@ -358,6 +364,14 @@ def __init__( self._default_retries = retries self._max_result_retries = output_retries if output_retries is not None else retries self._mcp_servers = mcp_servers + self._builtin_tools: list[AbstractBuiltinTool] = [] + + for tool in builtin_tools: + if tool == 'web-search': + self._builtin_tools.append(WebSearchTool()) + else: + self._builtin_tools.append(tool) + self._prepare_tools = prepare_tools self.history_processors = history_processors or [] for tool in tools: @@ -722,6 +736,7 @@ async def get_instructions(run_context: RunContext[AgentDepsT]) -> str | None: output_validators=output_validators, history_processors=self.history_processors, function_tools=run_function_tools, + builtin_tools=self._builtin_tools, mcp_servers=self._mcp_servers, default_retries=self._default_retries, tracer=tracer, diff --git a/pydantic_ai_slim/pydantic_ai/builtin_tools.py b/pydantic_ai_slim/pydantic_ai/builtin_tools.py new file mode 100644 index 000000000..46cc7b628 --- /dev/null +++ b/pydantic_ai_slim/pydantic_ai/builtin_tools.py @@ -0,0 +1,95 @@ +from __future__ import annotations as _annotations + +from abc import ABC +from dataclasses import dataclass +from typing import Any, Literal + +from typing_extensions import TypedDict + +__all__ = ('AbstractBuiltinTool', 'WebSearchTool', 'UserLocation') + + +@dataclass +class AbstractBuiltinTool(ABC): + """A builtin tool that can be used by an agent. + + This class is abstract and cannot be instantiated directly. + + The builtin tools are passed to the model as part of the `ModelRequestParameters`. + """ + + def handle_custom_tool_definition(self, model: str) -> Any: ... + + +@dataclass +class WebSearchTool(AbstractBuiltinTool): + """A builtin tool that allows your agent to search the web for information. + + The parameters that PydanticAI passes depend on the model, as some parameters may not be supported by certain models. + """ + + search_context_size: Literal['low', 'medium', 'high'] = 'medium' + """The `search_context_size` parameter controls how much context is retrieved from the web to help the tool formulate a response. + + Supported by: + * OpenAI + """ + + user_location: UserLocation | None = None + """The `user_location` parameter allows you to localize search results based on a user's location. + + Supported by: + * Anthropic + * OpenAI + """ + + blocked_domains: list[str] | None = None + """If provided, these domains will never appear in results. + + With Anthropic, you can only use one of `blocked_domains` or `allowed_domains`, not both. + + Supported by: + * Anthropic (https://docs.anthropic.com/en/docs/build-with-claude/tool-use/web-search-tool#domain-filtering) + * Groq (https://console.groq.com/docs/agentic-tooling#search-settings) + * MistralAI + """ + + allowed_domains: list[str] | None = None + """If provided, only these domains will be included in results. + + With Anthropic, you can only use one of `blocked_domains` or `allowed_domains`, not both. + + Supported by: + * Anthropic (https://docs.anthropic.com/en/docs/build-with-claude/tool-use/web-search-tool#domain-filtering) + * Groq (https://console.groq.com/docs/agentic-tooling#search-settings) + """ + + max_uses: int | None = None + """If provided, the tool will stop searching the web after the given number of uses. + + Supported by: + * Anthropic + """ + + +class UserLocation(TypedDict, total=False): + """Allows you to localize search results based on a user's location. + + Supported by: + * Anthropic + * OpenAI + """ + + city: str + country: str + region: str + timezone: str + + +class CodeExecutionTool(AbstractBuiltinTool): + """A builtin tool that allows your agent to execute code. + + Supported by: + * Anthropic + * OpenAI + """ diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index f25f6310b..5e325105a 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -407,8 +407,8 @@ def otel_event(self, settings: InstrumentationSettings) -> Event: @dataclass(repr=False) -class ToolReturnPart: - """A tool return message, this encodes the result of running a tool.""" +class BaseToolReturnPart: + """Base class for tool return parts.""" tool_name: str """The name of the "tool" was called.""" @@ -425,9 +425,6 @@ class ToolReturnPart: timestamp: datetime = field(default_factory=_now_utc) """The timestamp, when the tool returned.""" - part_kind: Literal['tool-return'] = 'tool-return' - """Part type identifier, this is available on all parts as a discriminator.""" - def model_response_str(self) -> str: """Return a string representation of the content for the model.""" if isinstance(self.content, str): @@ -454,9 +451,29 @@ def otel_event(self, settings: InstrumentationSettings) -> Event: }, ) + def has_content(self) -> bool: + """Return `True` if the tool return has content.""" + return self.content is not None # pragma: no cover + __repr__ = _utils.dataclasses_no_defaults_repr +@dataclass(repr=False) +class ToolReturnPart(BaseToolReturnPart): + """A tool return message, this encodes the result of running a tool.""" + + part_kind: Literal['tool-return'] = 'tool-return' + """Part type identifier, this is available on all parts as a discriminator.""" + + +@dataclass(repr=False) +class ServerToolReturnPart(BaseToolReturnPart): + """A tool return message from a server tool.""" + + part_kind: Literal['server-tool-return'] = 'server-tool-return' + """Part type identifier, this is available on all parts as a discriminator.""" + + error_details_ta = pydantic.TypeAdapter(list[pydantic_core.ErrorDetails], config=pydantic.ConfigDict(defer_build=True)) @@ -598,7 +615,7 @@ def has_content(self) -> bool: @dataclass(repr=False) -class ToolCallPart: +class BaseToolCallPart: """A tool call from a model.""" tool_name: str @@ -616,9 +633,6 @@ class ToolCallPart: In case the tool call id is not provided by the model, PydanticAI will generate a random one. """ - part_kind: Literal['tool-call'] = 'tool-call' - """Part type identifier, this is available on all parts as a discriminator.""" - def args_as_dict(self) -> dict[str, Any]: """Return the arguments as a Python dictionary. @@ -655,7 +669,29 @@ def has_content(self) -> bool: __repr__ = _utils.dataclasses_no_defaults_repr -ModelResponsePart = Annotated[Union[TextPart, ToolCallPart, ThinkingPart], pydantic.Discriminator('part_kind')] +@dataclass(repr=False) +class ToolCallPart(BaseToolCallPart): + """A tool call from a model.""" + + part_kind: Literal['tool-call'] = 'tool-call' + """Part type identifier, this is available on all parts as a discriminator.""" + + +@dataclass(repr=False) +class ServerToolCallPart(BaseToolCallPart): + """A tool call from a server tool.""" + + model_name: str | None = None + """The name of the model that generated the response.""" + + part_kind: Literal['server-tool-call'] = 'server-tool-call' + """Part type identifier, this is available on all parts as a discriminator.""" + + +ModelResponsePart = Annotated[ + Union[TextPart, ToolCallPart, ServerToolCallPart, ServerToolReturnPart, ThinkingPart], + pydantic.Discriminator('part_kind'), +] """A message part returned by a model.""" @@ -1042,6 +1078,29 @@ def tool_call_id(self) -> str: __repr__ = _utils.dataclasses_no_defaults_repr +@dataclass(repr=False) +class ServerToolCallEvent: + """An event indicating the start to a call to a server tool.""" + + part: ServerToolCallPart + """The server tool call to make.""" + + event_kind: Literal['server_tool_call'] = 'server_tool_call' + """Event type identifier, used as a discriminator.""" + + +@dataclass(repr=False) +class ServerToolResultEvent: + """An event indicating the result of a server tool call.""" + + result: ServerToolReturnPart + """The result of the call to the server tool.""" + + event_kind: Literal['server_tool_result'] = 'server_tool_result' + """Event type identifier, used as a discriminator.""" + + HandleResponseEvent = Annotated[ - Union[FunctionToolCallEvent, FunctionToolResultEvent], pydantic.Discriminator('event_kind') + Union[FunctionToolCallEvent, FunctionToolResultEvent, ServerToolCallEvent, ServerToolResultEvent], + pydantic.Discriminator('event_kind'), ] diff --git a/pydantic_ai_slim/pydantic_ai/models/__init__.py b/pydantic_ai_slim/pydantic_ai/models/__init__.py index 0c476f269..d7c6e0d98 100644 --- a/pydantic_ai_slim/pydantic_ai/models/__init__.py +++ b/pydantic_ai_slim/pydantic_ai/models/__init__.py @@ -18,6 +18,7 @@ import httpx from typing_extensions import Literal, TypeAliasType, TypedDict +from pydantic_ai.builtin_tools import AbstractBuiltinTool from pydantic_ai.profiles import DEFAULT_PROFILE, ModelProfile, ModelProfileSpec from .. import _utils @@ -308,6 +309,7 @@ class ModelRequestParameters: """Configuration for an agent's request to a model, specifically related to tools and output handling.""" function_tools: list[ToolDefinition] = field(default_factory=list) + builtin_tools: list[AbstractBuiltinTool] = field(default_factory=list) output_mode: OutputMode = 'text' output_object: OutputObjectDefinition | None = None diff --git a/pydantic_ai_slim/pydantic_ai/models/anthropic.py b/pydantic_ai_slim/pydantic_ai/models/anthropic.py index 6e0bd443c..510fe54fc 100644 --- a/pydantic_ai_slim/pydantic_ai/models/anthropic.py +++ b/pydantic_ai_slim/pydantic_ai/models/anthropic.py @@ -8,8 +8,16 @@ from datetime import datetime, timezone from typing import Any, Literal, Union, cast, overload +from anthropic.types.beta import ( + BetaCodeExecutionToolResultBlock, + BetaCodeExecutionToolResultBlockParam, + BetaServerToolUseBlockParam, + BetaWebSearchToolResultBlockParam, +) from typing_extensions import assert_never +from pydantic_ai.builtin_tools import CodeExecutionTool, WebSearchTool + from .. import ModelHTTPError, UnexpectedModelBehavior, _utils, usage from .._utils import guard_tool_call_id as _guard_tool_call_id from ..messages import ( @@ -22,6 +30,8 @@ ModelResponsePart, ModelResponseStreamEvent, RetryPromptPart, + ServerToolCallPart, + ServerToolReturnPart, SystemPromptPart, TextPart, ThinkingPart, @@ -40,6 +50,7 @@ from anthropic.types.beta import ( BetaBase64PDFBlockParam, BetaBase64PDFSourceParam, + BetaCodeExecutionTool20250522Param, BetaContentBlock, BetaContentBlockParam, BetaImageBlockParam, @@ -55,6 +66,7 @@ BetaRawMessageStopEvent, BetaRawMessageStreamEvent, BetaRedactedThinkingBlock, + BetaServerToolUseBlock, BetaSignatureDelta, BetaTextBlock, BetaTextBlockParam, @@ -66,9 +78,13 @@ BetaToolChoiceParam, BetaToolParam, BetaToolResultBlockParam, + BetaToolUnionParam, BetaToolUseBlock, BetaToolUseBlockParam, + BetaWebSearchTool20250305Param, + BetaWebSearchToolResultBlock, ) + from anthropic.types.beta.beta_web_search_tool_20250305_param import UserLocation from anthropic.types.model_param import ModelParam except ImportError as _import_error: @@ -216,6 +232,7 @@ async def _messages_create( ) -> BetaMessage | AsyncStream[BetaRawMessageStreamEvent]: # standalone function to make it easier to override tools = self._get_tools(model_request_parameters) + tools += self._get_builtin_tools(model_request_parameters) tool_choice: BetaToolChoiceParam | None if not tools: @@ -234,6 +251,7 @@ async def _messages_create( try: extra_headers = model_settings.get('extra_headers', {}) extra_headers.setdefault('User-Agent', get_user_agent()) + extra_headers.setdefault('anthropic-beta', 'code-execution-2025-05-22') return await self.client.beta.messages.create( max_tokens=model_settings.get('max_tokens', 4096), system=system_prompt or NOT_GIVEN, @@ -262,6 +280,31 @@ def _process_response(self, response: BetaMessage) -> ModelResponse: for item in response.content: if isinstance(item, BetaTextBlock): items.append(TextPart(content=item.text)) + elif isinstance(item, BetaWebSearchToolResultBlock): + items.append( + ServerToolReturnPart( + tool_name=item.type, + content=item.content, + tool_call_id=item.tool_use_id, + ) + ) + elif isinstance(item, BetaServerToolUseBlock): + items.append( + ServerToolCallPart( + model_name='anthropic', + tool_name=item.name, + args=cast(dict[str, Any], item.input), + tool_call_id=item.id, + ) + ) + elif isinstance(item, BetaCodeExecutionToolResultBlock): + items.append( + ServerToolReturnPart( + tool_name=item.type, + content=item.content, + tool_call_id=item.tool_use_id, + ) + ) elif isinstance(item, BetaRedactedThinkingBlock): # pragma: no cover warnings.warn( 'PydanticAI currently does not handle redacted thinking blocks. ' @@ -300,6 +343,24 @@ def _get_tools(self, model_request_parameters: ModelRequestParameters) -> list[B tools += [self._map_tool_definition(r) for r in model_request_parameters.output_tools] return tools + def _get_builtin_tools(self, model_request_parameters: ModelRequestParameters) -> list[BetaToolUnionParam]: + tools: list[BetaToolUnionParam] = [] + for tool in model_request_parameters.builtin_tools: + if isinstance(tool, WebSearchTool): + user_location = UserLocation(type='approximate', **tool.user_location) if tool.user_location else None + tools.append( + BetaWebSearchTool20250305Param( + name='web_search', + type='web_search_20250305', + allowed_domains=tool.allowed_domains, + blocked_domains=tool.blocked_domains, + user_location=user_location, + ) + ) + elif isinstance(tool, CodeExecutionTool): + tools.append(BetaCodeExecutionTool20250522Param(name='code_execution', type='code_execution_20250522')) + return tools + async def _map_message(self, messages: list[ModelMessage]) -> tuple[str, list[BetaMessageParam]]: # noqa: C901 """Just maps a `pydantic_ai.Message` to a `anthropic.types.MessageParam`.""" system_prompt_parts: list[str] = [] @@ -336,11 +397,26 @@ async def _map_message(self, messages: list[ModelMessage]) -> tuple[str, list[Be if len(user_content_params) > 0: anthropic_messages.append(BetaMessageParam(role='user', content=user_content_params)) elif isinstance(m, ModelResponse): - assistant_content_params: list[BetaTextBlockParam | BetaToolUseBlockParam | BetaThinkingBlockParam] = [] + assistant_content_params: list[ + BetaTextBlockParam + | BetaToolUseBlockParam + | BetaServerToolUseBlockParam + | BetaWebSearchToolResultBlockParam + | BetaCodeExecutionToolResultBlockParam + | BetaThinkingBlockParam + ] = [] for response_part in m.parts: if isinstance(response_part, TextPart): - if response_part.content: # Only add non-empty text + if response_part.content: assistant_content_params.append(BetaTextBlockParam(text=response_part.content, type='text')) + elif isinstance(response_part, ToolCallPart): + tool_use_block_param = BetaToolUseBlockParam( + id=_guard_tool_call_id(t=response_part), + type='tool_use', + name=response_part.tool_name, + input=response_part.args_as_dict(), + ) + assistant_content_params.append(tool_use_block_param) elif isinstance(response_part, ThinkingPart): # NOTE: We only send thinking part back for Anthropic, otherwise they raise an error. if response_part.signature is not None: # pragma: no branch @@ -349,14 +425,29 @@ async def _map_message(self, messages: list[ModelMessage]) -> tuple[str, list[Be thinking=response_part.content, signature=response_part.signature, type='thinking' ) ) - else: - tool_use_block_param = BetaToolUseBlockParam( + elif isinstance(response_part, ServerToolCallPart): + server_tool_use_block_param = BetaServerToolUseBlockParam( id=_guard_tool_call_id(t=response_part), - type='tool_use', - name=response_part.tool_name, + type='server_tool_use', + name=cast(Literal['web_search', 'code_execution'], response_part.tool_name), input=response_part.args_as_dict(), ) - assistant_content_params.append(tool_use_block_param) + assistant_content_params.append(server_tool_use_block_param) + elif isinstance(response_part, ServerToolReturnPart): + tool_use_id = _guard_tool_call_id(t=response_part) + if response_part.tool_name == 'web_search_tool_result': + server_tool_result_block_param = BetaWebSearchToolResultBlockParam( + tool_use_id=tool_use_id, type=response_part.tool_name, content=response_part.content + ) + elif response_part.tool_name == 'code_execution_tool_result': + server_tool_result_block_param = BetaCodeExecutionToolResultBlockParam( + tool_use_id=tool_use_id, type=response_part.tool_name, content=response_part.content + ) + else: + raise ValueError(f'Unsupported tool name: {response_part.tool_name}') + assistant_content_params.append(server_tool_result_block_param) + else: + assert_never(response_part) if len(assistant_content_params) > 0: anthropic_messages.append(BetaMessageParam(role='assistant', content=assistant_content_params)) else: diff --git a/pydantic_ai_slim/pydantic_ai/models/cohere.py b/pydantic_ai_slim/pydantic_ai/models/cohere.py index b51bed1b3..dc3a3226c 100644 --- a/pydantic_ai_slim/pydantic_ai/models/cohere.py +++ b/pydantic_ai_slim/pydantic_ai/models/cohere.py @@ -16,6 +16,8 @@ ModelResponse, ModelResponsePart, RetryPromptPart, + ServerToolCallPart, + ServerToolReturnPart, SystemPromptPart, TextPart, ThinkingPart, @@ -221,6 +223,14 @@ def _map_messages(self, messages: list[ModelMessage]) -> list[ChatMessageV2]: pass elif isinstance(item, ToolCallPart): tool_calls.append(self._map_tool_call(item)) + elif isinstance(item, ServerToolCallPart): + # ServerToolCallPart represents a tool call from a remote server + # Never returned from cohere + pass + elif isinstance(item, ServerToolReturnPart): + # ServerToolReturnPart represents a tool return from a remote server + # Never returned from cohere + pass else: assert_never(item) message_param = AssistantChatMessageV2(role='assistant') @@ -242,7 +252,7 @@ def _get_tools(self, model_request_parameters: ModelRequestParameters) -> list[T return tools @staticmethod - def _map_tool_call(t: ToolCallPart) -> ToolCallV2: + def _map_tool_call(t: ToolCallPart | ServerToolCallPart) -> ToolCallV2: return ToolCallV2( id=_guard_tool_call_id(t=t), type='function', diff --git a/pydantic_ai_slim/pydantic_ai/models/function.py b/pydantic_ai_slim/pydantic_ai/models/function.py index d3a5b8fbd..d94c34269 100644 --- a/pydantic_ai_slim/pydantic_ai/models/function.py +++ b/pydantic_ai_slim/pydantic_ai/models/function.py @@ -24,6 +24,8 @@ ModelResponse, ModelResponseStreamEvent, RetryPromptPart, + ServerToolCallPart, + ServerToolReturnPart, SystemPromptPart, TextPart, ThinkingPart, @@ -294,6 +296,11 @@ def _estimate_usage(messages: Iterable[ModelMessage]) -> usage.Usage: elif isinstance(part, ToolCallPart): call = part response_tokens += 1 + _estimate_string_tokens(call.args_as_json_str()) + elif isinstance(part, ServerToolCallPart): + call = part + response_tokens += 1 + _estimate_string_tokens(call.args_as_json_str()) + elif isinstance(part, ServerToolReturnPart): + response_tokens += _estimate_string_tokens(part.model_response_str()) else: assert_never(part) else: diff --git a/pydantic_ai_slim/pydantic_ai/models/gemini.py b/pydantic_ai_slim/pydantic_ai/models/gemini.py index 64008622b..baa91ca1a 100644 --- a/pydantic_ai_slim/pydantic_ai/models/gemini.py +++ b/pydantic_ai_slim/pydantic_ai/models/gemini.py @@ -27,6 +27,8 @@ ModelResponsePart, ModelResponseStreamEvent, RetryPromptPart, + ServerToolCallPart, + ServerToolReturnPart, SystemPromptPart, TextPart, ThinkingPart, @@ -620,6 +622,14 @@ def _content_model_response(m: ModelResponse) -> _GeminiContent: elif isinstance(item, TextPart): if item.content: parts.append(_GeminiTextPart(text=item.content)) + elif isinstance(item, ServerToolCallPart): + # Handle ServerToolCallPart the same as ToolCallPart + # Never returned from gemini + pass + elif isinstance(item, ServerToolReturnPart): + # Convert ServerToolReturnPart to a function response part + # Never returned from gemini + pass else: assert_never(item) return _GeminiContent(role='model', parts=parts) diff --git a/pydantic_ai_slim/pydantic_ai/models/google.py b/pydantic_ai_slim/pydantic_ai/models/google.py index 094b4b050..e7ab6be0f 100644 --- a/pydantic_ai_slim/pydantic_ai/models/google.py +++ b/pydantic_ai_slim/pydantic_ai/models/google.py @@ -12,6 +12,7 @@ from .. import UnexpectedModelBehavior, _utils, usage from .._output import OutputObjectDefinition +from ..builtin_tools import CodeExecutionTool, WebSearchTool from ..exceptions import UserError from ..messages import ( BinaryContent, @@ -22,6 +23,8 @@ ModelResponsePart, ModelResponseStreamEvent, RetryPromptPart, + ServerToolCallPart, + ServerToolReturnPart, SystemPromptPart, TextPart, ThinkingPart, @@ -54,11 +57,13 @@ FunctionDeclarationDict, GenerateContentConfigDict, GenerateContentResponse, + GoogleSearchDict, HttpOptionsDict, Part, PartDict, SafetySettingDict, ThinkingConfigDict, + ToolCodeExecutionDict, ToolConfigDict, ToolDict, ToolListUnionDict, @@ -212,6 +217,13 @@ def _get_tools(self, model_request_parameters: ModelRequestParameters) -> list[T ToolDict(function_declarations=[_function_declaration_from_tool(t)]) for t in model_request_parameters.output_tools ] + for tool in model_request_parameters.builtin_tools: + if isinstance(tool, WebSearchTool): + tools.append(ToolDict(google_search=GoogleSearchDict())) + elif isinstance(tool, CodeExecutionTool): + tools.append(ToolDict(code_execution=ToolCodeExecutionDict())) + else: + raise UserError(f'Unsupported builtin tool: {tool}') return tools or None def _get_tool_config( @@ -483,6 +495,12 @@ def _content_model_response(m: ModelResponse) -> ContentDict: # please open an issue. The below code is the code to send thinking to the provider. # parts.append({'text': item.content, 'thought': True}) pass + elif isinstance(item, ServerToolCallPart): + # Never returned from google + pass + elif isinstance(item, ServerToolReturnPart): + # Never returned from google + pass else: assert_never(item) return ContentDict(role='model', parts=parts) @@ -497,7 +515,18 @@ def _process_response_from_parts( ) -> ModelResponse: items: list[ModelResponsePart] = [] for part in parts: - if part.text is not None: + if part.executable_code is not None: + items.append(ServerToolCallPart(args=part.executable_code.model_dump(), tool_name='code_execution')) + elif part.code_execution_result is not None: + # TODO(Marcelo): Is the idea to generate the tool_call_id on the `executable_code`, and then pass it here? + items.append( + ServerToolReturnPart( + tool_name='code_execution', + content=part.code_execution_result.output, + tool_call_id="It doesn't have.", + ) + ) + elif part.text is not None: if part.thought: items.append(ThinkingPart(content=part.text)) else: diff --git a/pydantic_ai_slim/pydantic_ai/models/groq.py b/pydantic_ai_slim/pydantic_ai/models/groq.py index acb5180c5..671623a8d 100644 --- a/pydantic_ai_slim/pydantic_ai/models/groq.py +++ b/pydantic_ai_slim/pydantic_ai/models/groq.py @@ -12,7 +12,7 @@ from pydantic_ai._thinking_part import split_content_into_text_and_thinking from .. import ModelHTTPError, UnexpectedModelBehavior, _utils, usage -from .._utils import guard_tool_call_id as _guard_tool_call_id, number_to_datetime +from .._utils import generate_tool_call_id, guard_tool_call_id as _guard_tool_call_id, number_to_datetime from ..messages import ( BinaryContent, DocumentUrl, @@ -23,6 +23,8 @@ ModelResponsePart, ModelResponseStreamEvent, RetryPromptPart, + ServerToolCallPart, + ServerToolReturnPart, SystemPromptPart, TextPart, ThinkingPart, @@ -223,7 +225,7 @@ async def _completions_create( extra_headers = model_settings.get('extra_headers', {}) extra_headers.setdefault('User-Agent', get_user_agent()) return await self.client.chat.completions.create( - model=str(self._model_name), + model=self._model_name, messages=groq_messages, n=1, parallel_tool_calls=model_settings.get('parallel_tool_calls', NOT_GIVEN), @@ -253,6 +255,15 @@ def _process_response(self, response: chat.ChatCompletion) -> ModelResponse: timestamp = number_to_datetime(response.created) choice = response.choices[0] items: list[ModelResponsePart] = [] + if choice.message.executed_tools: + for tool in choice.message.executed_tools: + tool_call_id = generate_tool_call_id() + items.append( + ServerToolCallPart( + tool_name=tool.type, args=tool.arguments, model_name='groq', tool_call_id=tool_call_id + ) + ) + items.append(ServerToolReturnPart(tool_name=tool.type, content=tool.output, tool_call_id=tool_call_id)) # NOTE: The `reasoning` field is only present if `groq_reasoning_format` is set to `parsed`. if choice.message.reasoning is not None: items.append(ThinkingPart(content=choice.message.reasoning)) @@ -304,6 +315,14 @@ def _map_messages(self, messages: list[ModelMessage]) -> list[chat.ChatCompletio elif isinstance(item, ThinkingPart): # Skip thinking parts when mapping to Groq messages continue + elif isinstance(item, ServerToolCallPart): + # ServerToolCallPart is handled separately in server-side tools + # Never returned from groq + pass + elif isinstance(item, ServerToolReturnPart): + # ServerToolReturnPart is handled separately in server-side tools + # Never returned from groq + pass else: assert_never(item) message_param = chat.ChatCompletionAssistantMessageParam(role='assistant') diff --git a/pydantic_ai_slim/pydantic_ai/models/mistral.py b/pydantic_ai_slim/pydantic_ai/models/mistral.py index 2a1452a0c..f1087fff0 100644 --- a/pydantic_ai_slim/pydantic_ai/models/mistral.py +++ b/pydantic_ai_slim/pydantic_ai/models/mistral.py @@ -25,6 +25,8 @@ ModelResponsePart, ModelResponseStreamEvent, RetryPromptPart, + ServerToolCallPart, + ServerToolReturnPart, SystemPromptPart, TextPart, ThinkingPart, @@ -373,7 +375,7 @@ def _map_mistral_to_pydantic_tool_call(tool_call: MistralToolCall) -> ToolCallPa return ToolCallPart(func_call.name, func_call.arguments, tool_call_id) @staticmethod - def _map_tool_call(t: ToolCallPart) -> MistralToolCall: + def _map_tool_call(t: ToolCallPart | ServerToolCallPart) -> MistralToolCall: """Maps a pydantic-ai ToolCall to a MistralToolCall.""" return MistralToolCall( id=_utils.guard_tool_call_id(t=t), @@ -497,6 +499,14 @@ def _map_messages(self, messages: list[ModelMessage]) -> list[MistralMessages]: pass elif isinstance(part, ToolCallPart): tool_calls.append(self._map_tool_call(part)) + elif isinstance(part, ServerToolCallPart): + # Handle ServerToolCallPart the same as ToolCallPart + # Never returned from mistral + pass + elif isinstance(part, ServerToolReturnPart): + # For now, we'll add ServerToolReturnPart as text content + # Never returned from mistral + pass else: assert_never(part) mistral_messages.append(MistralAssistantMessage(content=content_chunks, tool_calls=tool_calls)) diff --git a/pydantic_ai_slim/pydantic_ai/models/openai.py b/pydantic_ai_slim/pydantic_ai/models/openai.py index 37178ccb5..26b6bb052 100644 --- a/pydantic_ai_slim/pydantic_ai/models/openai.py +++ b/pydantic_ai_slim/pydantic_ai/models/openai.py @@ -10,13 +10,11 @@ from typing_extensions import assert_never -from pydantic_ai._thinking_part import split_content_into_text_and_thinking -from pydantic_ai.profiles.openai import OpenAIModelProfile -from pydantic_ai.providers import Provider, infer_provider - from .. import ModelHTTPError, UnexpectedModelBehavior, _utils, usage from .._output import DEFAULT_OUTPUT_TOOL_NAME, OutputObjectDefinition +from .._thinking_part import split_content_into_text_and_thinking from .._utils import guard_tool_call_id as _guard_tool_call_id, number_to_datetime +from ..builtin_tools import WebSearchTool from ..messages import ( AudioUrl, BinaryContent, @@ -28,6 +26,8 @@ ModelResponsePart, ModelResponseStreamEvent, RetryPromptPart, + ServerToolCallPart, + ServerToolReturnPart, SystemPromptPart, TextPart, ThinkingPart, @@ -37,16 +37,11 @@ VideoUrl, ) from ..profiles import ModelProfileSpec +from ..profiles.openai import OpenAIModelProfile +from ..providers import Provider, infer_provider from ..settings import ModelSettings from ..tools import ToolDefinition -from . import ( - Model, - ModelRequestParameters, - StreamedResponse, - check_allow_model_requests, - download_item, - get_user_agent, -) +from . import Model, ModelRequestParameters, StreamedResponse, check_allow_model_requests, download_item, get_user_agent try: from openai import NOT_GIVEN, APIStatusError, AsyncOpenAI, AsyncStream, NotGiven @@ -62,6 +57,11 @@ from openai.types.chat.chat_completion_content_part_input_audio_param import InputAudio from openai.types.chat.chat_completion_content_part_param import File, FileFile from openai.types.chat.chat_completion_prediction_content_param import ChatCompletionPredictionContentParam + from openai.types.chat.completion_create_params import ( + WebSearchOptions, + WebSearchOptionsUserLocation, + WebSearchOptionsUserLocationApproximate, + ) from openai.types.responses import ComputerToolParam, FileSearchToolParam, WebSearchToolParam from openai.types.responses.response_input_param import FunctionCallOutput, Message from openai.types.shared import ReasoningEffort @@ -285,6 +285,9 @@ async def _completions_create( model_request_parameters: ModelRequestParameters, ) -> chat.ChatCompletion | AsyncStream[ChatCompletionChunk]: tools = self._get_tools(model_request_parameters) + web_search_options = self._get_web_search_options(model_request_parameters) + + # standalone function to make it easier to override if not tools: tool_choice: Literal['none', 'required', 'auto'] | None = None elif not model_request_parameters.allow_text_output: @@ -328,6 +331,7 @@ async def _completions_create( seed=model_settings.get('seed', NOT_GIVEN), reasoning_effort=model_settings.get('openai_reasoning_effort', NOT_GIVEN), user=model_settings.get('openai_user', NOT_GIVEN), + web_search_options=web_search_options or NOT_GIVEN, service_tier=model_settings.get('openai_service_tier', NOT_GIVEN), prediction=model_settings.get('openai_prediction', NOT_GIVEN), temperature=sampling_settings.get('temperature', NOT_GIVEN), @@ -410,6 +414,19 @@ def _get_tools(self, model_request_parameters: ModelRequestParameters) -> list[c tools += [self._map_tool_definition(r) for r in model_request_parameters.output_tools] return tools + def _get_web_search_options(self, model_request_parameters: ModelRequestParameters) -> WebSearchOptions | None: + for tool in model_request_parameters.builtin_tools: + if isinstance(tool, WebSearchTool): + if tool.user_location: + return WebSearchOptions( + search_context_size=tool.search_context_size, + user_location=WebSearchOptionsUserLocation( + type='approximate', + approximate=WebSearchOptionsUserLocationApproximate(**tool.user_location), + ), + ) + return WebSearchOptions(search_context_size=tool.search_context_size) + async def _map_messages(self, messages: list[ModelMessage]) -> list[chat.ChatCompletionMessageParam]: """Just maps a `pydantic_ai.Message` to a `openai.types.ChatCompletionMessageParam`.""" openai_messages: list[chat.ChatCompletionMessageParam] = [] @@ -430,6 +447,9 @@ async def _map_messages(self, messages: list[ModelMessage]) -> list[chat.ChatCom pass elif isinstance(item, ToolCallPart): tool_calls.append(self._map_tool_call(item)) + # OpenAI doesn't return server tools calls. + elif isinstance(item, (ServerToolCallPart, ServerToolReturnPart)): + continue else: assert_never(item) message_param = chat.ChatCompletionAssistantMessageParam(role='assistant') @@ -717,6 +737,7 @@ async def _responses_create( ) -> responses.Response | AsyncStream[responses.ResponseStreamEvent]: tools = self._get_tools(model_request_parameters) tools = list(model_settings.get('openai_builtin_tools', [])) + tools + tools = self._get_builtin_tools(model_request_parameters) + tools if not tools: tool_choice: Literal['none', 'required', 'auto'] | None = None @@ -803,6 +824,20 @@ def _get_tools(self, model_request_parameters: ModelRequestParameters) -> list[r tools += [self._map_tool_definition(r) for r in model_request_parameters.output_tools] return tools + def _get_builtin_tools(self, model_request_parameters: ModelRequestParameters) -> list[responses.ToolParam]: + tools: list[responses.ToolParam] = [] + for tool in model_request_parameters.builtin_tools: + if isinstance(tool, WebSearchTool): + web_search_tool = responses.WebSearchToolParam( + type='web_search_preview', search_context_size=tool.search_context_size + ) + if tool.user_location: + web_search_tool['user_location'] = responses.web_search_tool_param.UserLocation( + type='approximate', **tool.user_location + ) + tools.append(web_search_tool) + return tools + def _map_tool_definition(self, f: ToolDefinition) -> responses.FunctionToolParam: return { 'name': f.name, @@ -857,6 +892,9 @@ async def _map_messages( openai_messages.append(responses.EasyInputMessageParam(role='assistant', content=item.content)) elif isinstance(item, ToolCallPart): openai_messages.append(self._map_tool_call(item)) + # OpenAI doesn't return server tools calls. + elif isinstance(item, (ServerToolCallPart, ServerToolReturnPart)): + continue elif isinstance(item, ThinkingPart): # NOTE: We don't send ThinkingPart to the providers yet. If you are unsatisfied with this, # please open an issue. The below code is the code to send thinking to the provider. diff --git a/pydantic_ai_slim/pydantic_ai/models/test.py b/pydantic_ai_slim/pydantic_ai/models/test.py index 87a0c79c0..b1b5122f1 100644 --- a/pydantic_ai_slim/pydantic_ai/models/test.py +++ b/pydantic_ai_slim/pydantic_ai/models/test.py @@ -19,6 +19,8 @@ ModelResponsePart, ModelResponseStreamEvent, RetryPromptPart, + ServerToolCallPart, + ServerToolReturnPart, TextPart, ThinkingPart, ToolCallPart, @@ -256,6 +258,9 @@ async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]: yield self._parts_manager.handle_tool_call_part( vendor_part_id=i, tool_name=part.tool_name, args=part.args, tool_call_id=part.tool_call_id ) + elif isinstance(part, (ServerToolCallPart, ServerToolReturnPart)): # pragma: no cover + # NOTE: These parts are not generated by TestModel, but we need to handle them for type checking + assert False, f'Unexpected part type in TestModel: {type(part).__name__}' elif isinstance(part, ThinkingPart): # pragma: no cover # NOTE: There's no way to reach this part of the code, since we don't generate ThinkingPart on TestModel. assert False, "This should be unreachable — we don't generate ThinkingPart on TestModel." diff --git a/pydantic_ai_slim/pyproject.toml b/pydantic_ai_slim/pyproject.toml index a04bd07c5..ed4b8f160 100644 --- a/pydantic_ai_slim/pyproject.toml +++ b/pydantic_ai_slim/pyproject.toml @@ -66,7 +66,7 @@ cohere = ["cohere>=5.13.11; platform_system != 'Emscripten'"] vertexai = ["google-auth>=2.36.0", "requests>=2.32.2"] google = ["google-genai>=1.15.0"] anthropic = ["anthropic>=0.52.0"] -groq = ["groq>=0.19.0"] +groq = ["groq>=0.25.0"] mistral = ["mistralai>=1.2.5"] bedrock = ["boto3>=1.37.24"] # Tools diff --git a/pyproject.toml b/pyproject.toml index fb21096fa..406128616 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -265,4 +265,4 @@ skip = '.git*,*.svg,*.lock,*.css,*.yaml' check-hidden = true # Ignore "formatting" like **L**anguage ignore-regex = '\*\*[A-Z]\*\*[a-z]+\b' -ignore-words-list = 'asend' +ignore-words-list = 'asend,Hemishpere,synchonizing' diff --git a/tests/models/cassettes/test_anthropic/test_anthropic_code_execution_tool.yaml b/tests/models/cassettes/test_anthropic/test_anthropic_code_execution_tool.yaml new file mode 100644 index 000000000..76f19dc00 --- /dev/null +++ b/tests/models/cassettes/test_anthropic/test_anthropic_code_execution_tool.yaml @@ -0,0 +1,86 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '250' + content-type: + - application/json + host: + - api.anthropic.com + method: POST + parsed_body: + max_tokens: 1024 + messages: + - content: + - text: How much is 3 * 12390? + type: text + role: user + model: claude-sonnet-4-0 + stream: false + tool_choice: + type: auto + tools: + - name: code_execution + type: code_execution_20250522 + uri: https://api.anthropic.com/v1/messages?beta=true + response: + headers: + connection: + - keep-alive + content-length: + - '927' + content-type: + - application/json + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + parsed_body: + container: + expires_at: '2025-05-26T13:30:45.703429+00:00' + id: container_011CPW9LpfbF8dmXMvVNCiQJ + content: + - text: I'll calculate 3 * 12390 for you. + type: text + - id: srvtoolu_01CPfaeVC7ju4VsdzxjSLDrY + input: + code: |- + result = 3 * 12390 + print(f"3 * 12390 = {result}") + name: code_execution + type: server_tool_use + - content: + content: [] + return_code: 0 + stderr: '' + stdout: | + 3 * 12390 = 37170 + type: code_execution_result + tool_use_id: srvtoolu_01CPfaeVC7ju4VsdzxjSLDrY + type: code_execution_tool_result + - text: The answer is **37,170**. + type: text + id: msg_015H6Emn2T8vZhE52mU2jF1U + model: claude-sonnet-4-20250514 + role: assistant + stop_reason: end_turn + stop_sequence: null + type: message + usage: + cache_creation_input_tokens: 0 + cache_read_input_tokens: 0 + input_tokens: 1630 + output_tokens: 105 + server_tool_use: + web_search_requests: 0 + service_tier: standard + status: + code: 200 + message: OK +version: 1 diff --git a/tests/models/cassettes/test_anthropic/test_anthropic_server_tool_pass_history_to_another_provider.yaml b/tests/models/cassettes/test_anthropic/test_anthropic_server_tool_pass_history_to_another_provider.yaml new file mode 100644 index 000000000..a73ea3aa4 --- /dev/null +++ b/tests/models/cassettes/test_anthropic/test_anthropic_server_tool_pass_history_to_another_provider.yaml @@ -0,0 +1,280 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '312' + content-type: + - application/json + host: + - api.anthropic.com + method: POST + parsed_body: + max_tokens: 1024 + messages: + - content: + - text: What day is today? + type: text + role: user + model: claude-3-5-sonnet-latest + stream: false + tool_choice: + type: auto + tools: + - allowed_domains: null + blocked_domains: null + name: web_search + type: web_search_20250305 + user_location: null + uri: https://api.anthropic.com/v1/messages?beta=true + response: + headers: + connection: + - keep-alive + content-length: + - '15304' + content-type: + - application/json + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + parsed_body: + content: + - text: Let me search for today's date. + type: text + - id: srvtoolu_01BJh8n7va96puUF3hhNnYnY + input: + query: current date today May 26 2025 + name: web_search + type: server_tool_use + - content: + - encrypted_content: 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 + page_age: null + title: May 2025 Calendar + type: web_search_result + url: https://www.calendar-365.com/calendar/2025/May.html + - encrypted_content: EuwCCioIAxgCIiQ0NGFlNjc2Yy05NThmLTRkNjgtOTEwOC1lYWU5ZGU3YjM2NmISDNOs+T2nScTnsxVJZRoM3g9Mm5RlKN0sgiPBIjC1PJPZRfoUgRpmpfVZcF8sBTMUr6Lu9BK9xOxTQ8nMMBGjC7kFOrwJZ3ENgXLhRzcq7wF51cgLQ+Pb0MiXBGozKXa1r7DbogMoIacrDz92bvzbwXbo4mTQMipnow47//PYVelJXipxVAA6ISswEiYPGXyCjqSy6QEIde1TOc8nvMpaM0Y4u3DdOXez5EsgP+ZxIYly/I7JS35h/azV0nbpzEKsptZwNWwuBtNIYbkklPaluda13QbkE0KK1Cl6PnkNMVbO0Uo9XURcwLn5aDLJol0GJGVpuiApXVJoMk7e7L/Ib+p1BE77VW619KzDmQvuAjQXZR28Fb5KEbRxXkSaq7xAZnYZyQFtOlqg/07Tv+V4g0WYZhI3tn0phBe0w+95NhgD + page_age: null + title: May 26, 2025 Calendar with Holidays & Count Down - USA + type: web_search_result + url: https://www.wincalendar.com/Calendar/Date/May-26-2025 + - encrypted_content: Es0ICioIAxgCIiQ0NGFlNjc2Yy05NThmLTRkNjgtOTEwOC1lYWU5ZGU3YjM2NmISDE+yP8aOxk+46r0AERoMFOlAyJD/AGdtDxeVIjDDhlbQbqqMmuMK9bN7ZNTwHWQwahkAaZd/LltU3eIPifLnfk4Apnck+mUOM61zRiQq0Ac12DCFUzhrlxvkXqpK3gWjgosGbUyzt8WbmwSaEmCbXf9F4bNc7+LKvQUtEceYmCesYcQa0lOT+88IhhRTxYBMyXo0Ws3Pw1yrLJi9P1JqfpcnWebg8McOGY/pD/MFeJORQvH+0UvGjBoMD0uaPPY1U6cFC7EWL+IebMjjmpj80RqAi/xjOOnU2XO8N98dFCzW4GiI9IWut8OBv8u3kLpmyYOV6cVxr3WTEntDv3onveQvTIyJFiDG8bmOKK72ONWrevnrKGQ7JY2gPYp5CakS+SYahIKiJOOY3cWQfUEheIfEZeuGadI907uvREyb1bf4h/0KsFjnOBWrHXdl7LIDRSfz8OIwfJufTERGCfG0vzMiHzFJbFcezciz3K9m25QjxL676NcrYbgZGFhIXar9jNvPAjGcUecpaOLAEztHx7pd0nHMsW5KKUTS0yU5sMFNY1+RMSKk8kVoCcBtOuOsiegf+BJV/J997IvbQG5A7h/72VsVyUbiBDT1z77VjKTPcerZr4EReCobAoyGdMKt1mihwvYvsyvZepAkvrxOoKnb+2pkzhZQtxGt0ub23yhSZyLgi0frVSS04KeYncVWW7cQJJygzfDS37pdSPnU0wZmAgZLzQ3X1NVmR9AQVSQ+IcIoVSCuru/WiBcS7aJPC4uh6cn5sB9C6yThrD8i7T7DJGbR/uutIADP9IgGUzdoppStgQugx4TwnQI9SC6e36zjfsK6NeUkxLi2b4T1uuRDvjgUDc0OXGZdoGQgc8nHoe7rexoLJqeWat19KNwy4DqnM7oywivVJWDJ2ThV4863f3+hYRlXeuxnojBsmENJsSxZtF46ye1gfPSvBYlEpO/Uu9FzR3yy4bB3b/qOh2+JMTLkk3R5u4C6Ca/Nye0bpnsKG7xjW2cKguqfKhUuUFaSucCCJqg/hT21DlUiHXZQ6wq6Ywi9C9l+wJbzfJWWHczjkTJvWhFq8saXSAzfWDd8tTKH1zje7NN38jRQPUDxS0CWMM78/+BgWoqsB5+IjkEl5QHV55GSCg+05HQPLyHQWWjBdMLWr+Uu3T0kKI0B/QDJPHO1mSX5AsGot5HZzva5qEPzn3vGly1n1vs14JWE+RrtIJTBHi0ypFhaO6oCRGejMRDbZhotP4gWW1VW0gLNikgfW0Z5waOb8wmLcN4vQhsHRFfZiXsqUF0dTXsme8+kF8qcVZNnPtghyf1ACvFgZ8TAF4PaM1wzpa8kDeBFvyp14U39R8XDJ3T6lGWR2rpv0WjS8d5h0RCZ3XlFuqPUXWEQ89OpEH8vEiYXGAM= + page_age: null + title: Daily Calendar for Monday, May 26, 2025 | Almanac.com + type: web_search_result + url: https://www.almanac.com/calendar/date/2025-05-26 + - encrypted_content: EpoFCioIAxgCIiQ0NGFlNjc2Yy05NThmLTRkNjgtOTEwOC1lYWU5ZGU3YjM2NmISDClGwxB0YzJdub7VGhoMASvr8D1KqeBCa4knIjAV1lHpDjDIw6nag+Y/ax/aq2LhfZ1/CXJoRyRFiUGLVQHpERodH6/Kg+l8aLy01gAqnQQR8cCm+MlfMM3pSSNah8dZ0m5wam3enQ+5A3VLu0P/CF/TlwL5yB21pfaiu0lad9Ee99YFvs7LInvXpyFcUgLuhankIzNNZZbUIeS4sZIK39RrN+pmifOl6KQqT1f2tpJE6x7erDlAscnMvbRIr9U8Pwf8Au709bI6zcHqCg4GNJk2CqDf6kAXRHRRhcWYFtm8b0wvg9HaWVYZFOunZMJ1gFD3D82dSm3m4awyDSZm54A/9hmzE/lcQyiSVBC73NOmJMBxbNec16qaeJdUTZp33JYk0Fdro1VnkUiJyq5Kb2W7VYvfeQ1a2dLXPTYIsXeQXLHesmVTe7m0X70w0BgCAC+yKZPp68bbsEbsbjwxaWOxQ50seu8kkXDiEgscUK5e4N5YnPXMNjyMNZmlbhmFtw3abOmOCVdpcDORXGOgJZBWBkEqQ4mE8b+aEzXMGRqLzYjDKZHx11wolF3MGu/V/+f4lWEyNwR2Kipp67teOx1ypvl4DiZiPVkZwx9lt08u+TJGEGbTYtdPMQRLVX1K2K4YOEcP5/abNrdT9KdYH7dNscRDqDY04gi+47JyyTSRObcCjQun9LUqh0q6/NfIIWQwjguZ6dbYL67ymiuRgHJDXsPnc/P58qP4kRsClz9lu/XPTFuAzCQwaXioK3XiBNW73nSKyRaxm6/OxbYetPGIG2JUTkQtrmGL5AYJTfFbI6XOB1AFDcsoSC7nGAM= + page_age: null + title: How long ago was May 26th 2025? | howlongagogo.com + type: web_search_result + url: https://howlongagogo.com/date/2025/may/26 + - encrypted_content: Eq0DCioIAxgCIiQ0NGFlNjc2Yy05NThmLTRkNjgtOTEwOC1lYWU5ZGU3YjM2NmISDDT/5zRyvf5kzhhLgxoM5vWn/Zjmu43rvWgPIjCf4z9SIoZxgNKyiLOqk422DLmHbFXGX+mhy6FPo43NOogfLhu/a3F3EPkiMxfd0UMqsAKIhRJKuEhfoi2saPCA3LpNhJ3YPO2NZyYL75SJdCbkfACe2YJOcCXHmt4hkrbA98Mrvh9DmtP6HNA9iYdsotXkDi0BuTlmiIcspblXnkpXLIURVpNYqqZ2jbjBEmi/mVlTonm9NJwx1rZHzwciKhaIrz440Vk/uTIqpiMb22U00hWrQ563gNlMSXpvIpyayhJcCnNEjQybJjQUsKaMqb/3Xbp8NUoWtidUb4ARNeoNe4BlzMEEcIPc0QWi1CKoQJH3JazxAhMSqvc4SVWXRyspZZP6lSHhMcIYlf/yorVYVF3FmL1djZfxXbVnI/VbFbpoNejvnOh5QIdbDrHu6UN3u2iGZBUJLgs6i9OUwMWw8kpi2WUm+3JeRkdyThE0M9WAhK+4qYD3UHgFCcucX7ZeGAM= + page_age: null + title: How many days until 26th May 2025? + type: web_search_result + url: https://days.to/26-may/2025 + - encrypted_content: EpACCioIAxgCIiQ0NGFlNjc2Yy05NThmLTRkNjgtOTEwOC1lYWU5ZGU3YjM2NmISDNXuvVNnWjvXgGql0RoMXwqBXk4fJbTzoM0pIjC1HFbYVJOlp39xpYzw/uJVUO6cC2L9ScOu9ceP2MDjyBWuE0X2LxBsyFjM3ZwzQtYqkwHukWybclFQu5CuM7gntDFNVYsSAVDbslXo5WwOlljEXnC7ohNjuB4lJfXS5HomdOmnUs0/thHHvR5mvCvHt5o+XrnTV8PSOXJvZonVctfjBeMe/3VWaQX2Pr8F71hiBVghYnc4lqnvA/nebBXcqdz2VTlG7V2/u+KCGVfR27l0+BX9s8vroR6dZ7flEBZBSDDzt3QYAw== + page_age: null + title: What day of the week will May 26th 2025 be on? | howlongagogo.com + type: web_search_result + url: https://howlongagogo.com/day-of-the-week/2025-05-26 + - encrypted_content: EtkGCioIAxgCIiQ0NGFlNjc2Yy05NThmLTRkNjgtOTEwOC1lYWU5ZGU3YjM2NmISDNqnvrEdyAJMqZiouRoMl3zAD6YOqFa5Y+LGIjCFzjG2cb9Uv/fUGE2iFHKU3CP5i7yVrpvusD4UBOrARkME7qTDdfIczSbj1PvU7ygq3AUQ9yGVSgg9AB7qawws2Zx43sVi5sDW/thrC8owactbSzc2qE9IsmucxwknSZXemJFSMW3I2vWhlpJVcfipESwRyVSnVNoTivU7/aIbnI15/NOGATtd/TR0mkyO2MMMAFIHiEfq0iuvMUX/EmnEqlSzDngrlWdPvb0qDrZIkSVEYFE5FEPHXuXEFEWZ/yLs7rUK7M9kc161gTbQ/NMALrWDjKtFtgI20Wf2luvbfX8G8kivyPttdUk9aiBxStWetpJZqsBEGT1auj3hk7FaHU2FTNW0LGvIe++QZNL+w/nKa+YuNGpJCxAiLt3X+2hnZmz8Mskfyfl5I5EWt7eOQmoR0qStl7Fh9Myb3vZ0rpZ8gZeaLd1xn6pKKa+tBonm1Oq1zQvsoMkMwTcMRK3LA53dF3rtkgSFyBAWis/tenbdrRVUGlicecn/bSd9FVFoGM5RWYvXR9/3zr3SBf2qNTT2jmeLqcl07LYUk+WDGcas6vAVGDXxVoAh+uAC0WziUtKfv//V4IeleGEOLJNdX1jDC8uQMKyGuRCXNRJAoHRhgUr2F46Ax9xJOHWXKDheklfqTFff0wf93d5U/d4p52RaeM7jcBexjz0wMQRBL91ZOZVweRHbV2BVTBpPH79Y9W/UmBEycRnTmurJJspZre10EVH7MIX/4DRhxBipdZrQFgqTrCgbN6dRFLlQR9w1n+Y2pSEXnSeHdwOTPVHSKBKQO43XCKWmJPgNFELcRBZdVxGcE9xd2/dzs0C6Aa7vXZ/5ZPx9rgis1TY2UegdpiyWmKV0yw1K2LKpq8BOdH4t3OPDTPvJrymaZwldTb+w6rLGf0p1YAkZIBPpUX9tAstQ1HAz58zHnLhQfUzpm5ft4S3m2i+WkyjzIkh4F2Y1bIvbwQvqT2x3rGJRRWJGyg9PDEVCY4gjKObhjkQbanRG58uPuf7mBofFqKbUBJOngnAvSmGibWNn9uSftf8YAw== + page_age: null + title: Calendar 2025 + type: web_search_result + url: https://www.timeanddate.com/calendar/ + - encrypted_content: Eq0CCioIAxgCIiQ0NGFlNjc2Yy05NThmLTRkNjgtOTEwOC1lYWU5ZGU3YjM2NmISDJI9LlFy1IIRylVnDxoMIXCLnspt86G8qxeIIjD0AiJH0r4ULSsSOXmrBj0ntQEjeADrBeCC4P53iCMga7fv0Uxic9EYRY73zIkr7aAqsAFql3nOsfaopCrkf/WC9qvKgDAeuBfKcTNjj+rfHn5HXgVM4BOpomSXxBc0CHUe3Heqw2mD9+Hy99flWI60pgPB+umunXeOjz5HC4W1q39ROTzvMkdpJzvXoUgG/dbgjJ9vEvZ0VC4z+ZwRAYSNgOgiRt3zApcZJ4iSsF/XgrQNs2L2ThcQyc84+lKKAaKPrSl6Lwm1bH+HTFYIm9KPyhvlbfEbp5buj/wQgn4Na/3qrhgD + page_age: null + title: May 26 + type: web_search_result + url: https://www.nationaldaycalendar.com/may/may-26 + - encrypted_content: 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 + page_age: null + title: Day Numbers for 2025 + type: web_search_result + url: https://www.epochconverter.com/days/2025 + - encrypted_content: EosGCioIAxgCIiQ0NGFlNjc2Yy05NThmLTRkNjgtOTEwOC1lYWU5ZGU3YjM2NmISDDVXhko+GaoaARLiWxoMhhwixTlmn7YeimyXIjBqLMdGYisjngIKCxw2kKV4whivBWinF+rFSah3+1JUtoRUor9o+HmU0StGZxaPydsqjgUGl0OUiZ7Dlc0YqCN8bOGaoUeUv7MpoODHTH97tj67PKZmSbEkDp0J1bJzxHd4WeL1SdNnExDsDcSk4joJFA4lBVAAgxvBogir7w/ws8fRoANAp3F4T9IoZHSQVR2PhtRpymmT1JIaelypYDnda1cLVKrOhF26o8zSp3sDKsilZRW7TSGG1YmzEEKdFK0h/jrGO9yr35kL8vzIF1vdXeJbJa79pFo8e1OLxq9Z2e3y5thBAB3aMK+WMT6n3vRaaRpG4/sqL69lUB/J8Ju1X2VAqfUhYe1GbTKVwAqv+Fllq/NQfZywAaqkHF0YqzMFNYYNB0KTpzrPetfmvXMvog2I0qn8Xuuefw6ROAgsUJaIbyY2b2AbO9Ohxlqv08Lgt/CEmghIi2I/zvt4Y5AjUZzpRUqYAomrkagze50qj5P5Mj191pU+SLehkArpjDp131C5k3jhJ/6m7EaRpv7R4JWxWZCETvqonBRkooPRYqTZkHAD61WjNrFzDOn3fOHjrK9XjQMYE6C9rLOQf/Cy3XdlJgmSa6OKzak0NhRzIA8h7O16Mlf5kaMz5gW4vhIY/luKVQCgo7O39nxitIw94BlW0u0qT0XzCFgS05ADWdIC7SHJaU8NB4x2h0zQTTX2P5m0U2giLYPO86lUWicae08l14+JUB+uPlpDhNU7UQl29SM77+XnzdGiX/Kk99k8FcMrgp6+X5riQWuE0+rBhuDzOnVhGOgFCazxWCIpXIAvYqin429shbJtttU/upLPrbj9HsXpCJjOPI7+FLK7JXH5tH3rz1OCa/14jkNd4GTg5eGNnJSDgSJ2dWxC9oRQyFub6rPc58n9BCkHzcqrF502Q4TTEfNUuQsqBMJL8yEYAw== + page_age: null + title: Countdown to May 26, 2025 in Washington DC, District of Columbia + type: web_search_result + url: https://www.timeanddate.com/countdown/to?msg=Cressida_Glitter's+birthday&year=2025&month=5&day=26&hour=0&min=0&sec=0&fromtheme=birthday + tool_use_id: srvtoolu_01BJh8n7va96puUF3hhNnYnY + type: web_search_tool_result + - text: "\n\nBased on the search results, " + type: text + - citations: + - cited_text: Current moon phase · Moon calendar 2025 · Moon phases (Full Moon) 2025 & 2026 · World Clock · World + Clock · Calculate · Period between two dates · Mon... + encrypted_index: Eo8BCioIAxgCIiQ0NGFlNjc2Yy05NThmLTRkNjgtOTEwOC1lYWU5ZGU3YjM2NmISDEgJNRfMEmnW19PSuBoMpvnoqkDvIRkx6yxdIjBNsAkQg3SrCCN1zCEKO4LtptywdqMNvgULDnwzA4e8JoIj6nDuov4EZIdg37AOh2QqEx0w/3OTuMMUW8z0FsIsEkBStrkYBA== + title: May 2025 Calendar + type: web_search_result_location + url: https://www.calendar-365.com/calendar/2025/May.html + text: today is Monday, May 26, 2025 (Week 22) + type: text + - text: '. This is notably ' + type: text + - citations: + - cited_text: The custom of honoring ancestors by cleaning cemeteries and decorating graves is an ancient and worldwide + tradition, but the specific origin of Memori... + encrypted_index: Eo8BCioIAxgCIiQ0NGFlNjc2Yy05NThmLTRkNjgtOTEwOC1lYWU5ZGU3YjM2NmISDDh1HLd3wIXrMlbIYRoMJbIyO6jhVRytVw9tIjBp5E9G6P/4oCUT+AvhQia2q7og82j1ZKFJMEyZllOT5wNB8wxo/hua8sprObTuApUqEwNbgxwhB3Rzdzbu8jotXE3VzSYYBA== + title: Daily Calendar for Monday, May 26, 2025 | Almanac.com + type: web_search_result_location + url: https://www.almanac.com/calendar/date/2025-05-26 + text: Memorial Day, which was originally known as Decoration Day + type: text + - text: '. ' + type: text + - citations: + - cited_text: 'The year 2025 has 365 days. ' + encrypted_index: Eo8BCioIAxgCIiQ0NGFlNjc2Yy05NThmLTRkNjgtOTEwOC1lYWU5ZGU3YjM2NmISDGSJfkRRZt9POYx8TRoM9ZykWxngb2XQ3vvZIjDwg7OYkVRW8t1wzFmTQ1TL5akbS2y3zcQRN/I2nU4v3NsNZbfOSHEBhWOql3U/nekqE4EtVZ7XYLel8iTWpvOfRFX8iUAYBA== + title: Day Numbers for 2025 + type: web_search_result_location + url: https://www.epochconverter.com/days/2025 + text: The year 2025 is a regular year with 365 days + type: text + - text: . + type: text + id: msg_014DEwKKUs2hUThC8aqhrc5d + model: claude-3-5-sonnet-20241022 + role: assistant + stop_reason: end_turn + stop_sequence: null + type: message + usage: + cache_creation_input_tokens: 0 + cache_read_input_tokens: 0 + input_tokens: 9582 + output_tokens: 178 + server_tool_use: + web_search_requests: 1 + service_tier: standard + status: + code: 200 + message: OK +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '732' + content-type: + - application/json + host: + - api.openai.com + method: POST + parsed_body: + input: + - content: What day is today? + role: user + - content: Let me search for today's date. + role: assistant + - content: "\n\nBased on the search results, " + role: assistant + - content: today is Monday, May 26, 2025 (Week 22) + role: assistant + - content: '. This is notably ' + role: assistant + - content: Memorial Day, which was originally known as Decoration Day + role: assistant + - content: '. ' + role: assistant + - content: The year 2025 is a regular year with 365 days + role: assistant + - content: . + role: assistant + - content: What day is tomorrow? + role: user + model: gpt-4.1 + stream: false + tool_choice: auto + tools: + - search_context_size: medium + type: web_search_preview + uri: https://api.openai.com/v1/responses + response: + headers: + alt-svc: + - h3=":443"; ma=86400 + connection: + - keep-alive + content-length: + - '1494' + content-type: + - application/json + openai-organization: + - pydantic-28gund + openai-processing-ms: + - '1271' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + parsed_body: + background: false + created_at: 1748263711 + error: null + id: resp_6834631faf2481918638284f62855ddf040b4e5d7e74f261 + incomplete_details: null + instructions: null + max_output_tokens: null + metadata: {} + model: gpt-4.1-2025-04-14 + object: response + output: + - content: + - annotations: [] + text: Tomorrow will be **Tuesday, May 27, 2025**. + type: output_text + id: msg_68346320b7608191a49fcd12e06dd3b5040b4e5d7e74f261 + role: assistant + status: completed + type: message + parallel_tool_calls: true + previous_response_id: null + reasoning: + effort: null + summary: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: + - search_context_size: medium + type: web_search_preview + user_location: + city: null + country: US + region: null + timezone: null + type: approximate + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 410 + input_tokens_details: + cached_tokens: 0 + output_tokens: 17 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 427 + user: null + status: + code: 200 + message: OK +version: 1 diff --git a/tests/models/cassettes/test_anthropic/test_anthropic_web_search_tool.yaml b/tests/models/cassettes/test_anthropic/test_anthropic_web_search_tool.yaml new file mode 100644 index 000000000..57f5e285c --- /dev/null +++ b/tests/models/cassettes/test_anthropic/test_anthropic_web_search_tool.yaml @@ -0,0 +1,166 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '312' + content-type: + - application/json + host: + - api.anthropic.com + method: POST + parsed_body: + max_tokens: 1024 + messages: + - content: + - text: What day is today? + type: text + role: user + model: claude-3-5-sonnet-latest + stream: false + tool_choice: + type: auto + tools: + - allowed_domains: null + blocked_domains: null + name: web_search + type: web_search_20250305 + user_location: null + uri: https://api.anthropic.com/v1/messages?beta=true + response: + headers: + connection: + - keep-alive + content-length: + - '42593' + content-type: + - application/json + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + parsed_body: + content: + - text: Let me search for current events to help establish today's date. + type: text + - id: srvtoolu_01MqVvTi9LWTrMRuZ2KttD3M + input: + query: current events news today May 26 2025 + name: web_search + type: server_tool_use + - content: + - encrypted_content: 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 + page_age: null + title: 'News: U.S. and World News Headlines : NPR' + type: web_search_result + url: https://www.npr.org/sections/news/ + - encrypted_content: ErwHCioIAxgCIiQ0NGFlNjc2Yy05NThmLTRkNjgtOTEwOC1lYWU5ZGU3YjM2NmISDK2W0Qu0wNgI6mf5EhoMNCqr1gVeM/Fj3PSYIjBrgyGKmTOrJLgDCXcOvRtrbigKDeccd5oypMBGnMVhm6h3Ade/9+vNOwI3ByflKmwqvwZqUUdfJ6+k9ZDrmb7VM6ktRqsZ4Z++yOdyubDNbsyM6RdwYuNi+bS5ZUON+rMd8+ZrQYlGYqq7NF43o5klxpac+Dsgx3OlKbu6Hq6eiKOQ3rdPGYlUYKdDouAx6RjypXjhYkqErPrjlFZhNv2lO6cohI0QU66p6b7G8UMVyweqYZ+2QYTFfbwU5VdIAOiQW8PBgNwPC5LRnidfbiT04VY+cEsNW04zOq9coXs4NgFRw2WDCZDBPGTEJex0xv7vD0/D0YpBhfiawNJ8FgBTI6q0gXQ2+YwqelVaZ+BDpu2JeRABLXiXQAMIiBBiayofacvfgJZ4omPY1JRiJwX5IpbLFqLcNz2fWr8veYedwrDZV/lOjyn755WTp2i89GD4Pv53htWrDOH8/YJBQ9u5KA2DFz7zAtRLyPqvPz3YaLMr3ATFvs8m0igrllgC5uaWPWfO/28RU7QNnxyBLGNonF3dtz3Uu2naeNvxjRhqCtUOON5odOahtPrRs5qkjv/UrL2YzlnfsRL4Qb/qsGJE6YWScvLhjBaum29Whk2p6RtYJqzzSqDbk0jxKe/hNatl3s2JF1bAW4L7p9FnsK1v/G7AYSaIYl4RDLGuL1bFOKGKVlUZtohNMws+gvTCYKdhQzfurimTsNIpBP4Ci6aJ+/yACa22AXGhZQqyiOS7yxI6zj3vZdQGFBle1TjDpzveY2Nz/kuuTCPbGsWt5kd9v7BkWvkNacqZ70KijyIk5dVt3H0q4eavyNLU0gF4hSCPDHW7eeWXTmNs1YniKiaHrwmOOqXjw2PCQrZv0i7UQRjDmRQqx9NtuqzMup9DRPbQuZM23b8JwzqA0Qjyxc5pTlWRL9aU+U7ZKOD1OdBszAU54c5N9jOca8S2Plt4TGJcAv2Wy73Bex74GPlkHcKWO8TJYhrV4ZF2nMjssncQEKCltJaZg6TJpazpLKoQ1XmYmgzebbVMRc8RTDXk335AYKkN62xRnfrDd5T5wBhGbPNQeF7PGigtAK/SpSpTna/vmGOBul/cONWOFFKNdY+FtCAGd4AOo3s/N8QUnKR66TEv9ocuVep1UZxV2fJcqIuJukutfT9eWPcou6VImLUzRQMYAw== + page_age: 4 days ago + title: The Biggest News Stories Of 2025 + type: web_search_result + url: https://92q.com/playlist/the-biggest-news-stories-of-2025/ + - encrypted_content: EuYCCioIAxgCIiQ0NGFlNjc2Yy05NThmLTRkNjgtOTEwOC1lYWU5ZGU3YjM2NmISDCmLeaYgzUtJ4Mi+fBoMpktwxWvlSdpeNRRlIjDckkEHzeMKWP+vkSJ+7ci91OlLvn1nTU4wG+0am9miZ+68Q8XjsyCBGekIPeSsgpkq6QFTAvqrNhd55GMbj8VQtB/7oV66lwp8PzaymgQlLzCnxBdZ6IRYyEd6XwFOPrWCwyjtlKbwRiM2NIaNsGcrraBVrDfsjCz20qsDPGNsQf587z/TD3zWUSelhjhf+T5nDCEXUkYM2+4MaGP5Ty57Khh3WQr5q6Q46m85jBBF+akWf1uKZEgjgFug1ufj/8TXEEAaKCVY9YeXTXfYH8DocKveCXH4Bp9TNbgx55UrL8NdXiwdtpI/zqY+8hM/SiaVeXXI/Rbmjg3HzFTLfrH4wSrl5awdKWuGwQy8nqRISZlwNVnwFY0e8uc08xgD + page_age: null + title: ABC News – Breaking News, Latest News and Videos + type: web_search_result + url: https://abcnews.go.com/ + - encrypted_content: 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 + page_age: null + title: Breaking News, Latest News and Videos | CNN + type: web_search_result + url: https://www.cnn.com/ + - encrypted_content: ErQCCioIAxgCIiQ0NGFlNjc2Yy05NThmLTRkNjgtOTEwOC1lYWU5ZGU3YjM2NmISDGPat3RtBffd6jd9uxoMkx9uAfM4hgJRbrR4IjCBVNWqux+TsqDP0poLm+ss84SLrVR4rAcjrQSDPna9ZfR0OFhPjv2ko1ZVuBzeRE4qtwGOPV6my0G/y4nPEH8gNVc3y/8uZzh7O8CBrduzchEMd5RRXLlsC+bU/SjZ+5LBYGzAVwRCfVXIdaJ0/d8RYdJWHo3bvKc5Lu/WFPV6Po9gVHLOU5WVDsyzwmrvqzCYC0UhkUMa0yf5j7WTFaT+kgHZcFcbvYPG53USqNh0seahaaCC5fJRjRBTAvuyj4md+ppTjIXGZEp3rTMG3MTkv8t60MgPzn4ObLGEmBQIQrfES9G2BT1k7lUYAw== + page_age: null + title: 'Philippines Top Stories: Politics, Environment, Education, Trending | Inquirer.net' + type: web_search_result + url: https://newsinfo.inquirer.net + - encrypted_content: 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 + page_age: null + title: Portal:Current events/May 2025 - Wikipedia + type: web_search_result + url: https://en.wikipedia.org/wiki/Portal:Current_events/May_2025 + - encrypted_content: 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 + page_age: null + title: Portal:Current events - Wikipedia + type: web_search_result + url: https://en.wikipedia.org/wiki/Portal:Current_events + - encrypted_content: EsoJCioIAxgCIiQ0NGFlNjc2Yy05NThmLTRkNjgtOTEwOC1lYWU5ZGU3YjM2NmISDAYjLeq1Kci5o2pi8BoMqJacE/46sh+pR01VIjDPMDUx0d3wj2zbUnowpxFwHvCCnCwLoPMxQsQm/dnvm0Fzfga5o6zXqDwWpXvUzhEqzQg/X9w2opL/m5o7bUw/TubgkYaR6l4t2n0oQlGgetbSj1gOEor/WWJ8bWXL3BZnS2xkIzwGrbLdlPDn/NoXICDQZ+P3IlA6B8CVwiijOnNq+x5nTpX0m794VFJIfO5SdupAiWqhWmtqt8XhcWs8W2gnhDDvNsBG6oH2ZsRenxt3n7a7eWo7yRk/KSHdBM9c+L5r3wu3Ul81DW9CuE6KqUdFjjePJfWKL8OvzfkvJjIqcRaqc/3RIRZbSPbimBiRMXtCBZeCYE1yeBs3xLQ7TJXgRM/9ScKromcFWckYpGXBYSGL8SiXXoBUD7pLsuP5FnRnZUkQLCHTLoId0/w4jVbuXmDh3oipIlGUQCSbp3FkogFB/CZFpKz4tY5E9WQ4pBkApGYgAeGgOOStiUW3pE9oCy5TRpCfilrg66RtJozGI+LWM/XYuuOwSK+f+/c6AaUJ7av+LCUSPFI6G1XErfHK/KeBSJp7ZVoRXn/f7yJXlZvybKQXdN6UtxqxRJbil9RnmmXsBc6cesWW/cHbz01V8tkaqcYdrtJdVM/LesICK77C/JYiA6PQsneeg5xdZDCUp7yUO9P/CHMBqhPB8geS9y4dG7UIdJrFbv43cGOiqoSBsBGCLCc7crptYYGydT6YBgKb+ktUJm14MfbF8lzKt6SVYpn8KWL2dyhsDbfi88h51fvZqDV6loTDpyHbMHeJoA4pIxLhkBIriQOLNnEIEwqTGy2XFy326bahzINKJVTY1mMq2v3O0Snl0DNcAZ1X/iHt393xPgdcSy6c2+sDRexvpU4grX1GGFD4E8kg1QP0fErasq17XzRVpnU7Kedk/ntU/X6zeI3aTEeyRNG7IPH67w6GyIF8XmgCh25H6bCBGN87N8hnPSVAy8/qIMcfZYaF1c8W/QB9n7HBWhQgdyZv3relj0Ur0xdRi2osqo+k2c0a9mmIVupbzpLAxfY7LiwU8Edsr+1WY62x1omk+b4XNiGnhHnrF4B2o+f89icgAVSqRo2ydqIUDnZUYewu8jjUg/j+WUI8yKqZHCgCRdkm4fDSOcK8faTeaITl1iI6XFbUicEWZzG87tFykNSv5fz+ueDbMj936cm97rPUhp/qMnS2uloAxmiWLcS2/oV605i97ccR9IlwB0tt259e9iCvltjxzcC6P95vbhLS94+xVNOG2fmQtzE8oyaREZBkwSjVHuJ3lDAxvHDRYY8F+lkuLE4AvLiye3CDAMXNyCrG+/xiQIBNUGs+1aV9edHMmwpCVs99Q+nHO1RBVPljY607Q6u06Wt4VHnY+45+IxzpHHWXxg3Jn8Lh1AuzFKEaRWaI7JDSCgJmYxjIwkUO7988PWjOmFLquOd6mQsQ6iVG/89zSwr019RlAQRDIbMimefHIYhLm4S/Y8TzPhLFXJ6FaxrPFAkkkp2LnLQUoNKlo2h64JaAerGAku2FEwn/vo3hsCXwILg6R4QYAw== + page_age: null + title: Current Affairs Today – Current Affairs – 2025-26 - GKToday + type: web_search_result + url: https://www.gktoday.in/current-affairs/ + - encrypted_content: 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 + page_age: 3 weeks ago + title: 'May 6, 2025: Top news events to look out for today - People Daily' + type: web_search_result + url: https://peopledaily.digital/news/may-6-2025-top-news-events-to-look-out-for-today + - encrypted_content: EvcfCioIAxgCIiQ0NGFlNjc2Yy05NThmLTRkNjgtOTEwOC1lYWU5ZGU3YjM2NmISDLM35Bum8iGp3KQslRoM14ZW+oAvbXsy7nUsIjCLxAF5JOrnB/xWs20058EEqp98kwMCubS8ohl/TFUHHJN7eeUDJz7IuZOFycr4+2Eq+h4AUNPhuzguwpPktjSAdmE8fd4sXi46MXN+AqpE3NlTX7NqhmtEhPnwn3HdMGnBiQMG///1824z5wmFCjV2v/aqK/HIy1wvC7M0C/oWcQiBhVR1zNhPbGz153Vt5tw/XgOusVQZz+8tKl3yXac7lWmksW03m2JK39XQFFcs5CZJIaYTqqReD28AyyAaNFW29WF9GrW13HCaOn8YeSHP+zVLqBWR2+WmEnIDStBBEpnl5QVyjhFMdiUG/0TzPTfhXOvHJo5WL/pef7qEKG1ECVjkF4BbYGXh/4E+3CFu2xaFO7Kfcds3pqb2hPgU5gaBnXFAv8QRXxPqfAWOX54vAW0H1ahBM9sUQQcfK8XTVyoVvEF/ImqoC8m4I7ciw8cGW9g7UF4ML8w8NGefqMDeWBz57Q3fPDkAZdr3OLdaUQkY2Vub+LFeI9hAqbLBixWmG6l9iTytGF/XuBuqcM81HOo9BaD0Dgh11IUcz3F2iCo53yoUAqjC40nL/oHYbeJHSGKbhZSYjZc16WQ1RKw8AAbaKxQofOKVH7L+eYxoUnUzbl5WnqiwKdy9k7/lOH0o+/Xu3CUlyi9kTuFRv3MfhuZCmB3t/sflVtPBqSNin7wXEcUduJlODsWQ2zPzqbqjLJr8Uc0Bxjpb3MzwOeSAVrkG09Dxn/mdtdRoJ11WsLDqna7SJ9LBGqD0liHqFPF0b3Zi5Xm00dIjhpe5mGHZPiTEfkC7Rtt86Ifl9pvuaEPiCIAMF2TRfGAHsA59C3yBdsSnbdV2OOuK6JOqdLyt1qEP9tGDkYX2fdU4fgyK/gva7KR4sX00DpC95D16vt0AhUhr4uE4CZAxyzp898Q990qgmjkGccTiM5VbDk+1cFE0q8Kksb0Byd2JCUelWl/sFlMHYJHzswVshTeGRgwaUiiwICrgBB6Oc/21+qISLLka8+dyIvnDmSNG0KUp4a1bLA8TR4WlTB5THJoM9kWEqhPIPkx+Q1DmqPzSvPuCNfXOiNBUrAsLFOVij3l+B9zNJDJEi67UeQ81cGclwstJyI+F04QjOxynhRmjsMY3Vj9n7tZ1MjoYfclrcFaV7H98USfV7Z1Jid+e5qS3t8ZP7w0v3CSMfKZpo1WB0R/cDIE+fS+APoydzO/k6EL155uYA4UoFyKAAoEcgnNkBK9E8AhZPpvila+XCtdCrq1Rrrp3J4O4e4DWcWefL/dhWuslr4UhlAhjbfvyz4yCphHKbAakZjh0SD2J+1laXJaiZenpo954DfggYKIYlvriyjGikWvcebJey0b5qw3+Mol38WBRXt9ikYKNNUONeLDsiBXgoOo84kAGigQ2O1c1aV1oAX7xcPVfIhUWnFQ2gY5wtfPeqWLCEYaenNlN8G7kqIPcWVLKdbeMk0PCmyMeZQi4HlxO/cwkLnf3fI4++7/AL7zlEFYYei17YP2NjvYTKD5PQld3bcEKYozrF5LVReRbMpwhaLTLVmowuU4jwLawC7vEv34ALQPblM0MJijk+JuafV9uQ7y9/w90OxaRZ0Gnvb/ZuRcY35g8OjB3TLONRU7vlAYKoUH513Lkjk9lGNcjep3AeiuboLuFD2AbVv8CmZ4lsAs+NeN6R5c8arThgqBIiNfprN3uStBoqHp2hnEU2NfAxPHblGRVSfmEUvJJL3yfb28eVG05Fp4W9qp+Ju49V6242x0/DXOhV2Dz6uUYsJJotNX4Ei+2HcjNSRGQDvmBmvrzxHynybVls3SY86LUAogQ7cl381a2n1gIhUFootBUpRSQSBTm5EEsLCpBWaC+itiRj6c+dV+qcQvinExRLuRLRyIDWmvZnfHNypERhLnfuAqLG4z9cplHajHnlBLA7lJIeFwhTZCHmhDw6sTwmQhpx5gCbdFhPHkBK4KycXhbdhV8ksE6efOXqI6ZArPEbAs0EklZkukS9j2i5W3xLaeO7TT9RXgthdcIpDdpTpby5E+dX8Z/e5TSbUQSQZhoMfZPyJY4Y4+LGq5t4FgRJJq5oLiRCEFokq+7JHuhnHI/yvHgERjR1pq8hiffv3h38bm8aIoe9dnmQBL3HeRgIPba4L1E5R95G+WzhToeHmn9E53oWSjXe8PpQHack54hR8qSHJmHsjjsADUjo0mrOBZa7hMwkX1Z7ysPL0p5W58Fx7Pi5w0DDBRY+KKfjMm/tZw60uMR7UfK0xfweOl43GRe/nwXH+7t2Rp2jpuAWGSH2uhKyvnQpZl3zTLwG8BLLAQOhXFblOK+Ozo9M51hJESZCDfxUG+QDGh41AVrX//t/4ZiY3h6EbwPI8j+/YIyxDsQewGnCrJ8zKqt0b7Evq57FM70q8Xc5uIoxPB0ZtfSGLY8kZLY+aYDGTy1IIpTa11q4CGC7RGlOV0qkcZcuyHhAF9h2zsjLrbeBQEdgHoXT57CZhMua4iQTqh4oHwq0k3bekt+gYp97Mx50R2UDCw24dfCeBrEeZuE5Sin1HxXt9/OaiP+cjNP7hGRZf0wYe0Y23XgDVfRwOmpCASSscBgjeimT9XviurY/RaI3ilfMJMsb/f/reoXEzglV4o+i/F6aBt970M47H+KoKptQIwKSDYcXxDbv1YzaTafmgKHObn5nXzB1BAMQIoNtUb3/2ZH6HfWjaXVuPoqYUS2GXpcRnxBDqvaFx44BOwP02q7uuUXkLI49j7TMpT3tnWk2nc5HMtZOetakbjklR2CcVEGKAxttR7wMUrNWBh7lUYuIeicuQBsl1rgGP9BP5pjkFh7ttxzQw3ShTDp2AfzZlogK7y8TKACZU2pHEe8HQ4rXuuYUR08+zxqrXBrzBKNsbLK5X37Z6nrcMntLjr6L3x7nx4bfoHKDp3lLWDfjH7AFfPJXBtSOk3+cuntDt50rhMFgxGx6iwAQJuT8T3ABoaiyDTIsKLL8wRT5STRRZXjBGqsRX6JyXkBmUFlqM5f1Gc91ArKRrjJDNS9+3+8t7z3z6jMVMMjaW1bFJlwe71TrQIGFzVltwflr1+1HwYp7KMzsdeIQlUDSeoy19xl8fPDKaulUHe5RjOsKwCp3rqIW/l5yrZ2cPfdugFs0NJeGj6P1s/myBxd9J2BNw/SSUEVqFvIYHPbwJNe56TmDAkpIXM6/p41h4H58Ezw99jCNzJf9akBunZCxh3gMFigG8EMTTXNdUMkICeYG3PZs3zjax68X62e/sFA3MWjlb5P+ULvuev0kmXyh83Ot4C2b+a1XR5lRp91KE60i8OyGbDRycctX9EhQENSgvG3gblDD0OSkVbyRGqC/BqACu9Q9N2cWBPCJib8AtW/MDCtIbbe/TQg8rPCRLVkKOZpqfJDKNcXCbfd5d0hjXuut9el43TzwlbfrOKzY8Piubx3u6TtA9iXwit/vPuAZb7pYivaswBJrdIg3q3UbTUZrCWKpenAQuI2i1PWbFPrNXmT3WP8ucGiOw4BZL/us2SmoHI/QgKzZ7kYrB9rFaR3Eyoxm0khw20ZrGbep1VUuKlQHLG+OQzBrarYRG6d2Or5WlUgtV7jmMTWaZThFJ00YDGDpwRx11t79Ul2rX7iDCTr1IacM2S1zdPm9A790O7UEroB63OFc6YyG6UT2m7H2mo1KnD92GLjSra19NBE9WaY3L+SPLpxlOL+jqovWZqN1aRHlUIaO0pW/c0mootGjajXdW95RHjCwuvOJ59JJfRGtawht5AhFzjfejqBReAiBgP/rypuFQE9Czz+2C6rPm56lbi7GDTqIFDqjsfP5wUYhPwvMDFYgpIvRx4/MFjCPhG99FgrnbEi5WhTiwlFBm3+KVsGtEC035GmM2OKCTzLhgc5SZdbiw7y1FTDmz6es4RRnuOfcUKOg9nOs9/bqJkaAZJ13cZjJ4OI3LBZCifHJ8HX740yytpJu0mO/5qkCUGMz4CIb3so1HUY4yN6JyzBsVDa442n6CfcF/0EIlwS67WW3sq/r2GmvNAFgBQvtRckwmoA0qc2A3/OMzu7vcEDiMnD/Mj20+cM89PYWl6eCp7MA3CVfFvdcxdRqpcEWCZCz5nZSABdlcKuvdwaHANzvWUtIj5tjGyloHsOtErPa5PYcWDa78e/zQ5jJzWcI7/V+7RIjXWtr8hdWSju4SSxeJITGEnr82AuXrtcQR4N8FTd2c+oudOhZI/+vP6o24mgpYvM4vh3RxCiit/fc8A0TyL6uTXXCDMT6Zd3VdyO1L/szRNfxrzGW2KifJ7j6vlQ6y/70VYek01PqNYIHWhbcU3vxT9L4RKvl1xfWDtnwVBey8nVynS+GqBixUaHeITUwFmmgqLgsusOhybqm47PQDu6cK6wdqLgv0OKu5CleyvApsHWL/bWUY7qgXOEVZSO9fjeaE4TBd+ZCWiZBCW8GTxWTBxQNJ7Rt6qYEW2Qu9vY1sl8Lad2AABeDxTeY74CGyGGrhHO5LaA5gLdWmgfBi3nMZVODuIwpjFjtcnOwEXLevSIzcljrM80fMiCBkviECr45Mu7zAAIWMuEEy5mSkMsY0ifxmhFLGp63xCUc1iaouY/geO1Pu53MH0zh/Vm6Jka3Iks+5l9lSwJ8PlLKTViyfVynQseOLGPYCD7070r2OKvV1eZEZochpJFHcB3eC9WBIOTBWAyR/1QNnOXx0nl6/Co2ROFV8I6FvmXl7vdLsfogynpeH5hTGvbMxGUIhlOBPRrdvytXYB5I1EGMCYd1Hwl7iGX5FtktQx0epzBuLeYpBaoMEl0KgkCUPorpQqkE2FmREB9aVpM8QYayC5tqJZhhV1+6Ec+SEE+Ol8+ZG+0K+Dogbx6ra/ktD1X2X4QPeieLGvCLGFgVlzVxmuryoZa+m8E9JFnt3DvyqOnZ/GjutTdI1/JC/JJ2q4IvNo/oFQyqZitB/NX3IGXIm7Qe+AGVXYukItPSh9wNp1dmlCHQwMdN6fu9HOh5NswBrXqAR/TbK+7JjIY6HeWlykdOUeE//3e0SACTbjq7EbH0mbnWLTGPLCAhb49c5RJbXNJrPKWxLj5y9eDAxTrpqUQ3IfjjGiU9JBUTAUjwlKrE2/skjZtJbVegv1QhBFuwaUloEXHh89oOBh+4B5KbxqlS/YXtrHfKbFewdGiRSV4KUYc1FV4emyUZmn27joV1qc93UgWkqyAgXg9X75I7GtygxzN0SYqMp1R1LSOofRiqHMLOMs68He0BOPCRHw21/veVKiC1gN5R5g64DvLH4uhL+BBf15TivY/XnJKPJKtmG8pEWd6uXX/fYSo670WD2A7tWV1ZhszWai3tgH/1wR7kpOzik6wkhgD + page_age: 7 hours ago + title: 26 May 2025 UPSC Current Affairs - Daily News Headlines + type: web_search_result + url: https://testbook.com/ias-preparation/upsc-current-affairs-for-26-may-2025 + tool_use_id: srvtoolu_01MqVvTi9LWTrMRuZ2KttD3M + type: web_search_tool_result + - text: "\n\nBased on the search results, today is Monday, May 26, 2025. This is confirmed by several sources:\n\n1. " + type: text + - citations: + - cited_text: "Katja Ridderbusch hide caption toggle caption Katja Ridderbusch · May 26, 2025 \x95 Efforts to improve + officers' mental health have grown over the p..." + encrypted_index: Eo8BCioIAxgCIiQ0NGFlNjc2Yy05NThmLTRkNjgtOTEwOC1lYWU5ZGU3YjM2NmISDDbwqv5slJiinAczsRoMhSEnYjGPJgqqehvhIjD+BvlqTC7HRjtE/aw2RZWQrImtkFRHsMc+fLuePmKCTV6CchthAUcDrpTbM2sVfeoqE2azhBPbtwuu/6GGPopddo8c5owYBA== + title: 'News: U.S. and World News Headlines : NPR' + type: web_search_result_location + url: https://www.npr.org/sections/news/ + - cited_text: "May 26, 2025 \x95 It's Memorial Day. " + encrypted_index: EpABCioIAxgCIiQ0NGFlNjc2Yy05NThmLTRkNjgtOTEwOC1lYWU5ZGU3YjM2NmISDP+cEzOyt7FZcwJ+GBoMyXx/ouZvvFOJk680IjAWGSs9Nrqov5SEum6N3Gk0FGVUoIgOxF4aOdlcLDVixGpcVil7pLWnwcKf6aH1DhIqFFQUWnEOplUMZH6nQ1RQprK0YB59GAQ= + title: 'News: U.S. and World News Headlines : NPR' + type: web_search_result_location + url: https://www.npr.org/sections/news/ + text: It's Memorial Day today, May 26, 2025 + type: text + - text: "\n\n2. " + type: text + - citations: + - cited_text: 'May 2025 is the fifth month of the current common year. The month, which began on a Thursday, will + end on a Saturday after 31 days. ' + encrypted_index: EpMBCioIAxgCIiQ0NGFlNjc2Yy05NThmLTRkNjgtOTEwOC1lYWU5ZGU3YjM2NmISDKg1IA50Gwr+acWT5BoMGYg/QqdGIWZ1xBQKIjAkgAmLPkrAfHcJ5l/9cBkuIDZFR5HqWIMts7szVwtYu+KUtw636GWrp1YgAWw+PHkqF0xqh9pIZ2wPGUKGN4beDYqhKyS3yKNSGAQ= + title: Portal:Current events/May 2025 - Wikipedia + type: web_search_result_location + url: https://en.wikipedia.org/wiki/Portal:Current_events/May_2025 + text: May 2025 is the fifth month of the current common year. The month began on a Thursday and will end on a Saturday + after 31 days + type: text + - text: "\n\n3. " + type: text + - citations: + - cited_text: On May 26, 2025, India and the world witnessed significant developments across various sectors. In recent + developments shaping global and national aff... + encrypted_index: EpIBCioIAxgCIiQ0NGFlNjc2Yy05NThmLTRkNjgtOTEwOC1lYWU5ZGU3YjM2NmISDFkt41LpatakOxyiFhoMv3NkIlXjrN17gwJmIjBYF1Ds1OSA6s5ysr6ObQrej6qB8TY2C7CLY3A9izWkBKF92leDuAKzxrbr0q2HuTYqFvMJ033dlXOze2WorB4B4VxlNRvBw7EYBA== + title: 26 May 2025 UPSC Current Affairs - Daily News Headlines + type: web_search_result_location + url: https://testbook.com/ias-preparation/upsc-current-affairs-for-26-may-2025 + text: On May 26, 2025, there are significant developments happening, including India's launch of the Bharat Forecasting + System to boost weather prediction and disaster preparedness + type: text + id: msg_01WWvGdHT1E2kYaV5ZLWMB2N + model: claude-3-5-sonnet-20241022 + role: assistant + stop_reason: end_turn + stop_sequence: null + type: message + usage: + cache_creation_input_tokens: 0 + cache_read_input_tokens: 0 + input_tokens: 16312 + output_tokens: 258 + server_tool_use: + web_search_requests: 1 + service_tier: standard + status: + code: 200 + message: OK +version: 1 diff --git a/tests/models/cassettes/test_google/test_google_model_code_execution_tool.yaml b/tests/models/cassettes/test_google/test_google_model_code_execution_tool.yaml new file mode 100644 index 000000000..7301ab260 --- /dev/null +++ b/tests/models/cassettes/test_google/test_google_model_code_execution_tool.yaml @@ -0,0 +1,89 @@ +interactions: +- request: + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '234' + content-type: + - application/json + host: + - generativelanguage.googleapis.com + method: POST + parsed_body: + contents: + - parts: + - text: What day is today in Utrecht? + role: user + generationConfig: {} + systemInstruction: + parts: + - text: You are a helpful chatbot. + role: user + tools: + - codeExecution: {} + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent + response: + headers: + alt-svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + content-length: + - '1462' + content-type: + - application/json; charset=UTF-8 + server-timing: + - gfet4t7; dur=7674 + transfer-encoding: + - chunked + vary: + - Origin + - X-Origin + - Referer + parsed_body: + candidates: + - content: + parts: + - text: |+ + To determine the current day in Utrecht, I need to know the current date and time. I will use a tool to get this information. + + - executableCode: + code: | + import datetime + import pytz + + utrecht_timezone = pytz.timezone('Europe/Amsterdam') + now_utrecht = datetime.datetime.now(utrecht_timezone) + print(now_utrecht.strftime("%A, %Y-%m-%d")) + language: PYTHON + - codeExecutionResult: + outcome: OUTCOME_OK + output: | + Wednesday, 2025-05-28 + - text: | + Today is Wednesday, May 28, 2025 in Utrecht. + role: model + finishReason: STOP + modelVersion: gemini-2.0-flash + responseId: 8ww3aLDxJY24qsMP97vYeA + usageMetadata: + candidatesTokenCount: 119 + candidatesTokensDetails: + - modality: TEXT + tokenCount: 119 + promptTokenCount: 13 + promptTokensDetails: + - modality: TEXT + tokenCount: 13 + toolUsePromptTokenCount: 114 + toolUsePromptTokensDetails: + - modality: TEXT + tokenCount: 114 + totalTokenCount: 246 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/models/cassettes/test_google/test_google_model_web_search_tool.yaml b/tests/models/cassettes/test_google/test_google_model_web_search_tool.yaml new file mode 100644 index 000000000..e880750ac --- /dev/null +++ b/tests/models/cassettes/test_google/test_google_model_web_search_tool.yaml @@ -0,0 +1,199 @@ +interactions: +- request: + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '233' + content-type: + - application/json + host: + - generativelanguage.googleapis.com + method: POST + parsed_body: + contents: + - parts: + - text: What day is today in Utrecht? + role: user + generationConfig: {} + systemInstruction: + parts: + - text: You are a helpful chatbot. + role: user + tools: + - googleSearch: {} + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent + response: + headers: + alt-svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + content-length: + - '5991' + content-type: + - application/json; charset=UTF-8 + server-timing: + - gfet4t7; dur=1340 + transfer-encoding: + - chunked + vary: + - Origin + - X-Origin + - Referer + parsed_body: + candidates: + - content: + parts: + - text: | + Today is Wednesday, May 28, 2025, in Utrecht. + role: model + finishReason: STOP + groundingMetadata: + retrievalMetadata: {} + searchEntryPoint: + renderedContent: | + +
+
+ + + + + + + + + + + + + +
+
+ +
+ webSearchQueries: + - current date Utrecht + modelVersion: gemini-2.0-flash + responseId: LN42aKzNCaqvgLUP1Lz-8As + usageMetadata: + candidatesTokenCount: 19 + candidatesTokensDetails: + - modality: TEXT + tokenCount: 19 + promptTokenCount: 13 + promptTokensDetails: + - modality: TEXT + tokenCount: 13 + totalTokenCount: 32 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/models/cassettes/test_groq/test_groq_model_web_search_tool.yaml b/tests/models/cassettes/test_groq/test_groq_model_web_search_tool.yaml new file mode 100644 index 000000000..73e9b042c --- /dev/null +++ b/tests/models/cassettes/test_groq/test_groq_model_web_search_tool.yaml @@ -0,0 +1,273 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '106' + content-type: + - application/json + host: + - api.groq.com + method: POST + parsed_body: + messages: + - content: What day is today? + role: user + model: compound-beta + n: 1 + stream: false + uri: https://api.groq.com/openai/v1/chat/completions + response: + headers: + alt-svc: + - h3=":443"; ma=86400 + cache-control: + - private, max-age=0, no-store, no-cache, must-revalidate + connection: + - keep-alive + content-length: + - '13044' + content-type: + - application/json + transfer-encoding: + - chunked + vary: + - Origin + parsed_body: + choices: + - finish_reason: stop + index: 0 + logprobs: null + message: + content: The current day is Tuesday. + executed_tools: + - arguments: '{"query": "What is the current date?"}' + index: 0 + output: |+ + Title: Today's Date - Find Out Quickly What's The Date Today ️ + URL: https://calendarhours.com/todays-date/ + Content: The current date in RFC 2822 Format with shortened day of week, numerical date, three-letter month abbreviation, year, time, and time zone is: Tue, 13 May 2025 06:07:56 -0400; The current date in Unix Epoch Format with number of seconds that have elapsed since January 1, 1970 (midnight UTC/GMT) is: + Score: 0.8299 + + Title: Today's Date | Current date now - MaxTables + URL: https://maxtables.com/tools/todays-date.html + Content: The current date, including day of the week, month, day, and year. The exact time, down to seconds. Details on the time zone, its location, and its GMT difference. A tool to select the present date. A visual calendar chart. Why would I need to check Today's Date on this platform instead of my device? + Score: 0.7223 + + Title: Current Time and Date - Exact Time! + URL: https://time-and-calendar.com/ + Content: The actual time is: Mon May 12 2025 22:14:39 GMT-0700 (Pacific Daylight Time) Your computer time is: 22:14:38 The time of your computer is synchronized with our web server. This mean that it is synchonizing in real time with our server clock. + Score: 0.6799 + + Title: Today's Date - CalendarDate.com + URL: https://www.calendardate.com/todays.htm + Content: Details about today's date with count of days, weeks, and months, Sun and Moon cycles, Zodiac signs and holidays. Monday May 12, 2025 . Home; Calendars. 2025 Calendar; ... Current Season Today: Spring with 40 days until the start of Summer. S. Hemishpere flip seasons - i.e. Winter is Summer. + Score: 0.6416 + + Title: What is the date today | Today's Date + URL: https://www.datetoday.info/ + Content: Master time tracking with Today's Date. Stay updated with real-time information on current date, time, day of the week, days left in the week, current day and remaining days of the year. Explore time in globally accepted formats. Keep up with the current week and month, along with the remaining weeks and months for the year. Embrace efficient time tracking with Today's Date. + Score: 0.6282 + + Title: Explore Today's Date, Time Zones, Holidays & More + URL: https://whatdateis.today/ + Content: Check what date and time it is today (May 8, 2025). View current time across different time zones, upcoming holidays, and use our date calculator. Your one-stop destination for all date and time information. + Score: 0.6181 + + Title: Today's Date and Time - Date and Time Tools + URL: https://todaysdatetime.com/ + Content: Discover today's exact date and time, learn about time zones, date formats, and explore our comprehensive collection of date and time tools including calculators, converters, and calendars. ... Get the exact current date and time, along with powerful calculation tools for all your scheduling needs. 12h. Today. Day 76 of year (366) Yesterday + Score: 0.5456 + + Title: Current Time Now - What time is it? - RapidTables.com + URL: https://www.rapidtables.com/tools/current-time.html + Content: This page includes the following information: Current time: hours, minutes, seconds. Today's date: day of week, month, day, year. Time zone with location and GMT offset. + Score: 0.4255 + + Title: Current Time + URL: https://www.timeanddate.com/ + Content: Welcome to the world's top site for time, time zones, and astronomy. Organize your life with free online info and tools you can rely on. No sign-up needed. Sign in. News. News Home; Astronomy News; ... Current Time. Monday May 12, 2025 Roanoke Rapids, North Carolina, USA. Set home location. 11:27: 03 pm. World Clock. + Score: 0.3876 + + Title: Current local time in the United States - World clock + URL: https://dateandtime.info/country.php?code=US + Content: Time and Date of DST Change Time Change; DST started: Sunday, March 9, 2025 at 2:00 AM: The clocks were put forward an hour to 3:00 AM. DST ends: Sunday, November 2, 2025 at 2:00 AM: The clocks will be put back an hour to 1:00 AM. DST starts: Sunday, March 8, 2026 at 2:00 AM: The clocks will be put forward an hour to 3:00 AM. + Score: 0.3042 + + Title: Time.is - exact time, any time zone + URL: https://time.is/ + Content: 7 million locations, 58 languages, synchronized with atomic clock time. Time.is. Get Time.is Ad-free! Exact time now: 05:08:45. Tuesday, 13 May, 2025, week 20. Sun: ↑ 05:09 ↓ 20:45 (15h 36m) - More info - Make London time default - Remove from favorite locations + Score: 0.2796 + + Title: Time in United States now + URL: https://time.is/United_States + Content: Exact time now, time zone, time difference, sunrise/sunset time and key facts for United States. Time.is. Get Time.is Ad-free! Time in United States now . 11:17:42 PM. Monday, May 12, 2025. United States (incl. dependent territories) has 11 time zones. The time zone for the capital Washington, D.C. is used here. + Score: 0.2726 + + Title: Current Local Time in the United States - timeanddate.com + URL: https://www.timeanddate.com/worldclock/usa + Content: United States time now. USA time zones and time zone map with current time in each state. + Score: 0.2519 + + Title: Current local time in United States - World Time Clock & Map + URL: https://24timezones.com/United-States/time + Content: Check the current time in United States and time zone information, the UTC offset and daylight saving time dates in 2025. + Score: 0.2221 + + Title: The World Clock — Worldwide - timeanddate.com + URL: https://www.timeanddate.com/worldclock/ + Content: World time and date for cities in all time zones. International time right now. Takes into account all DST clock changes. + Score: 0.2134 + + type: search + reasoning: |2- + + To determine the current day, I need to access real-time information. I will use the search tool to find out the current date. + + + search(What is the current date?) + + Title: Today's Date - Find Out Quickly What's The Date Today ️ + URL: https://calendarhours.com/todays-date/ + Content: The current date in RFC 2822 Format with shortened day of week, numerical date, three-letter month abbreviation, year, time, and time zone is: Tue, 13 May 2025 06:07:56 -0400; The current date in Unix Epoch Format with number of seconds that have elapsed since January 1, 1970 (midnight UTC/GMT) is: + Score: 0.8299 + + Title: Today's Date | Current date now - MaxTables + URL: https://maxtables.com/tools/todays-date.html + Content: The current date, including day of the week, month, day, and year. The exact time, down to seconds. Details on the time zone, its location, and its GMT difference. A tool to select the present date. A visual calendar chart. Why would I need to check Today's Date on this platform instead of my device? + Score: 0.7223 + + Title: Current Time and Date - Exact Time! + URL: https://time-and-calendar.com/ + Content: The actual time is: Mon May 12 2025 22:14:39 GMT-0700 (Pacific Daylight Time) Your computer time is: 22:14:38 The time of your computer is synchronized with our web server. This mean that it is synchonizing in real time with our server clock. + Score: 0.6799 + + Title: Today's Date - CalendarDate.com + URL: https://www.calendardate.com/todays.htm + Content: Details about today's date with count of days, weeks, and months, Sun and Moon cycles, Zodiac signs and holidays. Monday May 12, 2025 . Home; Calendars. 2025 Calendar; ... Current Season Today: Spring with 40 days until the start of Summer. S. Hemishpere flip seasons - i.e. Winter is Summer. + Score: 0.6416 + + Title: What is the date today | Today's Date + URL: https://www.datetoday.info/ + Content: Master time tracking with Today's Date. Stay updated with real-time information on current date, time, day of the week, days left in the week, current day and remaining days of the year. Explore time in globally accepted formats. Keep up with the current week and month, along with the remaining weeks and months for the year. Embrace efficient time tracking with Today's Date. + Score: 0.6282 + + Title: Explore Today's Date, Time Zones, Holidays & More + URL: https://whatdateis.today/ + Content: Check what date and time it is today (May 8, 2025). View current time across different time zones, upcoming holidays, and use our date calculator. Your one-stop destination for all date and time information. + Score: 0.6181 + + Title: Today's Date and Time - Date and Time Tools + URL: https://todaysdatetime.com/ + Content: Discover today's exact date and time, learn about time zones, date formats, and explore our comprehensive collection of date and time tools including calculators, converters, and calendars. ... Get the exact current date and time, along with powerful calculation tools for all your scheduling needs. 12h. Today. Day 76 of year (366) Yesterday + Score: 0.5456 + + Title: Current Time Now - What time is it? - RapidTables.com + URL: https://www.rapidtables.com/tools/current-time.html + Content: This page includes the following information: Current time: hours, minutes, seconds. Today's date: day of week, month, day, year. Time zone with location and GMT offset. + Score: 0.4255 + + Title: Current Time + URL: https://www.timeanddate.com/ + Content: Welcome to the world's top site for time, time zones, and astronomy. Organize your life with free online info and tools you can rely on. No sign-up needed. Sign in. News. News Home; Astronomy News; ... Current Time. Monday May 12, 2025 Roanoke Rapids, North Carolina, USA. Set home location. 11:27: 03 pm. World Clock. + Score: 0.3876 + + Title: Current local time in the United States - World clock + URL: https://dateandtime.info/country.php?code=US + Content: Time and Date of DST Change Time Change; DST started: Sunday, March 9, 2025 at 2:00 AM: The clocks were put forward an hour to 3:00 AM. DST ends: Sunday, November 2, 2025 at 2:00 AM: The clocks will be put back an hour to 1:00 AM. DST starts: Sunday, March 8, 2026 at 2:00 AM: The clocks will be put forward an hour to 3:00 AM. + Score: 0.3042 + + Title: Time.is - exact time, any time zone + URL: https://time.is/ + Content: 7 million locations, 58 languages, synchronized with atomic clock time. Time.is. Get Time.is Ad-free! Exact time now: 05:08:45. Tuesday, 13 May, 2025, week 20. Sun: ↑ 05:09 ↓ 20:45 (15h 36m) - More info - Make London time default - Remove from favorite locations + Score: 0.2796 + + Title: Time in United States now + URL: https://time.is/United_States + Content: Exact time now, time zone, time difference, sunrise/sunset time and key facts for United States. Time.is. Get Time.is Ad-free! Time in United States now . 11:17:42 PM. Monday, May 12, 2025. United States (incl. dependent territories) has 11 time zones. The time zone for the capital Washington, D.C. is used here. + Score: 0.2726 + + Title: Current Local Time in the United States - timeanddate.com + URL: https://www.timeanddate.com/worldclock/usa + Content: United States time now. USA time zones and time zone map with current time in each state. + Score: 0.2519 + + Title: Current local time in United States - World Time Clock & Map + URL: https://24timezones.com/United-States/time + Content: Check the current time in United States and time zone information, the UTC offset and daylight saving time dates in 2025. + Score: 0.2221 + + Title: The World Clock — Worldwide - timeanddate.com + URL: https://www.timeanddate.com/worldclock/ + Content: World time and date for cities in all time zones. International time right now. Takes into account all DST clock changes. + Score: 0.2134 + + + The current date is Tuesday, May 13, 2025. + + + + The current day is Tuesday. + role: assistant + created: 1747232793 + id: stub + model: compound-beta + object: chat.completion + system_fingerprint: null + usage: + completion_time: 0.312814 + completion_tokens: 117 + prompt_time: 0.737839 + prompt_tokens: 4287 + queue_time: 1.687734 + total_time: 1.050655 + total_tokens: 4404 + usage_breakdown: + models: + - model: llama-3.3-70b-versatile + usage: + completion_time: 0.033092212 + completion_tokens: 4 + prompt_time: 0.008749282 + prompt_tokens: 134 + queue_time: 0.251916212 + total_time: 0.041841494 + total_tokens: 138 + - model: meta-llama/llama-4-scout-17b-16e-instruct + usage: + completion_time: 0.198660413 + completion_tokens: 82 + prompt_time: 0.026323206 + prompt_tokens: 491 + queue_time: 0.556546565 + total_time: 0.224983619 + total_tokens: 573 + - model: meta-llama/llama-4-scout-17b-16e-instruct + usage: + completion_time: 0.047136438 + completion_tokens: 24 + prompt_time: 0.077990192 + prompt_tokens: 1926 + queue_time: 0.619169638 + total_time: 0.12512663 + total_tokens: 1950 + - model: llama-3.3-70b-versatile + usage: + completion_time: 0.033926045 + completion_tokens: 7 + prompt_time: 0.624777225 + prompt_tokens: 1736 + queue_time: 0.26010064499999996 + total_time: 0.65870327 + total_tokens: 1743 + x_groq: + id: stub + status: + code: 200 + message: OK +version: 1 diff --git a/tests/models/cassettes/test_openai/test_openai_web_search_tool.yaml b/tests/models/cassettes/test_openai/test_openai_web_search_tool.yaml new file mode 100644 index 000000000..cd48b3ea9 --- /dev/null +++ b/tests/models/cassettes/test_openai/test_openai_web_search_tool.yaml @@ -0,0 +1,79 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '218' + content-type: + - application/json + host: + - api.openai.com + method: POST + parsed_body: + messages: + - content: You are a helpful assistant. + role: system + - content: What day is today? + role: user + model: gpt-4o-search-preview + stream: false + web_search_options: + search_context_size: low + uri: https://api.openai.com/v1/chat/completions + response: + headers: + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + connection: + - keep-alive + content-length: + - '785' + content-type: + - application/json + openai-organization: + - pydantic-28gund + openai-processing-ms: + - '2051' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + parsed_body: + choices: + - finish_reason: stop + index: 0 + message: + annotations: [] + content: 'May 14, 2025, 8:51:29 AM ' + refusal: null + role: assistant + created: 1747227087 + id: chatcmpl-e05fbf7b-44c0-406d-8662-bc7a9a518747 + model: gpt-4o-search-preview-2025-03-11 + object: chat.completion + system_fingerprint: '' + usage: + completion_tokens: 17 + completion_tokens_details: + accepted_prediction_tokens: 0 + audio_tokens: 0 + reasoning_tokens: 0 + rejected_prediction_tokens: 0 + prompt_tokens: 11 + prompt_tokens_details: + audio_tokens: 0 + cached_tokens: 0 + total_tokens: 28 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/models/cassettes/test_openai/test_openai_web_search_tool_model_not_supported.yaml b/tests/models/cassettes/test_openai/test_openai_web_search_tool_model_not_supported.yaml new file mode 100644 index 000000000..0a102fa79 --- /dev/null +++ b/tests/models/cassettes/test_openai/test_openai_web_search_tool_model_not_supported.yaml @@ -0,0 +1,57 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '203' + content-type: + - application/json + host: + - api.openai.com + method: POST + parsed_body: + messages: + - content: You are a helpful assistant. + role: system + - content: What day is today? + role: user + model: gpt-4o + stream: false + web_search_options: + search_context_size: low + uri: https://api.openai.com/v1/chat/completions + response: + headers: + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + connection: + - keep-alive + content-length: + - '177' + content-type: + - application/json + openai-organization: + - pydantic-28gund + openai-processing-ms: + - '23' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + parsed_body: + error: + code: null + message: Web search options not supported with this model. + param: web_search_options + type: invalid_request_error + status: + code: 400 + message: Bad Request +version: 1 diff --git a/tests/models/cassettes/test_openai/test_openai_web_search_tool_with_user_location.yaml b/tests/models/cassettes/test_openai/test_openai_web_search_tool_with_user_location.yaml new file mode 100644 index 000000000..30a557451 --- /dev/null +++ b/tests/models/cassettes/test_openai/test_openai_web_search_tool_with_user_location.yaml @@ -0,0 +1,91 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '322' + content-type: + - application/json + host: + - api.openai.com + method: POST + parsed_body: + messages: + - content: You are a helpful assistant. + role: system + - content: What is the current temperature? + role: user + model: gpt-4o-search-preview + stream: false + web_search_options: + search_context_size: medium + user_location: + approximate: + city: Utrecht + country: NL + type: approximate + uri: https://api.openai.com/v1/chat/completions + response: + headers: + access-control-expose-headers: + - X-Request-ID + alt-svc: + - h3=":443"; ma=86400 + connection: + - keep-alive + content-length: + - '1656' + content-type: + - application/json + openai-organization: + - pydantic-28gund + openai-processing-ms: + - '3979' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + parsed_body: + choices: + - finish_reason: stop + index: 0 + message: + annotations: [] + content: "Het is momenteel zonnig in Utrecht met een temperatuur van 22°C.\n\n## Weer voor Utrecht, Nederland:\nHuidige + omstandigheden: Zonnig, 72°F (22°C)\n\nDagvoorspelling:\n* woensdag, mei 14: minimum: 48°F (9°C), maximum: 71°F + (22°C), beschrijving: Afnemende bewolking\n* donderdag, mei 15: minimum: 43°F (6°C), maximum: 67°F (20°C), beschrijving: + Na een bewolkt begin keert de zon terug\n* vrijdag, mei 16: minimum: 45°F (7°C), maximum: 64°F (18°C), beschrijving: + Overwegend zonnig\n* zaterdag, mei 17: minimum: 47°F (9°C), maximum: 68°F (20°C), beschrijving: Overwegend zonnig\n* + zondag, mei 18: minimum: 47°F (8°C), maximum: 68°F (20°C), beschrijving: Deels zonnig\n* maandag, mei 19: minimum: + 49°F (9°C), maximum: 70°F (21°C), beschrijving: Deels zonnig\n* dinsdag, mei 20: minimum: 49°F (10°C), maximum: + 72°F (22°C), beschrijving: Zonnig tot gedeeltelijk bewolkt\n " + refusal: null + role: assistant + created: 1747227540 + id: chatcmpl-da029146-a630-4224-9d12-7d808b031fbc + model: gpt-4o-search-preview-2025-03-11 + object: chat.completion + system_fingerprint: '' + usage: + completion_tokens: 293 + completion_tokens_details: + accepted_prediction_tokens: 0 + audio_tokens: 0 + reasoning_tokens: 0 + rejected_prediction_tokens: 0 + prompt_tokens: 12 + prompt_tokens_details: + audio_tokens: 0 + cached_tokens: 0 + total_tokens: 305 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/models/cassettes/test_openai_responses/test_openai_responses_model_web_search_tool.yaml b/tests/models/cassettes/test_openai_responses/test_openai_responses_model_web_search_tool.yaml new file mode 100644 index 000000000..5996ed6c4 --- /dev/null +++ b/tests/models/cassettes/test_openai_responses/test_openai_responses_model_web_search_tool.yaml @@ -0,0 +1,114 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '231' + content-type: + - application/json + host: + - api.openai.com + method: POST + parsed_body: + input: + - content: What day is it today? + role: user + instructions: You are a helpful assistant. + model: gpt-4o + stream: false + tool_choice: auto + tools: + - search_context_size: medium + type: web_search_preview + uri: https://api.openai.com/v1/responses + response: + headers: + alt-svc: + - h3=":443"; ma=86400 + connection: + - keep-alive + content-length: + - '2569' + content-type: + - application/json + openai-organization: + - pydantic-28gund + openai-processing-ms: + - '3632' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + parsed_body: + created_at: 1747227360 + error: null + id: resp_682492e0aab88191afcadcbbb85e1a490e7f27fcb18d04ed + incomplete_details: null + instructions: You are a helpful assistant. + max_output_tokens: null + metadata: {} + model: gpt-4o-2024-08-06 + object: response + output: + - id: ws_682492e1b7a08191b30170317ea7ee570e7f27fcb18d04ed + status: completed + type: web_search_call + - content: + - annotations: [] + text: "Today is Wednesday, May 14, 2025.\n\n## Weather for San Francisco, CA:\nCurrent Conditions: Mostly clear, + 50°F (10°C)\n\nDaily Forecast:\n* Wednesday, May 14: Low: 51°F (10°C), High: 65°F (18°C), Description: Areas of + low clouds early; otherwise, mostly sunny\n* Thursday, May 15: Low: 53°F (12°C), High: 66°F (19°C), Description: + Areas of low clouds, then sun\n* Friday, May 16: Low: 53°F (12°C), High: 64°F (18°C), Description: Partly sunny\n* + Saturday, May 17: Low: 52°F (11°C), High: 63°F (17°C), Description: Low clouds breaking for some sun; breezy in + the afternoon\n* Sunday, May 18: Low: 51°F (10°C), High: 68°F (20°C), Description: Clouds yielding to sun\n* Monday, + May 19: Low: 53°F (12°C), High: 68°F (20°C), Description: Sunny\n* Tuesday, May 20: Low: 52°F (11°C), High: 70°F + (21°C), Description: Mostly sunny\n " + type: output_text + id: msg_682492e36ab08191813ec707a61c272f0e7f27fcb18d04ed + role: assistant + status: completed + type: message + parallel_tool_calls: true + previous_response_id: null + reasoning: + effort: null + summary: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: + - search_context_size: medium + type: web_search_preview + user_location: + city: null + country: US + region: null + timezone: null + type: approximate + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 317 + input_tokens_details: + cached_tokens: 0 + output_tokens: 286 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 603 + user: null + status: + code: 200 + message: OK +version: 1 diff --git a/tests/models/cassettes/test_openai_responses/test_openai_responses_model_web_search_tool_with_user_location.yaml b/tests/models/cassettes/test_openai_responses/test_openai_responses_model_web_search_tool_with_user_location.yaml new file mode 100644 index 000000000..6ad0ff843 --- /dev/null +++ b/tests/models/cassettes/test_openai_responses/test_openai_responses_model_web_search_tool_with_user_location.yaml @@ -0,0 +1,118 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '313' + content-type: + - application/json + host: + - api.openai.com + method: POST + parsed_body: + input: + - content: What is the current temperature? + role: user + instructions: You are a helpful assistant. + model: gpt-4o + stream: false + tool_choice: auto + tools: + - search_context_size: medium + type: web_search_preview + user_location: + city: Utrecht + country: NL + type: approximate + uri: https://api.openai.com/v1/responses + response: + headers: + alt-svc: + - h3=":443"; ma=86400 + connection: + - keep-alive + content-length: + - '2631' + content-type: + - application/json + openai-organization: + - pydantic-28gund + openai-processing-ms: + - '3405' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + parsed_body: + created_at: 1747227481 + error: null + id: resp_682493599ce08191a7d279acb969c28b05f032fc13ed3d8b + incomplete_details: null + instructions: You are a helpful assistant. + max_output_tokens: null + metadata: {} + model: gpt-4o-2024-08-06 + object: response + output: + - id: ws_6824935a6ca481918cae37433d3e2f8005f032fc13ed3d8b + status: completed + type: web_search_call + - content: + - annotations: [] + text: "As of 12:58 PM on Wednesday, May 14, 2025, in Utrecht, Netherlands, the weather is sunny with a temperature + of 22°C (71°F).\n\n## Weather for Utrecht, Netherlands:\nCurrent Conditions: Sunny, 71°F (22°C)\n\nDaily Forecast:\n* + Wednesday, May 14: Low: 48°F (9°C), High: 71°F (22°C), Description: Clouds yielding to sun\n* Thursday, May 15: + Low: 43°F (6°C), High: 67°F (20°C), Description: After a cloudy start, sun returns\n* Friday, May 16: Low: 45°F + (7°C), High: 64°F (18°C), Description: Mostly sunny\n* Saturday, May 17: Low: 47°F (9°C), High: 68°F (20°C), Description: + Mostly sunny\n* Sunday, May 18: Low: 47°F (8°C), High: 68°F (20°C), Description: Some sun\n* Monday, May 19: Low: + 49°F (9°C), High: 70°F (21°C), Description: Delightful with partial sunshine\n* Tuesday, May 20: Low: 49°F (10°C), + High: 72°F (22°C), Description: Warm with sunshine and a few clouds\n " + type: output_text + id: msg_6824935c4dfc8191b0abacb1c0fd0a7805f032fc13ed3d8b + role: assistant + status: completed + type: message + parallel_tool_calls: true + previous_response_id: null + reasoning: + effort: null + summary: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: + - search_context_size: medium + type: web_search_preview + user_location: + city: Utrecht + country: NL + region: null + timezone: null + type: approximate + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 317 + input_tokens_details: + cached_tokens: 0 + output_tokens: 300 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 617 + user: null + status: + code: 200 + message: OK +version: 1 diff --git a/tests/models/test_anthropic.py b/tests/models/test_anthropic.py index 3891c5108..2b514b53a 100644 --- a/tests/models/test_anthropic.py +++ b/tests/models/test_anthropic.py @@ -14,6 +14,7 @@ from pydantic import BaseModel from pydantic_ai import Agent, ModelHTTPError, ModelRetry +from pydantic_ai.builtin_tools import CodeExecutionTool, WebSearchTool from pydantic_ai.exceptions import UserError from pydantic_ai.messages import ( BinaryContent, @@ -25,6 +26,8 @@ PartDeltaEvent, PartStartEvent, RetryPromptPart, + ServerToolCallPart, + ServerToolReturnPart, SystemPromptPart, TextPart, TextPartDelta, @@ -45,6 +48,8 @@ from anthropic import NOT_GIVEN, APIStatusError, AsyncAnthropic from anthropic.resources.beta import AsyncBeta from anthropic.types.beta import ( + BetaCodeExecutionResultBlock, + BetaCodeExecutionToolResultBlock, BetaContentBlock, BetaInputJSONDelta, BetaMessage, @@ -56,9 +61,12 @@ BetaRawMessageStartEvent, BetaRawMessageStopEvent, BetaRawMessageStreamEvent, + BetaServerToolUseBlock, BetaTextBlock, BetaToolUseBlock, BetaUsage, + BetaWebSearchResultBlock, + BetaWebSearchToolResultBlock, ) from anthropic.types.beta.beta_raw_message_delta_event import Delta @@ -1306,6 +1314,259 @@ async def test_anthropic_model_empty_message_on_history(allow_model_requests: No """) +@pytest.mark.vcr() +async def test_anthropic_web_search_tool(allow_model_requests: None, anthropic_api_key: str): + m = AnthropicModel('claude-3-5-sonnet-latest', provider=AnthropicProvider(api_key=anthropic_api_key)) + agent = Agent(m, builtin_tools=[WebSearchTool()]) + + result = await agent.run('What day is today?') + assert ( + result.all_messages() + == snapshot( + [ + ModelRequest(parts=[UserPromptPart(content='What day is today?', timestamp=IsDatetime())]), + ModelResponse( + parts=[ + TextPart(content="Let me search for current events to help establish today's date."), + ServerToolCallPart( + tool_name='web_search', + args={'query': 'current events news today May 26 2025'}, + tool_call_id='srvtoolu_01MqVvTi9LWTrMRuZ2KttD3M', + model_name='anthropic', + ), + ServerToolReturnPart( + tool_name='web_search_tool_result', + content=[ + BetaWebSearchResultBlock( + encrypted_content='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', + title='News: U.S. and World News Headlines : NPR', + type='web_search_result', + url='https://www.npr.org/sections/news/', + ), + BetaWebSearchResultBlock( + encrypted_content='ErwHCioIAxgCIiQ0NGFlNjc2Yy05NThmLTRkNjgtOTEwOC1lYWU5ZGU3YjM2NmISDK2W0Qu0wNgI6mf5EhoMNCqr1gVeM/Fj3PSYIjBrgyGKmTOrJLgDCXcOvRtrbigKDeccd5oypMBGnMVhm6h3Ade/9+vNOwI3ByflKmwqvwZqUUdfJ6+k9ZDrmb7VM6ktRqsZ4Z++yOdyubDNbsyM6RdwYuNi+bS5ZUON+rMd8+ZrQYlGYqq7NF43o5klxpac+Dsgx3OlKbu6Hq6eiKOQ3rdPGYlUYKdDouAx6RjypXjhYkqErPrjlFZhNv2lO6cohI0QU66p6b7G8UMVyweqYZ+2QYTFfbwU5VdIAOiQW8PBgNwPC5LRnidfbiT04VY+cEsNW04zOq9coXs4NgFRw2WDCZDBPGTEJex0xv7vD0/D0YpBhfiawNJ8FgBTI6q0gXQ2+YwqelVaZ+BDpu2JeRABLXiXQAMIiBBiayofacvfgJZ4omPY1JRiJwX5IpbLFqLcNz2fWr8veYedwrDZV/lOjyn755WTp2i89GD4Pv53htWrDOH8/YJBQ9u5KA2DFz7zAtRLyPqvPz3YaLMr3ATFvs8m0igrllgC5uaWPWfO/28RU7QNnxyBLGNonF3dtz3Uu2naeNvxjRhqCtUOON5odOahtPrRs5qkjv/UrL2YzlnfsRL4Qb/qsGJE6YWScvLhjBaum29Whk2p6RtYJqzzSqDbk0jxKe/hNatl3s2JF1bAW4L7p9FnsK1v/G7AYSaIYl4RDLGuL1bFOKGKVlUZtohNMws+gvTCYKdhQzfurimTsNIpBP4Ci6aJ+/yACa22AXGhZQqyiOS7yxI6zj3vZdQGFBle1TjDpzveY2Nz/kuuTCPbGsWt5kd9v7BkWvkNacqZ70KijyIk5dVt3H0q4eavyNLU0gF4hSCPDHW7eeWXTmNs1YniKiaHrwmOOqXjw2PCQrZv0i7UQRjDmRQqx9NtuqzMup9DRPbQuZM23b8JwzqA0Qjyxc5pTlWRL9aU+U7ZKOD1OdBszAU54c5N9jOca8S2Plt4TGJcAv2Wy73Bex74GPlkHcKWO8TJYhrV4ZF2nMjssncQEKCltJaZg6TJpazpLKoQ1XmYmgzebbVMRc8RTDXk335AYKkN62xRnfrDd5T5wBhGbPNQeF7PGigtAK/SpSpTna/vmGOBul/cONWOFFKNdY+FtCAGd4AOo3s/N8QUnKR66TEv9ocuVep1UZxV2fJcqIuJukutfT9eWPcou6VImLUzRQMYAw==', + page_age='4 days ago', + title='The Biggest News Stories Of 2025', + type='web_search_result', + url='https://92q.com/playlist/the-biggest-news-stories-of-2025/', + ), + BetaWebSearchResultBlock( + encrypted_content='EuYCCioIAxgCIiQ0NGFlNjc2Yy05NThmLTRkNjgtOTEwOC1lYWU5ZGU3YjM2NmISDCmLeaYgzUtJ4Mi+fBoMpktwxWvlSdpeNRRlIjDckkEHzeMKWP+vkSJ+7ci91OlLvn1nTU4wG+0am9miZ+68Q8XjsyCBGekIPeSsgpkq6QFTAvqrNhd55GMbj8VQtB/7oV66lwp8PzaymgQlLzCnxBdZ6IRYyEd6XwFOPrWCwyjtlKbwRiM2NIaNsGcrraBVrDfsjCz20qsDPGNsQf587z/TD3zWUSelhjhf+T5nDCEXUkYM2+4MaGP5Ty57Khh3WQr5q6Q46m85jBBF+akWf1uKZEgjgFug1ufj/8TXEEAaKCVY9YeXTXfYH8DocKveCXH4Bp9TNbgx55UrL8NdXiwdtpI/zqY+8hM/SiaVeXXI/Rbmjg3HzFTLfrH4wSrl5awdKWuGwQy8nqRISZlwNVnwFY0e8uc08xgD', + title='ABC News – Breaking News, Latest News and Videos', + type='web_search_result', + url='https://abcnews.go.com/', + ), + BetaWebSearchResultBlock( + encrypted_content='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', + title='Breaking News, Latest News and Videos | CNN', + type='web_search_result', + url='https://www.cnn.com/', + ), + BetaWebSearchResultBlock( + encrypted_content='ErQCCioIAxgCIiQ0NGFlNjc2Yy05NThmLTRkNjgtOTEwOC1lYWU5ZGU3YjM2NmISDGPat3RtBffd6jd9uxoMkx9uAfM4hgJRbrR4IjCBVNWqux+TsqDP0poLm+ss84SLrVR4rAcjrQSDPna9ZfR0OFhPjv2ko1ZVuBzeRE4qtwGOPV6my0G/y4nPEH8gNVc3y/8uZzh7O8CBrduzchEMd5RRXLlsC+bU/SjZ+5LBYGzAVwRCfVXIdaJ0/d8RYdJWHo3bvKc5Lu/WFPV6Po9gVHLOU5WVDsyzwmrvqzCYC0UhkUMa0yf5j7WTFaT+kgHZcFcbvYPG53USqNh0seahaaCC5fJRjRBTAvuyj4md+ppTjIXGZEp3rTMG3MTkv8t60MgPzn4ObLGEmBQIQrfES9G2BT1k7lUYAw==', # codespell:ignore + title='Philippines Top Stories: Politics, Environment, Education, Trending | Inquirer.net', + type='web_search_result', + url='https://newsinfo.inquirer.net', + ), + BetaWebSearchResultBlock( + encrypted_content='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', + title='Portal:Current events/May 2025 - Wikipedia', + type='web_search_result', + url='https://en.wikipedia.org/wiki/Portal:Current_events/May_2025', + ), + BetaWebSearchResultBlock( + encrypted_content='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', + title='Portal:Current events - Wikipedia', + type='web_search_result', + url='https://en.wikipedia.org/wiki/Portal:Current_events', + ), + BetaWebSearchResultBlock( + encrypted_content='EsoJCioIAxgCIiQ0NGFlNjc2Yy05NThmLTRkNjgtOTEwOC1lYWU5ZGU3YjM2NmISDAYjLeq1Kci5o2pi8BoMqJacE/46sh+pR01VIjDPMDUx0d3wj2zbUnowpxFwHvCCnCwLoPMxQsQm/dnvm0Fzfga5o6zXqDwWpXvUzhEqzQg/X9w2opL/m5o7bUw/TubgkYaR6l4t2n0oQlGgetbSj1gOEor/WWJ8bWXL3BZnS2xkIzwGrbLdlPDn/NoXICDQZ+P3IlA6B8CVwiijOnNq+x5nTpX0m794VFJIfO5SdupAiWqhWmtqt8XhcWs8W2gnhDDvNsBG6oH2ZsRenxt3n7a7eWo7yRk/KSHdBM9c+L5r3wu3Ul81DW9CuE6KqUdFjjePJfWKL8OvzfkvJjIqcRaqc/3RIRZbSPbimBiRMXtCBZeCYE1yeBs3xLQ7TJXgRM/9ScKromcFWckYpGXBYSGL8SiXXoBUD7pLsuP5FnRnZUkQLCHTLoId0/w4jVbuXmDh3oipIlGUQCSbp3FkogFB/CZFpKz4tY5E9WQ4pBkApGYgAeGgOOStiUW3pE9oCy5TRpCfilrg66RtJozGI+LWM/XYuuOwSK+f+/c6AaUJ7av+LCUSPFI6G1XErfHK/KeBSJp7ZVoRXn/f7yJXlZvybKQXdN6UtxqxRJbil9RnmmXsBc6cesWW/cHbz01V8tkaqcYdrtJdVM/LesICK77C/JYiA6PQsneeg5xdZDCUp7yUO9P/CHMBqhPB8geS9y4dG7UIdJrFbv43cGOiqoSBsBGCLCc7crptYYGydT6YBgKb+ktUJm14MfbF8lzKt6SVYpn8KWL2dyhsDbfi88h51fvZqDV6loTDpyHbMHeJoA4pIxLhkBIriQOLNnEIEwqTGy2XFy326bahzINKJVTY1mMq2v3O0Snl0DNcAZ1X/iHt393xPgdcSy6c2+sDRexvpU4grX1GGFD4E8kg1QP0fErasq17XzRVpnU7Kedk/ntU/X6zeI3aTEeyRNG7IPH67w6GyIF8XmgCh25H6bCBGN87N8hnPSVAy8/qIMcfZYaF1c8W/QB9n7HBWhQgdyZv3relj0Ur0xdRi2osqo+k2c0a9mmIVupbzpLAxfY7LiwU8Edsr+1WY62x1omk+b4XNiGnhHnrF4B2o+f89icgAVSqRo2ydqIUDnZUYewu8jjUg/j+WUI8yKqZHCgCRdkm4fDSOcK8faTeaITl1iI6XFbUicEWZzG87tFykNSv5fz+ueDbMj936cm97rPUhp/qMnS2uloAxmiWLcS2/oV605i97ccR9IlwB0tt259e9iCvltjxzcC6P95vbhLS94+xVNOG2fmQtzE8oyaREZBkwSjVHuJ3lDAxvHDRYY8F+lkuLE4AvLiye3CDAMXNyCrG+/xiQIBNUGs+1aV9edHMmwpCVs99Q+nHO1RBVPljY607Q6u06Wt4VHnY+45+IxzpHHWXxg3Jn8Lh1AuzFKEaRWaI7JDSCgJmYxjIwkUO7988PWjOmFLquOd6mQsQ6iVG/89zSwr019RlAQRDIbMimefHIYhLm4S/Y8TzPhLFXJ6FaxrPFAkkkp2LnLQUoNKlo2h64JaAerGAku2FEwn/vo3hsCXwILg6R4QYAw==', + title='Current Affairs Today – Current Affairs – 2025-26 - GKToday', + type='web_search_result', + url='https://www.gktoday.in/current-affairs/', + ), + BetaWebSearchResultBlock( + encrypted_content='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', + page_age='3 weeks ago', + title='May 6, 2025: Top news events to look out for today - People Daily', + type='web_search_result', + url='https://peopledaily.digital/news/may-6-2025-top-news-events-to-look-out-for-today', + ), + BetaWebSearchResultBlock( + encrypted_content='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', + page_age='7 hours ago', + title='26 May 2025 UPSC Current Affairs - Daily News Headlines', + type='web_search_result', + url='https://testbook.com/ias-preparation/upsc-current-affairs-for-26-may-2025', + ), + ], + tool_call_id=IsStr(), + timestamp=IsDatetime(), + ), + TextPart( + content="""\ + + +Based on the search results, today is Monday, May 26, 2025. This is confirmed by several sources: + +1. \ +""" + ), + TextPart(content="It's Memorial Day today, May 26, 2025"), + TextPart( + content="""\ + + +2. \ +""" + ), + TextPart( + content='May 2025 is the fifth month of the current common year. The month began on a Thursday and will end on a Saturday after 31 days' + ), + TextPart( + content="""\ + + +3. \ +""" + ), + TextPart( + content="On May 26, 2025, there are significant developments happening, including India's launch of the Bharat Forecasting System to boost weather prediction and disaster preparedness" + ), + ], + usage=Usage( + requests=1, + request_tokens=16312, + response_tokens=258, + total_tokens=16570, + details={ + 'cache_creation_input_tokens': 0, + 'cache_read_input_tokens': 0, + 'input_tokens': 16312, + 'output_tokens': 258, + }, + ), + model_name='claude-3-5-sonnet-20241022', + timestamp=IsDatetime(), + vendor_id=IsStr(), + ), + ] + ) + ) + + +@pytest.mark.vcr() +async def test_anthropic_code_execution_tool(allow_model_requests: None, anthropic_api_key: str): + m = AnthropicModel('claude-sonnet-4-0', provider=AnthropicProvider(api_key=anthropic_api_key)) + agent = Agent(m, builtin_tools=[CodeExecutionTool()]) + + result = await agent.run('How much is 3 * 12390?') + assert result.all_messages() == snapshot( + [ + ModelRequest(parts=[UserPromptPart(content='How much is 3 * 12390?', timestamp=IsDatetime())]), + ModelResponse( + parts=[ + TextPart(content="I'll calculate 3 * 12390 for you."), + ServerToolCallPart( + tool_name='code_execution', + args={ + 'code': """\ +result = 3 * 12390 +print(f"3 * 12390 = {result}")\ +""" + }, + tool_call_id=IsStr(), + model_name='anthropic', + ), + ServerToolReturnPart( + tool_name='code_execution_tool_result', + content=BetaCodeExecutionResultBlock( + content=[], + return_code=0, + stderr='', + stdout='3 * 12390 = 37170\n', + type='code_execution_result', + ), + tool_call_id=IsStr(), + timestamp=IsDatetime(), + ), + TextPart(content='The answer is **37,170**.'), + ], + usage=Usage( + requests=1, + request_tokens=1630, + response_tokens=105, + total_tokens=1735, + details={ + 'cache_creation_input_tokens': 0, + 'cache_read_input_tokens': 0, + 'input_tokens': 1630, + 'output_tokens': 105, + }, + ), + model_name='claude-sonnet-4-20250514', + timestamp=IsDatetime(), + vendor_id=IsStr(), + ), + ] + ) + + +@pytest.mark.vcr +async def test_anthropic_server_tool_pass_history_to_another_provider( + allow_model_requests: None, anthropic_api_key: str, openai_api_key: str +): + try: + from pydantic_ai.models.openai import OpenAIResponsesModel + from pydantic_ai.providers.openai import OpenAIProvider + except ImportError: + pytest.skip('OpenAI is not installed') + + openai_model = OpenAIResponsesModel('gpt-4.1', provider=OpenAIProvider(api_key=openai_api_key)) + anthropic_model = AnthropicModel('claude-3-5-sonnet-latest', provider=AnthropicProvider(api_key=anthropic_api_key)) + agent = Agent(anthropic_model, builtin_tools=[WebSearchTool()]) + + result = await agent.run('What day is today?') + assert result.output == snapshot("""\ +Let me search for today's date. + + + +Based on the search results, \n\ + +today is Monday, May 26, 2025 (Week 22) + +. This is notably \n\ + +Memorial Day, which was originally known as Decoration Day + +. \n\ + +The year 2025 is a regular year with 365 days + +.\ +""") + result = await agent.run('What day is tomorrow?', model=openai_model, message_history=result.all_messages()) + assert result.new_messages() == snapshot( + [ + ModelRequest(parts=[UserPromptPart(content='What day is tomorrow?', timestamp=IsDatetime())]), + ModelResponse( + parts=[TextPart(content='Tomorrow will be **Tuesday, May 27, 2025**.')], + usage=Usage( + request_tokens=410, + response_tokens=17, + total_tokens=427, + details={'reasoning_tokens': 0, 'cached_tokens': 0}, + ), + model_name='gpt-4.1-2025-04-14', + timestamp=IsDatetime(), + vendor_id='resp_6834631faf2481918638284f62855ddf040b4e5d7e74f261', + ), + ] + ) + + async def test_anthropic_empty_content_filtering(env: TestEnv): """Test the empty content filtering logic directly.""" @@ -1723,3 +1984,134 @@ async def get_user_country() -> str: Mexico City is not only the largest city in Mexico but also one of the largest metropolitan areas in the world, with a metropolitan population of over 21 million people. The city proper has a population of approximately 9 million people and serves as the capital and political, cultural, and economic center of Mexico.\ """) + + +async def test_anthropic_web_search_tool_pass_history_back(env: TestEnv, allow_model_requests: None): + """Test passing web search tool history back to Anthropic.""" + # Create the first mock response with server tool blocks + first_response = completion_message( + [ + BetaTextBlock(text='Let me search for the current date.', type='text'), + BetaServerToolUseBlock( + id='server_tool_123', name='web_search', input={'query': 'current date today'}, type='server_tool_use' + ), + BetaWebSearchToolResultBlock( + tool_use_id='server_tool_123', + type='web_search_tool_result', + content=[ + BetaWebSearchResultBlock( + title='Current Date and Time', + url='https://example.com/date', + type='web_search_result', + encrypted_content='dummy_encrypted_content', + ) + ], + ), + BetaTextBlock(text='Today is January 2, 2025.', type='text'), + ], + BetaUsage(input_tokens=10, output_tokens=20), + ) + + # Create the second mock response that references the history + second_response = completion_message( + [BetaTextBlock(text='The web search result showed that today is January 2, 2025.', type='text')], + BetaUsage(input_tokens=50, output_tokens=30), + ) + + mock_client = MockAnthropic.create_mock([first_response, second_response]) + m = AnthropicModel('claude-3-5-sonnet-latest', provider=AnthropicProvider(anthropic_client=mock_client)) + agent = Agent(m, builtin_tools=[WebSearchTool()]) + + # First run to get server tool history + result = await agent.run('What day is today?') + + # Verify we have server tool parts in the history + server_tool_calls = [p for m in result.all_messages() for p in m.parts if isinstance(p, ServerToolCallPart)] + server_tool_returns = [p for m in result.all_messages() for p in m.parts if isinstance(p, ServerToolReturnPart)] + assert len(server_tool_calls) == 1 + assert len(server_tool_returns) == 1 + assert server_tool_calls[0].tool_name == 'web_search' + assert server_tool_returns[0].tool_name == 'web_search_tool_result' + + # Pass the history back to another Anthropic agent run + agent2 = Agent(m) + result2 = await agent2.run('What was the web search result?', message_history=result.all_messages()) + assert result2.output == 'The web search result showed that today is January 2, 2025.' + + +async def test_anthropic_code_execution_tool_pass_history_back(env: TestEnv, allow_model_requests: None): + """Test passing code execution tool history back to Anthropic.""" + # Create the first mock response with server tool blocks + first_response = completion_message( + [ + BetaTextBlock(text='Let me calculate 2 + 2.', type='text'), + BetaServerToolUseBlock( + id='server_tool_456', name='code_execution', input={'code': 'print(2 + 2)'}, type='server_tool_use' + ), + BetaCodeExecutionToolResultBlock( + tool_use_id='server_tool_456', + type='code_execution_tool_result', + content=BetaCodeExecutionResultBlock( + content=[], + return_code=0, + stderr='', + stdout='4\n', + type='code_execution_result', + ), + ), + BetaTextBlock(text='The result is 4.', type='text'), + ], + BetaUsage(input_tokens=10, output_tokens=20), + ) + + # Create the second mock response that references the history + second_response = completion_message( + [BetaTextBlock(text='The code execution returned the result: 4', type='text')], + BetaUsage(input_tokens=50, output_tokens=30), + ) + + mock_client = MockAnthropic.create_mock([first_response, second_response]) + m = AnthropicModel('claude-3-5-sonnet-latest', provider=AnthropicProvider(anthropic_client=mock_client)) + agent = Agent(m, builtin_tools=[CodeExecutionTool()]) + + # First run to get server tool history + result = await agent.run('What is 2 + 2?') + + # Verify we have server tool parts in the history + server_tool_calls = [p for m in result.all_messages() for p in m.parts if isinstance(p, ServerToolCallPart)] + server_tool_returns = [p for m in result.all_messages() for p in m.parts if isinstance(p, ServerToolReturnPart)] + assert len(server_tool_calls) == 1 + assert len(server_tool_returns) == 1 + assert server_tool_calls[0].tool_name == 'code_execution' + assert server_tool_returns[0].tool_name == 'code_execution_tool_result' + + # Pass the history back to another Anthropic agent run + agent2 = Agent(m) + result2 = await agent2.run('What was the code execution result?', message_history=result.all_messages()) + assert result2.output == 'The code execution returned the result: 4' + + +async def test_anthropic_unsupported_server_tool_name_error(): + """Test that unsupported server tool names raise an error.""" + from pydantic_ai.messages import ModelMessage, ModelResponse, ServerToolReturnPart + + env = TestEnv() + env.set('ANTHROPIC_API_KEY', 'test-key') + model = AnthropicModel('claude-3-5-sonnet-latest', provider='anthropic') + + # Create a message with an unsupported server tool name + messages: list[ModelMessage] = [ + ModelResponse( + parts=[ + ServerToolReturnPart( + tool_name='unsupported_tool', # This should trigger the error + content='some content', + tool_call_id='test_id', + ) + ] + ) + ] + + # This should raise a ValueError + with pytest.raises(ValueError, match='Unsupported tool name: unsupported_tool'): + await model._map_message(messages) # type: ignore[attr-defined] diff --git a/tests/models/test_fallback.py b/tests/models/test_fallback.py index 89709d4a2..38aad6332 100644 --- a/tests/models/test_fallback.py +++ b/tests/models/test_fallback.py @@ -127,7 +127,7 @@ def test_first_failed_instrumented(capfire: CaptureLogfire) -> None: 'end_time': 3000000000, 'attributes': { 'gen_ai.operation.name': 'chat', - 'model_request_parameters': '{"function_tools": [], "output_mode": "text", "output_object": null, "output_tools": [], "allow_text_output": true}', + 'model_request_parameters': '{"function_tools": [], "builtin_tools": [], "output_mode": "text", "output_object": null, "output_tools": [], "allow_text_output": true}', 'logfire.span_type': 'span', 'logfire.msg': 'chat fallback:function:failure_response:,function:success_response:', 'gen_ai.system': 'function', @@ -200,7 +200,7 @@ async def test_first_failed_instrumented_stream(capfire: CaptureLogfire) -> None 'end_time': 3000000000, 'attributes': { 'gen_ai.operation.name': 'chat', - 'model_request_parameters': '{"function_tools": [], "output_mode": "text", "output_object": null, "output_tools": [], "allow_text_output": true}', + 'model_request_parameters': '{"function_tools": [], "builtin_tools": [], "output_mode": "text", "output_object": null, "output_tools": [], "allow_text_output": true}', 'logfire.span_type': 'span', 'logfire.msg': 'chat fallback:function::failure_response_stream,function::success_response_stream', 'gen_ai.system': 'function', @@ -272,7 +272,7 @@ def test_all_failed_instrumented(capfire: CaptureLogfire) -> None: 'gen_ai.operation.name': 'chat', 'gen_ai.system': 'fallback:function,function', 'gen_ai.request.model': 'fallback:function:failure_response:,function:failure_response:', - 'model_request_parameters': '{"function_tools": [], "output_mode": "text", "output_object": null, "output_tools": [], "allow_text_output": true}', + 'model_request_parameters': '{"function_tools": [], "builtin_tools": [], "output_mode": "text", "output_object": null, "output_tools": [], "allow_text_output": true}', 'logfire.json_schema': '{"type": "object", "properties": {"model_request_parameters": {"type": "object"}}}', 'logfire.span_type': 'span', 'logfire.msg': 'chat fallback:function:failure_response:,function:failure_response:', diff --git a/tests/models/test_google.py b/tests/models/test_google.py index 29bd5d201..a8549b93e 100644 --- a/tests/models/test_google.py +++ b/tests/models/test_google.py @@ -5,12 +5,14 @@ from typing import Any import pytest +from google.genai.types import Language from httpx import Timeout from inline_snapshot import Is, snapshot from pydantic import BaseModel from typing_extensions import TypedDict from pydantic_ai.agent import Agent +from pydantic_ai.builtin_tools import CodeExecutionTool, WebSearchTool from pydantic_ai.exceptions import ModelRetry, UnexpectedModelBehavior, UserError from pydantic_ai.messages import ( AudioUrl, @@ -25,6 +27,8 @@ PartDeltaEvent, PartStartEvent, RetryPromptPart, + ServerToolCallPart, + ServerToolReturnPart, SystemPromptPart, TextPart, TextPartDelta, @@ -553,6 +557,78 @@ async def test_google_model_safety_settings(allow_model_requests: None, google_p await agent.run('Tell me a joke about a Brazilians.') +async def test_google_model_web_search_tool(allow_model_requests: None, google_provider: GoogleProvider): + m = GoogleModel('gemini-2.0-flash', provider=google_provider) + agent = Agent(m, system_prompt='You are a helpful chatbot.', builtin_tools=[WebSearchTool()]) + + result = await agent.run('What day is today in Utrecht?') + assert result.output == snapshot('Today is Wednesday, May 28, 2025, in Utrecht.\n') + + +async def test_google_model_code_execution_tool(allow_model_requests: None, google_provider: GoogleProvider): + m = GoogleModel('gemini-2.0-flash', provider=google_provider) + agent = Agent(m, system_prompt='You are a helpful chatbot.', builtin_tools=[CodeExecutionTool()]) + + result = await agent.run('What day is today in Utrecht?') + assert result.all_messages() == snapshot( + [ + ModelRequest( + parts=[ + SystemPromptPart(content='You are a helpful chatbot.', timestamp=IsDatetime()), + UserPromptPart(content='What day is today in Utrecht?', timestamp=IsDatetime()), + ] + ), + ModelResponse( + parts=[ + TextPart( + content="""\ +To determine the current day in Utrecht, I need to know the current date and time. I will use a tool to get this information. + +""" + ), + ServerToolCallPart( + tool_name='code_execution', + args={ + 'code': """\ +import datetime +import pytz + +utrecht_timezone = pytz.timezone('Europe/Amsterdam') +now_utrecht = datetime.datetime.now(utrecht_timezone) +print(now_utrecht.strftime("%A, %Y-%m-%d")) +""", + 'language': Language.PYTHON, + }, + tool_call_id=IsStr(), + ), + ServerToolReturnPart( + tool_name='code_execution', + content='Wednesday, 2025-05-28\n', + tool_call_id="It doesn't have.", + timestamp=IsDatetime(), + ), + TextPart(content='Today is Wednesday, May 28, 2025 in Utrecht.\n'), + ], + usage=Usage( + requests=1, + request_tokens=13, + response_tokens=119, + total_tokens=246, + details={ + 'tool_use_prompt_tokens': 114, + 'text_candidates_tokens': 119, + 'text_prompt_tokens': 13, + 'text_tool_use_prompt_tokens': 114, + }, + ), + model_name='gemini-2.0-flash', + timestamp=IsDatetime(), + vendor_details={'finish_reason': 'STOP'}, + ), + ] + ) + + async def test_google_model_empty_user_prompt(allow_model_requests: None, google_provider: GoogleProvider): m = GoogleModel('gemini-1.5-flash', provider=google_provider) agent = Agent(m, instructions='You are a helpful assistant.') @@ -747,7 +823,6 @@ async def test_google_url_input( @pytest.mark.skipif( not os.getenv('CI', False), reason='Requires properly configured local google vertex config to pass' ) -@pytest.mark.vcr() async def test_google_url_input_force_download(allow_model_requests: None) -> None: provider = GoogleProvider(project='pydantic-ai', location='us-central1') m = GoogleModel('gemini-2.0-flash', provider=provider) diff --git a/tests/models/test_groq.py b/tests/models/test_groq.py index 0cb4bef99..2764a7bb6 100644 --- a/tests/models/test_groq.py +++ b/tests/models/test_groq.py @@ -15,12 +15,15 @@ from typing_extensions import TypedDict from pydantic_ai import Agent, ModelHTTPError, ModelRetry, UnexpectedModelBehavior +from pydantic_ai.builtin_tools import WebSearchTool from pydantic_ai.messages import ( BinaryContent, ImageUrl, ModelRequest, ModelResponse, RetryPromptPart, + ServerToolCallPart, + ServerToolReturnPart, SystemPromptPart, TextPart, ThinkingPart, @@ -521,7 +524,6 @@ async def test_no_delta(allow_model_requests: None): assert result.is_complete -@pytest.mark.vcr() async def test_extra_headers(allow_model_requests: None, groq_api_key: str): # This test doesn't do anything, it's just here to ensure that calls with `extra_headers` don't cause errors, including type. m = GroqModel('llama-3.3-70b-versatile', provider=GroqProvider(api_key=groq_api_key)) @@ -529,7 +531,6 @@ async def test_extra_headers(allow_model_requests: None, groq_api_key: str): await agent.run('hello') -@pytest.mark.vcr() async def test_image_url_input(allow_model_requests: None, groq_api_key: str): m = GroqModel('meta-llama/llama-4-scout-17b-16e-instruct', provider=GroqProvider(api_key=groq_api_key)) agent = Agent(m) @@ -545,7 +546,6 @@ async def test_image_url_input(allow_model_requests: None, groq_api_key: str): ) -@pytest.mark.vcr() async def test_image_as_binary_content_tool_response( allow_model_requests: None, groq_api_key: str, image_content: BinaryContent ): @@ -684,6 +684,207 @@ async def test_groq_model_instructions(allow_model_requests: None, groq_api_key: ) +async def test_groq_model_web_search_tool(allow_model_requests: None, groq_api_key: str): + m = GroqModel('compound-beta', provider=GroqProvider(api_key=groq_api_key)) + agent = Agent(m, builtin_tools=[WebSearchTool()]) + + result = await agent.run('What day is today?') + assert result.output == snapshot('The current day is Tuesday.') + assert result.all_messages() == snapshot( + [ + ModelRequest(parts=[UserPromptPart(content='What day is today?', timestamp=IsDatetime())]), + ModelResponse( + parts=[ + ServerToolCallPart( + tool_name='search', + args='{"query": "What is the current date?"}', + tool_call_id=IsStr(), + model_name='groq', + ), + ServerToolReturnPart( + tool_name='search', + content="""\ +Title: Today's Date - Find Out Quickly What's The Date Today ️ +URL: https://calendarhours.com/todays-date/ +Content: The current date in RFC 2822 Format with shortened day of week, numerical date, three-letter month abbreviation, year, time, and time zone is: Tue, 13 May 2025 06:07:56 -0400; The current date in Unix Epoch Format with number of seconds that have elapsed since January 1, 1970 (midnight UTC/GMT) is: +Score: 0.8299 + +Title: Today's Date | Current date now - MaxTables +URL: https://maxtables.com/tools/todays-date.html +Content: The current date, including day of the week, month, day, and year. The exact time, down to seconds. Details on the time zone, its location, and its GMT difference. A tool to select the present date. A visual calendar chart. Why would I need to check Today's Date on this platform instead of my device? +Score: 0.7223 + +Title: Current Time and Date - Exact Time! +URL: https://time-and-calendar.com/ +Content: The actual time is: Mon May 12 2025 22:14:39 GMT-0700 (Pacific Daylight Time) Your computer time is: 22:14:38 The time of your computer is synchronized with our web server. This mean that it is synchonizing in real time with our server clock. +Score: 0.6799 + +Title: Today's Date - CalendarDate.com +URL: https://www.calendardate.com/todays.htm +Content: Details about today's date with count of days, weeks, and months, Sun and Moon cycles, Zodiac signs and holidays. Monday May 12, 2025 . Home; Calendars. 2025 Calendar; ... Current Season Today: Spring with 40 days until the start of Summer. S. Hemishpere flip seasons - i.e. Winter is Summer. +Score: 0.6416 + +Title: What is the date today | Today's Date +URL: https://www.datetoday.info/ +Content: Master time tracking with Today's Date. Stay updated with real-time information on current date, time, day of the week, days left in the week, current day and remaining days of the year. Explore time in globally accepted formats. Keep up with the current week and month, along with the remaining weeks and months for the year. Embrace efficient time tracking with Today's Date. +Score: 0.6282 + +Title: Explore Today's Date, Time Zones, Holidays & More +URL: https://whatdateis.today/ +Content: Check what date and time it is today (May 8, 2025). View current time across different time zones, upcoming holidays, and use our date calculator. Your one-stop destination for all date and time information. +Score: 0.6181 + +Title: Today's Date and Time - Date and Time Tools +URL: https://todaysdatetime.com/ +Content: Discover today's exact date and time, learn about time zones, date formats, and explore our comprehensive collection of date and time tools including calculators, converters, and calendars. ... Get the exact current date and time, along with powerful calculation tools for all your scheduling needs. 12h. Today. Day 76 of year (366) Yesterday +Score: 0.5456 + +Title: Current Time Now - What time is it? - RapidTables.com +URL: https://www.rapidtables.com/tools/current-time.html +Content: This page includes the following information: Current time: hours, minutes, seconds. Today's date: day of week, month, day, year. Time zone with location and GMT offset. +Score: 0.4255 + +Title: Current Time +URL: https://www.timeanddate.com/ +Content: Welcome to the world's top site for time, time zones, and astronomy. Organize your life with free online info and tools you can rely on. No sign-up needed. Sign in. News. News Home; Astronomy News; ... Current Time. Monday May 12, 2025 Roanoke Rapids, North Carolina, USA. Set home location. 11:27: 03 pm. World Clock. +Score: 0.3876 + +Title: Current local time in the United States - World clock +URL: https://dateandtime.info/country.php?code=US +Content: Time and Date of DST Change Time Change; DST started: Sunday, March 9, 2025 at 2:00 AM: The clocks were put forward an hour to 3:00 AM. DST ends: Sunday, November 2, 2025 at 2:00 AM: The clocks will be put back an hour to 1:00 AM. DST starts: Sunday, March 8, 2026 at 2:00 AM: The clocks will be put forward an hour to 3:00 AM. +Score: 0.3042 + +Title: Time.is - exact time, any time zone +URL: https://time.is/ +Content: 7 million locations, 58 languages, synchronized with atomic clock time. Time.is. Get Time.is Ad-free! Exact time now: 05:08:45. Tuesday, 13 May, 2025, week 20. Sun: ↑ 05:09 ↓ 20:45 (15h 36m) - More info - Make London time default - Remove from favorite locations +Score: 0.2796 + +Title: Time in United States now +URL: https://time.is/United_States +Content: Exact time now, time zone, time difference, sunrise/sunset time and key facts for United States. Time.is. Get Time.is Ad-free! Time in United States now . 11:17:42 PM. Monday, May 12, 2025. United States (incl. dependent territories) has 11 time zones. The time zone for the capital Washington, D.C. is used here. +Score: 0.2726 + +Title: Current Local Time in the United States - timeanddate.com +URL: https://www.timeanddate.com/worldclock/usa +Content: United States time now. USA time zones and time zone map with current time in each state. +Score: 0.2519 + +Title: Current local time in United States - World Time Clock & Map +URL: https://24timezones.com/United-States/time +Content: Check the current time in United States and time zone information, the UTC offset and daylight saving time dates in 2025. +Score: 0.2221 + +Title: The World Clock — Worldwide - timeanddate.com +URL: https://www.timeanddate.com/worldclock/ +Content: World time and date for cities in all time zones. International time right now. Takes into account all DST clock changes. +Score: 0.2134 + +""", + tool_call_id=IsStr(), + timestamp=IsDatetime(), + ), + ThinkingPart( + content="""\ + +To determine the current day, I need to access real-time information. I will use the search tool to find out the current date. + + +search(What is the current date?) + +Title: Today's Date - Find Out Quickly What's The Date Today ️ +URL: https://calendarhours.com/todays-date/ +Content: The current date in RFC 2822 Format with shortened day of week, numerical date, three-letter month abbreviation, year, time, and time zone is: Tue, 13 May 2025 06:07:56 -0400; The current date in Unix Epoch Format with number of seconds that have elapsed since January 1, 1970 (midnight UTC/GMT) is: +Score: 0.8299 + +Title: Today's Date | Current date now - MaxTables +URL: https://maxtables.com/tools/todays-date.html +Content: The current date, including day of the week, month, day, and year. The exact time, down to seconds. Details on the time zone, its location, and its GMT difference. A tool to select the present date. A visual calendar chart. Why would I need to check Today's Date on this platform instead of my device? +Score: 0.7223 + +Title: Current Time and Date - Exact Time! +URL: https://time-and-calendar.com/ +Content: The actual time is: Mon May 12 2025 22:14:39 GMT-0700 (Pacific Daylight Time) Your computer time is: 22:14:38 The time of your computer is synchronized with our web server. This mean that it is synchonizing in real time with our server clock. +Score: 0.6799 + +Title: Today's Date - CalendarDate.com +URL: https://www.calendardate.com/todays.htm +Content: Details about today's date with count of days, weeks, and months, Sun and Moon cycles, Zodiac signs and holidays. Monday May 12, 2025 . Home; Calendars. 2025 Calendar; ... Current Season Today: Spring with 40 days until the start of Summer. S. Hemishpere flip seasons - i.e. Winter is Summer. +Score: 0.6416 + +Title: What is the date today | Today's Date +URL: https://www.datetoday.info/ +Content: Master time tracking with Today's Date. Stay updated with real-time information on current date, time, day of the week, days left in the week, current day and remaining days of the year. Explore time in globally accepted formats. Keep up with the current week and month, along with the remaining weeks and months for the year. Embrace efficient time tracking with Today's Date. +Score: 0.6282 + +Title: Explore Today's Date, Time Zones, Holidays & More +URL: https://whatdateis.today/ +Content: Check what date and time it is today (May 8, 2025). View current time across different time zones, upcoming holidays, and use our date calculator. Your one-stop destination for all date and time information. +Score: 0.6181 + +Title: Today's Date and Time - Date and Time Tools +URL: https://todaysdatetime.com/ +Content: Discover today's exact date and time, learn about time zones, date formats, and explore our comprehensive collection of date and time tools including calculators, converters, and calendars. ... Get the exact current date and time, along with powerful calculation tools for all your scheduling needs. 12h. Today. Day 76 of year (366) Yesterday +Score: 0.5456 + +Title: Current Time Now - What time is it? - RapidTables.com +URL: https://www.rapidtables.com/tools/current-time.html +Content: This page includes the following information: Current time: hours, minutes, seconds. Today's date: day of week, month, day, year. Time zone with location and GMT offset. +Score: 0.4255 + +Title: Current Time +URL: https://www.timeanddate.com/ +Content: Welcome to the world's top site for time, time zones, and astronomy. Organize your life with free online info and tools you can rely on. No sign-up needed. Sign in. News. News Home; Astronomy News; ... Current Time. Monday May 12, 2025 Roanoke Rapids, North Carolina, USA. Set home location. 11:27: 03 pm. World Clock. +Score: 0.3876 + +Title: Current local time in the United States - World clock +URL: https://dateandtime.info/country.php?code=US +Content: Time and Date of DST Change Time Change; DST started: Sunday, March 9, 2025 at 2:00 AM: The clocks were put forward an hour to 3:00 AM. DST ends: Sunday, November 2, 2025 at 2:00 AM: The clocks will be put back an hour to 1:00 AM. DST starts: Sunday, March 8, 2026 at 2:00 AM: The clocks will be put forward an hour to 3:00 AM. +Score: 0.3042 + +Title: Time.is - exact time, any time zone +URL: https://time.is/ +Content: 7 million locations, 58 languages, synchronized with atomic clock time. Time.is. Get Time.is Ad-free! Exact time now: 05:08:45. Tuesday, 13 May, 2025, week 20. Sun: ↑ 05:09 ↓ 20:45 (15h 36m) - More info - Make London time default - Remove from favorite locations +Score: 0.2796 + +Title: Time in United States now +URL: https://time.is/United_States +Content: Exact time now, time zone, time difference, sunrise/sunset time and key facts for United States. Time.is. Get Time.is Ad-free! Time in United States now . 11:17:42 PM. Monday, May 12, 2025. United States (incl. dependent territories) has 11 time zones. The time zone for the capital Washington, D.C. is used here. +Score: 0.2726 + +Title: Current Local Time in the United States - timeanddate.com +URL: https://www.timeanddate.com/worldclock/usa +Content: United States time now. USA time zones and time zone map with current time in each state. +Score: 0.2519 + +Title: Current local time in United States - World Time Clock & Map +URL: https://24timezones.com/United-States/time +Content: Check the current time in United States and time zone information, the UTC offset and daylight saving time dates in 2025. +Score: 0.2221 + +Title: The World Clock — Worldwide - timeanddate.com +URL: https://www.timeanddate.com/worldclock/ +Content: World time and date for cities in all time zones. International time right now. Takes into account all DST clock changes. +Score: 0.2134 + + +The current date is Tuesday, May 13, 2025. + + + +The current day is Tuesday.\ +""" + ), + TextPart(content='The current day is Tuesday.'), + ], + usage=Usage(requests=1, request_tokens=4287, response_tokens=117, total_tokens=4404), + model_name='compound-beta', + timestamp=IsDatetime(), + vendor_id='stub', + ), + ] + ) + + async def test_groq_model_thinking_part(allow_model_requests: None, groq_api_key: str): m = GroqModel('deepseek-r1-distill-llama-70b', provider=GroqProvider(api_key=groq_api_key)) settings = GroqModelSettings(groq_reasoning_format='raw') @@ -697,64 +898,7 @@ async def test_groq_model_thinking_part(allow_model_requests: None, groq_api_key instructions='You are a chef.', ), ModelResponse( - parts=[ - ThinkingPart(content=IsStr()), - TextPart( - content="""\ - - -To make Uruguayan alfajores, follow these organized steps for a delightful baking experience: - -### Ingredients: -- 2 cups all-purpose flour -- 1 cup cornstarch -- 1 tsp baking powder -- 1/2 tsp salt -- 1 cup unsalted butter, softened -- 1 cup powdered sugar -- 1 egg -- 1 tsp vanilla extract -- Dulce de leche (store-bought or homemade) -- Powdered sugar for coating - -### Instructions: - -1. **Preheat Oven:** - - Preheat your oven to 300°F (150°C). Line a baking sheet with parchment paper. - -2. **Prepare Dough:** - - In a bowl, whisk together flour, cornstarch, baking powder, and salt. - - In another bowl, cream butter and powdered sugar until smooth. Add egg and vanilla, mixing well. - - Gradually incorporate the dry ingredients into the wet mixture until a dough forms. Wrap and let rest for 30 minutes. - -3. **Roll and Cut:** - - Roll dough to 1/4 inch thickness. Cut into 2-inch circles using a cutter or glass. - -4. **Bake:** - - Place cookies on the prepared baking sheet, bake for 15-20 minutes until edges are lightly golden. Cool on the sheet for 5 minutes, then transfer to a wire rack to cool completely. - -5. **Assemble Alfajores:** - - Spread a layer of dulce de leche on one cookie half. Sandwich with another cookie. Handle gently to avoid breaking. - -6. **Coat with Powdered Sugar:** - - Roll each alfajor in powdered sugar, pressing gently to adhere. - -7. **Optional Chocolate Coating:** - - For a chocolate version, melt chocolate and dip alfajores, then chill to set. - -8. **Storage:** - - Store in an airtight container at room temperature for up to a week. Freeze for longer storage. - -### Tips: -- Ensure butter is softened for smooth creaming. -- Check cookies after 15 minutes to avoid over-browning. -- Allow cookies to cool completely before handling. -- Homemade dulce de leche can be made by heating condensed milk until thickened. - -Enjoy your traditional Uruguayan alfajores with a cup of coffee or tea!\ -""" - ), - ], + parts=[IsInstance(ThinkingPart), IsInstance(TextPart)], usage=Usage(requests=1, request_tokens=21, response_tokens=1414, total_tokens=1435), model_name='deepseek-r1-distill-llama-70b', timestamp=IsDatetime(), @@ -779,7 +923,7 @@ async def test_groq_model_thinking_part(allow_model_requests: None, groq_api_key usage=Usage(requests=1, request_tokens=21, response_tokens=1414, total_tokens=1435), model_name='deepseek-r1-distill-llama-70b', timestamp=IsDatetime(), - vendor_id=IsStr(), + vendor_id='chatcmpl-9748c1af-1065-410a-969a-d7fb48039fbb', ), ModelRequest( parts=[ @@ -791,112 +935,11 @@ async def test_groq_model_thinking_part(allow_model_requests: None, groq_api_key instructions='You are a chef.', ), ModelResponse( - parts=[ - ThinkingPart( - content="""\ -Alright, so I want to make Argentinian alfajores after successfully making the Uruguayan ones. I know that both countries have their own versions of alfajores, but I'm not entirely sure how they differ. Maybe the ingredients or the preparation steps are slightly different? I should probably start by researching what makes Argentinian alfajores unique compared to the Uruguayan ones. - -First, I remember that in the Uruguayan recipe, the cookies were more delicate, and the filling was a generous amount of dulce de leche. The process involved making the dough from scratch, baking the cookies, and then assembling them with the filling and sometimes coating them in powdered sugar or chocolate. - -For Argentinian alfajores, I think the cookies might have a different texture—perhaps they're crunchier or have a different flavor profile. Maybe the type of flour used is different, or there's an addition like cocoa powder for a chocolate version. Also, the filling might have variations, like adding caramel or nuts to the dulce de leche. - -I should look up some authentic Argentinian recipes to see the differences. Maybe the method of making the dough is slightly different, or the baking time and temperature vary. I also wonder if Argentinian alfajores are typically coated in something else besides powdered sugar, like cinnamon or coconut flakes. - -Another thing to consider is the size and shape of the cookies. Uruguayan alfajores might be smaller and rounder, while Argentinian ones could be larger or have a different shape. I should also check if there are any additional steps, like toasting the cookies or adding a layer of meringue. - -I need to make sure I have all the necessary ingredients. If the Argentinian version requires something different, like a specific type of flour or a particular flavoring, I'll need to adjust my shopping list. Also, if the filling is different, I might need to prepare it in a different way or add extra ingredients. - -I should also think about the assembly process. Maybe Argentinian alfajores are sandwiched with more filling or have a different way of sealing the cookies together. Perhaps they're rolled in coconut after being filled, or there's a step involving dipping them in chocolate. - -It would be helpful to watch a video or read a detailed recipe from an Argentinian source to get a better understanding. That way, I can follow the traditional method and ensure that my alfajores turn out authentic. I should also consider any tips or tricks that Argentinian bakers use to make their alfajores special. - -Finally, I need to plan the timing. Making alfajores can be a bit time-consuming, especially if you're making the dulce de leche from scratch. I should allocate enough time for preparing the dough, baking the cookies, and assembling the alfajores. - -Overall, the key steps I think I'll need to follow are: - -1. Research authentic Argentinian alfajores recipes to identify unique ingredients and steps. -2. Adjust the ingredient list based on the differences from the Uruguayan version. -3. Prepare the dough according to the Argentinian method, which might involve different mixing techniques or additional ingredients. -4. Bake the cookies, possibly at a different temperature or for a different duration. -5. Prepare the filling, which might include variations like caramel or nuts. -6. Assemble the alfajores, possibly adding extra coatings or layers. -7. Allow the alfajores to set before serving, to ensure the flavors meld together. - -By carefully following these steps and paying attention to the unique aspects of Argentinian alfajores, I should be able to create a delicious and authentic batch that captures the essence of this traditional South American treat. -""" - ), - TextPart( - content="""\ -To create authentic Argentinian alfajores, follow these organized steps, which highlight the unique characteristics and differences from the Uruguayan version: - -### Ingredients: -- **For the Cookies:** - - 2 cups all-purpose flour - - 1/2 cup cornstarch - - 1/4 cup unsweetened cocoa powder (optional for chocolate version) - - 1 teaspoon baking powder - - 1/2 teaspoon salt - - 1 cup unsalted butter, softened - - 1 cup powdered sugar - - 1 egg - - 1 teaspoon vanilla extract - -- **For the Filling:** - - Dulce de leche (store-bought or homemade) - - Optional: caramel sauce, chopped nuts, or cinnamon for added flavor - -- **For Coating:** - - Powdered sugar - - Optional: cinnamon, shredded coconut, or melted chocolate - -### Instructions: - -1. **Prepare the Dough:** - - In a large bowl, whisk together the flour, cornstarch, cocoa powder (if using), baking powder, and salt. - - In another bowl, cream the softened butter and powdered sugar until smooth. Add the egg and vanilla extract, mixing well. - - Gradually incorporate the dry ingredients into the wet mixture until a dough forms. Wrap the dough in plastic wrap and let it rest for 30 minutes. - -2. **Roll and Cut the Cookies:** - - Roll the dough to about 1/4 inch thickness on a lightly floured surface. - - Use a round cookie cutter or the rim of a glass to cut out circles of dough. Argentinian alfajores are often slightly larger than their Uruguayan counterparts. - -3. **Bake the Cookies:** - - Preheat the oven to 300°F (150°C). Line a baking sheet with parchment paper. - - Place the cookie rounds on the prepared baking sheet, leaving about 1 inch of space between each cookie. - - Bake for 15-20 minutes, or until the edges are lightly golden. Allow the cookies to cool on the baking sheet for 5 minutes before transferring them to a wire rack to cool completely. - -4. **Prepare the Filling:** - - Use store-bought dulce de leche or make your own by heating sweetened condensed milk until thickened. - - Optional: Stir in caramel sauce or chopped nuts into the dulce de leche for added flavor. - -5. **Assemble the Alfajores:** - - Once the cookies are completely cool, spread a generous amount of dulce de leche on the flat side of one cookie. - - Sandwich with another cookie, pressing gently to adhere. For an extra touch, roll the edges in chopped nuts or shredded coconut. - -6. **Coat with Powdered Sugar or Chocolate:** - - Roll each alfajor in powdered sugar, pressing gently to ensure it adheres. - - Optional: Melt chocolate and dip the alfajores, then sprinkle with cinnamon or coconut before the chocolate sets. - -7. **Allow to Set:** - - Let the alfajores sit for about 30 minutes to allow the flavors to meld together. - -8. **Serve and Enjoy:** - - Serve with a cup of coffee or tea. Store any leftovers in an airtight container at room temperature for up to a week. - -### Tips: -- **Dough Handling:** Ensure the butter is softened for a smooth dough, and don't overwork the dough to maintain the cookies' tender texture. -- **Baking:** Keep an eye on the cookies after 15 minutes to prevent over-browning. -- **Filling Variations:** Experiment with different fillings like caramel or nuts to create unique flavor profiles. -- **Coating Options:** Try different coatings such as cinnamon or coconut for a varied texture and taste. - -By following these steps, you'll create authentic Argentinian alfajores that capture the rich flavors and traditions of this beloved South American treat.\ -""" - ), - ], + parts=[IsInstance(ThinkingPart), IsInstance(TextPart)], usage=Usage(requests=1, request_tokens=524, response_tokens=1590, total_tokens=2114), model_name='deepseek-r1-distill-llama-70b', timestamp=IsDatetime(), - vendor_id=IsStr(), + vendor_id='chatcmpl-994aa228-883a-498c-8b20-9655d770b697', ), ] ) diff --git a/tests/models/test_instrumented.py b/tests/models/test_instrumented.py index c0befafef..132cd1613 100644 --- a/tests/models/test_instrumented.py +++ b/tests/models/test_instrumented.py @@ -153,7 +153,7 @@ async def test_instrumented_model(capfire: CaptureLogfire): 'gen_ai.request.model': 'my_model', 'server.address': 'example.com', 'server.port': 8000, - 'model_request_parameters': '{"function_tools": [], "output_mode": "text", "output_object": null, "output_tools": [], "allow_text_output": true}', + 'model_request_parameters': '{"function_tools": [], "builtin_tools": [], "output_mode": "text", "output_object": null, "output_tools": [], "allow_text_output": true}', 'logfire.json_schema': '{"type": "object", "properties": {"model_request_parameters": {"type": "object"}}}', 'gen_ai.request.temperature': 1, 'logfire.msg': 'chat my_model', @@ -382,7 +382,7 @@ async def test_instrumented_model_stream(capfire: CaptureLogfire): 'gen_ai.request.model': 'my_model', 'server.address': 'example.com', 'server.port': 8000, - 'model_request_parameters': '{"function_tools": [], "output_mode": "text", "output_object": null, "output_tools": [], "allow_text_output": true}', + 'model_request_parameters': '{"function_tools": [], "builtin_tools": [], "output_mode": "text", "output_object": null, "output_tools": [], "allow_text_output": true}', 'logfire.json_schema': '{"type": "object", "properties": {"model_request_parameters": {"type": "object"}}}', 'gen_ai.request.temperature': 1, 'logfire.msg': 'chat my_model', @@ -469,7 +469,7 @@ async def test_instrumented_model_stream_break(capfire: CaptureLogfire): 'gen_ai.request.model': 'my_model', 'server.address': 'example.com', 'server.port': 8000, - 'model_request_parameters': '{"function_tools": [], "output_mode": "text", "output_object": null, "output_tools": [], "allow_text_output": true}', + 'model_request_parameters': '{"function_tools": [], "builtin_tools": [], "output_mode": "text", "output_object": null, "output_tools": [], "allow_text_output": true}', 'logfire.json_schema': '{"type": "object", "properties": {"model_request_parameters": {"type": "object"}}}', 'gen_ai.request.temperature': 1, 'logfire.msg': 'chat my_model', @@ -571,7 +571,7 @@ async def test_instrumented_model_attributes_mode(capfire: CaptureLogfire): 'gen_ai.request.model': 'my_model', 'server.address': 'example.com', 'server.port': 8000, - 'model_request_parameters': '{"function_tools": [], "output_mode": "text", "output_object": null, "output_tools": [], "allow_text_output": true}', + 'model_request_parameters': '{"function_tools": [], "builtin_tools": [], "output_mode": "text", "output_object": null, "output_tools": [], "allow_text_output": true}', 'gen_ai.request.temperature': 1, 'logfire.msg': 'chat my_model', 'logfire.span_type': 'span', diff --git a/tests/models/test_model_request_parameters.py b/tests/models/test_model_request_parameters.py index 98a6d1ccc..2915796ab 100644 --- a/tests/models/test_model_request_parameters.py +++ b/tests/models/test_model_request_parameters.py @@ -9,6 +9,7 @@ def test_model_request_parameters_are_serializable(): ) assert TypeAdapter(ModelRequestParameters).dump_python(params) == { 'function_tools': [], + 'builtin_tools': [], 'output_mode': 'text', 'allow_text_output': True, 'output_tools': [], diff --git a/tests/models/test_openai.py b/tests/models/test_openai.py index 74f08b190..31f721870 100644 --- a/tests/models/test_openai.py +++ b/tests/models/test_openai.py @@ -16,6 +16,7 @@ from typing_extensions import TypedDict from pydantic_ai import Agent, ModelHTTPError, ModelRetry, UnexpectedModelBehavior +from pydantic_ai.builtin_tools import WebSearchTool from pydantic_ai.messages import ( AudioUrl, BinaryContent, @@ -1828,10 +1829,7 @@ async def test_openai_instructions_with_logprobs(allow_model_requests: None): 'gpt-4o', provider=OpenAIProvider(openai_client=mock_client), ) - agent = Agent( - m, - instructions='You are a helpful assistant.', - ) + agent = Agent(m, instructions='You are a helpful assistant.') result = await agent.run( 'What is the capital of Minas Gerais?', model_settings=OpenAIModelSettings(openai_logprobs=True), @@ -1849,6 +1847,56 @@ async def test_openai_instructions_with_logprobs(allow_model_requests: None): ] +@pytest.mark.vcr() +async def test_openai_web_search_tool_model_not_supported(allow_model_requests: None, openai_api_key: str): + m = OpenAIModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key)) + agent = Agent( + m, instructions='You are a helpful assistant.', builtin_tools=[WebSearchTool(search_context_size='low')] + ) + + with pytest.raises(ModelHTTPError, match='.*Web search options not supported with this model.*'): + await agent.run('What day is today?') + + +@pytest.mark.vcr() +async def test_openai_web_search_tool(allow_model_requests: None, openai_api_key: str): + m = OpenAIModel('gpt-4o-search-preview', provider=OpenAIProvider(api_key=openai_api_key)) + agent = Agent( + m, instructions='You are a helpful assistant.', builtin_tools=[WebSearchTool(search_context_size='low')] + ) + + result = await agent.run('What day is today?') + assert result.output == snapshot('May 14, 2025, 8:51:29 AM ') + + +@pytest.mark.vcr() +async def test_openai_web_search_tool_with_user_location(allow_model_requests: None, openai_api_key: str): + m = OpenAIModel('gpt-4o-search-preview', provider=OpenAIProvider(api_key=openai_api_key)) + agent = Agent( + m, + instructions='You are a helpful assistant.', + builtin_tools=[WebSearchTool(user_location={'city': 'Utrecht', 'country': 'NL'})], + ) + + result = await agent.run('What is the current temperature?') + assert result.output == snapshot("""\ +Het is momenteel zonnig in Utrecht met een temperatuur van 22°C. + +## Weer voor Utrecht, Nederland: +Huidige omstandigheden: Zonnig, 72°F (22°C) + +Dagvoorspelling: +* woensdag, mei 14: minimum: 48°F (9°C), maximum: 71°F (22°C), beschrijving: Afnemende bewolking +* donderdag, mei 15: minimum: 43°F (6°C), maximum: 67°F (20°C), beschrijving: Na een bewolkt begin keert de zon terug +* vrijdag, mei 16: minimum: 45°F (7°C), maximum: 64°F (18°C), beschrijving: Overwegend zonnig +* zaterdag, mei 17: minimum: 47°F (9°C), maximum: 68°F (20°C), beschrijving: Overwegend zonnig +* zondag, mei 18: minimum: 47°F (8°C), maximum: 68°F (20°C), beschrijving: Deels zonnig +* maandag, mei 19: minimum: 49°F (9°C), maximum: 70°F (21°C), beschrijving: Deels zonnig +* dinsdag, mei 20: minimum: 49°F (10°C), maximum: 72°F (22°C), beschrijving: Zonnig tot gedeeltelijk bewolkt + \ +""") + + @pytest.mark.vcr() async def test_reasoning_model_with_temperature(allow_model_requests: None, openai_api_key: str): m = OpenAIModel('o3-mini', provider=OpenAIProvider(api_key=openai_api_key)) diff --git a/tests/models/test_openai_responses.py b/tests/models/test_openai_responses.py index 46a38262b..61652f9b6 100644 --- a/tests/models/test_openai_responses.py +++ b/tests/models/test_openai_responses.py @@ -7,6 +7,7 @@ from typing_extensions import TypedDict from pydantic_ai.agent import Agent +from pydantic_ai.builtin_tools import WebSearchTool from pydantic_ai.exceptions import ModelHTTPError, ModelRetry from pydantic_ai.messages import ( BinaryContent, @@ -471,6 +472,58 @@ async def test_openai_responses_model_instructions(allow_model_requests: None, o ) +async def test_openai_responses_model_web_search_tool(allow_model_requests: None, openai_api_key: str): + m = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key)) + agent = Agent(m, instructions='You are a helpful assistant.', builtin_tools=['web-search']) + + result = await agent.run('What day is it today?') + assert result.output == snapshot("""\ +Today is Wednesday, May 14, 2025. + +## Weather for San Francisco, CA: +Current Conditions: Mostly clear, 50°F (10°C) + +Daily Forecast: +* Wednesday, May 14: Low: 51°F (10°C), High: 65°F (18°C), Description: Areas of low clouds early; otherwise, mostly sunny +* Thursday, May 15: Low: 53°F (12°C), High: 66°F (19°C), Description: Areas of low clouds, then sun +* Friday, May 16: Low: 53°F (12°C), High: 64°F (18°C), Description: Partly sunny +* Saturday, May 17: Low: 52°F (11°C), High: 63°F (17°C), Description: Low clouds breaking for some sun; breezy in the afternoon +* Sunday, May 18: Low: 51°F (10°C), High: 68°F (20°C), Description: Clouds yielding to sun +* Monday, May 19: Low: 53°F (12°C), High: 68°F (20°C), Description: Sunny +* Tuesday, May 20: Low: 52°F (11°C), High: 70°F (21°C), Description: Mostly sunny + \ +""") + + +async def test_openai_responses_model_web_search_tool_with_user_location( + allow_model_requests: None, openai_api_key: str +): + m = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key)) + agent = Agent( + m, + instructions='You are a helpful assistant.', + builtin_tools=[WebSearchTool(user_location={'city': 'Utrecht', 'country': 'NL'})], + ) + + result = await agent.run('What is the current temperature?') + assert result.output == snapshot("""\ +As of 12:58 PM on Wednesday, May 14, 2025, in Utrecht, Netherlands, the weather is sunny with a temperature of 22°C (71°F). + +## Weather for Utrecht, Netherlands: +Current Conditions: Sunny, 71°F (22°C) + +Daily Forecast: +* Wednesday, May 14: Low: 48°F (9°C), High: 71°F (22°C), Description: Clouds yielding to sun +* Thursday, May 15: Low: 43°F (6°C), High: 67°F (20°C), Description: After a cloudy start, sun returns +* Friday, May 16: Low: 45°F (7°C), High: 64°F (18°C), Description: Mostly sunny +* Saturday, May 17: Low: 47°F (9°C), High: 68°F (20°C), Description: Mostly sunny +* Sunday, May 18: Low: 47°F (8°C), High: 68°F (20°C), Description: Some sun +* Monday, May 19: Low: 49°F (9°C), High: 70°F (21°C), Description: Delightful with partial sunshine +* Tuesday, May 20: Low: 49°F (10°C), High: 72°F (22°C), Description: Warm with sunshine and a few clouds + \ +""") + + def test_model_profile_strict_not_supported(): my_tool = ToolDefinition( 'my_tool', diff --git a/tests/test_logfire.py b/tests/test_logfire.py index 34aff7514..b0261ca15 100644 --- a/tests/test_logfire.py +++ b/tests/test_logfire.py @@ -291,6 +291,7 @@ async def my_ret(x: int) -> str: 'strict': None, } ], + 'builtin_tools': [], 'output_mode': 'text', 'output_tools': [], 'output_object': None, @@ -474,14 +475,44 @@ async def test_feedback(capfire: CaptureLogfire) -> None: 'gen_ai.operation.name': 'chat', 'gen_ai.system': 'test', 'gen_ai.request.model': 'test', - 'model_request_parameters': '{"function_tools": [], "output_mode": "text", "output_object": null, "output_tools": [], "allow_text_output": true}', + 'model_request_parameters': IsJson( + { + 'function_tools': [], + 'builtin_tools': [], + 'output_mode': 'text', + 'output_object': None, + 'output_tools': [], + 'allow_text_output': True, + } + ), 'logfire.span_type': 'span', 'logfire.msg': 'chat test', 'gen_ai.usage.input_tokens': 51, 'gen_ai.usage.output_tokens': 4, 'gen_ai.response.model': 'test', - 'events': '[{"content": "Hello", "role": "user", "gen_ai.system": "test", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"index": 0, "message": {"role": "assistant", "content": "success (no tool calls)"}, "gen_ai.system": "test", "event.name": "gen_ai.choice"}]', - 'logfire.json_schema': '{"type": "object", "properties": {"events": {"type": "array"}, "model_request_parameters": {"type": "object"}}}', + 'events': IsJson( + [ + { + 'content': 'Hello', + 'role': 'user', + 'gen_ai.system': 'test', + 'gen_ai.message.index': 0, + 'event.name': 'gen_ai.user.message', + }, + { + 'index': 0, + 'message': {'role': 'assistant', 'content': 'success (no tool calls)'}, + 'gen_ai.system': 'test', + 'event.name': 'gen_ai.choice', + }, + ] + ), + 'logfire.json_schema': IsJson( + { + 'type': 'object', + 'properties': {'events': {'type': 'array'}, 'model_request_parameters': {'type': 'object'}}, + } + ), }, }, { diff --git a/uv.lock b/uv.lock index 1cfb6f386..32707d647 100644 --- a/uv.lock +++ b/uv.lock @@ -1288,7 +1288,7 @@ wheels = [ [[package]] name = "groq" -version = "0.22.0" +version = "0.25.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1298,9 +1298,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fd/59/7e03f5b12c097b7af48d5fe847a5bd00c90ccfc04b801c2ae68a043ddf1e/groq-0.22.0.tar.gz", hash = "sha256:9d090fbe4a051655faff649890d18aaacb3121393ad9d55399171fe081f1057b", size = 122956, upload-time = "2025-04-02T20:34:07.722Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a4/fc/29e9c24ab59602747027f41b9d761d24cf9e5771014c9a731137f51e9cce/groq-0.25.0.tar.gz", hash = "sha256:6e1c7466b0da0130498187b825bd239f86fb77bf7551eacfbfa561d75048746a", size = 128199, upload-time = "2025-05-16T19:57:43.381Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/77/e6b60636f648922cc53144c36e1e7b77c9670539aab9e8b84a5fd1a53880/groq-0.22.0-py3-none-any.whl", hash = "sha256:f53d3966dff713aaa635671c2d075ebb932b0d48e3c4031ede9b84a2a6694c79", size = 126685, upload-time = "2025-04-02T20:34:06.637Z" }, + { url = "https://files.pythonhosted.org/packages/4d/11/1019a6cfdb2e520cb461cf70d859216be8ca122ddf5ad301fc3b0ee45fd4/groq-0.25.0-py3-none-any.whl", hash = "sha256:aadc78b40b1809cdb196b1aa8c7f7293108767df1508cafa3e0d5045d9328e7a", size = 129371, upload-time = "2025-05-16T19:57:41.786Z" }, ] [[package]] @@ -3149,7 +3149,7 @@ requires-dist = [ { name = "google-auth", marker = "extra == 'vertexai'", specifier = ">=2.36.0" }, { name = "google-genai", marker = "extra == 'google'", specifier = ">=1.15.0" }, { name = "griffe", specifier = ">=1.3.2" }, - { name = "groq", marker = "extra == 'groq'", specifier = ">=0.19.0" }, + { name = "groq", marker = "extra == 'groq'", specifier = ">=0.25.0" }, { name = "httpx", specifier = ">=0.27" }, { name = "logfire", marker = "extra == 'logfire'", specifier = ">=3.11.0" }, { name = "mcp", marker = "python_full_version >= '3.10' and extra == 'mcp'", specifier = ">=1.9.4" },