pydantic--pydantic-ai
9201ef759e
Harness Compat / harness compat (push) Failing after 0s
CI / test on 3.12 (standard) (push) Has been cancelled
CI / test on 3.13 (standard) (push) Has been cancelled
CI / test on 3.14 (standard) (push) Has been cancelled
CI / test on 3.10 (all-extras) (push) Has been cancelled
CI / test on 3.11 (all-extras) (push) Has been cancelled
CI / test on 3.12 (all-extras) (push) Has been cancelled
CI / test on 3.14 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.10 (pydantic-evals) (push) Has been cancelled
CI / test on 3.11 (pydantic-evals) (push) Has been cancelled
CI / test on 3.12 (pydantic-evals) (push) Has been cancelled
CI / deploy-docs-preview (push) Has been cancelled
CI / build release artifacts (push) Has been cancelled
CI / publish to PyPI (push) Has been cancelled
CI / Send tweet (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / mypy (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / test on 3.10 (standard) (push) Has been cancelled
CI / test on 3.11 (standard) (push) Has been cancelled
CI / test on 3.13 (all-extras) (push) Has been cancelled
CI / test on 3.14 (all-extras) (push) Has been cancelled
CI / test on 3.10 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.11 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.12 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.13 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.13 (pydantic-evals) (push) Has been cancelled
CI / test on 3.14 (pydantic-evals) (push) Has been cancelled
CI / test on 3.10 (lowest-versions) (push) Has been cancelled
CI / test on 3.11 (lowest-versions) (push) Has been cancelled
CI / test on 3.12 (lowest-versions) (push) Has been cancelled
CI / test on 3.13 (lowest-versions) (push) Has been cancelled
CI / test on 3.14 (lowest-versions) (push) Has been cancelled
CI / test examples on 3.11 (push) Has been cancelled
CI / test examples on 3.12 (push) Has been cancelled
CI / test examples on 3.13 (push) Has been cancelled
CI / test examples on 3.14 (push) Has been cancelled
CI / coverage (push) Has been cancelled
CI / check (push) Has been cancelled
CI / deploy-docs (push) Has been cancelled
12533 行
639 KiB
Python
12533 行
639 KiB
Python
import json
|
|
import re
|
|
import warnings
|
|
from collections.abc import AsyncGenerator
|
|
from contextlib import asynccontextmanager
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
from typing import Any, Literal, cast
|
|
|
|
import pytest
|
|
from pydantic import BaseModel
|
|
from typing_extensions import TypedDict
|
|
from vcr.cassette import Cassette
|
|
|
|
from pydantic_ai import (
|
|
BinaryContent,
|
|
BinaryImage,
|
|
CompactionPart,
|
|
DocumentUrl,
|
|
FilePart,
|
|
FinalResultEvent,
|
|
ImageGenerationTool,
|
|
ImageUrl,
|
|
ModelRequest,
|
|
ModelResponse,
|
|
NativeToolCallPart,
|
|
NativeToolReturnPart,
|
|
PartDeltaEvent,
|
|
PartEndEvent,
|
|
PartStartEvent,
|
|
RetryPromptPart,
|
|
SystemPromptPart,
|
|
TextContent,
|
|
TextPart,
|
|
TextPartDelta,
|
|
ThinkingPart,
|
|
ThinkingPartDelta,
|
|
ToolCallPart,
|
|
ToolCallPartDelta,
|
|
ToolReturnPart,
|
|
UnexpectedModelBehavior,
|
|
UsageLimitExceeded,
|
|
UserError,
|
|
UserPromptPart,
|
|
capture_run_messages,
|
|
)
|
|
from pydantic_ai.agent import Agent
|
|
from pydantic_ai.capabilities import NativeTool
|
|
from pydantic_ai.direct import model_request as direct_model_request
|
|
from pydantic_ai.exceptions import ContentFilterError, ModelHTTPError, ModelRetry
|
|
from pydantic_ai.models import ModelRequestParameters
|
|
from pydantic_ai.native_tools import CodeExecutionTool, FileSearchTool, ImageAspectRatio, MCPServerTool, WebSearchTool
|
|
from pydantic_ai.output import NativeOutput, PromptedOutput, TextOutput, ToolOutput
|
|
from pydantic_ai.profiles import merge_profile
|
|
from pydantic_ai.profiles.openai import OpenAIModelProfile, openai_model_profile
|
|
from pydantic_ai.tools import ToolDefinition
|
|
from pydantic_ai.usage import RequestUsage, RunUsage, UsageLimits
|
|
|
|
from .._inline_snapshot import snapshot
|
|
from ..cassette_utils import single_request_body
|
|
from ..conftest import IsDatetime, IsFloat, IsInstance, IsInt, IsNow, IsStr, TestEnv, message, try_import
|
|
from .mock_openai import MockOpenAIResponses, get_mock_responses_kwargs, response_message
|
|
|
|
with try_import() as imports_successful:
|
|
from openai import AsyncAzureOpenAI, AsyncOpenAI, omit
|
|
from openai.types import responses as resp
|
|
from openai.types.responses import ResponseFunctionWebSearch
|
|
from openai.types.responses.response_output_message import Content, ResponseOutputMessage
|
|
from openai.types.responses.response_output_refusal import ResponseOutputRefusal
|
|
from openai.types.responses.response_output_text import ResponseOutputText
|
|
from openai.types.responses.response_reasoning_item import (
|
|
Content as ReasoningContent,
|
|
ResponseReasoningItem,
|
|
Summary,
|
|
)
|
|
from openai.types.responses.response_refusal_delta_event import ResponseRefusalDeltaEvent
|
|
from openai.types.responses.response_refusal_done_event import ResponseRefusalDoneEvent
|
|
from openai.types.responses.response_usage import ResponseUsage
|
|
|
|
from pydantic_ai.models.anthropic import AnthropicModel, AnthropicModelSettings
|
|
from pydantic_ai.models.openai import (
|
|
OpenAIResponsesModel,
|
|
OpenAIResponsesModelSettings,
|
|
_resolve_openai_image_generation_size, # pyright: ignore[reportPrivateUsage]
|
|
)
|
|
from pydantic_ai.providers.anthropic import AnthropicProvider
|
|
from pydantic_ai.providers.azure import AzureProvider
|
|
from pydantic_ai.providers.openai import OpenAIProvider
|
|
from pydantic_ai.providers.openrouter import OpenRouterProvider
|
|
|
|
pytestmark = [
|
|
pytest.mark.skipif(not imports_successful(), reason='openai not installed'),
|
|
pytest.mark.anyio,
|
|
pytest.mark.vcr,
|
|
]
|
|
|
|
|
|
async def _cleanup_openai_resources(file: Any, vector_store: Any, async_client: Any) -> None: # pragma: lax no cover
|
|
"""Helper function to clean up OpenAI file search resources if they exist."""
|
|
if file is not None:
|
|
await async_client.files.delete(file.id)
|
|
if vector_store is not None:
|
|
await async_client.vector_stores.delete(vector_store.id)
|
|
await async_client.close()
|
|
|
|
|
|
@asynccontextmanager
|
|
async def _openai_conversation(openai_api_key: str) -> AsyncGenerator[tuple['AsyncOpenAI', str]]:
|
|
async with AsyncOpenAI(api_key=openai_api_key) as async_client:
|
|
conversation = await async_client.conversations.create()
|
|
try:
|
|
yield async_client, conversation.id
|
|
finally:
|
|
await async_client.conversations.delete(conversation.id)
|
|
|
|
|
|
def test_openai_responses_model(env: TestEnv):
|
|
env.set('OPENAI_API_KEY', 'test')
|
|
provider = OpenAIProvider()
|
|
model = OpenAIResponsesModel('gpt-4o', provider=provider)
|
|
assert model.model_name == 'gpt-4o'
|
|
assert model.system == 'openai'
|
|
assert model.base_url == 'https://api.openai.com/v1/'
|
|
assert model.client is provider.client
|
|
assert model.client.api_key == 'test'
|
|
|
|
|
|
async def test_openai_responses_model_simple_response(allow_model_requests: None, openai_api_key: str):
|
|
model = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model=model)
|
|
result = await agent.run('What is the capital of France?')
|
|
assert result.output == snapshot('The capital of France is Paris.')
|
|
|
|
|
|
async def test_openai_responses_image_detail_vendor_metadata(allow_model_requests: None):
|
|
c = response_message(
|
|
[
|
|
ResponseOutputMessage(
|
|
id='output-1',
|
|
content=cast(list[Content], [ResponseOutputText(text='done', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
)
|
|
]
|
|
)
|
|
mock_client = MockOpenAIResponses.create_mock(c)
|
|
model = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(openai_client=mock_client))
|
|
agent = Agent(model=model)
|
|
|
|
image_url = ImageUrl('https://example.com/image.png', vendor_metadata={'detail': 'high'})
|
|
binary_image = BinaryContent(b'\x89PNG', media_type='image/png', vendor_metadata={'detail': 'high'})
|
|
|
|
result = await agent.run(['Describe these inputs.', image_url, binary_image])
|
|
assert result.output == 'done'
|
|
|
|
response_kwargs = get_mock_responses_kwargs(mock_client)
|
|
image_parts = [
|
|
item
|
|
for message in response_kwargs[0]['input']
|
|
if message.get('role') == 'user'
|
|
for item in message['content']
|
|
if item['type'] == 'input_image'
|
|
]
|
|
assert image_parts
|
|
assert all(part['detail'] == 'high' for part in image_parts)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
('model_settings', 'expected_reasoning'),
|
|
[
|
|
({'openai_reasoning_mode': 'standard'}, {'mode': 'standard'}),
|
|
({'openai_reasoning_mode': 'pro'}, {'mode': 'pro'}),
|
|
(
|
|
{
|
|
'openai_reasoning_effort': 'high',
|
|
'openai_reasoning_mode': 'pro',
|
|
'openai_reasoning_summary': 'concise',
|
|
},
|
|
{'effort': 'high', 'mode': 'pro', 'summary': 'concise'},
|
|
),
|
|
],
|
|
)
|
|
async def test_openai_responses_reasoning_mode(
|
|
allow_model_requests: None,
|
|
model_settings: 'OpenAIResponsesModelSettings',
|
|
expected_reasoning: dict[str, str],
|
|
) -> None:
|
|
"""Not a VCR test: this pins the exact typed `reasoning` request object."""
|
|
c = response_message(
|
|
[
|
|
ResponseOutputMessage(
|
|
id='output-1',
|
|
content=cast(list[Content], [ResponseOutputText(text='done', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
)
|
|
]
|
|
)
|
|
mock_client = MockOpenAIResponses.create_mock(c)
|
|
model = OpenAIResponsesModel('gpt-5.6-sol', provider=OpenAIProvider(openai_client=mock_client))
|
|
|
|
await Agent(model=model, model_settings=model_settings).run('Solve this carefully.')
|
|
|
|
assert get_mock_responses_kwargs(mock_client)[0]['reasoning'] == expected_reasoning
|
|
|
|
|
|
async def test_openrouter_responses_reasoning_mode(allow_model_requests: None) -> None:
|
|
"""Not a VCR test: exact request kwargs prove the OpenRouter Responses profile enables mode."""
|
|
c = response_message(
|
|
[
|
|
ResponseOutputMessage(
|
|
id='output-1',
|
|
content=cast(list[Content], [ResponseOutputText(text='done', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
)
|
|
]
|
|
)
|
|
mock_client = MockOpenAIResponses.create_mock(c)
|
|
model = OpenAIResponsesModel('openai/gpt-5.6-sol', provider=OpenRouterProvider(openai_client=mock_client))
|
|
|
|
await Agent(model, model_settings=OpenAIResponsesModelSettings(openai_reasoning_mode='pro')).run('Hello')
|
|
|
|
assert get_mock_responses_kwargs(mock_client)[0]['reasoning'] == {'mode': 'pro'}
|
|
|
|
|
|
async def test_azure_responses_reasoning_mode(allow_model_requests: None) -> None:
|
|
"""Not a VCR test: exact request kwargs prove the Azure GPT-5.6 profile enables mode."""
|
|
c = response_message(
|
|
[
|
|
ResponseOutputMessage(
|
|
id='output-1',
|
|
content=cast(list[Content], [ResponseOutputText(text='done', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
)
|
|
]
|
|
)
|
|
mock_client = MockOpenAIResponses.create_mock(c)
|
|
model = OpenAIResponsesModel(
|
|
'gpt-5.6-sol', provider=AzureProvider(openai_client=cast(AsyncAzureOpenAI, mock_client))
|
|
)
|
|
|
|
await Agent(model, model_settings=OpenAIResponsesModelSettings(openai_reasoning_mode='pro')).run('Hello')
|
|
|
|
assert get_mock_responses_kwargs(mock_client)[0]['reasoning'] == {'mode': 'pro'}
|
|
|
|
|
|
@pytest.mark.parametrize('provider_name', ['openai', 'openrouter'])
|
|
async def test_openai_responses_reasoning_mode_omitted_when_unsupported(
|
|
allow_model_requests: None, provider_name: Literal['openai', 'openrouter']
|
|
):
|
|
"""Not a VCR test: unsupported paths must omit `reasoning.mode` before sending."""
|
|
c = response_message(
|
|
[
|
|
ResponseOutputMessage(
|
|
id='output-1',
|
|
content=cast(list[Content], [ResponseOutputText(text='done', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
)
|
|
]
|
|
)
|
|
mock_client = MockOpenAIResponses.create_mock(c)
|
|
if provider_name == 'openai':
|
|
model = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(openai_client=mock_client))
|
|
else:
|
|
model = OpenAIResponsesModel('openai/gpt-5.4', provider=OpenRouterProvider(openai_client=mock_client))
|
|
|
|
await Agent(model, model_settings=OpenAIResponsesModelSettings(openai_reasoning_mode='pro')).run('Hello')
|
|
|
|
assert 'reasoning' not in get_mock_responses_kwargs(mock_client)[0]
|
|
|
|
|
|
async def test_openai_responses_reasoning_mode_pro(allow_model_requests: None, openai_api_key: str, vcr: Cassette):
|
|
"""VCR test: the real GPT-5.6 Responses API accepts `reasoning.mode='pro'` end to end.
|
|
|
|
The mock tests above pin the request shape; this records the real request and response so the
|
|
`reasoning.mode` wire contract is validated against the provider. The request-body assertion
|
|
guards against a serialization regression that the cassette matcher would not catch on its own.
|
|
"""
|
|
model = OpenAIResponsesModel('gpt-5.6-sol', provider=OpenAIProvider(api_key=openai_api_key))
|
|
settings = OpenAIResponsesModelSettings(openai_reasoning_mode='pro')
|
|
agent = Agent(model=model, model_settings=settings)
|
|
|
|
result = await agent.run('What is the capital of France? Answer in one word.')
|
|
|
|
assert result.output == snapshot('Paris')
|
|
assert single_request_body(vcr)['reasoning'] == snapshot({'mode': 'pro'})
|
|
|
|
|
|
async def test_openai_responses_gpt_5_6_reasoning_off_keeps_sampling_params(
|
|
allow_model_requests: None, openai_api_key: str, vcr: Cassette
|
|
):
|
|
"""VCR test: the real GPT-5.6 API accepts sampling params while reasoning is off.
|
|
|
|
GPT-5.6 reasons by default but accepts `effort='none'`, and in that mode honors sampling
|
|
parameters — the fact behind `openai_supports_reasoning_effort_none=True` in its profile.
|
|
The request-body assertion proves `temperature` was actually sent rather than dropped.
|
|
"""
|
|
model = OpenAIResponsesModel('gpt-5.6-sol', provider=OpenAIProvider(api_key=openai_api_key))
|
|
settings = OpenAIResponsesModelSettings(openai_reasoning_effort='none', temperature=0.5)
|
|
agent = Agent(model=model, model_settings=settings)
|
|
|
|
result = await agent.run('What is the capital of France? Answer in one word.')
|
|
|
|
assert result.output == snapshot('Paris')
|
|
request_body = single_request_body(vcr)
|
|
assert request_body['reasoning'] == snapshot({'effort': 'none'})
|
|
assert request_body['temperature'] == 0.5
|
|
|
|
|
|
async def test_openai_responses_gpt_5_5_drops_sampling_params_by_default(
|
|
allow_model_requests: None, openai_api_key: str, vcr: Cassette
|
|
):
|
|
"""VCR test: GPT-5.5 reasons by default, so sampling params must be dropped when no effort is set.
|
|
|
|
The live API rejects `temperature` on gpt-5.5 unless `effort='none'` is sent — i.e.
|
|
`openai_reasoning_enabled_by_default=True`, unlike the gpt-5.1..5.4 mainline models.
|
|
The request-body assertion proves `temperature` was dropped so the request succeeds.
|
|
"""
|
|
model = OpenAIResponsesModel('gpt-5.5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model=model, model_settings=OpenAIResponsesModelSettings(temperature=0.5))
|
|
|
|
with pytest.warns(UserWarning, match='Sampling parameters'):
|
|
result = await agent.run('What is the capital of France? Answer in one word.')
|
|
|
|
assert result.output == snapshot('Paris')
|
|
assert 'temperature' not in single_request_body(vcr)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
('settings', 'temperature_kept'),
|
|
[
|
|
# GPT-5.6 reasons on by default at 'medium', so an omitted effort drops sampling params.
|
|
({'temperature': 0.5}, False),
|
|
# `effort='none'` turns reasoning off, so sampling params are kept (real API accepts them).
|
|
({'temperature': 0.5, 'openai_reasoning_effort': 'none'}, True),
|
|
# An active effort drops sampling params.
|
|
({'temperature': 0.5, 'openai_reasoning_effort': 'low'}, False),
|
|
],
|
|
)
|
|
async def test_openai_responses_gpt_5_6_sampling_params(
|
|
allow_model_requests: None, settings: 'OpenAIResponsesModelSettings', temperature_kept: bool
|
|
) -> None:
|
|
"""Not a VCR test: pins GPT-5.6 sampling-param handling before the request is sent.
|
|
|
|
GPT-5.6 defaults to reasoning on at 'medium' yet accepts `effort='none'`. The probe against the
|
|
real API confirmed sampling is rejected while reasoning is active and accepted when it is off;
|
|
this pins that pydantic-ai drops/keeps `temperature` to match, which a cassette would not verify.
|
|
"""
|
|
c = response_message(
|
|
[
|
|
ResponseOutputMessage(
|
|
id='output-1',
|
|
content=cast(list[Content], [ResponseOutputText(text='done', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
)
|
|
]
|
|
)
|
|
mock_client = MockOpenAIResponses.create_mock(c)
|
|
model = OpenAIResponsesModel('gpt-5.6-sol', provider=OpenAIProvider(openai_client=mock_client))
|
|
|
|
with warnings.catch_warnings(record=True) as caught:
|
|
warnings.simplefilter('always')
|
|
await Agent(model=model, model_settings=settings).run('Hello')
|
|
|
|
temperature = get_mock_responses_kwargs(mock_client)[0].get('temperature', omit)
|
|
warned = any('Sampling parameters' in str(w.message) for w in caught)
|
|
if temperature_kept:
|
|
assert temperature == 0.5
|
|
assert not warned
|
|
else:
|
|
assert temperature is omit
|
|
assert warned
|
|
|
|
|
|
async def test_parallel_tool_calls_not_sent_without_tools(allow_model_requests: None) -> None:
|
|
c = response_message(
|
|
[
|
|
ResponseOutputMessage(
|
|
id='output-1',
|
|
content=cast(list[Content], [ResponseOutputText(text='world', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
)
|
|
]
|
|
)
|
|
mock_client = MockOpenAIResponses.create_mock(c)
|
|
model = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(openai_client=mock_client))
|
|
agent = Agent(model=model, model_settings=OpenAIResponsesModelSettings(parallel_tool_calls=True))
|
|
|
|
await agent.run('Hello')
|
|
assert 'parallel_tool_calls' not in get_mock_responses_kwargs(mock_client)[0]
|
|
|
|
|
|
async def test_openai_responses_tool_choice_list_unsupported_raises_error(allow_model_requests: None) -> None:
|
|
"""Tuple-resolved forcing must consult `_support_tool_forcing` in the Responses tuple branch too.
|
|
|
|
Same regression as the Chat-side test — a list[str] `tool_choice` resolved to `('required', {names})`
|
|
used to be sent without checking the model profile.
|
|
"""
|
|
c = response_message(
|
|
[
|
|
ResponseOutputMessage(
|
|
id='output-1',
|
|
content=cast(list[Content], [ResponseOutputText(text='ok', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
)
|
|
]
|
|
)
|
|
mock_client = MockOpenAIResponses.create_mock(c)
|
|
profile = OpenAIModelProfile(openai_supports_tool_choice_required=False)
|
|
model = OpenAIResponsesModel('custom-model', provider=OpenAIProvider(openai_client=mock_client), profile=profile)
|
|
|
|
tools = [
|
|
ToolDefinition(name='get_weather', parameters_json_schema={'type': 'object', 'properties': {}}),
|
|
ToolDefinition(name='get_time', parameters_json_schema={'type': 'object', 'properties': {}}),
|
|
]
|
|
mrp = ModelRequestParameters(function_tools=tools, allow_text_output=True)
|
|
|
|
with pytest.raises(
|
|
UserError,
|
|
match=re.escape("tool_choice=['get_weather'] is not supported by model 'custom-model'"),
|
|
):
|
|
await direct_model_request(
|
|
model,
|
|
[ModelRequest.user_text_prompt('What is the weather?')],
|
|
model_settings={'tool_choice': ['get_weather']},
|
|
model_request_parameters=mrp,
|
|
)
|
|
|
|
|
|
async def test_responses_tool_choice_kept_when_only_builtin_tools(allow_model_requests: None) -> None:
|
|
"""Regression: with builtin tools but no function/output tools and `allow_text_output=False`,
|
|
`_get_responses_tool_choice` previously returned `tool_choice=None` because `tool_defs` was empty,
|
|
which silently dropped the resolved `'required'` value. Builtin tools merged in by
|
|
`_responses_create` then went out with the API default `'auto'`.
|
|
"""
|
|
c = response_message(
|
|
[
|
|
ResponseOutputMessage(
|
|
id='output-1',
|
|
content=cast(list[Content], [ResponseOutputText(text='ok', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
)
|
|
]
|
|
)
|
|
mock_client = MockOpenAIResponses.create_mock(c)
|
|
model = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(openai_client=mock_client))
|
|
|
|
mrp = ModelRequestParameters(native_tools=[WebSearchTool()], allow_text_output=False)
|
|
await model.request([ModelRequest.user_text_prompt('search the web')], None, mrp)
|
|
|
|
kwargs = get_mock_responses_kwargs(mock_client)[0]
|
|
assert kwargs.get('tool_choice') == 'required'
|
|
assert kwargs.get('tools')
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
('aspect_ratio', 'explicit_size', 'expected_size'),
|
|
[
|
|
('1:1', 'auto', '1024x1024'),
|
|
('2:3', '1024x1536', '1024x1536'),
|
|
('3:2', 'auto', '1536x1024'),
|
|
],
|
|
)
|
|
def test_openai_responses_image_generation_tool_aspect_ratio_mapping(
|
|
aspect_ratio: ImageAspectRatio,
|
|
explicit_size: Literal['1024x1024', '1024x1536', '1536x1024', 'auto'],
|
|
expected_size: Literal['1024x1024', '1024x1536', '1536x1024'],
|
|
) -> None:
|
|
tool = ImageGenerationTool(aspect_ratio=aspect_ratio, size=explicit_size)
|
|
assert _resolve_openai_image_generation_size(tool) == expected_size
|
|
|
|
|
|
def test_openai_responses_image_generation_tool_aspect_ratio_invalid() -> None:
|
|
tool = ImageGenerationTool(aspect_ratio='16:9')
|
|
|
|
with pytest.raises(UserError, match='OpenAI image generation only supports `aspect_ratio` values'):
|
|
_resolve_openai_image_generation_size(tool)
|
|
|
|
|
|
def test_openai_responses_image_generation_tool_aspect_ratio_conflicts_with_size() -> None:
|
|
tool = ImageGenerationTool(aspect_ratio='1:1', size='1536x1024')
|
|
|
|
with pytest.raises(UserError, match='cannot combine `aspect_ratio` with a conflicting `size`'):
|
|
_resolve_openai_image_generation_size(tool)
|
|
|
|
|
|
def test_openai_responses_image_generation_tool_unsupported_size_raises_error() -> None:
|
|
tool = ImageGenerationTool(size='2K')
|
|
with pytest.raises(UserError, match='OpenAI image generation only supports `size` values'):
|
|
_resolve_openai_image_generation_size(tool)
|
|
|
|
|
|
async def test_openai_responses_image_generation_tool_options(allow_model_requests: None) -> None:
|
|
c = response_message(
|
|
[
|
|
ResponseOutputMessage(
|
|
id='output-1',
|
|
content=cast(list[Content], [ResponseOutputText(text='done', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
)
|
|
]
|
|
)
|
|
mock_client = MockOpenAIResponses.create_mock(c)
|
|
model = OpenAIResponsesModel('gpt-5.5', provider=OpenAIProvider(openai_client=mock_client))
|
|
agent = Agent(
|
|
model=model,
|
|
capabilities=[
|
|
NativeTool(
|
|
ImageGenerationTool(
|
|
action='generate',
|
|
model='gpt-image-2',
|
|
background='opaque',
|
|
output_format='jpeg',
|
|
size='1536x1024',
|
|
)
|
|
)
|
|
],
|
|
)
|
|
|
|
result = await agent.run('Generate an image.')
|
|
|
|
assert result.output == 'done'
|
|
response_kwargs = get_mock_responses_kwargs(mock_client)[0]
|
|
assert len(response_kwargs['tools']) == 1
|
|
assert response_kwargs['tools'] == snapshot(
|
|
[
|
|
{
|
|
'type': 'image_generation',
|
|
'action': 'generate',
|
|
'background': 'opaque',
|
|
'moderation': 'auto',
|
|
'output_compression': 100,
|
|
'output_format': 'jpeg',
|
|
'partial_images': 0,
|
|
'quality': 'auto',
|
|
'size': '1536x1024',
|
|
'model': 'gpt-image-2',
|
|
}
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_image_generation_tool_input_fidelity_set(allow_model_requests: None) -> None:
|
|
c = response_message(
|
|
[
|
|
ResponseOutputMessage(
|
|
id='output-1',
|
|
content=cast(list[Content], [ResponseOutputText(text='done', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
)
|
|
]
|
|
)
|
|
mock_client = MockOpenAIResponses.create_mock(c)
|
|
model = OpenAIResponsesModel('gpt-5.5', provider=OpenAIProvider(openai_client=mock_client))
|
|
agent = Agent(
|
|
model=model,
|
|
capabilities=[
|
|
NativeTool(
|
|
ImageGenerationTool(
|
|
action='generate',
|
|
model='gpt-image-2',
|
|
background='opaque',
|
|
output_format='jpeg',
|
|
size='1536x1024',
|
|
input_fidelity='high',
|
|
)
|
|
)
|
|
],
|
|
)
|
|
|
|
result = await agent.run('Generate an image.')
|
|
|
|
assert result.output == 'done'
|
|
response_kwargs = get_mock_responses_kwargs(mock_client)[0]
|
|
assert len(response_kwargs['tools']) == 1
|
|
assert response_kwargs['tools'] == snapshot(
|
|
[
|
|
{
|
|
'type': 'image_generation',
|
|
'action': 'generate',
|
|
'background': 'opaque',
|
|
'input_fidelity': 'high',
|
|
'moderation': 'auto',
|
|
'output_compression': 100,
|
|
'output_format': 'jpeg',
|
|
'partial_images': 0,
|
|
'quality': 'auto',
|
|
'size': '1536x1024',
|
|
'model': 'gpt-image-2',
|
|
}
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_model_simple_response_with_tool_call(allow_model_requests: None, openai_api_key: str):
|
|
model = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key))
|
|
|
|
agent = Agent(model=model)
|
|
|
|
@agent.tool_plain
|
|
async def get_capital(country: str) -> str:
|
|
return 'Potato City'
|
|
|
|
result = await agent.run('What is the capital of PotatoLand?')
|
|
assert result.output == snapshot('The capital of PotatoLand is Potato City.')
|
|
|
|
|
|
async def test_openai_responses_output_type(allow_model_requests: None, openai_api_key: str):
|
|
model = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key))
|
|
|
|
class MyOutput(TypedDict):
|
|
name: str
|
|
age: int
|
|
|
|
agent = Agent(model=model, output_type=MyOutput)
|
|
result = await agent.run('Give me the name and age of Brazil, Argentina, and Chile.')
|
|
assert result.output == snapshot({'name': 'Brazil', 'age': 2023})
|
|
|
|
|
|
async def test_openai_responses_reasoning_effort(allow_model_requests: None, openai_api_key: str):
|
|
model = OpenAIResponsesModel('o3-mini', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model=model, model_settings=OpenAIResponsesModelSettings(openai_reasoning_effort='low'))
|
|
result = await agent.run(
|
|
'Explain me how to cook uruguayan alfajor. Do not send whitespaces at the end of the lines.'
|
|
)
|
|
assert [line.strip() for line in result.output.splitlines()] == snapshot(
|
|
[
|
|
'Ingredients for the dough:',
|
|
'• 300 g cornstarch',
|
|
'• 200 g flour',
|
|
'• 150 g powdered sugar',
|
|
'• 200 g unsalted butter',
|
|
'• 3 egg yolks',
|
|
'• Zest of 1 lemon',
|
|
'• 1 teaspoon vanilla extract',
|
|
'• A pinch of salt',
|
|
'',
|
|
'Ingredients for the filling (dulce de leche):',
|
|
'• 400 g dulce de leche',
|
|
'',
|
|
'Optional coating:',
|
|
'• Powdered sugar for dusting',
|
|
'• Grated coconut',
|
|
'• Crushed peanuts or walnuts',
|
|
'• Melted chocolate',
|
|
'',
|
|
'Steps:',
|
|
'1. In a bowl, mix together the cornstarch, flour, powdered sugar, and salt.',
|
|
'2. Add the unsalted butter cut into small pieces. Work it into the dry ingredients until the mixture resembles coarse breadcrumbs.',
|
|
'3. Incorporate the egg yolks, lemon zest, and vanilla extract. Mix until you obtain a smooth and homogeneous dough.',
|
|
'4. Wrap the dough in plastic wrap and let it rest in the refrigerator for at least one hour.',
|
|
'5. Meanwhile, prepare a clean workspace by lightly dusting it with flour.',
|
|
'6. Roll out the dough on the working surface until it is about 0.5 cm thick.',
|
|
'7. Use a round cutter (approximately 3-4 cm in diameter) to cut out circles. Re-roll any scraps to maximize the number of cookies.',
|
|
'8. Arrange the circles on a baking sheet lined with parchment paper.',
|
|
'9. Preheat the oven to 180°C (350°F) and bake the cookies for about 10-12 minutes until they are lightly golden at the edges. They should remain soft.',
|
|
'10. Remove the cookies from the oven and allow them to cool completely on a rack.',
|
|
'11. Once the cookies are cool, spread dulce de leche on the flat side of one cookie and sandwich it with another.',
|
|
'12. If desired, roll the edges of the alfajores in powdered sugar, grated coconut, crushed nuts, or dip them in melted chocolate.',
|
|
'13. Allow any coatings to set before serving.',
|
|
'',
|
|
'Enjoy your homemade Uruguayan alfajores!',
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_reasoning_generate_summary(allow_model_requests: None, openai_api_key: str):
|
|
model = OpenAIResponsesModel('computer-use-preview', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(
|
|
model=model,
|
|
model_settings=OpenAIResponsesModelSettings(
|
|
openai_reasoning_summary='concise',
|
|
openai_truncation='auto',
|
|
),
|
|
)
|
|
result = await agent.run('What should I do to cross the street?')
|
|
assert result.output == snapshot("""\
|
|
To cross the street safely, follow these steps:
|
|
|
|
1. **Use a Crosswalk**: Always use a designated crosswalk or pedestrian crossing whenever available.
|
|
2. **Press the Button**: If there is a pedestrian signal button, press it and wait for the signal.
|
|
3. **Look Both Ways**: Look left, right, and left again before stepping off the curb.
|
|
4. **Wait for the Signal**: Cross only when the pedestrian signal indicates it is safe to do so or when there is a clear gap in traffic.
|
|
5. **Stay Alert**: Be mindful of turning vehicles and stay attentive while crossing.
|
|
6. **Walk, Don't Run**: Walk across the street; running can increase the risk of falling or not noticing an oncoming vehicle.
|
|
|
|
Always follow local traffic rules and be cautious, even when crossing at a crosswalk. Safety is the priority.\
|
|
""")
|
|
|
|
|
|
async def test_openai_responses_system_prompt(allow_model_requests: None, openai_api_key: str):
|
|
model = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model=model, system_prompt='You are a helpful assistant.')
|
|
result = await agent.run('What is the capital of France?')
|
|
assert result.output == snapshot('The capital of France is Paris.')
|
|
|
|
|
|
async def test_openai_responses_model_retry(allow_model_requests: None, openai_api_key: str):
|
|
model = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model=model)
|
|
|
|
@agent.tool_plain
|
|
async def get_location(loc_name: str) -> str:
|
|
if loc_name == 'London':
|
|
return json.dumps({'lat': 51, 'lng': 0})
|
|
else:
|
|
raise ModelRetry('Wrong location, I only know about "London".')
|
|
|
|
result = await agent.run('What is the location of Londos and London?')
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What is the location of Londos and London?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ToolCallPart(
|
|
tool_name='get_location',
|
|
args='{"loc_name":"Londos"}',
|
|
tool_call_id=IsStr(),
|
|
id='fc_67e547c540648191bc7505ac667e023f0ae6111e84dd5c08',
|
|
provider_name='openai',
|
|
),
|
|
ToolCallPart(
|
|
tool_name='get_location',
|
|
args='{"loc_name":"London"}',
|
|
tool_call_id=IsStr(),
|
|
id='fc_67e547c55c3081919da7a3f7fe81a1030ae6111e84dd5c08',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(details={'reasoning_tokens': 0}),
|
|
model_name='gpt-4o-2024-08-06',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 3, 27, 12, 42, 44, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_67e547c48c9481918c5c4394464ce0c60ae6111e84dd5c08',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelRequest(
|
|
parts=[
|
|
RetryPromptPart(
|
|
content='Wrong location, I only know about "London".',
|
|
tool_name='get_location',
|
|
tool_call_id=IsStr(),
|
|
timestamp=IsDatetime(),
|
|
),
|
|
ToolReturnPart(
|
|
tool_name='get_location',
|
|
content='{"lat": 51, "lng": 0}',
|
|
tool_call_id=IsStr(),
|
|
timestamp=IsDatetime(),
|
|
),
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
TextPart(
|
|
content="""\
|
|
It seems "Londos" might be incorrect or unknown. If you meant something else, please clarify.
|
|
|
|
For **London**, it's located at approximately latitude 51° N and longitude 0° W.\
|
|
""",
|
|
id='msg_67e547c615ec81918d6671a184f82a1803a2086afed73b47',
|
|
provider_name='openai',
|
|
)
|
|
],
|
|
usage=RequestUsage(input_tokens=335, output_tokens=44, details={'reasoning_tokens': 0}),
|
|
model_name='gpt-4o-2024-08-06',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 3, 27, 12, 42, 45, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_67e547c5a2f08191802a1f43620f348503a2086afed73b47',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_image_as_binary_content_input(
|
|
allow_model_requests: None, image_content: BinaryContent, openai_api_key: str
|
|
):
|
|
m = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(m)
|
|
|
|
result = await agent.run(['What fruit is in the image?', image_content])
|
|
assert result.output == snapshot('The fruit in the image is a kiwi.')
|
|
|
|
|
|
async def test_openai_responses_audio_as_binary_content_input(
|
|
allow_model_requests: None, audio_content: BinaryContent, openai_api_key: str
|
|
):
|
|
m = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(m)
|
|
|
|
with pytest.raises(NotImplementedError):
|
|
await agent.run(['Whose name is mentioned in the audio?', audio_content])
|
|
|
|
|
|
async def test_openai_responses_document_as_binary_content_input(
|
|
allow_model_requests: None, document_content: BinaryContent, openai_api_key: str
|
|
):
|
|
m = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(m)
|
|
|
|
result = await agent.run(['What is in the document?', document_content])
|
|
assert result.output == snapshot('The document contains the text "Dummy PDF file."')
|
|
|
|
|
|
async def test_openai_responses_document_url_input(allow_model_requests: None, openai_api_key: str):
|
|
m = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(m)
|
|
|
|
document_url = DocumentUrl(url='https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf')
|
|
|
|
result = await agent.run(['What is the main content on this document?', document_url])
|
|
assert result.output == snapshot(
|
|
'The main content of this document is a simple text placeholder: "Dummy PDF file."'
|
|
)
|
|
|
|
|
|
async def test_openai_responses_text_document_url_input(allow_model_requests: None, openai_api_key: str):
|
|
m = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(m)
|
|
|
|
text_document_url = DocumentUrl(url='https://example-files.online-convert.com/document/txt/example.txt')
|
|
|
|
result = await agent.run(['What is the main content on this document?', text_document_url])
|
|
assert result.output == snapshot(
|
|
'The main content of this document is an example of a TXT file type, with an explanation of the use of placeholder names like "John Doe" and "Jane Doe" in legal, medical, and other contexts. It discusses the practice in the U.S. and Canada, mentions equivalent practices in other English-speaking countries, and touches on cultural references. The document also notes that it\'s an example file created by an online conversion tool, with content sourced from Wikipedia under a Creative Commons license.'
|
|
)
|
|
|
|
|
|
async def test_openai_responses_image_url_input(allow_model_requests: None, openai_api_key: str):
|
|
m = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(m)
|
|
|
|
result = await agent.run(
|
|
[
|
|
'hello',
|
|
ImageUrl(url='https://t3.ftcdn.net/jpg/00/85/79/92/360_F_85799278_0BBGV9OAdQDTLnKwAPBCcg1J7QtiieJY.jpg'),
|
|
]
|
|
)
|
|
assert result.output == snapshot("Hello! I see you've shared an image of a potato. How can I assist you today?")
|
|
|
|
|
|
async def test_openai_responses_stream(allow_model_requests: None, openai_api_key: str):
|
|
model = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model=model)
|
|
|
|
@agent.tool_plain
|
|
async def get_capital(country: str) -> str:
|
|
return 'Paris'
|
|
|
|
output_text: list[str] = []
|
|
async with agent.run_stream('What is the capital of France?') as result:
|
|
async for output in result.stream_text():
|
|
output_text.append(output)
|
|
async for response in result.stream_response(debounce_by=None):
|
|
assert response == snapshot(
|
|
ModelResponse(
|
|
parts=[
|
|
TextPart(
|
|
content='The capital of France is Paris.',
|
|
id='msg_67e554a28bec8191b56d3e2331eff88006c52f0e511c76ed',
|
|
provider_name='openai',
|
|
)
|
|
],
|
|
usage=RequestUsage(input_tokens=278, output_tokens=9, details={'reasoning_tokens': 0}),
|
|
model_name='gpt-4o-2024-08-06',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 3, 27, 13, 37, 38, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_67e554a21aa88191b65876ac5e5bbe0406c52f0e511c76ed',
|
|
finish_reason='stop',
|
|
)
|
|
)
|
|
|
|
assert output_text == snapshot(['The capital of France is Paris.'])
|
|
|
|
|
|
async def test_openai_include_raw_annotations_streaming(allow_model_requests: None, openai_api_key: str):
|
|
prompt = 'What is the tallest mountain in Alberta? Provide one sentence with a citation.'
|
|
instructions = 'Use web search and include citations in your answer.'
|
|
|
|
model = OpenAIResponsesModel('gpt-5.2', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model, instructions=instructions, capabilities=[NativeTool(WebSearchTool())])
|
|
|
|
settings = OpenAIResponsesModelSettings(openai_include_raw_annotations=True)
|
|
|
|
async with agent.run_stream_events(prompt, model_settings=settings) as event_stream:
|
|
events = [event async for event in event_stream]
|
|
annotation_event = next(
|
|
event
|
|
for event in events
|
|
if isinstance(event, PartDeltaEvent)
|
|
and isinstance(event.delta, TextPartDelta)
|
|
and event.delta.provider_details
|
|
and 'annotations' in event.delta.provider_details
|
|
)
|
|
assert annotation_event.delta.provider_details == snapshot(
|
|
{
|
|
'annotations': [
|
|
{
|
|
'type': 'url_citation',
|
|
'start_index': 77,
|
|
'end_index': 162,
|
|
'title': 'Mount Columbia | mountain, Alberta, Canada | Britannica',
|
|
'url': 'https://www.britannica.com/place/Mount-Columbia?utm_source=openai',
|
|
}
|
|
]
|
|
}
|
|
)
|
|
|
|
model2 = OpenAIResponsesModel('gpt-5.2', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent2 = Agent(model2, instructions=instructions, capabilities=[NativeTool(WebSearchTool())])
|
|
async with agent2.run_stream_events(prompt) as event_stream2:
|
|
events2 = [event async for event in event_stream2]
|
|
assert not any(
|
|
(
|
|
isinstance(event, PartDeltaEvent)
|
|
and isinstance(event.delta, TextPartDelta)
|
|
and event.delta.provider_details
|
|
and 'annotations' in event.delta.provider_details
|
|
)
|
|
or (
|
|
isinstance(event, PartEndEvent)
|
|
and isinstance(event.part, TextPart)
|
|
and event.part.provider_details
|
|
and 'annotations' in event.part.provider_details
|
|
)
|
|
for event in events2
|
|
)
|
|
|
|
model3 = OpenAIResponsesModel('gpt-5.2', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent3 = Agent(model3, instructions='Answer directly.')
|
|
settings3 = OpenAIResponsesModelSettings(openai_include_raw_annotations=True)
|
|
async with agent3.run_stream_events('What is 2+2?', model_settings=settings3) as event_stream3:
|
|
events3 = [event async for event in event_stream3]
|
|
assert not any(
|
|
(
|
|
isinstance(event, PartDeltaEvent)
|
|
and isinstance(event.delta, TextPartDelta)
|
|
and event.delta.provider_details
|
|
and 'annotations' in event.delta.provider_details
|
|
)
|
|
or (
|
|
isinstance(event, PartEndEvent)
|
|
and isinstance(event.part, TextPart)
|
|
and event.part.provider_details
|
|
and 'annotations' in event.part.provider_details
|
|
)
|
|
for event in events3
|
|
)
|
|
|
|
|
|
async def test_openai_responses_model_http_error(allow_model_requests: None, openai_api_key: str):
|
|
"""Set temperature to -1 to trigger an error, given only values between 0 and 1 are allowed."""
|
|
model = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model=model, model_settings=OpenAIResponsesModelSettings(temperature=-1))
|
|
|
|
with pytest.raises(ModelHTTPError):
|
|
async with agent.run_stream('What is the capital of France?'):
|
|
... # pragma: lax no cover
|
|
|
|
|
|
async def test_openai_responses_model_builtin_tools_web_search(allow_model_requests: None, openai_api_key: str):
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
settings = OpenAIResponsesModelSettings(openai_native_tools=[{'type': 'web_search'}])
|
|
agent = Agent(model=model, model_settings=settings)
|
|
result = await agent.run('Give me the top 3 news in the world today')
|
|
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='Give me the top 3 news in the world today',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_0e3d55e9502941380068c4aaa4efb081958605d7b31e838366',
|
|
signature='gAAAAABoxKrgd0uCWxLjgCiIWj3ei9eYp9sdRdHLVNWOpZvOS6TS_8hF6IEgz5acjqUiaGnXfLl3kn78UERavEItdZ-6PupaB2V7M8btQ2v76ZJCPXR5DGvXe3K2y_zrSLC-qbX4ui3hPfGG01qGiftAM7m04zuCdJ33SVDyOasB8uzV7vSqFzM4CkcAeN0jueQtuGDJ9U5Qq9blCXo6Vxx4BVOVPYnCONMQvwJXlbZ7i_s3VmUFFDf2GlNYtkT07Z1Uc5ESVUVDYfVC2qlOWWp2MLh20tbsUMqHPYzO0R7Y1lmwAqNxaT4HIhhlQ0xVer1qBRgUfLn1fGXX0vBb4rN0N_w7c2w-iwY-4XAvhAr-Y3pejueHfepmv76G67cJVQjzgM37wlQFdl_UmDfkVDIxmAE62QjOjPs8TweVPEXUXAK4itTDQiS7M42dS6QzxivPVvzoMkNOjJ58vUy83DCr-Obw8SMfFGB5sd1hGg9enLYiGxN_Qzs9IGegBU4cH1wpCvARmuVP10-CJe0jzSFy0OI76JUgGMVido_cEgrAF5eEOS-3vkel6L07Q9Sl_f8C-ZW04zF40ZIvCZ4RJfRAKr2bfXH6IVNhu528-ilQTCoCeFy_CG6UYlUY2jws_DRuTsAVb6691hPRI8mG28NCPXNGV5h8sVgypbeqWyBNZEnSgqFcNVplAPTxDNqlcFps5bEND4Q0SLSNTZv9vFbRvfyrf-4s3UWqn-SI4QAmGzKRRuTumEpldsTuZgv69Nu2qA7px1ZNu-hN7S0E7ONGDs2fCaUG4X-Xp3j2fizfaTkZpOC_sdTK5e10lIG019zKGngXSrBy_sOWyTIsjiRGdr0Va-RjDw2ruFr3ewQcH5vZ8LgUwTzijfqLqbkF1zgZopHTnz1Gpt42AbZiyP30S9BQuDODD8RmtZQ5oB1NKmISeGkLCJRd6dZKGibFskFFMFr53YvUfVZx4mRpxSjuadceNKPhTVkbGPYE6XrZbChCxDL9aJJ37ctRxf91r9QAXMqeFZR-4HR13_Pp0AyN_H7gqBR2yVuGbXkhs1QwkEhl-6_keNsJYUaRSSf5QN9gRjsuWchWEsTr8AqTbIApGO24a5Rr4GDnZ_6ICYBr-IhUesv0VJKQF3DcNFaOQCLtLTKCC4G4SqURt60V0zkQKWBdUdUGFkxDUN5gtcKrR0F4J5hvZ6OMV3XaP6kpgx62TL_gd9g_QyV8QDFwXuDDrGyXi6l68veZXOElkZ4lpVAjfeXnysK401DRt3vF0z99wUc-QVMjZG0wVZUr5rYHjKKaB2vG85n_onMrddThz2_a1NG_THQZ3L1rprThcQY7FdPtw1JXWfXWeS7ZuOOZCZvjyCrVhevaxTl5UKNbkguqYhNJQfx5X8IkwJWVRObA3QxFD0ZEgW9OKt-v-g_EAsjtftPbeeqaDfPBwqVguYJUEZqPPwcsG2cv8Xu5sCc6h7J8fvwTK-MY847JS5Q5CSDe4GDFvJn4Tk4aIOeGlr-VlrgwOS_yaKd1GogBIDzjh8pXIXXSDP2UkEOd2T0zSoa0u8oewPf8Pwmd7pmVb10Y9tHPgEo44ZQRiyVCe9S36BVjf1iZgTYetfBfq9JJom1Ksz-WUf74sHYfLkUY96lOlSvziyFFmTXxFgssLFgtBuWNaehKeuJ0QiQm2r4jEvX3n7dvUj09tWw_boLWGUJqL5YkxVadlw8wF1KRFJjGIAvEvO7YNoEoyolmS9616ZBvWNlBg54A5DITXEfIMloXVYNmYomoBloM74USiV7AjQE5hPIIqO97dW4btd2zMx9Nbr8G-nZsLgCqrqzDVz0UorAHTgaThtp9BW6VJZJ9q3Ew_z_494P7GNv9ehuK6m3fT-MXIq-t0Bo28YGgGhiFjoYSSYUd1adlHQdPHZCxZojt4-DxgD3iFoWQGc7BBRU3f9rRVRzbDvlHpaLRUQUFXiaB6rQ=',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='web_search',
|
|
args={'query': 'top world news September 12, 2025 Reuters', 'type': 'search'},
|
|
tool_call_id='ws_0e3d55e9502941380068c4aaab56508195a1effa9583720d20',
|
|
id='ws_0e3d55e9502941380068c4aaab56508195a1effa9583720d20',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='web_search',
|
|
content={'status': 'completed'},
|
|
tool_call_id='ws_0e3d55e9502941380068c4aaab56508195a1effa9583720d20',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_0e3d55e9502941380068c4aaaef4b481959dfd7d8031662152',
|
|
signature='gAAAAABoxKrgnD1AQrb0I2TSTLa6DiSpAwMbkF6gxz_b8tkkns4MZ4Rr6a8ejwmX7aGMoXEgOO2nkuLFeKoQBzQBrfNZIhmCy68QZMQQKZfKBUv1k8OAKzz4A1dO-xNH6xLMS-3cG4ev4zqjQEOBSGoZNKcZMU9L3B0VCvZsBU7S50g7zCcVwEk6H0wx4HO6IuUEOzgqqx8NYHmOkudSv3ikiHn1xhLc1JEzXkupTyRxyw1O81jJEpNzLlEUIFeu0vkAJrlwQzAHeEzxFMMQMoru3pKwnzujgljefGG8RY34jsAc6XcbJSstAa5GnKn24ehA_CQu80ICcibs7LBKsa3oO8wWWHXgDhMCPJn0N322MZcHfH77PhgEr-T1YSIRrSMPXcxoPaptN0O4ceK9BYN4FDRddaR1jXzWdZ3VhYBNbRrQEuO6z0TOWsPmzIlDql1a20jiOteGNQgIX94Af4PB5g_DYWzJW8YVffnhKXJEmU7BmYuctQgyewLj_CoQYfQ9HtGcae6ZElUEP96lo1ID3AW2iMa3iP4C2xULWDVh-8rWf0D2fgS1toexXXCtWbXn8XlYMGWVjq3WX5q16Kq0KyInuCZleABTeFRuzh0MTx1GaYhDTwHxG8BRPYUxz0bHHESz-h_UGmhGu8-a49YdBpLe36_Z1wprXJ82Yg7KvJy68VwKnLeH1Zm56aMHviJl143iZYgiZaVmRBIRExMvnI9LVAT5pv0Y3CdCCSq8Bs2jSbhU0xe26HAqfZZnAsE0LpPAfW1tMCiKzqhtzoKR6yauAYCXP5YtnX6BqFr-J8px6owPJhepjyrSVCObyya7v7_rV81BkYOtLQSwCUUhOjbawgI6XDQ_FK0hye5lFVKckFNM3cVpgRcZymeqx-XoQeoFOR8uLtcXv2DIoo0TfP7RxgBvAvdohv8vZx7xJSXlrYKqLEK1ASQDcc36gIfNQuNXM24WuXForXTO2l_sTeos58eX5FGxWJFDghhrNa_ia1dL7towjcegQzf9LtLjLlnqUGpEte-o23DKKQQEiFfMpLlvGu2cOVwYUuoeOpEBe7QpDbJGdBjq0hOKdakHGl6KwBw6vCkRp_wtW4R7QBuncdYyRT6AJ1_Z_byBP7kH1A2-P6QMVycBVcXlUgc0BzuGlkt51l__O3CM4z-PmI8zR5cL6ZCXoQzG2Yp-OhQ-n-3hgMaCfBGca6J3wP1vgQpR2AF0',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='web_search',
|
|
args={'query': 'Nepal protests September 12 2025 Reuters', 'type': 'search'},
|
|
tool_call_id='ws_0e3d55e9502941380068c4aab0c534819593df0190332e7aa3',
|
|
id='ws_0e3d55e9502941380068c4aab0c534819593df0190332e7aa3',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='web_search',
|
|
content={'status': 'completed'},
|
|
tool_call_id='ws_0e3d55e9502941380068c4aab0c534819593df0190332e7aa3',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_0e3d55e9502941380068c4aab3df148195bdec4fe186f23877',
|
|
signature='gAAAAABoxKrgZN3V5pGaqoIM8EEiFso41O_kxOTWpzAh5Nlj3pqqDjIGrFH2zcmDyURpUmXdExY8L9K6KcwGOAlF6okEgQojeTxysBi4-gDVFNCVfp6c6K4tAtCBrvq5wC2g22Ny1pU2OMyxVU2GCxIIehCZiPQio_7IS8WY_VWkwLOag7bT4FBGn-aVFyoEfDDpIPF-4Zpcal6bAvdjD2hYGl6_-8alwh36ttUkJroo2qG-Mn0LsAWJ7YEzfrHgoPTDF7TB3Mfvvc5M_eP3pzY8O4WhZKMLBSnM92iIt5J3nSJYhRoiwEjaCamIM4vK0cnJR0oX87u_XtGvnNBX93ttrIrXDKK-mh-LIoe_sK1dViFINxk6rJHZvkFK12J6UXMK4me-C3uQ_qGygpw4uYvWhYk7LDR9Zgxfv1OoDg13DCYWWrHX7Oa1ALXPotk1Uw_Tof-Wc_wDqE16Elm1a5TP-ISH45v9W_Xl1IXo7J_jwOlAjkXvrh2a8YNljWQqBFCca-M2hSWvKuX8JuNF_tkI2q2E7jIDNt77jGd2yavqb1W2WoB_s7jqyAWomT91E2gZQtGJa4X2ydeTPQ_oWv2hgdTUynV0nbOKWA6suZixvxVDLLedhYHRnKY6EOtyso9MZav1qhr_DpHExn1_woquJXtS7c3Fe3Rs_YrU6PpRx5_DEVjVKme-3XjLJNclx6NF-rbXYqhXXExqPk-od7n-YMyrYhpfVP8lmLCewwyzVRb1koOEcCqnuhqM9DWyazKAcdvejM7VEM1AEk8ugT02cTiF7CfLefYFsLSYVBM0Ox47Ceh4BOA82jdlf1pZNvGqgHi8kKm9HLVh-yM_DAhD8O5Ub-SCd3bNi8735XPDWVIm6sKMdg1bcgVehz_R4iEBr_pguKfZUJLcckUTI6fitAQ6YSLpLAfRA0nMDBfM6p43jqsSCP8Ovjx58TwAPElgpme4ENBCozS_VaxmqawpfUfvnD60xia57wtSBYr5s1j-FUUjBsFTInjHdKcp0EBd3Pv-mpVE-Yj0MYExbn1upi3RxWN6jwVeYc603HQBjsjqsb-op9Tb0GZxf5Z4DpZ_eeb4IBTWNf3FTLIbsVg18Oyl128Std9CkMGak8iI_dFCvm1ZQQ6u3CyLEwxGsMZnkZl6OhSKDlnHDvRsF0F0OcRtFV5i7j92kMs9_qJ2JLdb5LzdqOBnFfKOcUCXBOflL58PYIav',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='web_search',
|
|
args={
|
|
'query': 'UN Security Council condemns attack in Doha September 12 2025 Reuters',
|
|
'type': 'search',
|
|
},
|
|
tool_call_id='ws_0e3d55e9502941380068c4aab597f48195ac7021b00e057308',
|
|
id='ws_0e3d55e9502941380068c4aab597f48195ac7021b00e057308',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='web_search',
|
|
content={'status': 'completed'},
|
|
tool_call_id='ws_0e3d55e9502941380068c4aab597f48195ac7021b00e057308',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_0e3d55e9502941380068c4aab85e7c81958c4b4ae526792e49',
|
|
signature='gAAAAABoxKrghWofTkZCzljg86Akl6ch3bNwR70Wz37t6mBpLah1wZ-7U6isPixPCn0t66fA6xKxGX75bmjRu8Gts4cIaYpm78c8n6R44UULYfQoDC9ZEyGgImbQUKoHFU63nSbsjuPTTFtLdLHhEebDE_t6AfqIWBlyZKRqYlXS_8mTZ_NwM5_JgJun1Xz-I3Pb0X5ZgX8RTP_Kh7Kk79PStvg0-qcVoxMtFsK4ZN3fzQBOSUwvkMhglIweiS3s9CpTbtOs0PYqFCOIjKEYZ2-Rt_7SKhOGaWEMuvuWggMLeO_Wkl8HyIHre5JolVFR9M-43XByZXQxrvBxFzzwHubiyCs-WHFicgMyZcAF8e2KR9KdUJxAwQ3acCi3zBc7e5q1jgc8-Csm-vZQJMTyABDu4yuLena6rF777C8jq-naUe5M-bBpiimK1nbpg5YDiwx7-TbZz5eiTpptHL3P6izhgEOXuEvLhlrhxPBKTezDkiwu-wjs0tHguRYbOIMf-3NZGHuYnOcGfC2wJKkE9DmRvbicnChrLqzHmiXWblYhPwsH9wt-QDvrz3tgCH4B3ri9APreQjBmxtZEVGQAtfdm1qpgiDcWqEijrj05rvr4HxbSReCFszZJDYAufNhJSPhuJXl4e7EHRLyVd2uJA264ONj-MxT2WRr4MGzubSXtPd1QJn7IEkCCuPZxbLf9q27DTSpAvS1oZVs1Ad0J4lbRV5tS_sG54JLvpXf4jtYHD-R2CG0vkL1i0273IJroXScLaPELp0iJMn-WzAkbEjjMsX8gmZlV2X06XuvSjry-dh2sU9Yldqw4NHMLM8rpZIfKbsm6w0ub5Icmu19E856R57JM3K3Pjm3fdO3HR-adVsJTAaIusyUVX3SOiTY53-X6UbqBJh5H3WOORqkwW2nGbNur6B_tyRjlegD3CGJzC-A9rNxMWrecALmCEJBwnXxOuvpsGkSgjP8vjnY9JJNj53hxAirHFIxknDMrKt5qlsRHxGlCdN9H7YuTGdTSgPWH_L9C4BtZrr2Qk41osiDCpacMwBeUDwo1YwYWd1SO0DEzm2qGlXSYeuAQ6Fvyc7sZHCkOsl-bINhCuY1aEBOLzXS7kcu0YAIuEZGVp5wUrr2L6YssdrzpzQ_KENFI7LiB2v5CrF1wZN85H2dkwaGciOXznAa0Su1fWD3BUdpyR0h_mVIcHUxmeoCywWbWO-Do3LFu70MMxKmfSzVfL9hlU2B2jo1aqJ5HesWsWbsbslW5FfREayeUzK7hxkrjliDePhN6gkfy0HOYQijPN6dko4TNEeKFO6Q-aw7c4X5IF3WBCYd_IszlLBK-vTX4EX2J5QtaLRfwFgRwz_K2fkOTT64eknQ6R3fFJpgeyLBZ5ut7j2o7xhEuHeE4KPm2T_AJi8yRScMU-ZsDcUZ8IVYAduy2TGov51AM7K2WojgvqWi62AwSLd16eEnd7SUD8fiCwtRN3zTdmh3MenUogxtKG2YL4hUvSN6Ia1STXpfU4ToLvBnPS5FoY2GuOG-EdEAHdKfYsSUZmSauAlQy7sT43STLkDE42lOKWqtSNHOygkGUodv1GNR0sA6CIg_gVAOyUG-o20rMsfANynNokpoKxJBPJScf1Mbivm-7wJFRipf2-Ay4HzXhXZ4RTkpoq2MMC7cZkHkEprUlLshEhCIHF_6sb1Uhqg4E3UPCCNZ-X0epbQ2GmhtaaIt6BCnWz4SccN5qTks5XpQarlyTW1HubLoLjjXmwJ5DImdUGZkitiJw6ermiOFAFhLfhug-XVKBcTBZOG_CHjrR_2j5TPn6FNLHbYpLYS5hkrUWCJy4U_1xebGl3F6VdQDy3LHZehxuKPowPtdYFenqdJ-naK_A2ygjDUdGBoB2-QFaq8ZPTAti5_Ca6LgiZPvzZdGZ712BED-Opges0mwyAhhsgKRvjjztcsiZ21QpfUaSGLS0vO7J-NcRVvCDyBisMRKfRcWk0PFa4LKcqx9_FNU9nqXH1RXYh_WNAJRVLJDR3WzpNzDv7xMcPOYUUx0wuAYAWcGbc3i5mkVRlzRW_WymBibPF_Y9Yf5yt7plmai5dzlg6aoRdrzSwT9Lphrf79QI3LfYzOV4sXmRGEnN1ud0FyfVB4aLHSsc59_eiPswLL-xg8XT0L27IU_Gja0VuE3zBlErtlQB4uPq778Ojs8hucNTD0rjxs2qqA==',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='web_search',
|
|
args={
|
|
'query': 'Israel airstrikes Yemen Sanaa September 10 2025 Reuters death toll',
|
|
'type': 'search',
|
|
},
|
|
tool_call_id='ws_0e3d55e9502941380068c4aabe83a88195b7a5ec62ec10a26e',
|
|
id='ws_0e3d55e9502941380068c4aabe83a88195b7a5ec62ec10a26e',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='web_search',
|
|
content={'status': 'completed'},
|
|
tool_call_id='ws_0e3d55e9502941380068c4aabe83a88195b7a5ec62ec10a26e',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_0e3d55e9502941380068c4aac1c2e081959628b721b679ddfb',
|
|
signature='gAAAAABoxKrgINXOfVTRxYfQpc8ZZGXsBHdv43DhHkpUjfExhAS41ACM9vHyRgDNfC9E62QVMWRCWPuz5QX9ks0NtD76PYS8n5bessBYeBtYgbiMtl0piW1gE5dlw-BeKLiijMhIVwytWhF3JTzoxoA60FjPK_sA8mFk6wDCNKDXlaLWsLaECxUwCtdktN9SQnQFgxKNemRKQTyRTNKsurCZSSt0tHyd4lxO0Ei3F2mO3WB4Oq28BeVG7RKlcZ9BmLRdBhFQX5eoLxTBHwC_qgSIGzoVCiyClW1OzFzXzmaCUCm3oUDQjooYIZtQqK1b8FBArzN9seOJ4vuxu2qqdtF-JC1vAi-_9J61EwELhN5gYvld83zGCSPg_asjeKeoA6qnA5RFtYwh5kmMSFo9VzGp9MlCmb4_-L-iux3JKc7Kz-jvF1sXSH7YfKgBvcn8HcOdXGjU-aBJTmdP3hCZSL9ko-NNsUO31667QwMZsQTlVoTCAfWS_xDEI0QgmV2kFReKhKanzMmOToUECPPQHQfofCGxwxjbGllSyhpSZHIdyjXpHBmwFALBflPAfeM8wUbqQbNyWbWTdx4Uz62Z4j0OGfcMpgMlDb6BON8vvpIjmlV-fOqRlzkP97klPBygPKeRyT-UezEN5Vj5t00nmB-cV2kNj1WYmL8-eBuJPs3LOU_4Q3ysb90AxYxRJGOsl74lEBqfUKb6b4JWff9JFv11EVJ-puIpE7MA3DPM4NcgGfDZYyDvLS589wbTVxSngBqEOIOEcAZF5Tae93Drajy_x8fXm9uWc8daMf5kqUeq_vwr-ZqEz5ZBUvhvGPL7xkYfTfn-RrQXBx2JfyDRakf4X4D1W6jaO_LXfExH922e9hQ1vH8VA_GPdOIqL5BTiIeO3qFjDSRxMi94XWPPRm87yStxEjx8bse00Bzi3grZ1c6M5dEUXNaHrnvEdJZECT6lz365_Qbl73_Ma_2CLYZhLhtqZRZ6Tycfpprg7rWxqTftOKq4twUgCzzv7kg0e1f_JM_om5loPP6r4MOeAL9O1p49tWmj1kQt_nmYcX1WFTQOgRuB_h3t6ZeOsDb3-VYjIjK0pvj_X_VArrT2suBVitTBXumnG2dXg_z2k5t4KTbWVe-aaGhije0VNxgPWCcu1RlIxOaz',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='web_search',
|
|
args={'query': 'typhoon September 12 2025', 'type': 'search'},
|
|
tool_call_id='ws_0e3d55e9502941380068c4aac378308195aca61a302c5ebae6',
|
|
id='ws_0e3d55e9502941380068c4aac378308195aca61a302c5ebae6',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='web_search',
|
|
content={'status': 'completed'},
|
|
tool_call_id='ws_0e3d55e9502941380068c4aac378308195aca61a302c5ebae6',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_0e3d55e9502941380068c4aac5d2bc8195b79e407fee2673cc',
|
|
signature='gAAAAABoxKrgkfoWE9D7tW3LtG9Hb8kBR9vHgjhSKvDrW0_FUU34LIByJhhBiwZOr5RfbqX9mBwahQKrIVAev3WGtBfgtJF0kP67CIXXRjA1-RHuY-4QXL_w-t9gttak5Dje2NU5hNyp-LyW0plO7DZwZDkFUgeW5plxMzcAFNTdflBSC_-zYqBFl9p-11YKOslzKYxkfQrDiodarFGFhDOJr97qwo-l4BhSg8jywQwgFOTSrOjJMlZRSrTkHd8CUaSF5rUaLKpY4AZWtpiR71otchA9N-d0AaVwnnzJbe53PXJpe4fGUkmkcZt-ZOcNTQlIpifirDsXln2Sc3jxSM05fteSPKoUeUFIIqbCaZwBPau45DKq54PvkVQ4Fpv8JtfqKEuQtJ6EVlNJALuDlskdxM2H3Z7XJsXkcNCVAKmpA80yYwh3eApMr_cERl2bLS9jJpGt8QN3z1yRe5oCPCNWj2_NTgtzjknxcFy8HdT-pcTzLDOhLJPYyl66psc0Hn8V_GFIFkRBa8tWb7CTLt77a3pW3Ifnxov5ANAaaJLM9gGiH_DgkkuNZMR3dz2sVnHzAG5TxmSQteu-uYQgIYanBH_D2BN24JfBFxckpT0z-kGHbJnL5q_wBeyy7o2puohaH3MNIluzWARcDWaFa1tGkzeZg59woqrrddAdWLRNULpnX9fzr7aAWXr1U5-XkSjyfWa4nmIFtchwPSC-12wHRNFDzdZiUvQDdJ2ENGoIXeYpob_O4Wa5zx4zZj_qHXoQWXLELyEMJZCVADjAjO8uy2gXDxZKcUxyDgi17hIyFtC9Z_4rxDbV_S_JJ68s1qHBZljuH0mrkLU0KXmYi5ZgB_z1CEaz9KkL32FGBt0YXuFoR0LjnrdpOTa9ifWC82ZhDfjz1E4y9FUoGPVl-QYQ5ihDY0LswB1x_FJfvwRLvLRtMeeGqNYEwnkX-XAcVa72acijnRJVxd5WjV5nolIrtq55l941oeun2ThZJZWujP7eMDuQ8SycBOx_6Bz6wECDbnCrfyxypwpVhKSPGuI1IoP_8fCeFDWzZZhD2bTbH2Uw6nzm9SLODQ47GqYlZ6ZtTIgNBlGpiSUrqXhtj9_1hkGZuGv6AE9UAjFNqAWX25db2I2uH1MXdsYRPLZFhYan9G60cozj6N0ekasNkbaAod39JQ7zL6Np2O_qz85s3bcJSS1_aIxW4YFSEv5IYFlztQrhnlyE_gloA8eRntHAinUaGbL9IKTmuj4w74Al1sN7ELITivL6aZ-EM-F7vvFM6Rt4gL0NvlfTYsafoUL99EfBTh3Rfl7pIwOQWXxg_p-51s13BQ1-HWOQxu1lyxbZdJHmhi-tIzk9iyQh1tbkCZJeh_qF-eGH6voxUlcz07gvTckVKR147UPjIrfSm6EO5zXBgva0Zk3nvGFCZshZSau3tLQrAnB7hQ3AAyQT8_6eFBHtsscuApVGtRYIw3vi9decgXmFdvSEg4Iq6JNObTilSq6a3zmUt8fop_M5qYzq-0ctNsXN5lkqi9iB19lLw9EyHNDgClaTAviXWh6aDdbWP-atkQQ82PXBnKJAiP7luW1qf-YVHtKkwNadbMy82CT-dMNu9c-chRSx3g0tdwTex6tgwKMdBRbPWa8NVZreuTy8x2yarHskXhHM21jrexM0pMbk',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='web_search',
|
|
args={'query': 'Nepal protests September 12 2025 BBC', 'type': 'search'},
|
|
tool_call_id='ws_0e3d55e9502941380068c4aac9b92081958054d2ec8fabe63f',
|
|
id='ws_0e3d55e9502941380068c4aac9b92081958054d2ec8fabe63f',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='web_search',
|
|
content={'status': 'completed'},
|
|
tool_call_id='ws_0e3d55e9502941380068c4aac9b92081958054d2ec8fabe63f',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_0e3d55e9502941380068c4aaccad0c8195bc667f0e38c64c2b',
|
|
signature='gAAAAABoxKrgxBU1Y3g_B0Eo5nVBHYxLC3Lgh2vNx7AcpSm-o7XiHfQvzLqaLvkI-Cc3F15mQexU0OTvx9FePdIKbwkMNm_X_s_K7YazPjZUTQ0TEod2VereH-Ebh6Xjq3bHm7mh5PWWGnY2SqVMCdKGtrXkoMzBraxedlv2-Tz8o0p6SYuyzM8yHecIfkG6Zd40AdZSiDzsnRNg7gA0zCddrDrRcOpeMTzSPw1z74UZtng-_pPeiv-TGCgwdlmBv8RRr2cuQTYE-yhcp6doCMKqemL8ShuIyfJz0KhQPwYE1zM1CB8sFc_TuArJJD3V2U-Bl3o8anIA8X7YclTlzz_N7HROtVI5qFQjSNhSrbxZKUBFDfAayrpQBEOyIRu7J42uAiBmoyms1WG1E2UtO69nx2ELSJs5yheEuVy4cTXyndBJr2sCs8VkVvcX7xvYkfKeChvkAbUfCotc991qAiyVNzhncM2Z31IEXDEDypeo2IFSwAcKuuXgePFFPBiJxmNQAQmErqbSoB3Woe1j5XjAzJ2eY5YEBZ-68GI3B5wmiZOLsPla_L4iBrczHI1iwGASgtMsuHPj5KVzwef093kg9QBlt-7pZHM3yoU1l5DFSJ5C168MdMdNGF3hn0T2Q3teUmJ5khgcKMKz4_ZVUjEDq8bPwp8DiaWlFgTv-Y-I8etik4o35EFmmmZbIZ7tk69xlBrGizm_KlcYWHBQ5BfuNyZDXZ13MKDyn4uyYxRvkHq4z4jPFEiZ3xX79mlNP3-B0T9g8CsqX1G1prKI7lde6oAHcWPFSWqZmM_JxvYXDBbck2DpEpx4xTuE_iJfGnKiNzanqV4EdOXiCTBVLZhMvXj9rAbwnhttvz5WhIeYAdsKEE0M1MUHuSWuWFVtClp6lPKSLtHQCBtE6mpPDyzUuaw6S1DoixZ6f33Sr8DB-EwF_deHRa95kEN9w4i_LqNbl5QQPF_1je6spo-yQTDpHc5wUidI0fBEQzM57rr9XH0F2afZtrQv9HcLfWKVufBTdd7ScpyOaKj70zgqTAq08Te-Yrj9eo3tbDt698U1fKEYW_uqP48ZKmnSNtFzKOoBzkPpKcwA5AQUiFOYH4-iDPDTOH23SYx8vlymoRiK1imCdPwWYI3miMURxPr9-zCHoM7AiB8cnJlD--zk-j1vQqcf3AntIKPwqycSEuJ7MWb9iN5Ybd1YE25_ZiXKJNVg8wnmTueelRdeM-2JVzAQwth1_3gnsemXn5v0uDVNpxvXoRtR1w8L_zQzKzag8kZMvfESnLCAEwYsCcrP-ngO97iKVvUQnII4RUtG_mSPV4V6Ses_cMUVqyHiM_W_frIosY-7dXnlox89-SPWrRwyC1jlGRA_LE1fpPZ2cZU7Gcyzrxp6yBuTCx8BHr9FJvqgbqtAUeYDpr_Sv-RsG8-w4IulSNZLH5Bh8TyvBGDhi8_lUbDCFTS3KI1ZJ8KJwbNLxF4YUI156zkWIN5yU0WDVlwoxpJD0naMPZzR0sQadMuaXEvLXTFm9Gtb667B2cjdzJqbb8z6NkAx3txRRD6EoezoYADq_ZR_LYha0iwv3bHvg4HIblhU_GVhnU-a-lQGQhTJ5Mh4OmrnTGUVD2Is1OVI0EmNscUuaVc7M1_ga5KbOgyff6bYS0ARh3Io5ekKQKkPVyBLgjjKlej4tB-vSEgitDhEJ-PD__ouuFaogm6twZy7hWVn9cgJmt-RHDZ6gOZm4QP8dWqRpuyEAtTpWR2TLTQVgM05hWpDqDL5AvBjAQ_GWkHCvdCvUINyyl5TsyXUcL207shrLUDCpBe_kESpF5dpAVng8_Zfu1dt3c04cCG1eg40e9JcO5iA9-upTrEPIPrXnAKy4vw-vbhQyL1r2jZWRVga9Do2idmzVf-c7yQ_AHGmf62SHGm-qqbljw0sXJe1rdPt2IHxzYXkhxpqqoaUueQk-pXLUvpMFeMcH97sK3toeCO3oiWQPG-nev0B0b__U8ntgI5m9df6n4IA97iS2zSylSY-F-XEJmLM2TKuSEdgAx1EBL_jyRQKB_8PW-0hSQGJLT70SQqDUJexwyrKABkApv3FuSH4FO0rXZ9TGN3GsnJSkIrTrzE2NG4OXK4syrmtBCb8DjsiicvjAvQhcouOM1xMZ89aSG9Psx5HRnViy6M73TIhYmWO71BRNEayMJaOMgUlgpl5alvV1YFBsChL6mxLVAJWUFuv2YPNaaDRqZEXYHWljhwSn24ASetweLc5GhnehdiT4JVJ_nfT3bygPIjEzvvIa7bbJSeL_bcY-qGAgsuR5m70BdjIH6xLmuqn3lEqulh9n6IPaDciryWqRr1OwxZJQ0-x3u6-G1wrbtrhVMK2Z6cyNUX6MvIMz39B_782X4JcLMrVm9Jgt6qzmfbJPnGA_NK3e9dlz6hP_AYoY-Je-IZEtpv4wyXAYE8v7QXsZbf6DetAM2LzGmxkEI647-pwVPQua-L-84L56GoAw9yDeoXxgyxyf40sbaPIiVLgl_3A4Nghl7uOnOX_1VnZL2X85zCkOZbmm5pZbuSeKesBYbX002PN-_P-P5xRv5b8dZzD0utGv4GUuZJXKJPhbpv8cuBUR0BYHKBQkmOzOBxgCFCDtX84VkZcrFwmQHcS7zmjgqEl39UNrqq6NZXW6HZDyi_SSvEYV7eJfJfxnUUF7RJ49RtSbC9n0AkzorBi0mSMnCC_A1zhamNLjT1-tj4E2a1zI9YsBZ8lPv3t7a6U85iMYjl3kCPiAXkRIDVBihBK4ki_OEa4v6kNBEgXNMuFmd1l8O3WTqZRSTLek4yH95V_uE5DQ9NH52pkgrN7QOe0QXxZ0aErqjkSQRbbhFVVRYp2VN7QpvMGZIAtu_mGssA5Id3X1ZsLEU9zGNibIzAmJdBjS98fVj2MsD-4qZmzlWiCGcC5ko2bbpTrFGtr4r3-SNc4UMOa3dsdyrRlnK3o_tbXbPN7c1H44oneAsqWuekfUVFGvCRm3yA0X7njFB2l8tSXkAuophgRUlWnzp4mEMcpFRwEX3WEnK9hPqXEhdirLtC18yupkKYBtIpCIT98zgJNb5TRbfwRplInEG1E8dk4gCbwyXCNu67QEI2NM2yqCHc4P5rWhwTGAl30tmDQ064ba920L9ZV8d6PgpBHZmUxpJ-JUZuYMzXfCFdlBQANdjtuxCy3-Pi0-cO7UEA84WN-keYB-kHck3aPpeTG7-lv3je0N-407H_A1TKUqkSknjlmwVdL3h41bbGmqxFGizNXfq-uCGUD2tWaZ-cdmZZtGXxgEQ2z7_tLur28eS1tlx43y9CKtKPPJruJm_7BljMOCMPnSmOJDI0JnoGpjNRqzKbSuZFTihaQSBo_Vc-NxRpFwM4xJgq3z5eShb_WamKw9uYrjCBEEwYFTW2QjmiQJtM9eVHBuLkfOVa66YZowcCvL8aCccsuPbe7KBMCD21IGzH4nlhfgUKa1cTAUiWjRSgn6SO5Wqahxs7dEf44F5HvPG6XUy9HFOe-d61ZE-tJQsHZgssQWqV1UfPsccqgyWIc2yv9aK4pPpu2lcrlGu8aDZDz7pBD-dPUG_B9XWt5c0CQj4CCnURDATNWqH8J8VvKap6Zn7pBHW_PxNSJ3f0z_l-GjBlx7U4w6XmOMBtJK8lE_Y8CuuQY9dNVnTGMPibCeJt7M_Q9-IYcqhriUh7Q5WkCvDVu8157gIRwwUAvgqsWcD2msXtO9svRkXKxNxYFdW7KolF-y8oxXRPwVJy1bf89pAOa8djb21ovJuJmbvrRzplFGYNj8rGZ2hXenxDoYiKv71LGALVU63mS9q-Y1zfTHCPpA-Rw7oR6T5G_Q35H-elaA_u-vkgh64mQNP5sgc_kpwbVlM0wSl79RcExnmBTpA-kn7B4w_QPwt185WD9jQRjhh3LMQa_crf4nCWLlsYcDCyB07TU0vXQiQ3nynqsX2MstUc2DaiseVG1SO0UEv8oobwLhnSvl3n8zWMWq93NSuISAsaWmqriNhM74aSHw4CVPoO68RSSdNrpxaKGf8kuO9Xy6iLr3VPE_vyMJDq65q42AEvKqP0TCoFUzXA28Tkrg0tsMLsXIhuT5MGtO3O8RpLnthF9vT0lM64jMp9_QSH2BuWYtwgok7xk3gRX5yBQeksAos3c7Jn2bLM9VNrV9dLi7MH_mRl5C64b0Lgj6Zi1USCyyPhL95ZJIvdxLWHSII2RFbL9ToCThKp_cgPZklLAVJXBeIOqG09pIQ==',
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content=IsStr(),
|
|
id='msg_0e3d55e9502941380068c4aada6d8c8195b8b6f92edbb53b4f',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(
|
|
input_tokens=115886,
|
|
cache_read_tokens=92160,
|
|
output_tokens=1720,
|
|
details={'reasoning_tokens': 1472},
|
|
),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 9, 12, 23, 19, 54, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_0e3d55e9502941380068c4aa9a62f48195a373978ed720ac63',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_model_instructions(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.')
|
|
|
|
result = await agent.run('What is the capital of France?')
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[UserPromptPart(content='What is the capital of France?', timestamp=IsDatetime())],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
instructions='You are a helpful assistant.',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
TextPart(
|
|
content='The capital of France is Paris.',
|
|
id='msg_67f3fdfe15b881918d7b865e6a5f4fb1003bc73febb56d77',
|
|
provider_name='openai',
|
|
)
|
|
],
|
|
usage=RequestUsage(input_tokens=24, output_tokens=8, details={'reasoning_tokens': 0}),
|
|
model_name='gpt-4o-2024-08-06',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 4, 7, 16, 31, 57, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_67f3fdfd9fa08191a3d5825db81b8df6003bc73febb56d77',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_model_web_search_tool(allow_model_requests: None, openai_api_key: str):
|
|
m = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(m, instructions='You are a helpful assistant.', capabilities=[NativeTool(WebSearchTool())])
|
|
|
|
result = await agent.run('What is the weather in San Francisco today?')
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What is the weather in San Francisco today?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
instructions='You are a helpful assistant.',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_028829e50fbcad090068c9c82f1b148195863375312bf5dc00',
|
|
signature='gAAAAABoycg20IcGnesrSxQLRh2yjCjaCx-O9xA4RVYLpo0E7n_6m0T1IUyes5d6U4gDzUNRWbxasFx_3NEhFIuRx4ymcqI_K-nZ6QNsq3V4CgwBbWBXRcBEDVzXSZZ4IoFASBzHpQGbs80RvZkgqmJkk8UzBw0ikt1q9jlUrwMKf1iGdH-S0fIgZn_uEbli1yGWRDryyS2YQWDKNTYuaER_WHVg8DadL6_ltUTwJ9dMzaXyFEenPfuLdDgmba8DP_-WYFMbggATUfdMNfM0O4YqnTmjR5ZnSA6kAbXvnp9sBoC-t8e2mWiCXzvy8iIJozNPo_NE_O1IcMdj1lsaY3__yWzoyLOFCgkrZEnB-_WQNCSx-sVcWWLZO_Tqxw2Afw9sWAvFR6CvTTKdigzDpbmRlvlAJCiOkFQCMrQeEiyGEu0SSfqmx6ptOukfJn4HtQguvigLDWUctpjmNPutwP880S1YwAcd7A-3xp611erVJtYFf6oxGDXKKb63QAff_nZ57-7LdlzSSUr6VaJa5dneGwCgKl-9J3H0Mo-cOns-8ahZOL8Qlpj8Z2vZLS5_JQrNgtmDaaoze13ONE5R84e6fcgHK8eRhBNTULgSD13F59Xx7ww3chlqWeiYfHFwmOkNZp0iNO7RJ-s7crs79n2l6Ppxx5kd4abA0c58k1AZj7avFrexN_t7snuYqCNPsUHMUK_1fSq1toGa7hTVX5b8A56WFSdMlFD51AuzeIzgaEqBtGvq51murGbghqUmOy9g-6_vHz-WOPZeE1M2p13VB1n5fIh3-V7nd9PAXLX1kLLKiS2ox5tODYvkxf6oqjgR56n5KCuWtF9WzCwikaSMN8pwC3ewW6nkkSCPhTBASEJ7BK9a7lDlV60T6gikDbZGHcAfSKDZ5mBBwSBRpDfH3F0MI0Uo4oQ83J63J8a5r3JKy4KVa-5eNsNZsCgxO-7xx_fan1MH9zT85SLwocpvryGSbIDD9itBHK7Yo7REFRV6_U_cdi5RhDpEc13QETSsFT6CaeoL4GAwvJDCrcKjW5u64StH8l-Z4XDAtChG-znHeme6WlJNElY5unp9L-IolqqypTS6lybk7bfUtGPBDeuZp6CD80qFkyd46M16vP1mudv8rMC_ZEdFvCoHDmUg6_KxBxdVbYi-jaXtXYY9D8G6SlfVkeBcNiDCWjsDXSlhE1ibI2pHHN2E-kJLRaHA_Pse0Gknu6ZecQLaUCKWr_mKh3axV9d-pkvxpCcVVakOF08By0bUe8h5ORELsRe5zzMpfbYGaUVhB360OxwqzizyISXmqhW3Q7FHcgZQOCZQVfpuk6ccAYpZwgZbft2YZWqw7_1MyK6TitpdyIwdLFnt2t81JNoJ8zWLveZGpuKABxW6krhjQ0_qJCnLHm03o_D-9BximrLUCs0PbleK5mu4Le8lCCs4eoVjeDHQs4xMm-VtJk_3KMT6EVe4nrb41ddSKX8hH9rh9l2NlPpmPh5UTledwhbtQYdJdQBNFkGei5gpAQ1oHaLkSOYRqrRmy-VIBobxAVBaQWNKcv8CrGx8RIMxrAiU8JoyRsU7Vsobwt1Jboo=',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='web_search',
|
|
args={'query': 'weather: San Francisco, CA', 'type': 'search'},
|
|
tool_call_id='ws_028829e50fbcad090068c9c8306aec8195ae9451d32175ed69',
|
|
id='ws_028829e50fbcad090068c9c8306aec8195ae9451d32175ed69',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='web_search',
|
|
content={'status': 'completed'},
|
|
tool_call_id='ws_028829e50fbcad090068c9c8306aec8195ae9451d32175ed69',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_028829e50fbcad090068c9c83115608195bd323499ece67477',
|
|
signature='gAAAAABoycg2MBei1jlOMd9YfezZ45PArjJAExhzJt4YG36vuQT_e4K6W78Awn6mrJEueCnEAbciBRoPBd8n0YMXbqTiKdgeceqAoZu_UJAVWxgY7tVDlkg4e8BgJ_SrAumbi0yL4Ttwy5yZNU8g1aICCSdjGqfI0cmVbJpXEyCU8Wt4UKV_912jaG62vA6Tlqii0ikc8UItcrgk94TEGpOEQXlG1HXsWyAryCvOMSM2F785Q4Jx2XOrNv4klRPEZGUeIbp4ReTVXVi0JT-cjc6O3gKNxN6vxzUbvPhmcyTa9UogLuCTHjv3KpcIvBOw-_pF3Z02oQE0GaJKBpP4SJLE2yZsIII4uMls7Lw07EuHZjsZoCQRg12dRle6rwba7IeRw0RJWYEp9aavT0Ttrj69dO0e20NpispmeAXLh0xxrRCKcjxAn6c5XtEbJP54_ka1FUSVY4x8IaU_pCKI85fGmHIx-HarXBtWzZO9B5O1K4Pqr3BE7LELTXaMwWQ2SU-RGsvgmDpmUZjwifQ2YgamjIJPt0UcuGWb8BTwssP81XT5mQ2Tsq1YjQmgfzeF28yeb7XhkEaBUNejSou3SuEXZ9aEuSaMz62gzPSpsSrr51QoBJpMBF9Jd7LXuFJwaQV7jP9NJawF9GT-CMWj2IOXgVca7cL_d99IMSR94vNyg8yPzDsncJZ9Dw3HXFsPfdGHtO2FaFUB3RRZAVKoHy7S1NTNfLxdtB-p0eDuu1JbcsgtULWC71E6TbPxg8OguiEgAPTXJviUAed6udruUrSMlZQv-AgRYfxYPPMXLeUIWTTUo6PKICy_PO3U5CF6VBkaNUvCLf317L47FCeEAJNTb9Uj_S67ZqoAnEG0tQG7tVPuN13cy12xO2-8xFQSpO7gg0DzF8vCD1cAcKAvo0FUEnIeXOVHVQxThLHDiXOmB_ZpoT-qJYb88RTLNoAq5oI0ZuZYvPHJ63EhVjaANKwNe4DrfAvoPpf0qWiBOH2vHxnlIJc84pRh33ixB-azK7arhetqwIuLhDo4u9REcD2avxew8rDEOTqb5Tk02hhCKX9drLYCriNdkQh3mrC3KYzOWZ9aebwOR1c-s54KbvGDHAjTNPCLlROf30MmTON3jb-NW15YyzQrVFfV1c-egUiWRwMVE3KeWi4wmicK_QGMZkdyEqZMSzNcgOZMFfUWxdUKxACHY5J_7lUZltrz9JnhsfuM7KMuEW3GMASIP8f8WmR03nleJTi7k21oLtX-xz1gjble9WzSzd5pTz9GrFw4KWatCyrLXtKWw9fAqm_k5HpIJdya9KK3jNve6MirP6jdetIUNIbN3MGkMJ8lfavyTaa6-t4hsQSmyTQn6OKwhK_PA8-KTluNMW-dpqZU2YPFYk_QHYW6EJe_Kw5aOq-zpKR3hGgoHm75Ossr23QERsVgP0LChljPzR4OQlce1GMDtRNqLX0wGu1RO7OdM9R_lqJWMlIaAa5wfvdH5LznaQV1vuGPrfpzGL4mlocKDv8ASvrxA4bm5fWBoqsfzcLu-H8uz069vLDyHgrPNse6W4Ex1BVY6By0K_f7sidbmc1FxwP3ypVv4nX_lncg6RiZzaQTHTxXJFmvVO8_L9XBHJcGkQGpEuEjx2aMTWZGJNxfaO2fKJ8U3XflYVXJkSg5b5ixTHuvDYjCOELs3fTVAy50CuMXMoCEgyZlqZNg_EJXEmz5niLNQnwQPRWUbe3kicaLzJqvZrtrvPOPcTM31Ph2-_dfEOeKNOIE2B0pvMgTaFRck_xOc7s5J2tWAEYszDz6aMXvnvzm1WH9cXYLbgZPyJmMUxeGZ70DdnueVbrNr8VA5bzvjkgjEkhks_BQprXEAZL1lSL2s0O9G8ekgFnt75JBJmSFGT0twl-t1ia1BFkRtMGXLIj91xWJb2GsF6ZN9Uknfm0Akfk1STtRbxFIeBRlwQsix5rQ7EstyhfsBXiBILky2rSfj0UJwH1NjDskXjFxxpy-FEE7KRYwMws9rKKuMQMyURUK-DbLvMmQoxekYvqu7bJfWqxj3lndGwD1sQL78cpVVPVfJeqnlAw7k_xd6QdHg9DwSlGNb4OCYdFWT4xaaltFIJfo6g1Pay7HD8gWTrrgUzHgEWfbJxcKIXs1etHx1lxYVTmm9TFkXshmsbKptL7kAaxBy9JknSsGsh9gZXf3YFkocEj1xa8f8Xcuf3zatefAeFFh1Q629b0Sc-GzfXnu-KfuSyJzAZulrP1IQ0jlOiGP5hKnvzePVL_JZGTNJrJxmtWXejLodY-JzLzUjIeALKtyUsu1ELFtwDxyadPSsFW8qvMeolLcVDysGm8NkmRgLzQTBDGR4AcipdozZmElDRTm5P6JArLlqdZCxXpiOH2x4juPIYUfRrrTT2g6emTXHz_AurjFgYn55G6xv1YGSuM5tNBXc_WP5ya9cdpBIEYj1i05DIMsvUPsNAkt0MIeTiVSPPDMgpT4lLsR1ezwBMx2kQBJI6E7rmH9f3Abn5H6yeKQLZckAAru1SLkVwoDxcTTJZqD3sZt6RhBDuuMWX5ZoB21K-zkE3Tde6caBupWLK-W2eGJSJ_oOaG2YGQxL56irxU6DIVxLuMWUTOVH5vpqeo2RlrGpXu-lJkg3tC69gXlNd55233uIkchhihakwSIxFF1Ka-hcBlKtn0Kz7CXrXam4B0sSWjc9xGRfSOaQ6LiameoozXfhj8r_GSOwoV8EMa2vIBFggFGrPEzaczNkOKBiA-xTQtdEPqmfQNznuZ-B-VX-s0E0Ew2EopP4ljZ4QMW8k6pbNX1aegBBxbxkNc5ugJhBBoSVJeEAC2Lw3iCZUnX_leWUJBp2up09oJtRWlnGG4mLAu7nYsI7blues0ZLZE4C49v2eYBmfkeyq1DBAGXu0RC1qMz5729tzLPUEPYpKS1H7w2iGHQ9P1jBBWAAfFoqgn1lYtBF1ioxL7ry6YMrvCgTlqvVRXB7zmAUlsJdPq-CTWpF79YSco4fAhrDVCmxdS6Y4arD7p26YWk8PioCDt9ranaUi7--wlyh2OTdJPHAUHW2-o5NaXXfhqaIVfCqH1sbVmNwP0BRiAmUlwK7GB_m7dtEztYz1sHl5sXmXEDcFjJtr6uozFDjEA42F48AVuZMlQfQ3eJNSRqHEThYeyzbtCdYZ6J6ntg2XS0uDHISgM4zi1mDeur6-ZCw4rGwUXvB1BWXifFeh2miEGtvRzw3sa1zBKBCGtYtRsl4Iz5Plo9RNN8eQ_vvwmfDk2F-5YWsDZbpJuSXQXy1hjDvyM7TVGj4uL9gxFQ-ZCxFl9cufUeqfEGgHX38mZoJAT2emXbe4A4byFYvWfM-NxjpbNA67ZkOWgcDPtY853Y6dKoBihh49ZAzvmEjmPixKp2rBuNX26jJzhW2OJH91GpsncHGwJ3ajWht88XbKBp4Lb8sNVxYD3hK4c-mB95WYYaUKe5_ugc-PhC4FGu-FYNLYTX2ZxLKpk_T4uEG64zBQ0NbS9y8WWiTojeQ7b4-MBG_j3VJr5Pi0T0meC623J2ldwud3DRBZXB5q5rKgofFF6WqvwhIDi8YLL7CVUJ9aOE57SkUKVrYYD48Cv8Wv9piI2hbTgXwWkCpg_tVROBjl4RYfYVlOBV4pM1G5AK73PXfDGsPdiCxhmxHlvzanAm30eVKIctRaS1xlcBqLp8CUPkgnPDlPVclMagd1CjIlN4igMnFN9gDPOUckrA0-VBlg-EKsHG3o_jNMbsvgfXg8BuApc=',
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content=IsStr(),
|
|
id='msg_028829e50fbcad090068c9c8362ef08195a8a69090feef1ac8',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(
|
|
input_tokens=9299, cache_read_tokens=8448, output_tokens=577, details={'reasoning_tokens': 512}
|
|
),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 9, 16, 20, 27, 26, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_028829e50fbcad090068c9c82e1e0081958ddc581008b39428',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
messages = result.all_messages()
|
|
result = await agent.run(user_prompt='how about Mexico City?', message_history=messages)
|
|
assert result.new_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='how about Mexico City?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
instructions='You are a helpful assistant.',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_028829e50fbcad090068c9c83c3bc88195a040e3b9fa2b3ee4',
|
|
signature='gAAAAABoychCJ5ngp_Es2S-gVyH01AJS0uQK5F4HdAkVFJcXPiZanGRjmEPXNGx45nUyTonk62hht1dZ8siaE7v0SCE-oBFoP3du4UqNqtCmJ_0_EmkXG7sHh3pR_zuS_iEDGae9S_qM-vcVXyqFYbEtEVD9ZimiQGtLEU7WFyQq4UeLuD-U4vRhpFreMCAfen1DkV9txJijEPRL_2cTUGT47rpi2HYyuN1CzYKzRrn2qbHsgDjnPtZ8cY-QGTm5Mm0LHV9GeDh4MmRY5Lgxt0slssKI7vy3OqTWR3OCESp-5VmMR3fbyVNxkeogT9XqPfnl_9maf5jYLv57tVGVRJUEx50QvMJ9V20qbUzIAuMw5d11s8q627IyyFu-bD8QmjGsaBj_wsjdMe6adDF8hzOau3svjuouGf066I73I2euw2NpokdNA8fbI3bAHfqyXpFDADKXg7WL_zYB0eyREbWe3n2mo3KL2sLW2908ScYEvsv9VlAo6q1vByI0wfGmnkqkgBvh04Fe15ljjSkvLy7iRnOFL_CCPakpDcViIOD-yRSDk-MSHpQsK1sP2GgxHHy8jGO2g_ef2bOH4FkcYZK1oJLIUGqhLJI0LurXFnLZ3zcUML01aV0rMFyweQwbdIjpivIGaAg1BUPU1Tc8nCNmZC5aRcbixMzzu21HtW1SWnMziebhKHyN66b5skUXl_RHrCoKhFyJxSJJjxHeuUKHQ5VxvJDJSylZjHvMkX0KQ-Vn78pv-Be5ETRxR2G3Agp-a-iX0zM4HbwVyoF5l5t7g07pTrfEMP0WFJu4_OG_tsy4u53JGMQwQLB_RNYcd2n1yXPCpZYHuq8Vkt6-A7kYHW3wvUmI2cSyZGBNpwt-pL7kqdPaGyqnfhMTDzTS_CTXBBrCjjQg-RsWGu9hYon5iKgHFv-w_qGykzyPtEzZt_VWUrVm0WFOinLqLXTQgiKm0sypDdGRht69Rbfe9WqP3fhFychLwcP22IvDQsh_OenHiF5ytB1XTI90VB4e890QUI2CzsnH-8fFkQT9Bj7ou-MstjIeOQrCwDGAPRnxP8PWoCg3uYk0DuAWuJY0lYq6isqGKc57Lz1bLaGRG3oYpWH0MC6b-D2y7c4cAgOYMhOzYq2ufblZDinvBLrr9TV5jtog21xrBy22o7dbVEgIJ2T2HI2XOmjG-l7qrchcAykaosXQkW3ASIv0OpfG-SSd9UU1_1dOUFzOXGej5UMxZidzQa_dW3XPLCqVqgiDW9HCu_XCmSZo36DY95I2hofXq5mXUHT4qxdZ48y7KGiM6mllFudcdyXu1w8ZGFlU0BfzKDOfbhEJz7MRLuXL6GO0bCHqgFo5WHJrsTNrXuHNNTe2LxPPIpejVl6kvE_1LtHy2jKffOR_BcBCS1c_KLIIbl7U10__OWglq3KpDXuupMa9-fXXSn0Ko8rRybTLQpXIn1D6phbi8hhS93EkaVE-9zZZGBvgcYhPP2fa0XniiexQcX-VDQ==',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='web_search',
|
|
args={'query': 'weather: Mexico City, Mexico', 'type': 'search'},
|
|
tool_call_id='ws_028829e50fbcad090068c9c83e3a648195a241c1a97eddfee8',
|
|
id='ws_028829e50fbcad090068c9c83e3a648195a241c1a97eddfee8',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='web_search',
|
|
content={'status': 'completed'},
|
|
tool_call_id='ws_028829e50fbcad090068c9c83e3a648195a241c1a97eddfee8',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_028829e50fbcad090068c9c83eeb7c8195ad29c9e8eaf82127',
|
|
signature='gAAAAABoychC16oV3bX-2fpfsHcxFWoRnoEx69lgG3ff6IIJemlYvbM8OVbf4e41oydklk1kkBbyWg2spj4puPrSV9w19NOknK00NJ170UxqM5jqvtZHcvAAdeBjA9XSyRObTamXE16W3KtXvRvyRBmBpqpC6pQGX15fxxdAESZUV6uUexSQIYZEfCT2q2aRj7YV4kCGXUQoMRvjzFE9YLE4LDNrQykcrIytjZ95hz6czjxsd95qmYtGdjMU-s4BlOvs34pE-d-H7cR3a3cQHI8SkpaQrL7bCOxZk2fYws-t0YBXEsOIRNCpX3uEany3iGgq_8jn-ggeZtwvnA6oRFtIkzpscLaU4kwhlZbYHNI_RinezdR5ByRjwSdc2UHvqoLb4a2rYmHSLLpSmvr1f9UesAz2M5AexJYlk4sDmGhMD5DoiLy05lbnbo86osBDmRpwXhb4F0pSVgPxUEadMvvr_l69Mv_VAhTJdr_iLFn3E15HCLPCFND9TcROgxPzhW7aeDrt8fJPwEZZ4fZ3BAphxP5sOzzmd3-6uwCHLZxB-51ILHGMkBVmGxFSXB3u5mr7TtaDafh7bxWQv2bpLoV3Y5QD1lRvBj6sx95B6J-CWgw0WeOd7jSgHR2Y6nDzD6XAGgg-aEK5Jk3CDGLsSqv6SxYMoY9MvT16syFsNuEki6XDx3cF252VeOHIPNPQiqBB5NRgf0Vx1zAMgAn8EYWarg8bWsJrazh_nSKWmM4gCFFAUK3Tqi2rfbx6eCPlPBYHxX73GdiHrypeAA50pqVySFxXzXgeRKghzGEQetBPzNMPykyUmiDuq3oPc_bliFQu_15-rDhEfmJcfS65DpL-_tLdtTFV4-BeAjVNsdPjX-7I1bTHdZzyuBiMr5sltxKzmHd4fLWLKv_ZsAustyfUmQnO5_reR0T3SwlY2Ytg4wJo96dtx-XUqJxWgZ9tAW8_rhwgejaH2H8zTM2wczgWVXJZxlsIl_U1xY4pSgxosqBq8a5EPrAqJFnpcZqj9ctCImVN5oElb8o4474pOhSeY0qFQgL5iol5d6QB1gNTKugU_rCgAPbHwBAvnONLJ0v3hQXncgcuIJgQw8BjpOgS6KTXLmf-5uH6CyXum-oE3JJy8EMBjvyerecMMQl6dpeJxYHlB6B0RUUzTI5bHFaoJeSGetoKH7t-L2lUwgcL7F84Wf1ZU3EUkCPWl6DdUq99aLfYLWPqd3bQ2JCvWiMVrlwuHZr_8l_N3gCWuy2t43N2nAKBBc3HWoWRJPgHCmkj0MIMdnZBiUD7IXz-b9jO_1ASYT0NhOPc3gqipzP_9lFE0EojjvqUXV1P_OiAX-Cl2cFpn7ACDQpxAGyW0yr-lgffzLI0GA6dP47DMYs0P6dQBD6XJFbvlxigcl_9GURApvAb66ITpFWMeQAJOCGdMMPZF2CahK8Riq9b0RtkSmgmmEL9SUNaMpEJBlk6j41_IdZnxnO4Qm0Fqos6RFKFbwqfxEopy9rVWvkbjFzRS_B7gAc0kH9AbFx0CZ61NZYNVnQcN1qpr0iuJtSGG-DW9EjT56IFtnt_clgrjfFuFj3cwX5ZcKMrN_RTQNgY5QhAPShSXUB4MDstvHgFhBObn-4rDl3TIFJiIgNY9lBz5egE8YZZXg8XxW7nFZpP0fmQD9a0CPdA1BhafzNcvCbReTjddrVeJcHhflTNjy0YiXrXUyJmlmjO1y0opcXkS8R3E-Md73KKEW9wJUOuEFDDr9PAaocHUsvqWPTNb_Lu90knDMKEi_NnlB8SHf2Agg6FkyMo4Z_k-T_51IGYfFJHPuGRZ8-CqK-qI8-6BRIDpnei_UIi2K9ALXGOuYrcG9A4YexW_vPg2qmoVgishgzr-ddFGOuWr_j05j7AKffDc6wqK0PNBTEqpnMKSVICOdOEBcilXsncLhjFm_JmS3JfxaM0Ly83tKhZqjP83hxrL_JvBjBQRuW7LwyYuFbE_8dAysUMI5jYwqPd40mGPALADFca0U1rolFD41tdX6LijA7Wz9JjYpfuphLiXNH5cGqTe4T_ReZAN29DffISVS08dRiQUEnw2-OMBYz_nY2qe1vyEItwYmUe2fjOgec4ClJPdRDXBW0HWVS6ei1sgOOD6FvA0moRFpSJypcEC2R1PiRqN_FEoTXzRsSAPF6pXoQIlgXxudLwitpW5xSZS4v_DZTlGa7GgHnq_dhDRdSw5GzCvqPU3CSlP7GmvxZKA_9WoiHNd6JdOSVJg6x8BGpxDjvJy9T-XB8SIKyNx2ymCVKaEhnNTh9UefBGcEXR32oYiRa6GOLtVLt_7OJ_YOqSU4XB9OEjoWlWisBxCrvnAI6URp-wxVLLkLzAPhX-O1sbjcOkCillvnJWyDbnL12JkI0NsvenYonUdprMbVKcX68KdkkpgmKyMICY7eUKpZfWy32E5stRQFUE1GMZ6wYKGOBFa8a5QiIwIx_4IAU44BZCqBDaV57H9KAlsHhqY0K9PJa2fetDVGb2MKohfcEmF4lAzmHKiu22OINYHBYX1LZulsVrcQUj6zSA7r3GEEP6K6wBmk6i1SuLgf4ze9WC2pyb9zemaZ7dHbb3btZw_xAk5a-RVoNb2hIXfiX9clN3BkMw5V2vbpDHaNM80N8z_3VC5uXkQ_v1543ZFWvxbdvEVHlR8P9JyG_Asts0VrwDnFAo6rTGmPj52GJcmhLVAgZ0KPDrujpGHu9HTV7sO-3KvqxOMHYuKG34GvpjfZzlgV8GzbXtpsRk2E-GJPKLfLN9KIHYMxdfkaWBurYvea7iMYe954Gcwehfvlk83foG1ez6FtysZ2V4eLjg9IcVJVAWucdnUWyIIgYMocgpS6ESkO2wRs6pUz4mg8MT8q-h03BJXmWiJIi-4_3TOhz0owLKMza_1IljVaMAUIHp6Kd9yEPohWQo3uyGulXU-vEsSeSkId_sVxLphe9yuimK3CtzU7FBjewoGhaj9vnTdv5_abDRZ13Glp_b4vpfUrr37CBAX_RwJ_mTqGhbv-mPuFRVD6ESjlg-JrJDCUY605dcyU_0hyvjSFepiHQ4FCEHzL6GNSfR',
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content='Today (Tuesday, September 16, 2025) in Mexico City: mostly cloudy, around 73°F (23°C) now. High near 74°F (23°C), low around 57°F (14°C) tonight.',
|
|
id='msg_028829e50fbcad090068c9c8422f108195b9836a498cc32b98',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(
|
|
input_tokens=9506, cache_read_tokens=8576, output_tokens=439, details={'reasoning_tokens': 384}
|
|
),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 9, 16, 20, 27, 39, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_028829e50fbcad090068c9c83b9fb88195b6b84a32e1fc83c0',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_model_web_search_tool_with_user_location(
|
|
allow_model_requests: None, openai_api_key: str
|
|
):
|
|
m = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(
|
|
m,
|
|
instructions='You are a helpful assistant.',
|
|
capabilities=[NativeTool(WebSearchTool(user_location={'city': 'Utrecht', 'region': 'NL'}))],
|
|
)
|
|
|
|
result = await agent.run('What is the weather?')
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What is the weather?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
instructions='You are a helpful assistant.',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_0b385a0fdc82fd920068c4aaf48ec08197be1398090bcf313b',
|
|
signature='gAAAAABoxKsLzYlqCMk-hnKu6ctqpIydzdi403okaNJo_-bPYc4bxS_ICvnX_bpkqwPT_LEd0bZq6X-3L2XLY0WNvH_0SrcNCc-tByTcFHNFkQnE_K7tiXA9H5-IA_93M-2CqB2GVOASFmCMBMu5KwzsYIYlAb-swkkEzltsf5JEmn1Fx9Dqh5V0hxkZI6cz35VsK0LEJSYpkJjAMcfoax1mXlnTMo7tDQ_eBtoCa_O2IQqdxwPnzAalvnO_F4tlycUBM5JQkGfocppxsh9CQWpr7bZysWq0zGfuUvtuOi1YJkHVlrqdeWJGDZN7bgBuTAHMiuzx68N-ZrNgQ2fvot0aoVYBnBDxJFbr82VJexB-Kuk_Zf3_FVm-MGcQfiMxvwHgEYsnaJBvMA56__KLlc3G4nL91fibIXbh3AZ24p3j1Dl1V3D03LaEdU3x6RF7fF47y5eyaFWyWkmPl1RwiEaYy9Pi7WHuh-6n69ADGYWbv0m4mgvECbmvbBIIkZWr4y0UK0B8hbC-Oqz776Taww73OmchIzgkg09rIz9CfoKcGMXgvzbpIBa4sME5BQ3mQtfIdPLY7uUIwya4o_g5wVy583MQva75jNsR4A6sRVW9SgVEWusMJPHv6NLzHCdWehp6SBcKuovxZayoM4KQrIvUMNlUkrSR-euoBaa_WNc1HeY8ikKolX6emm2LhRzXH5HssCgH0g8GUvWilYx7U-UFSB0r6yoy44_DzsyH85pXN1ivsSU5dGIBQgG7WiN3bfk6oBGSrz4XkBLiHJiBX9ZUe270TeDNfpgjmKO34_k35zviIUd7-kVY4EsJGGijEhjbkInFwhilyH08EdKvYDzrzpKJIHT235drt3eLTKXKEA-g3iW-qOMqH15KPk-slzPNkE8yahWEkLrYsqGsjwdHVXiKF77-i8rwvDWOf-pOs9d3bBxily3t-22D6RsOL6wFYQS6BsuroKdlO3b_0Ju5E2Kq4P3jxtZ8jnG9D2--XEcEB5x9yX_brfdFuFHrF3C4mYVWTrNN3_S9V8zUp4CdIh3EqAuSs_QJPJuN-RNlorK3bwYqOymgNlcezKIqxhWnqtS1vxuxC7msRlJRmzTN_Lg6XuLRNS1uIp8jmx7TcCnDx62ynYn2oGCOCLSspK_T_LVTG6js4Oiw9ZB5A_I3TfDLrtnLRh7pGJnAv9nVnfYd4Y1czSjhPui5LF-FvLOlzWxSu_1Mo56QA1BIerB9lCQsDjPOkLF_XHOFLWGLQANx5nQ2wlbgBNyMcPacQowRyn3NncjfzlSLyaPijEZ0HROyL_Hff5JXCMu5-6muvxQz1TirmbyjBbLjtv93JpXrVvby14mdXdNs97dMATIiqpwF2r0873_dijDKRxIDMZxqFB2ZBaHJc80khjG_NaA_jxv1GEqVWmllBXBz-wUDbUJKtNtI86YmcZboZIA71V416UW94-TXbtyQpGlB8tj_764sn9fKitg3vCqC42mr5Kj_aTzAN34BXLykkFWYl_AfVL5PRbJXc0Uh0GW0xTH8eD0hvqd2Xsr9eCoP0nGM6TBNMCl4T82wOhRy7jelWMpt8LBxAYkw3nAlVVOi2puCoYRaRFWNQnLcO5iYBF8_rg9oX-cUsBFepGGDmoOfwUmWLlYqNZDho3AJ_SL3azAVJz7lqa3vcFubrRMFiGcee6sHj0HJI_2N2mZqBO77kEbXrJ6SiUV0EXX5vrjZGzpU_wZ9G8AUz9Tdgistq8XLVsMC0uZWlbRdqD6-UjmnsJW7XINzH6MnkQwPvbduRKF4ywViUUbKVs5XRVFUQF5gTdVMTK8mIIppJx6fQRfZBju1NuNrdTDjd-5P9_QNBQj89_Y_N1fow_676bSvYrhlrIXVuLGy0-RuWezuqEwenIZ_U5wSTp9remqWzeuolwKnF7xG_QlcxGOgCivkRvqAyDxWiqlBhUtC-oPEQtychFa_W9uLHyBhm4bcSUz9KvOlUTt9fNYgvDWFciGCE7B5iPz2s-lCS-Onq0ZvUiZY5nB63htK1bIMzB5lc4N7XVh6COcSIArGBnXKARHdIenJ9vYBSmB4XBrKOIU6SmNNM4fq3ZFoWIc4gsS8L5LZyhTX_qlmY2L6znek3XT0Z7kjEHs5qQ87_sw9ho2KaqNSjMalbUEp7L0JlU73szrtdpMkmBk3BK0of4Nl_v_CCbmYWW9z_rsNpTpPQgUHNVn1s38DX3cesMqlzlBOky-rpLAj2-sS-Xj6WZWBs_8n0lLFS7FL3IpKzveOXE9eV4zjJSZ0y74b_g7u5US3dT8EgSEeHa_pGOMn3t3J37oz1pZcSufD8vjyG7wtGxYUGn8L9U3zJHN1VdOR9id5VYOo3OLtMjCrSqPO',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='web_search',
|
|
args={'query': 'weather: Utrecht, Netherlands', 'type': 'search'},
|
|
tool_call_id='ws_0b385a0fdc82fd920068c4aaf7037081978e951ac15bf07978',
|
|
id='ws_0b385a0fdc82fd920068c4aaf7037081978e951ac15bf07978',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='web_search',
|
|
content={'status': 'completed'},
|
|
tool_call_id='ws_0b385a0fdc82fd920068c4aaf7037081978e951ac15bf07978',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_0b385a0fdc82fd920068c4aaf877808197aaba6a27b43b39aa',
|
|
signature='gAAAAABoxKsLhc5YCXvcJidIAJvFyzs2T3IwW0fie9oMN3Nk5fAcAP3apWArzw8DdWjWjR0tn8Fpw_H_xATFGktsCeA5nzkcKvdc0Bbu2bwMo2QUkQZfFcLHqlcNnAcvrw49XpolGFl-mu7hAyP38LGGtjbTBNRh4dHkd-hYZzy3nYd56JQi5GLS_KuxdU78xUW3gNOtAvrseTx1fcY2eseUcNLm8uDi8a_qDw16nFvuY31ZkrmuVCESawkppmxrhGFVg0Y99dgyufnSVXXMKyE89tmXMc60yZiaB1i5cIJQcZMkwupod7yZNGqmr1GtFru5uJq-bJfGx7nAEs50jUcu-rP-_ZbvptkuADDC-bfzFjaeq13wCih8wCXqDWqnGjqIHlFkBM6agn6VKOcuDC18L3caqcH3KEYT4f3TGwg_ZZjsiRDdBC-saqIduaAjjMDqMKx9XpmreRq5BLfC7fPjRykpUcWQQYbQ07J9pe0EW2VhZwoGtd1u96fmz55MzryX4VOWIwDsUTEZAoCzULvVrEBnzFqnfvQwejBxJX2XU4fIlOtT_XpOcI2afolh8KgitzHHpJ8Dr9ELI-Be2KEd6enxmdaPhgYUif2D8ZCVfOoXZEmrFBMQTRyuxtp9H0U3zGamEYuUxRavxkQD77HhmqWOSr1Agm8pWzAN97jxJSxxY4BEnjtrgp1mavtv4G7VHjrpNWrL-smZEWmnCPGKVxP9afrdSZYL-HXKY9yO6__0PR6DdX1o0JvUq1KFPx2dzag4eXDxb56HI5MKNr6J5P8Smmxxwoelx6UXEKw_hyFWMmPUHYD5Yw5dxrXeYmAiomYKFpG0bxVbuAb4_iAVliHkdIsOBcWoix0KLxmS-4RJnikZPMvDwLDWfENZ2sh9_RrQbuMBAgjHwlfWM_tww0ufm_aVdDZ1CULJ5Ki3ZxH_0oIRRyyB-a25q3DARnVzutgo32H9X6qjMb06ExMn--ndCinBglTTGvj1QOIJews6UMrcKj5ZPTc7GyPbHXvdPmPdIrtJ0wCqFj4cgNRuxjiaZDSCqmEQERYyX9Fxu8tY4f7-Fxje6A_zflqrIyhLfzo1iMaoNbba4HNkzRMWba1L1fC8St8MO4ZuZTGs_60FwzSUmBDW4Gl0CcRAdY39BE65uEpKGZeRqDfxvLUelG9YlJTowqN8hzAYShzcPPkgWk_s1AtY0RT_roregPuQ8PQayvHcJzKqnijOIhRA9k6LjF6cnHj90d6fSzTYn8F27rhufLySe56n9SA2WDWhVcjsFEFAcsL461tjiQ5U0mjaFdBQ5H__s09dhp0NzhE35I4q0pzM2KI1YWgLnwlyPFnnfce9bbL81jvbXw8DDC2KfZVOGU-ZDdqIqF0UmwNyBaMYb4SonrG8vrj5bFmCMPSFsEeuDPv_bmD8HRx8536b30RmYD0K38Wf6-UoatMxzgMpgmwsBP6Wh0HCpFeIhjRsJLxYXeoafypcKJPQgKXJwuXVLi4iejXkrbjBdc2Sq2dqIVzzUhULLJSPBYouyjeyVSbYYp9WPoBNWj67uQsX7OUbQN1_qxopsPJdqqQynJIAtULNHjKrDA0GKpyZ3OUV660OkogPAWoxTVevRemwkIJZbr2hXyy0Nx6Xc1Vf9xC0nPclJ6VXapdnjK69bIDHxDUZGCh8UZt6DbcA7azBrugcXlbaMJzoHWkzmusJoTh_2UXRjrS3B33jsxf6LQnUl0s1ETo3Tif868zLvkTEtfo6btbND0FPDFFQrdeVlW4mUWEOJhPeOmwnDeLsafTfRCI_V_xTzgkpQxx7pVZt6mkYZ2qDTE--NhqgFfHPlw-nC4zU6klRdbaO8284QGlbJvHmdsmHi4AtMSWAf-_jegocmaneM1wUquNKoy6hnbkZFul9qV2c-_L077uC4nZYNjRay3lT_3giVH6Ra6WnBovt9ocCYIwSeygVAyqBHxo5EJpfyJhNCtak3bl-CIz2TraYqqUCiB0h1fyxIF7M0uENZKALtwqRVHOtEsN5JVotgv-8YzaBRFs3qvtjQn7eEcw-zrIg5fwMP7tDi8O3TXl6qPVWTCHMa1wkfb7OkfuwXREognLvO-3qdRgxinodvKyHn9XbsUcQMQjPPFMLOs4wpEhTJpcIFPqtR6tArjTT3P-T21mc8B56K1wXfEDvpU64XQ0HnfZWaqS1TbDyfL2i12ddhhnxbCV-0f3lUGnZVsfeGEc4FlST7iqUguhwPGb4mBpjBVFu2dv3DMCIPHew1v92gZH1OJqZJJVDUpu0vvFGTqxHz31LSX6lWa4gn2l6hvkT1e4aXkjHg93iy0ZXMpB0JqJbbWseZY0LDYzpH9noHq626Q9H4ZEKPo_MYBWSS_yH-V2_cN6a4HarqhcRwD9oT1QJ4_4AzWeFIrCZlClYbA-84H1CbBfQjgtRh6zTZLDHM2In2M8mKGyFSfeIhMHIcfPBTpG4flLBmTNrwwbuOP-0ss_bb5gxLeDsgU5xjwfaUzOWXudPJOEorz4t6Oc88MiRH42troV2fun6Uf7e7j1OQSGtTQ1kXf0rroz2ykDfVIXCefX_3io_xJ7ev9dH54CNlARSF6cVpTqzbyLWkA0BJeAVYcX2JW_AT-9VYTOo1Vixja7KtMAmMMk1E08japeGnoAd_a_4-bEfklFTChseUDgZhOt5_XtBiuQdPvJDorSQWQl8VCPKdMATr-EdUiZN54GSM46pdBr6p-Dg7LvB-zBAbTlm_6SET0O0k4RkkHxUCtgRMZQ52aC4brcym771djtWC-BbaR5CefibOoSo-i-BP2Zf-RVaS_MuFar0dT03zXdb0XuC2vuhbVPPF-7gsJez2dufEiU9LBhV3__zTDlFc-rGwwf04Fh5KuleNzr1QNyVPH9GZSS8jZkja6EcRfGn0X-oBr2oRLyxuL5vWgOdPadBOJGjIoRnMhCAxGla_gD_5m0qwF9CtWWv7ugW7YpATe62zE0O1icYDPwaXGovzTOeRDRn4BfJzgzwLRkP3-zOgF_09X41umrq0TCnCujXe-JOhFuIcYx8IxOb_cCcfGRqGXeZYP7z',
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content=IsStr(),
|
|
id='msg_0b385a0fdc82fd920068c4ab0996c08197a1adfce3593080f0',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(
|
|
input_tokens=9463, cache_read_tokens=8320, output_tokens=660, details={'reasoning_tokens': 512}
|
|
),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 9, 12, 23, 21, 23, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_0b385a0fdc82fd920068c4aaf3ced88197a88711e356b032c4',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
assert result.output == snapshot(IsStr())
|
|
|
|
|
|
async def test_openai_responses_model_web_search_tool_with_allowed_domains(
|
|
allow_model_requests: None, openai_api_key: str
|
|
):
|
|
m = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(
|
|
m,
|
|
instructions='You are a helpful assistant.',
|
|
capabilities=[NativeTool(WebSearchTool(allowed_domains=['wikipedia.org']))],
|
|
)
|
|
|
|
result = await agent.run('Search Google for the current population of Tokyo prefecture. Give me just the number.')
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='Search Google for the current population of Tokyo prefecture. Give me just the number.',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsDatetime(),
|
|
instructions='You are a helpful assistant.',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id=IsStr(),
|
|
signature=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='web_search',
|
|
args={
|
|
'query': 'Tokyo population prefecture current site:wikipedia.org',
|
|
'type': 'search',
|
|
'queries': [
|
|
'Tokyo population prefecture current site:wikipedia.org',
|
|
'Tokyo Metropolis population site:wikipedia.org',
|
|
'Demographics of Tokyo population site:wikipedia.org',
|
|
],
|
|
},
|
|
tool_call_id=IsStr(),
|
|
id='ws_08e7da464e3f8cf70069780c0760ac819ba9469051f9e5f511',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='web_search',
|
|
content={'status': 'completed'},
|
|
tool_call_id=IsStr(),
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content='',
|
|
id=IsStr(),
|
|
signature=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='web_search',
|
|
args={
|
|
'query': 'Tokyo Metropolis population 2025 site:wikipedia.org',
|
|
'type': 'search',
|
|
'queries': [
|
|
'Tokyo Metropolis population 2025 site:wikipedia.org',
|
|
'Tokyo Prefecture population site:wikipedia.org',
|
|
],
|
|
},
|
|
tool_call_id=IsStr(),
|
|
id='ws_08e7da464e3f8cf70069780c0d1c34819ba50e2b797aa61283',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='web_search',
|
|
content={'status': 'completed'},
|
|
tool_call_id=IsStr(),
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content='',
|
|
id=IsStr(),
|
|
signature=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='web_search',
|
|
args={
|
|
'type': 'open_page',
|
|
'url': 'https://en.wikipedia.org/wiki/List_of_Japanese_prefectures_by_population',
|
|
},
|
|
tool_call_id=IsStr(),
|
|
id='ws_08e7da464e3f8cf70069780c102fac819bae37eff938310983',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='web_search',
|
|
content={'status': 'completed'},
|
|
tool_call_id=IsStr(),
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content='',
|
|
id=IsStr(),
|
|
signature=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='web_search',
|
|
args={'type': 'open_page', 'url': 'https://en.wikipedia.org/wiki/Demographics_of_Tokyo'},
|
|
tool_call_id=IsStr(),
|
|
id='ws_08e7da464e3f8cf70069780c1f76ac819ba8a7b5c5262ecb0b',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='web_search',
|
|
content={'status': 'completed'},
|
|
tool_call_id=IsStr(),
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content='',
|
|
id=IsStr(),
|
|
signature=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
TextPart(content='14195730', id=IsStr(), provider_name='openai'),
|
|
],
|
|
usage=RequestUsage(input_tokens=22013, output_tokens=1737, details={'reasoning_tokens': 1728}),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={'finish_reason': 'completed', 'timestamp': IsDatetime()},
|
|
provider_response_id=IsStr(),
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
assert result.output == snapshot('14195730')
|
|
|
|
|
|
async def test_openai_responses_model_web_search_tool_with_invalid_region(
|
|
allow_model_requests: None, openai_api_key: str
|
|
):
|
|
m = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(
|
|
m,
|
|
instructions='You are a helpful assistant.',
|
|
capabilities=[NativeTool(WebSearchTool(user_location={'city': 'Salvador', 'region': 'BRLO'}))],
|
|
)
|
|
|
|
result = await agent.run('What is the weather?')
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What is the weather?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
instructions='You are a helpful assistant.',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_0b4f29854724a3120068c4ab0be6a08191b495f9009b885649',
|
|
signature='gAAAAABoxKsml4Y3hqqolEa8BSvPr6mIoOyAbWRJz9FeLHoqX03v4b6Kni2j9HxfifAm2cHD_m9-b2nOHcwDPOeJA28LQpl_BfOakn7h4saDElA_yz3WgVfy8ZN_oTLQz2ONqptBxdxCaLMGOADqBJ1tJ93B5s8bsFNZUdGXe382lPpCNX0aKPGxd0e-UBAICRmjGVnKd9cVzB8jhtQWBrITvMOLBvi6bE_TqnpXWf8-rhed78mFVMRweh6zAzukkJPMAjD7QfUAiODvD6oynwU6G04UOJoFTItUsAULPfyAw-YZqRwfcfMxoiLAE0rOOj9V7-eyp_J7DYu2uF16jaOopnrehFDJr-0pIGMFRxMSyFp7Ze7z3gWvcCOB4VwpSFao12nozedMeinybf71wo0750TNXXQ9Uye6qsUxxMamqcNiB02LjCM3nyBQ6FpWa59TD5O5UytT5FPOWSflYEhuiTFknt_JRHbKoeqVTfe_CTeSVlYBtiW8ouhkTHAAVI5lXi_mgvUMHINTYw5MEilzBSPunuMRquopRjt_07YMKuwPDQ8o__s1NlyrDAYKLA0gPzse4tWMkKREcfxuvU948pEJwVN9RuKS-NNXI2KiKKOAtPoXLbflAEtpx9N9PpPdwvz_z3yhF6S1_D_9P8OrSdxd8ldqvnqec75Jwt-a0fuQvRTSC3GsYuhk1Cb1aBvZdBtfcwBd2CXRuDUEdtzbLZ5AUNBy3f0mC3ITHG9aSpuD4GUHQDTjF_10-Qr4Rzygnj4-qubY5ibVxGtHlXkI0QzvGMVf7obhHMNxEQNaJ4k2dKddRJEhrSFWmAVYdWbKiZp-Dwx8veUSlpwMu8kLfGUq64MBQOApf-Srtry0eJAr3cTBqzmUIU5OOPg2C8j9SbAuTLbbcR6XeWizp5fbxdcVipVRqqp_PJptIJhaAUpHaaOB9u1nZbtlKWFJhJbrZzdktth5DNim4ayYBbBX1VAefwCugReld4C6QtB5Q-j_Tt3dug3Jh9TJmkhS8pJE4aHURzbCikFohJHAukZYgMY7wCuLWlahQ8snlIj8kbhPP-l-iH-e0xM2vFDF8rZnfYblnDLZYQBezfiZ4GtvO64SB5apQuRXkxExfZyBd6Kv-WhAxhPGoQdmTXfVEXePJLvbzAJcAXYpmmzt1STxoxR9cnaeLL13fFXZ4DGXe4j68-R7xCC52jfoV-l8JZjI0NDRJ3Mx1R26bp-lnvoertQBs1c18QHVShluHtH5c6V3j4yOMgG6cA2aVM25i6sjhUV3iltijuRv3E19ZlzgVTtrypeCVH7ab0PQ3Qki28mFI9s5M1z1TSuFis1qhHwf3r0kkmjLXIUbXAnfJkcv50tlcweXRTLKs0ZX0nxsxiZptBo95wxqBf4VaqfOY4NUNAWVoZ4AS5oSIgjGfUZtfrLisWmX8NjDWiOiENLmn9fCCq9nxDDsaucnwNhsMZo9jJqJS_99kryMXi0yGX4GManClCTe31Fj5zOrtRIezlEILiTla6fZwvD6vcl8GWO2wuyEY9zsEvfjyuvcU6Ernvw9S5HFPnQ-FnDxNtSTe1A8IHTspfEROnuSNVCMs6j02eFZMbXFKMaVi6LNDD2i7SYn3dMbN7aOfubtjeilMpIZ20U-J3uBUsc0rr8s4b-szDB1lkmiMvRDVY8YKNqH3iJFCToE3OibVwHeaUnMmEHJkIvJvBOX4hSwmAMxjZArusTnlYnLE2raAD707H_Q5JhpWXwtgFPj5ra6HFtOjtbPtDWrDn5_M180klxvF-JxfSxSl6U6y2FYeou36ttPRprWJynfcPSPY_sdrB9ZupHDR5zZy01Uby1J7XXOZt5an91kuHr0qU4bQJsq6AigFQ72C_YxpDNmQXcy5awJDBlXv9SoLiXRcTxpoXgii9alV8MeorRbc23O0fP_O6XKUso-lp-e7Q6bOqzV0c9K3imYUDzM9cqlvEyUGMDLlWzEvVGSwpag1CsLCNQ5bPc31W8hc-2WXrlltP6JZ9gYpcueL5AIud6RUTSJWg4Li6Th4ZGNs5cqh6Nk6oSu07P4Ie2JJ5bt1tAJbE4EupK3NVzUpzYzFdPrQkBY-VQ-klCFq4icnvlpD3pajYv9OoCpo0z8GfsdLeJlefIQ1NejuMg3EwbGRA_OEWn7sJzR2RFCYkt3YIuWRJb2UzIzvWhZsLxr4UpihrsieNKggGBh7nDpOXeAZhS8pGrNSlKjfvWtvmWG9NKXSpx79dNLSkumiD3FsQjk-L1Ov-K5WksY0yJTgc3ipgO2UpN9zolpXhXum9Uy8UeKLlB35cCtte15t_HSogTh2HDkc9SuCq4d3adSdstdXodr9jLbST50cHYn-F9qmkKiqV2nBzxW-9A4BB9WB_tWEoazKWYHtIdmjRm6O9NxvOxYuWIwhMmRf-OE6MHOeH0emhuTFaeuZ4zjbM0T9peRh9shiUw6T1NT0doCgfyRAq1NL1rG7iSc4jxrc5ahP0gN',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='web_search',
|
|
args={'query': 'weather: Brazil, Bahia, Salvador', 'type': 'search'},
|
|
tool_call_id='ws_0b4f29854724a3120068c4ab0f070c8191b903ff534320cb64',
|
|
id='ws_0b4f29854724a3120068c4ab0f070c8191b903ff534320cb64',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='web_search',
|
|
content={'status': 'completed'},
|
|
tool_call_id='ws_0b4f29854724a3120068c4ab0f070c8191b903ff534320cb64',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_0b4f29854724a3120068c4ab1022f88191b2e3e726df9f635e',
|
|
signature='gAAAAABoxKsmZCctfduUbipds6REy8FkoOiADLcLER75WMHyO7PtQt26NIhcGkiXReZWucbDdEBRKk7_g9PUuu9g-zEBe6kIQwm4lwjxCGPy-rQdmfJpueznyPJ14Ood-wazqT9a8ab_BMFS7VLonsOjZR_b1gxcx5yO62oLvv1GnZfkEykIgRbGIBSYDWX6I55Sfwkf0JRaiOFgeHoOvQ6f2mdb5UetdJwbIFgRh9Bk-_l6goC-ONyElqvPxrh8zlLxqEhL-KtTVw6TPNW67QeYxekA4vdXseYT4W2oJMcMKp8aIfxYr3-ZWSy81UqGPD2PAfs1DoOYkWMHxt_VnZjLQs0qkO-JBPsWBFWEofZC1GxOIT6gd_dDvExBXkaFdNH7xf0OxsxSMWfyKSXMlq3kmVsDIN3hKImwfZQ171mkFEwgwIBeo4XiY58YJXmzyNXSs3c82gAeGpS8cOQw5shjC449uJZkixSaXmwOKwtm0z1MOVAp17QLGeD_2YVa-DZUy1z6xTqStuZWnwLDOz8HPL_rW3MXGcmC63kWmcTCsFngwR_IArcTd8lsRAXJghnEdOZDYgrU7uc7bbqO8W_PyzPDDnrAbcwo0InMqJ2BZErMXOmy2dEm1jlJPEn23PL26k8r_sKNCZpg-I-q8epjbF225NJ9S8g_vvqLsyCzo-WnHPHaFDMfUhRxU-ylSReCZO3pcjNJXAfmsNiBs3g272BtvNWn7GpDqlJL9aB9Erc79CpLghKKV9JiVRsr79aW-JSzn9gJET3JteU2MMCvRxv3ePPkmZUvQdKOmzZQMwQ8j0FQHd--4qMkXDdAz-lsUjitCKK0z2ES0oSnWOVVPoR5AVIUCSfg-yGwBWhKv6qIkMTsCYaCaR86j_hGlCSxNqYdbMwy7sr6nwqDmqgmcsiNkAVUAUeU7LLXmVfGDR9InNL3lNCICpmcHMd8YJO5A1wFMPHFgfXt3o4CZP1ZSjQjQuQ-Oh2AfLaAYSNbU4y8JDtKPiini_rWIqH1yykwV0Xt__QvQtj600ksUqij_zxbKnZKy_u3Ud5E04bNgTZ0Mq9ihVtPBlcDCtWSsp5U8Sm6JL0ZXV5XaT3CVG3T7Mj-kKs4yHHOLNoR2rKAGPTA6VRzaJDNO4goMeE7aIqWKhFTYMBcKJEGD-B2J2J36iZ2RNGo9JbxmUw4ZPMVaPPulSfpLvDptYEN3LX0D6L4Xu5iaW900EQ_Ym60siMB257NRxfVPb5Sg8hqxGeKKgg6NGa6y-qyVXvqjy4HA-ODvHLbiT2n75fTD_OE2CX1FpLgmpmKkSopjT5G1vv5qtXqdhigDy-l_b9Qxwvbd7XXD72EUVPzDVwMDBZNeJkylcCecaRVJZRnhmOMkGbV4WFrMxjy7eoYrIBQ6zytutBFXNkAb6a6UXdTrlOlzclPP4P81sp3J6BytVSaLJXCIpZ3pAM9aWVzfavRW22R-rIMbmCWT9hq-1ZDfjdglHN7yowAF_rjVGrgl02wsh8IlLKfJreh7ughi9vSk1WMinlsiZfZynp33IfB3ayv00a_huU4oSKXstf1KaeQ1Z8L-ReCdPRwDYaLbP1ZT7BQAbXKgIjUsLdSiU3MmW8FVBdevLQq8AUUKsXxfQLS4TsjMYTNZ_8LkMcVeuwTDQTBYkBdyTl7jawXy2jujxDJe5mK3ZvvS_70sWokuPXkCApVFkJpNRDdcvBuoLG3g_KZ7dA0oQW9QHkKpd_-FEuUZFnL6-ZhjR7pe-EmR6gqJbuQVs19N2qho2pnNEe21WqAN-anBb4H7QN2V1ODJkW6vDDRH5sV8Ya7YYUScSI3TUASWH3MWapL1_-lRiXtVIM9Q8leFFIO_qkr8DFXoDOHp29HNa3gpQkjOqAFqX0VLg1Ub6X6C-kUbXWMcYIUoKNvQx5-Yhy5Lo0N6izxdE4Zw6U6Lfu90rA2DWeQ5-iae79H9yUy74jZw3bclkJFzGkydXWIP4OkKnDPemIKmsh28ovmfgtz_gJ99SlQDBmI6paH6P8wmHd7QvDQkMBnuACOnTnTud_MqdNUR4-qtcnPoNkFPXoTfYJNDDBkxvaEIXylqKK0wPf9aBsICsvB0N96nPpQTYuV2YHfIr8PagOi8wWC9ceUmDib8fMq3xgClujOcXOPk2Hh4Xuslecn315m-SoLjRg-dIdmTjuIyT9CrSdXMto5Jp7vcPTsRPebw41Tf4iR78BOTuGhbe_B7_WDm5FH10EptF1e3GZ0eO--VdgqLY3T3ivuoxtXIkTvDHvLHqNwFJIvH4ULUAIx3UGqJwE84_OqGwKBRT4UuQRm5wwZUZ0teyzOQx0cp7aKhsOkBzKY8jVFMmTBKin52ioD1inMiyBUYICYwYUngdYRmE5Qx7qzqB6Mg5CSW_7TaXuZFNVuVnitQp5uw2RrOlookLqyKYIQhruNjaUAvvDnhhIrTjh_Bi7f-wv7znhbJDE7YWy_zC_ufQj9VfxJcz6eXKu3fXr4EKlLayk2nwO5BkwaijetPdBNs4SOroEo6WfvFgVtbt-c6kkEfY5abo5zK6OPVHrpBVyew-A53SA0bQNptBVMNkZDiPczaviF3H3fnkMQH59RhIhMV9knjfCbAhP5BTmBFyFIXjX_ErOJgb3RtUObwjnifMNwN2hIE_-eMqk8K-jxMrT7xNoojwqcCgmzcY5w8hbmA77xW4ZnlBuTZORjFhppokfhLPcoVCcbt1AEWLc3oFYhquugqG9WZbS_7p_pI8C_zB4Q4x8MTn7lO9RZFufBeI9iTm6JP95asBuEafpQxP91ZAhfiU93UybWsoaKQb78PvjqwwK2D-LRumK6ftSMU3LNn1MBmiFowwzOLPxrkN4dzqF89rXEXJCuqS3jl9fEwKOdCvhpXyVRN6Kx5VBxSrY8KO9ItwWkrjHF4cWCTRVNePbw92TzRnzgLB4aEZ9T5TkIvdNgOyCQYSaOZ1TMSgO3a-i03avh9KisZcyt-gUbD11-EJmt_KOSeK5o-Jn3GmUKnZJJX9hKCOWCmN00qv8DzYCfIO9Bd6kfOXAqJJ0RFDHn6a4VHv4NrZNyXQWrX12_V3H4oHVZhDurhlhhak-6xoSC6KWeHFFlU39xzKx-2BfggTfghpTj4x8WiObhHvg7I6OY67vzfyRtJoA4muFzqq0c-RJ1QMvOXLGDEMJMSmuXxT0GOux0GvkB6VB4snKw5ZWdzTdm-maT6LBL9POZ8f2psW9CtE9tuzs1EfrBS9SHn9s_B6NHRCahEwwaIRFePU0v9mT3hhQoq_CawOykzNVGAPPAKyA8PNZr5GGmdmV7v0fWppgHUZA_sQPbq0XuxgoQFLJttwnCEf_mkS1zPYMYBv16U9G-kZQ25-rdHBFyZG-Wa6nBCSk7lm6ZNkDKSN7L-lBAVgpPgzDvXlCHaklZmQXwtNnBSPOZ3yO2-MBcDmSyoDbXpdM0zYZhMCyv0vMf2mKhEP91a2xD4tsp-Og6gAo0AXgk6Ge_be4zhMaUxm_NdPGg65mkaSaOZqCuevYVh0En18B7x2erzzUAMuJoo5C8ab1yLVGZSKNda3z8j40JeqcaYLN-yS4RaGaNdva_pmCq0dXYadIjaoivy4TqnHig9uJtboQqBevHPq2xXdsSutQOyEEexxjYbEz1USu25bTvog4tJs5okxNWDnL_0vBXZTpYCGdVo2WcMJgwqNBp-CPoZjMxCQ9IM6iS3KKETc9U46ksBbN95ZSeRUoUUtO_i0AoBsxE9A4NFbK9Uox2RGcJxOlC9HM2n5D6LmOyIO5KaYl16sfmURTRlcNpgTYAvat5HbfDYMFrH9EgSxu0y735-2wvZSuD0credILM3XFTyBmM7-278If-6-QaDX7zV9JxJaXrXx92T-srNH2Z5DLBOJDkl7oo1lVGKcFAmEgHjnkT_rPt8DvU4tlh0eI8HzSe7B35oA02GJE17hiWk-_VOUG2zNaOaesGK437EOzcCcc1dMZAtN206qPtzDZsNPhQNEBUx9Ta_jPG6waGpwihNxVfhwVvrR0zFUy1IspR9B1ONXttsi7nQ0YAtDSJaBuUgwwtYk2KL4QqRAixv_KSma8mOfuxs0th-sTyFGQ5f77q71ZcLUeYqVqrsjcDsh0K9pDvj4-KXcQXgd6EzY8zfh7VvXOHIr2aHBcHk1tw9zjYAR19sP87lo7YdVNrYlB09IkCICT9N1RSWJHUsszCvP0oBSmdNPfelx1CvHlClrc2qNGcyalsF8hc4wnG3mrYIC0rb4sHLc6Xp47g7vWnXH1ud169K4dB5YwnLam08lPwSYJwqculJw5d_L2egSoNIdYGvlvH-4prN6EkkyiqmZCHXYSNoKorU-ce7cRpc6mbxxU6CLCS_1FhlgfG_mZFP-KAZ3b-lQVdimYcudQeCgtjaydeAcUP4raEP_Wa3bhMB-GK90eskPs0cZgeRDvwohATR8ynHvxFCAeoiQcL-3bQgdOhZxY6r8dn6HF3RWWaeA6o4xS0XTlxecl4rOXs4nJAvn3jGZ4VmU9qkYcoVBW44IkLnbx0q07n4rRiurI4596rknVRJwbeb--_d9l9gSqn_ZwIHHyO4tk9np7I8yMTGp0j3ea_GbKrss2_8gU-XDU57ihgCQyOrAcyyfljyHTE6m-upNK0glJ-2m9r0ktOToCN-6ve4H3trSNvRL26rmH_WV8d-gwsF76cPYdlCZu46pC3Ib_R4sHUeBjg39ilY0IxUTOsLz-34NuMeKKnaViX68pZw1XzMLb7ZJOYhe0AKKO4Yrrkwpwlqvbpgd369PENtcqdakdbn44wKOfp49d9czQYQcYlRK3L08MhGsHXuDTlUcqqEYSDpwM_D2__AicfRazviJzdWQQMNJHA_0COIuhQ4c0dbPOOZqCMM9BxQe69fNlTfZEpFL2Axh_6-TqEXdqU8CO2fYScvQfuXZ2AMbmit46qlhUJMj5082R_XYNwIR_b-QMqm0e6aI_vZRVw8MwdJHG73Z_u4whBIR36VHrrK1qUYLxC2pYyLOwHlPEYlyN7HlTs6i_iJ9z4TQuK_mk_b1bc4-1XfgQUU8ZfjYPNoQNII_Dtym-9k7Ukv-pU5Nk1lItlLk07wiCcKMlui8Y-23K9mb03O38x9ZhN051SusVM9ItehAp684sy-kb6MymRW0LsXXIPdRc9LxI85RZ3aANfAtMaHbRov2jpVvZT4OQhTQIJLg3656y_NG32DJvFQoBLEgfFCTKYQgpKWmbxj1gRsVDrdk8EBF3rz1ohyUfxqyrHSYM39YGs2bnk9TkvaOaHOluV_ZoY-qIDysJ_p1eKxJVdpF2VCxZ1ctwuKCbVx6pl6XLuN-g2KaJnpgxVcVbrnxsgLrh5OGeDuXiBFYeLYaF09wFBHTHF0naw63TgB8jy61c5r7_y4DVAiicoSJ3B8SJxEmB5qgXVse_vwmKOxvULXcgU9XLaONbYYIUulkSNOSK_x_xWnVRL7yWHj9xMjWTvBXgVcux1CmehPPQ7dGhooXgzCoipDZ_y_sRl43wYZiaqG7Nl79ciyfdwi6xKUb0CgLQp1D2Q90bHKRUV1Y1IdcIUl-atTUcMGYDyLKmYQQ0BWvqXeaZtHra_yDzoIlB7rR9Hg9agchVJsUA46egTwwvlHdiYPIxJidKAQFgpDospYReegQxCIZHg_PI0FPVfXBfNR2Vc8fIrXiNwzPi4jvj83YmDTvTJ1xBLYDao7QzDQUjkpl09EnP4UoGlvFYlrXH0Ev1sWz_svhFVAduqJzHke7BW5b7gYipmIqQCvPgehCMuD8-NkaEAtE613V6BLPTu51IPtkvFoS_zSRCkLnspDFVTeDToBKQlN0-u1LlMF9f1dQDPxBE8ZLacKFP2F6lezHhikzuoJTyfCzF0xT4nn8alqzDzRV3K0wAl_4NKjhwSHz9i8MRxPo1WEfO8Xpt1aKa6WIbZ2rr5ayhX3H4ASPQ7UDoMNrRZP82lcAerRb_j7wyL57W6oE7VetxnmbexD15h_7LukUqUNSSgg6D0zxX2C23EhpBaQ7Bw4Va_costesVZBuYwEig3VR5Y-9WvmN0CuaeE1oZkXJ5zBCBgO5F_hIESxHP9zx9Z4fs7fswQDJHaick1xpSSZNDbBghUqlswGvI4TTtUWGPc5R1mf9dLQDF6j5wTo1kycMpfXIUF6hVqZRlKHgP4DRetOCsAgb_WMW0b_GCVyK8JyeZsTSXN547g8Q6WMRYikbZDP25hglrI5hU03GLf3m2WLJAd4eKB5e1nlDhIqAGn289gdttwfe8rUzB5BhdSZ6BcaWAEVp64EHYFmtco1aBleXa0RVlSDS6gt7U7ozAp0YxkBW7YlqXxfM8A8y-Dn8LkKewv5p7q7yL5Bkun5Cy7rZ_FPQ_4ktHUr_RzqpQbgSgtXwOSyCfoDKqIPNg4AhjaI33nD93HuRQeV_mhxYwXN5GNTq-7SxkulMwTSgg7b2UhmOSu87pX_FMk5nFaglzYzHKpoZA3QuNxwHzTVInF8Ufu6fAIOPT5fEuhfilDU3uxCkpC-us4yeLwm8e36ICJZFfcqa5dXHkFezEXPKvFbhpVgjTO-TI2EH_vb4QcYNQxtQGWUqFcuQ7IaIgYChVS7ifjkPc65wR9ffjTEEqFAt6e-_mviI4ltyiTLTNTWY68JV64SnjeMQ9qR9gPYmefUp_E_LyOdwfetRYKBJ81jAMz2piWNoJHwHbFjBxeZj8iZ34TnirgvWRltUi20aN09b8TN_IbFNPFjkI1UwshqMwLY9GXT4eq0QaIdvhW9CE90--KNVjGvqyRLodo0gsGTpmTcoTPDgF_AuaeDlaBrbAnW-pFr1HOV5YqUGja5_vkDvi9mdKooFrlSau-Dt1HmZf81izJ8odFR-tHl0u-wT66G0aEkk1DS81IXvSLLNAQlIpj5FoZYx2RPFWyw1WBlY8iSa4r6HyN5YKW9taJ7ljUliA8KClax8VM282lqYL5Fd-wtYu5Iceez8jGGj4cZ7JetWp6X-wjLHeo6SDUGjNO7k7h3ODmCRnIKJZVtbx6qJEVX1u8J9mIAXEjdArqa_7YiUBTuka0W7IxVXZUx9R96h5f',
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content=IsStr(),
|
|
id='msg_0b4f29854724a3120068c4ab22122081918f25e06f1368274e',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(
|
|
input_tokens=9939, cache_read_tokens=8320, output_tokens=1610, details={'reasoning_tokens': 1344}
|
|
),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 9, 12, 23, 21, 47, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_0b4f29854724a3120068c4ab0b660081919707b95b47552782',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_model_web_search_tool_stream(allow_model_requests: None, openai_api_key: str):
|
|
m = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(
|
|
m,
|
|
instructions='You are a helpful assistant.',
|
|
capabilities=[NativeTool(WebSearchTool())],
|
|
model_settings=OpenAIResponsesModelSettings(openai_include_web_search_sources=True),
|
|
)
|
|
|
|
event_parts: list[Any] = []
|
|
async with agent.iter(user_prompt='What is the weather in San Francisco today?') as agent_run:
|
|
async for node in agent_run:
|
|
if Agent.is_model_request_node(node) or Agent.is_call_tools_node(node):
|
|
async with node.stream(agent_run.ctx) as request_stream:
|
|
async for event in request_stream:
|
|
event_parts.append(event)
|
|
|
|
assert agent_run.result is not None
|
|
messages = agent_run.result.all_messages()
|
|
assert messages == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What is the weather in San Francisco today?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
instructions='You are a helpful assistant.',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_00a60507bf41223d0068c9d2fc927081a088e0b920cdfe3866',
|
|
signature='gAAAAABoydMADQ6HaJB8mYQXlwd-4MrCfzmKqMHXUnSAXWV3huK1UrU1h3Do3pbK4bcD4BAvNiHTH-Pn27MGZDP_53IhKj_vB0egVf6Z_Y2uFPtzmyasYtTzrTkGSfAMR0xfI4wJk99aatk3UyLPNE7EO_vWYzN6CSX5ifJNNcmY3ArW1A7XnmsnMSBys05PsWqLMHZOUFuBvM2W37QUW6QOfBXZy0TamoO5UknNUfZb_TwvSnMEDpa-lXyDn4VuzfxreEGVHGdSyz5oLN0nBr3KwHIfxMRZIf9gi9-hKCnxX7i-ZktNIfTgd_WEmNKlaPO-qjKHPlO_XPKbEfpBdMv5b2P9BIC20ZG3m6qnEc4OqafWZa1iC2szi4eKOEa6neh2ltVLsLS3MlurF4sO-EHQT4O9t-zJ-738mZsOgjsI9rTrLm_aTAJrntSSWRLcP6PI6_ILHyeAl_aN4svtnwQJZhv4_Qf62q70SZQ5fSfqoqfO1YHLcXq6Op99iH3CfAhOjH-NcgThFLpT4-VLYABl8wiWBTsWzdndZoPmvMLEOaEGJOcM6_922FC0Q-fUio3psm_pLcElaG-XIkyn4oNuk6OJQonFE-Bm6WS_1I9sMF0ncSD4gH1Ey-5y2Ayxi3Kb3XWjFvs1RKW17KFXj8sthF3vY5WHUeRKA14WtN-cHsi4lXBFYJmn2FiD3CmV-_4ErzXH8sIMJrDDsqfCoiSbHwih25INTTIj7KAPL2QtIpU6A8zbzQIK-GOKqb0n4wGeOIyf7J4C2-5jhmlF2a6HUApFXZsRcD8e3X1WqSjdTdnRu_0GzDuHhPghRQJ3DHfGwDvoZy6UK55zb2MaxpNyMHT149sMwUWkCVg0BruxnOUfziuURWhT-VJWzv5mr3Z765TFB1PfHJhznKPFiZN0MTStVtqKQlOe8nkwLevCgZY4oT1Mysg7YJhcWtkquKILXe-y6luJBHzUy_aFAgFliUbcrOhkoBk5olAbSz8Y4sSz5vWugYA1kwlIofnRm4sPcvoIXgUD_SGGI3QNsQyRWQEhf7G5mNRrxmLhZZLXAcBAzkw10nEjRfew2Fri7bdvyzJ1OS_af9fHmeqCZG5ievKIX6keUkIYQo_qm4FQFkXZSl9lMHsUSF-di4F6ws31vM0zVLMmH52u12Z3SZhvAFzIV5Vtyt_IfrMV3ANMqVF4SmS4k2qUlv1KuPQVgqGCVHvfeE1oSyYgYF6oFX8ThXNB79wxvi4Oo8fWEZLzZMFH9QEr2c7sOWHYWk-wUMP1auXTQNExEVz22pBxueZGZhRyLdpcA12v8o6vJkVuBj-2eR8GRI7P6InJdQAO9TIBhM7NtJU2NUpeP_84js3RTBVktqBT74nWPaHIddGMSfW2aGmFJovvshhxGMLtN_6XMh4wRKW0IE_-Rfbhk8_-xHKI5McYI048N_TMYOS8KqPPAmGVklRGqPZ5xXMNvQEVweThDTYTo3NoAsS0fN2yMmSwrjRYBHsgYMtil4pd6ddp8dvF_XSJUkW0nF8t6ciI_k47sug3gyw4usqspWxY9Hwbzb4OFzzrgtO_7Ll6lFFFUx2oHy8AO9sJ97Y3Fg6luuew7ZRDzA_4XMrT7mNW6YuT-o2DunaZw-jvQezNHjPN2WhaTS7fkisyhFSFTMBYE-H4psfj_sizutv-LjwbumTcX2mnYE9SZhVr8dL0c7sgwHP1831RxTSSl3ql_obE3ICDooyuM8PYE56Jx0HOOGbEeJd3w91SzNHPG_3SQfXszrZlw4BGWrEUHBbtVY2ZEnsyGNAx6vKO8lz9D-6yZ618foDJSH-Ilk56a5rhr0beWjSd9mYMsr3zpVz6HcpTLYGEgHfPxpT2eaYaC1H_znw7y1eMKamwudYmtz_azX5LrOtwc0p-pXH-kdoNe248pSz9qsmHcXA41fuj2weKQNrmBcghwtfM95B060tnmebJ_B_KkLXL4cNF-hZqi0wAHrHYrZ_WM0Dy90AFH-b7iiWuWz5M1EhZXo179iEdybM-1PgccFJ0zvOqODl7FNxSgWVyNS1k9R42aZx2PzFAfAbBtJ-KVMhUayAvGLNmi35EAT0G6FK65VBEe7A6zPFqzrrAiG8dy3Z0I0253WzIblHPNMpmxI_ca5tIx3u8Za6Nu9rx8mi0CY2jsRSKnqb7RZvLuB78Uj32lb_9jbq5_gL9_y7Bt7U7i7FospyqMFzEYQLvdyrtfNrfY0rB4zr4Mo0tDn_4YOD_d_nP5axUh9_ruqXZ_d3eVdNmlITjQZj8ALe1EfidP8a-Dl62t6STVv8d2y8v9-jy3J7wReLJbJ6gDDnygJllY7NrIVXSjR45FXiCDnpaRonu--I_0b_LRJFOoJUJX0S9YMaXAkKyHSEj-UWjiuk8cIBNcXxwlxnqqNMezvvV113MAOEbfHygDnphzjzZQxteAVbSy0ucGDR2FPi30d6z51NxGnXNS_sM7wnjBMNp4Li0hhttOp6PgvDKPSMAcgUtKLFKE8iWQAvERoUVxw5Et20hNTNXf_0sXOyh0bF0URPGDxSYz9uZI6-nlwVlo1aobdEnn7STSq2_tuTDIrQyfBGZzhv8OB0H3cj9mBs=',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='web_search',
|
|
args={'query': 'weather: San Francisco, CA', 'type': 'search'},
|
|
tool_call_id='ws_00a60507bf41223d0068c9d30021d081a0962d80d50c12e317',
|
|
id='ws_00a60507bf41223d0068c9d30021d081a0962d80d50c12e317',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='web_search',
|
|
content={
|
|
'sources': [{'type': 'api', 'name': 'oai-weather'}],
|
|
'status': 'completed',
|
|
},
|
|
tool_call_id='ws_00a60507bf41223d0068c9d30021d081a0962d80d50c12e317',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_00a60507bf41223d0068c9d300b23481a0b77a03d911213220',
|
|
signature='gAAAAABoydMLww_4DcIPiCy5zW1t-Dtx57JodsdP9YkyDCvY9r0nBfoD-bwcBl8FfcFxRuq5nK5ndf90J6Xdxmvl9kGVbCUYSFaOd-kWeE4tgwXM8BwwE3jVs6ZMG3BdiWyXS3alnUO5jcE6kuXeun1LufAdZ6rWtJl3zSmqPycnTh9zoQ4cBBxLDq_qcVS1fgU4WjsWgjCZw6ZWRPWwZk8hywmU7ykCLH7SXItM4oH1m_GCEounJAS8YR4ajUh5KAdN6a1wngfGnXhYdzov98yiNLP6Nrkgr--K4vMTTqWXLTcR6fbWgkijanIeKmfSErCjMT6k5TrAbkFx0rgblHbdQii7zj8seV1BWZse92_k4sltxfc5Ocpyho1YSHhgxyGL7g442xMUEibjPCv6kwPMcW9yGu9wPMWsfPYCXpBbG6kQibQPNFJ_bEubwBRaqdSDq93Aqr1YkTYBja7Tewn8UfzZ8YYaGe5y_K4ZD47lfvDp019dOdXmOuZGC1ECRrMqKzSFYVG1CFY1VhjGdPmzobDoMcpZcLn25s1pg6lnNqNQwOk_IA4MvUcCU5HHD5YjmFkEy5-i_iRoDVu5coK0zyEMvPJ_h10y_ByszcfzS9e0ht5CSilckkFdxTBkZ5epp0YIg1e-PrZ790P-I35Ucquam9OXyULV1Y5bn9ohZa93Tv0JZRxUeTDG72_28xRj8tkJaBAZjoCC7VICw39KVmz-ZkuVN6IIX1WdNzyC4d808-2Tz4UZaU42-wxEWDnSDMD7iZu1Bi9fKKwAYBJt_OcEsJwpW63ZaUSG2PVFfm7a3wRcSMxMTUTTJB7L1Keu1hmNepif5tavn3P35nSq28D_IJyAqAgX7ZyROk2bJqjzSE4A0MddqAoBFFqKBi68n49KH09vDtDXIoh8jVWuIgowgVGr8pN3kuhLI9cir4Pr_WES0tPD7yWHPTzrD7OIJCfQbr_4Y4dEza4ixNi0RTADWzMUZBfr7bvwIsgvg6ZNuQlx_d71Go5VDsT2KI8H8AldiRvNWoLyYTFGyK9Kot97YsS5sEmSYgNAH48NU7pgnM0jNDQU1G39nTNFEjL_ziDwjDT5g3jm4S_gbQfwx-XFT3Pv-JYR-E71AqR--Lg71OsASq49rrlULfl5OENfiT-NB6x8MqnfUI6NpcCsOWLp8XfRbgqmZFutLIi43pcnxEe3cXHLWGF77qJXP6dFb-G5Ide7n9tAOoEgfsVu7hCDPEQ_xrIYRdc2DzDPUMCtXBai24E0AnQF8kxsEtlDW_YmAgGNTl9Gx0tFSGdDuUCsNx__c7v-_LOMWycXUKmH3iEr_su83oGIMapNp2PnLccN4iOxspdZQq0C6WBaR6SrdnGzK-0KwRPRoyKDLNWS8zfluR5bIgKlqd3Sbv_7eL-WO4LQXMvdKP3KS-DBt1HbA-gmyFW03iX2smPQbtVmRLWi1vG329R_07-tHMJSO9OQy6_6aiyO8Rgpbl_CHa1Q9BEkI2csonayDJRPvEXBPuk9-NPUP4VLNPB7npWBLlAqes5ZmhagnC7srTL0fFiLGLJiAxWo1f0BBiIlXjwqHdlgBjTw0KryCnEU8Ic8ATzrqEXXhs-FTBCcWInf3Bt5bzUhy20g7cTtYP-VCbsku-lXQ6wceWrfQVFtjKKICD8I4g9QusAIAvgCUm7J2rR3TLkzwOKngdTFPGQrQ1TYzlkA7q_Ew1uZpaPRckMaEioZYC6Sv_B0rgW0nyBJ0GLrB3AUN60hDrOFntyFHp0FM-Zh1SY-GKGBwZwVetOzM0ZAJ-NreFg1XVgyLTYDNjUrYJjRhr_JARsZ5t0pU4_yI6dPqM5jKO5_k4UpZspfQon6d2-NlWX0EDmz6G4CMTx0TScehYHrQZtPzpVnivc8h_pmXV3jO5GLzNeLWoB70SDPTETo1Of4txiEUaC2komu5B7MN9aR4c7VBOTv1NIjoiZcrd1HFACzZ7r1qAE-G38j1f1YhfZ0_TiMmtfR1cqjAKcFkyRM7rZMyMvvnsH7NFq59gFgWZt0dy0aAdw03XWXFNT67lrw58OYC3NcVozH4SKlmleu7TfjHNWSnJVjJ66riLn9DZWVxPeTk4zuISZn0yyaoXcdW8OMn_mJ9vP-8L1wElMyxKbtBRz-0cW7MshmJ3YXmHWDKbnqETSbDMtqcN_QyRJovopwlptJ8VzL7biuURRFw-l63Kc9vKP72Z-QWOUIPLB4q4nX4yb-IV0mkWFxIUlfv5Cze2anf7zDFyGzeU9xG0onfhJE4HFKcoUT8MzfrHZ0dDZtnEYeL5Xem3GuHpwEVGCxRE_J1joTmJfeWxSVnr2Vey9gaPmXCyRrdKS75v9xSXJFfHvcOO8Qp35Dzk-yFqL3dSOJfOEwDZbEf6QnV7VU1EhJvW4XmRS-wsRLMLCYcLrOx96NHEwb2h2l6gNfbCVJoQrMhMg68qBPnoSYLhML2ho7hWkSNZFy61yX5I-oEJV5XdtjFcBkyurmUD6uYTkJSqXyxLexQiPbT-uv49Yp9cAfFBG23sC9lUQ=',
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content='San Francisco weather today (Tuesday, September 16, 2025): Mostly sunny and pleasant. Current conditions around 71°F; expected high near 73°F and low around 58°F. A light jacket is useful for the cooler evening. ',
|
|
id='msg_00a60507bf41223d0068c9d30b055481a0b0ee28a021919c94',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(
|
|
input_tokens=9463,
|
|
cache_read_tokens=8320,
|
|
output_tokens=582,
|
|
details={'reasoning_tokens': 512},
|
|
),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 9, 16, 21, 13, 32, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_00a60507bf41223d0068c9d2fbf93481a0ba2a7796ae2cab4c',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
assert event_parts == snapshot(
|
|
[
|
|
PartStartEvent(
|
|
index=0,
|
|
part=ThinkingPart(
|
|
content='',
|
|
id='rs_00a60507bf41223d0068c9d2fc927081a088e0b920cdfe3866',
|
|
signature='gAAAAABoydMADQ6HaJB8mYQXlwd-4MrCfzmKqMHXUnSAXWV3huK1UrU1h3Do3pbK4bcD4BAvNiHTH-Pn27MGZDP_53IhKj_vB0egVf6Z_Y2uFPtzmyasYtTzrTkGSfAMR0xfI4wJk99aatk3UyLPNE7EO_vWYzN6CSX5ifJNNcmY3ArW1A7XnmsnMSBys05PsWqLMHZOUFuBvM2W37QUW6QOfBXZy0TamoO5UknNUfZb_TwvSnMEDpa-lXyDn4VuzfxreEGVHGdSyz5oLN0nBr3KwHIfxMRZIf9gi9-hKCnxX7i-ZktNIfTgd_WEmNKlaPO-qjKHPlO_XPKbEfpBdMv5b2P9BIC20ZG3m6qnEc4OqafWZa1iC2szi4eKOEa6neh2ltVLsLS3MlurF4sO-EHQT4O9t-zJ-738mZsOgjsI9rTrLm_aTAJrntSSWRLcP6PI6_ILHyeAl_aN4svtnwQJZhv4_Qf62q70SZQ5fSfqoqfO1YHLcXq6Op99iH3CfAhOjH-NcgThFLpT4-VLYABl8wiWBTsWzdndZoPmvMLEOaEGJOcM6_922FC0Q-fUio3psm_pLcElaG-XIkyn4oNuk6OJQonFE-Bm6WS_1I9sMF0ncSD4gH1Ey-5y2Ayxi3Kb3XWjFvs1RKW17KFXj8sthF3vY5WHUeRKA14WtN-cHsi4lXBFYJmn2FiD3CmV-_4ErzXH8sIMJrDDsqfCoiSbHwih25INTTIj7KAPL2QtIpU6A8zbzQIK-GOKqb0n4wGeOIyf7J4C2-5jhmlF2a6HUApFXZsRcD8e3X1WqSjdTdnRu_0GzDuHhPghRQJ3DHfGwDvoZy6UK55zb2MaxpNyMHT149sMwUWkCVg0BruxnOUfziuURWhT-VJWzv5mr3Z765TFB1PfHJhznKPFiZN0MTStVtqKQlOe8nkwLevCgZY4oT1Mysg7YJhcWtkquKILXe-y6luJBHzUy_aFAgFliUbcrOhkoBk5olAbSz8Y4sSz5vWugYA1kwlIofnRm4sPcvoIXgUD_SGGI3QNsQyRWQEhf7G5mNRrxmLhZZLXAcBAzkw10nEjRfew2Fri7bdvyzJ1OS_af9fHmeqCZG5ievKIX6keUkIYQo_qm4FQFkXZSl9lMHsUSF-di4F6ws31vM0zVLMmH52u12Z3SZhvAFzIV5Vtyt_IfrMV3ANMqVF4SmS4k2qUlv1KuPQVgqGCVHvfeE1oSyYgYF6oFX8ThXNB79wxvi4Oo8fWEZLzZMFH9QEr2c7sOWHYWk-wUMP1auXTQNExEVz22pBxueZGZhRyLdpcA12v8o6vJkVuBj-2eR8GRI7P6InJdQAO9TIBhM7NtJU2NUpeP_84js3RTBVktqBT74nWPaHIddGMSfW2aGmFJovvshhxGMLtN_6XMh4wRKW0IE_-Rfbhk8_-xHKI5McYI048N_TMYOS8KqPPAmGVklRGqPZ5xXMNvQEVweThDTYTo3NoAsS0fN2yMmSwrjRYBHsgYMtil4pd6ddp8dvF_XSJUkW0nF8t6ciI_k47sug3gyw4usqspWxY9Hwbzb4OFzzrgtO_7Ll6lFFFUx2oHy8AO9sJ97Y3Fg6luuew7ZRDzA_4XMrT7mNW6YuT-o2DunaZw-jvQezNHjPN2WhaTS7fkisyhFSFTMBYE-H4psfj_sizutv-LjwbumTcX2mnYE9SZhVr8dL0c7sgwHP1831RxTSSl3ql_obE3ICDooyuM8PYE56Jx0HOOGbEeJd3w91SzNHPG_3SQfXszrZlw4BGWrEUHBbtVY2ZEnsyGNAx6vKO8lz9D-6yZ618foDJSH-Ilk56a5rhr0beWjSd9mYMsr3zpVz6HcpTLYGEgHfPxpT2eaYaC1H_znw7y1eMKamwudYmtz_azX5LrOtwc0p-pXH-kdoNe248pSz9qsmHcXA41fuj2weKQNrmBcghwtfM95B060tnmebJ_B_KkLXL4cNF-hZqi0wAHrHYrZ_WM0Dy90AFH-b7iiWuWz5M1EhZXo179iEdybM-1PgccFJ0zvOqODl7FNxSgWVyNS1k9R42aZx2PzFAfAbBtJ-KVMhUayAvGLNmi35EAT0G6FK65VBEe7A6zPFqzrrAiG8dy3Z0I0253WzIblHPNMpmxI_ca5tIx3u8Za6Nu9rx8mi0CY2jsRSKnqb7RZvLuB78Uj32lb_9jbq5_gL9_y7Bt7U7i7FospyqMFzEYQLvdyrtfNrfY0rB4zr4Mo0tDn_4YOD_d_nP5axUh9_ruqXZ_d3eVdNmlITjQZj8ALe1EfidP8a-Dl62t6STVv8d2y8v9-jy3J7wReLJbJ6gDDnygJllY7NrIVXSjR45FXiCDnpaRonu--I_0b_LRJFOoJUJX0S9YMaXAkKyHSEj-UWjiuk8cIBNcXxwlxnqqNMezvvV113MAOEbfHygDnphzjzZQxteAVbSy0ucGDR2FPi30d6z51NxGnXNS_sM7wnjBMNp4Li0hhttOp6PgvDKPSMAcgUtKLFKE8iWQAvERoUVxw5Et20hNTNXf_0sXOyh0bF0URPGDxSYz9uZI6-nlwVlo1aobdEnn7STSq2_tuTDIrQyfBGZzhv8OB0H3cj9mBs=',
|
|
provider_name='openai',
|
|
),
|
|
),
|
|
PartEndEvent(
|
|
index=0,
|
|
part=ThinkingPart(
|
|
content='',
|
|
id='rs_00a60507bf41223d0068c9d2fc927081a088e0b920cdfe3866',
|
|
signature='gAAAAABoydMADQ6HaJB8mYQXlwd-4MrCfzmKqMHXUnSAXWV3huK1UrU1h3Do3pbK4bcD4BAvNiHTH-Pn27MGZDP_53IhKj_vB0egVf6Z_Y2uFPtzmyasYtTzrTkGSfAMR0xfI4wJk99aatk3UyLPNE7EO_vWYzN6CSX5ifJNNcmY3ArW1A7XnmsnMSBys05PsWqLMHZOUFuBvM2W37QUW6QOfBXZy0TamoO5UknNUfZb_TwvSnMEDpa-lXyDn4VuzfxreEGVHGdSyz5oLN0nBr3KwHIfxMRZIf9gi9-hKCnxX7i-ZktNIfTgd_WEmNKlaPO-qjKHPlO_XPKbEfpBdMv5b2P9BIC20ZG3m6qnEc4OqafWZa1iC2szi4eKOEa6neh2ltVLsLS3MlurF4sO-EHQT4O9t-zJ-738mZsOgjsI9rTrLm_aTAJrntSSWRLcP6PI6_ILHyeAl_aN4svtnwQJZhv4_Qf62q70SZQ5fSfqoqfO1YHLcXq6Op99iH3CfAhOjH-NcgThFLpT4-VLYABl8wiWBTsWzdndZoPmvMLEOaEGJOcM6_922FC0Q-fUio3psm_pLcElaG-XIkyn4oNuk6OJQonFE-Bm6WS_1I9sMF0ncSD4gH1Ey-5y2Ayxi3Kb3XWjFvs1RKW17KFXj8sthF3vY5WHUeRKA14WtN-cHsi4lXBFYJmn2FiD3CmV-_4ErzXH8sIMJrDDsqfCoiSbHwih25INTTIj7KAPL2QtIpU6A8zbzQIK-GOKqb0n4wGeOIyf7J4C2-5jhmlF2a6HUApFXZsRcD8e3X1WqSjdTdnRu_0GzDuHhPghRQJ3DHfGwDvoZy6UK55zb2MaxpNyMHT149sMwUWkCVg0BruxnOUfziuURWhT-VJWzv5mr3Z765TFB1PfHJhznKPFiZN0MTStVtqKQlOe8nkwLevCgZY4oT1Mysg7YJhcWtkquKILXe-y6luJBHzUy_aFAgFliUbcrOhkoBk5olAbSz8Y4sSz5vWugYA1kwlIofnRm4sPcvoIXgUD_SGGI3QNsQyRWQEhf7G5mNRrxmLhZZLXAcBAzkw10nEjRfew2Fri7bdvyzJ1OS_af9fHmeqCZG5ievKIX6keUkIYQo_qm4FQFkXZSl9lMHsUSF-di4F6ws31vM0zVLMmH52u12Z3SZhvAFzIV5Vtyt_IfrMV3ANMqVF4SmS4k2qUlv1KuPQVgqGCVHvfeE1oSyYgYF6oFX8ThXNB79wxvi4Oo8fWEZLzZMFH9QEr2c7sOWHYWk-wUMP1auXTQNExEVz22pBxueZGZhRyLdpcA12v8o6vJkVuBj-2eR8GRI7P6InJdQAO9TIBhM7NtJU2NUpeP_84js3RTBVktqBT74nWPaHIddGMSfW2aGmFJovvshhxGMLtN_6XMh4wRKW0IE_-Rfbhk8_-xHKI5McYI048N_TMYOS8KqPPAmGVklRGqPZ5xXMNvQEVweThDTYTo3NoAsS0fN2yMmSwrjRYBHsgYMtil4pd6ddp8dvF_XSJUkW0nF8t6ciI_k47sug3gyw4usqspWxY9Hwbzb4OFzzrgtO_7Ll6lFFFUx2oHy8AO9sJ97Y3Fg6luuew7ZRDzA_4XMrT7mNW6YuT-o2DunaZw-jvQezNHjPN2WhaTS7fkisyhFSFTMBYE-H4psfj_sizutv-LjwbumTcX2mnYE9SZhVr8dL0c7sgwHP1831RxTSSl3ql_obE3ICDooyuM8PYE56Jx0HOOGbEeJd3w91SzNHPG_3SQfXszrZlw4BGWrEUHBbtVY2ZEnsyGNAx6vKO8lz9D-6yZ618foDJSH-Ilk56a5rhr0beWjSd9mYMsr3zpVz6HcpTLYGEgHfPxpT2eaYaC1H_znw7y1eMKamwudYmtz_azX5LrOtwc0p-pXH-kdoNe248pSz9qsmHcXA41fuj2weKQNrmBcghwtfM95B060tnmebJ_B_KkLXL4cNF-hZqi0wAHrHYrZ_WM0Dy90AFH-b7iiWuWz5M1EhZXo179iEdybM-1PgccFJ0zvOqODl7FNxSgWVyNS1k9R42aZx2PzFAfAbBtJ-KVMhUayAvGLNmi35EAT0G6FK65VBEe7A6zPFqzrrAiG8dy3Z0I0253WzIblHPNMpmxI_ca5tIx3u8Za6Nu9rx8mi0CY2jsRSKnqb7RZvLuB78Uj32lb_9jbq5_gL9_y7Bt7U7i7FospyqMFzEYQLvdyrtfNrfY0rB4zr4Mo0tDn_4YOD_d_nP5axUh9_ruqXZ_d3eVdNmlITjQZj8ALe1EfidP8a-Dl62t6STVv8d2y8v9-jy3J7wReLJbJ6gDDnygJllY7NrIVXSjR45FXiCDnpaRonu--I_0b_LRJFOoJUJX0S9YMaXAkKyHSEj-UWjiuk8cIBNcXxwlxnqqNMezvvV113MAOEbfHygDnphzjzZQxteAVbSy0ucGDR2FPi30d6z51NxGnXNS_sM7wnjBMNp4Li0hhttOp6PgvDKPSMAcgUtKLFKE8iWQAvERoUVxw5Et20hNTNXf_0sXOyh0bF0URPGDxSYz9uZI6-nlwVlo1aobdEnn7STSq2_tuTDIrQyfBGZzhv8OB0H3cj9mBs=',
|
|
provider_name='openai',
|
|
),
|
|
next_part_kind='builtin-tool-call',
|
|
),
|
|
PartStartEvent(
|
|
index=1,
|
|
part=NativeToolCallPart(
|
|
tool_name='web_search',
|
|
tool_call_id='ws_00a60507bf41223d0068c9d30021d081a0962d80d50c12e317',
|
|
id='ws_00a60507bf41223d0068c9d30021d081a0962d80d50c12e317',
|
|
provider_name='openai',
|
|
),
|
|
previous_part_kind='thinking',
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta={'query': 'weather: San Francisco, CA', 'type': 'search'},
|
|
tool_call_id='ws_00a60507bf41223d0068c9d30021d081a0962d80d50c12e317',
|
|
),
|
|
),
|
|
PartEndEvent(
|
|
index=1,
|
|
part=NativeToolCallPart(
|
|
tool_name='web_search',
|
|
args={'query': 'weather: San Francisco, CA', 'type': 'search'},
|
|
tool_call_id='ws_00a60507bf41223d0068c9d30021d081a0962d80d50c12e317',
|
|
id='ws_00a60507bf41223d0068c9d30021d081a0962d80d50c12e317',
|
|
provider_name='openai',
|
|
),
|
|
next_part_kind='builtin-tool-return',
|
|
),
|
|
PartStartEvent(
|
|
index=2,
|
|
part=NativeToolReturnPart(
|
|
tool_name='web_search',
|
|
content={'status': 'completed', 'sources': [{'type': 'api', 'name': 'oai-weather'}]},
|
|
tool_call_id='ws_00a60507bf41223d0068c9d30021d081a0962d80d50c12e317',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
previous_part_kind='builtin-tool-call',
|
|
),
|
|
PartStartEvent(
|
|
index=3,
|
|
part=ThinkingPart(
|
|
content='',
|
|
id='rs_00a60507bf41223d0068c9d300b23481a0b77a03d911213220',
|
|
signature='gAAAAABoydMLww_4DcIPiCy5zW1t-Dtx57JodsdP9YkyDCvY9r0nBfoD-bwcBl8FfcFxRuq5nK5ndf90J6Xdxmvl9kGVbCUYSFaOd-kWeE4tgwXM8BwwE3jVs6ZMG3BdiWyXS3alnUO5jcE6kuXeun1LufAdZ6rWtJl3zSmqPycnTh9zoQ4cBBxLDq_qcVS1fgU4WjsWgjCZw6ZWRPWwZk8hywmU7ykCLH7SXItM4oH1m_GCEounJAS8YR4ajUh5KAdN6a1wngfGnXhYdzov98yiNLP6Nrkgr--K4vMTTqWXLTcR6fbWgkijanIeKmfSErCjMT6k5TrAbkFx0rgblHbdQii7zj8seV1BWZse92_k4sltxfc5Ocpyho1YSHhgxyGL7g442xMUEibjPCv6kwPMcW9yGu9wPMWsfPYCXpBbG6kQibQPNFJ_bEubwBRaqdSDq93Aqr1YkTYBja7Tewn8UfzZ8YYaGe5y_K4ZD47lfvDp019dOdXmOuZGC1ECRrMqKzSFYVG1CFY1VhjGdPmzobDoMcpZcLn25s1pg6lnNqNQwOk_IA4MvUcCU5HHD5YjmFkEy5-i_iRoDVu5coK0zyEMvPJ_h10y_ByszcfzS9e0ht5CSilckkFdxTBkZ5epp0YIg1e-PrZ790P-I35Ucquam9OXyULV1Y5bn9ohZa93Tv0JZRxUeTDG72_28xRj8tkJaBAZjoCC7VICw39KVmz-ZkuVN6IIX1WdNzyC4d808-2Tz4UZaU42-wxEWDnSDMD7iZu1Bi9fKKwAYBJt_OcEsJwpW63ZaUSG2PVFfm7a3wRcSMxMTUTTJB7L1Keu1hmNepif5tavn3P35nSq28D_IJyAqAgX7ZyROk2bJqjzSE4A0MddqAoBFFqKBi68n49KH09vDtDXIoh8jVWuIgowgVGr8pN3kuhLI9cir4Pr_WES0tPD7yWHPTzrD7OIJCfQbr_4Y4dEza4ixNi0RTADWzMUZBfr7bvwIsgvg6ZNuQlx_d71Go5VDsT2KI8H8AldiRvNWoLyYTFGyK9Kot97YsS5sEmSYgNAH48NU7pgnM0jNDQU1G39nTNFEjL_ziDwjDT5g3jm4S_gbQfwx-XFT3Pv-JYR-E71AqR--Lg71OsASq49rrlULfl5OENfiT-NB6x8MqnfUI6NpcCsOWLp8XfRbgqmZFutLIi43pcnxEe3cXHLWGF77qJXP6dFb-G5Ide7n9tAOoEgfsVu7hCDPEQ_xrIYRdc2DzDPUMCtXBai24E0AnQF8kxsEtlDW_YmAgGNTl9Gx0tFSGdDuUCsNx__c7v-_LOMWycXUKmH3iEr_su83oGIMapNp2PnLccN4iOxspdZQq0C6WBaR6SrdnGzK-0KwRPRoyKDLNWS8zfluR5bIgKlqd3Sbv_7eL-WO4LQXMvdKP3KS-DBt1HbA-gmyFW03iX2smPQbtVmRLWi1vG329R_07-tHMJSO9OQy6_6aiyO8Rgpbl_CHa1Q9BEkI2csonayDJRPvEXBPuk9-NPUP4VLNPB7npWBLlAqes5ZmhagnC7srTL0fFiLGLJiAxWo1f0BBiIlXjwqHdlgBjTw0KryCnEU8Ic8ATzrqEXXhs-FTBCcWInf3Bt5bzUhy20g7cTtYP-VCbsku-lXQ6wceWrfQVFtjKKICD8I4g9QusAIAvgCUm7J2rR3TLkzwOKngdTFPGQrQ1TYzlkA7q_Ew1uZpaPRckMaEioZYC6Sv_B0rgW0nyBJ0GLrB3AUN60hDrOFntyFHp0FM-Zh1SY-GKGBwZwVetOzM0ZAJ-NreFg1XVgyLTYDNjUrYJjRhr_JARsZ5t0pU4_yI6dPqM5jKO5_k4UpZspfQon6d2-NlWX0EDmz6G4CMTx0TScehYHrQZtPzpVnivc8h_pmXV3jO5GLzNeLWoB70SDPTETo1Of4txiEUaC2komu5B7MN9aR4c7VBOTv1NIjoiZcrd1HFACzZ7r1qAE-G38j1f1YhfZ0_TiMmtfR1cqjAKcFkyRM7rZMyMvvnsH7NFq59gFgWZt0dy0aAdw03XWXFNT67lrw58OYC3NcVozH4SKlmleu7TfjHNWSnJVjJ66riLn9DZWVxPeTk4zuISZn0yyaoXcdW8OMn_mJ9vP-8L1wElMyxKbtBRz-0cW7MshmJ3YXmHWDKbnqETSbDMtqcN_QyRJovopwlptJ8VzL7biuURRFw-l63Kc9vKP72Z-QWOUIPLB4q4nX4yb-IV0mkWFxIUlfv5Cze2anf7zDFyGzeU9xG0onfhJE4HFKcoUT8MzfrHZ0dDZtnEYeL5Xem3GuHpwEVGCxRE_J1joTmJfeWxSVnr2Vey9gaPmXCyRrdKS75v9xSXJFfHvcOO8Qp35Dzk-yFqL3dSOJfOEwDZbEf6QnV7VU1EhJvW4XmRS-wsRLMLCYcLrOx96NHEwb2h2l6gNfbCVJoQrMhMg68qBPnoSYLhML2ho7hWkSNZFy61yX5I-oEJV5XdtjFcBkyurmUD6uYTkJSqXyxLexQiPbT-uv49Yp9cAfFBG23sC9lUQ=',
|
|
provider_name='openai',
|
|
),
|
|
previous_part_kind='builtin-tool-return',
|
|
),
|
|
PartEndEvent(
|
|
index=3,
|
|
part=ThinkingPart(
|
|
content='',
|
|
id='rs_00a60507bf41223d0068c9d300b23481a0b77a03d911213220',
|
|
signature='gAAAAABoydMLww_4DcIPiCy5zW1t-Dtx57JodsdP9YkyDCvY9r0nBfoD-bwcBl8FfcFxRuq5nK5ndf90J6Xdxmvl9kGVbCUYSFaOd-kWeE4tgwXM8BwwE3jVs6ZMG3BdiWyXS3alnUO5jcE6kuXeun1LufAdZ6rWtJl3zSmqPycnTh9zoQ4cBBxLDq_qcVS1fgU4WjsWgjCZw6ZWRPWwZk8hywmU7ykCLH7SXItM4oH1m_GCEounJAS8YR4ajUh5KAdN6a1wngfGnXhYdzov98yiNLP6Nrkgr--K4vMTTqWXLTcR6fbWgkijanIeKmfSErCjMT6k5TrAbkFx0rgblHbdQii7zj8seV1BWZse92_k4sltxfc5Ocpyho1YSHhgxyGL7g442xMUEibjPCv6kwPMcW9yGu9wPMWsfPYCXpBbG6kQibQPNFJ_bEubwBRaqdSDq93Aqr1YkTYBja7Tewn8UfzZ8YYaGe5y_K4ZD47lfvDp019dOdXmOuZGC1ECRrMqKzSFYVG1CFY1VhjGdPmzobDoMcpZcLn25s1pg6lnNqNQwOk_IA4MvUcCU5HHD5YjmFkEy5-i_iRoDVu5coK0zyEMvPJ_h10y_ByszcfzS9e0ht5CSilckkFdxTBkZ5epp0YIg1e-PrZ790P-I35Ucquam9OXyULV1Y5bn9ohZa93Tv0JZRxUeTDG72_28xRj8tkJaBAZjoCC7VICw39KVmz-ZkuVN6IIX1WdNzyC4d808-2Tz4UZaU42-wxEWDnSDMD7iZu1Bi9fKKwAYBJt_OcEsJwpW63ZaUSG2PVFfm7a3wRcSMxMTUTTJB7L1Keu1hmNepif5tavn3P35nSq28D_IJyAqAgX7ZyROk2bJqjzSE4A0MddqAoBFFqKBi68n49KH09vDtDXIoh8jVWuIgowgVGr8pN3kuhLI9cir4Pr_WES0tPD7yWHPTzrD7OIJCfQbr_4Y4dEza4ixNi0RTADWzMUZBfr7bvwIsgvg6ZNuQlx_d71Go5VDsT2KI8H8AldiRvNWoLyYTFGyK9Kot97YsS5sEmSYgNAH48NU7pgnM0jNDQU1G39nTNFEjL_ziDwjDT5g3jm4S_gbQfwx-XFT3Pv-JYR-E71AqR--Lg71OsASq49rrlULfl5OENfiT-NB6x8MqnfUI6NpcCsOWLp8XfRbgqmZFutLIi43pcnxEe3cXHLWGF77qJXP6dFb-G5Ide7n9tAOoEgfsVu7hCDPEQ_xrIYRdc2DzDPUMCtXBai24E0AnQF8kxsEtlDW_YmAgGNTl9Gx0tFSGdDuUCsNx__c7v-_LOMWycXUKmH3iEr_su83oGIMapNp2PnLccN4iOxspdZQq0C6WBaR6SrdnGzK-0KwRPRoyKDLNWS8zfluR5bIgKlqd3Sbv_7eL-WO4LQXMvdKP3KS-DBt1HbA-gmyFW03iX2smPQbtVmRLWi1vG329R_07-tHMJSO9OQy6_6aiyO8Rgpbl_CHa1Q9BEkI2csonayDJRPvEXBPuk9-NPUP4VLNPB7npWBLlAqes5ZmhagnC7srTL0fFiLGLJiAxWo1f0BBiIlXjwqHdlgBjTw0KryCnEU8Ic8ATzrqEXXhs-FTBCcWInf3Bt5bzUhy20g7cTtYP-VCbsku-lXQ6wceWrfQVFtjKKICD8I4g9QusAIAvgCUm7J2rR3TLkzwOKngdTFPGQrQ1TYzlkA7q_Ew1uZpaPRckMaEioZYC6Sv_B0rgW0nyBJ0GLrB3AUN60hDrOFntyFHp0FM-Zh1SY-GKGBwZwVetOzM0ZAJ-NreFg1XVgyLTYDNjUrYJjRhr_JARsZ5t0pU4_yI6dPqM5jKO5_k4UpZspfQon6d2-NlWX0EDmz6G4CMTx0TScehYHrQZtPzpVnivc8h_pmXV3jO5GLzNeLWoB70SDPTETo1Of4txiEUaC2komu5B7MN9aR4c7VBOTv1NIjoiZcrd1HFACzZ7r1qAE-G38j1f1YhfZ0_TiMmtfR1cqjAKcFkyRM7rZMyMvvnsH7NFq59gFgWZt0dy0aAdw03XWXFNT67lrw58OYC3NcVozH4SKlmleu7TfjHNWSnJVjJ66riLn9DZWVxPeTk4zuISZn0yyaoXcdW8OMn_mJ9vP-8L1wElMyxKbtBRz-0cW7MshmJ3YXmHWDKbnqETSbDMtqcN_QyRJovopwlptJ8VzL7biuURRFw-l63Kc9vKP72Z-QWOUIPLB4q4nX4yb-IV0mkWFxIUlfv5Cze2anf7zDFyGzeU9xG0onfhJE4HFKcoUT8MzfrHZ0dDZtnEYeL5Xem3GuHpwEVGCxRE_J1joTmJfeWxSVnr2Vey9gaPmXCyRrdKS75v9xSXJFfHvcOO8Qp35Dzk-yFqL3dSOJfOEwDZbEf6QnV7VU1EhJvW4XmRS-wsRLMLCYcLrOx96NHEwb2h2l6gNfbCVJoQrMhMg68qBPnoSYLhML2ho7hWkSNZFy61yX5I-oEJV5XdtjFcBkyurmUD6uYTkJSqXyxLexQiPbT-uv49Yp9cAfFBG23sC9lUQ=',
|
|
provider_name='openai',
|
|
),
|
|
next_part_kind='text',
|
|
),
|
|
PartStartEvent(
|
|
index=4,
|
|
part=TextPart(
|
|
content='San Francisco',
|
|
id='msg_00a60507bf41223d0068c9d30b055481a0b0ee28a021919c94',
|
|
provider_name='openai',
|
|
),
|
|
previous_part_kind='thinking',
|
|
),
|
|
FinalResultEvent(tool_name=None, tool_call_id=None),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=' weather'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=' today'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=' ('),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta='Tuesday'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=','),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=' September'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=' '),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta='16'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=','),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=' '),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta='202'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta='5'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta='):'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=' Mostly'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=' sunny'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=' and'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=' pleasant'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta='.'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=' Current'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=' conditions'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=' around'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=' '),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta='71'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta='°F'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=';'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=' expected'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=' high'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=' near'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=' '),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta='73'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta='°F'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=' and'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=' low'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=' around'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=' '),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta='58'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta='°F'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta='.'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=' A light jacket'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=' is useful'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=' for the'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta=' cooler evening'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=4,
|
|
delta=TextPartDelta(content_delta='. '),
|
|
),
|
|
PartEndEvent(
|
|
index=4,
|
|
part=TextPart(
|
|
content='San Francisco weather today (Tuesday, September 16, 2025): Mostly sunny and pleasant. Current conditions around 71°F; expected high near 73°F and low around 58°F. A light jacket is useful for the cooler evening. ',
|
|
id='msg_00a60507bf41223d0068c9d30b055481a0b0ee28a021919c94',
|
|
provider_name='openai',
|
|
),
|
|
),
|
|
]
|
|
)
|
|
|
|
result = await agent.run(user_prompt='how about Mexico City?', message_history=messages)
|
|
assert result.new_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='how about Mexico City?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
instructions='You are a helpful assistant.',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_00a60507bf41223d0068c9d316accc81a096fd539b77c931cd',
|
|
signature='gAAAAABoydMovnl5STyQJfKyyT-LV6102tn7M3ppFZHklPnA1LWETYbnDdCSLgeh1OqOXicuil2GTd-peiKj033k_NL0ZF5mCymWY-g5qoovU8OauQyb2uR9zmLe-cjghlOuiIJjiZC1_DCbwY1MHObzuME-Hn5WiSlfTTcdKfZqaQpzIKVKgbx6cSDDyS5j29ClLw-M6GQUDVDsjkclLEcc8pdoAwvuWDoARgMYXwcS-7Ajl46_9oA92RP-64VjrO6Wxzz9HjKcnBTcSDUcyJxsdolHq6G0TjZFwECg4RWvzcpijO53OF58a4_SfgUqbupni7o-tMzITyF1lwE5Xq9fluUFHXmbH0QCrk_7lGRjeiFqY9tTv_VKbNeHSVj5obUnA5HyAYb5jEqgy9M-CgdN1DJeODMTq3Ncu1y81_p7sXqxpbh1c-2eHkGj6yMFjO-dF9LpX_GUZZgAoPXN-J0k3_6VFWc6FjwOGbPU_weslCBpBnS0USfiif9y8nzH2xg0VrHCUEliBOkN-QLqq68edZOBAmYgG8iRDx-yG762TzOBri-0EdFHGWnMij_onb0y4f0UOXD-qSqHvBj8WKasOSRkBpJmIkDViKXYab3nhOtUb4Y3jNhSh6KYEW1QETK9oOMc1zd0Osk-z0QBLQdGtMuFiR00Bs1M_E4T0lMYEsFRqQ8TZmM5-hmrAkBVx3u1f9-ccBZE0ANOiNWH-G75LozwgZhYrOwbuDSnG3wq2M0L7F1mkseg5lOGKgyaxkaifO6WyS6JCHMwDZUF4gZKyHItg3x3PACmTdUy_Wda55J5oIFklWtjFGbU-dY7vr8wvyF0Q0jEeMp8tFvMpGOGTVlydMBq6SCWrZAz8uDoMRxuNLecaHj3bSQHbfeC3hs8uKCLOMr0X_ZCQ8ATXSSjjml3onzNvqChlsspKcwtEKKSwHNTMUJbY6cyy45EQdYhbKg75k-ZL7Y6BXMRjCc5CJd-4uuD8_cXHi4ikmkpHmgZLHcQPOdFflXeDlpYVTF9-Hyblg4SsxvLX9Vp5h4T4J_RcalfwPsIAwIEn8RSutJyMAIm0tYsEzq5i4usmLMxyEBbekCgP5DlHbeWvj3B8h0WoPE7C4cA1m29A_7bRDcJiL06D2T13r9zh17W7UYucDtTcJF7dtKHJTFK_C9m6wW-rHhXi1CgTFU8acDLYGK_VhZhQmTD7tM5JX7IEw_yokWzqyZzWFHmN4mgvAn3imeOXliVLY2YxD7I8-6xAgez6tVyX6plXIpE4KL-GLnFXyqORwIhH4F4EvEm6AcurW8pPWBXXVOY8Ml25-3D1tSu6sQ4PFzgvE5FWiwkBUpLSKwBjZqfg3_aG3NQe4exExztofsCD1l12US7OTx76h7utifDiu_FuzSZHOq0sM0kWfsrzoaPW79T7CT0Ew97HqEJTvYvhkdmzgtA-57zYK-8kc2bUTmTNdl_nUovO-xRhvwamIjMTzgqo3FXjLAtj4QZYWIHInkGj8GIxLluow315yWxARpfTehrpgvwYbd-tJ0UFyCZ1J0RwXQ8QmBu7UV-qPxj88d8cuY9sn8xba3kFCLifxlohEOupJcDDNHjta5eunNYoE127ap0Pv5KdJHWaOUcpScrXz3dIEXBlax12ySZNkghKGgGqYzOyQBKvkAgcV2rHaUQjuAkEbV3uQuE7iG3413fqfRVyAOKHKv3ig0jUM2DqBfhK9Tmxdbh-5VI5H5r5dgw3GmTQtSZVd0Q3mIMCeghrfHeCW4Ms1lRjcwEbn1Uyffs7KylhabOdqmiRTUPavLgKZmSrh7q0Vrkmb3s-nZEcfnVL6o2OpuQrdm83K-aI0Pvnsf9V9U_qoW1HWf61ENQUhnMECD2P70EsSmXLnQ_7f3v4Nyw-MCWCPpdzJvCh0TrpcTpY4WcflgbkNxm9xorCEiTlnEaeGSYj0MDcNm8sJYZbWzNQoNmbj58XS4IgnfCIYcoyu6PTceMcE7o_w50MPC3LcMTzZWKSYnGA7xDrvfeD7boqfj-Xd37SDYSTp9OAifiwiTXZyl7FqVTk1Y-1RCYTvIPPpnhXedT4ehYPRL9_fYmTgVISPLK8IQyNHpme86nG1-0FOJoitzwOa94MICeNKJArYvZ4Kj9WlP5-cTjP6zoDlaYxXXuln6DRmOnqL5CDVqf3f-7Dg-n8ARgNFwaAuvLXhCxuuRdcnNN5gx1z5vnvusq2sMCZx-eRqaGQsRoAoWo1VsrW5bwPGHwZN9Ip97KeORMAV8ExDttxjS4DXO-nB5fVZ2KToAsglOjLfvoXi7ArwK4Du3u7N_kzERB8lVT25jOltMdhOISXCGzY-ORQr6WhS_fgM8s8wHJSAtEl2w5VaFku57kEgWmfmasDNz5O1iMlqKOzVGpd9qNUtWaqYDK9DIxaL-O1pQGbzzuCsq332tez68SMNdbjNaf5RS3MHgAKHmI0I2RaGdBcaXjlap3sEMANG7keCNYSrtU-vfoMfb708dt2Ux2dDktmtSMFwZyzbOnGOshGhxsW5O98Uo-I-PZLsHSj4ZJSD5yIayNiuf8bZ0_REJ-9I-5xdfyUDstO7xj4IRjwwnsF9Td8CUycBKxr4gsttwfOoo04LVLOg7mDbK1GtoLEP2e-nXBHsFsOObaW3bOTx7TZwQf5DLggHsEfqdArl1-MqhRllSJNFtBLV3T8bRIvDl-YCV_LYjvWqRvo0RsR3oxrrPGwHM5ROy0WdfHixv2t5voksrS40VJI-KVXqgvF4ixUTMCjpL_pKpBq3pVZEnsJc4yZgK-C-sz72NZNKFHZviJhcdPDuwd4dX7oiI9X2KbnRfoo67xMqTuQCryLeiF7FpFoBHIjH2OhMzk2HbJR5YK9Q8blsWHpAdy',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='web_search',
|
|
args={'query': 'weather: Mexico City, Mexico', 'type': 'search'},
|
|
tool_call_id='ws_00a60507bf41223d0068c9d31b6aec81a09d9e568afa7b59aa',
|
|
id='ws_00a60507bf41223d0068c9d31b6aec81a09d9e568afa7b59aa',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='web_search',
|
|
content={
|
|
'sources': [{'type': 'api', 'name': 'oai-weather'}],
|
|
'status': 'completed',
|
|
},
|
|
tool_call_id='ws_00a60507bf41223d0068c9d31b6aec81a09d9e568afa7b59aa',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_00a60507bf41223d0068c9d31c935881a0b835341209f6ac8b',
|
|
signature='gAAAAABoydMoKoctyyCO6gsPILkjEnvCX0VL-9Gqk9qAmNEdWKNRPvxRIBVCxX4hGZ4m5fZJmuSIjjrA-nU-cUj_XIsARJsJywo2ka8IDmGRF8m7lm5atgcSJQjytRVpIA6s7sz0Sw3iAKrjtQcbymz2sUViTiOn7OqUStKtW0h98UIubdU6d19hu3iDwNddCuAC4QDy8cg3qJhjq9QTtovoBwFpibBJ12ISJqoPLSs43YvWK26o-evCMfzVbkuqJ7Gqie14gZ0oQChxGj7-bopeml1MCaDAz0EUxD5EDfjSdgjB_JABqF13kTTFdAVJu8gY1WgjFt0m1CONQGlM2oQA7cywjU7NnGWSNOqZp_NSDeTBYsKykAmyJP_lTzIDhhG37GBW7PwvBwuUYbvPcMmsRR9FDXxcMeVcpZPmaDjXhRAkJ-Am48Xz676pYl5Sx732-Pv9w503O66ARt6jwQYB4ZW5GgJAnqoqugbmJoGfOV4TaF0glOfKB5XPNQx--_hARpmXuQX3M_Xg1zLa6n7xGmf9pv__Gnhk3V0OlEnTD5HPZzc13F2hKX1PZ8E4ykq4843ZHDV3vpc5WsNCp6C6Cq8STXq58_QAU8P9vpqEP8khnYt3EJTjzbweiqVrMj6cSoUS9C32z8dFcA0rQrTmt_tEMTaoTN1Q5nTboSm0jX1arXqGh3RhcDkqddBDLfI6PdTVulEPVnBkmZJmCFqdfm_aD9FCSCVJdKE5pktBFqtmGFRJ6RVeGbc_YB6XG9najhjXNhhXIpy176CIPLZbeXkxcgsJQBdDGm4PpUePHZAGKxOpFCNv7kZMyGcsd-Ye-envhfdGhJ5dMOqRq-1KtjopdvNFfmxASkrT8f33YFj6n07fXOOfY02pTl9Dyv7fp0gk_3DR6zKFZRwv-Y3u0sTjQTkk7xTZsuEb0iP_zpqMNcj834fq4FZFvmhJ_siVVOQUPMaP0OFJnYFTteQR8S8JXud4Er1jEZlVojHugyJ3K4yMoj5c16jIQLaFn1_Jk1G97LCO-WZjSxpDD5niEXmYEoC1cw5zweUE7MjkzG1cBU2Wgjw_K0zt0Ko9DxYMDDDS-ZphpCJFPKBiX7pDcpKDpkQnDkEpzIIyDQ3mEKoKvYAXLveKuhOnNnVpUVN28hvW5_QfhD3C1WEBTzz2-dfxLpiS_MHI9NVUZdIue_ThGAM8TFY9MqDrTfAMRMD_mdQHW8XE_QdxighLLuG56AqufuA4CutwifYdbMiAE_mWtApqG4U6dx8cMnmIxnN_lrerv3IQR9_rk6vgPG-MfyJ0drDmSaJGMKyBexYau6sCzyMZYzFO-YgPDa0Yz4DYwhjTnGqtoMSE94ciYiJWZV473WIcyvJ8lE2mQD735nf1OKk7FHsai2mmQzk6NHyyEvvltkTPN8ply0fqmxLksng1bKD43zkHjnP_wUU5uInfAPIGMtIXuwJJXUziMTFRcCawC0KcUUP1J9GK9nrIMeO2B-yM5GXwfvMq3TiI4VFHD9Dav18T5BufMsjIY6uOUuWKNHSOpSQ6VHoql3k7fh2NVGOWqq3juBo2P3BNwXpP6mPr_6diYK4ciukrh4MiUd3pkLZnaW_iv4XYoq0Wix4ENU4zI1kMj5ObFAQOEbeoqdC6u4I5MIOXU6Pep-kaFl6P3yb37Ce95GyPq6xx8q4G29DK6Rx9Qowha8x9BIphuSL01Z6snFTewQW9rqAP7GyEltkso456vXzay08wtzG0dGpxoCIc87mAhx7-ulTj1Wti0qekLhsavem7GPfNKqso4CPsiXMxtTBBoIHk0xAvXcpZcw33pY_71-SHpMafrMrkS-Rp2T6YztbX2u_Nx__O8NAD2V0T0l69gR4S0khT_z-rttSPuCfx0-C4_hz7mCjVPMlLGDzxahOxG25Z9LHst6NPvlfg0xxX5rQ80XAS9GtLJ5uKMEwMxoGCatV3VL2zT2M0SpNiZKLZpH2tHfm0j_2dFcsLWN0a9MAooVZQ1Rlnq_7r0QrAPqcca_Y1Q7Jlzx2dgiEylYfFzNlNU2JTtinZg25gq3A7WayuWE5iBV5dhPijkcgEQbDETKg0eRa584q_cd68Rlm7qYeID3pc8gAbZ4zdqz6SfcQqoZS_EN43Z4Mc-t_HKN-9BwgXFNfvzbLoNekhoCiTrcEUikzXjVKqTbcuczAtH-uie_bfQkwfljFn7J8t7A3SeP961mvpx7iE-yJ4HXTeFhJI2TlBm4JB3OKMCoJSFdEiHjx82bX7TEPvq9g940TgPaooWUD2mEJ_f9ByY84L4EywrGFhtj-DxA1igkbWnCgWlxEquBcvmkRHkbTylkJz6kyz-_-5EPUEJLHqGsDHgotxYWXsxCalzDktH_GivrkeTYqhy1SikEJw93-X5SPMLD7EdQUS_K3XIe9p4T9lpn__zs_tCqssrun7ZQEpY9ULoYiMn2ENU9rK4IYpDoV0beXs4Xa24nj3qgrzbuzbLeKKbm8Y8RxNStogi4E4pK_difBVb_1oTIxfPrLnAJibQ8H-Tb9v20L2Zd3RWXtKi46-XJizKe9r-_JI2HmZ4QM2JOaBhHdybeBrwnu1Z36WhPk4m7YyK8-0K-kIPd-mW_ZF29tHBVhLifqPOq7D3HkJbnBH--KJum-F3v5LLqmeBN-3LWv6bk9-jqQNum9pm2WHtUkOMvH3zw0h8yiBjK3Qov7XHAP9dKHKs3B1eVqiVFGNbuB3Ss07ZzXQrSxgNFP2z64-HtdLJdsSXu3BGc7BqFrnF1tUVeu-KDXKXxJ0SFYaxnLqThuQ4b8CUXYWd8fnhCbhu3OE9Pd2aKWr-4bj73DTDcHLnYmy53mgNKtItsJBfA7m5Dzf6WKREmictNl5nMUWWlEay0nvE6so39zkRlc7wihRthJTEMDbMUdARJw7o1F8JBUPY3cIJchDnq0ZiGkrCA-OyPx-rkxbrQq9usJoTT7XUZNVZ5u7mXH8dY6uY4opcJmV02W2eJms-VtTxgkXuh_HLz_VPmCRMGfACFMwigpShdnr_j3T70ixy80FLcY6ILu1EbuZeLeqo4L8Z5fznYZ1',
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content='Mexico City weather today (Tuesday, September 16, 2025): Cloudy. Current around 73°F; high near 74°F and low around 56°F. Showers return midweek. ',
|
|
id='msg_00a60507bf41223d0068c9d326034881a0bb60d6d5d39347bd',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(
|
|
input_tokens=9703,
|
|
cache_read_tokens=8576,
|
|
output_tokens=638,
|
|
details={'reasoning_tokens': 576},
|
|
),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 9, 16, 21, 13, 57, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_00a60507bf41223d0068c9d31574d881a090c232646860a771',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
def test_model_profile_strict_not_supported():
|
|
my_tool = ToolDefinition(
|
|
name='my_tool',
|
|
description='This is my tool',
|
|
parameters_json_schema={'type': 'object', 'title': 'Result', 'properties': {'spam': {'type': 'number'}}},
|
|
strict=True,
|
|
)
|
|
|
|
m = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(api_key='foobar'))
|
|
tool_param = m._map_tool_definition(my_tool) # type: ignore[reportPrivateUsage]
|
|
|
|
assert tool_param == snapshot(
|
|
{
|
|
'name': 'my_tool',
|
|
'parameters': {'type': 'object', 'title': 'Result', 'properties': {'spam': {'type': 'number'}}},
|
|
'type': 'function',
|
|
'description': 'This is my tool',
|
|
'strict': True,
|
|
}
|
|
)
|
|
|
|
# Some models don't support strict tool definitions
|
|
m = OpenAIResponsesModel(
|
|
'gpt-4o',
|
|
provider=OpenAIProvider(api_key='foobar'),
|
|
profile=merge_profile(
|
|
openai_model_profile('gpt-4o'), OpenAIModelProfile(openai_supports_strict_tool_definition=False)
|
|
),
|
|
)
|
|
tool_param = m._map_tool_definition(my_tool) # type: ignore[reportPrivateUsage]
|
|
|
|
assert tool_param == snapshot(
|
|
{
|
|
'name': 'my_tool',
|
|
'parameters': {'type': 'object', 'title': 'Result', 'properties': {'spam': {'type': 'number'}}},
|
|
'type': 'function',
|
|
'description': 'This is my tool',
|
|
'strict': False,
|
|
}
|
|
)
|
|
|
|
|
|
async def test_reasoning_model_with_temperature(allow_model_requests: None, openai_api_key: str):
|
|
m = OpenAIResponsesModel('o3-mini', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(m, model_settings=OpenAIResponsesModelSettings(temperature=0.5))
|
|
with pytest.warns(UserWarning, match='Sampling parameters.*temperature.*not supported when reasoning is enabled'):
|
|
result = await agent.run('What is the capital of Mexico?')
|
|
assert result.output == snapshot(
|
|
'The capital of Mexico is Mexico City. It serves as the political, cultural, and economic heart of the country and is one of the largest metropolitan areas in the world.'
|
|
)
|
|
|
|
|
|
async def test_gpt5_pro(allow_model_requests: None, openai_api_key: str):
|
|
m = OpenAIResponsesModel('gpt-5-pro', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(m)
|
|
result = await agent.run('What is the capital of Mexico?')
|
|
assert result.output == snapshot('Mexico City (Ciudad de México).')
|
|
|
|
|
|
async def test_tool_output(allow_model_requests: None, openai_api_key: str):
|
|
m = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key))
|
|
|
|
class CityLocation(BaseModel):
|
|
city: str
|
|
country: str
|
|
|
|
agent = Agent(m, output_type=ToolOutput(CityLocation))
|
|
|
|
@agent.tool_plain
|
|
async def get_user_country() -> str:
|
|
return 'Mexico'
|
|
|
|
result = await agent.run('What is the largest city in the user country?')
|
|
assert result.output == snapshot(CityLocation(city='Mexico City', country='Mexico'))
|
|
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What is the largest city in the user country?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ToolCallPart(
|
|
tool_name='get_user_country',
|
|
args='{}',
|
|
tool_call_id=IsStr(),
|
|
id='fc_68477f0bb8e4819cba6d781e174d77f8001fd29e2d5573f7',
|
|
provider_name='openai',
|
|
)
|
|
],
|
|
usage=RequestUsage(input_tokens=62, output_tokens=12, details={'reasoning_tokens': 0}),
|
|
model_name='gpt-4o-2024-08-06',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 6, 10, 0, 40, 43, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68477f0b40a8819cb8d55594bc2c232a001fd29e2d5573f7',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelRequest(
|
|
parts=[
|
|
ToolReturnPart(
|
|
tool_name='get_user_country',
|
|
content='Mexico',
|
|
tool_call_id='call_ZWkVhdUjupo528U9dqgFeRkH',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ToolCallPart(
|
|
tool_name='final_result',
|
|
args='{"city":"Mexico City","country":"Mexico"}',
|
|
tool_call_id='call_iFBd0zULhSZRR908DfH73VwN',
|
|
id='fc_68477f0c91cc819e8024e7e633f0f09401dc81d4bc91f560',
|
|
provider_name='openai',
|
|
)
|
|
],
|
|
usage=RequestUsage(input_tokens=85, output_tokens=20, details={'reasoning_tokens': 0}),
|
|
model_name='gpt-4o-2024-08-06',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 6, 10, 0, 40, 44, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68477f0bfda8819ea65458cd7cc389b801dc81d4bc91f560',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelRequest(
|
|
parts=[
|
|
ToolReturnPart(
|
|
tool_name='final_result',
|
|
content='Final result processed.',
|
|
tool_call_id='call_iFBd0zULhSZRR908DfH73VwN',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_text_output_function(allow_model_requests: None, openai_api_key: str):
|
|
m = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key))
|
|
|
|
def upcase(text: str) -> str:
|
|
return text.upper()
|
|
|
|
agent = Agent(m, output_type=TextOutput(upcase))
|
|
|
|
@agent.tool_plain
|
|
async def get_user_country() -> str:
|
|
return 'Mexico'
|
|
|
|
result = await agent.run('What is the largest city in the user country?')
|
|
assert result.output == snapshot('THE LARGEST CITY IN MEXICO IS MEXICO CITY.')
|
|
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What is the largest city in the user country?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ToolCallPart(
|
|
tool_name='get_user_country',
|
|
args='{}',
|
|
tool_call_id='call_aTJhYjzmixZaVGqwl5gn2Ncr',
|
|
id='fc_68477f0dff5c819ea17a1ffbaea621e00356a60c98816d6a',
|
|
provider_name='openai',
|
|
)
|
|
],
|
|
usage=RequestUsage(input_tokens=36, output_tokens=12, details={'reasoning_tokens': 0}),
|
|
model_name='gpt-4o-2024-08-06',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 6, 10, 0, 40, 45, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68477f0d9494819ea4f123bba707c9ee0356a60c98816d6a',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelRequest(
|
|
parts=[
|
|
ToolReturnPart(
|
|
tool_name='get_user_country',
|
|
content='Mexico',
|
|
tool_call_id='call_aTJhYjzmixZaVGqwl5gn2Ncr',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
TextPart(
|
|
content='The largest city in Mexico is Mexico City.',
|
|
id='msg_68477f0ebf54819d88a44fa87aadaff503434b607c02582d',
|
|
provider_name='openai',
|
|
)
|
|
],
|
|
usage=RequestUsage(input_tokens=59, output_tokens=11, details={'reasoning_tokens': 0}),
|
|
model_name='gpt-4o-2024-08-06',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 6, 10, 0, 40, 46, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68477f0e2b28819d9c828ef4ee526d6a03434b607c02582d',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_native_output(allow_model_requests: None, openai_api_key: str):
|
|
m = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key))
|
|
|
|
class CityLocation(BaseModel):
|
|
"""A city and its country."""
|
|
|
|
city: str
|
|
country: str
|
|
|
|
agent = Agent(m, output_type=NativeOutput(CityLocation))
|
|
|
|
@agent.tool_plain
|
|
async def get_user_country() -> str:
|
|
return 'Mexico'
|
|
|
|
result = await agent.run('What is the largest city in the user country?')
|
|
assert result.output == snapshot(CityLocation(city='Mexico City', country='Mexico'))
|
|
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What is the largest city in the user country?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ToolCallPart(
|
|
tool_name='get_user_country',
|
|
args='{}',
|
|
tool_call_id=IsStr(),
|
|
id='fc_68477f0fa7c081a19a525f7c6f180f310b8591d9001d2329',
|
|
provider_name='openai',
|
|
)
|
|
],
|
|
usage=RequestUsage(input_tokens=66, output_tokens=12, details={'reasoning_tokens': 0}),
|
|
model_name='gpt-4o-2024-08-06',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 6, 10, 0, 40, 47, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68477f0f220081a1a621d6bcdc7f31a50b8591d9001d2329',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelRequest(
|
|
parts=[
|
|
ToolReturnPart(
|
|
tool_name='get_user_country',
|
|
content='Mexico',
|
|
tool_call_id='call_tTAThu8l2S9hNky2krdwijGP',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
TextPart(
|
|
content='{"city":"Mexico City","country":"Mexico"}',
|
|
id='msg_68477f10846c81929f1e833b0785e6f3020197534e39cc1f',
|
|
provider_name='openai',
|
|
)
|
|
],
|
|
usage=RequestUsage(input_tokens=89, output_tokens=16, details={'reasoning_tokens': 0}),
|
|
model_name='gpt-4o-2024-08-06',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 6, 10, 0, 40, 47, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68477f0fde708192989000a62809c6e5020197534e39cc1f',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_native_output_multiple(allow_model_requests: None, openai_api_key: str):
|
|
m = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key))
|
|
|
|
class CityLocation(BaseModel):
|
|
city: str
|
|
country: str
|
|
|
|
class CountryLanguage(BaseModel):
|
|
country: str
|
|
language: str
|
|
|
|
agent = Agent(m, output_type=NativeOutput([CityLocation, CountryLanguage]))
|
|
|
|
@agent.tool_plain
|
|
async def get_user_country() -> str:
|
|
return 'Mexico'
|
|
|
|
result = await agent.run('What is the largest city in the user country?')
|
|
assert result.output == snapshot(CityLocation(city='Mexico City', country='Mexico'))
|
|
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What is the largest city in the user country?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ToolCallPart(
|
|
tool_name='get_user_country',
|
|
args='{}',
|
|
tool_call_id=IsStr(),
|
|
id='fc_68477f1168a081a3981e847cd94275080dd57d732903c563',
|
|
provider_name='openai',
|
|
)
|
|
],
|
|
usage=RequestUsage(input_tokens=153, output_tokens=12, details={'reasoning_tokens': 0}),
|
|
model_name='gpt-4o-2024-08-06',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 6, 10, 0, 40, 48, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68477f10f2d081a39b3438f413b3bafc0dd57d732903c563',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelRequest(
|
|
parts=[
|
|
ToolReturnPart(
|
|
tool_name='get_user_country',
|
|
content='Mexico',
|
|
tool_call_id='call_UaLahjOtaM2tTyYZLxTCbOaP',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
TextPart(
|
|
content='{"result":{"kind":"CityLocation","data":{"city":"Mexico City","country":"Mexico"}}}',
|
|
id='msg_68477f1235b8819d898adc64709c7ebf061ad97e2eef7871',
|
|
provider_name='openai',
|
|
)
|
|
],
|
|
usage=RequestUsage(input_tokens=176, output_tokens=26, details={'reasoning_tokens': 0}),
|
|
model_name='gpt-4o-2024-08-06',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 6, 10, 0, 40, 49, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68477f119830819da162aa6e10552035061ad97e2eef7871',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_prompted_output(allow_model_requests: None, openai_api_key: str):
|
|
m = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key))
|
|
|
|
class CityLocation(BaseModel):
|
|
city: str
|
|
country: str
|
|
|
|
agent = Agent(m, output_type=PromptedOutput(CityLocation))
|
|
|
|
@agent.tool_plain
|
|
async def get_user_country() -> str:
|
|
return 'Mexico'
|
|
|
|
result = await agent.run('What is the largest city in the user country?')
|
|
assert result.output == snapshot(CityLocation(city='Mexico City', country='Mexico'))
|
|
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What is the largest city in the user country?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ToolCallPart(
|
|
tool_name='get_user_country',
|
|
args='{}',
|
|
tool_call_id=IsStr(),
|
|
id='fc_68482f1b0ff081a1b37b9170ee740d1e02f8ef7f2fb42b50',
|
|
provider_name='openai',
|
|
)
|
|
],
|
|
usage=RequestUsage(input_tokens=107, output_tokens=12, details={'reasoning_tokens': 0}),
|
|
model_name='gpt-4o-2024-08-06',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 6, 10, 13, 11, 46, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68482f12d63881a1830201ed101ecfbf02f8ef7f2fb42b50',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelRequest(
|
|
parts=[
|
|
ToolReturnPart(
|
|
tool_name='get_user_country',
|
|
content='Mexico',
|
|
tool_call_id='call_FrlL4M0CbAy8Dhv4VqF1Shom',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
TextPart(
|
|
content='{"city":"Mexico City","country":"Mexico"}',
|
|
id='msg_68482f1c159081918a2405f458009a6a044fdb7d019d4115',
|
|
provider_name='openai',
|
|
)
|
|
],
|
|
usage=RequestUsage(input_tokens=130, output_tokens=12, details={'reasoning_tokens': 0}),
|
|
model_name='gpt-4o-2024-08-06',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 6, 10, 13, 11, 55, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68482f1b556081918d64c9088a470bf0044fdb7d019d4115',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_prompted_output_multiple(allow_model_requests: None, openai_api_key: str):
|
|
m = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key))
|
|
|
|
class CityLocation(BaseModel):
|
|
city: str
|
|
country: str
|
|
|
|
class CountryLanguage(BaseModel):
|
|
country: str
|
|
language: str
|
|
|
|
agent = Agent(m, output_type=PromptedOutput([CityLocation, CountryLanguage]))
|
|
|
|
@agent.tool_plain
|
|
async def get_user_country() -> str:
|
|
return 'Mexico'
|
|
|
|
result = await agent.run('What is the largest city in the user country?')
|
|
assert result.output == snapshot(CityLocation(city='Mexico City', country='Mexico'))
|
|
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What is the largest city in the user country?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ToolCallPart(
|
|
tool_name='get_user_country',
|
|
args='{}',
|
|
tool_call_id=IsStr(),
|
|
id='fc_68482f2889d481a199caa61de7ccb62c08e79646fe74d5ee',
|
|
provider_name='openai',
|
|
)
|
|
],
|
|
usage=RequestUsage(input_tokens=283, output_tokens=12, details={'reasoning_tokens': 0}),
|
|
model_name='gpt-4o-2024-08-06',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 6, 10, 13, 11, 57, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68482f1d38e081a1ac828acda978aa6b08e79646fe74d5ee',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelRequest(
|
|
parts=[
|
|
ToolReturnPart(
|
|
tool_name='get_user_country',
|
|
content='Mexico',
|
|
tool_call_id='call_my4OyoVXRT0m7bLWmsxcaCQI',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
TextPart(
|
|
content='{"result":{"kind":"CityLocation","data":{"city":"Mexico City","country":"Mexico"}}}',
|
|
id='msg_68482f296bfc81a18665547d4008ab2c06b4ab2d00d03024',
|
|
provider_name='openai',
|
|
)
|
|
],
|
|
usage=RequestUsage(input_tokens=306, output_tokens=22, details={'reasoning_tokens': 0}),
|
|
model_name='gpt-4o-2024-08-06',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 6, 10, 13, 12, 8, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68482f28c1b081a1ae73cbbee012ee4906b4ab2d00d03024',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_verbosity(allow_model_requests: None, openai_api_key: str):
|
|
"""Test that verbosity setting is properly passed to the OpenAI API"""
|
|
# Following GPT-5 + verbosity documentation pattern
|
|
provider = OpenAIProvider(
|
|
api_key=openai_api_key,
|
|
base_url='https://api.openai.com/v1', # Explicitly set base URL
|
|
)
|
|
model = OpenAIResponsesModel('gpt-5', provider=provider)
|
|
agent = Agent(model=model, model_settings=OpenAIResponsesModelSettings(openai_text_verbosity='low'))
|
|
result = await agent.run('What is 2+2?')
|
|
assert result.output == snapshot('4')
|
|
|
|
|
|
async def test_openai_previous_response_id(allow_model_requests: None, openai_api_key: str):
|
|
"""Test if previous responses are detected via previous_response_id in settings"""
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model=model)
|
|
result = await agent.run('The secret key is sesame')
|
|
settings = OpenAIResponsesModelSettings(openai_previous_response_id=result.all_messages()[-1].provider_response_id) # type: ignore
|
|
result = await agent.run('What is the secret code?', model_settings=settings)
|
|
assert result.output == snapshot('sesame')
|
|
|
|
|
|
async def test_openai_previous_response_id_auto_mode(allow_model_requests: None, openai_api_key: str):
|
|
"""Test if invalid previous response id is ignored when history contains non-OpenAI responses"""
|
|
history = [
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='The first secret key is sesame',
|
|
),
|
|
],
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
TextPart(content='Open sesame! What would you like to unlock?'),
|
|
],
|
|
model_name='gpt-5',
|
|
provider_name='openai',
|
|
provider_response_id='resp_68b9bd97025c8195b443af591ca2345c08cb6072affe6099',
|
|
),
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='The second secret key is olives',
|
|
),
|
|
],
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
TextPart(content='Understood'),
|
|
],
|
|
model_name='gpt-5',
|
|
provider_name='openai',
|
|
provider_response_id='resp_68b9bda81f5c8197a5a51a20a9f4150a000497db2a4c777b',
|
|
),
|
|
]
|
|
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model=model)
|
|
settings = OpenAIResponsesModelSettings(openai_previous_response_id='auto')
|
|
result = await agent.run('what is the first secret key', message_history=history, model_settings=settings)
|
|
assert result.output == snapshot('sesame')
|
|
|
|
|
|
async def test_openai_previous_response_id_mixed_model_history(allow_model_requests: None, openai_api_key: str):
|
|
"""Test if invalid previous response id is ignored when history contains non-OpenAI responses"""
|
|
history = [
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='The first secret key is sesame',
|
|
),
|
|
],
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
TextPart(content='Open sesame! What would you like to unlock?'),
|
|
],
|
|
model_name='claude-sonnet-4-5',
|
|
provider_name='anthropic',
|
|
provider_response_id='msg_01XUQuedGz9gusk4xZm4gWJj',
|
|
),
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='what is the first secret key?',
|
|
),
|
|
],
|
|
),
|
|
]
|
|
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
previous_response_id, messages = model._get_previous_response_id_and_new_messages(history) # type: ignore
|
|
assert not previous_response_id
|
|
assert messages == snapshot(
|
|
[
|
|
ModelRequest(parts=[UserPromptPart(content='The first secret key is sesame', timestamp=IsDatetime())]),
|
|
ModelResponse(
|
|
parts=[TextPart(content='Open sesame! What would you like to unlock?')],
|
|
usage=RequestUsage(),
|
|
model_name='claude-sonnet-4-5',
|
|
timestamp=IsDatetime(),
|
|
provider_name='anthropic',
|
|
provider_response_id='msg_01XUQuedGz9gusk4xZm4gWJj',
|
|
),
|
|
ModelRequest(parts=[UserPromptPart(content='what is the first secret key?', timestamp=IsDatetime())]),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_previous_response_id_same_model_history(allow_model_requests: None, openai_api_key: str):
|
|
"""Test if message history is trimmed when model responses are from same model"""
|
|
history = [
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='The first secret key is sesame',
|
|
),
|
|
],
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
TextPart(content='Open sesame! What would you like to unlock?'),
|
|
],
|
|
model_name='gpt-5',
|
|
provider_name='openai',
|
|
provider_response_id='resp_68b9bd97025c8195b443af591ca2345c08cb6072affe6099',
|
|
),
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='The second secret key is olives',
|
|
),
|
|
],
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
TextPart(content='Understood'),
|
|
],
|
|
model_name='gpt-5',
|
|
provider_name='openai',
|
|
provider_response_id='resp_68b9bda81f5c8197a5a51a20a9f4150a000497db2a4c777b',
|
|
),
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='what is the first secret key?',
|
|
),
|
|
],
|
|
),
|
|
]
|
|
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
previous_response_id, messages = model._get_previous_response_id_and_new_messages(history) # type: ignore
|
|
assert previous_response_id == 'resp_68b9bda81f5c8197a5a51a20a9f4150a000497db2a4c777b'
|
|
assert messages == snapshot(
|
|
[
|
|
ModelRequest(parts=[UserPromptPart(content='what is the first secret key?', timestamp=IsDatetime())]),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_previous_response_id_concrete_seed_without_history(openai_api_key: str):
|
|
"""A concrete seed is used as-is when there is no prior response in the history."""
|
|
history = [ModelRequest(parts=[UserPromptPart(content='Continue')])]
|
|
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
previous_response_id, messages = model._resolve_previous_response_id('resp_seed_from_prior_turn', history) # type: ignore
|
|
assert previous_response_id == 'resp_seed_from_prior_turn'
|
|
assert messages is history
|
|
|
|
|
|
async def test_openai_previous_response_id_concrete_seed_overridden_by_history(openai_api_key: str):
|
|
"""When the history contains a same-provider response, it overrides the concrete seed.
|
|
|
|
Regression test for the retry/continuation bug in https://github.com/pydantic/pydantic-ai/issues/5113:
|
|
a static seed sent on every in-run request causes OpenAI to store duplicate copies of the
|
|
conversation state. The most recent `provider_response_id` must take precedence.
|
|
"""
|
|
history = [
|
|
ModelRequest(parts=[UserPromptPart(content='Q')]),
|
|
ModelResponse(
|
|
parts=[TextPart(content='A1')],
|
|
model_name='gpt-5',
|
|
provider_name='openai',
|
|
provider_response_id='resp_from_first_call',
|
|
),
|
|
ModelRequest(parts=[ToolReturnPart(tool_name='t', content='ok', tool_call_id='tc_1')]),
|
|
]
|
|
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
previous_response_id, messages = model._resolve_previous_response_id('resp_seed_from_prior_turn', history) # type: ignore
|
|
assert previous_response_id == 'resp_from_first_call'
|
|
assert messages == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[ToolReturnPart(tool_name='t', content='ok', tool_call_id='tc_1', timestamp=IsDatetime())]
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_previous_response_id_concrete_seed_with_mixed_provider_history(openai_api_key: str):
|
|
"""Responses from other providers don't override the concrete seed."""
|
|
history = [
|
|
ModelRequest(parts=[UserPromptPart(content='Q')]),
|
|
ModelResponse(
|
|
parts=[TextPart(content='A1')],
|
|
model_name='claude-sonnet-4-5',
|
|
provider_name='anthropic',
|
|
provider_response_id='msg_abc',
|
|
),
|
|
ModelRequest(parts=[UserPromptPart(content='Follow-up')]),
|
|
]
|
|
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
previous_response_id, messages = model._resolve_previous_response_id('resp_seed_from_prior_turn', history) # type: ignore
|
|
assert previous_response_id == 'resp_seed_from_prior_turn'
|
|
assert messages is history
|
|
|
|
|
|
async def test_openai_previous_response_id_concrete_seed_broken_by_compaction(openai_api_key: str):
|
|
"""A compaction in the tail is a hard chain boundary even with a concrete seed.
|
|
|
|
Crossing a compaction would re-inject the context that the compaction was meant
|
|
to replace, since `previous_response_id` loads the referenced response's full
|
|
input/output and the compaction summary is included in the new input.
|
|
"""
|
|
history = [
|
|
ModelResponse(
|
|
parts=[TextPart(content='compacted summary')],
|
|
model_name='gpt-4.1',
|
|
provider_name='openai',
|
|
provider_response_id='resp_compact',
|
|
provider_details={'compaction': True},
|
|
),
|
|
ModelRequest(parts=[UserPromptPart(content='continue after compaction')]),
|
|
]
|
|
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
previous_response_id, messages = model._resolve_previous_response_id('resp_seed_from_prior_turn', history) # type: ignore
|
|
assert previous_response_id is None
|
|
assert messages is history
|
|
|
|
|
|
async def test_openai_previous_response_id_auto_broken_by_compaction(openai_api_key: str):
|
|
"""`'auto'` also breaks the chain at compaction, matching the concrete-seed behavior."""
|
|
history = [
|
|
ModelResponse(
|
|
parts=[TextPart(content='compacted summary')],
|
|
model_name='gpt-4.1',
|
|
provider_name='openai',
|
|
provider_response_id='resp_compact',
|
|
provider_details={'compaction': True},
|
|
),
|
|
ModelRequest(parts=[UserPromptPart(content='continue after compaction')]),
|
|
]
|
|
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
previous_response_id, messages = model._resolve_previous_response_id('auto', history) # type: ignore
|
|
assert previous_response_id is None
|
|
assert messages is history
|
|
|
|
|
|
async def test_openai_previous_response_id_unset_never_chains(openai_api_key: str):
|
|
"""No opt-in means no auto-chaining, even when the history contains a chainable response."""
|
|
history = [
|
|
ModelRequest(parts=[UserPromptPart(content='Q')]),
|
|
ModelResponse(
|
|
parts=[TextPart(content='A1')],
|
|
model_name='gpt-5',
|
|
provider_name='openai',
|
|
provider_response_id='resp_from_first_call',
|
|
),
|
|
ModelRequest(parts=[UserPromptPart(content='Follow-up')]),
|
|
]
|
|
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
previous_response_id, messages = model._resolve_previous_response_id(None, history) # type: ignore
|
|
assert previous_response_id is None
|
|
assert messages is history
|
|
|
|
|
|
async def test_openai_previous_response_id_seed_auto_chains_through_retries(
|
|
allow_model_requests: None, openai_api_key: str
|
|
):
|
|
"""Regression test for https://github.com/pydantic/pydantic-ai/issues/5113.
|
|
|
|
A concrete seed from a prior turn must act as the starting point for the first
|
|
in-run request only. On subsequent in-run requests (tool-call continuations,
|
|
`ModelRetry` retries), auto-chaining takes over so we chain to the latest stored
|
|
response instead of re-sending the same static seed and duplicating stored state.
|
|
"""
|
|
model = OpenAIResponsesModel('gpt-4.1', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model=model, retries={'tools': 3, 'output': 3})
|
|
|
|
attempts: list[str] = []
|
|
|
|
@agent.tool_plain
|
|
def get_weather(city: str) -> str:
|
|
attempts.append(city)
|
|
if city != 'NYC':
|
|
raise ModelRetry(
|
|
'Location not recognized. The tool only supports the airport code "NYC". Call again with city="NYC".'
|
|
)
|
|
return 'Sunny, 72F'
|
|
|
|
# Turn 1 establishes a stored response we can seed from.
|
|
result1 = await agent.run('Say hi in one word, no punctuation.')
|
|
last = message(result1.all_messages(), ModelResponse, index=-1)
|
|
seed_response_id = last.provider_response_id
|
|
assert seed_response_id is not None
|
|
|
|
# Turn 2 uses the seed and forces a retry + tool-call continuation inside the run.
|
|
captured_previous_response_ids: list[str | None] = []
|
|
original_responses_create: Any = model._responses_create # pyright: ignore[reportPrivateUsage]
|
|
|
|
async def capture(*args: Any, **kwargs: Any) -> Any:
|
|
messages = args[0] if args else kwargs['messages']
|
|
settings = args[2] if len(args) >= 3 else kwargs['model_settings']
|
|
resolved, _ = model._resolve_previous_response_id( # pyright: ignore[reportPrivateUsage]
|
|
settings.get('openai_previous_response_id'), messages
|
|
)
|
|
captured_previous_response_ids.append(resolved)
|
|
return await original_responses_create(*args, **kwargs)
|
|
|
|
model._responses_create = capture # type: ignore[method-assign]
|
|
|
|
settings = OpenAIResponsesModelSettings(
|
|
openai_store=True,
|
|
openai_previous_response_id=seed_response_id,
|
|
)
|
|
result2 = await agent.run("What's the weather in New York?", model_settings=settings)
|
|
|
|
# Tool was called at least twice: first with wrong input, then correctly with "NYC".
|
|
assert len(attempts) >= 2
|
|
assert 'NYC' in attempts
|
|
# Final answer reports the weather returned by the tool.
|
|
assert 'Sunny' in result2.output or 'sunny' in result2.output
|
|
# At least 3 model requests: initial tool call, retry after ModelRetry, final answer.
|
|
assert len(captured_previous_response_ids) >= 3
|
|
# First request seeds from the prior turn.
|
|
assert captured_previous_response_ids[0] == seed_response_id
|
|
# Subsequent requests chain to the most recent stored response, never re-sending the static seed.
|
|
for resolved in captured_previous_response_ids[1:]:
|
|
assert resolved is not None
|
|
assert resolved != seed_response_id
|
|
# All subsequent previous_response_ids are distinct — each chains to the most recent prior response.
|
|
assert len(set(captured_previous_response_ids[1:])) == len(captured_previous_response_ids[1:])
|
|
# Snapshot the full trace so regressions in message trimming or retry structure surface here too.
|
|
assert result2.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[UserPromptPart(content="What's the weather in New York?", timestamp=IsDatetime())],
|
|
timestamp=IsDatetime(),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ToolCallPart(
|
|
tool_name='get_weather',
|
|
args=IsStr(),
|
|
tool_call_id=IsStr(),
|
|
id=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=IsInstance(RequestUsage),
|
|
model_name=IsStr(),
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={'finish_reason': IsStr(), 'timestamp': IsDatetime()},
|
|
provider_response_id=IsStr(),
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelRequest(
|
|
parts=[
|
|
RetryPromptPart(
|
|
content=IsStr(),
|
|
tool_name='get_weather',
|
|
tool_call_id=IsStr(),
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsDatetime(),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ToolCallPart(
|
|
tool_name='get_weather',
|
|
args='{"city":"NYC"}',
|
|
tool_call_id=IsStr(),
|
|
id=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=IsInstance(RequestUsage),
|
|
model_name=IsStr(),
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={'finish_reason': IsStr(), 'timestamp': IsDatetime()},
|
|
provider_response_id=IsStr(),
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelRequest(
|
|
parts=[
|
|
ToolReturnPart(
|
|
tool_name='get_weather',
|
|
content='Sunny, 72F',
|
|
tool_call_id=IsStr(),
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsDatetime(),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[TextPart(content=IsStr(), id=IsStr(), provider_name='openai')],
|
|
usage=IsInstance(RequestUsage),
|
|
model_name=IsStr(),
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={'finish_reason': IsStr(), 'timestamp': IsDatetime()},
|
|
provider_response_id=IsStr(),
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_conversation_id_explicit_and_auto(allow_model_requests: None, openai_api_key: str):
|
|
async with _openai_conversation(openai_api_key) as (async_client, conversation_id):
|
|
model = OpenAIResponsesModel('gpt-4.1', provider=OpenAIProvider(openai_client=async_client))
|
|
agent = Agent(
|
|
model=model,
|
|
instructions='Follow the user instructions exactly. When asked for a code, reply only with that code.',
|
|
)
|
|
result = await agent.run(
|
|
'Remember this exact code for later in this conversation: CONV-PAI-5222. Reply exactly: stored',
|
|
model_settings=OpenAIResponsesModelSettings(openai_conversation_id=conversation_id),
|
|
)
|
|
|
|
assert result.output == snapshot('stored')
|
|
response = message(result.all_messages(), ModelResponse, index=-1)
|
|
assert response.provider_details is not None
|
|
assert response.provider_details['conversation_id'] == conversation_id
|
|
|
|
result = await agent.run(
|
|
'What exact code did I ask you to remember? Reply with only the code.',
|
|
message_history=[response],
|
|
model_settings=OpenAIResponsesModelSettings(openai_conversation_id='auto'),
|
|
)
|
|
|
|
assert result.output == snapshot('CONV-PAI-5222')
|
|
response = message(result.all_messages(), ModelResponse, index=-1)
|
|
assert response.provider_details is not None
|
|
assert response.provider_details['conversation_id'] == conversation_id
|
|
|
|
|
|
async def test_openai_conversation_id_auto_respects_pydantic_ai_conversation_id(
|
|
allow_model_requests: None, openai_api_key: str
|
|
):
|
|
async with _openai_conversation(openai_api_key) as (async_client, conversation_id):
|
|
model = OpenAIResponsesModel('gpt-4.1', provider=OpenAIProvider(openai_client=async_client))
|
|
agent = Agent(model=model, instructions='Follow the user instructions exactly.')
|
|
|
|
result = await agent.run(
|
|
'Reply exactly: stored',
|
|
model_settings=OpenAIResponsesModelSettings(openai_conversation_id=conversation_id),
|
|
)
|
|
|
|
response = message(result.all_messages(), ModelResponse, index=-1)
|
|
assert response.provider_details is not None
|
|
assert response.provider_details['conversation_id'] == conversation_id
|
|
original_pydantic_ai_conversation_id = response.conversation_id
|
|
assert original_pydantic_ai_conversation_id is not None
|
|
|
|
forked = await agent.run(
|
|
'Reply exactly: forked',
|
|
message_history=result.all_messages(),
|
|
conversation_id='new',
|
|
model_settings=OpenAIResponsesModelSettings(openai_conversation_id='auto'),
|
|
)
|
|
|
|
assert forked.output == snapshot('forked')
|
|
request = message(forked.all_messages(), ModelRequest, index=-2)
|
|
assert request.conversation_id != original_pydantic_ai_conversation_id
|
|
response = message(forked.all_messages(), ModelResponse, index=-1)
|
|
assert response.provider_details is not None
|
|
assert 'conversation_id' not in response.provider_details
|
|
|
|
|
|
async def test_openai_conversation_id_preserves_mismatched_history(allow_model_requests: None, openai_api_key: str):
|
|
async with _openai_conversation(openai_api_key) as (async_client, conversation_id):
|
|
model = OpenAIResponsesModel('gpt-4.1', provider=OpenAIProvider(openai_client=async_client))
|
|
agent = Agent(
|
|
model=model,
|
|
instructions='Follow the user instructions exactly. When asked for a code, reply only with that code.',
|
|
)
|
|
history = [
|
|
ModelRequest(parts=[UserPromptPart(content='The local-only code is LOCAL-PAI-5222.')]),
|
|
ModelResponse(
|
|
parts=[TextPart(content='Understood.')],
|
|
model_name='claude-sonnet-4-5',
|
|
provider_name='anthropic',
|
|
provider_details={'conversation_id': conversation_id},
|
|
),
|
|
ModelResponse(
|
|
parts=[TextPart(content='Different OpenAI conversation acknowledged.')],
|
|
model_name='gpt-4.1',
|
|
provider_name='openai',
|
|
provider_details={'conversation_id': 'conv_different'},
|
|
),
|
|
]
|
|
|
|
result = await agent.run(
|
|
'What is the local-only code?',
|
|
message_history=history,
|
|
model_settings=OpenAIResponsesModelSettings(openai_conversation_id=conversation_id),
|
|
)
|
|
|
|
assert result.output == snapshot('LOCAL-PAI-5222')
|
|
response = message(result.all_messages(), ModelResponse, index=-1)
|
|
assert response.provider_details is not None
|
|
assert response.provider_details['conversation_id'] == conversation_id
|
|
|
|
|
|
async def test_openai_conversation_id_auto_without_history(allow_model_requests: None, openai_api_key: str):
|
|
model = OpenAIResponsesModel('gpt-4.1', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model=model, instructions='Follow the user instructions exactly.')
|
|
|
|
result = await agent.run(
|
|
'Reply exactly: no conversation',
|
|
model_settings=OpenAIResponsesModelSettings(openai_conversation_id='auto'),
|
|
)
|
|
|
|
assert result.output == snapshot('no conversation')
|
|
response = message(result.all_messages(), ModelResponse, index=-1)
|
|
assert response.provider_details is not None
|
|
assert 'conversation_id' not in response.provider_details
|
|
|
|
|
|
async def test_openai_conversation_id_tool_call_continuation(allow_model_requests: None, openai_api_key: str):
|
|
async with _openai_conversation(openai_api_key) as (async_client, conversation_id):
|
|
model = OpenAIResponsesModel('gpt-4.1', provider=OpenAIProvider(openai_client=async_client))
|
|
agent = Agent(
|
|
model=model,
|
|
instructions='Use the provided tool when the user asks for the conversation code.',
|
|
)
|
|
|
|
@agent.tool_plain
|
|
def get_conversation_code() -> str:
|
|
return 'TOOL-PAI-5222'
|
|
|
|
result = await agent.run(
|
|
'Call get_conversation_code and reply with only the returned code.',
|
|
model_settings=OpenAIResponsesModelSettings(openai_conversation_id=conversation_id),
|
|
)
|
|
|
|
assert result.output == snapshot('TOOL-PAI-5222')
|
|
response = message(result.all_messages(), ModelResponse, index=-1)
|
|
assert response.provider_details is not None
|
|
assert response.provider_details['conversation_id'] == conversation_id
|
|
|
|
|
|
async def test_openai_conversation_id_conflicts_with_previous_response_id(
|
|
allow_model_requests: None, openai_api_key: str
|
|
):
|
|
model = OpenAIResponsesModel('gpt-4.1', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model=model)
|
|
|
|
with pytest.raises(
|
|
UserError, match='`openai_previous_response_id` and `openai_conversation_id` cannot both be set'
|
|
):
|
|
await agent.run(
|
|
'Hello',
|
|
model_settings=OpenAIResponsesModelSettings(
|
|
openai_previous_response_id='auto',
|
|
openai_conversation_id='auto',
|
|
),
|
|
)
|
|
|
|
|
|
async def test_openai_conversation_id_streaming_provider_details(allow_model_requests: None, openai_api_key: str):
|
|
async with _openai_conversation(openai_api_key) as (async_client, conversation_id):
|
|
model = OpenAIResponsesModel('gpt-4.1', provider=OpenAIProvider(openai_client=async_client))
|
|
agent = Agent(
|
|
model=model,
|
|
instructions='Follow the user instructions exactly.',
|
|
)
|
|
|
|
async with agent.run_stream(
|
|
'Reply exactly: streamed',
|
|
model_settings=OpenAIResponsesModelSettings(openai_conversation_id=conversation_id),
|
|
) as result:
|
|
output = await result.get_output()
|
|
|
|
assert output == snapshot('streamed')
|
|
response = message(result.all_messages(), ModelResponse, index=-1)
|
|
assert response.provider_details is not None
|
|
assert response.provider_details['conversation_id'] == conversation_id
|
|
|
|
|
|
async def test_openai_responses_usage_without_tokens_details(allow_model_requests: None):
|
|
c = response_message(
|
|
[
|
|
ResponseOutputMessage(
|
|
id='123',
|
|
content=cast(list[Content], [ResponseOutputText(text='4', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
)
|
|
],
|
|
# Intentionally use model_construct so that input_tokens_details and output_tokens_details will not be set.
|
|
usage=ResponseUsage.model_construct(input_tokens=14, output_tokens=1, total_tokens=15),
|
|
)
|
|
mock_client = MockOpenAIResponses.create_mock(c)
|
|
model = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(openai_client=mock_client))
|
|
|
|
agent = Agent(model=model)
|
|
result = await agent.run('What is 2+2?')
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What is 2+2?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[TextPart(content='4', id='123', provider_name='openai')],
|
|
usage=RequestUsage(input_tokens=14, output_tokens=1, details={'reasoning_tokens': 0}),
|
|
model_name='gpt-4o-123',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1',
|
|
provider_details={'timestamp': datetime(2024, 1, 1, 0, 0, tzinfo=timezone.utc)},
|
|
provider_response_id='123',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
assert result.usage == snapshot(
|
|
RunUsage(input_tokens=14, output_tokens=1, details={'reasoning_tokens': 0}, requests=1)
|
|
)
|
|
|
|
|
|
async def test_openai_responses_model_thinking_part(allow_model_requests: None, openai_api_key: str):
|
|
m = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
settings = OpenAIResponsesModelSettings(openai_reasoning_effort='high', openai_reasoning_summary='detailed')
|
|
agent = Agent(m, model_settings=settings)
|
|
|
|
result = await agent.run('How do I cross the street?')
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[UserPromptPart(content='How do I cross the street?', timestamp=IsDatetime())],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42c90b950819c9e32c46d4f8326ca07460311b0c8d3de',
|
|
signature=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42c90b950819c9e32c46d4f8326ca07460311b0c8d3de',
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42c90b950819c9e32c46d4f8326ca07460311b0c8d3de',
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42c90b950819c9e32c46d4f8326ca07460311b0c8d3de',
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42c90b950819c9e32c46d4f8326ca07460311b0c8d3de',
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42c90b950819c9e32c46d4f8326ca07460311b0c8d3de',
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content=IsStr(),
|
|
id='msg_68c42cb1aaec819cb992bd92a8c7766007460311b0c8d3de',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(input_tokens=13, output_tokens=2199, details={'reasoning_tokens': 1920}),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 9, 12, 14, 22, 8, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68c42c902794819cb9335264c342f65407460311b0c8d3de',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
result = await agent.run(
|
|
'Considering the way to cross the street, analogously, how do I cross the river?',
|
|
message_history=result.all_messages(),
|
|
)
|
|
assert result.new_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='Considering the way to cross the street, analogously, how do I cross the river?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42cb43d3c819caf078978cc2514ea07460311b0c8d3de',
|
|
signature=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42cb43d3c819caf078978cc2514ea07460311b0c8d3de',
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42cb43d3c819caf078978cc2514ea07460311b0c8d3de',
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42cb43d3c819caf078978cc2514ea07460311b0c8d3de',
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42cb43d3c819caf078978cc2514ea07460311b0c8d3de',
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content=IsStr(),
|
|
id='msg_68c42cd36134819c800463490961f7df07460311b0c8d3de',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(input_tokens=314, output_tokens=2737, details={'reasoning_tokens': 2112}),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 9, 12, 14, 22, 43, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68c42cb3d520819c9d28b07036e9059507460311b0c8d3de',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_thinking_part_from_other_model(
|
|
allow_model_requests: None, anthropic_api_key: str, openai_api_key: str
|
|
):
|
|
m = AnthropicModel(
|
|
'claude-sonnet-4-6',
|
|
provider=AnthropicProvider(api_key=anthropic_api_key),
|
|
settings=AnthropicModelSettings(anthropic_thinking={'type': 'enabled', 'budget_tokens': 1024}),
|
|
)
|
|
agent = Agent(m)
|
|
|
|
result = await agent.run('How do I cross the street?')
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='How do I cross the street?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
signature=IsStr(),
|
|
provider_name='anthropic',
|
|
),
|
|
TextPart(content=IsStr()),
|
|
],
|
|
usage=RequestUsage(
|
|
input_tokens=42,
|
|
output_tokens=291,
|
|
details={
|
|
'cache_creation_input_tokens': 0,
|
|
'cache_read_input_tokens': 0,
|
|
'input_tokens': 42,
|
|
'output_tokens': 291,
|
|
},
|
|
),
|
|
model_name='claude-sonnet-4-6',
|
|
timestamp=IsDatetime(),
|
|
provider_name='anthropic',
|
|
provider_url='https://api.anthropic.com',
|
|
provider_details={'finish_reason': 'end_turn'},
|
|
provider_response_id='msg_0114iHK2ditgTf1N8FWomc4E',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
result = await agent.run(
|
|
'Considering the way to cross the street, analogously, how do I cross the river?',
|
|
model=OpenAIResponsesModel(
|
|
'gpt-5',
|
|
provider=OpenAIProvider(api_key=openai_api_key),
|
|
settings=OpenAIResponsesModelSettings(openai_reasoning_effort='high', openai_reasoning_summary='detailed'),
|
|
),
|
|
message_history=result.all_messages(),
|
|
)
|
|
assert result.new_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='Considering the way to cross the street, analogously, how do I cross the river?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42ce323d48193bcf88db6278980cf0ad492c7955fc6fc',
|
|
signature=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42ce323d48193bcf88db6278980cf0ad492c7955fc6fc',
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42ce323d48193bcf88db6278980cf0ad492c7955fc6fc',
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42ce323d48193bcf88db6278980cf0ad492c7955fc6fc',
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42ce323d48193bcf88db6278980cf0ad492c7955fc6fc',
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42ce323d48193bcf88db6278980cf0ad492c7955fc6fc',
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content=IsStr(),
|
|
id='msg_68c42d0b5e5c819385352dde1f447d910ad492c7955fc6fc',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(input_tokens=306, output_tokens=3134, details={'reasoning_tokens': 2496}),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 9, 12, 14, 23, 30, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68c42ce277ac8193ba08881bcefabaf70ad492c7955fc6fc',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_thinking_part_iter(allow_model_requests: None, openai_api_key: str):
|
|
provider = OpenAIProvider(api_key=openai_api_key)
|
|
responses_model = OpenAIResponsesModel('o3-mini', provider=provider)
|
|
settings = OpenAIResponsesModelSettings(openai_reasoning_effort='high', openai_reasoning_summary='detailed')
|
|
agent = Agent(responses_model, model_settings=settings)
|
|
|
|
async with agent.iter(user_prompt='How do I cross the street?') as agent_run:
|
|
async for node in agent_run:
|
|
if Agent.is_model_request_node(node) or Agent.is_call_tools_node(node):
|
|
async with node.stream(agent_run.ctx) as request_stream:
|
|
async for _ in request_stream:
|
|
pass
|
|
|
|
assert agent_run.result is not None
|
|
assert agent_run.result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='How do I cross the street?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42d1d0878819d8266007cd3d1402c08fbf9b1584184ff',
|
|
signature='gAAAAABoxC0m_QWpOlSt8wyPk_gtnjiI4mNLOryYlNXO-6rrVeIqBYDDAyMVg2_ldboZvfhW8baVbpki29gkTAyNygTr7L8gF1XK0hFovoa23ZYJKvuOnyLIJF-rXCsbDG7YdMYhi3bm82pMFVQxNK4r5muWCQcHmyJ2S1YtBoJtF_D1Ah7GpW2ACvJWsGikb3neAOnI-RsmUxCRu-cew7rVWfSj8jFKs8RGNQRvDaUzVniaMXJxVW9T5C7Ytzi852MF1PfVq0U-aNBzZBtAdwQcbn5KZtGkYLYTChmCi2hMrh5-lg9CgS8pqqY9-jv2EQvKHIumdv6oLiW8K59Zvo8zGxYoqT--osfjfS0vPZhTHiSX4qCkK30YNJrWHKJ95Hpe23fnPBL0nEQE5l6XdhsyY7TwMom016P3dgWwgP5AtWmQ30zeXDs=',
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42d1d0878819d8266007cd3d1402c08fbf9b1584184ff',
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42d1d0878819d8266007cd3d1402c08fbf9b1584184ff',
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42d1d0878819d8266007cd3d1402c08fbf9b1584184ff',
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content=IsStr(),
|
|
id='msg_68c42d26866c819da8d5c606621c911608fbf9b1584184ff',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(input_tokens=13, output_tokens=1680, details={'reasoning_tokens': 1408}),
|
|
model_name='o3-mini-2025-01-31',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 9, 12, 14, 24, 15, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68c42d0fb418819dbfa579f69406b49508fbf9b1584184ff',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_thinking_with_tool_calls(allow_model_requests: None, openai_api_key: str):
|
|
provider = OpenAIProvider(api_key=openai_api_key)
|
|
m = OpenAIResponsesModel(
|
|
model_name='gpt-5',
|
|
provider=provider,
|
|
settings=OpenAIResponsesModelSettings(openai_reasoning_summary='detailed', openai_reasoning_effort='low'),
|
|
)
|
|
agent = Agent(model=m)
|
|
|
|
@agent.instructions
|
|
def system_prompt():
|
|
return (
|
|
'You are a helpful assistant that uses planning. You MUST use the update_plan tool and continually '
|
|
"update it as you make progress against the user's prompt"
|
|
)
|
|
|
|
@agent.tool_plain
|
|
def update_plan(plan: str) -> str:
|
|
return 'plan updated'
|
|
|
|
prompt = (
|
|
'Compose a 12-line poem where the first letters of the odd-numbered lines form the name "SAMIRA" '
|
|
'and the first letters of the even-numbered lines spell out "DAWOOD." Additionally, the first letter '
|
|
'of each word in every line should create the capital of a country'
|
|
)
|
|
|
|
result = await agent.run(prompt)
|
|
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='Compose a 12-line poem where the first letters of the odd-numbered lines form the name "SAMIRA" and the first letters of the even-numbered lines spell out "DAWOOD." Additionally, the first letter of each word in every line should create the capital of a country',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
instructions="You are a helpful assistant that uses planning. You MUST use the update_plan tool and continually update it as you make progress against the user's prompt",
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42d29124881968e24c1ca8c1fc7860e8bc41441c948f6',
|
|
signature=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42d29124881968e24c1ca8c1fc7860e8bc41441c948f6',
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42d29124881968e24c1ca8c1fc7860e8bc41441c948f6',
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42d29124881968e24c1ca8c1fc7860e8bc41441c948f6',
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42d29124881968e24c1ca8c1fc7860e8bc41441c948f6',
|
|
provider_name='openai',
|
|
),
|
|
ToolCallPart(
|
|
tool_name='update_plan',
|
|
args=IsStr(),
|
|
tool_call_id='call_gL7JE6GDeGGsFubqO2XGytyO',
|
|
id='fc_68c42d3e9e4881968b15fbb8253f58540e8bc41441c948f6',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(input_tokens=124, output_tokens=1926, details={'reasoning_tokens': 1792}),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 9, 12, 14, 24, 40, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68c42d28772c819684459966ee2201ed0e8bc41441c948f6',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelRequest(
|
|
parts=[
|
|
ToolReturnPart(
|
|
tool_name='update_plan',
|
|
content='plan updated',
|
|
tool_call_id='call_gL7JE6GDeGGsFubqO2XGytyO',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
instructions="You are a helpful assistant that uses planning. You MUST use the update_plan tool and continually update it as you make progress against the user's prompt",
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
TextPart(
|
|
content=IsStr(),
|
|
id='msg_68c42d408eec8196ae1c5883e07c093e0e8bc41441c948f6',
|
|
provider_name='openai',
|
|
)
|
|
],
|
|
usage=RequestUsage(
|
|
input_tokens=2087, cache_read_tokens=2048, output_tokens=124, details={'reasoning_tokens': 0}
|
|
),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 9, 12, 14, 25, 3, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68c42d3fd6a08196bce23d6be960ff8a0e8bc41441c948f6',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_thinking_without_summary(allow_model_requests: None):
|
|
c = response_message(
|
|
[
|
|
ResponseReasoningItem(
|
|
id='rs_123',
|
|
summary=[],
|
|
type='reasoning',
|
|
encrypted_content='123',
|
|
),
|
|
ResponseOutputMessage(
|
|
id='msg_123',
|
|
content=cast(list[Content], [ResponseOutputText(text='4', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
),
|
|
],
|
|
)
|
|
mock_client = MockOpenAIResponses.create_mock(c)
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(openai_client=mock_client))
|
|
|
|
agent = Agent(model=model)
|
|
result = await agent.run('What is 2+2?')
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What is 2+2?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(content='', id='rs_123', signature='123', provider_name='openai'),
|
|
TextPart(content='4', id='msg_123', provider_name='openai'),
|
|
],
|
|
model_name='gpt-4o-123',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1',
|
|
provider_details={'timestamp': datetime(2024, 1, 1, 0, 0, tzinfo=timezone.utc)},
|
|
provider_response_id='123',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
_, openai_messages = await model._map_messages( # type: ignore[reportPrivateUsage]
|
|
result.all_messages(),
|
|
model_settings=cast(OpenAIResponsesModelSettings, model.settings or {}),
|
|
model_request_parameters=ModelRequestParameters(),
|
|
)
|
|
assert openai_messages == snapshot(
|
|
[
|
|
{'role': 'user', 'content': 'What is 2+2?'},
|
|
{'id': 'rs_123', 'summary': [], 'encrypted_content': '123', 'type': 'reasoning'},
|
|
{
|
|
'role': 'assistant',
|
|
'id': 'msg_123',
|
|
'content': [{'text': '4', 'type': 'output_text', 'annotations': []}],
|
|
'type': 'message',
|
|
'status': 'completed',
|
|
},
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_thinking_with_multiple_summaries(allow_model_requests: None):
|
|
c = response_message(
|
|
[
|
|
ResponseReasoningItem(
|
|
id='rs_123',
|
|
summary=[
|
|
Summary(text='1', type='summary_text'),
|
|
Summary(text='2', type='summary_text'),
|
|
Summary(text='3', type='summary_text'),
|
|
Summary(text='4', type='summary_text'),
|
|
],
|
|
type='reasoning',
|
|
encrypted_content='123',
|
|
),
|
|
ResponseOutputMessage(
|
|
id='msg_123',
|
|
content=cast(list[Content], [ResponseOutputText(text='4', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
),
|
|
],
|
|
)
|
|
mock_client = MockOpenAIResponses.create_mock(c)
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(openai_client=mock_client))
|
|
|
|
agent = Agent(model=model)
|
|
result = await agent.run('What is 2+2?')
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What is 2+2?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(content='1', id='rs_123', signature='123', provider_name='openai'),
|
|
ThinkingPart(content='2', id='rs_123', provider_name='openai'),
|
|
ThinkingPart(content='3', id='rs_123', provider_name='openai'),
|
|
ThinkingPart(content='4', id='rs_123', provider_name='openai'),
|
|
TextPart(content='4', id='msg_123', provider_name='openai'),
|
|
],
|
|
model_name='gpt-4o-123',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1',
|
|
provider_details={'timestamp': datetime(2024, 1, 1, 0, 0, tzinfo=timezone.utc)},
|
|
provider_response_id='123',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
_, openai_messages = await model._map_messages( # type: ignore[reportPrivateUsage]
|
|
result.all_messages(),
|
|
model_settings=cast(OpenAIResponsesModelSettings, model.settings or {}),
|
|
model_request_parameters=ModelRequestParameters(),
|
|
)
|
|
assert openai_messages == snapshot(
|
|
[
|
|
{'role': 'user', 'content': 'What is 2+2?'},
|
|
{
|
|
'id': 'rs_123',
|
|
'summary': [
|
|
{'text': '1', 'type': 'summary_text'},
|
|
{'text': '2', 'type': 'summary_text'},
|
|
{'text': '3', 'type': 'summary_text'},
|
|
{'text': '4', 'type': 'summary_text'},
|
|
],
|
|
'encrypted_content': '123',
|
|
'type': 'reasoning',
|
|
},
|
|
{
|
|
'role': 'assistant',
|
|
'id': 'msg_123',
|
|
'content': [{'text': '4', 'type': 'output_text', 'annotations': []}],
|
|
'type': 'message',
|
|
'status': 'completed',
|
|
},
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_thinking_with_modified_history(allow_model_requests: None, openai_api_key: str):
|
|
m = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
settings = OpenAIResponsesModelSettings(openai_reasoning_effort='low', openai_reasoning_summary='detailed')
|
|
agent = Agent(m, model_settings=settings)
|
|
|
|
result = await agent.run('What is the meaning of life?')
|
|
messages = result.all_messages()
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What is the meaning of life?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42de022c881948db7ed1cc2529f2e0202c9ad459e0d23',
|
|
signature=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content=IsStr(),
|
|
id='msg_68c42de31d348194a251b43ad913ef140202c9ad459e0d23',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(input_tokens=13, output_tokens=248, details={'reasoning_tokens': 64}),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 9, 12, 14, 27, 43, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68c42ddf9bbc8194aa7b97304dd909cb0202c9ad459e0d23',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
response = message(messages, ModelResponse, index=-1)
|
|
assert isinstance(response.parts, list)
|
|
response.parts[1] = TextPart(content='The meaning of life is 42')
|
|
|
|
with pytest.raises(
|
|
ModelHTTPError,
|
|
match=r"Item '.*' of type 'reasoning' was provided without its required following item\.",
|
|
):
|
|
await agent.run('Anything to add?', message_history=messages)
|
|
|
|
result = await agent.run(
|
|
'Anything to add?',
|
|
message_history=messages,
|
|
model_settings=OpenAIResponsesModelSettings(
|
|
openai_reasoning_effort='low',
|
|
openai_reasoning_summary='detailed',
|
|
openai_send_reasoning_ids=False,
|
|
),
|
|
)
|
|
assert result.new_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='Anything to add?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c42de4f63c819fb31b6019a4eaf67c051f82c608a83beb',
|
|
signature=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content=IsStr(),
|
|
id='msg_68c42de8a410819faf7a9cbebd2b4bc4051f82c608a83beb',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(input_tokens=142, output_tokens=355, details={'reasoning_tokens': 128}),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 9, 12, 14, 27, 48, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68c42de4afcc819f995a1c59fe87c9d5051f82c608a83beb',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_thinking_with_code_execution_tool(allow_model_requests: None, openai_api_key: str):
|
|
provider = OpenAIProvider(api_key=openai_api_key)
|
|
m = OpenAIResponsesModel(
|
|
model_name='gpt-5',
|
|
provider=provider,
|
|
settings=OpenAIResponsesModelSettings(
|
|
openai_reasoning_summary='detailed',
|
|
openai_reasoning_effort='low',
|
|
openai_include_code_execution_outputs=True,
|
|
),
|
|
)
|
|
agent = Agent(model=m, capabilities=[NativeTool(CodeExecutionTool())])
|
|
|
|
result = await agent.run(user_prompt='what is 65465-6544 * 65464-6+1.02255')
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='what is 65465-6544 * 65464-6+1.02255',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68cdba57390881a3b7ef1d2de5c8499709b7445677780c8f',
|
|
signature='gAAAAABozbpoKwjspVdWvC2skgCFSKx1Fiw9QGDrOxixFaC8O5gPVmC35FfE2jaedsn0zsHctrsl2LvPt7ELnOB3N20bvDGcDHkYzjSOLpf1jl2IAtQrkPWuLPOb6h8mIPL-Z1wNrngsmuoaKP0rrAcGwDwKzq8hxpLQbjvpRib-bbaVQ0SX7KHDpbOuEam3bIEiNSCNsA1Ot54R091vvwInnCCDMWVj-9u2fn7xtNzRGjHorkAt9mOhOBIVgZNZHnWb4RQ-PaYccgi44-gtwOK_2rhI9Qo0JiCBJ9PDdblms0EzBE7vfAWrCvnb_jKiEmKf2x9BBv3GMydsgnTCJdbBf6UVaMUnth1GvnDuJBdV12ecNT2LhOF2JNs3QjlbdDx661cnNoCDpNhXpdH3bL0Gncl7VApVY3iT2vRw4AJCU9U4xVdHeWb5GYz-sgkTgjbgEGg_RiU42taKsdm6B2gvc5_Pqf4g6WTdq-BNCwOjXQ4DatQBiJkgV5kyg4PqUqr35AD05wiSwz6reIsdnxDEqtWv4gBJWfGj4I96YqkL9YEuIBKORJ7ArZnjE5PSv6TIhqW-X9mmQTGkXl8emxpbdsNfow3QEd_l8rQEo4fHiFOGwU-uuPCikx7v6vDsE-w_fiZTFkM0X4iwFb6NXvOxKSdigfUgDfeCySwfmxtMx67QuoRA4xbfSHI9cctr-guZwMIIsMmKnTT-qGp-0F4UiyRQdgz2pF1bRUjkPml2rsleHQISztdSsiOGC2jozXNHwmf1b5z6KxymO8gvlImvLZ4tgseYpnAP8p_QZzMjIU7Y7Z2NQMDASr9hvv3tVjVCphqz1RH-h4gifjZJexwK9BR9O98u63X03f01NqgimS_dZHZUeC9voUb7_khNizA9-dS-fpYUduqvxZt-KZ7Q9gx7kFIH3wJvF-Gef55lwy4JNb8svu1wSna3EaQWTBeZOPHD3qbMXWVT5Yf5yrz7KvSemiWKqofYIInNaRLTtXLAOqq4VXP3dmgyEmAZIUfbh3IZtQ1uYwaV2hQoF-0YgM7JLPNDBwX8cRZtlyzFstnDsL_QLArf0bA8FMFNPuqPfyKFvXcGTgzquaUzngzNaoGo7k6kPHWLoSsWbvY3WvzYg4CO04sphuuSHh9TZRBy6LXCdxaMHIZDY_qVB1Cf-_dmDW6Eqr9_xodcTMBqs6RHlttLwFMMiul4aE_hUgNFlzOX7oVbisIS2Sm36GTuKE4zrbkvsA==',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='code_execution',
|
|
args={
|
|
'container_id': 'cntr_68cdba56addc81918f656db25fd0a6800d6da575ea4fee9b',
|
|
'code': """\
|
|
# compute the value
|
|
65465 - 6544 * 65464 - 6 + 1.02255
|
|
""",
|
|
},
|
|
tool_call_id='ci_68cdba5af39881a393a01eebb253854e09b7445677780c8f',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='code_execution',
|
|
content={'status': 'completed', 'logs': ['-428330955.97745']},
|
|
tool_call_id='ci_68cdba5af39881a393a01eebb253854e09b7445677780c8f',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68cdba63843881a3a9c585d83e4df9f309b7445677780c8f',
|
|
signature='gAAAAABozbpoJefk0Fp1xqQzY6ego00t7KnH2ohbIw-rR9ZgaEAQs3n0Fubka6xbgRxzb1og6Xup1BuT8hQKMS-NHFxYsYXw4b6KeSbCd5oySVO53bsITEVk0A6tgjGssDJc1xSct1ORo-nCNV24MCNZvL9MKFeGQHP-jRypOZ9Vhepje87kFWTpw9lP9j54fZJdRIBGA9G_goI9m1cPztFUufcUxtLsgorsM053oxh8yWiEccAbvBaGXRlPWSoZYktbKrWeBVwiRt2ul-jRV43Z3chB32bEM1l9sIWG1xnvLE3OY6HuAy5s3bB-bnk78dibx5yx_iA36zGOvRkfiF0okXZoYiMNzJz3U7rTSsKlYoMtCKgnYGFdrh0D8RPj4VtxnRr-zAMJSSZQCm7ZipNSMS0PpN1wri14KktSkIGZGLhPBJpzPf9AjzaBBi2ZcUM347BtOfEohPdLBn8R6Cz-WxmoA-jH9qsyO-bPzwtRkv28H5G6836IxU2a402Hl0ZQ0Q-kPb5iqhvNmyvEQr6sEY_FN6ogkxwS-UEdDs0QlvJmgGfOfhMpdxfi5hr-PtElPg7j5_OwA7pXtuEI8mADy2VEqicuZzIpo6d-P72-Wd8sapjo-bC3DLcJVudFF09bJA0UirrxwC-zJZlmOLZKG8OqXKBE4GLfiLn48bYa5FC8a_QznrX8iAV6qPoqyqXANXuBtBClmzTHQU5A3lUgwSgtJo6X_0wZqw0O4lQ1iQQrkt7ZLeT7Ef6QVLyh9ZVaMZqVGrmHbphZK5N1u8b4woZYJKe0J57SrNihO8Slu8jZ71dmXjB4NAPjm0ZN6pVaZNLUajSxolJfmkBuF1BCcMYMVJyvV7Kk9guTCtntLZjN4XVOJWRU8Db5BjL17ciWWHGPlQBMxMdYFZOinwCHLIRrtdVxz4Na2BODjl0-taYJHbKd-_5up5nysUPc4imgNawbN2mNwjhdc1Qv919Q9Cz-he9i3j6lKYnEkgJvKF2RDY6-XAI=',
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content="""\
|
|
Using standard order of operations (multiplication before addition/subtraction):
|
|
|
|
65465 - 6544 * 65464 - 6 + 1.02255 = -428,330,955.97745
|
|
|
|
If you intended different grouping with parentheses, let me know.\
|
|
""",
|
|
id='msg_68cdba6652ac81a3a58625883261465809b7445677780c8f',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(
|
|
input_tokens=1493, cache_read_tokens=1280, output_tokens=125, details={'reasoning_tokens': 64}
|
|
),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 9, 19, 20, 17, 21, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68cdba511c7081a389e67b16621029c609b7445677780c8f',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
messages = result.all_messages()
|
|
result = await agent.run(user_prompt='how about 2 to the power of 8?', message_history=messages)
|
|
assert result.new_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='how about 2 to the power of 8?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68cdba6c100481a394047de63f3e175009b7445677780c8f',
|
|
signature='gAAAAABozbpuOXVfjIYw7Gw6uSeadpkyaqMU1Frav7mTaf9LP8p8YuC8CWR9fYa02yZ5oYr1mqmYraD8ViOE33zqO2HBCdiWpOkVdNX-s4SGuPPB7ewyM7bDD4XbaSzo-Q5I6MgZmvVGWDGodqa3MfSKKNcGyD4aEfryQRLi4ObvHE5yuOqRo8FzGXMqe_pFdnvJXXD7njyfUofhWNvQPsLVLQFA_g_e7WKXtJJf_2JY183oi7-jNQ6rD9wGhM81HWSv0sTSBIHMpcE44rvlVQMFuh_rOPVUHUhT7vED7fYtrMoaPl46yDBc148T3MfXTnS-zm163zBOa34Yy_VXjyXw04a8Ig32y72bJY7-PRpZdBaeqD3BLvXfMuY4C911Z7FSxVze36mUxVO62g0uqV4PRw9qFA9mG37KF2j0ZsRzfyAClK1tu5omrYpenVKuRlrOO6JFtgyyE9OtLJxqvRNRKgULe2-cOQlo5S74t9lSMgcSGQFqF4JKG0A4XbzlliIcvC3puEzObHz-jArn_2BVUL_OPqx9ohJ9ZxAkXYgf0IRNYiKF4fOwKufYa5scL1kx2VAmsmEv5Yp5YcWlriB9L9Mpg3IguNBmq9DeJPiEQBtlnuOpSNEaNMTZQl4jTHVLgA5eRoCSbDdqGtQWgQB5wa7eH085HktejdxFeG7g-Fc1neHocRoGARxwhwcTT0U-re2ooJp99c0ujZtym-LiflSQUICi59VMAO8dNBE3CqXhG6S_ZicUmAvguo1iGKaKElMBv1Tv5qWcs41eAQkhRPBXQXoBD6MtBLBK1M-7jhidVrco0uTFhHBUTqx3jTGzE15YUJAwR69WvIOuZOvJdcBNObYWF9k84j0bZjJfRRbJG0C7XbU=',
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content='256', id='msg_68cdba6e02c881a3802ed88715e0be4709b7445677780c8f', provider_name='openai'
|
|
),
|
|
],
|
|
usage=RequestUsage(input_tokens=793, output_tokens=7, details={'reasoning_tokens': 0}),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 9, 19, 20, 17, 46, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68cdba6a610481a3b4533f345bea8a7b09b7445677780c8f',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_thinking_with_code_execution_tool_stream(
|
|
allow_model_requests: None, openai_api_key: str
|
|
):
|
|
provider = OpenAIProvider(api_key=openai_api_key)
|
|
m = OpenAIResponsesModel(
|
|
model_name='gpt-5',
|
|
provider=provider,
|
|
settings=OpenAIResponsesModelSettings(openai_reasoning_summary='detailed', openai_reasoning_effort='low'),
|
|
)
|
|
agent = Agent(model=m, capabilities=[NativeTool(CodeExecutionTool())])
|
|
|
|
event_parts: list[Any] = []
|
|
async with agent.iter(user_prompt="what's 123456 to the power of 123?") as agent_run:
|
|
async for node in agent_run:
|
|
if Agent.is_model_request_node(node) or Agent.is_call_tools_node(node):
|
|
async with node.stream(agent_run.ctx) as request_stream:
|
|
async for event in request_stream:
|
|
event_parts.append(event)
|
|
|
|
assert agent_run.result is not None
|
|
assert agent_run.result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content="what's 123456 to the power of 123?",
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c3509b2ee0819eba32735182d275ad0f2d670b80edc507',
|
|
signature=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='code_execution',
|
|
args='{"container_id":"cntr_68c3509aa0348191ad0bfefe24878dbb0deaa35a4e39052e","code":"n = pow(123456, 123)\\nlen(str(n))"}',
|
|
tool_call_id='ci_68c3509faff0819e96f6d45e6faf78490f2d670b80edc507',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='code_execution',
|
|
content={'status': 'completed'},
|
|
tool_call_id='ci_68c3509faff0819e96f6d45e6faf78490f2d670b80edc507',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='code_execution',
|
|
args='{"container_id":"cntr_68c3509aa0348191ad0bfefe24878dbb0deaa35a4e39052e","code":"str(n)[:100], str(n)[-100:]"}',
|
|
tool_call_id='ci_68c350a41d2c819ebb23bdfb9ff322770f2d670b80edc507',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='code_execution',
|
|
content={'status': 'completed'},
|
|
tool_call_id='ci_68c350a41d2c819ebb23bdfb9ff322770f2d670b80edc507',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='code_execution',
|
|
args='{"container_id":"cntr_68c3509aa0348191ad0bfefe24878dbb0deaa35a4e39052e","code":"n"}',
|
|
tool_call_id='ci_68c350a5e1f8819eb082eccb870199ec0f2d670b80edc507',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='code_execution',
|
|
content={'status': 'completed'},
|
|
tool_call_id='ci_68c350a5e1f8819eb082eccb870199ec0f2d670b80edc507',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content=IsStr(),
|
|
id='msg_68c350a75ddc819ea5406470460be7850f2d670b80edc507',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(
|
|
input_tokens=3727, cache_read_tokens=3200, output_tokens=347, details={'reasoning_tokens': 128}
|
|
),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 9, 11, 22, 43, 36, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68c35098e6fc819e80fb94b25b7d031b0f2d670b80edc507',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
assert event_parts == snapshot(
|
|
[
|
|
PartStartEvent(
|
|
index=0,
|
|
part=ThinkingPart(
|
|
content='', id='rs_68c3509b2ee0819eba32735182d275ad0f2d670b80edc507', provider_name='openai'
|
|
),
|
|
),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='**Calcul')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='ating')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' a')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' large')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' integer')),
|
|
PartDeltaEvent(
|
|
index=0,
|
|
delta=ThinkingPartDelta(
|
|
content_delta="""\
|
|
**
|
|
|
|
I\
|
|
"""
|
|
),
|
|
),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' need')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' to')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' compute')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' 123')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='456')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' raised')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' to')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' the')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' power')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' of')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' 123')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='.')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' That')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=IsStr())),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' an')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' enormous')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' integer')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=',')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' and')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' the')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' user')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' probably')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' wants')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' the')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' exact')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' value')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='.')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' I')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' can')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' use')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' Python')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta="'s")),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' ability')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' to')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' handle')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' big')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' integers')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=',')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' but')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' the')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' output')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' will')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' likely')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' be')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' extremely')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' long')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' —')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' potentially')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' hundreds')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' of')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' digits')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='.')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' I')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' should')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' consider')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' that')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' and')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' prepare')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' to')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' return')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' the')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' result')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' as')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' plain')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' text')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=',')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' even')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' if')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' it')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' ends')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' up')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' being')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' around')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' 627')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' digits')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='.')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' So')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=',')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' let')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=IsStr())),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' go')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' ahead')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' and')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' compute')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta=' that')),
|
|
PartDeltaEvent(index=0, delta=ThinkingPartDelta(content_delta='!')),
|
|
PartDeltaEvent(
|
|
index=0,
|
|
delta=ThinkingPartDelta(signature_delta=IsStr()),
|
|
),
|
|
PartEndEvent(
|
|
index=0,
|
|
part=ThinkingPart(
|
|
content=IsStr(),
|
|
id='rs_68c3509b2ee0819eba32735182d275ad0f2d670b80edc507',
|
|
signature='gAAAAABow1CfwMTF6GjgPzWVr8oKbF3qM2qnldMGM_sXMoJ2SSXHrcL4lsIK69rnKn43STNM_YZ3f5AcwxF4oThzCOPl1g9-u4GGFd5sISVWJYruCukTVDPaEEzdmJqCU1JMSIZvlvqo7b5PsUGyQU5ldX4KXDq8zs4NmRyLIJe-34SCmDG3BYVWR_O-CtcjH0tF9e3XnJ5T9TvxioDEGbASqXMKx5XB9P_b1ser8P9WIQk6hxZ8YX-FAmWSt-sad-zScdeTmyPcakDb7Z4NVcXmL_I-hoQYH_lu-HPFVwcXU8R7yeXU-7YF3vZBE84cmFuv25lftyojbdGq2A7uxGJZBPMCoUBDGBNG2_7mVvKyGz_ZZ6vXIO0GVDhHdW4Y012pkoDfLp6B-B9CGvANOH3ORlcbhB8aT9qN5bY773wW44JIxRU3umkmNzwF7lkbmuMCbGybHYSzqtkOrMIRgqxaXOx3bGbsreM4kGwgD3EXWqQ1PVye_K7gRkToVQpfpID5iuH4jJZDkvNjjJI09JR2yqlR6QkQayVg2x1y8VHXoMYjNdQdZeP62AguqYbgrlBRcjaUnw78KcWscQHaNsg0MfxL_5Q-pZR1OPVsFppHRTzrVK8458d05yEhDmun345oI9ScBrtXFRdHXPy0dQaayfjxM9H0grPrIogMw_zz4jAcFqWxE_C7GPMnNIJ_uEAhkPOetpNb-izd-iY4pGYKs8pmCB5czrAlKC1MXTnowrlWcwf5_kuD5SzWlzlWOoKWCeBDOZuKTDVJKXh_QCtQfftomQazDFCiCSgaQMuP7GaPcDuS1jdQoMQBcFfKuWoq-3eQBOCiEOAERH81zR4hz1x02T_910jGreSpfgxSqt4Td0pDDSmlEV6CwaUDQvrPc67d8_Wtx8YKv4eBH544_p1k9T8tHo3Q7xvgE37ZCdd_AVhC2ed1b5oUI95tM570HAVugFilcHJICa1RbFzIlRkNgI4k2JvsVWtD5_h3x6ZaEFTomwIXlochYgsegh8RJIRRCNKO9ebsvTrkdl8n1mb3hLrz7puwCkRFyUkxYBGT9zUjuKrjp_IjTvvov29v6pwYHg2Xd0nAfLP4WWWPBLNx3oV1-yOfXStRGHMZTB6iN9d0Bxi2QS7dk-rPPXml5HxrSo1TG06EdBXQ1VgrkWIxG1TF97-gK9oWWT9S5aaYKZAOdaqDvi7qO8I-4VwExtIq4Do3BHnWrgKNHfyuAobQK4H_CFMElYibJHwA9t-UGujMic07AxS-2XjXaCtjf7LnW_aXE2rQDqzHiTiLmTqT6jYHP0WHGSqFTOFkNmzqy6uVfU-TbdT91zDBeesc8XpzCXWBVKqxEzuQGdJrYk6ieZaxL76Kjs4jyo838LMJCXzhcF8enukz_llnoxAV59hTDAn0MUQvstGlDX0ToI7C8Oc0NZfZU5Pi4gs8u0He_Nw5UsoV7sA-jk4M45sFt6g3u00kJFP3gIcdvOzHcRK5z3Sfb9JF0bnvIYSbUFUidEJxSOAcRlxofOJPnkPtWCYiiv3zSVxZXX77-wtc8yrOYFzH1k_8P6CDpcfzOW7Yl1Tajgcm20nygmPlFtXF3RNFPztW1V5GwQHc99FvT4ZAex3fQ_UBDKyXnyGoySgpZbHQIvhzUhDEGm77EiYw5FoF6JgnHGGUCbfXr2EudtpbGW8MRHop2ytonb8Hq7w10yQSginBbH_w3bwtd7cwgDKcp6wIPotjpEC-N1YDsRqhPuqxVA==',
|
|
provider_name='openai',
|
|
),
|
|
next_part_kind='builtin-tool-call',
|
|
),
|
|
PartStartEvent(
|
|
index=1,
|
|
part=NativeToolCallPart(
|
|
tool_name='code_execution',
|
|
tool_call_id='ci_68c3509faff0819e96f6d45e6faf78490f2d670b80edc507',
|
|
provider_name='openai',
|
|
),
|
|
previous_part_kind='thinking',
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='{"container_id":"cntr_68c3509aa0348191ad0bfefe24878dbb0deaa35a4e39052e","code":"',
|
|
tool_call_id='ci_68c3509faff0819e96f6d45e6faf78490f2d670b80edc507',
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='n', tool_call_id='ci_68c3509faff0819e96f6d45e6faf78490f2d670b80edc507'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' =', tool_call_id='ci_68c3509faff0819e96f6d45e6faf78490f2d670b80edc507'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' pow', tool_call_id='ci_68c3509faff0819e96f6d45e6faf78490f2d670b80edc507'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='(', tool_call_id='ci_68c3509faff0819e96f6d45e6faf78490f2d670b80edc507'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='123', tool_call_id='ci_68c3509faff0819e96f6d45e6faf78490f2d670b80edc507'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='456', tool_call_id='ci_68c3509faff0819e96f6d45e6faf78490f2d670b80edc507'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=',', tool_call_id='ci_68c3509faff0819e96f6d45e6faf78490f2d670b80edc507'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' ', tool_call_id='ci_68c3509faff0819e96f6d45e6faf78490f2d670b80edc507'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='123', tool_call_id='ci_68c3509faff0819e96f6d45e6faf78490f2d670b80edc507'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=')\\n', tool_call_id='ci_68c3509faff0819e96f6d45e6faf78490f2d670b80edc507'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='len', tool_call_id='ci_68c3509faff0819e96f6d45e6faf78490f2d670b80edc507'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='(str', tool_call_id='ci_68c3509faff0819e96f6d45e6faf78490f2d670b80edc507'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='(n', tool_call_id='ci_68c3509faff0819e96f6d45e6faf78490f2d670b80edc507'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='))', tool_call_id='ci_68c3509faff0819e96f6d45e6faf78490f2d670b80edc507'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='"}', tool_call_id='ci_68c3509faff0819e96f6d45e6faf78490f2d670b80edc507'
|
|
),
|
|
),
|
|
PartEndEvent(
|
|
index=1,
|
|
part=NativeToolCallPart(
|
|
tool_name='code_execution',
|
|
args='{"container_id":"cntr_68c3509aa0348191ad0bfefe24878dbb0deaa35a4e39052e","code":"n = pow(123456, 123)\\nlen(str(n))"}',
|
|
tool_call_id='ci_68c3509faff0819e96f6d45e6faf78490f2d670b80edc507',
|
|
provider_name='openai',
|
|
),
|
|
next_part_kind='builtin-tool-return',
|
|
),
|
|
PartStartEvent(
|
|
index=2,
|
|
part=NativeToolReturnPart(
|
|
tool_name='code_execution',
|
|
content={'status': 'completed'},
|
|
tool_call_id='ci_68c3509faff0819e96f6d45e6faf78490f2d670b80edc507',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
previous_part_kind='builtin-tool-call',
|
|
),
|
|
PartStartEvent(
|
|
index=3,
|
|
part=NativeToolCallPart(
|
|
tool_name='code_execution',
|
|
tool_call_id='ci_68c350a41d2c819ebb23bdfb9ff322770f2d670b80edc507',
|
|
provider_name='openai',
|
|
),
|
|
previous_part_kind='builtin-tool-return',
|
|
),
|
|
PartDeltaEvent(
|
|
index=3,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='{"container_id":"cntr_68c3509aa0348191ad0bfefe24878dbb0deaa35a4e39052e","code":"',
|
|
tool_call_id='ci_68c350a41d2c819ebb23bdfb9ff322770f2d670b80edc507',
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=3,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='str', tool_call_id='ci_68c350a41d2c819ebb23bdfb9ff322770f2d670b80edc507'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=3,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='(n', tool_call_id='ci_68c350a41d2c819ebb23bdfb9ff322770f2d670b80edc507'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=3,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=')', tool_call_id='ci_68c350a41d2c819ebb23bdfb9ff322770f2d670b80edc507'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=3,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='[:', tool_call_id='ci_68c350a41d2c819ebb23bdfb9ff322770f2d670b80edc507'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=3,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='100', tool_call_id='ci_68c350a41d2c819ebb23bdfb9ff322770f2d670b80edc507'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=3,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='],', tool_call_id='ci_68c350a41d2c819ebb23bdfb9ff322770f2d670b80edc507'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=3,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' str', tool_call_id='ci_68c350a41d2c819ebb23bdfb9ff322770f2d670b80edc507'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=3,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='(n', tool_call_id='ci_68c350a41d2c819ebb23bdfb9ff322770f2d670b80edc507'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=3,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=')[', tool_call_id='ci_68c350a41d2c819ebb23bdfb9ff322770f2d670b80edc507'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=3,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='-', tool_call_id='ci_68c350a41d2c819ebb23bdfb9ff322770f2d670b80edc507'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=3,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='100', tool_call_id='ci_68c350a41d2c819ebb23bdfb9ff322770f2d670b80edc507'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=3,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=':]', tool_call_id='ci_68c350a41d2c819ebb23bdfb9ff322770f2d670b80edc507'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=3,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='"}', tool_call_id='ci_68c350a41d2c819ebb23bdfb9ff322770f2d670b80edc507'
|
|
),
|
|
),
|
|
PartEndEvent(
|
|
index=3,
|
|
part=NativeToolCallPart(
|
|
tool_name='code_execution',
|
|
args='{"container_id":"cntr_68c3509aa0348191ad0bfefe24878dbb0deaa35a4e39052e","code":"str(n)[:100], str(n)[-100:]"}',
|
|
tool_call_id='ci_68c350a41d2c819ebb23bdfb9ff322770f2d670b80edc507',
|
|
provider_name='openai',
|
|
),
|
|
next_part_kind='builtin-tool-return',
|
|
),
|
|
PartStartEvent(
|
|
index=4,
|
|
part=NativeToolReturnPart(
|
|
tool_name='code_execution',
|
|
content={'status': 'completed'},
|
|
tool_call_id='ci_68c350a41d2c819ebb23bdfb9ff322770f2d670b80edc507',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
previous_part_kind='builtin-tool-call',
|
|
),
|
|
PartStartEvent(
|
|
index=5,
|
|
part=NativeToolCallPart(
|
|
tool_name='code_execution',
|
|
tool_call_id='ci_68c350a5e1f8819eb082eccb870199ec0f2d670b80edc507',
|
|
provider_name='openai',
|
|
),
|
|
previous_part_kind='builtin-tool-return',
|
|
),
|
|
PartDeltaEvent(
|
|
index=5,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='{"container_id":"cntr_68c3509aa0348191ad0bfefe24878dbb0deaa35a4e39052e","code":"',
|
|
tool_call_id='ci_68c350a5e1f8819eb082eccb870199ec0f2d670b80edc507',
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=5,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='n', tool_call_id='ci_68c350a5e1f8819eb082eccb870199ec0f2d670b80edc507'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=5,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='"}', tool_call_id='ci_68c350a5e1f8819eb082eccb870199ec0f2d670b80edc507'
|
|
),
|
|
),
|
|
PartEndEvent(
|
|
index=5,
|
|
part=NativeToolCallPart(
|
|
tool_name='code_execution',
|
|
args='{"container_id":"cntr_68c3509aa0348191ad0bfefe24878dbb0deaa35a4e39052e","code":"n"}',
|
|
tool_call_id='ci_68c350a5e1f8819eb082eccb870199ec0f2d670b80edc507',
|
|
provider_name='openai',
|
|
),
|
|
next_part_kind='builtin-tool-return',
|
|
),
|
|
PartStartEvent(
|
|
index=6,
|
|
part=NativeToolReturnPart(
|
|
tool_name='code_execution',
|
|
content={'status': 'completed'},
|
|
tool_call_id='ci_68c350a5e1f8819eb082eccb870199ec0f2d670b80edc507',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
previous_part_kind='builtin-tool-call',
|
|
),
|
|
PartStartEvent(
|
|
index=7,
|
|
part=TextPart(
|
|
content='123', id='msg_68c350a75ddc819ea5406470460be7850f2d670b80edc507', provider_name='openai'
|
|
),
|
|
previous_part_kind='builtin-tool-return',
|
|
),
|
|
FinalResultEvent(tool_name=None, tool_call_id=None),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='456'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='^'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='123'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta=' equals'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta=':\n'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='180'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='302'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='106'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='304'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='044'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='807'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='508'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='140'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='927'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='865'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='938'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='572'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='807'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='342'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='688'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='638'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='559'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='680'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='488'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='440'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='159'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='857'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='958'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='502'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='360'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='813'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='732'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='502'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='197'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='826'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='969'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='863'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='225'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='730'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='871'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='630'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='436'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='419'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='794'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='758'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='932'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='074'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='350'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='380'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='367'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='697'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='649'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='814'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='626'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='542'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='926'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='602'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='664'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='707'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='275'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='874'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='269'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='201'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='777'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='743'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='912'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='313'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='197'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='516'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='323'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='690'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='221'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='274'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='713'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='845'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='895'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='457'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='748'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='735'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='309'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='484'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='337'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='191'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='373'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='255'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='527'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='928'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='271'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='785'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='206'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='382'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='967'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='998'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='984'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='330'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='482'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='105'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='350'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='942'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='229'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='970'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='677'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='054'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='940'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='838'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='210'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='936'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='952'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='303'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='939'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='401'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='656'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='756'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='127'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='607'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='778'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='599'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='667'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='243'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='702'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='814'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='072'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='746'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='219'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='431'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='942'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='293'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='005'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='416'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='411'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='635'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='076'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='021'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='296'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='045'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='493'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='305'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='133'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='645'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='615'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='566'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='590'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='735'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='965'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='652'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='587'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='934'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='290'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='425'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='473'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='827'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='719'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='935'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='012'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='870'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='093'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='575'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='987'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='789'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='431'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='818'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='047'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='013'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='404'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='691'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='795'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='773'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='170'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='405'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='764'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='614'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='646'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='054'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='949'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='298'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='846'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='184'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='678'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='296'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='813'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='625'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='595'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='333'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='311'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='611'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='385'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='251'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='735'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='244'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='505'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='448'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='443'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='050'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='050'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='547'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='161'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='779'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='229'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='749'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='134'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='489'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='643'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='622'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='579'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='100'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='908'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='331'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='839'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='817'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='426'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='366'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='854'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='332'),
|
|
),
|
|
PartDeltaEvent(
|
|
index=7,
|
|
delta=TextPartDelta(content_delta='416'),
|
|
),
|
|
PartEndEvent(
|
|
index=7,
|
|
part=TextPart(
|
|
content="""\
|
|
123456^123 equals:
|
|
180302106304044807508140927865938572807342688638559680488440159857958502360813732502197826969863225730871630436419794758932074350380367697649814626542926602664707275874269201777743912313197516323690221274713845895457748735309484337191373255527928271785206382967998984330482105350942229970677054940838210936952303939401656756127607778599667243702814072746219431942293005416411635076021296045493305133645615566590735965652587934290425473827719935012870093575987789431818047013404691795773170405764614646054949298846184678296813625595333311611385251735244505448443050050547161779229749134489643622579100908331839817426366854332416\
|
|
""",
|
|
id='msg_68c350a75ddc819ea5406470460be7850f2d670b80edc507',
|
|
provider_name='openai',
|
|
),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_streaming_usage(allow_model_requests: None, openai_api_key: str):
|
|
class Result(BaseModel):
|
|
result: int
|
|
|
|
agent = Agent(
|
|
model=OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key)),
|
|
model_settings=OpenAIResponsesModelSettings(
|
|
openai_reasoning_effort='low',
|
|
openai_service_tier='flex',
|
|
),
|
|
output_type=Result,
|
|
)
|
|
|
|
async with agent.iter('Calculate 100 * 200 / 3') as run:
|
|
async for node in run:
|
|
if Agent.is_model_request_node(node):
|
|
async with node.stream(run.ctx) as response_stream:
|
|
async for _ in response_stream:
|
|
pass
|
|
assert response_stream.response.usage == snapshot(
|
|
RequestUsage(input_tokens=53, output_tokens=469, details={'reasoning_tokens': 448})
|
|
)
|
|
assert response_stream.usage == snapshot(
|
|
RunUsage(input_tokens=53, output_tokens=469, details={'reasoning_tokens': 448}, requests=1)
|
|
)
|
|
assert run.usage == snapshot(RunUsage(requests=1))
|
|
assert run.usage == snapshot(
|
|
RunUsage(input_tokens=53, output_tokens=469, details={'reasoning_tokens': 448}, requests=1)
|
|
)
|
|
assert run.usage == snapshot(
|
|
RunUsage(input_tokens=53, output_tokens=469, details={'reasoning_tokens': 448}, requests=1)
|
|
)
|
|
|
|
|
|
async def test_openai_responses_non_reasoning_model_no_item_ids(allow_model_requests: None, openai_api_key: str):
|
|
model = OpenAIResponsesModel('gpt-4.1', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model)
|
|
|
|
@agent.tool_plain
|
|
def get_meaning_of_life() -> int:
|
|
return 42
|
|
|
|
result = await agent.run('What is the meaning of life?')
|
|
messages = result.all_messages()
|
|
assert messages == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What is the meaning of life?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ToolCallPart(
|
|
tool_name='get_meaning_of_life',
|
|
args='{}',
|
|
tool_call_id='call_3WCunBU7lCG1HHaLmnnRJn8I',
|
|
id='fc_68cc4fa649ac8195b0c6c239cd2c14470548824120ffcf74',
|
|
provider_name='openai',
|
|
)
|
|
],
|
|
usage=RequestUsage(input_tokens=36, output_tokens=15, details={'reasoning_tokens': 0}),
|
|
model_name='gpt-4.1-2025-04-14',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 9, 18, 18, 29, 57, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68cc4fa5603481958e2143685133fe530548824120ffcf74',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelRequest(
|
|
parts=[
|
|
ToolReturnPart(
|
|
tool_name='get_meaning_of_life',
|
|
content=42,
|
|
tool_call_id='call_3WCunBU7lCG1HHaLmnnRJn8I',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
TextPart(
|
|
content="""\
|
|
The meaning of life, according to popular culture and famously in Douglas Adams' "The Hitchhiker's Guide to the Galaxy," is 42!
|
|
|
|
If you're looking for a deeper or philosophical answer, let me know your perspective or context, and I can elaborate further.\
|
|
""",
|
|
id='msg_68cc4fa7693081a184ff6f32e5209ab00307c6d4d2ee5985',
|
|
provider_name='openai',
|
|
)
|
|
],
|
|
usage=RequestUsage(input_tokens=61, output_tokens=56, details={'reasoning_tokens': 0}),
|
|
model_name='gpt-4.1-2025-04-14',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 9, 18, 18, 29, 58, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68cc4fa6a8a881a187b0fe1603057bff0307c6d4d2ee5985',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
_, openai_messages = await model._map_messages( # type: ignore[reportPrivateUsage]
|
|
messages,
|
|
model_settings=cast(OpenAIResponsesModelSettings, model.settings or {}),
|
|
model_request_parameters=ModelRequestParameters(),
|
|
)
|
|
assert openai_messages == snapshot(
|
|
[
|
|
{'role': 'user', 'content': 'What is the meaning of life?'},
|
|
{
|
|
'name': 'get_meaning_of_life',
|
|
'arguments': '{}',
|
|
'call_id': 'call_3WCunBU7lCG1HHaLmnnRJn8I',
|
|
'type': 'function_call',
|
|
},
|
|
{'type': 'function_call_output', 'call_id': 'call_3WCunBU7lCG1HHaLmnnRJn8I', 'output': '42'},
|
|
{
|
|
'role': 'assistant',
|
|
'content': """\
|
|
The meaning of life, according to popular culture and famously in Douglas Adams' "The Hitchhiker's Guide to the Galaxy," is 42!
|
|
|
|
If you're looking for a deeper or philosophical answer, let me know your perspective or context, and I can elaborate further.\
|
|
""",
|
|
},
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_code_execution_return_image(allow_model_requests: None, openai_api_key: str):
|
|
model = OpenAIResponsesModel(
|
|
'gpt-5',
|
|
provider=OpenAIProvider(api_key=openai_api_key),
|
|
settings=OpenAIResponsesModelSettings(openai_include_code_execution_outputs=True),
|
|
)
|
|
|
|
agent = Agent(model=model, capabilities=[NativeTool(CodeExecutionTool())], output_type=BinaryImage)
|
|
|
|
result = await agent.run('Create a chart of y=x^2 for x=-5 to 5')
|
|
assert result.output == snapshot(IsInstance(BinaryImage))
|
|
messages = result.all_messages()
|
|
assert messages == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='Create a chart of y=x^2 for x=-5 to 5',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_68cdc38812288190889becf32c2934990187028ba77f15f7',
|
|
signature=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='code_execution',
|
|
args={
|
|
'container_id': 'cntr_68cdc387531c81938b4bee78c36acb820dbd09bdba403548',
|
|
'code': """\
|
|
import numpy as np\r
|
|
import matplotlib.pyplot as plt\r
|
|
\r
|
|
# Data\r
|
|
x = np.arange(-5, 6, 1)\r
|
|
y = x**2\r
|
|
\r
|
|
# Plot\r
|
|
plt.figure(figsize=(6, 4))\r
|
|
plt.plot(x, y, marker='o')\r
|
|
plt.title('y = x^2 for x = -5 to 5')\r
|
|
plt.xlabel('x')\r
|
|
plt.ylabel('y')\r
|
|
plt.grid(True, linestyle='--', alpha=0.6)\r
|
|
plt.xticks(x)\r
|
|
plt.tight_layout()\r
|
|
\r
|
|
# Save and show\r
|
|
plt.savefig('/mnt/data/y_equals_x_squared.png', dpi=200)\r
|
|
plt.show()\r
|
|
\r
|
|
'/mnt/data/y_equals_x_squared.png'\
|
|
""",
|
|
},
|
|
tool_call_id='ci_68cdc39029a481909399d54b0a3637a10187028ba77f15f7',
|
|
provider_name='openai',
|
|
),
|
|
FilePart(
|
|
content=IsInstance(BinaryImage),
|
|
id='ci_68cdc39029a481909399d54b0a3637a10187028ba77f15f7',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='code_execution',
|
|
content={'status': 'completed', 'logs': ["'/mnt/data/y_equals_x_squared.png'"]},
|
|
tool_call_id='ci_68cdc39029a481909399d54b0a3637a10187028ba77f15f7',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content=IsStr(),
|
|
id='msg_68cdc398d3bc8190bbcf78c0293a4ca60187028ba77f15f7',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(
|
|
input_tokens=2973, cache_read_tokens=1920, output_tokens=707, details={'reasoning_tokens': 512}
|
|
),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 9, 19, 20, 56, 34, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68cdc382bc98819083a5b47ec92e077b0187028ba77f15f7',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
result = await agent.run('Style it more futuristically.', message_history=messages)
|
|
assert result.output == snapshot(IsInstance(BinaryImage))
|
|
assert result.new_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='Style it more futuristically.',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_68cdc39f6aa48190b5aece25d55f80720187028ba77f15f7',
|
|
signature='gAAAAABozcPV8NxzVAMDdbpqK7_ltYa5_uAVsbnSW9OMWGRwlnwasaLvuaC4XlgGmC2MHbiPrccJ8zYuu0QoQm7jB6KgimG9Ax3vwoFGqMnfVjMAzoy_oJVadn0Odh3sKGifc11yVMmIkvrl0OcPYwJFlxlt2JhPkKotUDHY0P2LziSsMnQB_KaVdyYQxfcVbwrJJnB9wm2QbA3zNZogWepoXGrHXL1mBRR3J7DLdKGfMF_7gQC5fgEtb3G4Xhvk8_XNgCCZel48bqgzWvNUyaVPb4TpbibAuZnKnCNsFll6a9htGu9Ljol004p_aboehEyIp6zAm_1xyTDiJdcmfPfUiNgDLzWSKf-TwGFd-jRoJ3Aiw1_QY-xi1ozFu2oIeXb2oaZJL4h3ENrrMgYod3Wiprr99FfZw9IRN4ApagGJBnWYqW0O75d-e8jUMJS8zFJH0jtCl0jvuuGmM5vBAV4EpRLTcNGOZyoRpfqHwWfZYIi_u_ajs_A6NdqhzYvxYE-FAE1aJ89HxhnQNjRqkQFQnB8sYeoPOLBKIKAWYi3RziNE8klgSPC250QotupFaskTgPVkzbYe9ZtRZ9IHPeWdEHikb2RP-o1LVVO_zFMJdC6l4TwEToqRG8LaZOgSfkxS8eylTw7ROI2p8IBSmMkbkjvEkpmIic0FSx23Ew_Q-Y6DPa9isxGZcMMS0kOPKSPSML2MGoVq5L3-zIVj6ZBcFOMSaV5ytTlH-tKqBP9fejMyujwQFl5iXawuSjVjpnd2VL83o-xKbm6lEgsyXY1vynlS2hT52OYUY3MMvGSCeW5d7xwsVReO0O1EJqKS0lLh8thEMpJvar9dMgg-9ZCgZ1wGkJlpANf2moQlOWXKPXcbBa2kU0OW2WEffr4ecqg1QwPoMFLmR4HDL-KknuWjutF5bo8FW0CAWmxObxiHeDWIJYpS4KIIwp9DoLdJDWlg8FpD6WbBjKQN6xYmewHaTLWbZQw8zMGBcnhAkkyVopjrbM_6rvrH4ew05mPjPRrq9ODdHBqDYEn1kWj9MBDR-nhhLrci_6GImd64HZXYo0OufgcbxNu5mcAOsN3ww13ui8CTQVsPJO20XHc4jfwZ2Yr4iEIYLGdp0Xgv8EjIkJNA1xPeWn9COgCRrRSVLoF6qsgZwt9IRRGGEbH6kvznO_Y7BTTqufsORG6WNKc_8DDlrczoZVy0d6rI1zgqjXSeMuEP9LBG-bJKAvoAGDPXod8ShlqGX3Eb9CmBTZtTOJZYdgAlsZHx9BZ6zHlrJDjSDhc8xvdUAn9G3JvTI3b5JWSNX0eEerZ4c0FVqlpR-mSG201qnFghtoGHTLJhlIf9Ir8Daio_AYxUTRarQbcKnJuyKHPOz1u0PX2zS0xegO-IZhFbzNaB8qwQgeBiHfP-1dP9mkttqIRMt-hMt9NMHXoGIvFxgQ-xUVw7GRWx-ffKY7nPAbZD8kwVP3i4jTVj8phhwQcDy9UmbaPjm4LBgJkfdwNfSpm3g_ePK4aLa_l7iF2WSSfy2wObb7VatDzYDcNRG0ZTMGsiHy8yzZAcec18rG7uE6QCKx32G8NI5YvcN1kbnrZEuoKTBuSb2B_ZAhvED9HxbG8mH4ZEHHioVuH3_-b2TesVUAbORab_-rG9CU6qyy_eAqP54FYiXXSWtBWNo4baVdqCzgSCiNxgpxx64WPw8y2M1bOMoV6KPGwDOjcNwbO9nQwztqTWPW0Ot_Llf0HV0p-RPC1Uy8uBB5flhJ3p5uqxCPV3kDRzXgjh28EaBEkaSw_6SZkJNvwbD_7VihlHGaO89TwlqSIYUT_gc72NZKRrj4f-Y-0NwxjaSVVGuWCoeG-TMjG6uXpSozo2J47_x_a0lr4KCT8NDYlksajyuPUbYhC7jhQ9uJakmAc7ay_VHn_LYlAWRdAA7wYvqw7aYIuSIYg2OfL6NlggCpBnhsUPEXmMRHcfj1Ctc1aeUjBcpLFVmTZ82lB0FdcKRe3bBsKRckbdKalehoK0NJtrWqNQQH7xPrS-r7or_oOWhA4EDIkRUOG9eZhdsvTXBUamxGwutJ97SdDkgppVC4M7DMK2ZGGBzQsE-JMilERvFQ8JqwVWPxExWmE_-H2-bYe-T-CguCin-mTqhLYswHVtXjtruoHBmDs2SdnkD3intwSpqxsltscCfRaoRYWTCTbchCdbctSEIc39ECpc5tL1Gnav0bwSkMYkxyaRVBiYBbmIG9JftkKIYtdZ_Ddjmq8k29QflqrcigahsVLZPye3dxVTuviqbQjRd2SPMv8RxgSebgm5RZZIpP4WposryghYZFvuA1WImRzsImnAJI9J-8dv6IhHpHsWOw9K-Neg8GlnDU1mGHUElMUbqHiLojmXqPGfhBI3iSR0Ugs7ErpeRUrSk3il2o3rysG1Fn7ePuP5qNJUt2NyBUxf3TExMOwG_zqvpIPr2V_ARr3PsfeD0IcY83Bh428S8KPzc7ASOjT9dGQtVVrdjSxHi8o5ANxGx6z3bHC5dJvDCXg8a7FIJHAd5CUqJxrBi-K4p21jf1BNqgO5JAJO1JrvtdTk4GOVe8YEfhxmGWW9oeuRg8crsIWCCCoxr2XJKgPCj2TTPkBDZ1O3Yw3_nuWaBU5sB09uEB5lTKMd0OfSHbPF4c50RWAFgQB-tHjIUss3oEcAUaZHC77r6sIYoAEBlU8Dgly983fFD0HCqtpIpKS_B_K1fTXYpWRM3uUZpPKEgbfw1Kiqp5cweKTeRKNvjlau6VxhPyVi66xPdHUCC_BcX1eeFe-zcxe6fczcJWqGZGtYyVS_S_GlWZcdA6AHvGU6c4KjG0oU_9q-pdHSRtpnrhqFu2L884m64A_HsFU71Dj34AxhmXO1Am-zSL3j9nEPPUe6lJSGyhHU9k8ApDadWagvlODdXYWaWiMCXGXcYtl_iUAm24IJozlLJ1IW9HW6RoTfKrxwQwND3pX9CLNewuPV776pVtRjvUMbLaYg8nzOu1eNT2IW9dUdzc7wqOjiT1gHuVd6RzJyTCWJb9yPwDTkB_NKkjfUPmJ9Id924xtxy6H0eDYRq-SqsSSEklr6KJc88PV35QqvaMUW1dt_tGynHgYy9PXlWXQLKw-Xphku3FS_R4BLUhJbXDsMOQq332yhizP3qQ7vjEmPm8KB4DMIWBNn_D9xFuDuTCMNPAA9AGYWgC39-L4wPbpBHpqWjDwMzijFpm0CEViPD9ghyyV8syT1uLscxJVVDlBx90u_qWLSzMnFrVWmZ60OyWa9EqG44ZU8ELLHlEDRO_yHuTVpSafCLeDe5baOG2mI6tZnDBmm_ysbYdaC2N_zNBK9rhx7g7BNLQPevl0vtZm7GVLYXiVaO5ZinHxeTyJ6dRU5b0HmSw8r7EpdgORfjUuMkUfWPwhXgTU8SbvjTZg1gJowyNDYCvacrgnmnpBG9BgNjsfWlGTwz19AcEP_GjCWRWoE-uE_5fIyq5eFEefCBUKU0Ejs0IB-Re5h8bbdc6bNV3Tnx4UfGDU6FbQrJmPzrw5wp_wCeVYjtNGRbO2MKr_m52km5xMpVMMHtthVbQ9Zsa9F9zB6Dkr-R4F7o0dITMhG3qaREHKc8mXIGoHND-WSGPZLntB43JmRIWwjlJNstv7VlVc-dU89oh6Z1biH9B88SENI1ao2wMQV-BB17E6cmfzm1JsSR-HkzSf3yoUJWwvIu4CaR4jeMZohuoNqfGvQWIJSfyyUNzq5uY5__04QUmNcRVspOTH4EOHAoXLfCV3VI7fodj4FppiIuIXKwS3N03-Qt4sQ__XQWuyDdORvhRJeCvYcK5kkyOQILcABxDItxLmk8AgdT0Hz0BAo_u1U71srS-T8a8O0-fXWsJAHxDg_rJn0LUm6zq2vXNl8zmOKwEayyb0YySbMRxI-LwLyOXGRDyAVvm_7KKJu1HHqMntLyY2G1xowFpwMVLYXlGxDbsSpE-g5kFnHWhj13FiekLxaFgMRNsMA-r5_rWbEjRa6H328FKsUJcYe9qsp2LlzdJmYZDTIMgzxupFwQ-R5F6QjWOudMBsRszb4YqnOPJ8P9YnY2WYd0B7srb5Gh7T6r6mcCl-HAb2z9QDeXOc2Lu7ujuSvGj7_Gk7PkZH-LzoAEaGG9Z-7IVJlV_hOBPif3GlJUSUhTlIwWxn75gOyoOFuMak-rQqkb0SaL5anfXS_NUTVgSh5G5JQIoykLxbVlGiyeq0M_oEvTw2wMZcWT2hhaudcQ6L912pntcD-WF2tfppgp6sN5-cq-D8Y39N5Txvs-wo-H7-vYKPozTNUKCfnzgXfvt5fOi3RBR4MZU3eHT8OZ7d1d3otho_4GVMNIFa6mxjW1BC_J42Hn27-vrNDLZI_BXdF1t2CCq9VeRwxIW1R9vadd04HzAXyhap95BAYacmbULR6BkX97TvY3hv5cMiaQFkzxg-tf-nGC_VCknvwKxu4ocoB14p9w5TPSKcJz4J26XvyQbi6AdaXbOk625ajB_clv3VJvXYz7DgvWZd408tMykYQLMEyv5lnS7qwQokeM4ilIXwM7EugiakhfefTM9ZdxaWVcvQdqGerx98wlhifCSv0FqFRpJdkqgHmV1qzrAjPDEKT5HJOjsvs5hb7gKBqHR-bYlgS94pvDUpPArQXYcGYGum6vFsCAJypefMTF3D7Zhu4hhWQQv-DzSmfcZOxSeVJFrgVeqJnIbZPtd59HCBXNIRXJa42wUYE4szNli8wKWX0rYSIhiX-ig2YYZz3ZoBE1KDOpzheuk9OMYg7tQG2UlmVq27ggaKJ2gEGuVv-GI7uD7vKxPQ97QwCf38gWKU95CjMEBm_EvmLs9eubNpSpz8Yoek8hWWgrCXUSwRsYnF-lGdG0nIkCClvzqqAGOjyPxG4qfrCXJ-4rVc4DQiJUj71_I0EAhOgxb5WYBt4a7C1aUxC__qeOTAecof-UjzNlUPTo91JgOh5xvZkRkgGFNsq1OFqOcRrrKV8U8brizYkIhDjzjwCIzScSYvEfY4S6st-oJBv5fwTqwICSs59hf6WR8GXsPFR4v3UtF0Rkt-Nrek-X6V7BCui1M5HeFRN7lcTYs1Qw2bIwu4Td5PIkZ16oHdCk9u5pEZce-n_MIwj2Yoq_Lq1BBY9f1rpG9IuaycwabFnd2MOj89-xdgC197DAij5WjZjXahooyAl0Mt3p9MrHCit7LYbxqd_dGBOmg9YRfGPhsoZ17oAmHyg_gvpooOsu21T_06ynhvySjOG0yUcphquvtHJWqQdcT6BBX0X-kGE4nA41VdMhepLhDRDXtR4HJ1m_dPFpkHeAAFIefjt5Kb782TDLFE3KuHFWqSU2K2UmlY12P21dpRvyUNz8ss_AA3rl5jFpcnC2IyJNDIZbqdJPd2z0SNlwNyBq7Vl6poenR-j2X3xzIGlCDQ9zRgs50wdWtZ3ZRWLVWMrVkhkddoVKuh1W9rlwsvxmlZbOeRk_Uh0BymAa0-4-n0jI4_-O8jqpL-YzL1Y191brY4ywLUrQXpln41UK76pxc34FojI1Nymw523SNYxAHSlpj01gNmcjPrBTFxQ9SDY7AlrSFwJia_KvWnsZ53qt6fiDHV7p62KzlG_rpz_dQSQoj-z1hZBoUxi4nqzeCIzPcB_3JqeqD6x1O-Vh3uk-6NxN_qCE8cRsizB5vV-Ur-4tqau6LIrdfIB3Db12vpgiCmD_BD4xCxOijDn-97edRZw__xYfhx9_MBEB6gYl1ZBtLJfxDN54N5UION2tiZ2U8THD_h4d8-c26H7NQv44kYppbaseMckhpVOBDh52P5gxWFwp4VGqAIkZ7KU10qAD6M3GTFx7vGth8cT8YS1s2gPDW-WcVQGlAF94gT-FE6vzAjxwRJ4m7B1rJZfYReDvMrAoLroayOVmfB8pOKVQLQEF5dUmlzAIIpeh1NAiTg4n3FXW7OXQhzhU8bmo0e2FuSEOUVimGw9Nk_Wor3kQFp-9kj_iazSC4p5VURnyY_lAirPfyw4nskpZzCjSg_EAU8Au5vvOqrdDEPjrbeT8ks0wi2rsB0AxQxhgf6jUWzp0apeZOIl9dJFH_OnyJfvwrV4YHpee3174WKYhOJIOy2-8FJbMw1MpQtVV49yWmZsIyjRNj2uLbqY7jWBo2UEeOVW5n1tdk5zAVF-RFPKyh9150MnJz_RQtgoNdUD4iLBwlHYHVGLyH4a3GJmOJP6ZC-A-8RiUjvhu5co0yC8M83aVFjLe-yob3sNgJQgdVJnEOfPz4-1DVORoDgIRrRBcZQZqvkZwADFUkyy9jy5oXdEJ5XzthnizbrOZkHk6sQsNXrP4Uadqo9w99uy7TUh62l5AMWBFcaaQhuAuFkUZCavIqoO-2k4oXIDoTeBYzbyo_HH6caMk0D0_zgEg_5i-NhT3EUPdoCBNmjbOKmN2wzf6kqEyc8-nunjfq6HOjC6B6SE6VgOVJgBrhB4cBto4CxO45eqeuCi_WCjRtSS43Bh0QFZi6xK8rRjItyQRIfBpomETElbng3mAmBLPNb_7CzfsBdhBhJQLKu9KZ__uL3YVGtrCaLcOsfwP7BXRNQJH0yN_JWfMZH3y3B8z1O__xGhR63ugExWJZyUn55KAEiODbX35_PcftWXjslq-wzsK4J2fO_HFNU8Pi4egk6ibvCUDFRUelukaAy_YHdb0VTSB6XCymTo96jK0HGjG8FaVwvQaesaUE-e0_JpdMXN3KstKFeTlDUx1o3Ny93-VxLB5rkOSd6cRjEnFRA7Q6HnturEjwPAeJjR2Ll5dsisVrdjqHMbSfSObkpd2dZ0T3LP4-_ug7qRJF60DJTjTPpx7YxeARzuwiu02TlVW0J0PrdXT8EpISHneKc1VWhtRcdD0R0spuAMzJLwELaOemihL1TJSIMBqFikbpulZCZ1k1kA_5D7I5c7pOF1g4uYBW-gJNTenfC9wYmDJAOCcnwk1W4=',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='code_execution',
|
|
args={
|
|
'container_id': 'cntr_68cdc387531c81938b4bee78c36acb820dbd09bdba403548',
|
|
'code': """\
|
|
import numpy as np\r
|
|
import matplotlib.pyplot as plt\r
|
|
import matplotlib.patheffects as pe\r
|
|
\r
|
|
# Data\r
|
|
x_smooth = np.linspace(-5, 5, 501)\r
|
|
y_smooth = x_smooth**2\r
|
|
x_int = np.arange(-5, 6, 1)\r
|
|
y_int = x_int**2\r
|
|
\r
|
|
# Futuristic styling parameters\r
|
|
bg_color = '#0b0f14' # deep space blue-black\r
|
|
grid_color = '#00bcd4' # cyan\r
|
|
neon_cyan = '#00e5ff'\r
|
|
neon_magenta = '#ff2bd6'\r
|
|
accent = '#8a2be2' # electric purple\r
|
|
\r
|
|
plt.style.use('dark_background')\r
|
|
plt.rcParams.update({\r
|
|
'font.family': 'DejaVu Sans Mono',\r
|
|
'axes.edgecolor': neon_cyan,\r
|
|
'xtick.color': '#a7ffff',\r
|
|
'ytick.color': '#a7ffff',\r
|
|
'axes.labelcolor': '#a7ffff'\r
|
|
})\r
|
|
\r
|
|
fig, ax = plt.subplots(figsize=(8, 5), dpi=200)\r
|
|
fig.patch.set_facecolor(bg_color)\r
|
|
ax.set_facecolor(bg_color)\r
|
|
\r
|
|
# Neon glow effect: draw the curve multiple times with increasing linewidth and decreasing alpha\r
|
|
for lw, alpha in [(12, 0.06), (9, 0.09), (6, 0.14), (4, 0.22)]:\r
|
|
ax.plot(x_smooth, y_smooth, color=neon_cyan, linewidth=lw, alpha=alpha, solid_capstyle='round')\r
|
|
\r
|
|
# Main crisp curve\r
|
|
ax.plot(x_smooth, y_smooth, color=neon_cyan, linewidth=2.5)\r
|
|
\r
|
|
# Glowing integer markers\r
|
|
ax.scatter(x_int, y_int, s=220, color=neon_magenta, alpha=0.10, zorder=3)\r
|
|
ax.scatter(x_int, y_int, s=60, color=neon_magenta, edgecolor='white', linewidth=0.6, zorder=4)\r
|
|
\r
|
|
# Grid and spines\r
|
|
ax.grid(True, which='major', linestyle=':', linewidth=0.8, color=grid_color, alpha=0.25)\r
|
|
for spine in ax.spines.values():\r
|
|
spine.set_linewidth(1.2)\r
|
|
\r
|
|
# Labels and title with subtle glow\r
|
|
title_text = ax.set_title('y = x^2 • x ∈ [-5, 5]', fontsize=16, color=neon_cyan, pad=12)\r
|
|
title_text.set_path_effects([pe.withStroke(linewidth=3, foreground=accent, alpha=0.35)])\r
|
|
\r
|
|
ax.set_xlabel('x', fontsize=12)\r
|
|
ax.set_ylabel('y', fontsize=12)\r
|
|
\r
|
|
# Ticks\r
|
|
ax.set_xticks(x_int)\r
|
|
ax.set_yticks(range(0, 26, 5))\r
|
|
\r
|
|
# Subtle techy footer\r
|
|
footer = ax.text(0.98, -0.15, 'generated • neon-grid',\r
|
|
transform=ax.transAxes, ha='right', va='top',\r
|
|
color='#7fdfff', fontsize=9, alpha=0.6)\r
|
|
footer.set_path_effects([pe.withStroke(linewidth=2, foreground=bg_color, alpha=0.9)])\r
|
|
\r
|
|
plt.tight_layout()\r
|
|
\r
|
|
# Save and show\r
|
|
out_path = '/mnt/data/y_equals_x_squared_futuristic.png'\r
|
|
plt.savefig(out_path, facecolor=fig.get_facecolor(), dpi=200, bbox_inches='tight')\r
|
|
plt.show()\r
|
|
\r
|
|
out_path\
|
|
""",
|
|
},
|
|
tool_call_id='ci_68cdc3be6f3481908f64d8f0a71dc6bb0187028ba77f15f7',
|
|
provider_name='openai',
|
|
),
|
|
FilePart(
|
|
content=IsInstance(BinaryImage),
|
|
id='ci_68cdc3be6f3481908f64d8f0a71dc6bb0187028ba77f15f7',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='code_execution',
|
|
content={
|
|
'status': 'completed',
|
|
'logs': [
|
|
"""\
|
|
/tmp/ipykernel_11/962152713.py:40: UserWarning: You passed a edgecolor/edgecolors ('white') for an unfilled marker ('x'). Matplotlib is ignoring the edgecolor in favor of the facecolor. This behavior may change in the future.
|
|
ax.scatter(x_int, y_int, s=60, color=neon_magenta, edgecolor='white', linewidth=0.6, zorder=4)
|
|
""",
|
|
"'/mnt/data/y_equals_x_squared_futuristic.png'",
|
|
],
|
|
},
|
|
tool_call_id='ci_68cdc3be6f3481908f64d8f0a71dc6bb0187028ba77f15f7',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content="""\
|
|
I gave the chart a neon, futuristic look with a dark theme, glowing curve, and cyber-style markers and grid.
|
|
|
|
Download the image: [y_equals_x_squared_futuristic.png](sandbox:/mnt/data/y_equals_x_squared_futuristic.png)
|
|
|
|
If you want different colors or a holographic gradient background, tell me your preferred palette.\
|
|
""",
|
|
id='msg_68cdc3d0303c8190b2a86413acbedbe60187028ba77f15f7',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(
|
|
input_tokens=4614, cache_read_tokens=1792, output_tokens=1844, details={'reasoning_tokens': 1024}
|
|
),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 9, 19, 20, 57, 1, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68cdc39da72481909e0512fef9d646240187028ba77f15f7',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_code_execution_return_image_stream(allow_model_requests: None, openai_api_key: str):
|
|
model = OpenAIResponsesModel(
|
|
'gpt-5',
|
|
provider=OpenAIProvider(api_key=openai_api_key),
|
|
settings=OpenAIResponsesModelSettings(openai_include_code_execution_outputs=True),
|
|
)
|
|
|
|
agent = Agent(model=model, capabilities=[NativeTool(CodeExecutionTool())], output_type=BinaryImage)
|
|
|
|
event_parts: list[Any] = []
|
|
async with agent.iter(user_prompt='Create a chart of y=x^2 for x=-5 to 5') as agent_run:
|
|
async for node in agent_run:
|
|
if Agent.is_model_request_node(node) or Agent.is_call_tools_node(node):
|
|
async with node.stream(agent_run.ctx) as request_stream:
|
|
async for event in request_stream:
|
|
event_parts.append(event)
|
|
|
|
assert agent_run.result is not None
|
|
assert agent_run.result.output == snapshot(IsInstance(BinaryImage))
|
|
assert agent_run.result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='Create a chart of y=x^2 for x=-5 to 5',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_06c1a26fd89d07f20068dd936ae09c8197b90141e9bf8c36b1',
|
|
signature=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='code_execution',
|
|
args="{\"container_id\":\"cntr_68dd936a4cfc81908bdd4f2a2f542b5c0a0e691ad2bfd833\",\"code\":\"import numpy as np\\r\\nimport matplotlib.pyplot as plt\\r\\n\\r\\n# Data\\r\\nx = np.linspace(-5, 5, 1001)\\r\\ny = x**2\\r\\n\\r\\n# Plot\\r\\nfig, ax = plt.subplots(figsize=(6, 4))\\r\\nax.plot(x, y, label='y = x^2', color='#1f77b4')\\r\\nxi = np.arange(-5, 6)\\r\\nyi = xi**2\\r\\nax.scatter(xi, yi, color='#d62728', s=30, zorder=3, label='integer points')\\r\\n\\r\\nax.set_xlabel('x')\\r\\nax.set_ylabel('y')\\r\\nax.set_title('Parabola y = x^2 for x in [-5, 5]')\\r\\nax.grid(True, alpha=0.3)\\r\\nax.set_xlim(-5, 5)\\r\\nax.set_ylim(0, 26)\\r\\nax.legend()\\r\\n\\r\\nplt.tight_layout()\\r\\n\\r\\n# Save image\\r\\nout_path = '/mnt/data/y_eq_x_squared_plot.png'\\r\\nfig.savefig(out_path, dpi=200)\\r\\n\\r\\nout_path\"}",
|
|
tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7',
|
|
provider_name='openai',
|
|
),
|
|
FilePart(
|
|
content=IsInstance(BinaryImage),
|
|
id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='code_execution',
|
|
content={'status': 'completed', 'logs': ["'/mnt/data/y_eq_x_squared_plot.png'"]},
|
|
tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content=IsStr(),
|
|
id='msg_06c1a26fd89d07f20068dd937ecbd48197bd91dc501bd4a4d4',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(input_tokens=2772, output_tokens=1166, details={'reasoning_tokens': 896}),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 10, 1, 20, 47, 35, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_06c1a26fd89d07f20068dd9367869c819788cb28e6f19eff9b',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
assert event_parts == snapshot(
|
|
[
|
|
PartStartEvent(
|
|
index=0,
|
|
part=ThinkingPart(
|
|
content='',
|
|
id='rs_06c1a26fd89d07f20068dd936ae09c8197b90141e9bf8c36b1',
|
|
signature=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
),
|
|
PartEndEvent(
|
|
index=0,
|
|
part=ThinkingPart(
|
|
content='',
|
|
id='rs_06c1a26fd89d07f20068dd936ae09c8197b90141e9bf8c36b1',
|
|
signature='gAAAAABo3ZN28TIB2hESP9n7FpWJJ4vj1KEPIVHYTNh64J3S9rOSRfmmTK_uSNB79wwlv3ur6X9Yl9sPe6moHK4nud8jgeScuOeCDq70JGXZ6xH_NBdiDWzeMis1WIDsyJrADdADGQRhjb8sXi6lz3nNvjeqXD-oZJkxTJ9FeJsCNNPBHX-ZYRIYZ7vGKLPfmi5qNS7V6VVGvwEWOBwW75ptObu5E8g2TqhPlUzsVoZsIZiczRXq6zQpDtMPAtv6Mz8puaq-o65P5-vZMywmEjyi0Dd2M9ozUfhWfhpEhCsAiItesA802-TSBQCKeP62riRAMJvfD3PEGLYL9d_7mUvJYSsiOADU0K6wfI6y8bRL-UaWUvn60KfPvqfBFm9-hwP1NS77OKoZABIuGz5sc3BuAh6ebKrJkfNHq7W0BA09S2gt3wLPzflpVl-wJ74L9UGnaKpmG3XRFogff_SNgDhO0_Cb4-1PYJi2NpqnCwTG2c8EFxXiP4trdynbpgRD5hKDj65FU46cBjR0g00bCShqwsseAzw_lAxbawcjF0zmAyz68Km2jCRKHRGgeMpbT-YQWs04IizKYsWfF-8pXX2vwSqk3Kb51OysuPN0K3gihF9v2tPnK2qFzkvNics__CDabCmafEKQlLp6TDRc5RY4ZcSHNwUM_dybJStzoH6qed1GQNt05wBhDZg39N7pJ8_dG7wXCSGHY5CRORZm19UGTd9DoZMzr8JmtxmRgJoKCHW_gavpt4__zifPVxqLUWj6GBaQRT8pR_Tym27HcsC0GbHLR1nel9hC6RzydTU5y7LWY_NoGUE4WZX5rHe5t73lFNSMwd9-6i9Qlj60_rBZ5z9oTAl_Ksywgo68AG7dFdSeI3VLnOyzhqeePn0ywaMp3HqO-FIXW3fjqtM2XMMMMn2Cje5rZhJ9JNmMqnxpltITkVdHMo7Yr1WFTkwLByEOb3M4LCq5B3dM1s1pVmqWAc9YNjpB7Fbi6fG90EAYFNEM4ubOE7y2d5E4hco0MbEKg-Fh0ubh1I2Y1kthZFEmPQLm6fFaljJKPtYojEZZ2cZ7sN3UaVg8Zpf3A7WS9kM2--lL5LuBnVDebf8Xrzv9dTmJvOtwWzJsY4RxWdnzfl_ZokHmg_HDNbeZpHsVI0gqHGr7YTlFJ0NUXW9mzZMx9e_VTrrf34XwRue3xVCqzsspRMjMIlAoDp0Rp0L2tJWAbKs_btqVpqjz8p-64CzSRq65BmSP6i86G0cJ9WLSD3gL3wR-Zt2HyvUvecHVmgKhXgY3F-RchYRO7TarJgyZY5bP2EEpHUwSWx4uWjYfzXMGYn8gNwgwl89qog-inK88qSG0DbqJQPwYNuRjS7Mu01O6eV39Zu7Njsn2io-kPc5HLRrbbhN7qCSki8yPWE_7yPtbIKlwWKOlEYx8_SGgE7waBFRem7ElsE9wvCX5KknilmN5_d9L4Sos0oT5NHAhApvVVDcygz9VGYBAmWfMOynDnOiTIpsAdjHmuZG7GJNAtUEYx7U7pNqbD2FJMIeN0L-3uqhxisRzeX64JZkVHWYL8HjeC1zHiUMZXKW1KXIvIU2_BCtqay22FtBskeMXZAReKhv3eX2oQlWL2Ps9VOk2imzjqBbFLzJgDq0iFoaHdOXGqo54GYZIxfWi10uo65s-3gOGmqPPE02FHEMjK7VHFjMh91FPhh8TmpWjOfa9QEcpEHSZJ6ipUMTVfRHHHshB6Sb74x-Jfr6Ioq2RnWd3E32GpE3kd1poqOssBi5jCqsA86tIMt0m8p_CDu_ANvMNKTiGTQdejm2rUhccpdbp8uLBPnqWxyGOCTlREglHPeh2EzjEMbtIaFp2NhHE6UlJ_nw40CDa5PA7C4lgUkn-4KtPy6rSaMu0mWM4vPO-5ksdtB3E5PkCdIB8j7htbhZH_MTv9RL7loDNkRVlJRSBiAC_qCGgVPyP4l1w4imdey-_HuVCKBD2vaXUz2l2efn-jLSlhty5vBOR-kr0EsU02_NYZtOKgBR1zIslAlnhM8lTxJWH4osSXHa4fIx9O9tyALjvxhooYww_Die_8iCH4u5cF53z3mvoK3Knzeada3jglwQyL3_uUQegcFKpvZwVAcguVMvrsbNgdR9VeKmYq8U7yBvziP-_vpj1UZcf3QxlNK_oOgDg9lxP3vsSKzxliW422svFDiyPkWPh1DWmry1xBD4Pldemf8OEvgSHSDAlegWoBnfOHljDcPf6kT0PaC-jHrKn8t1cQgWk1-1oxiW4zKIlKGoRvmo4lCcUfqGXb5EPuZM1qRFWxv4roAVoxdLV0Pz53L_Q-grQWvbKH_Rl6Dw1BysU55Klt8vn_XBL5Zw_UlbT9FrszDRjJ56F7zElzqVYunI5uJaPWTwQyO-4dvM94CqiUU59iFkfZqaSulYktZrgZeXe0lw59ecQnL_pR2xwkialTgDoqtPksIjTuWVzkiW9hIL5t9sHyCdJ9nqmwZRZU-JuTPXswmrJEJ23GhvtH9kWsswLd0qvmY5mV3cwr7hlFNWEf8_5e3LoCa9uHQgIa0uquekJ3St9dLOXpkcRv74nCpxkcjems_2ZC71DRU63NILFjKC5ffsUPOZ4NfevDMUDbYHdeyVV6E2f-_1yMYCWI_sws69fWQkWUIv33hk7Gm55NaNgLD4RYCUBTO7v1FtEZiVYAU5ab7NvvnTJ3FaEHo9G9eTzN1I_MmPzqlYX539YF_DDedh0ThnSoJl7PYD-7LhRRG1215KmsTWbqDGmtTsHePAVRSh464XHgiZ6cNPNogtMl4ym6r6nsMbzFP2krBR1f-u0tHfQFxAeLyBWij01Z1WBz4GBh3bpdLrB85AlvFeY7R46PPydAHxwwanYVyxpS0UmS7Y2S37EVRdFzai1izvoy3-wA05YKcnRiUKR-oMcLf-BmB3HHZnY77YOuqQBUZNI7OR8B6lvTARQuoJbK26ONmXEsH-VoBJR7C-hNiXMVh1jHfhuaBAj6Dg9g1Vs2kGxfoJUXB5dlFmR42mnyGcT96N8ZAIdIoQSrBzai6bQbuvOb3OAcG2lEhOZHZiwFRCzpHMfu5dctZ_wcTUhYZwgOcBNIo4WELyjv0Yx22AHSHcrUzFezOwibs-heUF_ciKWkGv9OaabaAGTaTVncfCnS7rOcD3Xum89EAVegpYiQzK0DZ_VKooPoddgHs6diYOEn4iJyvE54vaVi72NAy0Tf9poRlidKaM009FImefEtZqwD1MmaeVbjcClv5Xwyh-KCQ2hCZmrnJ2P_e0bWIsE0MAJOK8iU6Q3zxbntbZAQAKZHqqauT8kkRYxk6oBicV5BS-whqDN_GoNZrnRLTNkjk1a8mnqg_kucvC1mCQRbvP367DYqZGuAd2EQWVLSBQibHoVIUcYAFbsfRHfsQ-uiZVZsjZ-xGM-ZcTzCJ6p-hFi9IQXKqOioM_xzRl4TSY-AEbGja_RY0puxi8BeZXvSxx8eYsJ0TRtIIQwloZzKpbx1OwyK-Ibfj01PU5NIurJL10PKXcnc7ImXN-b_p8wfzEVN12lSbQ8m-Rs0tx32jfvviXyHtWYfHuNqP0eL3Xjuka6FGnuDOeOAIzy4xj1vqhXd8UN2tiFOObl4Rza5pKzF-0IcEsKX36v4iN8oYxOoCxCxLwvFw3znYiAKe6CVky4e46LxZOI3bGM6MSrypwblPMA2gC_ogfMiYViJe8gsgld9UvgQaFfj0EEgfc0BWfxVw2i6Yv3OcH3T1jaHnCVgvcDpTXI4-ZeeWKl6fhH9ukYAG4-Y2mGiJhxJ7cjSg8CwU0KDmNRwoXGB2FT0bKWovkcFYM5ueMbXFTZ4FFcgfWcOzXFZka82HFB_iqD1XvOYMFQNiz3jdtuOr8o66rtCVAjJnuoTQDmbSrWPU0-utUMJx-4QAlZM8hdtXGfNBp0JRxctMZdxR4BAzF7JH_ETYi3itZkgDLEs9JBdty6gUiM0NdR6F_7mxsHCik3rpb5bauJKP89gV03mnBQuSUQTauNxdzXqw55SPDAHMBWg8QwyffzWwmyTAjl_R1QiFsTOv31U-HditYAeYMhLAP0mIs97T0inLsTUri1s2b1s7j6-I-NLXuT4VKiBO8lqVicTbQdQwiXehHQsi18e0H6T9XM0xBQK2t1dd4Jz2oLUGroSB3XuNbcaaxsffqRQgk43KIMEw9VsUA3FOTEpdM_xYIYEFM_-ApjDQJ15JyMRspfmu7HDdd-ybcXZ-C8WASJUPV8tFEfP4xgUcZeu-mExkryebbdMExq78yj7GlwWaeqBYfEXsvG6FIOqL9iFVcc3iIelrly0oM_xJmLOB_CCkGylDmHLxZZydf5v0RDh0KOXd7J-QYepcALXYoXmToj2JPrJPkaznH-2tI5xwp_M-mktoYNOhWrOepFjceXDSF5G5ILomGd9mHLnkq514ayZJCeE437I2geH4s6upgSAaqc07IVvdU3WjorhBw9fvefI5NnYwMiUSk_LC-JiQZDJ0bMLttvwKDx0TmOnMDJqxDr06_MWXn3i0zLQlAjItS2foksr6EMeK2InZznVZtgjcbD0exqZuzjCAqKz4PLQl62xyuJx8trJe0uHbQk-NweJthN5xcj41kJTcDuXbA1bA9HerCBWMX0RW3RXAKTvltGaqyMyUsJ_uOb40D0m56SqOmxnyA-mauiV2R11KC5Hh7YSS587NxkWUx2t7G9uio6WgWyx-HvhXYVi8wejyZw51z70YEa-aUDS2G_N0e6BV2B6dMGyd3lzTkMY6Ncs127IwQmXkV4VGL0stfchFf7rhXc1CZmFm7NZOMQPgb3_Heb39gZfMa4EYUVLuvfSpuM8wHZcQa57_uj6wmGp7NBBVpcgTee9ADvJXxjlmAj6gm9TiCl_GYbBLCdoTRAgsgsy1r4WijYr2sA_zch6EbDpTjQy6ER5GINZ4zi0VDy9avZcxhGmOEHYvKzcLB5PANOAW-8FLFHGgDWvf0cEMCD0UpSLAJVIX6rMjMJC3N_cgWmmv_zbllaW-vDVNFPyZOW32zU-l7r46_5IuF9Vc5choUlWOGLADSnXReau9WC4rfGF05CAvLe5Q0dex4K14SHJTEJuBWhGTaaXzONQSGtU9LJexoI1ijcnz9X59VvXxFX0oHmLvgTAim6nN96X5kllHFvrdDjMOiZKQTXtodUI-3ZcjfA5booJk7tnFeni0H2L1sqvpGy8JDlfl0fds8hST0vtXscfD5jDC-i6btLnRgpOpRDQMebCkqRlisZScBXb0nxoHK7CHtnQy4aCQq4oCBgMXdbwHOnbBygBSAg-HCpK53YoT-R5NUdESGmGCX5uJ0qlmGaXSshFbNW_NpQItJIrD7NW3VmqfWvSB1VL-nyVLOmc_wPmUhY7dSGArYKYQFKL4cBOSfHHHuftrRXy356_mTcDeFsHzqH3RXPaXhiad_lmQ9Bcw0OD_BotHvYfvVCaETpweH3eHl3RPBiUHlc5Da4nprHbXrvQL675qwVLiwLwOvPULU4VdGU-jIfSMkRUbJhSt349C1poj4aM-aD3s5iJy-3YDRYzmqMmFFr9CoKMah6hmn6n0oKSwg0YpLOc9JRDhBfp87_NNsWdRkpNw_DC7OaIF6VNxc6o2t9jExqmAiAbyRSkW2x-UiZl6kbB3uqffgAYWNylgJDZ-UPQNki30zURQFl1anKa8xhIGOgH7piVerG2LO8X7pFxa3DlYxFm37HC6irFtBwsFbvNGicua6MfUD3dV2MhE9x-sOlG9O08DKObUwBTpTzfAe-P_jGWHnyOsLXbaiV_cwxgWkEw9rKuFpI1SPuPrdO8_iSYdH36TqIREPLVbRcSJvHrsWP2Bf-Bb04SIonHV4Olu9KEYWVCOltRx7JFjp3eVQZLAGwjtxG_vDlublMpybM6TZdg1UYaCU4ZqLKss3iWO3wBNwC2usITNSjaiiLSH96fOHpAyXMhhodFDS9X-frLB46hilqE3PwoIyiR5R1dAdM7oiWa5qD6KH_dISw5H-uO6ZrUFo6i14E4RcCtRBBKALvVnApLxA_lcpnFR9_TZkstK-6klIEiSttNhxhHhv36XJw_J6jUTHnxRBr4JyXLL3-NmDZy8mplsbS4OXl7gg0vuIOBBHarKFvCEdvZv8ikxbDeftTz2je9mrCNCAHKTeNQWKf7Q7HFfPcza_BwhSqrd64DndvGVkfLlYBrbVSZp5nxPF13qBWIw9bbXTU5z8Wna72Lh4HqL-cUDsKbKBpst1VuBgaA7Va',
|
|
provider_name='openai',
|
|
),
|
|
next_part_kind='builtin-tool-call',
|
|
),
|
|
PartStartEvent(
|
|
index=1,
|
|
part=NativeToolCallPart(
|
|
tool_name='code_execution',
|
|
tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7',
|
|
provider_name='openai',
|
|
),
|
|
previous_part_kind='thinking',
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='{"container_id":"cntr_68dd936a4cfc81908bdd4f2a2f542b5c0a0e691ad2bfd833","code":"',
|
|
tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7',
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='import', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' numpy', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' as', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' np', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='\\r\\n', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='import', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' matplotlib', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='.pyplot', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' as', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' plt', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='\\r\\n\\r\\n', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='#', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' Data', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='\\r\\n', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='x', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' =', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' np', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='.linspace', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='(-', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='5', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=',', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' ', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='5', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=',', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' ', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='100', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='1', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=')\\r\\n', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='y', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' =', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' x', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='**', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='2', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='\\r\\n\\r\\n', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='#', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' Plot', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='\\r\\n', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='fig', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=',', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' ax', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' =', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' plt', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='.subplots', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='(figsize', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='=(', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='6', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=',', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' ', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='4', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='))\\r\\n', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='ax', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='.plot', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='(x', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=',', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' y', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=',', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' label', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta="='", tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='y', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' =', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' x', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='^', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='2', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta="',", tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' color', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta="='#", tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='1', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='f', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='77', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='b', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='4', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta="')\\r\\n", tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='xi', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' =', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' np', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='.arange', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='(-', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='5', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=',', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' ', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='6', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=')\\r\\n', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='yi', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' =', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' xi', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='**', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='2', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='\\r\\n', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='ax', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='.scatter', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='(x', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='i', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=',', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' yi', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=',', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' color', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta="='#", tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='d', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='627', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='28', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta="',", tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' s', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='=', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='30', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=',', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' z', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='order', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='=', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='3', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=',', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' label', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta="='", tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='integer', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' points', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta="')\\r\\n\\r\\n", tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='ax', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='.set', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='_xlabel', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta="('", tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='x', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta="')\\r\\n", tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='ax', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='.set', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='_ylabel', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta="('", tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='y', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta="')\\r\\n", tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='ax', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='.set', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='_title', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta="('", tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='Par', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='ab', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='ola', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' y', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' =', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' x', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='^', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='2', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' for', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' x', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' in', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' [-', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='5', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=',', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' ', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='5', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=']', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta="')\\r\\n", tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='ax', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='.grid', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='(True', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=',', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' alpha', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='=', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='0', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='.', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='3', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=')\\r\\n', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='ax', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='.set', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='_xlim', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='(-', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='5', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=',', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' ', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='5', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=')\\r\\n', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='ax', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='.set', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='_ylim', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='(', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='0', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=',', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' ', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='26', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=')\\r\\n', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='ax', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='.legend', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='()\\r\\n\\r\\n', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='plt', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='.tight', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='_layout', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='()\\r\\n\\r\\n', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='#', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' Save', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' image', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='\\r\\n', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='out', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='_path', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' =', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=" '/", tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='mnt', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='/data', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='/y', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='_eq', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='_x', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='_squared', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='_plot', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='.png', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta="'\\r\\n", tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='fig', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='.savefig', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='(out', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='_path', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=',', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=' dpi', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='=', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='200', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta=')\\r\\n\\r\\n', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='out', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='_path', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=1,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='"}', tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7'
|
|
),
|
|
),
|
|
PartEndEvent(
|
|
index=1,
|
|
part=NativeToolCallPart(
|
|
tool_name='code_execution',
|
|
args="{\"container_id\":\"cntr_68dd936a4cfc81908bdd4f2a2f542b5c0a0e691ad2bfd833\",\"code\":\"import numpy as np\\r\\nimport matplotlib.pyplot as plt\\r\\n\\r\\n# Data\\r\\nx = np.linspace(-5, 5, 1001)\\r\\ny = x**2\\r\\n\\r\\n# Plot\\r\\nfig, ax = plt.subplots(figsize=(6, 4))\\r\\nax.plot(x, y, label='y = x^2', color='#1f77b4')\\r\\nxi = np.arange(-5, 6)\\r\\nyi = xi**2\\r\\nax.scatter(xi, yi, color='#d62728', s=30, zorder=3, label='integer points')\\r\\n\\r\\nax.set_xlabel('x')\\r\\nax.set_ylabel('y')\\r\\nax.set_title('Parabola y = x^2 for x in [-5, 5]')\\r\\nax.grid(True, alpha=0.3)\\r\\nax.set_xlim(-5, 5)\\r\\nax.set_ylim(0, 26)\\r\\nax.legend()\\r\\n\\r\\nplt.tight_layout()\\r\\n\\r\\n# Save image\\r\\nout_path = '/mnt/data/y_eq_x_squared_plot.png'\\r\\nfig.savefig(out_path, dpi=200)\\r\\n\\r\\nout_path\"}",
|
|
tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7',
|
|
provider_name='openai',
|
|
),
|
|
next_part_kind='file',
|
|
),
|
|
PartStartEvent(
|
|
index=2,
|
|
part=FilePart(
|
|
content=IsInstance(BinaryImage),
|
|
id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7',
|
|
),
|
|
previous_part_kind='builtin-tool-call',
|
|
),
|
|
FinalResultEvent(tool_name=None, tool_call_id=None),
|
|
PartStartEvent(
|
|
index=3,
|
|
part=NativeToolReturnPart(
|
|
tool_name='code_execution',
|
|
content={'status': 'completed', 'logs': ["'/mnt/data/y_eq_x_squared_plot.png'"]},
|
|
tool_call_id='ci_06c1a26fd89d07f20068dd937636948197b6c45865da36d8f7',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
previous_part_kind='file',
|
|
),
|
|
PartStartEvent(
|
|
index=4,
|
|
part=TextPart(
|
|
content='Here', id='msg_06c1a26fd89d07f20068dd937ecbd48197bd91dc501bd4a4d4', provider_name='openai'
|
|
),
|
|
previous_part_kind='builtin-tool-return',
|
|
),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta=IsStr())),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta=' the')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta=' chart')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta=' of')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta=' y')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta=' =')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta=' x')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta='^')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta='2')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta=' for')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta=' x')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta=' from')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta=' -')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta='5')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta=' to')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta=' ')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta='5')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta='.')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta=' \n')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta='Download')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta=' the')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta=' image')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta=':')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta=' [')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta='Download')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta=' the')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta=' chart')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta='](')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta='sandbox')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta=':/')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta='mnt')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta='/data')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta='/y')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta='_eq')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta='_x')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta='_squared')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta='_plot')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta='.png')),
|
|
PartDeltaEvent(index=4, delta=TextPartDelta(content_delta=')')),
|
|
PartEndEvent(
|
|
index=4,
|
|
part=TextPart(
|
|
content=IsStr(), id='msg_06c1a26fd89d07f20068dd937ecbd48197bd91dc501bd4a4d4', provider_name='openai'
|
|
),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_image_generation(allow_model_requests: None, openai_api_key: str):
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model=model, output_type=BinaryImage)
|
|
|
|
result = await agent.run('Generate an image of an axolotl.')
|
|
messages = result.all_messages()
|
|
|
|
assert result.output == snapshot(IsInstance(BinaryImage))
|
|
assert messages == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='Generate an image of an axolotl.',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_68cdc3d72da88191a5af3bc08ac54aad08537600f5445fc6',
|
|
signature=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='image_generation',
|
|
tool_call_id='ig_68cdc3ed36dc8191b543d16151961f8e08537600f5445fc6',
|
|
provider_name='openai',
|
|
),
|
|
FilePart(
|
|
content=IsInstance(BinaryImage),
|
|
id='ig_68cdc3ed36dc8191b543d16151961f8e08537600f5445fc6',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='image_generation',
|
|
content={
|
|
'status': 'completed',
|
|
'background': 'opaque',
|
|
'quality': 'high',
|
|
'size': '1536x1024',
|
|
'revised_prompt': IsStr(),
|
|
},
|
|
tool_call_id='ig_68cdc3ed36dc8191b543d16151961f8e08537600f5445fc6',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content='', id='msg_68cdc42eae2c81918eeacdbceb60d7fa08537600f5445fc6', provider_name='openai'
|
|
),
|
|
],
|
|
usage=RequestUsage(
|
|
input_tokens=2746,
|
|
cache_read_tokens=1664,
|
|
output_tokens=1106,
|
|
details={'reasoning_tokens': 960},
|
|
),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 9, 19, 20, 57, 58, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id=IsStr(),
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
result = await agent.run('Now give it a sombrero.', message_history=messages)
|
|
assert result.output == snapshot(IsInstance(BinaryImage))
|
|
assert result.new_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='Now give it a sombrero.',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_68cdc4311c948191a7fb4cb3e04f12f508537600f5445fc6',
|
|
signature=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='image_generation',
|
|
tool_call_id='ig_68cdc46a3bc881919771488b1795a68908537600f5445fc6',
|
|
provider_name='openai',
|
|
),
|
|
FilePart(
|
|
content=IsInstance(BinaryImage),
|
|
id='ig_68cdc46a3bc881919771488b1795a68908537600f5445fc6',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='image_generation',
|
|
content={
|
|
'status': 'completed',
|
|
'background': 'opaque',
|
|
'quality': 'high',
|
|
'size': '1536x1024',
|
|
'revised_prompt': IsStr(),
|
|
},
|
|
tool_call_id='ig_68cdc46a3bc881919771488b1795a68908537600f5445fc6',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content='', id='msg_68cdc4c5951c8191ace8044f1e89571508537600f5445fc6', provider_name='openai'
|
|
),
|
|
],
|
|
usage=RequestUsage(
|
|
input_tokens=2804,
|
|
cache_read_tokens=1280,
|
|
output_tokens=792,
|
|
details={'reasoning_tokens': 576},
|
|
),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 9, 19, 20, 59, 28, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id=IsStr(),
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_image_generation_stream(allow_model_requests: None, openai_api_key: str):
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model, output_type=BinaryImage)
|
|
|
|
async with agent.run_stream('Generate an image of an axolotl') as result:
|
|
assert await result.get_output() == snapshot(IsInstance(BinaryImage))
|
|
|
|
event_parts: list[Any] = []
|
|
async with agent.iter(user_prompt='Generate an image of an axolotl.') as agent_run:
|
|
async for node in agent_run:
|
|
if Agent.is_model_request_node(node) or Agent.is_call_tools_node(node):
|
|
async with node.stream(agent_run.ctx) as request_stream:
|
|
async for event in request_stream:
|
|
event_parts.append(event)
|
|
|
|
assert agent_run.result is not None
|
|
assert agent_run.result.output == snapshot(IsInstance(BinaryImage))
|
|
assert agent_run.result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='Generate an image of an axolotl.',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_00d13c4dbac420df0068dd91a321d8819faab4a11031f79355',
|
|
signature=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='image_generation',
|
|
tool_call_id='ig_00d13c4dbac420df0068dd91af3070819f86da82a11b9239c2',
|
|
provider_name='openai',
|
|
),
|
|
FilePart(
|
|
content=IsInstance(BinaryImage),
|
|
id='ig_00d13c4dbac420df0068dd91af3070819f86da82a11b9239c2',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='image_generation',
|
|
content={
|
|
'status': 'completed',
|
|
'background': 'opaque',
|
|
'quality': 'high',
|
|
'size': '1024x1536',
|
|
'revised_prompt': IsStr(),
|
|
},
|
|
tool_call_id='ig_00d13c4dbac420df0068dd91af3070819f86da82a11b9239c2',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(
|
|
input_tokens=1588,
|
|
output_tokens=1114,
|
|
details={'reasoning_tokens': 960},
|
|
),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 10, 1, 20, 40, 2, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id=IsStr(),
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
assert event_parts == snapshot(
|
|
[
|
|
PartStartEvent(
|
|
index=0,
|
|
part=ThinkingPart(
|
|
content='',
|
|
id='rs_00d13c4dbac420df0068dd91a321d8819faab4a11031f79355',
|
|
signature=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
),
|
|
PartEndEvent(
|
|
index=0,
|
|
part=ThinkingPart(
|
|
content='',
|
|
id='rs_00d13c4dbac420df0068dd91a321d8819faab4a11031f79355',
|
|
signature='gAAAAABo3ZGveBi351h31WQM2aG_dbN1N74J4X3Lf1SbUrUhElKaT5odbh4N1liwG5Hjip3Ci1illQSsd4n035fOOIV3sZzAMvV3ypncux4WDBpQ9NbeuFMNSyNOPTxJLg4j66UbW2ptw3u1VP3j0vCHvV5MoDhErheYZsWKhYVtkUNkKSVLWkS_yK0pOltSwHfRy3tbrkxnqD99BuVbCjV1nWSzTAmJLicBtjDaH0NjjD_vMyFiUe83-eZRs-Q_6njWasZNCmTcOq4zlpFoJ_AGeaTbaLIC1OwDV3sNT7pXvo7YI7jmsYEhHAKa8BjZmMjzBPLDRu9TMWtXMnO6nyVYqMxsyQPdNmP-BDNfr_8Rmo_uI5egfE0qRKgAc5MrOGd1fSgtUqeKah3kbLMyCD0_-jWmVInb2Y4LfPcX0iOeTGum2IRKwy6G1tdY8C_gEOnIGAUKOT2sEF98Ythy9auV27BCbcjfCBJlH0rOir_OiQjUIXZqqY0My1kVENBbXj2-VIFIqG-CcxCldFG2Mq0NGo86h1igQIFmLItXLPTS_QnaWADSD9La8JWpg8CuWg-yB3UqYaG5_f2Cl5jRDQdIYavTBvD-lp54y8aEnGA6HksQaCtB7jHX0ZM0pqYu7LvLjeHxAJWsnF4NN0HPz3d307muS0TtrXUxqeZFdTNoqdBOxfuJ2-Ym_LmeubnEzh5wHAguJKZ6S_jcEFM3Jdb1R8Gk9dv2y7BUz1hKSFF7peXc9Ear00JjPHAlR1x0ECqONTSD9Kda80pQlDSh05ITKQ2viOy1jmCsWeSsll6EJPcGEMfAcZ1UMgHYX3sBa5Oz3DS28Ur8yk-I62nUWbcj8n7IsZmZL0CWc2qgCtj2TzFZaVEx8iumUKpU0hmML_kF3JPH2Ie8nB1ko18HZV7A_-n9XGDZzwsfPD9pu4P-fb68KNqU_qQBfe8msYvuFuljC-0kyGrQIQH2X6stwEkyme1TuJfxIZ2t2q2l02gEUVN8LN8qX98hp7DBxXepgdKvqWVOM7icvtW0mPACf1b4izSDqEgqhqx4tNsjixoHcM9M8awzss_y2_jZ3V7gY3pbPgwWKHyyTUzA1ogPfMkjxxUrVLNyHRPmnklUeQdV-vytip3BzNOq4yTUz7jVFrudSDcr_KM6Ie806OkgKF81l-W-40qzx6bGg2DAcZf5hfbTzk-ho51sRBwDp7RJrx2SXSBGXA3ArYzgq-2iat368uDLiQhhbunzKm3_6CFWggpbUO8Kp3FP7-k4Z4CRbHkg8WVT0HhH6w0ysoi-P6_ZH-IKI7XG-GT1kq4yje3qlfRUT0-0_LPsr8LyM6AbOYj4NiWHP3XJ2qa978VVOLJQtY-qG3VX9kMq13C-uU8PDOsOEidYZl2gqFtXhxkXivwACbLMnvzJayXJRev1QkoNxIg1Stl9II4D_ndHfNYeAvMvOnSNafoCOmMzCBp1klovMP_31YvR2B3af1TYanbbHoJt2UR1GRR_Aqr7G6RukNkXAl63LPlDQSYm5BB6zD9iNX9hJ8MSZ1IFIcbM0L32tAWsyKKAEWyr9MGckicDa_hES9adeXuunqqKhUctd94J1dsXLiWCGIet57YIUj5WoF_FQ6D6FY9rB00KhCDlHr1Ot5NCMmn6y-u6TYJUhpl7elEErYGXaGPhUtKUSbAIzOXzBIAKb_MiMVvo6a2VYwsxwZV14X8TYkKw_Y7w5Wt6JA_wTOoen7Cc0eFyc7FZA4NjIMkIUOXymtjzOSkFJz1eMBqp9diET9VYKGsn6GxviD8jWM6-RCWcFurewcn4d6TeTclAt7G_LZrJ9bZtMVlieSJT-3vWr9qVt8OGBUJEJRVOzpr5FBnEceqK8s7D_s8EZwTaGwyAuuaZThy2PNWJhpE4c0UeKgh0ec36Q0ZRN8DF8Khne8Epe1rehOrsfeyFFRuQ5CDGdHimhtOAIbDyg_5PPCp8fgiU4R9xqtizCVTR4ej1VPIClmebUErOl3TN-IyoSc8rv--Vi0ATn69Q8tSPweI07KVEzRJpDtxbnGcbbilPN5_liJcQrLMf5ikaWBoq42s6FXDjr-ASD0h7IlNGHxnN8q__iO6jA9-2PTywI2bbBJsie2L7OaGGehO5zv_rWv_6rbk4HLVcQafi2nC5w1GNeDaXWSz0RjiTfXxjBh98302CQxiM-e1Lvt1Pe6Mqv-pAgXlFrSHDrqw8s4NSS2YpLDTUIOcOx8UutAJOgVpyZm2sQcvtOsGsSUBIyNI_4huseO9EuXF4TUQ-yzQRsimtXaDa6VId0y6qG7dWxTP30SWZkft2iW2_Nz_56MiioY7xACIjzo4s2aGLM352ufd4nEeU-K3UQd5hvhdIWUZn6KTyCUnqgChyIlB0Sto24VwIIj74DYisSiu-d8EYsVr5gZaQ_NaW4T7M_ZB0TJ0ptlU0X_h8uLu0ro2Vc_s7D8nkIKSzhGuuHO4lOjvZ-qLsPxG-pBa6jGvv1hOyng_x99icZ0oM7G7FmDl7SjP1pdLiZAA1hMPPU9b8Uk0j8hb4AFtfoXSfwZBQ8sYlT0_QmcSBgGxfZKXv4RcFSnAEGDNUn1V-P2uNoj06MOwzroZVjTuzVy284Hqe-08Gtt_bvZDmfsHonbEw5DrthsP9SzoC62hc6pcVs_ApQE5LwHgODxT-oejDppixNCr--hJ1IYVj4rRsHsmBv33H5kJP0rwmkdJ-I8rLj66jLf_Qu_OEh02dJqf3XSYsG7io3XCVjA-d-jUhLJSqcPS_3y5thCtWUcG_ucT64ADWdtOH0EkmzN0o7HmOJ48pkGhttNScjXlQUmOdkeBV55dTdXAzAyjKZsxP5ZK1F9m_1TMWDJX6nT4rRFrzv3PQByEyc3Rje7ZUdGa3Qky1-T5uhu1dk4ty_I92CbMDCM-jGZorhg5MX10B_zZ03DFrYTrdcDILS5i_BSOlGT8Du4aSMvwvUC5FLOYQFQdM_ZNIRIGhOSWsvObmVYh0j70YKqitDudSIm1V_Yw6qsW3ZPpLDgBju176FVDJJBn1Wx-DeQ6FrYtOjFHctqJN-2mjWQi_7lAzKbTLsB-9c4iZ4_efWXsHncmAeqvt0gvglQHDhY6cM4yZurpHkrE-lb5-vDLYamv-Du7Cs0pAaynEcbT-f3_F_WOgoXFy2WYOTt2KkSQZnW6ZPHzl3gfVOsHfAkWalMJ6vXa8FuoYfMmgZJpqtee5J6AxJaUea8xQ0VlVwuXmcK8EOPcwF1pWg8w5_SweA9jZ0fh5PaFW-BNlzGDmhRR-8Up0TCUTsdnZN7bABJBlxeQ5GEcwOjgT0UBF0_zXZo5fbk34TSDoEgfdQydVLlOGda8McmvsnNzDSq77a-Vj_8BeVacM1PPG9rp9F_-PQgpM7_7YsNoWMXha4b4_H58q7vPOvMK1zxRzNrq-sm9QhQ1LkzPgt158Gf2IPq8D3rh9YCmJvg1Ju7roShfnVdV_UO73MLnDhoqaUZEdq10723KFpescGNTRpsuWDE8qBiu58rbOzjmpy7nJfuOtfrv_qSjaFRTkShLV5PW2neHjNLlvQlWy-q3yjJXq-2zM-iRehbFIxI3ATcCq-SgThDeQ1qnTg9G0Jtsx3qBNZtCIi8x1oVsyavVJcqvo36UC-IXaXA1vpjuwER1dcZ999sP9MUnXcMTO9ba-GM3dslKvDtuZ5b8x_u7eCJfawzUPItU8iwISYKDWW8wTNOS8Iukujq3-IDOEFqmCOAlkdv6-AWNc7ZVOmyvvgDCpSN5nSkjpJhWI5kP13FJtJNHkNtP4RQkhRhwRh2ei308TvNgT8YSaa4E_BJ-QWQ_9PMNBsfAYSGIl1VaQinZF0qdvNhRIlonuZMV58aEEzsLk6hS7CGlbFwBMwAzZ5Q6PANavXDFiPGeIadxTE4r-iZLQ3CdvJWUiUv3AL3lzYraXX8BGDpEVAAIqoRYZEpR2QgIUui5b3gkCSlG-YdKqJ4HZ_6VCFqpywKsgPCX_c8pVD_6eJhgt9o5Vc0ARsfc1IG_XC-nFWOV4caiARMobX0y4qXDFulrAZInqBZ9Pq5MmbbhBmLLdT-y5fdPpB5UxsIHGqb3pip4ZaKS80IqAt8t7HPXSNza7zb1TwrjNlYcO_KhbLQBB0hMmKULnEJPWLDPKf_9NeAsN3U9AWyj1WpAKjSfpjjbXn37qpTMdgd-Js9-_FDaXDFH_aOYXI0GY1AMpvSSQzx_f6Erq4qyS5TAuAtXbvUm-iVJcHaZTIy7buGJqOUBb7BC1L33KpeQEZuCg6QyAdzn4bZUKvwjXuxNykpZA9LZWaFVdx2QfwCV_yqN2TTvLFmSj5SjldGwbBndjmtHs5kkDcV2mDlm3huEfbEJqf9sdxXaYhIfmUIkFDtYTpE1C0qSol-A6Yagtx_aNfWTL7F2lFI0OusuBwnDfkNow5mPsKqGMIqx5eJA2InLcpV7GTyCxT3BjVsggtSb1-4Zz2TYzBz7iYe8NPe-rxF6XWyHf1N0nyyCY8Y0_CqJS9OPFpsd53a6qY7xlhh1kwBOM8nJWb3OEJjMVspTUfwF90O8D9fDNS293vnG8SArU6d-1L4u0LalQbKXDRzcze8W8R3KWv1N0LXrWwfArPrO1WnpdEkJnbFfc1eUHqThJ39c7RAInK66VtNe5xtUVzuNZDfPKsIfD4Ms5xqMKEOWQt8RIciRapDo9aoWv5l-YCkuTrWp4pWP4b7eu9fizM5ZuzmRCj3Ecc7ZT2uvxe9sP045dqTH6lSeBNW1eW-pmb3oQ-g_mYL6SU60NmDp_mMa5HFuTdGSAAI9jP11k8KQUX6oGGGhx24w9seLaY98N_0v-cWsiNMQSnwR_SsGs6tPYqltHguz_azu0qsQuuXTQK9B06oEDR8tyb6CTqfX8pcumXIXC_DMFYfQ3pBK5R37G_oXTtX9srpw9vSulg4z52GhuvfT09ukMmdNGoIAS0551PjpZRz7-sI_nNTJQKpGgbhiH_zvA3U5hxue7fpAnQYXd6DYxR_y7QXSleoqQhZ2iVQW90Lwqp5MIDJaAx14bn27WBmQSLcuMpgnwpothMYFMmmNMdWYnGcQ0MIjhlOoykau7DRBFsLOKZ88y_9Pke7k9ISeTmArge2IdC1Ma7-GiJ90YVwwXDBSs9ssae8F1kWgyYV9rFxNbpF4uiWdQkVvASmW-QUNWzsHAtfuvrt-TR1SQ1Z-mMP_zF8mVjC14pAP5Z4pYkolLBinwy9V7DjcN0kymIM6fwpLt2h0LgfC1eLK3sutJcJJP9fFd8tTLIskEvUly-TeEct-syQebPxjxpxae7UPmqeDrOtvPi8-JWiHeIoJrUQnnw3ik2ULXvX1VFSnzDcKBAs_xZzdtjRlCGZWD-hgPPRTmG-YWeyovXZDp5Wv06AEL-hJlk4z1ZEt3yA0H6Ni7zE8jQ0_c6zJCWk6YtPhFk0ARZfjjdYSOFwJvx6rIrteH39b5W1yE8X0bm_cdeA1Q6TluBBkwv-9liCSOGT4ctzwaK3-cb0b4ko_apEEtpYkevu2ulqZoFi1S9g1joFZ5ooBLpxYGntuXXbALvq-zZniOJOtTdbpgsFQPS6Ae9kWXddWChNeyv_CEdkwXCkM__ua4GiH_Ce9WlqCzDCEoCYFpr7PyJP3gNg9Q_vkiLQa9V9bc3VtA5z4cjWB3rU5X9fLDZ0xzwO9krtGmsK9r2gkENMMu5Yy5BxGo94n6wRef0eMY6_GTzi7QsRuQSqNQLa98UdN4QGDa2c_-uDpENkMya7_hgM1z_RyUGtqVgpCHrld-jfSIGLPUI6kKWUDZ3USldXuep47KNuO3-BEOP2QEAKgHVlS9g2viG5r6wdeMl7Njs6iMsjs1KnHaqHlfZww8egAuOxAJjFxUYPy5djKn8n5lgPdk9ISeMZfxW5LcP80kPQekLohUbHcJ_JC2rTI76ckZvwuEGDUQTwGHR0B7YonoiTVzrhOWeqndwk3EBp0cr2mIc8vsWANK1WechMxunFVn7RuwV926PZhqFrnoep4ytDP8h4nJ4Z5zr9cXQCDv624H3JdPUYBBoxJ_7-QDM0fpuFXuRArtuezy6PV0a21CHLFtNq3DCWp56o4xgGm8x_8r2NtKTXxSwUY4_5cBHWd80aXF84Z42ldtGkAXayyFsv5er4VvWTzjwfEc39qkUGDQ5feVJb3YhfsT2qFyUnhb167hdJIPkI8rud4vLu3e9eu6xNLcw-LEjHptgEtfxOqiAPrBZLfWgkhfpU-encYtxg9cing8f_bAkf4-sP1tEaczGkdkMD-0orT-aN46m-8Dyn82fQgQdvov6n7KIuQipYomIQ3mJh5mSl2BAGMFlvLY297s3dCkBD3pGbRb6AAqu-5l8yCCVtg7FvzUWoQ3gL8FcP2cK_fYoJf7Z2YbgTNI_5SHiaAb-qxWuIP8ICEsxCHJJWIOfL6UnBXXctp8B_TiSbOFfGFrQPTJDUvKPyN9_mzO4mzXlOXLXu2VRG9J4NMSYTJT6-Q269vzse4SGqnULEUnpm2zQz9b9W97ahoMFYfV8xaVeFZK5ZU8LpyaN6v4mOJuuHm_vZuVircckh7UVVEK67jvRMi5JcKv-hDQhy1EmSRNCiZ4WHmGi7wcLEcJaUVFBRi84nU50Gjjs2kTslgVAnR9MyGqL2N4xvTAjoi4o-SCvDIvgWDnRCHXSD6ghfQagEUVGldGzk3EKEQF7VO5KTdheZ9FDiSXaaJJKit9NnohzmxM651VFC-AW0Ghklj52C5yvHScmJrIpMv4IjFAKj7erMRDjvYJ0v0PZDE0guTvoUFHrZd6umnB68QFINJogoy5GeT1hUs87OjZVQzPrxZqO6rzJK9m3meI2dFvdbgyAdbUx7fJRAu4yf2LC4dh0QaS5z24wuND3y-jHEsOvjUyIklRGeoH8EdGTBI8ZJIYKXJ8Ow797VYFI3FBzKNiPxJH-VFjpw0aqTLXVrAvCxwVK3awVAoWpwWMHN5yT57TOn3kpAbnBdAXG80kwTuOAAagePIVGrzENRGWVPGhvBFi55TDrQFXyymCP6c5q01KY04VU0udmOSe2Bwd-jMk2pjT3CLHb95G4PUVgy-l-occtk0mNRX4k3P9ETjeyOuA05c2rzMDthoHcFUnMqofePnvVK3eliJjh1uoNOrbx1rJuGsDZFEGxUfkjc5z5BW9zVw5YS7mlXjACPSDgMgreTTygsKTL0xhvSPsmu18K-cGz19v8ho7ix5B1WmPDsL75qXEqKsiO0ry1Ka23z8c4omngareIMqyM6OANeslUhQ7M_4o-OSaHUKQ3kAmJ3c_iPpedZUCo8GALcrgifqgd_ckfBRBpYssZhFQkxPNKJZhncuoRkdjxeAzANinaBUCxZ-Bg5DRQI6GCHgzUiUFMIWEqi21FF5UEiq0G2PM7PTE-RRO7wu8qg==',
|
|
provider_name='openai',
|
|
),
|
|
next_part_kind='builtin-tool-call',
|
|
),
|
|
PartStartEvent(
|
|
index=1,
|
|
part=NativeToolCallPart(
|
|
tool_name='image_generation',
|
|
tool_call_id='ig_00d13c4dbac420df0068dd91af3070819f86da82a11b9239c2',
|
|
provider_name='openai',
|
|
),
|
|
previous_part_kind='thinking',
|
|
),
|
|
PartEndEvent(
|
|
index=1,
|
|
part=NativeToolCallPart(
|
|
tool_name='image_generation',
|
|
tool_call_id='ig_00d13c4dbac420df0068dd91af3070819f86da82a11b9239c2',
|
|
provider_name='openai',
|
|
),
|
|
next_part_kind='file',
|
|
),
|
|
PartStartEvent(
|
|
index=2,
|
|
part=FilePart(
|
|
content=IsInstance(BinaryImage),
|
|
id='ig_00d13c4dbac420df0068dd91af3070819f86da82a11b9239c2',
|
|
),
|
|
previous_part_kind='builtin-tool-call',
|
|
),
|
|
FinalResultEvent(tool_name=None, tool_call_id=None),
|
|
PartStartEvent(
|
|
index=2,
|
|
part=FilePart(
|
|
content=IsInstance(BinaryImage),
|
|
id='ig_00d13c4dbac420df0068dd91af3070819f86da82a11b9239c2',
|
|
),
|
|
previous_part_kind='file',
|
|
),
|
|
PartStartEvent(
|
|
index=3,
|
|
part=NativeToolReturnPart(
|
|
tool_name='image_generation',
|
|
content={
|
|
'status': 'completed',
|
|
'background': 'opaque',
|
|
'quality': 'high',
|
|
'size': '1024x1536',
|
|
'revised_prompt': IsStr(),
|
|
},
|
|
tool_call_id='ig_00d13c4dbac420df0068dd91af3070819f86da82a11b9239c2',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
previous_part_kind='file',
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_image_generation_tool_without_image_output(
|
|
allow_model_requests: None, openai_api_key: str
|
|
):
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
|
|
agent = Agent(model=model, capabilities=[NativeTool(ImageGenerationTool())])
|
|
|
|
with capture_run_messages() as messages:
|
|
with pytest.raises(UnexpectedModelBehavior, match=re.escape('Exceeded maximum output retries (1)')):
|
|
await agent.run('Generate an image of an axolotl.')
|
|
|
|
assert messages == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='Generate an image of an axolotl.',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_68cdec207364819f94cc61029ed4e1d2079003437d26d0c0',
|
|
signature='gAAAAABozexg98F3au1443vbazt85gDycVpBeC63gC3bUrFg_jpx3BYCksfaKwRkdFirkKpRHBYG1go0Kbzl_1w7ZqwT-BcPYX1Q7a--X9S-5OvU43ikr2kPbJgGhjWv1W37aLPKfIFOvt0VspdWlN3Diq6u67ktR0R0BXOFuSSv7WQmRAIl2JxiDzquSCb12Uc6i61LxNLpzalYk9lyW4Nm5c4czAsbUNv22AeTsB-nwWzO9NJyPfe6cVybz5gdShl8lKIvBsjHMonyiZqqzuSZakXSlbteA8yvZmOYO9XT1ruXNhCoFnq4pNuMHiZuOyCSIvASPUebLO9Q9uMv-t2aKku_4Xg4VsNUuQQpJfZ_gqYJyZFmANBTAcbMGBVotJS0V0ZkO9kyM9W99udKPuS-IzAkPCiHuBsjMaQzhM9xA2-uNm169O4P6TfqD65iCYAMLvwZbXauC6etI17If_731Oo0k4qcR5G7vruSd_Om-vwAGmb0u3_FNcI73bNw4WkhfGdUucfquaGIBkSm2_H63SOfLHOiLsnLIZT1QkEm2k2LP6O57WHAE3H1Rd1nKkw7GQcY-oQlF9_AGi_okM_nCNf7RXzfEocRYXlkSwoJT67TGV_rp1aSzhxKgzOzntDlcOT4FdyVY_0MMtxnBANZL2mBM8nvmqogWOnrJui-7qmFYP6DJj-GkoqPhi_gXlb9gTHtus_lQtLvJSXFhDlQDT40dPbU6T716Bp1TRQSAtj5s4aIhEupcAyoza4Vzko9WF0lrwyTKeU0Yh2nkrieKNqL9XbhknW9bp8dRMRM5-cG4WZHWgD5nLV4EP-8xcZc0ApwBLebiUhkX3MZ7phpS_ZpwnHxO8GDnvuPNT6pXbvvrUL-Vitwe-qWoodziaUetUcopfpGlBYnZb6_cJOVFUoJDxPxgOkLiUW0WSSe-NsU0XllTG75sVx6cciU7LbDJjkPU3yLOrJcwpE81Khsefm-yyk4BOMzPbhJKg9Lrs9pZmuhXa0-GdMP6fHks4UKYeR5pX7zTU36t4BUYFssq-p8QqSuXah64S50W644yvaJR52PzkjDhNtl_lBbLRsVvlimUz2vGuJfFrATSnw-pkuGEDznc5BfdjqNDxncVWpwu0u2HnfAXbzBBtgNFVvWUegjRhBAwqrlWdjhvTcfzp2xUAoaXcQdRmZUh9Cw1Ib3pvejHk8l56i2Gf5X0VrDVxj2av3LugJ0sDgahri96q_EfYVD6TEF5D1lz-GBFTWJ3CZgdICN1HM2ny8yNg60U7EWFNqN3Rn9wx1LnntWcKxmwLRfTWFr3k3-TTHGam9Nw41T8hSkpGmpDYM2LsOcqdGa6ZYlFEBstzvf8JmPtBxIlD-nFIAVLo0Y4lFmt29x8hi56Ybu3_xdwY63SMj0rydOF0bX1K1QXcIJrSY4joCesFCJ05vB9F39HNByztFA3cBvyZkwzpmif9juYOpM2qfZVtG8oCnaBU90u7lkIvfgRd2H8lH0pnVTLjSq885mjhxRad1pnD-0rIoPY7ppz-quELsMOVqNTZWW8mkCy6uKiWna-3m0THLNz0CNFCnIz5ZTmchoj0x1WewlSamGL6n_SnB0H_lIXq9RUZlwrd1ASQtiOnI94pBRHPXfQ3a4PfNyeZv9GOKHr4pR0uHATkCd-rldmWQ5kG6MbNjNMJChaCMuSwrJiBondTeGIZswsMwOfV5eHgoZwfzkTxD3xo91CLGsE77IDjp05mNYc_KMbEqSmnDWpPJzsHWAX5vahfVXsRFx1Jjw-D4d5PIU_Mx4mwjPLjTrWkd5Hl2uKWvjw0Hb55lHT8D3cGlvIjRnTGM6ndLGndeKjXobs0NBcmgDQNLpHMccHAbb2UVIdvx8cF3zhkc6FdKJ17TZGJQbzWSqkkKwNUCYuRrf6CeHoEZh2ErArmcj57QVdXeDREoSzHhu1XRBfgc-ey4WF2RMzJ47Y0LNxFHpLxEk4Tpg4jcMvfRRKv-8tIu5kZeVSBqZTac90ly9qDeb32mv131YXy9G1A4RCLhjCoxM9M5r8a3AnDIvoMCusPxAM5mHpLEdv8A_5mliza9hztzmDKIQq7sQAow_XafhRJfX0dOeOft6aLkLTaq3KHN1HE0xy9Fupg5ut_AUd8tIvst7Av6vHPiK7gR-rxgWNx2FmKKXhvcWqMeeqFwSyq_18Y4w3ibPc-l-oYXD5gEMAaXGa6WgyaeNBSHNup7HDYlm_WLK3UdH-5IuIMxkhLFgDFED7g9lFl5V0I4bmW8Vqg0AqmNC2hkADsr7mNjuYr3NOzGCTsE2Ed992VG_e3k2PqtHVegZ41jxZTbfqYRM_HYg2rOdnzNN_NV3w0hpKmV-F6UIrb8Sfpscdwa2kz9Zz2huG_QGzI6AyjoepNgOYYZ_Phq4JMtrjge8HcOGRR3z8_hV21N61o5Z9yBfwx5kSnAvPXuAgeuh2MIfB_koOrwbEryE4HJTSA9b2IAs3BS9cvAu36VHVYP7Cj-U3EZxaxRIqusjNHb3WIzswa4AXFfjK65aaCCizHwxTSdn53nlSe_f7y9pMnOV3o467Vtt-oR9dqz92pauT1MxXYdBD8o0q7iOgHsqdcHBDvQmWOlpag9W1sF5uEyzA9bxDGlKLA9xtvfwnC2Y3nv9i-haFaoknx8eQGsM_bzWC54o6VlhJiNeeRUuTNEAsoTXv13usbK6MtSXoGvWPNDkj0CJ4sluSWmOidGG6DW0bEj58HYCedg71EKyNcrxcN7JJFklv1ju6IVY4k6pLMPu6kbYdKzXaxFWkOmB27Qz0dgBbK7hHhHagJWjOKrHWNiOphce9SHf9xQP2U0uJRNKqNnqmKRZLBQ-QcwRUxzaFrDByG7nRV0ybRHpJvrRfCjq-FVF0b8JXudNY3D2gI4p7WJeBqk84bt5a00DzmTZap1cEYi8VYs6zwFRa-LTemxgq8jOaeR33x8I8tDBbnqJdOV_o88Mek2E0mXkKfl_3ZzPzajcjyNHYdfhNHfcB0Oeyk6w_TXnVxOJVHuoYUsXuB4gDDMENk2yubSUFjOUzuZhXXZ4CdE4bymK54tzj9qATV0c7k-YsRlfUk5fX0JRQF6iMMWl0VtVgi3HolN7S5K4Tbs9tB0ByOS5H5N0D2BjlDl-kmd9Yrn6-B7Dxz2rxCtj_TQneCyMzfb6edt8X17c-iLsOV-fZOP3mFTCcnI6HyMxaLQDENgPF12gW3mDZyX2gAd7iRFXCHzmTnVkUEBhlCoIbq5Dc5s0niA3Jq9MHvhtgcXaUusGqCxOtQ22_CWixDntwS3hQx1KxEUzYZxThNSv-f5ji56iiWOB9uVzv6XLPdGYkN5CgdKzqPsim6VWfz52q2TTSZFRuaMGZsJuEpprdcWpHcHiBZsk3Qa-giYazywLHxj3Ph_Kf7kByYuzspVpGX3bct_unkM8jQzg8AWkn0LuFIY8ScyzXAVECJbFiOv4HipzyoyYcpDiPmadV-qAG8ghpw90xwcvgrGnHm5pzHQaqZVPKWXrHnkaJFgQ7OyEpmX-U52R5t4PZCeWJA90zAnvmL7s5MFEDLzSlzXPFjzUo3T8XWz6p6tUksLsGdUUqILT7hYdWwCdDnUJYZvjysEVIkNPNx3lAGW_e82KYUZUjQqiQqsbu1meJkdZdvMPmOlVEGx9oFvTW8cZrZZfJ1EKNOPR_1eyGdh5ZOfxGU2sogpOST3U6dxdeUwpEdhl1dTaZA_chepczFdHI7aMoKSISbpNC7nNhKNfQIpBcwPLtAhlgCOQKuBRMJUPK7VcOpl_nhz3FKexgJ0X_hFqN5qygPtKITZ_Et5ME9k5tzelHVAiosKhWOLngFJzKZkYFavSYUOHemVmUo7licjeUTPAb0d6kKbqqrJ44ZP22gL-Rw0YZa7t0VM7LrD4jD_taPhqmZ28CCchPAfP6Umo6txaiXDusvPG7v-zUGh4XWQlV_5SBSQwvPHUfQHlogHH69jxfJYRD3F-lDIfKR9ta8RuCuCdd2VsBUUAWFaRuulGcLiQ1BFWZ2Fx7W6rXgW8QkufqX9sp0zgTK9fCPN0rPLLU27BUwn0EB3EZe4bDBYrm9xAxYCgoOOE28GMpZsznXXIVaAXvUGVTBnXks9ycNghOw35fsAniH64H8u7Yp45JH8BMb7sk4zHcW_An1TzmUxjq3_JdOo9kkzNEu_qs0Y9btf4M6NajoVWlttXT2RD8XPxIrASjFMv8fxYJG0OsVHJoKTnGFlHqy13dIDMtfOkT658EuGe_pHwp8B1zI1jjpPmMW2MG3TAlumFgy9T0iyw0V3dp6qCZCjavMPIJ2HD9V9TJYueufIptuN1_hZF-xESc1ENH6z_NtMBJmW0hwCU9a6UqZ-K3_ZsJ7EK7q06V53KOSzzaJUY7SgUAFgwfbqFpvqi8emyHMxxIFylw_E1iauYGssBb_tzY1fTWwk9n4h4A9bdKesz_JBNRuKmKyOeYOS2LdJGhp9VOsbTvJtJ3MvwXuiH9rgFTXzXbDgIHxSUnUkMo4mJhnWXiqENnfqc_oOgx6zkicwwX05Bjn_k4nj800fT4RkJTY81dXy54TbvqBgPR--nTkq-WJD0e8_9hjOrtD7c7_p08FhVGWl_d499ylunVccNQ51zfeP0uGt8uYK-S9fNN8ED5PJ5iBycLRU0uvyFdsZFS0DJ4N96xSrdkdPAZ5CjfIn4bawnCs8MrHg47sCgONAqrZ1WZTgyL8j8J0kdfVJTZoLRud4oFmM4t8nQsL1sxH322EWVaXsy9aWAMVWGcc2fesk4jVODZ4mfGtnvm-Q8ifjV7wqRa7s5uXv_-JFUawxNvHqMw2AvG1M0RJTIEnRkCKB_8CXNb4K-GRw12XtZs1KRED6j1yPzLwabcWNl2BpR9JCBmogxFFuPPLdevD2JWptQ3YwTQSIMridFriwY8g31iFMFrNub9Z356Vc9zyMvZ0sRBB4oK6iSy8Y39nU3512YdGpDmMTH-T5Cxy3pi1dphlbYHcar1Yl-n5gSS0_dezGILZbxfvGbxOAIL-AGwSE4JJpUFn6N0xRclaNTPDk9VkKpVktgJeWqkWFI1xdBUp0K9BQdNs6XsJsfMCaIE-ed9SbQpJRdc1S7m6lFKFZFvHujK1zplJemcpOrYIpsuTD8wzWJAxzWwtUoo0DXi0YQOrnFdo9PbcTscUGyNArCcGjOYiL5_oMwhJh5kg5L_OPLH3UhiLI_aHs4gT7nX__HHm_3GVOov7-kK_Rs5lGsZtFQ-yoau74FnzVn8N3YWMqWzCVbwGRBhvAm_fdI53h77CZeJm9yWZkWTzrc0Ua2HqA7KB-eZgCNx9Ox9n0Ilh42hdGLFSwOEF1oEHw63x4ZTENyAP7j7mHWnyVbqRVSZOlP6w8JquYAtS9hUJQb8GZs2MFuFcwTO8SX8gMWWVM3tGtpnz7UwqhcFMZHrJ0ehues8kdtrlqR2BWPYAquFhOQebvqhlYxieuLggwAIxudJr3PFYJrAgAxivKZlhKRSoa1HCFSp_4k_kuVp5AvfBMViLJ39pi_xHX8PnokEmi3_GZiz31hGjj1GgNg8HZCkQtQJcOjMA3YPE3kQHy76YIdxzL59EdWsfuHj9IhJ4J_PoHTg5k683yXkvcXsz_sOYj9j6eMNn3Krp-ZFmWbtmqpWuvKS7vmE2Wtq9RmzvkGPC0jeY6J_ndbyCCljLpFGbvSYC50ejEulcMSUw1ypHhHzoDzO1jFo1xBDfta1H_hAsLkmYKOseTZce6hHIZR5xUwWStmu0y3OoOeVT3cZXEErr7qPLxEzB6nTlSgzLRX-ncRCnnUy0f_pXWwBE9dE6w1Nq4Y9BuS2Ip9oZZvj-gRCxTmmJLL8RYd-G9EeiBSlf09kezq4plUsJwvvPqVlj5CVLk6DkEsTvAM5r8DK3FYnLUgf7hQI_EZ8P9VN_rrPTPa25y19MR1-XIsVGLrVPmyXcj_w6XLBxEc1J8qQ37NZCw_DRfS0_ZUUqSDNlIy-y-DsdLIVtXE-BzpUkgqx5ADmbsiFFj87rS7wE5Yi2-btSpkWdTjaBbsWBgN3sOvcZgFt-AQrCK8BMLENwFrkFicExianGcwm55IlO2iUvCNjz84ogzU0IYhzDbHGCZBzYaLNX4JIVeonOoQuKA6QGueCffX1MKQ8fDKVZQc11Pr9rwjPNR3xxAs0G4ykW0c3809YE60dLbP-AZ8Qm6vxvAmTzd3XlMFaC-KbBxskXtMJRJqxUd5jYx-YQMQiOA8wLytP8INXSHi2_lgMI8QivRHoKx4oAl-qTx_qz0Ut4iw192OVWoJqTjhwIE_NB1Um9GawsbM9gb8VgI69RTPmQi9obao9kkQEUU-NrNtDkumrltR9Q9fXFyRbKn5Hs0PHzC2yiRFa_IqGEqJeqz2CSBkdC5GauukZj2XumrXKKCIYV2vJ-42D6MUqD138clb-prSiOe4dA2-wrKPjaaWdpaMeAbLXbLkHuH4K5h6HOpYIVMu_JWdnzzDnvye0o8A8vrxFZsOSDeiHPcoDyN6-6KYQwOlKpjiswFVdoD_e3Aw9ge6YHDOggsKeyERzv46Hc2GwW_GWrwaWgXSv-lePsj94L1b17WG_y4oEKbNMAmE4A4EBH1aJI6_wEeySONFKhbyOIwUBcRKCoKT40dbGie7kW23rrzSTQHpjdd4NbAbNAGEtbAJbVwZbFC2RMwMWQbz8mmnMSdVw0VN6UVaCdUatYzATx-gk-lg_8ikyOhMwA_lXFI9pfnHIIqGmKHDJdrE4pdDBK5wVLB1H_CYqzUneygueeTUc_rHiMj7QdekTGh4KnA2lurqXemNemePYl621G_FqimReuPlTPmn88h-DvnmcVhTJEMCT2O6huU2_6yOlyulJw-mq6KMS_b3PSB3bgYmJEH7EM7ljv7lu0ZIqZm9xbpVTntP2s9jDBPMb95q8AnllIwH6uZEYdyiIDU9NQj9RIlR4RZ8Oi5b6n2IuKojvP6aK47npwKMvC4IOAMmGaE3_S2X0vFnpT1djiFPAjvklOEA8o0k91YV_z-QbWwVTd5fdRgaqXcqb3dLAuvnFq0bOpJMchlL9lrC02JW07Xi9Lw4EIoDsPBRr5s5DWLG8kLfpEv-YNZatryAbjbwMtJX0O9aNxLzG1bzyXVFwBb_HUgkotdbmlT7cXMMX8D4_xIQcDyjwo925uAVU3va121ddHt2JasKJc1vMyuOKXuDg5F3dYByqEQQH8zywgBi_IRtUx8aXNoNXobUf3LGaLc-3HqVTZby9Xv_Axm-s5jSqcEPL4WJX60rY28qx_lmajcmcVLY7irKovGmfEIlVeHYdMxDxZue2nHHHc7NmF7dGORg870x5iGUcqtmjvOx-NIqVShVQlalGOdIF3_u6r8xlvOL8xWJ6WLXKNaAFVtaoDdObEoR3stpG8iaaaGNcrRqIH7J86ntBUmQ8chIgOOvPz81W0Op5jbSc4d2e_qAAuvt6j_qElh9qsGNMVPoY9DlxqqrlJ3ojG_6yqlX_jP6NGSx31vxc-IgggbyeEm2BVt1v3dJv_Z1Kmt8PGa3--atQsH3cemYMbWcwX2mAdMCHLY7xQJs_X9OgDaPd25bGPHFJQVNM4cypyPSWXEDiR-xIqkB7NzTrSEGf82wSgIPRAo7k-CBpbBVvsuLre4zof8K5u7QH2jYFQA0-9YtDa_Uyc_JcErLU7AroB5ZZr7s_s9qx5w_wJxMNW1NLZJdOKUVryP6DJPqTNmly3PT88GaPUDdrcQa7NplSF1FcqFmzOlZc1Se_fSkgEcFKrX9M7_65bTgLbAvj-8bCTIxZp0-PcmoQO4mrkNkW2Vr1rfSuOH5_vqJCT7s9A-H77RjLseMxJ27YF_dEXOHjsEV0DPM_zfno9_Lb6gL0rqffxhKdM_xRhSsLg47PJlCgoZkL2xVTJfahwydAzbr4ZWbZ84WwprOimGLJBdcQgjHEhxx0_cT9tFpUKkUbpAlUYqm1e23JGUXKrS9kBqM68RjcBAiaAgeKMx_5pNU4RzuJqCARPYgqhsy50LxkN6DgIMiDglLxho4wUYHVVkez3XXPHmO8FJcRuuk85Z-7qDeExR-Dp8vHkAvSGuyuUKHdyaQdhigMm18qFzu5-L_m3-C7xEblO7KJBDtK9mDdaviyLU9bQA4i7lLgUUdkCClPjm7yDt3SgjDZKXpBeD_HEb134vjhc6EVJN0IK0bf-lSswaAPJYcGc9ArU_mch_F1w0X1ISwvFHWKLYBM1bUSCr7vLyX-j-sbwroVSJ53bTedlaUpoVhEjKxcksjSgnndvAeeHAwHV-HCkOH_zqhxp5aTO1vNXeNESG7WoSRLE1_6vF7ZzTGmpG3GIbuBexi0BUe95UwrAJpdtzWMrAiRNQ3LlB8JkPIqX_ZXmHsu1bo84_x1u-VWQZFNTOitRXyLdA9GtDqg2q7RUJLj1Axq6CzjcewoW380K7r95Kimr6RDUUkvi85SjekZuV8NNd328heZWNSdov9lzmJlCokX6N5VDpg-NIPvjJyaQtf6YZfYtOk7pp_koGxatbsxZTSVh_DwXz7K8VfC2mEhr_xdDPe9nKAPrU4r_J9zdtAkkzdJssMWhSCMTj8z3l1bbIzMt05ivwEGLZYqZ5Sy2-duQVd1wA0NfKB2HjfgSyYhX5wN4aDW15copsEtPTrxCLidwc75rLdoi5Ch1Jt74v5UzKh8mxDaGqjdWeHkDrHbVy5hDVk00n1TZ5UAzWCq3lge3717y0ECZX992BrkqjffUYe0dZUUJr_3GWKSS0AZHg5uI-1Tka_DGqF7mWwdz7XpafzU4siolRkGa3QYmB8LWqdbAnpvte-uSwxAiQm2LiFF9h4dOO8U_2gNsniQViwLkD0KTHnuk1_N94P6QsypEL2bcPWCvCw1SgMmgKBaXYpP8FIN1trlpqyk_0abqnq9GoeCI2AjAOkHI0LnNsM8lTwWNqh1b8YVco3or8J4dOPeVQ=',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='image_generation',
|
|
tool_call_id='ig_68cdec307db4819fbc6af5c42bc6f373079003437d26d0c0',
|
|
provider_name='openai',
|
|
),
|
|
FilePart(
|
|
content=IsInstance(BinaryImage),
|
|
id='ig_68cdec307db4819fbc6af5c42bc6f373079003437d26d0c0',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='image_generation',
|
|
content={
|
|
'status': 'completed',
|
|
'background': 'opaque',
|
|
'quality': 'high',
|
|
'size': '1024x1024',
|
|
'revised_prompt': IsStr(),
|
|
},
|
|
tool_call_id='ig_68cdec307db4819fbc6af5c42bc6f373079003437d26d0c0',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content='', id='msg_68cdec605234819fab332bfc0ba35a5d079003437d26d0c0', provider_name='openai'
|
|
),
|
|
],
|
|
usage=RequestUsage(
|
|
input_tokens=2799, cache_read_tokens=2048, output_tokens=1390, details={'reasoning_tokens': 1216}
|
|
),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 9, 19, 23, 49, 51, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68cdec1f3290819f99d9caba8703b251079003437d26d0c0',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelRequest(
|
|
parts=[
|
|
RetryPromptPart(
|
|
content='Please return text.',
|
|
tool_call_id=IsStr(),
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_68cdec62725c819f92668a905ed1738d079003437d26d0c0',
|
|
signature='gAAAAABozey1vHMbs_Q2Ou1f-stI__3zJS-qTasRefyc_eyOxqogM8UGbPpL8D6PLFHcypshJpa9SQli-qZRIyG4ioUDLsKpBwFbfjdIhps667-8st03DRTRP0ms2izupS0ae6QqY9qrsPSchrSF2o2PlJOWKZAFJ609S0hGX8VDtrU8nESfp78NQ5HgpgXksXQTxk3_xRmXES2AThlUD0LYykoVKRX-xOyPQsOK7aDEs1CIk3lG1meiXdtJxP1Jm9JQGLWk6kePWUgwnAs818LMVvjcj8GWzFjxKUQlI3S855vYngivkMqYqh4gOcDRGRWej4NRzRmhOK-2yrATl26qnpRwNA1YXkFtn1ojxEkXD99P8RIXNItH4KW19ALs7ZizQmQlKzd96eyPT16OSLqEIfHAXWEKwoB2vTM2ExvHK4il76X9XmgRDy_CI3HAPI-7M3787MJBEY3z9cBe2sIS_GtSk12_GXRBUREhu8wcc4920FxkufYegHd3FzKxBRjyxGpR-jLyI24ahOZRKvoXi4-n1v4umoD5OSMjYpMtr0ykwIBQyyqldi9KqHBpJCzB0wA3JyAn-4JvQsXwIeeAtq3bNSJFaaf9aLJ5OwMO9I6IIWGxoQ1mzqmCs5cVwwjeJLzEc0T5g2qWJdXxdYmjesvMj3pJtgIq3iR2105LydhUiKE-0VLVAQGg-lnjkCtj-muEqlko2_FCHQ7b_hA0VkOUIKOYUDHRtwgtaeNnUpiWk8L7GnBNHtVQ7_kHEGj00UIVC4CKiJqESXS1om73Xt1K1-bglZLfSKfjrAd6E3W51cKQXM7KOfmpRwP-9DThdeOBgjlmMFveru6NYl2ntiu7GF8JJAvjebF3Q6SR4AtFSp8zrZVjlduW3hzQtKaROHLBVgH1KaMST-Nfnkn4AHCbhYNGSZxg4J8M3hh-BLba-lM7o9d0cHsHSORXeuAg40qioVCZNCtIooo6fAdWSAULw-uGdAbRbrJq5nE-w_Lilyeb2mWnMwMbKBHjQO7Kwe92UHvve46vMkiSSX-wZYwthfbO9BM6_ha5BJOtwNggKuBXxqMVizL8WKdvdTVwzP1guDvuVgVoCYKl340jB_EE2V-L-YzbSdaxHi09Gi6E10MgdaSGhNqUJMZWXrezkT6pyRYYRIWhaaImIuQf6JybMUH5hHH8DDEKvofLnQmgPVccU6womuYosIgLOLPOetK6OEFlMsQPBn95hb6jY5vETMyhiVYAVxaeXgk5WA-NdYQ-F2q4kQQS6Ku858AaO9rXMYpkaJ0nIubIbdgQMaXzq6ha5Z0BaxrJhxCDqmHXA5-INBBqWDw0AgJcAlMM_a5ShZA4zVWu4ydzikq8PlnATLbFzOkL8WBhJGRyveKaSyknPfPCBvXu_1l-hwAyvFv2dWWBloD_IIJ3ID_qtjgT7epwC88qmQd5ICPMMdx4DjEu72hU-rTIz30ks970qi_dKVbgpsLAJbHeCCWXBJzJ3UKrC6sc7Kj3rhtelPNEJqFyuB_EJOVX4o7R0AZvkk3wWs3IZ2nE9TeuYckw-hHg35YsC43QSrbIZUbfonaN57ZbwyrnEwnhH3oYXaMiPqqywH0CsZYrO0QLuAeGJTknlpWHgLEVmz39-e3UZFb6WIGIfSFaZHAFpmQBiPjae-qudcbvvfmAgBHqt-Aq4D_06ySnELOtDWlFusZcmHQwG9b0OCDXt6KRTR_-49uuoPCoXlv2nKb2eFhXp1gp6m6WsH21XNaeLU4RF4PR6oRUh-TCLzyBtwCscukF_3gBvcTdwJ4io0Fu6YVtEJux_Ec1vCaQHlUVGtZR7JDVyE5lu1y-aZx0u4s6HheF0bHLYaFgqgOmaNNWwK_jldqp99ZhU7Qat8GcG8YLLEJ04WDIp6_i_Ri7OUf5xgTEkAxS9gOxeJ1EMKR2oB0pf7YJ18XkNqeA7zxXmufJNIbEmXOC3XQKiDY9-2UzTyqjzdZ4V2naUggs7DjAAntcHhVFLGOZgGeQ5FLJ9jfzFlpE8mAg95ZtVvPzYBNFaPoTynqUlukCH4eje_62w_u2TruBMSU3cOV5IqVTLMHu2uwxHWdA1zrVi32LMv8FEYZ8nPyyk_BdapV-SRGQKn1yjGml__I5ksVlqNWVC3BX4hkIxH9K1bO6HjWP2-cdRtTYtRNcOOGOZZv5RtKfpvzjIK5o6d45KDK-jp39_cY9Veyawzc4XwT7jkyL8U0YNsRTEjafcPONK7yWasrOIzNuUppBFdMyER_R8Q1bTMQp1sE-NoAN-0MqupZe1jltzga6i6KLWuOXtMm1_DeHHH3OPNq-kfVs19gbQD13R9kMNjgu8FbAWdoVreG24tKUVSf2nWKReXwxc3WiiODaYTew2ynZ3BUchm3eKebybh4lKoYCw6lLoclnD7smFkP4-72RfaTylVe_npaWU_kWIhxItYosjWGc_ScJhLFdwAOUaNijGNX0TmeMb3uaESw23E3Y0M2pAC_wVkaHvJEYv_nYebwYc6yON5oMFAnOmqaJ14d-LfhuATtTrqD8fGXTPi73rpN5IpA3fVklkyUuu0GWqvsRujNnEcN2nL43LPRwFvwYSeL_tXJoTpTxdgkwjEg4dJr6hImPIwk6Yu_a159LyNKIeZOSZbHi0gao7OeDiSM2_1zb_srjUXgiVPJ26r3GCz07dhcwqUS8lU6nx5q1ncCt9mjTgUG0qmVPzDjfRmeqOU1gPZhh2XQeiXp2AWB-_9M9vu2EiSeOO2gfYKeaFjVhBLbjOeKs9r0xt5JXpdx5IDB1JOfK_MSlKwjl2kOlNo7p3e7RIVB38MQoih95hSz5E6EyOoH2O6KAXLM3qhgcmXe-XJwQKOkA8rPRclgvN-hLSopl6mDGqg7dNKXZB6SQspQPiB4mx91Wwt62aMqWa7Ve-thQ6-oCq9IQTLT34xpCOpHWz4b8kSfr-WDg9cTDdvYIGEDGj_XlE6CK5PuxzezWGYvYggwVJC_65zkGHFBURkBCRHc7otAyxMTKMCTKZnYWUdUEGpTCBonYFPOlh_ApDPnh1h3feF1x8AEIoVkq_ADRKhKbs28y44LQGp9pe49LdsgKN7YT8_azHknz5frvCUQJu8aiP_TaFCAiZhUc0JSATHPv3q6ZsNoUzF8ZfeSWuI3ZpQyoyMq4wxcyH1sFNqtIHd_iS2U9AkEACp6q6FAohK_O7GaDr_T7VfeO3JuRL9icg82WtPOtgJ1o7oqZxa1lfypGkWAgX_KF5aytblSoxlwn-Zk84Mf_YlbJEBO2mUa0Me5uLhM183akeG4y06FR-ANXrsYxrGmhpIWfl90WAKEG4ExdaexgzQOKXedTLOKnIwDW0ZSAu6O-Eiddn2w5GBO93OGEGQwkXDddA9PB9--mdyHgJM1OvXFYIWtziDLZ5qHAUrUFpYUth9B9mV4TXXLeLqx25TO7DL9czT6cGPeYNSOR3HpNdw93Pbro_kDIk-4zrmhlLd-I6w1tSisK7veJE0Y9Svvf6VLky34iV0RlIw-Z976oPP3rVJpSIujNbuFm84V2EvHFkG1oaHoViDxp4QesbToOdhl8GfnHHuwPaT78C1rwucflil_XkLAi0PBv0uGYLWaaAVlhm_lNZc4CYd7qHcdgma8dL27kHwVUJctIzphtV4yAhL-06SY-kwR9snNogfzDSuuNTVHPofu0_B7qA9vjwf3jeE8WJUbq0HxPSIGMSvJ57uh2YzRPWnMJ2PmARWgHAYoWUsZknHyr7DlKqzV1kxrLmr40xk98D9-Qkkq3enbEazhNWXRTIGMuWQbM1FQB3VL8GX3Xa1TjujWQS8_nC9LTId1XzNWsSCNOg8gHXqvt5hAeJVZ9GMTFJREPCBk5ts9odm3N3bjK5KgA3pNahnOS0u_c2auAc1t9C_5xqxEnIRxE6R1lVHz7hDTebYMcpldFI8IEuToExCjUUpoW6FmpeJrv16hwzTqbX69oToXJ2YA4WYCerDob0BAMHKWDC0nqPJTp9wjq2OnC5YKmMKfJ7lKhVprYG14RKnJfOJdChRpWQH2iK1Hc12sCJBJT2Lvm138cQeRApo2uUTs1dpJ-faoPvb6H_Wg-9ys5JsZtrzUXVpj4ttC84M7dZnbdyEh64iOoUINTSG4yryVDfN7PokiuN3DHuuXlWG8hkWAP4uyZg6sXdeSBGfVOjmG7Tc0zNqxZFkCMo9XEaLk_Zz6X7fQPM-MQVkSrb_0-S7tNwcufe3Y0nEnKpM1WohnyojInkRUzPWLUcBukzYpwuXg3MxxayDLwfjfybNb3hO_aNF_5mLz8s40IotW2Oxa1eKfZO5igty32jA5ipXwPjJjBj5yVgmTESDPmYrPkJCdFUtJJpGRTRX30BbLAybWkhJWBGilR-f5wp7yHp1pAcLl8YIKi-Di35kCCG0qtTimMeyb6E9hQww06pdDKAuVuDmC-RhgG9-x6MlmeJgKeNu2VeANq5lIiAKuDEXz4cs-TBrC_PC4j7dnaIbvchVdxx9Regcqfa2XTeXsKJB8uGwHDBQ_qu6zyKJ2ANyqk8x7Vmmz3YNREGNgiiRQK-zDjU2ZRJN0DJnHxdFMYcmywKz0QOab4orE01eqaG_Zy3vr7fDLOMJOl_puAjLnF7xiVmj1UWC2FwoWngvCrGO-2wsHB7fnvJapv-NpIoW146QuSVwhMROuZ-5SmzQz4MlF-ARtdkEvCAO45BZTM-AnTlOiMqMXspKrHGCiwE_S4FStVdHT4D7m6Ha4q_BRD9nBYp9r0vP4QdsfsK0pegYawDfZ3U9Vuk0dRZp3Z2QBXDLuIs0-0ol7_liDeC64KCdL2zrDSQCXeSQGtDpBdbSDf5xkzQ1N_IStAz6dI_FLbLqRPfE9tf3AfA9k7j3BwN_K6eeVfRueVnf0VOXukHURPwMTVE9C8IdPA8OnwLBD-k1YT2aV3Aow6eC4Dwl7cW2FtQ_BuHcpjMfWuJFiUuw9L8bLCWSlJ3rYdX_MYv-9mY--9d3r49p5FHAoK-bJS5yxHg2Q6Eg6DrSC4dORU5p_fKxrPklgOJKbZoQyb96_dp2qkCVgPjN1naoHfhgqHp49MxalqSx9n-vd86NTSXNw22lnboj4qAsLg54P4RSayO-U6jvj09ZuGtLGrCvKPAgHl_xP4dtpdbOC8OJ3nwHNfxAgZr2P5JEaFHN3RuIQGH5gkwNEA2otd40YptNJvD6r4gasyBPNY15jeYYL18yFQobK60gnXQweT_JXTlnGgbvgoGw6rJ2nifXw_o6Q_uBDQE9MOCJiN0FoivBdcwk-NHU-nNg93U30q4zbGbJ96O0E2X2qRyoOaEhBKAqkHrQM-NGjX7aj1YJFCxIFtQ00OQjT0vhGng6-3cB01l44mGdDFV47PZLzcFrGx7iDaR1Oy2Na5rOLphgT1_zvVF2mCw10HYxOYwSDnm6uqo_kADX_HQAGxQx1xDZ9iaZZGD00hdoZp55kRPGt6uZ9ZEEXPWUpAV8mPUlu0DcK6rr948_btFuMWo9KrVneCiF4qxkk_Q56aFoRa_HQwEEuh3sp9PSCT9wIfBYSR3661Ox7C-25rHNkrdv7XC8ArnQ7xT04y9JYOF_XLxtOLlAzscUryz56Qk7lexv7Qqe80fTTSRNFseDNFCxLjWm6wAo_kpaTOOvFiPUxlYwcmW4hy-hW8zH_JRVPaBCCKR_s68yEEAVve-8q0ePzqcElS4Bt8cFyU2kqodDmzzh2HAFcrA_ScDciNbwKWeJublK97rkWmLCuPZwJcvKNT_FfgYU5OxtF6G2TVVhUcbVAhPQFFegVn_8hYkZ3mJFkZa8FYYNMSGn8nssR8F9RInU9PFLfBiI2_sDJKwfhgLZ9Lb4x2Idx1-XNh9pAFkCv4eRKCoQNrfxZ4VgD7L2UeLTiTBOsH117rSSF3Aq5kaLLV9yqXc7bB8ZVdvsQ64ET6Z3sNM6xk78phFYy-Dnl9sM73dX2qpUefxdItLtql4E_jwm-D2qRdGtdkm3FpQhYhTKgfm--H3hj64AfDQP-7WVykl508pb7Ultl6j9ne28UqtsV6LHaXX3Raq78sZZl7pmkgp3dSZlZXuo0zwyh0eR8aDp201EdOqbG6a8Vs7cHo2pjy_31ZrIGA_rZhFRN5uVohUvtPWCwwV3D6qq9XQRFK6GsiIMgO8oH8v2AB_qopEXWkStyn9HgA-UntHqncuVnnVrprWFYfWVuTDkJhTC1-i9ZgdA1eUvZVxl-I4SfmDG540HUVVRw-Kt15vS7K2vc1FDiQA91Iya8eMjVKOs96Cr--cngQ_zZw062ZqKp7avO0EOTamvz8Zi2a0XzdIH5dH0_9_kXE_T4U43Ud_29QMElOxJfxt-9p9lBNBjfEkUvBayPX2XWlePtoQL89QLg-Xwe7RaGu0hvUiROaArk47B_703dSKDll2V5eUKc5f0H7icWyqp8u5rnjQsafBu6RCWHr7YJ1Pk5TBaw1qQCvNA2Z_FVZWN18No61fV16DHyjoiHBBjTROCS7m7_3snv4SDkoiFvIswpEt7pbYtbXLp5t0EQOQkWQOITojrGYIUi9c23kdiXr3EoPsvSMKlJcDyag25wzVNfA-bfiMCkdHPqaaTFBqYqlwA0SI6bwHKhqFKdGkrJ5YIxgaFLsLwwiNMSBR2wWXSFRXz-HCMewHkVV4LpGxkzeihWXJUO3gXeJuVY1EGxB8U15BUtQQcAAe2Om9KZtOsS2kRtO7vZq62E8jl7bUbTmw0XTZ4eh3xg-IRiK1ynur6XqtZH1kNQFq7k60X-6cFwDS7eDGdzrgl-Kbl6VSzxpwxu5Lz-KIjV5gGBUcOjnIpRuwY_s',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='image_generation',
|
|
tool_call_id='ig_68cdec701280819fab216c216ff58efe079003437d26d0c0',
|
|
provider_name='openai',
|
|
),
|
|
FilePart(
|
|
content=IsInstance(BinaryImage),
|
|
id='ig_68cdec701280819fab216c216ff58efe079003437d26d0c0',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='image_generation',
|
|
content={
|
|
'status': 'completed',
|
|
'background': 'opaque',
|
|
'quality': 'high',
|
|
'size': '1024x1024',
|
|
'revised_prompt': IsStr(),
|
|
},
|
|
tool_call_id='ig_68cdec701280819fab216c216ff58efe079003437d26d0c0',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content='', id='msg_68cdecb54530819f9e25118291f5d1fe079003437d26d0c0', provider_name='openai'
|
|
),
|
|
],
|
|
usage=RequestUsage(
|
|
input_tokens=2858, cache_read_tokens=1920, output_tokens=1071, details={'reasoning_tokens': 896}
|
|
),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 9, 19, 23, 50, 57, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_68cdec61d0a0819fac14ed057a9946a1079003437d26d0c0',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_image_or_text_output(allow_model_requests: None, openai_api_key: str):
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model=model, output_type=str | BinaryImage)
|
|
|
|
result = await agent.run('Tell me a two-sentence story about an axolotl.')
|
|
assert result.output == snapshot(IsStr())
|
|
|
|
result = await agent.run('Generate an image of an axolotl.')
|
|
assert result.output == snapshot(IsInstance(BinaryImage))
|
|
|
|
|
|
async def test_openai_responses_image_and_text_output(allow_model_requests: None, openai_api_key: str):
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model=model, capabilities=[NativeTool(ImageGenerationTool())])
|
|
|
|
result = await agent.run('Tell me a two-sentence story about an axolotl with an illustration.')
|
|
assert result.output == snapshot(IsStr())
|
|
assert result.response.files == snapshot([IsInstance(BinaryImage)])
|
|
|
|
|
|
async def test_openai_responses_image_generation_with_tool_output(allow_model_requests: None, openai_api_key: str):
|
|
class Animal(BaseModel):
|
|
species: str
|
|
name: str
|
|
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model=model, capabilities=[NativeTool(ImageGenerationTool())], output_type=Animal)
|
|
|
|
result = await agent.run('Generate an image of an axolotl.')
|
|
assert result.output == snapshot(Animal(species='Axolotl', name='Axie'))
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='Generate an image of an axolotl.',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_0360827931d9421b0068dd832972fc81a0a1d7b8703a3f8f9c',
|
|
signature=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='image_generation',
|
|
tool_call_id='ig_0360827931d9421b0068dd833f660c81a09fc92cfc19fb9b13',
|
|
provider_name='openai',
|
|
),
|
|
FilePart(
|
|
content=IsInstance(BinaryImage),
|
|
id='ig_0360827931d9421b0068dd833f660c81a09fc92cfc19fb9b13',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='image_generation',
|
|
content={
|
|
'status': 'completed',
|
|
'background': 'opaque',
|
|
'quality': 'high',
|
|
'size': '1024x1024',
|
|
'revised_prompt': IsStr(),
|
|
},
|
|
tool_call_id='ig_0360827931d9421b0068dd833f660c81a09fc92cfc19fb9b13',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content='', id='msg_0360827931d9421b0068dd836f4de881a0ae6d58054d203eb2', provider_name='openai'
|
|
),
|
|
],
|
|
usage=RequestUsage(input_tokens=2253, output_tokens=1755, details={'reasoning_tokens': 1600}),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 10, 1, 19, 38, 16, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_0360827931d9421b0068dd8328c08c81a0ba854f245883906f',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelRequest(
|
|
parts=[
|
|
RetryPromptPart(
|
|
content='Please return text or include your response in a tool call.',
|
|
tool_call_id=IsStr(),
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_0360827931d9421b0068dd8371573081a09265815c4896c60f',
|
|
signature=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
ToolCallPart(
|
|
tool_name='final_result',
|
|
args='{"species":"Axolotl","name":"Axie"}',
|
|
tool_call_id='call_eE7MHM5WMJnMt5srV69NmBJk',
|
|
id='fc_0360827931d9421b0068dd83918a8c81a08a765e558fd5e071',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(input_tokens=587, output_tokens=2587, details={'reasoning_tokens': 2560}),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 10, 1, 19, 39, 28, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_0360827931d9421b0068dd8370a70081a09d6de822ee43bbc4',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelRequest(
|
|
parts=[
|
|
ToolReturnPart(
|
|
tool_name='final_result',
|
|
content='Final result processed.',
|
|
tool_call_id='call_eE7MHM5WMJnMt5srV69NmBJk',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_image_generation_with_native_output(allow_model_requests: None, openai_api_key: str):
|
|
class Animal(BaseModel):
|
|
species: str
|
|
name: str
|
|
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model=model, capabilities=[NativeTool(ImageGenerationTool())], output_type=NativeOutput(Animal))
|
|
|
|
result = await agent.run('Generate an image of an axolotl.')
|
|
assert result.output == snapshot(Animal(species='Ambystoma mexicanum', name='Axolotl'))
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='Generate an image of an axolotl.',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_09b7ce6df817433c0068dd840825f481a08746132be64b7dbc',
|
|
signature=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='image_generation',
|
|
tool_call_id='ig_09b7ce6df817433c0068dd8418e65881a09a80011c41848b07',
|
|
provider_name='openai',
|
|
),
|
|
FilePart(
|
|
content=IsInstance(BinaryImage),
|
|
id='ig_09b7ce6df817433c0068dd8418e65881a09a80011c41848b07',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='image_generation',
|
|
content={
|
|
'status': 'completed',
|
|
'background': 'opaque',
|
|
'quality': 'high',
|
|
'size': '1024x1024',
|
|
'revised_prompt': IsStr(),
|
|
},
|
|
tool_call_id='ig_09b7ce6df817433c0068dd8418e65881a09a80011c41848b07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content='{"species":"Ambystoma mexicanum","name":"Axolotl"}',
|
|
id='msg_09b7ce6df817433c0068dd8455d66481a0a265a59089859b56',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(input_tokens=1789, output_tokens=1312, details={'reasoning_tokens': 1152}),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 10, 1, 19, 41, 59, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_09b7ce6df817433c0068dd8407c37881a0ad817ef3cc3a3600',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_image_generation_with_prompted_output(allow_model_requests: None, openai_api_key: str):
|
|
class Animal(BaseModel):
|
|
species: str
|
|
name: str
|
|
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model=model, capabilities=[NativeTool(ImageGenerationTool())], output_type=PromptedOutput(Animal))
|
|
|
|
result = await agent.run('Generate an image of an axolotl.')
|
|
assert result.output == snapshot(Animal(species='axolotl', name='Axel'))
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='Generate an image of an axolotl.',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_0d14a5e3c26c21180068dd8721f7e08190964fcca3611acaa8',
|
|
signature=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='image_generation',
|
|
tool_call_id='ig_0d14a5e3c26c21180068dd87309a608190ab2d8c7af59983ed',
|
|
provider_name='openai',
|
|
),
|
|
FilePart(
|
|
content=IsInstance(BinaryImage),
|
|
id='ig_0d14a5e3c26c21180068dd87309a608190ab2d8c7af59983ed',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='image_generation',
|
|
content={
|
|
'status': 'completed',
|
|
'background': 'opaque',
|
|
'quality': 'high',
|
|
'size': '1024x1024',
|
|
'revised_prompt': IsStr(),
|
|
},
|
|
tool_call_id='ig_0d14a5e3c26c21180068dd87309a608190ab2d8c7af59983ed',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content='{"species":"axolotl","name":"Axel"}',
|
|
id='msg_0d14a5e3c26c21180068dd8763b4508190bb7487109f73e1f4',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(input_tokens=1812, output_tokens=1313, details={'reasoning_tokens': 1152}),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 10, 1, 19, 55, 9, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_0d14a5e3c26c21180068dd871d439081908dc36e63fab0cedf',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_image_generation_with_tools(allow_model_requests: None, openai_api_key: str):
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model=model, output_type=BinaryImage)
|
|
|
|
@agent.tool_plain
|
|
async def get_animal() -> str:
|
|
return 'axolotl'
|
|
|
|
result = await agent.run('Generate an image of the animal returned by the get_animal tool.')
|
|
assert result.output == snapshot(IsInstance(BinaryImage))
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='Generate an image of the animal returned by the get_animal tool.',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_0481074da98340df0068dd88e41588819180570a0cf50d0e6e',
|
|
signature=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
ToolCallPart(
|
|
tool_name='get_animal',
|
|
args='{}',
|
|
tool_call_id='call_t76xO1K2zqrJkawkU3tur8vj',
|
|
id='fc_0481074da98340df0068dd88f000688191afaf54f799b1dfaf',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(input_tokens=389, output_tokens=721, details={'reasoning_tokens': 704}),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 10, 1, 20, 2, 36, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_0481074da98340df0068dd88dceb1481918b1d167d99bc51cd',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelRequest(
|
|
parts=[
|
|
ToolReturnPart(
|
|
tool_name='get_animal',
|
|
content='axolotl',
|
|
tool_call_id='call_t76xO1K2zqrJkawkU3tur8vj',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
NativeToolCallPart(
|
|
tool_name='image_generation',
|
|
tool_call_id='ig_0481074da98340df0068dd88fb39c0819182d36f882ee0904f',
|
|
provider_name='openai',
|
|
),
|
|
FilePart(
|
|
content=IsInstance(BinaryImage),
|
|
id='ig_0481074da98340df0068dd88fb39c0819182d36f882ee0904f',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='image_generation',
|
|
content={
|
|
'status': 'completed',
|
|
'background': 'opaque',
|
|
'quality': 'high',
|
|
'size': '1024x1024',
|
|
'revised_prompt': IsStr(),
|
|
},
|
|
tool_call_id='ig_0481074da98340df0068dd88fb39c0819182d36f882ee0904f',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content='', id='msg_0481074da98340df0068dd8934b3f48191920fd2feb9de2332', provider_name='openai'
|
|
),
|
|
],
|
|
usage=RequestUsage(input_tokens=1294, output_tokens=65, details={'reasoning_tokens': 0}),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 10, 1, 20, 2, 56, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_0481074da98340df0068dd88f0ba04819185a168065ef28040',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_multiple_images(allow_model_requests: None, openai_api_key: str):
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model=model, output_type=BinaryImage)
|
|
|
|
result = await agent.run('Generate two separate images of axolotls.')
|
|
# The first image is used as output
|
|
assert result.output == snapshot(IsInstance(BinaryImage))
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='Generate two separate images of axolotls.',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_0b6169df6e16e9690068dd80d6daec8191ba71651890c0e1e1',
|
|
signature=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='image_generation',
|
|
tool_call_id='ig_0b6169df6e16e9690068dd80f7b070819189831dcc01b98a2a',
|
|
provider_name='openai',
|
|
),
|
|
FilePart(
|
|
content=IsInstance(BinaryImage),
|
|
id='ig_0b6169df6e16e9690068dd80f7b070819189831dcc01b98a2a',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='image_generation',
|
|
content={
|
|
'status': 'completed',
|
|
'background': 'opaque',
|
|
'quality': 'high',
|
|
'size': '1024x1024',
|
|
'revised_prompt': IsStr(),
|
|
},
|
|
tool_call_id='ig_0b6169df6e16e9690068dd80f7b070819189831dcc01b98a2a',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='image_generation',
|
|
tool_call_id='ig_0b6169df6e16e9690068dd8125f4448191bac6818b54114209',
|
|
provider_name='openai',
|
|
),
|
|
FilePart(
|
|
content=IsInstance(BinaryImage),
|
|
id='ig_0b6169df6e16e9690068dd8125f4448191bac6818b54114209',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='image_generation',
|
|
content={
|
|
'status': 'completed',
|
|
'background': 'opaque',
|
|
'quality': 'high',
|
|
'size': '1536x1024',
|
|
'revised_prompt': IsStr(),
|
|
},
|
|
tool_call_id='ig_0b6169df6e16e9690068dd8125f4448191bac6818b54114209',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content='', id='msg_0b6169df6e16e9690068dd8163a99c8191ae96a95eaa8e6365', provider_name='openai'
|
|
),
|
|
],
|
|
usage=RequestUsage(
|
|
input_tokens=2675,
|
|
output_tokens=2157,
|
|
details={'reasoning_tokens': 1984},
|
|
),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 10, 1, 19, 28, 22, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_0b6169df6e16e9690068dd80d64aec81919c65f238307673bb',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_image_generation_jpeg(allow_model_requests: None, openai_api_key: str):
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(
|
|
model=model, capabilities=[NativeTool(ImageGenerationTool(output_format='jpeg'))], output_type=BinaryImage
|
|
)
|
|
|
|
result = await agent.run('Generate an image of axolotl.')
|
|
|
|
assert result.output == snapshot(IsInstance(BinaryImage))
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='Generate an image of axolotl.',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_08acbdf1ae54befc0068dd9cee0698819791dc1b2461291dbe',
|
|
signature=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='image_generation',
|
|
tool_call_id='ig_08acbdf1ae54befc0068dd9d0347bc8197ad70005495e64e62',
|
|
provider_name='openai',
|
|
),
|
|
FilePart(
|
|
content=IsInstance(BinaryImage),
|
|
id='ig_08acbdf1ae54befc0068dd9d0347bc8197ad70005495e64e62',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='image_generation',
|
|
content={
|
|
'status': 'completed',
|
|
'background': 'opaque',
|
|
'quality': 'high',
|
|
'size': '1536x1024',
|
|
'revised_prompt': IsStr(),
|
|
},
|
|
tool_call_id='ig_08acbdf1ae54befc0068dd9d0347bc8197ad70005495e64e62',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content='', id='msg_08acbdf1ae54befc0068dd9d468248819786f55b61db3a9a60', provider_name='openai'
|
|
),
|
|
],
|
|
usage=RequestUsage(input_tokens=1889, output_tokens=1434, details={'reasoning_tokens': 1280}),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 10, 1, 21, 28, 13, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_08acbdf1ae54befc0068dd9ced226c8197a2e974b29c565407',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_history_with_combined_tool_call_id(allow_model_requests: None, openai_api_key: str):
|
|
m = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
|
|
class CityLocation(BaseModel):
|
|
city: str
|
|
country: str
|
|
|
|
agent = Agent(m, output_type=ToolOutput(CityLocation))
|
|
|
|
messages = [
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What is the largest city in the user country?',
|
|
)
|
|
]
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ToolCallPart(
|
|
tool_name='get_user_country',
|
|
args='{}',
|
|
tool_call_id='call_ZWkVhdUjupo528U9dqgFeRkH|fc_68477f0bb8e4819cba6d781e174d77f8001fd29e2d5573f7',
|
|
)
|
|
],
|
|
model_name='gpt-4o-2024-08-06',
|
|
provider_name='openai',
|
|
provider_response_id='resp_68477f0b40a8819cb8d55594bc2c232a001fd29e2d5573f7',
|
|
finish_reason='stop',
|
|
),
|
|
ModelRequest(
|
|
parts=[
|
|
ToolReturnPart(
|
|
tool_name='get_user_country',
|
|
content='Mexico',
|
|
tool_call_id='call_ZWkVhdUjupo528U9dqgFeRkH|fc_68477f0bb8e4819cba6d781e174d77f8001fd29e2d5573f7',
|
|
)
|
|
]
|
|
),
|
|
]
|
|
|
|
result = await agent.run('What is the largest city in the user country?', message_history=messages)
|
|
assert result.output == snapshot(CityLocation(city='Mexico City', country='Mexico'))
|
|
assert result.new_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What is the largest city in the user country?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_001fd29e2d5573f70068ece2e816fc819c82755f049c987ea4',
|
|
signature='gAAAAABo7OLt_-yMcMz15n_JkwU0selGH2vqiwJDNU86YIjY_jQLXid4usIFjjCppiyOnJjtU_C6e7jUIKnfZRBt1DHVFMGpAVvTBZBVdJhXl0ypGjkAj3Wv_3ecAG9oU3DoUMKrbwEMqL0LaSfNSN1qgCTt-RL2sgeEDgFeiOpX40BWgS8tVMfR4_qBxJcp8KeYvw5niPgwcMF3UPIEjHlaVpglJH2SzZtTOdxeFDfYbnvdWTMvwYFIc0jKOREG_-hZE4AznhHdSLV2-I5nGlxuxqaI4GQCk-Fp8Cvcy15_NYYP62ii50VlR6HPp_gQZEetwgC5pThsiuuG7-n1hGOnsj8gZyjSKsMe2KpzlYzhT7ighmArDVEx8Utvp1FXikqGkEzt4RTqqPInp9kuvqQTSyd8JZ6BEetRl1EuZXT7zXrzLwFN7Vm_gqixmf6mLXZUw6vg6LqGkhSh5fo6C7akPTwwJXjVJ37Dzfejo6RiVKOT-_9sdYCHW2kZ9XfQAmRQfB97UpSZ8QrVfaKy_uRIHLexs8QrQvKuw-uHDQBAL3OEmSTzHzCQ-q7b0FHr514Z29l9etavHNVdpeleWGo6VEtLWGQyblIdIBtf946YnQvr6NYIR8uATn9Z91rr8FsFJTpJh_v5iGA2f8rfPRu27nmw-q8XnPVc_FYCZDk08r_YhdEJZn1INBi8wYSWmpib8VxNpkFO7FFRuK-F8rh3MTpYgIOqPQYbf3LCRvKukTwv1b3mjSKVpHQSm_s6s7djdD-rLuc22-3_MLd0ii4_oOT8w51TQIM61LtonGvxUqf4oKHSUFCVnrWWiT-0ttdpwpJ_iB5frnEeY2mWyU1u7sd38BI3dOzoM82IFaIm98g9fa99bmoA7Z7gI60tzyF8YbJmWF-PCwyKHJ7B1MbCBonO36NmeEM-SplrR54fGykxTmwvtbYGhd5f0cdYzD0zulRDj-AhOd96rrUB_fIgoQGTXey8L_w0whcnVTWdG6is-rx8373Sz8ZRoE5RiLWW1mfHzVXxwslphx4BedRVF0tL-1YO7sg5MXhHCf6hpw8dOht-21NMrb1F1DQadFE_fhySFl-TgOD5BlhAuupLMsqcCIa4lcXP_loyA4ERP6WSdz2Bybz7_1eOiflfVodRrNqvr_DnL0NEXD_JkYTeIn84ziarFV7U7ZnkMvRiA_p1fWdbHTsE_8lu1rsf8fcJ1e76_6ycPkOc4TrOZw8gVRb7gIbMMVrv72BT_sFhW7GkXrzCQpQaeybmRw-bjFhkMMjMDYGXkA_H0q2Zfyh3zCOoa40hl2cqRWp7n1XuafmtKG_F8e9hyWox0q7AhZr5HOOaHz8r3O3-dmNl1KP52bqA8S72rLDslAOQlDupmAQgAmkm5ApYeYcEBredN78jHQ1pviUEI2-3qr4ClXZFHPa54AJ_q4HQ-EcKXEcYQglG21mSUy_tFQF-m4X46Qu8yYWcBVW4E0CG3wbvYx0BCdbc5RhIDkJo1elxLK8XS64lpFkCWy62xLVeMuVuCj8q84-Kk7tZ7gtMtLV9PHQCdbl3s2pAzMfuNIBJog6-HPmwha2n9T0Md5qF7OqCtnYWOWUfIMmQVcdW-ECGsQy9uIUmpsOjdtH31hrX3MUEhIOUB5xErLwfp-_s22ciAY_ap3JlYAiTKGlMCxKxTzK7wWEG_nYhDXC1Afj2z-tgvYhtn9MyDf2v0aIpDM9BoTOLEO-ButzylJ06pJlrJhpdvklvwJxUiuhlwy0bHNilb4Zv4QwnUv3DCrIeKe1ne90vEXe6YlDwSMeWJcz1DZIQBvVcNlN8q2y8Rae3lMWzsvD0YXrcXp02ckYoLSOQZgNYviGYLsgRgPGiIkncjSDt7WWV6td3l-zTrP6MT_hKigmg5F5_F6tS1bKb0jlQBZd0NP-_L_TPqMGRjCYG8johd6VyMiagslDjxG39Dh2wyTI19ZW7h_AOuOpnfkt2armqiq6iGfevA3malqkNakb6mFAS04J9O0butWVAw4yiPCEcLuDNAzzi_qrqLee4gkjh0NplvfGCaE6qqYms61GJbJC4wge6vjyTakurbqWEV3YoR3y_dn-0pjQ7TOx9kkruDwg0nZIV5O6yYxaulmbuvo3fs5CZb9ptZPD0MzGZj7CZU2MDCa4a4gr0McOx2MricxSzIu6emuRUzZuC6C1JxPRC00M0TrZNMIe_WVa9fXDLV1ULEAIMwMXzNT9zV6yiYQCwhkp30Wqde3W0LlIRpSbDuJXcvT8OCbXkdPNIScccdT9LvUQQ--hU2P45kisOev3TYn7yv-pdxM3u1KFNwuFxedSArMBPg7GDz1BOxDQRzv0mfwbf_CcoFbuyj7Tf4zWO46HVdHeRNbvIE--bnaSYD-UFaKknp8ZsBQQhBU_2TEca3fKwmg81-g7Vdb28QUZEuPzgE4ekxZejkKpiKqlLC5nJYgvXrqk2H35D51mYdzPs0ST05Mc41x9MFm_YOLxSFyA0yGAKVINmD5wT6kvRflPkgoksd2ryIvo4KMw3oZQKodv5By0mSJ8iX2vhTGylxiM8wj-ICyNuOsaRFrcMSpX7tZbXcDyysApdmx217BSADoQiNZBLngF7ptxc2QGyo3CwuDjaljwmSgL9KeGthd1RJFd826M287IPpCjLM4WRquCL_E0pQryNqOMn-ZEOCAlBjE37290EhkjKbhiGBEnHUvSbhoH4nL47AmunP_Q5aqh5173VfyoyaybuS3fXjQ5WO0kyFjMdD-a7C6PVdwToCTP-TljoF2YnQKCiqUGs9gNHS9mYhQSXzY4uuGlTHLfKB4JKS5_MQHvwI9zCbTvVG854fPuo_2mzSh-y8TSzBWPokhYWI_q095Sh6tOqDIJNMGyjI2GDFRSyKpKhIFCLyU2JEo9B6l91jPlir0XI8ZOQfBd9J0I4JIqnyoj40_1bF1zUDGc014bdGfxazxwlGph_ysKAP39wV7X9DBFS3ZmeSIn-r3s-sci0HmwnJUb2r03m40rFuNTV1cJMAFP7ZY7PQQQ0TtlO_al0uedaOWylLauap_eoRqc6xGJ2rSz1e7cOevksUlAqzK5xknYKHlsW970xuDGHKOZnKPg8O9nb2PKrcjwEQF5RFPc3l8TtOUXPhhvTERZFGoEuGuSuSp1cJhzba06yPnL-wE3CstYUm3jvkaUme6kKqM4tWBCQDg-_2PYf24xXYlmkIklylskqId826Y3pVVUd7e0vQO0POPeVYU1qwtTp7Ln-MhYEWexxptdNkVQ-kWx63w6HXF6_kefSxaf0UcvL8tOV73u7w_udle9MC_TXgwJZpoW2tSi5HETjQ_i28FAP2iJmclWOm3gP08cMiXvgpTpjzh6meBdvKepnifl_ivPzRnyjz3mYCZH-UJ4LmOHIonv-8arnckhCwHoFIpaIX7eSZyY0JcbBETKImtUwrlTSlbD8l02KDtqw2FJURtEWI5dC1sTS8c2HcyjXyQDA9A25a0M1yIgZyaadODGQ1zoa9xXB',
|
|
provider_name='openai',
|
|
),
|
|
ToolCallPart(
|
|
tool_name='final_result',
|
|
args='{"city":"Mexico City","country":"Mexico"}',
|
|
tool_call_id='call_LIXPi261Xx3dGYzlDsOoyHGk',
|
|
id='fc_001fd29e2d5573f70068ece2ecc140819c97ca83bd4647a717',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(input_tokens=103, output_tokens=409, details={'reasoning_tokens': 384}),
|
|
model_name='gpt-5-2025-08-07',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 10, 13, 11, 30, 47, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_001fd29e2d5573f70068ece2e6dfbc819c96557f0de72802be',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelRequest(
|
|
parts=[
|
|
ToolReturnPart(
|
|
tool_name='final_result',
|
|
content='Final result processed.',
|
|
tool_call_id='call_LIXPi261Xx3dGYzlDsOoyHGk',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_builtin_tool_call_id_uses_id_field(allow_model_requests: None):
|
|
"""Regression test: NativeToolCallPart.id should be preferred over tool_call_id when replaying.
|
|
|
|
The Responses API generates long id fields (~53 chars) for ResponseFunctionWebSearch items.
|
|
These are stored as both tool_call_id and id on NativeToolCallPart. When replayed via
|
|
_map_messages, the id field should be used to preserve the original Responses API item id.
|
|
See: https://github.com/pydantic/pydantic-ai/issues/4389
|
|
"""
|
|
mock_client = MockOpenAIResponses.create_mock(
|
|
response_message(
|
|
[
|
|
ResponseOutputMessage(
|
|
id='msg_123',
|
|
content=cast(
|
|
list[Content],
|
|
[ResponseOutputText(text='Here is the answer.', type='output_text', annotations=[])],
|
|
),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
),
|
|
],
|
|
)
|
|
)
|
|
model = OpenAIResponsesModel(
|
|
'gpt-5',
|
|
provider=OpenAIProvider(openai_client=mock_client),
|
|
settings=OpenAIResponsesModelSettings(openai_send_reasoning_ids=True),
|
|
)
|
|
|
|
long_id = 'ws_67e886540a108191a3db18a0336eb0f80bb7f4baa8488460a1' # 53 chars, typical Responses API id
|
|
|
|
messages: list[ModelRequest | ModelResponse] = [
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(content='Search the web for Python news'),
|
|
]
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
NativeToolCallPart(
|
|
tool_name='web_search',
|
|
tool_call_id=long_id,
|
|
args={'query': 'Python news'},
|
|
provider_name='openai',
|
|
id=long_id,
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='web_search',
|
|
tool_call_id=long_id,
|
|
content={'status': 'completed'},
|
|
provider_name='openai',
|
|
),
|
|
TextPart(content='Here are the results.', id='msg_456', provider_name='openai'),
|
|
],
|
|
model_name='gpt-4o',
|
|
provider_name='openai',
|
|
),
|
|
]
|
|
|
|
_, openai_messages = await model._map_messages( # type: ignore[reportPrivateUsage]
|
|
messages,
|
|
model_settings=cast(OpenAIResponsesModelSettings, model.settings or {}),
|
|
model_request_parameters=ModelRequestParameters(),
|
|
)
|
|
|
|
# Find the web_search_call item in the output and verify the id field is preserved
|
|
web_search_items = [m for m in openai_messages if isinstance(m, dict) and m.get('type') == 'web_search_call']
|
|
assert len(web_search_items) == 1
|
|
web_search_item = cast(dict[str, Any], web_search_items[0])
|
|
assert web_search_item['id'] == long_id
|
|
|
|
|
|
async def test_openai_responses_mcp_call_replays_empty_tool_args(allow_model_requests: None):
|
|
c = response_message(
|
|
[
|
|
ResponseOutputMessage(
|
|
id='msg_123',
|
|
content=cast(list[Content], [ResponseOutputText(text='ok', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
)
|
|
]
|
|
)
|
|
mock_client = MockOpenAIResponses.create_mock(c)
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(openai_client=mock_client))
|
|
|
|
messages: list[ModelRequest | ModelResponse] = [
|
|
ModelRequest(parts=[UserPromptPart(content='Call current_time')]),
|
|
ModelResponse(
|
|
parts=[
|
|
NativeToolCallPart(
|
|
tool_name='mcp_server:clock',
|
|
tool_call_id='mcp_123',
|
|
args={'action': 'call_tool', 'tool_name': 'current_time', 'tool_args': {}},
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='mcp_server:clock',
|
|
tool_call_id='mcp_123',
|
|
content={'output': '2026-05-06T00:00:00Z', 'error': None},
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
provider_name='openai',
|
|
),
|
|
ModelRequest(parts=[UserPromptPart(content='What did you call?')]),
|
|
]
|
|
|
|
await model.request(
|
|
messages,
|
|
cast(OpenAIResponsesModelSettings, {'openai_send_reasoning_ids': True}),
|
|
ModelRequestParameters(
|
|
native_tools=[MCPServerTool(id='clock', url='https://example.com/mcp', allowed_tools=['current_time'])]
|
|
),
|
|
)
|
|
|
|
response_kwargs = get_mock_responses_kwargs(mock_client)[0]
|
|
assert response_kwargs['input'][1] == snapshot(
|
|
{
|
|
'id': 'mcp_123',
|
|
'server_label': 'clock',
|
|
'name': 'current_time',
|
|
'arguments': '{}',
|
|
'error': None,
|
|
'output': None,
|
|
'type': 'mcp_call',
|
|
}
|
|
)
|
|
|
|
|
|
async def test_openai_responses_model_mcp_server_tool(allow_model_requests: None, openai_api_key: str):
|
|
m = OpenAIResponsesModel(
|
|
'o4-mini',
|
|
provider=OpenAIProvider(api_key=openai_api_key),
|
|
)
|
|
agent = Agent(
|
|
m,
|
|
instructions='You are a helpful assistant.',
|
|
capabilities=[
|
|
NativeTool(
|
|
MCPServerTool(
|
|
id='deepwiki',
|
|
url='https://mcp.deepwiki.com/mcp',
|
|
description='DeepWiki MCP server',
|
|
allowed_tools=['ask_question'],
|
|
headers={'custom-header-key': 'custom-header-value'},
|
|
)
|
|
),
|
|
],
|
|
)
|
|
|
|
result = await agent.run('Can you tell me more about the pydantic/pydantic-ai repo? Keep your answer short')
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='Can you tell me more about the pydantic/pydantic-ai repo? Keep your answer short',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
instructions='You are a helpful assistant.',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
NativeToolCallPart(
|
|
tool_name='mcp_server:deepwiki',
|
|
args={'action': 'list_tools'},
|
|
tool_call_id='mcpl_0083938b3a28070e0068fabd81d51081a09d4b183ced693273',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='mcp_server:deepwiki',
|
|
content={
|
|
'tools': [
|
|
{
|
|
'input_schema': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'repoName': {
|
|
'type': 'string',
|
|
'description': 'GitHub repository: owner/repo (e.g. "facebook/react")',
|
|
},
|
|
'question': {
|
|
'type': 'string',
|
|
'description': 'The question to ask about the repository',
|
|
},
|
|
},
|
|
'required': ['repoName', 'question'],
|
|
'additionalProperties': False,
|
|
'$schema': 'http://json-schema.org/draft-07/schema#',
|
|
},
|
|
'name': 'ask_question',
|
|
'annotations': {'read_only': False},
|
|
'description': 'Ask any question about a GitHub repository',
|
|
}
|
|
],
|
|
'error': None,
|
|
},
|
|
tool_call_id='mcpl_0083938b3a28070e0068fabd81d51081a09d4b183ced693273',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_0083938b3a28070e0068fabd84727c81a0a52c171d2568a947',
|
|
signature='gAAAAABo-r2bs6ChS2NtAXH6S8ZWRHzygQvAZrQGsb5ziJKg6dINF9TQnq4llBquiZh-3Ngx2Ha4S-2_TLSbgcsglradULI8c8N2CnilghcqlLE90MXgHWzGfMDbmnRVpTW9iJsOnBn4ferQtNLIsXzfGWq4Ov0Bbvlw_fCm9pQsqOavcJ5Kop2lJ9Xqb__boYMcBCPq3FcNlfC3aia2wZkacS4qKZGqytqQP13EX3q6LwFVnAMIFuwn5XLrh4lFf-S5u8UIw3C6wvVIXEUatY6-awgHHJKXxWUxqRQPJegatMb8KE-QtuKQUfdvEE0ykdHtWqT7nnC3qTY67UaSCCvJ9SdXj-t806GVei9McSUe8riU3viHnfY0R0u9GIXsVnfVthIDRnX7KzpF5ot_CpCrgbCmD9Rj2AAos5pCdSzpc08G5auUuuMZfoiWANADTHHhO2OvflSEpmO8pb-QAYfMoK9exYVQ8Oig-Nj35unupcYy7A2bDCViXzqy32aw9QHmH7rErI4v72beWQxRVdX15Z7VS2c6L1dD7cU18K35CWqlSz9hEX5AcGqEEtIDVu1TdF3m1m2u4ooc4TjYpRecjYoG8Ib-vVKoX5C65a7G1cTbCo8dO0DYKGgM8jM7ZDubxbCcZ22Sxk58f8cer7WxHyp7WRo5-6zvMwMCk8uEY44RJmg-m0Oxl_6qxdr4Md80xZah_6tCCB62agQmYwCrR75_r93xOckQAK0R_37khvQD5gWVlE5Rg-01eUTboiPGqYmIsqWvOkziMGnxgKVw_yUf8swHU1ciWr7O1EdVPHLG7YXlVQTHTE_CX3uOsE2FoZnpS_MgpxGfjb76majV50h7mJ6ySVPF_3NF3RQXx64W08SW4eVFD8JJf0yChqXDmlwu2CDZN1n99xdaE9QbMODNEOmfTQOPhQ9g-4LhstNTKCCxWDh0qiv_dq2qAd0I9Gupoit33xGpb66mndc0nuuNFe8-16iC_KzQtHBNzgasgYK-r83KFVmiYK3Jxvz_2dfdwe0M1q7NLBvbnWc6k9LIf8iDUF6Q1J-cfC7SsncCbROtzIPlKpQwxhP-M09Xy3RVxlH9dcvuk3_qqEAartUQC8ZbuLRbhiq66eE1RvQzdNd2tsoBQ85cdNs57Penio7w9zILUf1JP5O8-zCe5GPC3W3EXTIEvHR-kiuxJvhcsySijpldGmuygRx05ARNOIT7VDCZvF23RfmnRduY1X1FAqb_i_aMStK7iyHr_2ohwOWLuklpyuoG0Y1ulvq1A9-hyCZ0mpvTEF6om2tAZ9_7h8W9ksiOkey0yA-6ze17MCjfnK2XcbqmSMgOngW1PrD81oKoheMnIeJdcWgF2mk8VDqmAwaDTxMxdnXkzK74rA43a4rWk3d2bUts8dAUkuYXTwJwKQw4LfXtu-mwwgJ6BkT_GiBcBJ6ulBuPsNZfpwPuxox6PS6KpzVTQ94cKNqSIIyFCD4xZsEvPALud09-gmAEDHxdnPjqLSi2U8xd0j-6XYKN0JtZ45kwEIRsOrFu-SYLz1OcYFKI5A5P-vYlzGx1WhEnoeUlyooJBhNj6ZBfj9f63SByxm7sgh260vf1t-4OGzVTIUKFluxkI4ubigLZ-g4q4dSwiEWXn50JFPrtuPs5VxsIIz_lXbh1SrKeQ647KdDSAQZFgEfzOOt3el5K97V1x7V7gEWCCgmqDIz3yZPpwD6qmUQKqlj_p8-OQrniamGULkXrmrgbNQVfV-Qw7Hg6ELw4aHF_IZME9Qnyn7peFhH6ai_YapuNF7FK-MBtPYoMaqBf05U2-uJAVUas3VuT_-pTyHvhtFmB7vc0-qgf_CtVNIXSPq2_vXdQdEwwCVPPwW6xWm-invrzhyQR_mf3OQqZT6_zOHIMPBJUaXcQKT0KTdoBZUDamAR-ECZl8r6wdLCn0HjAEwj3ifUCNMzQ7CZHUQG46rj61YyasNWO__4Ef4kTcApKgljosuABqP4HAdmkP5eEnX-6nutrL50iv-Mms_R-T7SKtmEEf9wihTu4Meb441cU9DI4WwSyiBSnsYdGy9FJKmHwP7HD0FmpmWkOrtROkQVMlMVKQFlKK8OBtxafHYsZkWDawbA1eetzMBzQ3PP8PSvva6SJWjbgURHVm5RjXV8Hk6toIBEDx9r9vAIczSp49eDCkQbzPkGAVilO3KLQpNx2itBbZzgE36uV0neZZsVs7aqafI4qCTQOLzYA8YFDKz92yhgdIzl5VPFLFNHqRS4duPRQImQ7vb6yKSxjDThiyQQUTPBX_EXUAAR7JHwJI1i8la3V',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='mcp_server:deepwiki',
|
|
args={
|
|
'action': 'call_tool',
|
|
'tool_name': 'ask_question',
|
|
'tool_args': {
|
|
'repoName': 'pydantic/pydantic-ai',
|
|
'question': 'Provide a brief summary of the repository, including purpose, main features, and status.',
|
|
},
|
|
},
|
|
tool_call_id='mcp_0083938b3a28070e0068fabd88db5c81a08e56f163bbc6088b',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='mcp_server:deepwiki',
|
|
content={
|
|
'output': """\
|
|
Pydantic AI is a Python agent framework designed to build production-grade applications using Generative AI, emphasizing an ergonomic developer experience and type-safety . It provides type-safe agents, a model-agnostic design supporting over 15 LLM providers, structured outputs with Pydantic validation, comprehensive observability, and production-ready tooling . The project is structured as a UV workspace monorepo, including core framework components, an evaluation system, a graph execution engine, examples, and a CLI tool .
|
|
|
|
## Purpose <cite/>
|
|
|
|
The primary purpose of Pydantic AI is to simplify the development of reliable AI applications by offering a robust framework that integrates type-safety and an intuitive developer experience . It aims to provide a unified approach to interacting with various LLM providers and managing complex agent workflows .
|
|
|
|
## Main Features <cite/>
|
|
|
|
### Type-Safe Agents <cite/>
|
|
Pydantic AI agents are generic `Agent[Deps, Output]` for compile-time validation, utilizing `RunContext[Deps]` for dependency injection and Pydantic `output_type` for output validation . This ensures that the inputs and outputs of agents are strictly typed and validated .
|
|
|
|
### Model-Agnostic Design <cite/>
|
|
The framework supports over 15 LLM providers through a unified `Model` interface, allowing developers to switch between different models without significant code changes . Implementations for providers like OpenAI, Anthropic, and Google are available .
|
|
|
|
### Structured Outputs <cite/>
|
|
Pydantic AI leverages Pydantic for automatic validation and self-correction of structured outputs from LLMs . This is crucial for ensuring data integrity and reliability in AI applications .
|
|
|
|
### Comprehensive Observability <cite/>
|
|
The framework includes comprehensive observability features via OpenTelemetry and native Logfire integration . This allows for tracing agent runs, model requests, tool executions, and monitoring token usage and costs .
|
|
|
|
### Production-Ready Tooling <cite/>
|
|
Pydantic AI offers an evaluation framework, durable execution capabilities, and protocol integrations .
|
|
* **Tool System**: Tools can be registered using the `@agent.tool` decorator, with automatic JSON schema generation from function signatures and docstrings .
|
|
* **Graph Execution**: The `pydantic_graph.Graph` module provides a graph-based state machine for orchestrating agent execution, using nodes like `UserPromptNode`, `ModelRequestNode`, and `CallToolsNode` .
|
|
* **Evaluation Framework**: The `pydantic-evals` package provides tools for creating datasets, running evaluators (e.g., `ExactMatch`, `LLMEvaluator`), and generating reports .
|
|
* **Integrations**: It integrates with various protocols and environments, including Model Context Protocol (MCP) for external tool servers, AG-UI for interactive frontends, and Temporal/DBOS for durable execution .
|
|
|
|
## Status <cite/>
|
|
The project is actively maintained and considered "Production/Stable" . It supports Python versions 3.10 through 3.13 . The documentation is built using MkDocs and includes API references and examples .
|
|
|
|
## Notes <cite/>
|
|
The repository is organized as a monorepo using `uv` for package management . Key packages include `pydantic-ai-slim` (core framework), `pydantic-evals` (evaluation system), `pydantic-graph` (graph execution engine), `examples` (example applications), and `clai` (CLI tool) .
|
|
|
|
Wiki pages you might want to explore:
|
|
- [Overview (pydantic/pydantic-ai)](/wiki/pydantic/pydantic-ai#1)
|
|
|
|
View this search on DeepWiki: https://deepwiki.com/search/provide-a-brief-summary-of-the_a5712f6e-e928-4886-bcea-b9b75761aac5
|
|
""",
|
|
'error': None,
|
|
},
|
|
tool_call_id='mcp_0083938b3a28070e0068fabd88db5c81a08e56f163bbc6088b',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_0083938b3a28070e0068fabd97008081a0ad1b2362bcb153c9',
|
|
signature='gAAAAABo-r2bD-v0Y3pAlyAEK1Sb8qJJcJRKSRtYwymHwLNXY-SKCqd_Q5RbN0DLCclspuPCAasGLm1WM1Q2Y_3szaEEr_OJalXTVEfRvhCJE1iTgoz2Uyf7KttZ4W92hlYjE8cjgdo5tKtSVkNyzTs4JUHKRHoDMutL2KivjZKuK_4n-lo9paJC_jmz6RWO8wUoXo3_fGxjliOGnWyRXwEPmgAcEWNOSVgCgAEO3vXerXRPLie02HegWcLMtK6WORDHd02Kr86QSK3W30bnvU7glAFX6VhSSnR8G0ceAM-ImoomQ8obEDyedX1-pYDKPOa4pZ5iTjD24ABYOwz-0L7SNziQJLycwwsr11Fj0_Au9yJph8YkNb2nAyFeiNVCRjKul51B7dZgz-UZ9juWO2ffeI0GNtQTYzf46_Y1t0qykGW6w59xjmBHTKf5SiSe0pqWxZ6LOLoPx01rX2gLaKgNZZiERSbO0iwbA4tpxb9ur-qeFVv5tS7xy8KFYOa8SPrypvFWDoY6CjSwTS3ir0vyfpbJy-n6bcYP_pTwDZxy_1aVkciim8Tmm_9wYgI0uY5kcA9VYJuyc4cg7S7ykTUxMZz7xiLMf8FoXl1gHbVJrYriyZzh2poYTWlcCuSCiUaXhQKxcxMRrt_P7WANx0n68ENQ40HkoJ6rThvWUuwtmEYqZ0ldh3XSFtyNrqha4PQ5eg_DudlU_5CxyykuzWmi_o5MEW4_XW4b9vdXg1laqx4189_jEuV_JPGNeL3Ke4EbMbKHzsiaGePRZGgNutnlERagmU4VFTeoE5bN3oHlR_Au4PeQxdb7BuBmZRDDCnnIRd2NfSWb7bgfUozkA4S6rm_089OlRBeRVoLtA8zZZinNGtOZl7MtkLnoJVIWpF1rr7D_47eWSyyegUIIS2e5UKLJfCLkNgSlWPU9VquHEzSfqeHfzoN5ccoVwrvrHmeveTjI-wIJygdfuyti5cMgOOkAtLzjWmbs4CjmlWcbZKeidtDj5YpCSmYAGFuZze-cSbNjMv4th639dCu_jmRMze-l2Y5npbRwMqEJr7VLXghmLc1vhOsaQM3gxoF0CJJlmvtR4jxPqhE3694YRva6LS1WjR4oueM6zfpVeB2kC0hQgqaL6MiwtTRYFfuCzEHi18TwA5bqqkfgrDXedmjAzlEGSZFe2EBRlF_ZtagrVVTCagHQArnH3DkVQMEDCHCqDxA_PINR_997IxeNgGPsvazVdOOBef7sO4rvAWrC94nIlt7d4aViqbTNMW-W8rqjGFOqj1swrM0yoX5y6LY5oXPc3Mu35xeitn_paqtGPkvuH6WeGzAiNZFDoQkUdLkZ4SIH2lr4ZXmMI3nuTzCrwyshwcEu-hhVtGAEQEqVrIn8J75IzYTs1UGLBvhmcpHxCfG04MFNoVf-EPI4SgjNEgV61861TYshxCRrydVhaJmbLqYh8yzLYBHK6oIymv-BrIJ0LX222LwoGbSc0gMTMaudtthlFXrHdnswKf81ubhF7viiD3Y=',
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content=IsStr(),
|
|
id='msg_0083938b3a28070e0068fabd989bb481a08c61416ab343ef49',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(input_tokens=1207, output_tokens=535, details={'reasoning_tokens': 320}),
|
|
model_name='o4-mini-2025-04-16',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 10, 23, 23, 42, 57, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_0083938b3a28070e0068fabd81970881a0a1195f2cab45bd04',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
messages = result.all_messages()
|
|
result = await agent.run('What packages does the repo contain?', message_history=messages)
|
|
assert result.new_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What packages does the repo contain?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
instructions='You are a helpful assistant.',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_0083938b3a28070e0068fabd9de42881a08fbb49a65d0f9b06',
|
|
signature='gAAAAABo-r2izZacxe_jVh_p3URhewxBJuyLNqkJOd0owsDPt9uCE7MXn06WHhO_mp6gLDAqcF1uhMhXCqwztJ1Nbpc0cEDAxUpUCUn2bKSgG6r8Snc_FPtKGgQWDsByvW_Nigx55CyPuNeDO_MiDgYee_WeUw7ASLPfiGOx_9YNc_BFYo1ngsb8CKZcJn3AoponMheLoxkVAPgOjMgteRVaQTr13MljTDUlBIZLIOhVbtIu_dI23saXPigbgwR4RhGn5mCHG_a9ILNkXDJUmGy5TKklIEi2HuJM3ZJ3gfoGYS3OONvzmU4AgMP2UrU17YKZAYKxUBKSpyAqigd4RJSYWzxBCoYzCTmiITwdZ6Cpsw1X9Wox_TQSGt5G2Xu0UY2TQZGRNNH8knJpWs-UQxBBV4L3alMwJuIeV-uzqeKr5fKO5rL_c9as-qQIW_EGQItjvR5z80Hi-S9VXthWCmtqZFIJkgLB5JfTYuFL86valsFVLzSavUIWJAG5qOcxag2mbZMwMRRNfvR__BBtoqBoeGIqveQAbIeZbG0ymw30PH1a2v1mmSrpkK6PB3AHYRDdpkezXLkbyGYgidyV2DAAtPaFplsubWCh_74UxmOuk4BH-9cWkE15mRUBrvtnbTb793RsPzOe7nPmkMpdgqa3nqc6RcQZ_M30lFLUViAbfpEpMVrCzz2cv1RklT1JUzpuVXBTKqQ4FxVCfnvzSgQ2INQ8K50E1X5w_7TAWhrHbNg6LetCa-4KWe9ps0GH6r1x9FWvGyVxSwa7SIdPq3sGpxjOydluPECbBOnHWFUB-3rI2DcUl4rGWYbv2FEFNeCH9Zr67uUvMc4Doi8nVMoeb1lJxFCrfziGhbEXY0FepH3zIzlj-_dXqLAL1qqhfCznT_xkDMVYg-D5gMu-_p3r2SirjJbeaz5UFmP-Dihd9v7jWgD6hx_Mq1uIdzIPE8ImGiDPR7PK64svkvwYg1Czdrc_7GmrKRuzsBL0720UXe19NQqCZfYvUJAjgbEqr3tuS_RkhuEQeeVORn88xkhkrGCEgBS0LHFpe4tcnUEXKnaYYRnoYtk5xo4EyOGVKR2yhF9ht2zrMTo83YuRAPcNT38Jk4gMtVhBaJw_GOfee-IWN_F258rpmU4p8sRV-1iSuQI3Arm4JBU66QuyjoY-KJmTcE9ft3Bfm9If3yG5W0RFRJrsVb--GjHmiiXDGWiR5Q8L1of_RnSD5QDEbXXxhn4dsDejtCXUaQXE9Ty-NvkvA7G6Ru8cMvIKqP2fXS9SmiW6ePJ2Znrlyafxx6L58pT26RF42h90BVrSldf6SjxQApK3AKZW6q8AkuJnYWTtkR9-qfIDl7W94BsgOFoEd-SDQGxWzGJV9YqAu6_SQKiNDQoZZHrJkRSOPEW_b3-BAdrpwL700I92Rye4-BdhlgeK1RwhT3w1Z-z1tvGZXJtPwdpPa3iIw2TIlesMbC1ZJ22iT3CB_r0lnlZhMtIH6o50l50UGfSDuv8HZ_RNgGnYEPqP3FW-o_VD_Yu_KBqGSA0Eb5xAJjl0vpin2vFGO1P4RdgI17eZXRsCp1KvkpWjbEQTWAvJz39yr7wFQ4BrPfgxUqMP0-ZI_h1DkdPBzWs1uKqHw-4qC77sZXgxgHGEIU1tfKosTy_fK4c-WAbdqIHNTh9VdlM1EdrUJQ4rs2rsUG8o9WXwnGTFchI9Ao64LiCFTFTiFL_dvKI4ZraNNXXprfPhxsdLBaNfgj2CIfUwBMJ9xMGmHKQKLtwZdHpQNVqi8DNm1qjvs3CxbSXGKtkl5K8UhJtI1g4OnEnbq3jDO8DGIyDl0NH-0bcCDqS2yAkh8I3IobzxTg16mqU3roXLQ4pGXnWbx26A_9zb4Y1jV7rzCq24VIfNJzMUtW4fVMYzlrp3X1l32I5hF3YP-tU2paD98xobgc2Cn2RWXd3OirrdjKAE088KhXYLZZY59y4LYRLC6MDMHSX0cbEXbBvl6mKmbaFig2_7ICiSa7rR_Ij6PpQRxIW7NfS7ZMu5w7TnhLJyg5nuwMI8A5pVxfy3gYg2L60wepuX7UUV0USaHNKi8qxbp4RJj4nO-GdE8TbLJtvPw-OzrH9Qiv7iDHVMHOe1CDPLD5IeGqmVB0tuLqlyASuIe3oPxTU7QdctyxHa1z-sO8nN6kpPnzmVmS6XK8bY-h5do28dkZvefomSquXwKeiVg9VAMWVziKLPWWg5iWp2x-spLkWcQsQle2T7xizyETaF1t6YbecXtSoVFmu90_o6ns07etU3RVK1YpQLgqUIJwwF3ZwP65MaWPwqDuWCuoQErlApdhRptxId67KE3UC4j8cAaGSoG0kXnws-jzpPyAg1GU8c-Gu_K0F-h-KFbHPMiWCrrQqzVfvoA2wLaQz3NPAqpq-kbFmrXRGkzLIeIvRVxck-sKkxQIcg3amSV5Dykl-lRCXGxlWNiFG_1SFrTSfp5VKyg7l1KjJzXUXHtqAErsPtMyhxaMmlh4An5a8NIaM9W6tafJrBXpUh85DfwZ8W92OAi1WOgoJIwWXSSeSuo6ECDstjVWW3OQQh9183jliwS7Bis3eu9jgAF3q8sYILBdwjrJRa6aAna2GirNwqZMEIg60kIlvmf1U6S2PgYaPm9UDzvMxjpzwjhXhzxHJitfU1tfl0vo-ATaTV8CxmKerNzy2AjlIZnjknG3xLyonCHbGbAe33QQTclb98y_vr5nA4WKlrls413o0a0f8GL8GjINCOd1RHVMjV',
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content="""\
|
|
The monorepo is organized into these main packages: \n\
|
|
|
|
• pydantic-ai-slim\u2003– core agent framework (type-safe agents, model interface, tooling) \n\
|
|
• pydantic-evals\u2003\u2003– evaluation system (datasets, metrics, evaluators, reports) \n\
|
|
• pydantic-graph\u2003\u2003– graph-based execution engine (state-machine orchestration) \n\
|
|
• clai\u2003\u2003\u2003\u2003\u2003\u2003\u2003– CLI for scaffolding and running agents \n\
|
|
• examples\u2003\u2003\u2003\u2003– sample apps & demos showing real-world usage\
|
|
""",
|
|
id='msg_0083938b3a28070e0068fabda04de881a089010e6710637ab3',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(input_tokens=1109, output_tokens=444, details={'reasoning_tokens': 320}),
|
|
model_name='o4-mini-2025-04-16',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 10, 23, 23, 43, 25, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_0083938b3a28070e0068fabd9d414881a089cf24784f80e021',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_model_mcp_server_tool_stream(allow_model_requests: None, openai_api_key: str):
|
|
m = OpenAIResponsesModel('o4-mini', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(
|
|
m,
|
|
instructions='You are a helpful assistant.',
|
|
capabilities=[
|
|
NativeTool(
|
|
MCPServerTool(
|
|
id='deepwiki',
|
|
url='https://mcp.deepwiki.com/mcp',
|
|
allowed_tools=['ask_question', 'read_wiki_structure'],
|
|
)
|
|
),
|
|
],
|
|
)
|
|
|
|
event_parts: list[Any] = []
|
|
|
|
async with agent.iter(
|
|
user_prompt='Can you tell me more about the pydantic/pydantic-ai repo? Keep your answer short'
|
|
) as agent_run:
|
|
async for node in agent_run:
|
|
if Agent.is_model_request_node(node) or Agent.is_call_tools_node(node):
|
|
async with node.stream(agent_run.ctx) as request_stream:
|
|
async for event in request_stream:
|
|
if (
|
|
isinstance(event, PartStartEvent)
|
|
and isinstance(event.part, NativeToolCallPart | NativeToolReturnPart)
|
|
) or (isinstance(event, PartDeltaEvent) and isinstance(event.delta, ToolCallPartDelta)):
|
|
event_parts.append(event)
|
|
|
|
assert agent_run.result is not None
|
|
messages = agent_run.result.all_messages()
|
|
assert messages == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='Can you tell me more about the pydantic/pydantic-ai repo? Keep your answer short',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
instructions='You are a helpful assistant.',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
NativeToolCallPart(
|
|
tool_name='mcp_server:deepwiki',
|
|
args={'action': 'list_tools'},
|
|
tool_call_id='mcpl_00b9cc7a23d047270068faa0e29804819fb060cec0408ffbcd',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='mcp_server:deepwiki',
|
|
content={
|
|
'tools': [
|
|
{
|
|
'input_schema': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'repoName': {
|
|
'type': 'string',
|
|
'description': 'GitHub repository: owner/repo (e.g. "facebook/react")',
|
|
}
|
|
},
|
|
'required': ['repoName'],
|
|
'additionalProperties': False,
|
|
'$schema': 'http://json-schema.org/draft-07/schema#',
|
|
},
|
|
'name': 'read_wiki_structure',
|
|
'annotations': {'read_only': False},
|
|
'description': 'Get a list of documentation topics for a GitHub repository',
|
|
},
|
|
{
|
|
'input_schema': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'repoName': {
|
|
'type': 'string',
|
|
'description': 'GitHub repository: owner/repo (e.g. "facebook/react")',
|
|
},
|
|
'question': {
|
|
'type': 'string',
|
|
'description': 'The question to ask about the repository',
|
|
},
|
|
},
|
|
'required': ['repoName', 'question'],
|
|
'additionalProperties': False,
|
|
'$schema': 'http://json-schema.org/draft-07/schema#',
|
|
},
|
|
'name': 'ask_question',
|
|
'annotations': {'read_only': False},
|
|
'description': 'Ask any question about a GitHub repository',
|
|
},
|
|
],
|
|
'error': None,
|
|
},
|
|
tool_call_id='mcpl_00b9cc7a23d047270068faa0e29804819fb060cec0408ffbcd',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_00b9cc7a23d047270068faa0e4cd5c819f8855c183ff0fe957',
|
|
signature='gAAAAABo-qDma-ZMjX6meVDoCLYMqgkbQoEVzx_VFnmBFRLqsq37MiF7LP1HrMpqXqtrZ0R2Knb6lUiGSKhsOjOUAn9IFNUCuJx23cPLObF2CKt86wGLb7vccbCrp8bx-I6-kUtZASjlJx7_eJnvwyr24FLZlaDyGDuqRecGA8H4tXnQSAQTT9fJqy8h8dXvxvYzNj5rgOUWgRGn1NBph164KpiEzVWHADzZ_K0l4fX-DFHgtNFssPDYqOKLs_nU0XO8xaIZOgJ8QTf0XmHYF02GA_KciV6sIlSzVricQkwmu1XfJbjpME8XmRMIzlnLRqC8SAJs2kiaYnA8ObfI-s0RbRd3ztIUrzmAsdeo13ualD3tqC1w1_H6S5F47BB47IufTTbpwe_P6f5dLGpOzcrDPbtfHXv-aAW5YEsGyusXqxk51Wp7EONtADmPmVLJffFbRgnwfvPslbxxpNGfxNkN2pIs3U1FW7g1VvmxUfrF84LJpPKvs3xOaWXGorrPBY5nUyeRckhDFt6hGdS59VICmVy8lT4dL_LNswq7dVRS74HrrkfraXDDm2EhL2rtkwhiMqZtuYFsyIK2ys0lZuhNAkhtfgIoV8IwY6O4Y7iXbODxXUr48oZyvLdgV2J2TCcyqIbWClh3-q8MXMmP5wUJdrqajJ8lMVyhQt0UtMJKyk6EWY1DayGpSEW6t8vkqmuYdhyXQOstluONd31LqnEq58Sh8aHCzrypjcLfjDRo5Om1RlxIa-y8S-6rEIXahcJCX_juSg8uYHzDNJffYdBbcLSVQ5mAVl6OM9hE8gHs7SYqw-k-MCeoYsZwt3MqSV7piAu91SMZqB0gXrRDD67bdhmcLBYKmZYKNmLce60WkLH0eZMPSls-n2yyvmwflJA---IZQZOvYXpNUuS7FgMrh3c7n9oDVp15bUgJ8jDx6Mok4pq9E-MHxboblGUpMlFCJDH3NK_7_iHetcqC6Mp2Vc5KJ0OMpDFhCfT3Bvohsee5dUYZezxAkM67qg0BUFyQykulYLHoayemGxzi1YhiX1Of_PEfijmwV2qkUJodq5-LeBVIv8Nj0WgRO-1Y_QW3AWNfQ80Iy6AVa8j9YfsvQU1vwwE9qiAhzSIEeN1Pm2ub8PaRhVIFRgyMOLPVW7cDoNN8ibcOpX-k9p_SfKA9WSzSXuorAs80CTC9OwJibfcPzFVugnnBjBENExTQRfn4l7nWq-tUQNrT4UNGx-xdNeiSeEFCNZlH50Vr5dMaz5sjQQEw_lcTrvxKAV5Zs1mtDf6Kf29LkqhuUEdlMLEJwnAdz2IHLIy41zWLQctSnzBl9HB3mkw8eHZ1LdaRBQRFH4o7Rumhb3D1HdIqDLWeE3jkA6ZBAh2KadGx1u3AIIh4g3dHUS6UREkmzyRIuImbdTsoin1DrQbuYbaqZwIqU4TTIEmA8VeohMfff0rIL5yyFy7cfgGYurgAyMhARPGAAMAoTrR8ldWwymzPkGOJ_SQlzfNGV8weHOEYUl2BgQe57EDX4n1Uk294GIbvGR7eLRL_TLBUyHQErCaOCi8TkBNlLXIobw4ScN_jqqtURmC0mjRDVZeBi6hfrVShWChpQR8A2HxxHrcuHi2hi_2akgUea3zz6_zbUYVoIRdOa9DvZuN015E8ZSL-v_1_vOzUGvt0MuWPazjiRDWgpgcISYzT8N-Xzu_EbwO1OsaOFIeUqrD8mZ6MKOuBQts68og0DWo8KQaHmCaWi4O-c8-5fbB2q3H6oiIoZtSJIoowAmFGOwyWxn_OPS9svDgEaeFYEYhXZ5wZDphxoHkjJ703opxrWoEfQw==',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='mcp_server:deepwiki',
|
|
args='{"action":"call_tool","tool_name":"ask_question","tool_args":{"repoName":"pydantic/pydantic-ai","question":"What is the pydantic/pydantic-ai repository about?"}}',
|
|
tool_call_id='mcp_00b9cc7a23d047270068faa0e67fb0819fa9e21302c398e9ac',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='mcp_server:deepwiki',
|
|
content={
|
|
'error': None,
|
|
'output': """\
|
|
The `pydantic/pydantic-ai` repository is a Python agent framework designed to simplify the development of production-grade applications using Generative AI . It aims to bring the ergonomic developer experience and type-safety philosophy of Pydantic and FastAPI to AI agent development .
|
|
|
|
## Core Purpose and Features
|
|
|
|
The framework focuses on providing a robust and type-safe environment for building AI agents . Key features include:
|
|
|
|
* **Type-safe Agents**: Agents are generic `Agent[Deps, Output]` for compile-time validation, leveraging Pydantic for output validation and dependency injection .
|
|
* **Model-agnostic Design**: It supports over 15 LLM providers through a unified `Model` interface, allowing for easy switching between different models and providers .
|
|
* **Structured Outputs**: Automatic Pydantic validation and reflection/self-correction ensure structured and reliable outputs from LLMs .
|
|
* **Comprehensive Observability**: Integration with OpenTelemetry and native Logfire provides real-time debugging, performance monitoring, and cost tracking .
|
|
* **Production-ready Tooling**: This includes an evaluation framework (`pydantic-evals`), durable execution capabilities, and various protocol integrations like MCP, A2A, and AG-UI .
|
|
* **Graph Support**: It provides a way to define graphs using type hints for complex applications .
|
|
|
|
## Framework Architecture
|
|
|
|
The framework is structured as a UV workspace monorepo, containing several packages .
|
|
|
|
### Core Packages
|
|
|
|
* `pydantic-ai-slim`: Contains the core framework components such as `Agent`, `Model`, and tools .
|
|
* `pydantic-ai`: A meta-package that includes all optional extras .
|
|
|
|
### Supporting Packages
|
|
|
|
* `pydantic-graph`: Provides the graph execution engine with `Graph` and `BaseNode` .
|
|
* `pydantic-evals`: An evaluation framework for datasets and evaluators .
|
|
* `examples`: Contains example applications .
|
|
* `clai`: Provides a CLI interface .
|
|
|
|
## Agent Execution Flow
|
|
|
|
The `Agent` class serves as the primary orchestrator . Agent execution is graph-based, utilizing a state machine from `pydantic_graph.Graph` . The execution involves three core node types:
|
|
|
|
* `UserPromptNode`: Processes user input and creates initial `ModelRequest` .
|
|
* `ModelRequestNode`: Calls `model.request()` or `model.request_stream()` and handles retries .
|
|
* `CallToolsNode`: Executes tool functions via `RunContext[Deps]` .
|
|
|
|
The `Agent` provides methods like `run()`, `run_sync()`, and `run_stream()` for different execution scenarios .
|
|
|
|
## Model Provider Support
|
|
|
|
The framework offers a unified `Model` abstract base class for various LLM providers . This includes native support for providers like OpenAI, Anthropic, Google, Groq, Mistral, Cohere, and Bedrock . Additionally, many OpenAI-compatible providers can be used with `OpenAIChatModel` .
|
|
|
|
## Tool System
|
|
|
|
Tools are registered using the `@agent.tool` decorator . The system automatically generates JSON schemas from function signatures and docstrings, validates tool call arguments, and provides context injection via `RunContext[Deps]` .
|
|
|
|
## Observability Integration
|
|
|
|
Pydantic AI integrates with OpenTelemetry, allowing for instrumentation of agent runs, model requests, and tool executions . It has native integration with Pydantic Logfire for enhanced monitoring and visualization .
|
|
|
|
## Evaluation Framework
|
|
|
|
The `pydantic-evals` package provides a framework for systematically testing and evaluating AI systems . It supports defining datasets with `Case` objects and using various evaluators, including built-in and custom ones .
|
|
|
|
## Integration Ecosystem
|
|
|
|
Pydantic AI supports various integrations for development and production:
|
|
|
|
* **Model Context Protocol (MCP)**: For external tool server access .
|
|
* **AG-UI Protocol**: For interactive application frontends .
|
|
* **Agent2Agent (A2A)**: For multi-agent communication and workflows .
|
|
* **Temporal**: For durable workflow execution .
|
|
* **DBOS**: For database-backed execution and state persistence .
|
|
|
|
## Notes
|
|
|
|
The `CLAUDE.md` file provides guidance for Claude Code when working with the repository, including development commands and an overview of core components and design patterns . The `mkdocs.yml` file defines the structure and content of the project's documentation, including navigation, plugins, and watch directories for various packages . The `docs/install.md` file details how to install the `pydantic-ai` package and its optional components, including a "slim" installation option for specific model dependencies .
|
|
|
|
Wiki pages you might want to explore:
|
|
- [Overview (pydantic/pydantic-ai)](/wiki/pydantic/pydantic-ai#1)
|
|
|
|
View this search on DeepWiki: https://deepwiki.com/search/what-is-the-pydanticpydanticai_e234e9cf-d4aa-4c67-a257-56034816dd56
|
|
""",
|
|
},
|
|
tool_call_id='mcp_00b9cc7a23d047270068faa0e67fb0819fa9e21302c398e9ac',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_00b9cc7a23d047270068faa0f4ff54819f9fb9ff25bebe7f5f',
|
|
signature='gAAAAABo-qD2WTMmhASwWVtFPlo7ILZP_OxHfRvHhda5gZeKL20cUyt0Np6wAHsJ6pyAsXCkLlKBVz3Vwm52JrJuUbqmw-zlXL19rbpvTPRMkiv_GdSfvmxKKNJvSm417OznBDVjsIAqmes2bMq03nRf6Pq2C0oUJnIbpbMwtWzs3jMQqUb0IwyopqXGhn3MWKctLPKZS89nyL4E9kJAx_TyWTQvME8bf8UrV8y2yrNz9odjSQQyZq5YXrlHzpOJjDTfLofVFjsEzM8J29SdLcWnqlv4djJ8xeMpP2ByXuHRnTEyNNuxpYJB7uQbYT0T_eLhwcLv2ZzDZ_hf2Msv7ZdyuPc7Yxc5YWlChB0iaHqQ_8UuMjIVurfgSIjSq2lTvJwdaA365-ZoBMpo4mG04jQDP3XM-0xEM6JTFWc4jZ1OjIXVpkjaXxdOOkYq3t3j8cqBQH69shFCEQr5tnM8jOEl3WHnkvaBg4xEMcd61hiLOKnWbQiYisbFucA8z5ZNbdohUZd-4ww0R8kSjIE5veiyT66gpIte0ItUnTyhIWy8SZYF9bnZGeS-2InDhv5UgjF2iXzgl6dmUrS-_ITgJkwu4Rdf9SBDJhji3_GUO9Za0sBKW8WohP142qY0Tbq4I6-7W1wJ3_gHJqiXVwDLcY90ODSyyC5_I3MgaALRC1wt55sHSeSsDjmNGmiH-m0snaqsI0JnAZwycnWCK17NamjQ9SxVM5tTqJgemkGFQNH1XhZPWvVj56mlj74KKbCJALQpdXD27C8LfdrlBd0v_zEmF1dh7e12I95fYeAlO51xOglBaMCgcMWSDHMGHsJBbJ04eVQSwYTl72rmkASTMaybD-aAm1m8qZnKU-f3xQradhs9l1x9eOfQDIsfWMr1aVMiZi59--VsrgYCbqBj7AGf8n6VNbQWkhO2etozwYZcdGIyiu4TaULX1Xp89Gb28M-tVkIrkQoHO_Z7wzKU1HRBViES1wRKUJ-Sa6wc8UP5orDxeOTFPUr7JL-qaj49cpKzvdlfuoIdbYwpsNvAg69sNbFI3w4jLxOT4yxS6thra1Bit6SY5wAEfrrjtzofLeg49aFqFVGIHeJ8kE3spc1rctpETkdHNyP9fEjZaM3mxR4yz0tPmEgUsd-sdw5BbOKDAVzwconmbeGBmf9KLXMEpRRH7-qSIWUscCi5qIdHXGYoQkStsNGrnhucn_hwqZCSti3Kbzfosud3zQPjW6NyuJCdeTxbDbsnrV7Lkge5j92pyxCHw9j0iuzofRW55_KToBtIvRoPr_37G_6d6TxK42mKqdbgk9GHrcXf27mXszCEzX-VfRVTxyc6JLfEy1iikdo-J2AzXPd4m3zE-zazBU3Z5ey596g8gxwXMkHakLrvwp4_-fQfcvs7sIH34xkEhz7BRdNok3Aqbu_zCt2np69jjHqfPQWZzAy1C-bmMuhAaItPYkkw-LgSu-YP6L89zNofK9Q_S3JwVsLN-fq-9OwhSjy_rQu22Gn4KD6saAu61QMXBPa6z0QJSFUZHJQ_megq1tENfB6wRVtQ0DdAvUwhUsMwx6yE9CT20bma4CloGW__aZuD9gikdQrQ1DCHOvTrfEpvHkl6-wuCImeNjsCvbRFAkx6Xgpc6fdbq4j6WyEVW_4VePNknFWYZ1cw795ka5uJMLc3hVughVlGwDbw60Q3utsjHPbu03pxPle5pdcVEYSQWa0WbFDCrF4ysK0lpmlF7',
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content=IsStr(),
|
|
id='msg_00b9cc7a23d047270068faa0f63798819f83c5348ca838d252',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(input_tokens=1401, output_tokens=480, details={'reasoning_tokens': 256}),
|
|
model_name='o4-mini-2025-04-16',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 10, 23, 21, 40, 50, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_00b9cc7a23d047270068faa0e25934819f9c3bfdec80065bc4',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
assert event_parts == snapshot(
|
|
[
|
|
PartStartEvent(
|
|
index=0,
|
|
part=NativeToolCallPart(
|
|
tool_name='mcp_server:deepwiki',
|
|
args={'action': 'list_tools'},
|
|
tool_call_id='mcpl_00b9cc7a23d047270068faa0e29804819fb060cec0408ffbcd',
|
|
provider_name='openai',
|
|
),
|
|
),
|
|
PartStartEvent(
|
|
index=1,
|
|
part=NativeToolReturnPart(
|
|
tool_name='mcp_server:deepwiki',
|
|
content={
|
|
'tools': [
|
|
{
|
|
'input_schema': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'repoName': {
|
|
'type': 'string',
|
|
'description': 'GitHub repository: owner/repo (e.g. "facebook/react")',
|
|
}
|
|
},
|
|
'required': ['repoName'],
|
|
'additionalProperties': False,
|
|
'$schema': 'http://json-schema.org/draft-07/schema#',
|
|
},
|
|
'name': 'read_wiki_structure',
|
|
'annotations': {'read_only': False},
|
|
'description': 'Get a list of documentation topics for a GitHub repository',
|
|
},
|
|
{
|
|
'input_schema': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'repoName': {
|
|
'type': 'string',
|
|
'description': 'GitHub repository: owner/repo (e.g. "facebook/react")',
|
|
},
|
|
'question': {
|
|
'type': 'string',
|
|
'description': 'The question to ask about the repository',
|
|
},
|
|
},
|
|
'required': ['repoName', 'question'],
|
|
'additionalProperties': False,
|
|
'$schema': 'http://json-schema.org/draft-07/schema#',
|
|
},
|
|
'name': 'ask_question',
|
|
'annotations': {'read_only': False},
|
|
'description': 'Ask any question about a GitHub repository',
|
|
},
|
|
],
|
|
'error': None,
|
|
},
|
|
tool_call_id='mcpl_00b9cc7a23d047270068faa0e29804819fb060cec0408ffbcd',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
previous_part_kind='builtin-tool-call',
|
|
),
|
|
PartStartEvent(
|
|
index=3,
|
|
part=NativeToolCallPart(
|
|
tool_name='mcp_server:deepwiki',
|
|
tool_call_id='mcp_00b9cc7a23d047270068faa0e67fb0819fa9e21302c398e9ac',
|
|
provider_name='openai',
|
|
),
|
|
previous_part_kind='thinking',
|
|
),
|
|
PartDeltaEvent(
|
|
index=3,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='{"action":"call_tool","tool_name":"ask_question","tool_args":',
|
|
tool_call_id='mcp_00b9cc7a23d047270068faa0e67fb0819fa9e21302c398e9ac',
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=3,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='{"repoName":"pydantic/pydantic-ai","question":"What is the pydantic/pydantic-ai repository about?"}',
|
|
tool_call_id='mcp_00b9cc7a23d047270068faa0e67fb0819fa9e21302c398e9ac',
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=3,
|
|
delta=ToolCallPartDelta(
|
|
args_delta='}', tool_call_id='mcp_00b9cc7a23d047270068faa0e67fb0819fa9e21302c398e9ac'
|
|
),
|
|
),
|
|
PartStartEvent(
|
|
index=4,
|
|
part=NativeToolReturnPart(
|
|
tool_name='mcp_server:deepwiki',
|
|
content={
|
|
'error': None,
|
|
'output': """\
|
|
The `pydantic/pydantic-ai` repository is a Python agent framework designed to simplify the development of production-grade applications using Generative AI . It aims to bring the ergonomic developer experience and type-safety philosophy of Pydantic and FastAPI to AI agent development .
|
|
|
|
## Core Purpose and Features
|
|
|
|
The framework focuses on providing a robust and type-safe environment for building AI agents . Key features include:
|
|
|
|
* **Type-safe Agents**: Agents are generic `Agent[Deps, Output]` for compile-time validation, leveraging Pydantic for output validation and dependency injection .
|
|
* **Model-agnostic Design**: It supports over 15 LLM providers through a unified `Model` interface, allowing for easy switching between different models and providers .
|
|
* **Structured Outputs**: Automatic Pydantic validation and reflection/self-correction ensure structured and reliable outputs from LLMs .
|
|
* **Comprehensive Observability**: Integration with OpenTelemetry and native Logfire provides real-time debugging, performance monitoring, and cost tracking .
|
|
* **Production-ready Tooling**: This includes an evaluation framework (`pydantic-evals`), durable execution capabilities, and various protocol integrations like MCP, A2A, and AG-UI .
|
|
* **Graph Support**: It provides a way to define graphs using type hints for complex applications .
|
|
|
|
## Framework Architecture
|
|
|
|
The framework is structured as a UV workspace monorepo, containing several packages .
|
|
|
|
### Core Packages
|
|
|
|
* `pydantic-ai-slim`: Contains the core framework components such as `Agent`, `Model`, and tools .
|
|
* `pydantic-ai`: A meta-package that includes all optional extras .
|
|
|
|
### Supporting Packages
|
|
|
|
* `pydantic-graph`: Provides the graph execution engine with `Graph` and `BaseNode` .
|
|
* `pydantic-evals`: An evaluation framework for datasets and evaluators .
|
|
* `examples`: Contains example applications .
|
|
* `clai`: Provides a CLI interface .
|
|
|
|
## Agent Execution Flow
|
|
|
|
The `Agent` class serves as the primary orchestrator . Agent execution is graph-based, utilizing a state machine from `pydantic_graph.Graph` . The execution involves three core node types:
|
|
|
|
* `UserPromptNode`: Processes user input and creates initial `ModelRequest` .
|
|
* `ModelRequestNode`: Calls `model.request()` or `model.request_stream()` and handles retries .
|
|
* `CallToolsNode`: Executes tool functions via `RunContext[Deps]` .
|
|
|
|
The `Agent` provides methods like `run()`, `run_sync()`, and `run_stream()` for different execution scenarios .
|
|
|
|
## Model Provider Support
|
|
|
|
The framework offers a unified `Model` abstract base class for various LLM providers . This includes native support for providers like OpenAI, Anthropic, Google, Groq, Mistral, Cohere, and Bedrock . Additionally, many OpenAI-compatible providers can be used with `OpenAIChatModel` .
|
|
|
|
## Tool System
|
|
|
|
Tools are registered using the `@agent.tool` decorator . The system automatically generates JSON schemas from function signatures and docstrings, validates tool call arguments, and provides context injection via `RunContext[Deps]` .
|
|
|
|
## Observability Integration
|
|
|
|
Pydantic AI integrates with OpenTelemetry, allowing for instrumentation of agent runs, model requests, and tool executions . It has native integration with Pydantic Logfire for enhanced monitoring and visualization .
|
|
|
|
## Evaluation Framework
|
|
|
|
The `pydantic-evals` package provides a framework for systematically testing and evaluating AI systems . It supports defining datasets with `Case` objects and using various evaluators, including built-in and custom ones .
|
|
|
|
## Integration Ecosystem
|
|
|
|
Pydantic AI supports various integrations for development and production:
|
|
|
|
* **Model Context Protocol (MCP)**: For external tool server access .
|
|
* **AG-UI Protocol**: For interactive application frontends .
|
|
* **Agent2Agent (A2A)**: For multi-agent communication and workflows .
|
|
* **Temporal**: For durable workflow execution .
|
|
* **DBOS**: For database-backed execution and state persistence .
|
|
|
|
## Notes
|
|
|
|
The `CLAUDE.md` file provides guidance for Claude Code when working with the repository, including development commands and an overview of core components and design patterns . The `mkdocs.yml` file defines the structure and content of the project's documentation, including navigation, plugins, and watch directories for various packages . The `docs/install.md` file details how to install the `pydantic-ai` package and its optional components, including a "slim" installation option for specific model dependencies .
|
|
|
|
Wiki pages you might want to explore:
|
|
- [Overview (pydantic/pydantic-ai)](/wiki/pydantic/pydantic-ai#1)
|
|
|
|
View this search on DeepWiki: https://deepwiki.com/search/what-is-the-pydanticpydanticai_e234e9cf-d4aa-4c67-a257-56034816dd56
|
|
""",
|
|
},
|
|
tool_call_id='mcp_00b9cc7a23d047270068faa0e67fb0819fa9e21302c398e9ac',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
previous_part_kind='builtin-tool-call',
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_model_mcp_list_tools_stream_backfills_missing_results(
|
|
allow_model_requests: None, openai_api_key: str
|
|
):
|
|
"""With multiple MCP servers, OpenAI Responses streaming only emits `output_item.done` for the
|
|
|
|
last `mcp_list_tools` item; the earlier items' results arrive only in the final
|
|
`response.completed` payload. Verify every server's discovery call gets a result part during
|
|
streaming (the earlier ones backfilled from `response.completed`), with no duplicate for the
|
|
last item that appears in both `output_item.done` and `response.completed`. See #5419.
|
|
"""
|
|
m = OpenAIResponsesModel('gpt-4.1', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(
|
|
m,
|
|
instructions='You are a helpful assistant.',
|
|
capabilities=[
|
|
NativeTool(MCPServerTool(id='deepwiki', url='https://mcp.deepwiki.com/mcp')),
|
|
NativeTool(MCPServerTool(id='microsoft_learn', url='https://learn.microsoft.com/api/mcp')),
|
|
NativeTool(MCPServerTool(id='gitmcp', url='https://gitmcp.io/pydantic/pydantic-ai')),
|
|
],
|
|
)
|
|
|
|
streamed_list_tools_results: list[NativeToolReturnPart] = []
|
|
async with agent.iter(
|
|
user_prompt='List the names of the tools available from each connected MCP server, then stop. Do not call any tools.'
|
|
) as agent_run:
|
|
async for node in agent_run:
|
|
if Agent.is_model_request_node(node) or Agent.is_call_tools_node(node):
|
|
async with node.stream(agent_run.ctx) as request_stream:
|
|
async for event in request_stream:
|
|
if (
|
|
isinstance(event, PartStartEvent)
|
|
and isinstance(event.part, NativeToolReturnPart)
|
|
and event.part.tool_call_id.startswith('mcpl_')
|
|
):
|
|
streamed_list_tools_results.append(event.part)
|
|
|
|
assert agent_run.result is not None
|
|
messages = agent_run.result.all_messages()
|
|
assert messages == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='List the names of the tools available from each connected MCP server, then stop. Do not call any tools.',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsDatetime(),
|
|
instructions='You are a helpful assistant.',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
NativeToolCallPart(
|
|
tool_name='mcp_server:deepwiki',
|
|
args={'action': 'list_tools'},
|
|
tool_call_id='mcpl_034c5e93e2fa45ad006a2c2b74d53c819dbb0f25635141142d',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='mcp_server:microsoft_learn',
|
|
args={'action': 'list_tools'},
|
|
tool_call_id='mcpl_034c5e93e2fa45ad006a2c2b74d640819d9ec49d5fdfab8a5c',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='mcp_server:gitmcp',
|
|
args={'action': 'list_tools'},
|
|
tool_call_id='mcpl_034c5e93e2fa45ad006a2c2b74d794819da28402187d7e0600',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='mcp_server:gitmcp',
|
|
content={
|
|
'tools': [
|
|
{
|
|
'input_schema': {'type': 'object'},
|
|
'name': 'fetch_pydantic_ai_documentation',
|
|
'annotations': {'read_only': False},
|
|
'description': 'Fetch entire documentation file from GitHub repository: pydantic/pydantic-ai. Useful for general questions. Always call this tool first if asked about pydantic/pydantic-ai.',
|
|
},
|
|
{
|
|
'input_schema': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'query': {
|
|
'type': 'string',
|
|
'description': 'The search query to find relevant documentation',
|
|
}
|
|
},
|
|
'required': ['query'],
|
|
'additionalProperties': False,
|
|
'$schema': 'http://json-schema.org/draft-07/schema#',
|
|
},
|
|
'name': 'search_pydantic_ai_documentation',
|
|
'annotations': {'read_only': False},
|
|
'description': 'Semantically search within the fetched documentation from GitHub repository: pydantic/pydantic-ai. Useful for specific queries.',
|
|
},
|
|
{
|
|
'input_schema': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'query': {
|
|
'type': 'string',
|
|
'description': 'The search query to find relevant code files',
|
|
},
|
|
'page': {
|
|
'type': 'number',
|
|
'description': 'Page number to retrieve (starting from 1). Each page contains 30 results.',
|
|
},
|
|
},
|
|
'required': ['query'],
|
|
'additionalProperties': False,
|
|
'$schema': 'http://json-schema.org/draft-07/schema#',
|
|
},
|
|
'name': 'search_pydantic_ai_code',
|
|
'annotations': {'read_only': False},
|
|
'description': 'Search for code within the GitHub repository: "pydantic/pydantic-ai" using the GitHub Search API (exact match). Returns matching files for you to query further if relevant.',
|
|
},
|
|
{
|
|
'input_schema': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'url': {
|
|
'type': 'string',
|
|
'description': 'The URL of the document or page to fetch',
|
|
}
|
|
},
|
|
'required': ['url'],
|
|
'additionalProperties': False,
|
|
'$schema': 'http://json-schema.org/draft-07/schema#',
|
|
},
|
|
'name': 'fetch_generic_url_content',
|
|
'annotations': {'read_only': False},
|
|
'description': 'Generic tool to fetch content from any absolute URL, respecting robots.txt rules. Use this to retrieve referenced urls (absolute urls) that were mentioned in previously fetched documentation.',
|
|
},
|
|
],
|
|
'error': None,
|
|
},
|
|
tool_call_id='mcpl_034c5e93e2fa45ad006a2c2b74d794819da28402187d7e0600',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content="""\
|
|
Here are the available tools from each connected MCP server:
|
|
|
|
---
|
|
|
|
**mcp_deepwiki**
|
|
- read_wiki_structure
|
|
- read_wiki_contents
|
|
- ask_question
|
|
|
|
---
|
|
|
|
**mcp_microsoft_learn**
|
|
- microsoft_docs_search
|
|
- microsoft_code_sample_search
|
|
- microsoft_docs_fetch
|
|
|
|
---
|
|
|
|
**mcp_gitmcp**
|
|
- fetch_pydantic_ai_documentation
|
|
- search_pydantic_ai_documentation
|
|
- search_pydantic_ai_code
|
|
- fetch_generic_url_content
|
|
|
|
---\
|
|
""",
|
|
id='msg_034c5e93e2fa45ad006a2c2b77483c819dbedc979fa0978d17',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='mcp_server:deepwiki',
|
|
content={
|
|
'tools': [
|
|
{
|
|
'input_schema': {
|
|
'properties': {'repoName': {'type': 'string'}},
|
|
'required': ['repoName'],
|
|
'type': 'object',
|
|
},
|
|
'name': 'read_wiki_structure',
|
|
'annotations': {'read_only': False},
|
|
'description': """\
|
|
Get a list of documentation topics for a GitHub repository.
|
|
|
|
Args:
|
|
repoName: GitHub repository in owner/repo format (e.g. "facebook/react")\
|
|
""",
|
|
},
|
|
{
|
|
'input_schema': {
|
|
'properties': {'repoName': {'type': 'string'}},
|
|
'required': ['repoName'],
|
|
'type': 'object',
|
|
},
|
|
'name': 'read_wiki_contents',
|
|
'annotations': {'read_only': False},
|
|
'description': """\
|
|
View documentation about a GitHub repository.
|
|
|
|
Args:
|
|
repoName: GitHub repository in owner/repo format (e.g. "facebook/react")\
|
|
""",
|
|
},
|
|
{
|
|
'input_schema': {
|
|
'properties': {
|
|
'repoName': {
|
|
'anyOf': [
|
|
{'type': 'string'},
|
|
{'items': {'type': 'string'}, 'type': 'array'},
|
|
]
|
|
},
|
|
'question': {'type': 'string'},
|
|
},
|
|
'required': ['repoName', 'question'],
|
|
'type': 'object',
|
|
},
|
|
'name': 'ask_question',
|
|
'annotations': {'read_only': False},
|
|
'description': """\
|
|
Ask any question about a GitHub repository and get an AI-powered, context-grounded response.
|
|
|
|
Args:
|
|
repoName: GitHub repository or list of repositories (max 10) in owner/repo format
|
|
question: The question to ask about the repository\
|
|
""",
|
|
},
|
|
],
|
|
'error': None,
|
|
},
|
|
tool_call_id='mcpl_034c5e93e2fa45ad006a2c2b74d53c819dbb0f25635141142d',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='mcp_server:microsoft_learn',
|
|
content={
|
|
'tools': [
|
|
{
|
|
'input_schema': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'query': {
|
|
'description': 'a query or topic about Microsoft/Azure products, services, platforms, developer tools, frameworks, or APIs',
|
|
'type': 'string',
|
|
'default': None,
|
|
}
|
|
},
|
|
},
|
|
'name': 'microsoft_docs_search',
|
|
'annotations': {'read_only': True},
|
|
'description': """\
|
|
Search official Microsoft/Azure documentation to find the most relevant and trustworthy content for a user's query. This tool returns up to 10 high-quality content chunks (each max 500 tokens), extracted from Microsoft Learn and other official sources. Each result includes the article title, URL, and a self-contained content excerpt optimized for fast retrieval and reasoning. Always use this tool to quickly ground your answers in accurate, first-party Microsoft/Azure knowledge.
|
|
|
|
## Follow-up Pattern
|
|
To ensure completeness, use microsoft_docs_fetch when high-value pages are identified by search. The fetch tool complements search by providing the full detail. This is a required step for comprehensive results.\
|
|
""",
|
|
},
|
|
{
|
|
'input_schema': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'query': {
|
|
'description': 'a descriptive query, SDK name, method name or code snippet related to Microsoft/Azure products, services, platforms, developer tools, frameworks, APIs or SDKs',
|
|
'type': 'string',
|
|
},
|
|
'language': {
|
|
'description': 'Optional parameter specifying the programming language of code snippets to retrieve. Can significantly improve search quality if provided. Eligible values: csharp javascript typescript python powershell azurecli al sql java kusto cpp go rust ruby php',
|
|
'type': 'string',
|
|
'default': None,
|
|
},
|
|
},
|
|
'required': ['query'],
|
|
},
|
|
'name': 'microsoft_code_sample_search',
|
|
'annotations': {'read_only': True},
|
|
'description': """\
|
|
Search for code snippets and examples in official Microsoft Learn documentation. This tool retrieves relevant code samples from Microsoft documentation pages providing developers with practical implementation examples and best practices for Microsoft/Azure products and services related coding tasks. This tool will help you use the **LATEST OFFICIAL** code snippets to empower coding capabilities.
|
|
|
|
## When to Use This Tool
|
|
- When you are going to provide sample Microsoft/Azure related code snippets in your answers.
|
|
- When you are **generating any Microsoft/Azure related code**.
|
|
|
|
## Usage Pattern
|
|
Input a descriptive query, or SDK/class/method name to retrieve related code samples. The optional parameter `language` can help to filter results.
|
|
|
|
Eligible values for `language` parameter include: csharp javascript typescript python powershell azurecli al sql java kusto cpp go rust ruby php\
|
|
""",
|
|
},
|
|
{
|
|
'input_schema': {
|
|
'type': 'object',
|
|
'properties': {
|
|
'url': {
|
|
'description': 'URL of the Microsoft documentation page to read',
|
|
'type': 'string',
|
|
}
|
|
},
|
|
'required': ['url'],
|
|
},
|
|
'name': 'microsoft_docs_fetch',
|
|
'annotations': {'read_only': True},
|
|
'description': """\
|
|
Fetch and convert a Microsoft Learn documentation webpage to markdown format. This tool retrieves the latest complete content of Microsoft documentation webpages including Azure, .NET, Microsoft 365, and other Microsoft technologies.
|
|
|
|
## When to Use This Tool
|
|
- When search results provide incomplete information or truncated content
|
|
- When you need complete step-by-step procedures or tutorials
|
|
- When you need troubleshooting sections, prerequisites, or detailed explanations
|
|
- When search results reference a specific page that seems highly relevant
|
|
- For comprehensive guides that require full context
|
|
|
|
## Usage Pattern
|
|
Use this tool AFTER microsoft_docs_search when you identify specific high-value pages that need complete content. The search tool gives you an overview; this tool gives you the complete picture.
|
|
|
|
## URL Requirements
|
|
- The URL must be a valid HTML documentation webpage from the microsoft.com domain
|
|
- Binary files (PDF, DOCX, images, etc.) are not supported
|
|
|
|
## Output Format
|
|
markdown with headings, code blocks, tables, and links preserved.\
|
|
""",
|
|
},
|
|
],
|
|
'error': None,
|
|
},
|
|
tool_call_id='mcpl_034c5e93e2fa45ad006a2c2b74d640819d9ec49d5fdfab8a5c',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(input_tokens=1199, output_tokens=103, details={'reasoning_tokens': 0}),
|
|
model_name='gpt-4.1-2025-04-14',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={'timestamp': IsDatetime(), 'finish_reason': 'completed'},
|
|
provider_response_id='resp_034c5e93e2fa45ad006a2c2b74c2e4819dafbd93fcd1b49697',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
# Each server's discovery call produced exactly one result during streaming: the last item via
|
|
# `output_item.done`, the earlier two backfilled from `response.completed` (and not duplicated).
|
|
streamed_server_results = [part.tool_name for part in streamed_list_tools_results]
|
|
assert sorted(streamed_server_results) == snapshot(
|
|
['mcp_server:deepwiki', 'mcp_server:gitmcp', 'mcp_server:microsoft_learn']
|
|
)
|
|
|
|
|
|
async def test_openai_responses_model_mcp_server_tool_with_connector(allow_model_requests: None, openai_api_key: str):
|
|
m = OpenAIResponsesModel(
|
|
'o4-mini',
|
|
provider=OpenAIProvider(api_key=openai_api_key),
|
|
)
|
|
agent = Agent(
|
|
m,
|
|
instructions='You are a helpful assistant.',
|
|
capabilities=[
|
|
NativeTool(
|
|
MCPServerTool(
|
|
id='google_calendar',
|
|
url='x-openai-connector:connector_googlecalendar',
|
|
authorization_token='fake',
|
|
description='Google Calendar',
|
|
allowed_tools=['search_events'],
|
|
)
|
|
),
|
|
],
|
|
)
|
|
|
|
result = await agent.run('What do I have on my Google Calendar for today?')
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(content='What do I have on my Google Calendar for today?', timestamp=IsDatetime())
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
instructions='You are a helpful assistant.',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
NativeToolCallPart(
|
|
tool_name='mcp_server:google_calendar',
|
|
args={'action': 'list_tools'},
|
|
tool_call_id='mcpl_0558010cf1416a490068faa0f9679481a082dc4ac08889f104',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='mcp_server:google_calendar',
|
|
content={
|
|
'tools': [
|
|
{
|
|
'input_schema': {
|
|
'properties': {
|
|
'calendar_id': {
|
|
'anyOf': [{'type': 'string'}, {'type': 'null'}],
|
|
'default': None,
|
|
'description': "The ID of the calendar to search. Default one is 'primary'",
|
|
'title': 'Calendar Id',
|
|
},
|
|
'max_results': {'default': 50, 'title': 'Max Results', 'type': 'integer'},
|
|
'next_page_token': {
|
|
'anyOf': [{'type': 'string'}, {'type': 'null'}],
|
|
'default': None,
|
|
'title': 'Next Page Token',
|
|
},
|
|
'query': {
|
|
'anyOf': [{'type': 'string'}, {'type': 'null'}],
|
|
'default': None,
|
|
'title': 'Query',
|
|
},
|
|
'time_max': {
|
|
'anyOf': [{'type': 'string'}, {'type': 'null'}],
|
|
'default': None,
|
|
'description': "Time in the ISO-8601 format. You can also use 'now' or leave null.",
|
|
'title': 'Time Max',
|
|
},
|
|
'time_min': {
|
|
'anyOf': [{'type': 'string'}, {'type': 'null'}],
|
|
'default': None,
|
|
'description': "Time in the ISO-8601 format. You can also use 'now' or leave null.",
|
|
'title': 'Time Min',
|
|
},
|
|
'timezone_str': {
|
|
'anyOf': [{'type': 'string'}, {'type': 'null'}],
|
|
'default': None,
|
|
'description': "Timezone of the event. Default is 'America/Los_Angeles'",
|
|
'title': 'Timezone Str',
|
|
},
|
|
},
|
|
'title': 'search_events_input',
|
|
'type': 'object',
|
|
},
|
|
'name': 'search_events',
|
|
'annotations': {'read_only': True},
|
|
'description': 'Look up Google Calendar events using various filters.',
|
|
}
|
|
],
|
|
'error': None,
|
|
},
|
|
tool_call_id='mcpl_0558010cf1416a490068faa0f9679481a082dc4ac08889f104',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_0558010cf1416a490068faa0fb684081a0a0b70f55d8194bb5',
|
|
signature='gAAAAABo-qEE669V-_c3vkQAeRtSj9pi72OLJweRJe4IRZkLcFfnuwdxSeJM5DVDLzb3LbfzU0ee6a4KAae0XsETU3hELT1hn3LZPwfFku5zl7CVgsc1DmYBf41Qki1EPHFyIlMj937K8TbppAAqMknfLHHwV1FLb8TapccSEhJbzGutqD3c2519P9f6XHKcuDa8d-sjyUejF0QuSjINFcjifJ8DiU40cL_-K6OJotlx6e0FqOivz6Nlj13QZxQ0I3FiiSi03mYKy240jYMpOpjXr7yPmEXLdCJdP5ycmTiJLxf4Bugww6u4F2uxy22978ACyFGSLHBiQyjczj_can7qKXAkMwYJKcGNjaNi8jG5iTIwsGswRjD1hvY-AGUotMFbPCszX3HW1M_ar-livaheiZauCfKV-Uc1ZeI3gijWEwtWQ0jye29FyQPCCpOBvT6RbUvFEpfqpwcMQuUhOyEfgzli2dpuOAgkSjCPE6ctoxjbYa62YzE-yrXAGc5_ptQy_2vw7t0k3jUzSo2Tv0aKnqvvKcj9SIilkZV4Nf-TL_d2E7d48bBJDlqbAv7fkhhd2YlkLqwdR1MqZtygcR1Jh8p2Y1pFAa4mSj7hh4M-zfSu--6dij2iKIbnKQ4DbXyGpMZXBAqTHMe9PPOwGxWKShlN5a5T89B04d_GwJYBDJx2ctecqZxDMjkTn3wVGl_5wuDnrEgd0I91vmAoYuWldR_h8M_FjDFiHefdbZjw1TxVKjkp6wk6zQiXCvvCZYJa9XkhytcllWvUI4C0gbxHrEzZRy9Vii3buqnbiIM9Qj0VPx-Q-FKM_usZBBmlvmk9PMQ8rH9vVT8dRFNQEj-aqudB5yUcTx8XaUFwYAts04OObGBqXoazYtxh6WvHwrf09pb_g0dwzE_rlcQdYxcFLOpYD-AentRAjOuIr4bLRM9BMERBxPvvPCxZ2Mva8YqV2TIOtxzMY08freim6du1IuYprO6CoejPaBdULhct-nsPubOdjLBikZt_bwumvmqGXnxI_uu51b9HtzPeDpWIjF6pi88bcsOk0qglA9GAu3wwX-iIdaV19VdVCO4KJjxiVrbTY1IVgWSdz98Alb_HzpXsoS6i2PRAjjsYOe4RBX3etxjsY07XXLlmXAM_vuYXc8Y6STxvBk4ST4OkaCvUk9DoZbVL5KmVcT6TaFpbVCOB_eHkHIvMjXc35kzxCdqEMG3FpRzL_UkY8pPridvq2z1Xw0al2KEBvdKPlInB8-zX5ANGeRkMGZ6ZfyX1zCIdYLe3wrC8xqr5nUZ-ueWmtqYLavSg8mQKphp4QyVaiwtbxEt5GEiVG7_LR754mGQYPdr9Shh3ECAp8wmSfDVO8MHaLmzgo3RXeqlqFldRjQzDHtCaGhjD9bHKF3yWF2LtH4gUN-Sf--86lcq7iwHDSDm656P_FBfYmE7rA0svH-m3hQoBhza4CKJ7s7f7ZymEhcHAfH7SPImZ3Y-kT_Sy1mbCCf3Yg8uitrpX7ukO6_bIANS_R4oiOPcuLixbWY0ZSyq8ERB5fa5EsIUm7PpGxbO96nmk5rPkewyB4gCtslwJI0Ye7zHtqrDBz1j1nsjIKsRCfFWlUdRF8J1JPiiBSvP8SraQ_94cnKBCsl34BGsVm-R1_ULbuyahBzSHq2Kwr0XQuNLdGChyLKS_FZVT58kbRFsvjZnbalAZ-k9alMeZ-pdWX5f9nSn3w7fz675zOxnBaqiZmoWHXFNOBVGH7gkz05ynJ2B8j_RpdRNJKXUN8pAvf595HGl2IPdaDhqoeS2_3jixO5mmxZuPEdzopoBFRarWud99mxH-mYxWJzKiA1pLNqj7SO93p2-jB-jtsCfZfk6bVEWpRRkIEz0XvxffFTVuGUCqpGS7FiFZc4pQU24pCrdpg2w3xeDSrmfHDAx2vUvv0iRBnQxTTWx2-de2TQQTpR5tjFNyOhYGVn1OXqkbkNtIUHdnNGA1QBCU0Qs0471Ss1CrxXIeeNVSTd00jiu4_ELk6nJYgSpmS8G_crrDza8mRLV5Yk0ItRrZj6pwKUOEaYeyM-RHyhrjf09yaf7Qc3sAozQF0aXFCQjSYiVb98DuGH28HLUxW9ulmSKKR4pYKlCOLNGm0h_gWCpSa0H1HXCgEoPn68HyaJogv_xH3k4ERYyJnxu8zVbVPMGoa9q9nNRQQ9Ks2AvxYRQeGFSCTACBmuookvHsO1zjYfHNuSCD7pCLRFE76KlmSiAX6l9LNOq_xe9Oos-1AvcZHkmVsuh-mjTVkBOjG6zmnHiNJirBpORs_UWL5lmlQBeaXgdHxcb4tHIn8XYXFkQiC4b4pw==',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='mcp_server:google_calendar',
|
|
args={
|
|
'action': 'call_tool',
|
|
'tool_name': 'search_events',
|
|
'tool_args': {
|
|
'time_min': '2025-10-23T00:00:00',
|
|
'time_max': '2025-10-23T23:59:59',
|
|
'timezone_str': 'America/Los_Angeles',
|
|
'max_results': 50,
|
|
'query': None,
|
|
'calendar_id': 'primary',
|
|
'next_page_token': None,
|
|
},
|
|
},
|
|
tool_call_id='mcp_0558010cf1416a490068faa0fdf64481a085a3e5b8f7d6559a',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='mcp_server:google_calendar',
|
|
content={
|
|
'output': None,
|
|
'error': {
|
|
'code': 500,
|
|
'message': 'An unknown error occurred while executing the tool.',
|
|
'type': 'http_error',
|
|
},
|
|
},
|
|
tool_call_id='mcp_0558010cf1416a490068faa0fdf64481a085a3e5b8f7d6559a',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_0558010cf1416a490068faa0ff5c9081a0b156a84d46e5d787',
|
|
signature='gAAAAABo-qEE72KCH4RlulMdH6cOTaOQwFy4of4dPd8YlZ-zF9MIsPbumWO2qYlZdGjIIXDJTrlRh_5FJv2LtTmMbdbbECA20AzFMwE4pfNd2aNLC5RhcHKa4M9acC1wYKAddqEOPP7ETVNBj-GMx-tMT_CY8XnBLWvSwPpcfde9E--kSrfsgvRn1umqDsao4sLlAtV-9Gc6hmW1P9CSJDQbHWkdTKMV-cjQ-wZHFCly5kSdIW4OKluFuFRPkrXs7kVmlGnMr8-Q5Zuu1ZOFR9mPvpu2JdxAFohjioM-ftjeBuBWVJvOrIF4nV-yIVHVT-_psAZaPUUB5cyPAtqpoxxIV3iPKPU8DHctP03g_0R6pSWWHhggvO5PBw3zyPwtBwOrHBipc4nQEWEMxZxLH5SYJauTKwHNOx9NyCq8JUjZXM_v4xsGxNa4cAp7GuXqR2YyW2sx7syRUiDwtebh0xk_YOQtkv8tAjzCofmaz3n8FJ2nGSXkilaV5Q8LUNO-9-D2tsAaScDVMuLMMAHFNp_GPplWrmGES4mTCNtTXWyF1GLcQBw8dYYctV66Ocy2_zxyDoB7SsR5htlV77nJ6u1Hbp3tk26LutDrhAhe55xcki8iblHbXNY9MRzR1SS5Zk3-dv0ex4QOzC663NvS9aK3olQbKYko5TvM7Pq4MFYfaxwFTVFVEdaskoDJieVyikz0ZzBjTsItIwL-Q2BVN2F_P_wgCV5hyDclNMPEGTMxajxfIFv-oEunmHY1_RJavl47iXWS8H3JWAvp-9YYQdTS4Aa6m5zPndvHOvEV355UawLHRPctHFUS7rE7rYmcU6KQaqC96JRM0KRfXNIgYtNfw6cxgnyqGxzTF7qeeVzObOqoQmz59Rh0U9ti37vqHb8Ca43-q2Gx2KaVZFj7MBQK8UodfaDRIEuyMB3XNfckxCefwHs7FeAj5NuNDBrm0uDcwJjs2JfY2i54gAES8kAPLGJgRpq_qdjVXqpO6W0H9E1vBdRem7zLPYbA8OOo-KCkRW4AFCVbgCpgIvo4GDNvFOMksl-d8zgQU2qroUWJRu58j1bdaar7Zlfxk0UR33nROmJpXGb_R-RCNAN1ZxJTdEU_dVfyLCeuIXPsnO-FlfO8J6Un3WWPNLuN_bDS5RocniI_ms71qLsisJQiPTs-JDFl-eMM2Hk3QqSCC6OT0CLG9XMmI_zva9yp2joQ8HdGMddE3FDCbLejRrx8fV-9Nd0tZ7SYjFG78_fre8IfL0L67CK1JIPYzhgRZgCb-FFwUy-stR_BstIn0sRr_tDCoHdxuoVCh0dZfTY1p27xbKQ50svHxp1caNp3uze0wLXP9STNouFjFpdIHMsDRaGfO9R9mMmUsFcmBMK3aikuHTpebyL1CeZsIzH2cbZLPRx3pN2IqJ-5h6-cORHuMqf3ysEEFCjXnqmzvWPuBjYDsxnxA1awaGkYKsKhqchgakrfplOjdG5tSkklggBJA93iRaUWIR-4oV6HkkrnpdK1w7BL_VT8upqZmkpHZtZCDSgINk5S5hoYPLBTtS3dcCmQIbLvPXPuGzdAZxl0bhD4Rm3GPDFszaDoFK0Jszcjlaf4SJqyZABKEf71dDbi1as-2Qwr4fxBiQIOsF8ChbYo6Z2iFtUpBnbruFUIwB5QyKfWnwEZbOgf4UbIvIqNMkTzMc8tJgz6Ddqfih8VeNH3v8_84J6vHU0SVm_gvkgQ6P6N_6r5LwNdlAEff0hFwn-aTHWZ3s8MICckUZj97lKoZxAl91WlsKa0yrLw24dxvJ6bhZf0FsOitUJGd7vFPx0TxSobUkzE2RrbQ3hziPxw2Gins4aI6YG3M1gfumd3MgdH-fYBvZulJ9vmw0ZC1Dqh6BkCWHOFKsnpQvHmYuyTzUmnYuJf8N5j_b9XNw0krmxouOCPQClFmIOBLw8XPbe3xf0F5JP7BC0PpjlPT33A5Z6Za5zlA5O-DE_Wp0WG885-GaKtZI-zBZW3R0lc9A4s0HbxqA3lqH8leXOCe6WO46Z_iTQlALpTR-7oaHqzTegq0KSmEjCFO-jLSrVZnBOQ4ddTvLj4ASsQbj-o6TFUFVZAKSLI3FtWovHw02Gc_D0luFz9TbfaXM-EapEQYajkG0_b_nSCoPq0T9HSyvU4oCxXyQvhwIgzbijR-BheN6a_l6hiqZCw9L1c8MdPRtjpbHtEwWkpQ62s8XdydeJnV5vJYp9ezBbS_vWQ7Nz1siai6epJTdzDkRm-dudVhKzdohwg-FOQ-5gSrvoPS_MF4lZvah3iXY1g4uePO4eNDWGJ74YPybiy',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolCallPart(
|
|
tool_name='mcp_server:google_calendar',
|
|
args={
|
|
'action': 'call_tool',
|
|
'tool_name': 'search_events',
|
|
'tool_args': {
|
|
'time_min': '2025-10-23T00:00:00Z',
|
|
'time_max': '2025-10-23T23:59:59Z',
|
|
'timezone_str': None,
|
|
'max_results': 50,
|
|
'query': None,
|
|
'calendar_id': 'primary',
|
|
'next_page_token': None,
|
|
},
|
|
},
|
|
tool_call_id='mcp_0558010cf1416a490068faa102400481a09fa35e84bc26c170',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='mcp_server:google_calendar',
|
|
content={
|
|
'output': None,
|
|
'error': {
|
|
'code': 500,
|
|
'message': 'An unknown error occurred while executing the tool.',
|
|
'type': 'http_error',
|
|
},
|
|
},
|
|
tool_call_id='mcp_0558010cf1416a490068faa102400481a09fa35e84bc26c170',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_0558010cf1416a490068faa102d89481a0b74cca04bcb8f127',
|
|
signature='gAAAAABo-qEECuiSxfvrR92v1hkqyCTCWyfmpHSaW-vVouk5mOTIFDvaBZdVTFH8-dJfpwEG3MCejRKh9V-I8mrYAjhudVr1ayHo8UYOOU1cfVc6w3wsrkL8hXljjE-amiJhBSjvRc2nwwGtgYpDxOfWTqJkaUvFnMD6MrS4CwMrCBbDOLYZgM1cQbidtrrtpP7D5u42tR6coC_PCOqwPzDN4f0RggrxVxh0038p81VUmlkUeA2jWzRyFpeDGRjXFk84Og73rXAp7EWQv7TmzgVXBjCVwwzJNU8HCZ_gkwh5dvL94QxBx32lEmfOOKcqA3hN3FLwDqXlZ8f7jEqYInnpILQgX5XMdM9OrCyXmDCr_eIy00cjvxnTcXhCnZBOaKCKmTP74yUpGNdLbQcr4BalTiviNYEeCAhJyRo4KnhUZbBoT7MB5NULf-kqhRo1gEGKjWiLdV47PhR7Z8i4BK7zBceganMKpLtzIMW5a6JAujC4Z9FYxcpJZI_CD9NHsPr4SjKgIwv89d6BYo89-xfflF6ZUZBkuDUnL2-Nc9CKgGuKlcDunvYLr38pzA278OFYzh9T42u4SbS8KkSXKjGU3H8LfpMnBEZigriixLt5vj7qnWmZvCFarzxT4U4qqR1ITp5rkO6G9kYvBEfS7wu768mteDBgAajUaeOMQEfjJRErC4wfzbB89YCsXPJz0JE90QZ5LeiP5ZlVezTTaddG9JmiGsBCPckqUb1LWdpvekCfPkePF_uDMVWyJpQ4ZBzQsZx8sHf5spygsiQjlzTiriqwhoTcPuXoONoCr9HeFX1Qy8SGOm87siRPAD7FHJdDxbJwq8tOlMpx8MH1dqEY07lwoxZB0GQ9XbB7QJXfQR_27nkpqBYFkrbqChNJLO2x8gNFClbB0mgYQE1CRy64y6yOrG3CtS53RK5VGrF1GnqwuWdZ452VgShT5nAmPFRlRk1S9px4eMUTAozT0QAYrlHQC7b6I6K3m_Qe3kXGpnn_87i2eGG8mHmXG2FvFChkgf2OU7-LRy_Wl_u-ataICeoBwfngBFMppvUW6tJP009HK7mUE8P1KJntN3ExKLIBhmKhV6ziBpIi1bSTmd8leYqfSaf648c7-sVuDRx7DzxTp19l3fwVFa67GdiagZFs7xaU1HxMnMc3uy5VKWAH_qcv-Mga3VCTtTPpMTjvB95nsLeOFjS2FtpPvaP0N6o5kkkzW7cteWpOHhSX0z7AQA7CqgOCQLfLUc7ltVxnOH4WdHoeZFah_q_Ue6caf0kNo4YsTfbRDdzsW70o8P5Agr-Pgttg19vTDA_eBFur9GDKIRT0vYMWPpykwJBDTgJKOFW6uyNkqNWk_RAAvleE9pAyOoSmgomyrMcnnpdeYHNxeNxvTWFC3mcKSjJIB316wypPvaGTJyaK_pxJScD7CtLrIPkgwPpOsJnDySF6wGe-fGsUMt3zxJrc-S6fp24mYVfTRZbjUsP0fJgLmCohJiAtEg_xvlQ8sPyuLoLdOdossTQ7ufl0CwVn4f_ol4q__gpTvYVaoGsWl3QmHul5zj7OUAn7of6iBfCSlXbrauJvMyNYt4x_dLM8SXTRNPe-ZMDmER9DOw0KJXcUrpl6uw4TphKmUOK6KrxqshujXdN9VDgOwD7eKqIHpvC_6a2R6sS6ZHcebmh2o3bic-Hctomrbv03OQ==',
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content=IsStr(),
|
|
id='msg_0558010cf1416a490068faa103e6c481a0930eda4f04bb3f2a',
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(input_tokens=1065, output_tokens=760, details={'reasoning_tokens': 576}),
|
|
model_name='o4-mini-2025-04-16',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 10, 23, 21, 41, 13, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='resp_0558010cf1416a490068faa0f945bc81a0b6a6dfb7391030d5',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_requires_function_call_status_none(allow_model_requests: None, openai_api_key: str):
|
|
model = OpenAIResponsesModel(
|
|
'gpt-5',
|
|
provider=OpenAIProvider(api_key=openai_api_key),
|
|
profile=merge_profile(
|
|
openai_model_profile('gpt-5'), OpenAIModelProfile(openai_responses_requires_function_call_status_none=True)
|
|
),
|
|
)
|
|
agent = Agent(model)
|
|
|
|
@agent.tool_plain
|
|
def get_meaning_of_life() -> int:
|
|
return 42
|
|
|
|
result = await agent.run('What is the meaning of life?')
|
|
messages = result.all_messages()
|
|
|
|
_, openai_messages = await model._map_messages( # type: ignore[reportPrivateUsage]
|
|
messages,
|
|
model_settings=cast(OpenAIResponsesModelSettings, model.settings or {}),
|
|
model_request_parameters=ModelRequestParameters(),
|
|
)
|
|
assert openai_messages == snapshot(
|
|
[
|
|
{'role': 'user', 'content': 'What is the meaning of life?'},
|
|
{
|
|
'id': 'rs_01d311e2633707df0068fbac0050ec81a2ad76fd9256abcaf7',
|
|
'summary': [],
|
|
'encrypted_content': 'gAAAAABo-6wE6H4S9A886ZkwXcvvHqZ6Vx5BtpYvvNAJV5Ijq7pz-mTBJxfdjilNSzBj0ruy7NOsMRMhWzNahRf-n3KDQ2x1p-PjVCHM5IAGqHqae8A-aAUn_FDRiTbAT5N5FXTrZ80DAtdDv17z2HlODmTTYRvBU2-rX7opysjc4rf7-rvy6j4cUcNbM0ntT5DH8UHxC9LCM_s7Cb2unEV0jaDt7NzFxgfWN2u24Avs2EnjPoxOjd6BR-PWHJk_7kGGkVBub8NU7ZOyHsci3T8DAq_eX38DgkHJBJCPT4EqvlNP-VjPdecYEFUCw5G_Pye6h55-77g8LjkrFO43f8p6wscQ0iM601i1Ugmqbzxyv1ogPIN-YuSk2tkCw-D7xBD7I4fum2AmvyN-fR58lWcn-Z0WTqACA4baTJiCtW5b7uVeAp8vm8-gWzFR5BdDHVdQqu1TAKVWl_1P8NauDtd5M24MjVZd6WC0WrbTDPY9i2gieMMjFek2M8aoQFO0CG7r3JHn2zxfFB3THWCpl4VqZAQp6Ok7rymeY0Oayj--OLpNMBXIYUWc51eyYeurwQ943BSkf-m6PPVKO8T5U__Bx-biCNCePSlFKp7V0Du6h7UgYoqqonH2S3Jrg87c6dk7VJ7ca2i8sZqhy0rG6Kb7ENDVvwkMOdpnaFgdWd3VINp6P8j69kBQg-qwWP-YHPC9LnsjT2j1ktMowVO97eOpV4j2BhiThxunmu_SOIAEbghmjJEkLuRxLxBUPFRIajke2CvvFeIuReJr53isPKOxOjVzsc6oG5ZeykDlfz_mfEap7AByPNY0987zwG58tGueNxXjdpd7NQFcn_6DKj60SvUg0sk49V_QrDY3cAhSRvZoEeqA8XR97pEe7CByYMl80b9fzgyahc4NCdUwK8es2ll-lsJwEx1ZGdC8cB45QOrTnw8tJAUsSM44rLKwAQY-KsuN4UygO99d1CQZEm2YWtnPAvA9I-EhY87UIDx0CpPsEyxxFu2GZCTy7ceSnpcmQbAFWXzfBSpM7k42xVV8G8IK_bHpoF1enF5Vbc37_L_aWd4AgzuAwF_RVyd8exVh3NVJtO3BqPv72kTukr2Fok3KEaSeU0whP_dxr-thP2exS0F2Jdn13ZtB_pqxwKVWEsvzdbN92Q9qs10BAgYs2SA4cq66semwRl-1n-dr7XJyZzPOEiA9TQYgUCw0ueIc0ciMOZ0Waaj094bKIylw_TD5Bu1diXpzbTma_AVO-NZn7INhAZN3guSme-zIUEMrh66w0VJP-DbDA-ecSD41eMRSadyV4g86wLL4NOBE5NwSiSkwd2xJ9NqG7YohFM8BlPdEV4zhmqHcIKpVwAitFItqnAaUSU42Aebdritt9oNVnpKCeeA4QQv_8W7rOXJlLfGXRJUBCrh3Rv7KCVC3yncAOIU8FWu3jyaAqhLrWHLW958wjF8ka7lw80YZbToPjIuiii0UXu2w3Tv5EGVdkhf05A3Yj6M_LXStns8iBMzcU4-mJ1649FnnImLnW5AeohoWPBB6WYhW9gfwjuxejTI3Q5R0mo9jUSP3_tFiawlC2zFgvkNFufC6Kry8-Burjf8l6rpAX7_sjtCu1AlAbI6PEFtxcKhNWHfQp4mUATR6P4k68jk_Kl-FpRBtNOf8YOlLGrKE-WbwCoIV7VAgK2CTZJOxaslxVZRCLObNrA3XuEtc3jo8pMzqx8GJWshIgmF4XiQcmgh65U_kjB07adlgnbCZvGUXdIIQiA2vqIWC6Qu8SSO20nOOR65hGXyIgf4aOolU0Ljbi4slXnJKjbcPaX5O3cXvKHbkVFwXmHK2Ymaqb6fZcap78_On8jLK_GRlw3jV18SLeOcJiG2LqtHzcUawY4K7bPDNY2QX89yL5d4qxRF577QgzalmdQDsKyC_N-wk',
|
|
'type': 'reasoning',
|
|
},
|
|
{
|
|
'name': 'get_meaning_of_life',
|
|
'arguments': '{}',
|
|
'call_id': 'call_cp3x6W9eeyMIryJUNhgMaP5w',
|
|
'type': 'function_call',
|
|
'status': None,
|
|
'id': 'fc_01d311e2633707df0068fbac038f1c81a29847e80d6a1a3f60',
|
|
},
|
|
{'type': 'function_call_output', 'call_id': 'call_cp3x6W9eeyMIryJUNhgMaP5w', 'output': '42'},
|
|
{
|
|
'role': 'assistant',
|
|
'id': 'msg_01d311e2633707df0068fbac094ff481a297b1f4fdafb6ebd9',
|
|
'content': [{'text': '42', 'type': 'output_text', 'annotations': []}],
|
|
'type': 'message',
|
|
'status': 'completed',
|
|
},
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_raw_cot_only(allow_model_requests: None):
|
|
"""Test raw CoT content from gpt-oss models (no summary, only raw content)."""
|
|
c = response_message(
|
|
[
|
|
ResponseReasoningItem(
|
|
id='rs_123',
|
|
summary=[],
|
|
type='reasoning',
|
|
content=[
|
|
ReasoningContent(text='Let me think about this...', type='reasoning_text'),
|
|
ReasoningContent(text='The answer is 4.', type='reasoning_text'),
|
|
],
|
|
),
|
|
ResponseOutputMessage(
|
|
id='msg_123',
|
|
content=cast(list[Content], [ResponseOutputText(text='4', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
),
|
|
],
|
|
)
|
|
mock_client = MockOpenAIResponses.create_mock(c)
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(openai_client=mock_client))
|
|
|
|
agent = Agent(model=model)
|
|
result = await agent.run('What is 2+2?')
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What is 2+2?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_123',
|
|
provider_name='openai',
|
|
provider_details={'raw_content': ['Let me think about this...', 'The answer is 4.']},
|
|
),
|
|
TextPart(content='4', id='msg_123', provider_name='openai'),
|
|
],
|
|
model_name='gpt-4o-123',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1',
|
|
provider_details={'timestamp': datetime(2024, 1, 1, 0, 0, tzinfo=timezone.utc)},
|
|
provider_response_id='123',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_raw_cot_with_summary(allow_model_requests: None):
|
|
"""Test raw CoT content with summary from gpt-oss models.
|
|
|
|
When both summary and raw content exist, raw content is stored in provider_details
|
|
while summary goes in content.
|
|
"""
|
|
c = response_message(
|
|
[
|
|
ResponseReasoningItem(
|
|
id='rs_123',
|
|
summary=[Summary(text='Summary of thinking', type='summary_text')],
|
|
type='reasoning',
|
|
encrypted_content='encrypted_sig',
|
|
content=[
|
|
ReasoningContent(text='Raw thinking step 1', type='reasoning_text'),
|
|
ReasoningContent(text='Raw thinking step 2', type='reasoning_text'),
|
|
],
|
|
),
|
|
ResponseOutputMessage(
|
|
id='msg_123',
|
|
content=cast(list[Content], [ResponseOutputText(text='4', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
),
|
|
],
|
|
)
|
|
mock_client = MockOpenAIResponses.create_mock(c)
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(openai_client=mock_client))
|
|
|
|
agent = Agent(model=model)
|
|
result = await agent.run('What is 2+2?')
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What is 2+2?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='Summary of thinking',
|
|
id='rs_123',
|
|
signature='encrypted_sig',
|
|
provider_name='openai',
|
|
provider_details={'raw_content': ['Raw thinking step 1', 'Raw thinking step 2']},
|
|
),
|
|
TextPart(content='4', id='msg_123', provider_name='openai'),
|
|
],
|
|
model_name='gpt-4o-123',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1',
|
|
provider_details={'timestamp': datetime(2024, 1, 1, 0, 0, tzinfo=timezone.utc)},
|
|
provider_response_id='123',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_multiple_summaries(allow_model_requests: None):
|
|
"""Test reasoning item with multiple summaries.
|
|
|
|
When a reasoning item has multiple summary texts, each should become a separate ThinkingPart.
|
|
"""
|
|
c = response_message(
|
|
[
|
|
ResponseReasoningItem(
|
|
id='rs_123',
|
|
summary=[
|
|
Summary(text='First summary', type='summary_text'),
|
|
Summary(text='Second summary', type='summary_text'),
|
|
Summary(text='Third summary', type='summary_text'),
|
|
],
|
|
type='reasoning',
|
|
encrypted_content='encrypted_sig',
|
|
content=[
|
|
ReasoningContent(text='Raw thinking step 1', type='reasoning_text'),
|
|
],
|
|
),
|
|
ResponseOutputMessage(
|
|
id='msg_123',
|
|
content=cast(list[Content], [ResponseOutputText(text='Done', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
),
|
|
],
|
|
)
|
|
mock_client = MockOpenAIResponses.create_mock(c)
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(openai_client=mock_client))
|
|
|
|
agent = Agent(model=model)
|
|
result = await agent.run('Test multiple summaries')
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='Test multiple summaries',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='First summary',
|
|
id='rs_123',
|
|
signature='encrypted_sig',
|
|
provider_name='openai',
|
|
provider_details={'raw_content': ['Raw thinking step 1']},
|
|
),
|
|
ThinkingPart(content='Second summary', id='rs_123', provider_name='openai'),
|
|
ThinkingPart(content='Third summary', id='rs_123', provider_name='openai'),
|
|
TextPart(content='Done', id='msg_123', provider_name='openai'),
|
|
],
|
|
model_name='gpt-4o-123',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1',
|
|
provider_details={'timestamp': datetime(2024, 1, 1, 0, 0, tzinfo=timezone.utc)},
|
|
provider_response_id='123',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_raw_cot_stream_openrouter(allow_model_requests: None, openrouter_api_key: str):
|
|
"""Test streaming raw CoT content from gpt-oss via OpenRouter.
|
|
|
|
This is a live test (with cassette) that verifies the streaming raw CoT implementation
|
|
works end-to-end with a real gpt-oss model response.
|
|
"""
|
|
from pydantic_ai.providers.openrouter import OpenRouterProvider
|
|
|
|
model = OpenAIResponsesModel('openai/gpt-oss-20b', provider=OpenRouterProvider(api_key=openrouter_api_key))
|
|
agent = Agent(model=model)
|
|
async with agent.run_stream('What is 2+2?') as result:
|
|
output = await result.get_output()
|
|
assert output == snapshot('4')
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What is 2+2?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_tmp_2kbe7x16sax',
|
|
provider_name='openrouter',
|
|
provider_details={
|
|
'raw_content': [
|
|
'The user asks: "What is 2+2?" They expect a straightforward answer: 4. Just answer 4.'
|
|
]
|
|
},
|
|
),
|
|
TextPart(content='4', id='msg_tmp_8cjof4f6zpw', provider_name='openrouter'),
|
|
],
|
|
usage=RequestUsage(
|
|
input_tokens=78,
|
|
output_tokens=37,
|
|
details={'is_byok': 0, 'reasoning_tokens': 22},
|
|
),
|
|
model_name='openai/gpt-oss-20b',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openrouter',
|
|
provider_url='https://openrouter.ai/api/v1',
|
|
provider_details={
|
|
'finish_reason': 'completed',
|
|
'timestamp': datetime(2025, 11, 27, 17, 43, 31, tzinfo=timezone.utc),
|
|
},
|
|
provider_response_id='gen-1764265411-Fu1iEX7h5MRWiL79lb94',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_raw_cot_sent_in_multiturn(allow_model_requests: None):
|
|
"""Test that raw CoT and summaries are sent back correctly in multi-turn conversations.
|
|
|
|
Tests three distinct cases across turns:
|
|
- Turn 1: Only raw content (no summary) - gpt-oss style
|
|
- Turn 2: Summary AND raw content - hybrid case
|
|
- Turn 3: Only summary (no raw content) - official OpenAI style
|
|
"""
|
|
# Track messages sent to OpenAI
|
|
sent_openai_messages: list[Any] = []
|
|
|
|
# Turn 1: Only raw content, no summary
|
|
c1 = response_message(
|
|
[
|
|
ResponseReasoningItem(
|
|
id='rs_123',
|
|
summary=[],
|
|
type='reasoning',
|
|
content=[
|
|
ReasoningContent(text='Raw CoT step 1', type='reasoning_text'),
|
|
ReasoningContent(text='Raw CoT step 2', type='reasoning_text'),
|
|
],
|
|
),
|
|
ResponseOutputMessage(
|
|
id='msg_123',
|
|
content=cast(list[Content], [ResponseOutputText(text='4', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
),
|
|
],
|
|
)
|
|
|
|
# Turn 2: Summary and raw content
|
|
c2 = response_message(
|
|
[
|
|
ResponseReasoningItem(
|
|
id='rs_456',
|
|
summary=[
|
|
Summary(text='First summary', type='summary_text'),
|
|
Summary(text='Second summary', type='summary_text'),
|
|
],
|
|
type='reasoning',
|
|
encrypted_content='encrypted_sig_abc',
|
|
content=[
|
|
ReasoningContent(text='More raw thinking', type='reasoning_text'),
|
|
],
|
|
),
|
|
ResponseOutputMessage(
|
|
id='msg_456',
|
|
content=cast(list[Content], [ResponseOutputText(text='9', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
),
|
|
],
|
|
)
|
|
|
|
# Turn 3: Only summary, no raw content
|
|
c3 = response_message(
|
|
[
|
|
ResponseReasoningItem(
|
|
id='rs_789',
|
|
summary=[
|
|
Summary(text='Final summary', type='summary_text'),
|
|
],
|
|
type='reasoning',
|
|
encrypted_content='encrypted_sig_xyz',
|
|
content=[],
|
|
),
|
|
ResponseOutputMessage(
|
|
id='msg_789',
|
|
content=cast(list[Content], [ResponseOutputText(text='42', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
),
|
|
],
|
|
)
|
|
|
|
mock_client = MockOpenAIResponses.create_mock([c1, c2, c3])
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(openai_client=mock_client))
|
|
|
|
# Hook into model to capture sent messages
|
|
original_map_messages = model._map_messages # pyright: ignore[reportPrivateUsage]
|
|
|
|
async def capture_messages(*args: Any, **kwargs: Any) -> Any:
|
|
result = await original_map_messages(*args, **kwargs)
|
|
sent_openai_messages.append(result[1]) # result is (instructions, messages)
|
|
return result
|
|
|
|
model._map_messages = capture_messages # type: ignore[method-assign]
|
|
|
|
agent = Agent(model=model)
|
|
|
|
# Turn 1: Only raw content, no summary
|
|
result1 = await agent.run('What is 2+2?')
|
|
assert result1.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What is 2+2?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_123',
|
|
provider_name='openai',
|
|
provider_details={'raw_content': ['Raw CoT step 1', 'Raw CoT step 2']},
|
|
),
|
|
TextPart(content='4', id='msg_123', provider_name='openai'),
|
|
],
|
|
model_name='gpt-4o-123',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1',
|
|
provider_details={'timestamp': datetime(2024, 1, 1, 0, 0, tzinfo=timezone.utc)},
|
|
provider_response_id='123',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
# Turn 2: Summary and raw content
|
|
result2 = await agent.run('Add 5 to that', message_history=result1.all_messages())
|
|
assert result2.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What is 2+2?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_123',
|
|
provider_name='openai',
|
|
provider_details={'raw_content': ['Raw CoT step 1', 'Raw CoT step 2']},
|
|
),
|
|
TextPart(content='4', id='msg_123', provider_name='openai'),
|
|
],
|
|
model_name='gpt-4o-123',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1',
|
|
provider_details={'timestamp': datetime(2024, 1, 1, 0, 0, tzinfo=timezone.utc)},
|
|
provider_response_id='123',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='Add 5 to that',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='First summary',
|
|
id='rs_456',
|
|
signature='encrypted_sig_abc',
|
|
provider_name='openai',
|
|
provider_details={'raw_content': ['More raw thinking']},
|
|
),
|
|
ThinkingPart(content='Second summary', id='rs_456', provider_name='openai'),
|
|
TextPart(content='9', id='msg_456', provider_name='openai'),
|
|
],
|
|
model_name='gpt-4o-123',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1',
|
|
provider_details={'timestamp': datetime(2024, 1, 1, 0, 0, tzinfo=timezone.utc)},
|
|
provider_response_id='123',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
# Turn 3: Only summary, no raw content
|
|
result3 = await agent.run('What next?', message_history=result2.all_messages())
|
|
assert result3.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What is 2+2?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='',
|
|
id='rs_123',
|
|
provider_name='openai',
|
|
provider_details={'raw_content': ['Raw CoT step 1', 'Raw CoT step 2']},
|
|
),
|
|
TextPart(content='4', id='msg_123', provider_name='openai'),
|
|
],
|
|
model_name='gpt-4o-123',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1',
|
|
provider_details={'timestamp': datetime(2024, 1, 1, 0, 0, tzinfo=timezone.utc)},
|
|
provider_response_id='123',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='Add 5 to that',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='First summary',
|
|
id='rs_456',
|
|
signature='encrypted_sig_abc',
|
|
provider_name='openai',
|
|
provider_details={'raw_content': ['More raw thinking']},
|
|
),
|
|
ThinkingPart(content='Second summary', id='rs_456', provider_name='openai'),
|
|
TextPart(content='9', id='msg_456', provider_name='openai'),
|
|
],
|
|
model_name='gpt-4o-123',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1',
|
|
provider_details={'timestamp': datetime(2024, 1, 1, 0, 0, tzinfo=timezone.utc)},
|
|
provider_response_id='123',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What next?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
ThinkingPart(
|
|
content='Final summary',
|
|
id='rs_789',
|
|
signature='encrypted_sig_xyz',
|
|
provider_name='openai',
|
|
),
|
|
TextPart(content='42', id='msg_789', provider_name='openai'),
|
|
],
|
|
model_name='gpt-4o-123',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1',
|
|
provider_details={'timestamp': datetime(2024, 1, 1, 0, 0, tzinfo=timezone.utc)},
|
|
provider_response_id='123',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
# Verify what was sent to the API in each turn
|
|
assert len(sent_openai_messages) == 3
|
|
|
|
# Turn 2 messages: should contain raw-only reasoning item from turn 1
|
|
turn2_messages = sent_openai_messages[1]
|
|
turn2_reasoning = [msg for msg in turn2_messages if msg.get('type') == 'reasoning']
|
|
assert len(turn2_reasoning) == 1
|
|
assert turn2_reasoning[0] == snapshot(
|
|
{
|
|
'type': 'reasoning',
|
|
'summary': [],
|
|
'encrypted_content': None,
|
|
'id': 'rs_123',
|
|
'content': [
|
|
{'type': 'reasoning_text', 'text': 'Raw CoT step 1'},
|
|
{'type': 'reasoning_text', 'text': 'Raw CoT step 2'},
|
|
],
|
|
}
|
|
)
|
|
|
|
# Turn 3 messages: should contain both reasoning items
|
|
turn3_messages = sent_openai_messages[2]
|
|
turn3_reasoning = [msg for msg in turn3_messages if msg.get('type') == 'reasoning']
|
|
assert len(turn3_reasoning) == 2
|
|
assert turn3_reasoning[0] == snapshot(
|
|
{
|
|
'type': 'reasoning',
|
|
'summary': [],
|
|
'encrypted_content': None,
|
|
'id': 'rs_123',
|
|
'content': [
|
|
{'type': 'reasoning_text', 'text': 'Raw CoT step 1'},
|
|
{'type': 'reasoning_text', 'text': 'Raw CoT step 2'},
|
|
],
|
|
}
|
|
)
|
|
assert turn3_reasoning[1] == snapshot(
|
|
{
|
|
'type': 'reasoning',
|
|
'summary': [
|
|
{'type': 'summary_text', 'text': 'First summary'},
|
|
{'type': 'summary_text', 'text': 'Second summary'},
|
|
],
|
|
'encrypted_content': 'encrypted_sig_abc',
|
|
'id': 'rs_456',
|
|
'content': [
|
|
{'type': 'reasoning_text', 'text': 'More raw thinking'},
|
|
],
|
|
}
|
|
)
|
|
|
|
|
|
async def test_openai_responses_unified_thinking_with_send_reasoning_ids(allow_model_requests: None):
|
|
"""Unified `thinking` and `openai_send_reasoning_ids` compose across a 2-turn round-trip.
|
|
|
|
The unified `thinking='high'` (not `openai_reasoning_effort`) drives `reasoning.effort='high'` on the wire,
|
|
and the reasoning item it yields is replayed on turn 2 with its provider id intact because
|
|
`openai_send_reasoning_ids=True`. Locks that the unified setting and reasoning-id replay don't interfere.
|
|
"""
|
|
c1 = response_message(
|
|
[
|
|
ResponseReasoningItem(
|
|
id='rs_123',
|
|
summary=[Summary(text='thinking about it', type='summary_text')],
|
|
encrypted_content='enc_123',
|
|
type='reasoning',
|
|
),
|
|
ResponseOutputMessage(
|
|
id='msg_123',
|
|
content=cast(list[Content], [ResponseOutputText(text='4', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
),
|
|
],
|
|
)
|
|
c2 = response_message(
|
|
[
|
|
ResponseOutputMessage(
|
|
id='msg_456',
|
|
content=cast(list[Content], [ResponseOutputText(text='9', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
),
|
|
],
|
|
)
|
|
mock_client = MockOpenAIResponses.create_mock([c1, c2])
|
|
model = OpenAIResponsesModel(
|
|
'gpt-5',
|
|
provider=OpenAIProvider(openai_client=mock_client),
|
|
settings=OpenAIResponsesModelSettings(thinking='high', openai_send_reasoning_ids=True),
|
|
)
|
|
agent = Agent(model=model)
|
|
|
|
result1 = await agent.run('What is 2+2?')
|
|
await agent.run('Add 5 to that', message_history=result1.all_messages())
|
|
|
|
turn1_kwargs, turn2_kwargs = get_mock_responses_kwargs(mock_client)
|
|
# Unified `thinking` drives `reasoning.effort` on the wire for both turns.
|
|
assert turn1_kwargs['reasoning'] == {'effort': 'high'}
|
|
assert turn2_kwargs['reasoning'] == {'effort': 'high'}
|
|
# The turn-1 reasoning item is replayed on turn 2 with its provider id intact.
|
|
turn2_reasoning = [m for m in turn2_kwargs['input'] if m.get('type') == 'reasoning']
|
|
assert [m['id'] for m in turn2_reasoning] == ['rs_123']
|
|
|
|
|
|
async def test_openai_responses_model_file_search_tool(tmp_path: Path, allow_model_requests: None, openai_api_key: str):
|
|
async_client = AsyncOpenAI(api_key=openai_api_key)
|
|
|
|
test_file_path = tmp_path / 'file.txt'
|
|
test_file_path.touch()
|
|
test_file_path.write_text('Paris is the capital of France. It is known for the Eiffel Tower.')
|
|
|
|
file = None
|
|
vector_store = None
|
|
try:
|
|
file = await async_client.files.create(file=test_file_path, purpose='assistants')
|
|
|
|
vector_store = await async_client.vector_stores.create(name='test-file-search')
|
|
await async_client.vector_stores.files.create(vector_store_id=vector_store.id, file_id=file.id)
|
|
|
|
m = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(openai_client=async_client))
|
|
agent = Agent(
|
|
m,
|
|
instructions='You are a helpful assistant.',
|
|
capabilities=[NativeTool(FileSearchTool(file_store_ids=[vector_store.id]))],
|
|
)
|
|
|
|
result = await agent.run('What is the capital of France?')
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What is the capital of France?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
instructions='You are a helpful assistant.',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
NativeToolCallPart(
|
|
tool_name='file_search',
|
|
args={'queries': ['What is the capital of France?']},
|
|
tool_call_id=IsStr(),
|
|
id='fs_0995dffd0769e0bd006931d72fecf48194bf314348aa6b2494',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='file_search',
|
|
content={'status': 'completed'},
|
|
tool_call_id=IsStr(),
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
TextPart(content='The capital of France is Paris.', id=IsStr(), provider_name='openai'),
|
|
],
|
|
usage=RequestUsage(input_tokens=870, output_tokens=30, details={'reasoning_tokens': 0}),
|
|
model_name='gpt-4o-2024-08-06',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={'finish_reason': 'completed', 'timestamp': IsDatetime()},
|
|
provider_response_id=IsStr(),
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
messages = result.all_messages()
|
|
result = await agent.run(user_prompt='Tell me about the Eiffel Tower.', message_history=messages)
|
|
assert result.new_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='Tell me about the Eiffel Tower.',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
instructions='You are a helpful assistant.',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
NativeToolCallPart(
|
|
tool_name='file_search',
|
|
args={'queries': ['Eiffel Tower']},
|
|
tool_call_id=IsStr(),
|
|
id='fs_019d8541afd6ed8d006931d7382ef481998c5cd3117a0ff8fe',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='file_search',
|
|
content={'status': 'completed'},
|
|
tool_call_id=IsStr(),
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
TextPart(
|
|
content='The Eiffel Tower is a famous landmark in Paris, the capital of France. It is widely recognized and serves as an iconic symbol of the city.',
|
|
id=IsStr(),
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
usage=RequestUsage(input_tokens=1188, output_tokens=55, details={'reasoning_tokens': 0}),
|
|
model_name='gpt-4o-2024-08-06',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={'finish_reason': 'completed', 'timestamp': IsDatetime()},
|
|
provider_response_id=IsStr(),
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
finally:
|
|
await _cleanup_openai_resources(file, vector_store, async_client)
|
|
|
|
|
|
def test_map_file_search_tool_call():
|
|
from openai.types.responses.response_file_search_tool_call import ResponseFileSearchToolCall
|
|
|
|
from pydantic_ai.models.openai import _map_file_search_tool_call # pyright: ignore[reportPrivateUsage]
|
|
|
|
item = ResponseFileSearchToolCall.model_validate(
|
|
{
|
|
'id': 'test-id',
|
|
'queries': ['test query'],
|
|
'status': 'completed',
|
|
'results': [
|
|
{
|
|
'id': 'result-1',
|
|
'title': 'Test Result',
|
|
'url': 'https://example.com',
|
|
'score': 0.9,
|
|
}
|
|
],
|
|
'type': 'file_search_call',
|
|
}
|
|
)
|
|
|
|
call_part, return_part = _map_file_search_tool_call(item, 'openai')
|
|
assert (call_part, return_part) == snapshot(
|
|
(
|
|
NativeToolCallPart(
|
|
tool_name='file_search',
|
|
args={'queries': ['test query']},
|
|
tool_call_id='test-id',
|
|
id='test-id',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='file_search',
|
|
content={
|
|
'status': 'completed',
|
|
'results': [
|
|
{
|
|
'attributes': None,
|
|
'file_id': None,
|
|
'filename': None,
|
|
'id': 'result-1',
|
|
'text': None,
|
|
'title': 'Test Result',
|
|
'url': 'https://example.com',
|
|
'score': 0.9,
|
|
}
|
|
],
|
|
},
|
|
tool_call_id='test-id',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
)
|
|
)
|
|
|
|
|
|
async def test_openai_responses_model_file_search_tool_stream(
|
|
tmp_path: Path, allow_model_requests: None, openai_api_key: str
|
|
):
|
|
async_client = AsyncOpenAI(api_key=openai_api_key)
|
|
|
|
test_file_path = tmp_path / 'file.txt'
|
|
test_file_path.touch()
|
|
test_file_path.write_text('Paris is the capital of France. It is known for the Eiffel Tower.')
|
|
|
|
file = None
|
|
vector_store = None
|
|
try:
|
|
file = await async_client.files.create(file=test_file_path, purpose='assistants')
|
|
|
|
vector_store = await async_client.vector_stores.create(name='test-file-search-stream')
|
|
await async_client.vector_stores.files.create(vector_store_id=vector_store.id, file_id=file.id)
|
|
|
|
m = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(openai_client=async_client))
|
|
agent = Agent(
|
|
m,
|
|
instructions='You are a helpful assistant.',
|
|
capabilities=[NativeTool(FileSearchTool(file_store_ids=[vector_store.id]))],
|
|
)
|
|
|
|
event_parts: list[Any] = []
|
|
async with agent.iter(user_prompt='What is the capital of France?') as agent_run:
|
|
async for node in agent_run:
|
|
if Agent.is_model_request_node(node) or Agent.is_call_tools_node(node):
|
|
async with node.stream(agent_run.ctx) as request_stream:
|
|
async for event in request_stream:
|
|
event_parts.append(event)
|
|
|
|
assert agent_run.result is not None
|
|
messages = agent_run.result.all_messages()
|
|
assert messages == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What is the capital of France?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
instructions='You are a helpful assistant.',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
NativeToolCallPart(
|
|
tool_name='file_search',
|
|
args={'queries': ['What is the capital of France?']},
|
|
tool_call_id=IsStr(),
|
|
id='fs_006dcb10dc68b990006931d758d64c819b8936fb07f31c09d4',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='file_search',
|
|
content={'status': 'completed'},
|
|
tool_call_id=IsStr(),
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
TextPart(content='The capital of France is Paris.', id=IsStr(), provider_name='openai'),
|
|
],
|
|
usage=RequestUsage(input_tokens=1177, output_tokens=37, details={'reasoning_tokens': 0}),
|
|
model_name='gpt-4o-2024-08-06',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={'finish_reason': 'completed', 'timestamp': IsDatetime()},
|
|
provider_response_id=IsStr(),
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
assert event_parts == snapshot(
|
|
[
|
|
PartStartEvent(
|
|
index=0,
|
|
part=NativeToolCallPart(
|
|
tool_name='file_search',
|
|
tool_call_id=IsStr(),
|
|
id='fs_006dcb10dc68b990006931d758d64c819b8936fb07f31c09d4',
|
|
provider_name='openai',
|
|
),
|
|
),
|
|
PartDeltaEvent(
|
|
index=0,
|
|
delta=ToolCallPartDelta(
|
|
args_delta={'queries': ['What is the capital of France?']},
|
|
tool_call_id=IsStr(),
|
|
),
|
|
),
|
|
PartEndEvent(
|
|
index=0,
|
|
part=NativeToolCallPart(
|
|
tool_name='file_search',
|
|
args={'queries': ['What is the capital of France?']},
|
|
tool_call_id=IsStr(),
|
|
id='fs_006dcb10dc68b990006931d758d64c819b8936fb07f31c09d4',
|
|
provider_name='openai',
|
|
),
|
|
next_part_kind='builtin-tool-return',
|
|
),
|
|
PartStartEvent(
|
|
index=1,
|
|
part=NativeToolReturnPart(
|
|
tool_name='file_search',
|
|
content={'status': 'completed'},
|
|
tool_call_id=IsStr(),
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
previous_part_kind='builtin-tool-call',
|
|
),
|
|
PartStartEvent(
|
|
index=2,
|
|
part=TextPart(content='The', id=IsStr(), provider_name='openai'),
|
|
previous_part_kind='builtin-tool-return',
|
|
),
|
|
FinalResultEvent(tool_name=None, tool_call_id=None),
|
|
PartDeltaEvent(index=2, delta=TextPartDelta(content_delta=' capital')),
|
|
PartDeltaEvent(index=2, delta=TextPartDelta(content_delta=' of')),
|
|
PartDeltaEvent(index=2, delta=TextPartDelta(content_delta=' France')),
|
|
PartDeltaEvent(index=2, delta=TextPartDelta(content_delta=' is')),
|
|
PartDeltaEvent(index=2, delta=TextPartDelta(content_delta=' Paris')),
|
|
PartDeltaEvent(index=2, delta=TextPartDelta(content_delta='.')),
|
|
PartEndEvent(
|
|
index=2,
|
|
part=TextPart(content='The capital of France is Paris.', id=IsStr(), provider_name='openai'),
|
|
),
|
|
]
|
|
)
|
|
|
|
finally:
|
|
await _cleanup_openai_resources(file, vector_store, async_client)
|
|
|
|
|
|
async def test_openai_responses_model_file_search_tool_with_results(
|
|
tmp_path: Path, allow_model_requests: None, openai_api_key: str
|
|
):
|
|
"""Test that openai_include_file_search_results setting includes file search results in the response."""
|
|
async_client = AsyncOpenAI(api_key=openai_api_key)
|
|
|
|
test_file_path = tmp_path / 'file.txt'
|
|
test_file_path.touch()
|
|
test_file_path.write_text('Paris is the capital of France. It is known for the Eiffel Tower.')
|
|
|
|
file = None
|
|
vector_store = None
|
|
try:
|
|
with open(test_file_path, 'rb') as f:
|
|
file = await async_client.files.create(file=f, purpose='assistants')
|
|
|
|
vector_store = await async_client.vector_stores.create(name='test-file-search-with-results')
|
|
await async_client.vector_stores.files.create(vector_store_id=vector_store.id, file_id=file.id)
|
|
|
|
m = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(openai_client=async_client))
|
|
agent = Agent(
|
|
m,
|
|
instructions='You are a helpful assistant.',
|
|
capabilities=[NativeTool(FileSearchTool(file_store_ids=[vector_store.id]))],
|
|
)
|
|
|
|
# Use the openai_include_file_search_results setting to include search results
|
|
result = await agent.run(
|
|
'What is the capital of France?',
|
|
model_settings=OpenAIResponsesModelSettings(openai_include_file_search_results=True),
|
|
)
|
|
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='What is the capital of France?',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
instructions='You are a helpful assistant.',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
NativeToolCallPart(
|
|
tool_name='file_search',
|
|
args={'queries': ['What is the capital of France?']},
|
|
tool_call_id=IsStr(),
|
|
id='fs_08aa886305ae5628006939ad6cfa30819a85b07d52d61eb121',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='file_search',
|
|
content={
|
|
'status': 'completed',
|
|
'results': [
|
|
{
|
|
'attributes': {},
|
|
'file_id': IsStr(),
|
|
'filename': IsStr(),
|
|
'score': IsFloat(),
|
|
'text': 'Paris is the capital of France. It is known for the Eiffel Tower.',
|
|
'vector_store_id': IsStr(),
|
|
}
|
|
],
|
|
},
|
|
tool_call_id=IsStr(),
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
TextPart(content=IsStr(), id=IsStr(), provider_name='openai'),
|
|
],
|
|
usage=RequestUsage(input_tokens=IsInt(), output_tokens=IsInt(), details={'reasoning_tokens': 0}),
|
|
model_name='gpt-4o-2024-08-06',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={'finish_reason': 'completed', 'timestamp': IsDatetime()},
|
|
provider_response_id=IsStr(),
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
finally:
|
|
await _cleanup_openai_resources(file, vector_store, async_client)
|
|
|
|
|
|
async def test_openai_responses_runs_with_instructions_only(
|
|
allow_model_requests: None,
|
|
openai_api_key: str,
|
|
):
|
|
model = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model=model, instructions='Generate a short article about artificial intelligence in 3 sentences.')
|
|
|
|
# Run with only instructions, no explicit input messages
|
|
result = await agent.run()
|
|
|
|
# Verify we got a valid response
|
|
assert result.output
|
|
assert isinstance(result.output, str)
|
|
assert len(result.output) > 0
|
|
|
|
|
|
async def test_web_search_call_action_find_in_page(allow_model_requests: None):
|
|
"""Test for https://github.com/pydantic/pydantic-ai/issues/3653"""
|
|
c1 = response_message(
|
|
[
|
|
ResponseFunctionWebSearch.model_construct(
|
|
id='web-search-1',
|
|
action={
|
|
'type': 'find_in_page',
|
|
'pattern': 'test',
|
|
'url': 'https://example.com',
|
|
},
|
|
status='completed',
|
|
type='web_search_call',
|
|
),
|
|
]
|
|
)
|
|
c2 = response_message(
|
|
[
|
|
ResponseOutputMessage(
|
|
id='output-1',
|
|
content=cast(list[Content], [ResponseOutputText(text='done', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
)
|
|
]
|
|
)
|
|
mock_client = MockOpenAIResponses.create_mock([c1, c2])
|
|
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(openai_client=mock_client))
|
|
agent = Agent(model=model)
|
|
|
|
result = await agent.run('test')
|
|
|
|
assert result.all_messages()[1] == snapshot(
|
|
ModelResponse(
|
|
parts=[
|
|
NativeToolCallPart(
|
|
tool_name='web_search',
|
|
args={'type': 'find_in_page', 'pattern': 'test', 'url': 'https://example.com'},
|
|
tool_call_id='web-search-1',
|
|
id='web-search-1',
|
|
provider_name='openai',
|
|
),
|
|
NativeToolReturnPart(
|
|
tool_name='web_search',
|
|
content={'status': 'completed'},
|
|
tool_call_id='web-search-1',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
),
|
|
],
|
|
model_name='gpt-4o-123',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1',
|
|
provider_details={'timestamp': datetime(2024, 1, 1, 0, 0, tzinfo=timezone.utc)},
|
|
provider_response_id='123',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
)
|
|
)
|
|
|
|
response_kwargs = get_mock_responses_kwargs(mock_client)
|
|
assert response_kwargs[1]['input'][1] == snapshot(
|
|
{
|
|
'id': 'web-search-1',
|
|
'action': {'type': 'find_in_page', 'pattern': 'test', 'url': 'https://example.com'},
|
|
'status': 'completed',
|
|
'type': 'web_search_call',
|
|
}
|
|
)
|
|
|
|
|
|
async def test_openai_responses_system_prompts_ordering(allow_model_requests: None):
|
|
"""Test that system prompts are correctly ordered in mapped messages."""
|
|
c = response_message(
|
|
[
|
|
ResponseOutputMessage(
|
|
id='msg_123',
|
|
content=cast(list[Content], [ResponseOutputText(text='ok', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
),
|
|
],
|
|
)
|
|
mock_client = MockOpenAIResponses.create_mock(c)
|
|
model = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(openai_client=mock_client))
|
|
|
|
messages: list[ModelRequest | ModelResponse] = [
|
|
ModelRequest(
|
|
parts=[
|
|
SystemPromptPart(content='System prompt 1'),
|
|
SystemPromptPart(content='System prompt 2'),
|
|
UserPromptPart(content='Hello'),
|
|
],
|
|
instructions='Instructions content',
|
|
),
|
|
]
|
|
|
|
instructions, openai_messages = await model._map_messages( # type: ignore[reportPrivateUsage]
|
|
messages,
|
|
model_settings=cast(OpenAIResponsesModelSettings, {}),
|
|
model_request_parameters=ModelRequestParameters(),
|
|
)
|
|
|
|
# Verify instructions are returned separately
|
|
assert instructions == 'Instructions content'
|
|
|
|
# Verify system prompts are in order, followed by user message
|
|
assert openai_messages == snapshot(
|
|
[
|
|
{'role': 'system', 'content': 'System prompt 1'},
|
|
{'role': 'system', 'content': 'System prompt 2'},
|
|
{'role': 'user', 'content': 'Hello'},
|
|
]
|
|
)
|
|
|
|
|
|
async def test_reasoning_summary_auto(allow_model_requests: None, openai_api_key: str):
|
|
model = OpenAIResponsesModel('gpt-5.2', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model, instructions='You are a helpful coding assistant.')
|
|
settings = OpenAIResponsesModelSettings(openai_reasoning_effort='high', openai_reasoning_summary='auto')
|
|
|
|
result = await agent.run(
|
|
'Write a Python function that calculates the factorial of a number. Think step by step.',
|
|
model_settings=settings,
|
|
)
|
|
assert result.response.thinking == snapshot("""\
|
|
**Generating factorial function**
|
|
|
|
I need to respond with a Python function for calculating the factorial. The user wants me to think step-by-step, but I need to keep my reasoning brief. I'll provide a brief explanation of how the function works and include some input validation. I could choose either an iterative or recursive approach. I'll keep the details high-level, showing only the essential steps before presenting the final code to the user.\
|
|
""")
|
|
|
|
|
|
async def test_responses_count_tokens(allow_model_requests: None, openai_api_key: str) -> None:
|
|
model = OpenAIResponsesModel('gpt-4.1-mini', provider=OpenAIProvider(api_key=openai_api_key))
|
|
|
|
result = await model.count_tokens(
|
|
[ModelRequest(parts=[], instructions='Follow the system instructions.')],
|
|
OpenAIResponsesModelSettings(timeout=123.0),
|
|
ModelRequestParameters(),
|
|
)
|
|
|
|
assert result.input_tokens == snapshot(16)
|
|
|
|
|
|
async def test_responses_count_tokens_no_messages(allow_model_requests: None, openai_api_key: str) -> None:
|
|
model = OpenAIResponsesModel('gpt-4.1-mini', provider=OpenAIProvider(api_key=openai_api_key))
|
|
|
|
with pytest.raises(UserError, match='Cannot count tokens without any messages or a previous response ID'):
|
|
await model.count_tokens([], None, ModelRequestParameters())
|
|
|
|
|
|
async def test_responses_count_tokens_with_tools(allow_model_requests: None, openai_api_key: str) -> None:
|
|
model = OpenAIResponsesModel('gpt-4.1-mini', provider=OpenAIProvider(api_key=openai_api_key))
|
|
|
|
tool_def = ToolDefinition(
|
|
name='get_weather',
|
|
description='Get the weather for a location',
|
|
parameters_json_schema={'type': 'object', 'properties': {'location': {'type': 'string'}}},
|
|
)
|
|
result = await model.count_tokens(
|
|
[ModelRequest.user_text_prompt('What is the weather in Paris?')],
|
|
None,
|
|
ModelRequestParameters(function_tools=[tool_def], allow_text_output=False),
|
|
)
|
|
|
|
assert result.input_tokens == snapshot(51)
|
|
|
|
|
|
async def test_responses_usage_limit_exceeded(allow_model_requests: None, openai_api_key: str) -> None:
|
|
model = OpenAIResponsesModel('gpt-4.1-mini', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model)
|
|
|
|
with pytest.raises(
|
|
UsageLimitExceeded,
|
|
match=r'The next request would exceed the input_tokens_limit of 9 \(input_tokens=18\)',
|
|
):
|
|
await agent.run(
|
|
'The quick brown fox jumps over the lazy dog.',
|
|
usage_limits=UsageLimits(input_tokens_limit=9, count_tokens_before_request=True),
|
|
)
|
|
|
|
|
|
async def test_responses_usage_limit_not_exceeded(allow_model_requests: None, openai_api_key: str) -> None:
|
|
model = OpenAIResponsesModel('gpt-4.1-mini', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model)
|
|
|
|
result = await agent.run(
|
|
'The quick brown fox jumps over the lazy dog.',
|
|
usage_limits=UsageLimits(input_tokens_limit=25, count_tokens_before_request=True),
|
|
)
|
|
assert result.output == snapshot(
|
|
"That's a classic pangram! It contains every letter of the English alphabet"
|
|
" at least once. It's commonly used for testing fonts, typewriters, and keyboards."
|
|
)
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[UserPromptPart(content='The quick brown fox jumps over the lazy dog.', timestamp=IsDatetime())],
|
|
timestamp=IsDatetime(),
|
|
run_id=(run_id := IsStr()),
|
|
conversation_id=(conversation_id := IsStr()),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
TextPart(
|
|
content="That's a classic pangram! It contains every letter of the English alphabet at least once. It's commonly used for testing fonts, typewriters, and keyboards.",
|
|
id=IsStr(),
|
|
provider_name='openai',
|
|
)
|
|
],
|
|
usage=RequestUsage(input_tokens=18, output_tokens=28, details={'reasoning_tokens': 0}),
|
|
model_name='gpt-4.1-mini',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1/',
|
|
provider_details={'finish_reason': 'completed', 'timestamp': IsDatetime()},
|
|
provider_response_id=IsStr(),
|
|
finish_reason='stop',
|
|
run_id=run_id,
|
|
conversation_id=conversation_id,
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_include_raw_annotations_non_streaming(allow_model_requests: None, openai_api_key: str):
|
|
"""Test that text annotations are included in provider_details when the setting is enabled."""
|
|
prompt = 'What is the tallest mountain in Alberta? Provide one sentence with a citation.'
|
|
instructions = 'Use web search and include citations in your answer.'
|
|
|
|
model = OpenAIResponsesModel('gpt-5.2', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model, instructions=instructions, capabilities=[NativeTool(WebSearchTool())])
|
|
|
|
# Test with annotations enabled
|
|
settings = OpenAIResponsesModelSettings(openai_include_raw_annotations=True)
|
|
result = await agent.run(prompt, model_settings=settings)
|
|
|
|
messages = result.all_messages()
|
|
assert messages[0] == ModelRequest(
|
|
parts=[UserPromptPart(content=prompt, timestamp=IsDatetime())],
|
|
timestamp=IsDatetime(),
|
|
instructions=instructions,
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
)
|
|
response = cast(ModelResponse, messages[1])
|
|
assert response.provider_name == 'openai'
|
|
assert response.provider_url == 'https://api.openai.com/v1/'
|
|
assert response.finish_reason == 'stop'
|
|
|
|
tool_call = next(part for part in response.parts if isinstance(part, NativeToolCallPart))
|
|
assert tool_call.tool_name == 'web_search'
|
|
assert isinstance(tool_call.args, dict)
|
|
assert tool_call.args.get('query') == 'tallest mountain in Alberta'
|
|
assert tool_call.args.get('type') == 'search'
|
|
|
|
text_part = next(part for part in response.parts if isinstance(part, TextPart))
|
|
assert text_part.provider_details and 'annotations' in text_part.provider_details
|
|
|
|
# Test with annotations disabled (default)
|
|
model2 = OpenAIResponsesModel('gpt-5.2', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent2 = Agent(model2, instructions=instructions, capabilities=[NativeTool(WebSearchTool())])
|
|
result2 = await agent2.run(prompt)
|
|
|
|
messages2 = result2.all_messages()
|
|
assert messages2[0] == ModelRequest(
|
|
parts=[UserPromptPart(content=prompt, timestamp=IsDatetime())],
|
|
timestamp=IsDatetime(),
|
|
instructions=instructions,
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
)
|
|
response2 = cast(ModelResponse, messages2[1])
|
|
text_part2 = next(part for part in response2.parts if isinstance(part, TextPart))
|
|
assert not (text_part2.provider_details or {}).get('annotations')
|
|
|
|
|
|
async def test_openai_responses_refusal_non_streaming(allow_model_requests: None):
|
|
"""Test that ResponseOutputRefusal in content triggers ContentFilterError."""
|
|
c = response_message(
|
|
[
|
|
ResponseOutputMessage(
|
|
id='msg_001',
|
|
content=[ResponseOutputRefusal(refusal="I can't help with that request.", type='refusal')],
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
)
|
|
]
|
|
)
|
|
c.model = 'gpt-4o'
|
|
|
|
mock_client = MockOpenAIResponses.create_mock(c)
|
|
model = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(openai_client=mock_client))
|
|
agent = Agent(model=model)
|
|
|
|
with pytest.raises(
|
|
ContentFilterError,
|
|
match=re.escape('Content filter triggered. Refusal: "I can\'t help with that request."'),
|
|
) as exc_info:
|
|
await agent.run('harmful prompt')
|
|
|
|
assert exc_info.value.body is not None
|
|
body_json = json.loads(exc_info.value.body)
|
|
response_msg = body_json[0]
|
|
assert response_msg['parts'] == []
|
|
assert response_msg['finish_reason'] == 'content_filter'
|
|
assert response_msg['provider_details']['refusal'] == "I can't help with that request."
|
|
|
|
|
|
async def test_openai_responses_refusal_streaming(allow_model_requests: None):
|
|
"""Test that ResponseRefusalDeltaEvent/DoneEvent in streaming triggers ContentFilterError."""
|
|
base_response = resp.Response(
|
|
id='resp_001',
|
|
model='gpt-4o',
|
|
object='response',
|
|
created_at=1704067200,
|
|
output=[],
|
|
parallel_tool_calls=True,
|
|
tool_choice='auto',
|
|
tools=[],
|
|
)
|
|
|
|
stream: list[resp.ResponseStreamEvent] = [
|
|
resp.ResponseCreatedEvent(response=base_response, type='response.created', sequence_number=0),
|
|
resp.ResponseInProgressEvent(response=base_response, type='response.in_progress', sequence_number=1),
|
|
resp.ResponseOutputItemAddedEvent(
|
|
item=ResponseOutputMessage(
|
|
id='msg_001',
|
|
content=[],
|
|
role='assistant',
|
|
status='in_progress',
|
|
type='message',
|
|
),
|
|
output_index=0,
|
|
type='response.output_item.added',
|
|
sequence_number=2,
|
|
),
|
|
ResponseRefusalDeltaEvent(
|
|
content_index=0,
|
|
delta="I can't help ",
|
|
item_id='msg_001',
|
|
output_index=0,
|
|
type='response.refusal.delta',
|
|
sequence_number=3,
|
|
),
|
|
ResponseRefusalDeltaEvent(
|
|
content_index=0,
|
|
delta='with that.',
|
|
item_id='msg_001',
|
|
output_index=0,
|
|
type='response.refusal.delta',
|
|
sequence_number=4,
|
|
),
|
|
ResponseRefusalDoneEvent(
|
|
content_index=0,
|
|
item_id='msg_001',
|
|
output_index=0,
|
|
refusal="I can't help with that.",
|
|
type='response.refusal.done',
|
|
sequence_number=5,
|
|
),
|
|
resp.ResponseCompletedEvent(
|
|
response=base_response.model_copy(update={'status': 'completed'}),
|
|
type='response.completed',
|
|
sequence_number=6,
|
|
),
|
|
]
|
|
|
|
mock_client = MockOpenAIResponses.create_mock_stream(stream)
|
|
model = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(openai_client=mock_client))
|
|
agent = Agent(model=model)
|
|
|
|
with pytest.raises(ContentFilterError, match='Content filter triggered') as exc_info:
|
|
async with agent.run_stream('harmful prompt'):
|
|
pass
|
|
|
|
assert exc_info.value.body is not None
|
|
body_json = json.loads(exc_info.value.body)
|
|
response_msg = body_json[0]
|
|
assert response_msg['parts'] == []
|
|
assert response_msg['finish_reason'] == 'content_filter'
|
|
assert response_msg['provider_details']['refusal'] == "I can't help with that."
|
|
|
|
|
|
async def test_stream_cancel(allow_model_requests: None):
|
|
from openai.types import responses as resp
|
|
|
|
base_response = resp.Response(
|
|
id='resp_001',
|
|
model='gpt-4o',
|
|
object='response',
|
|
created_at=1704067200,
|
|
output=[],
|
|
parallel_tool_calls=True,
|
|
tool_choice='auto',
|
|
tools=[],
|
|
)
|
|
|
|
stream: list[resp.ResponseStreamEvent] = [
|
|
resp.ResponseCreatedEvent(response=base_response, type='response.created', sequence_number=0),
|
|
resp.ResponseInProgressEvent(response=base_response, type='response.in_progress', sequence_number=1),
|
|
resp.ResponseOutputItemAddedEvent(
|
|
item=ResponseOutputMessage(
|
|
id='msg_001',
|
|
content=[],
|
|
role='assistant',
|
|
status='in_progress',
|
|
type='message',
|
|
),
|
|
output_index=0,
|
|
type='response.output_item.added',
|
|
sequence_number=2,
|
|
),
|
|
resp.ResponseTextDeltaEvent(
|
|
item_id='msg_001',
|
|
output_index=0,
|
|
content_index=0,
|
|
delta='hello ',
|
|
logprobs=[],
|
|
type='response.output_text.delta',
|
|
sequence_number=3,
|
|
),
|
|
resp.ResponseTextDeltaEvent(
|
|
item_id='msg_001',
|
|
output_index=0,
|
|
content_index=0,
|
|
delta='world',
|
|
logprobs=[],
|
|
type='response.output_text.delta',
|
|
sequence_number=4,
|
|
),
|
|
resp.ResponseCompletedEvent(
|
|
response=base_response.model_copy(update={'status': 'completed'}),
|
|
type='response.completed',
|
|
sequence_number=5,
|
|
),
|
|
]
|
|
|
|
mock_client = MockOpenAIResponses.create_mock_stream(stream)
|
|
model = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(openai_client=mock_client))
|
|
agent = Agent(model=model)
|
|
|
|
async with agent.run_stream('') as result:
|
|
async for _ in result.stream_text(delta=True, debounce_by=None): # pragma: no branch
|
|
break
|
|
await result.cancel()
|
|
await result.cancel() # double cancel is a no-op
|
|
assert result.cancelled
|
|
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[UserPromptPart(content='', timestamp=IsDatetime())],
|
|
timestamp=IsDatetime(),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[TextPart(content='hello ', id='msg_001', provider_name='openai')],
|
|
model_name='gpt-4o',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1',
|
|
provider_details={'timestamp': IsDatetime()},
|
|
provider_response_id='resp_001',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
state='interrupted',
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_null_text(allow_model_requests: None):
|
|
"""Test that ResponseOutputText with text=null (from gateways like Bifrost) is handled gracefully."""
|
|
c = response_message(
|
|
[
|
|
ResponseOutputMessage(
|
|
id='msg_001',
|
|
content=cast(
|
|
list[Content],
|
|
[
|
|
ResponseOutputText.model_construct(text=None, type='output_text', annotations=[]),
|
|
ResponseOutputText(text='Hello', type='output_text', annotations=[]),
|
|
],
|
|
),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
)
|
|
]
|
|
)
|
|
mock_client = MockOpenAIResponses.create_mock(c)
|
|
model = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(openai_client=mock_client))
|
|
agent = Agent(model=model)
|
|
|
|
result = await agent.run('Hello')
|
|
assert result.output == snapshot('Hello')
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='Hello',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[
|
|
TextPart(content='', id='msg_001', provider_name='openai'),
|
|
TextPart(content='Hello', id='msg_001', provider_name='openai'),
|
|
],
|
|
usage=RequestUsage(),
|
|
model_name='gpt-4o-123',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1',
|
|
provider_details={'timestamp': datetime(2024, 1, 1, 0, 0, tzinfo=timezone.utc)},
|
|
provider_response_id='123',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_null_text_stream(allow_model_requests: None):
|
|
"""Test that ResponseTextDeltaEvent with delta=null (from gateways like Bifrost) is handled gracefully."""
|
|
from openai.types import responses as resp
|
|
|
|
base_response = resp.Response(
|
|
id='resp_001',
|
|
model='gpt-4o',
|
|
object='response',
|
|
created_at=1704067200,
|
|
output=[],
|
|
parallel_tool_calls=True,
|
|
tool_choice='auto',
|
|
tools=[],
|
|
)
|
|
|
|
stream: list[resp.ResponseStreamEvent] = [
|
|
resp.ResponseCreatedEvent(response=base_response, type='response.created', sequence_number=0),
|
|
resp.ResponseInProgressEvent(response=base_response, type='response.in_progress', sequence_number=1),
|
|
resp.ResponseOutputItemAddedEvent(
|
|
item=ResponseOutputMessage(
|
|
id='msg_001',
|
|
content=[],
|
|
role='assistant',
|
|
status='in_progress',
|
|
type='message',
|
|
),
|
|
output_index=0,
|
|
type='response.output_item.added',
|
|
sequence_number=2,
|
|
),
|
|
resp.ResponseContentPartAddedEvent(
|
|
content_index=0,
|
|
item_id='msg_001',
|
|
output_index=0,
|
|
part=resp.ResponseOutputText(text='', type='output_text', annotations=[]),
|
|
type='response.content_part.added',
|
|
sequence_number=3,
|
|
),
|
|
resp.ResponseTextDeltaEvent.model_construct(
|
|
content_index=0,
|
|
delta=None,
|
|
item_id='msg_001',
|
|
output_index=0,
|
|
type='response.output_text.delta',
|
|
sequence_number=4,
|
|
logprobs=[],
|
|
),
|
|
resp.ResponseTextDeltaEvent(
|
|
content_index=0,
|
|
delta='Hello!',
|
|
item_id='msg_001',
|
|
output_index=0,
|
|
type='response.output_text.delta',
|
|
sequence_number=5,
|
|
logprobs=[],
|
|
),
|
|
resp.ResponseTextDoneEvent(
|
|
content_index=0,
|
|
item_id='msg_001',
|
|
output_index=0,
|
|
text='Hello!',
|
|
type='response.output_text.done',
|
|
sequence_number=6,
|
|
logprobs=[],
|
|
),
|
|
resp.ResponseOutputItemDoneEvent(
|
|
item=ResponseOutputMessage(
|
|
id='msg_001',
|
|
content=cast(list[Content], [ResponseOutputText(text='Hello!', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
),
|
|
output_index=0,
|
|
type='response.output_item.done',
|
|
sequence_number=7,
|
|
),
|
|
resp.ResponseCompletedEvent(
|
|
response=base_response.model_copy(update={'status': 'completed'}),
|
|
type='response.completed',
|
|
sequence_number=8,
|
|
),
|
|
]
|
|
|
|
mock_client = MockOpenAIResponses.create_mock_stream(stream)
|
|
model = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(openai_client=mock_client))
|
|
agent = Agent(model=model)
|
|
|
|
async with agent.run_stream('Hello') as result:
|
|
output = await result.get_output()
|
|
assert output == snapshot('Hello!')
|
|
assert result.all_messages() == snapshot(
|
|
[
|
|
ModelRequest(
|
|
parts=[
|
|
UserPromptPart(
|
|
content='Hello',
|
|
timestamp=IsDatetime(),
|
|
)
|
|
],
|
|
timestamp=IsNow(tz=timezone.utc),
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
ModelResponse(
|
|
parts=[TextPart(content='Hello!', id='msg_001', provider_name='openai')],
|
|
usage=RequestUsage(),
|
|
model_name='gpt-4o',
|
|
timestamp=IsDatetime(),
|
|
provider_name='openai',
|
|
provider_url='https://api.openai.com/v1',
|
|
provider_details={
|
|
'timestamp': datetime(2024, 1, 1, 0, 0, tzinfo=timezone.utc),
|
|
'finish_reason': 'completed',
|
|
},
|
|
provider_response_id='resp_001',
|
|
finish_reason='stop',
|
|
run_id=IsStr(),
|
|
conversation_id=IsStr(),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_text_content_input(allow_model_requests: None, openai_api_key: str):
|
|
"""Test that text content in ModelRequest is correctly mapped to OpenAI messages."""
|
|
model = OpenAIResponsesModel('gpt-5.2', provider=OpenAIProvider(api_key=openai_api_key))
|
|
m = await model._map_user_prompt( # pyright: ignore[reportPrivateUsage]
|
|
part=UserPromptPart(content=['test', TextContent(content='test2', metadata={'key': 'value'})])
|
|
)
|
|
assert m == snapshot(
|
|
{'role': 'user', 'content': [{'text': 'test', 'type': 'input_text'}, {'text': 'test2', 'type': 'input_text'}]}
|
|
)
|
|
|
|
|
|
async def test_openai_responses_compact_messages(allow_model_requests: None, openai_api_key: str):
|
|
"""Test OpenAI compaction: multi-turn conversation compacted via OpenAICompaction capability."""
|
|
from pydantic_ai.models.openai import OpenAICompaction
|
|
|
|
model = OpenAIResponsesModel('gpt-4o-mini', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(
|
|
model=model,
|
|
instructions='You are a helpful math assistant. Give short answers.',
|
|
capabilities=[OpenAICompaction(message_count_threshold=4)],
|
|
)
|
|
|
|
message_history: list[Any] = []
|
|
result = await agent.run('What is 2+2?', message_history=message_history)
|
|
message_history = result.all_messages()
|
|
result = await agent.run('And 3+3?', message_history=message_history)
|
|
message_history = result.all_messages()
|
|
# Third run should trigger compaction (>4 messages)
|
|
result = await agent.run('And 5+5?', message_history=message_history)
|
|
|
|
# Verify the result is reasonable
|
|
assert '10' in result.output
|
|
|
|
# Verify compaction happened — there should be a CompactionPart in the messages
|
|
all_msgs = result.all_messages()
|
|
compaction_parts = [
|
|
part
|
|
for msg in all_msgs
|
|
if isinstance(msg, ModelResponse)
|
|
for part in msg.parts
|
|
if isinstance(part, CompactionPart)
|
|
]
|
|
assert len(compaction_parts) >= 1
|
|
compaction = compaction_parts[0]
|
|
assert compaction.provider_name == 'openai'
|
|
assert compaction.provider_details is not None
|
|
assert 'encrypted_content' in compaction.provider_details
|
|
|
|
|
|
async def test_openai_responses_compact_stateful_mode_stream(allow_model_requests: None, openai_api_key: str):
|
|
"""Streaming variant: `ResponseCompactionItem` is handled in `_get_event_iterator`.
|
|
|
|
Validates that a `PartStartEvent` is emitted for the `CompactionPart` during streaming
|
|
(on both the "added" and "done" events) so UIs can render compaction progress.
|
|
"""
|
|
from pydantic_ai import AgentRunResultEvent
|
|
from pydantic_ai.models.openai import OpenAICompaction
|
|
|
|
model = OpenAIResponsesModel('gpt-4.1', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(
|
|
model=model,
|
|
capabilities=[OpenAICompaction(token_threshold=1000)],
|
|
)
|
|
|
|
message_history: list[Any] = []
|
|
all_events: list[Any] = []
|
|
last_output = ''
|
|
for question in [
|
|
'Tell me a 300-word story about a fox exploring a forest. Be very descriptive.',
|
|
'Now a 300-word story about a rabbit in a meadow. Be very descriptive.',
|
|
'Now a 300-word story about a bear in a cave. Be very descriptive.',
|
|
'What is 2+2?',
|
|
]:
|
|
async with agent.run_stream_events(question, message_history=message_history) as event_stream:
|
|
events = [event async for event in event_stream]
|
|
all_events.extend(events)
|
|
final = next(e for e in reversed(events) if isinstance(e, AgentRunResultEvent))
|
|
last_output = final.result.output
|
|
message_history = final.result.all_messages()
|
|
|
|
assert '4' in last_output
|
|
|
|
# Verify PartStartEvent was emitted for CompactionPart during streaming
|
|
compaction_start_events = [
|
|
e for e in all_events if isinstance(e, PartStartEvent) and isinstance(e.part, CompactionPart)
|
|
]
|
|
assert compaction_start_events, 'expected PartStartEvent for CompactionPart during streaming'
|
|
assert compaction_start_events[0].part.provider_name == 'openai'
|
|
|
|
# Verify final messages contain the CompactionPart with encrypted_content
|
|
compaction_parts = [
|
|
part
|
|
for msg in message_history
|
|
if isinstance(msg, ModelResponse)
|
|
for part in msg.parts
|
|
if isinstance(part, CompactionPart)
|
|
]
|
|
assert compaction_parts, 'expected at least one server-side compaction in streaming mode'
|
|
assert compaction_parts[0].provider_name == 'openai'
|
|
assert 'encrypted_content' in (compaction_parts[0].provider_details or {})
|
|
|
|
|
|
async def test_openai_responses_compact_messages_direct(allow_model_requests: None, openai_api_key: str):
|
|
"""Test OpenAI compact_messages method directly with ModelRequestContext."""
|
|
from pydantic_ai.models import ModelRequestContext
|
|
|
|
model = OpenAIResponsesModel('gpt-4o-mini', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(
|
|
model=model,
|
|
instructions='You are a helpful assistant.',
|
|
)
|
|
|
|
# Build up some history
|
|
message_history: list[Any] = []
|
|
result = await agent.run('Hello!', message_history=message_history)
|
|
message_history = result.all_messages()
|
|
result = await agent.run('How are you?', message_history=message_history)
|
|
messages = result.all_messages()
|
|
|
|
# Call compact_messages directly
|
|
request_context = ModelRequestContext(
|
|
model=model,
|
|
messages=messages,
|
|
model_settings=None,
|
|
model_request_parameters=ModelRequestParameters(),
|
|
)
|
|
compacted = await model.compact_messages(request_context)
|
|
|
|
assert isinstance(compacted, ModelResponse)
|
|
assert len(compacted.parts) == 1
|
|
assert isinstance(compacted.parts[0], CompactionPart)
|
|
assert compacted.parts[0].provider_name == 'openai'
|
|
assert compacted.parts[0].provider_details is not None
|
|
assert 'encrypted_content' in compacted.parts[0].provider_details
|
|
assert compacted.usage.input_tokens > 0
|
|
|
|
|
|
async def test_openai_responses_compact_with_auto_previous_response_id(allow_model_requests: None, openai_api_key: str):
|
|
"""Test compact_messages with openai_previous_response_id='auto'."""
|
|
from pydantic_ai.models import ModelRequestContext
|
|
|
|
model = OpenAIResponsesModel('gpt-4o-mini', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model=model, instructions='You are a helpful assistant.')
|
|
|
|
# Build up history with provider_response_id
|
|
message_history: list[Any] = []
|
|
result = await agent.run('Hello!', message_history=message_history)
|
|
messages = result.all_messages()
|
|
|
|
# Compact with auto previous_response_id
|
|
request_context = ModelRequestContext(
|
|
model=model,
|
|
messages=messages,
|
|
model_settings=OpenAIResponsesModelSettings(openai_previous_response_id='auto'),
|
|
model_request_parameters=ModelRequestParameters(),
|
|
)
|
|
compacted = await model.compact_messages(request_context)
|
|
|
|
assert isinstance(compacted, ModelResponse)
|
|
assert len(compacted.parts) == 1
|
|
assert isinstance(compacted.parts[0], CompactionPart)
|
|
|
|
|
|
async def test_openai_responses_compact_with_instructions(allow_model_requests: None, openai_api_key: str):
|
|
"""Test compact_messages with custom instructions override."""
|
|
from pydantic_ai.models import ModelRequestContext
|
|
|
|
model = OpenAIResponsesModel('gpt-4o-mini', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(model=model, instructions='You are a helpful assistant.')
|
|
|
|
# Build up history
|
|
message_history: list[Any] = []
|
|
result = await agent.run('Hello!', message_history=message_history)
|
|
messages = result.all_messages()
|
|
|
|
# Compact with custom instructions
|
|
request_context = ModelRequestContext(
|
|
model=model,
|
|
messages=messages,
|
|
model_settings=None,
|
|
model_request_parameters=ModelRequestParameters(),
|
|
)
|
|
compacted = await model.compact_messages(request_context, instructions='Summarize very briefly')
|
|
|
|
assert isinstance(compacted, ModelResponse)
|
|
assert len(compacted.parts) == 1
|
|
assert isinstance(compacted.parts[0], CompactionPart)
|
|
|
|
|
|
async def test_openai_responses_compact_with_auto_previous_response_id_chain(
|
|
allow_model_requests: None, openai_api_key: str
|
|
):
|
|
"""Agent with OpenAICompaction + openai_previous_response_id='auto' must continue working after compaction.
|
|
|
|
Regression test: the `/compact` endpoint is stateless, so its response id cannot be
|
|
used as `previous_response_id` on a subsequent `responses.create` call. After
|
|
compaction, the next request must pass the compaction item via the input array
|
|
instead of chaining the compaction response id.
|
|
"""
|
|
from pydantic_ai.models.openai import OpenAICompaction
|
|
|
|
model = OpenAIResponsesModel('gpt-4.1', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(
|
|
model=model,
|
|
capabilities=[OpenAICompaction(message_count_threshold=3)],
|
|
)
|
|
|
|
message_history: list[Any] = []
|
|
for question in ['What is 2+2?', 'And 3+3?', 'And 4+4?', '91 - 16?', "What's your favorite number?"]:
|
|
result = await agent.run(
|
|
question,
|
|
message_history=message_history,
|
|
model_settings=OpenAIResponsesModelSettings(openai_previous_response_id='auto'),
|
|
)
|
|
message_history = result.all_messages()
|
|
|
|
compaction_parts = [
|
|
part
|
|
for msg in message_history
|
|
if isinstance(msg, ModelResponse)
|
|
for part in msg.parts
|
|
if isinstance(part, CompactionPart)
|
|
]
|
|
assert compaction_parts, 'expected at least one compaction during the run'
|
|
# The compaction ModelResponse is marked via `provider_details={'compaction': True}`
|
|
# so `_get_previous_response_id_and_new_messages` breaks the auto-chain at it; its
|
|
# `provider_response_id` is still populated for observability.
|
|
for msg in message_history:
|
|
if isinstance(msg, ModelResponse) and any(isinstance(p, CompactionPart) for p in msg.parts):
|
|
assert msg.provider_details == {'compaction': True}
|
|
assert msg.provider_response_id is not None
|
|
|
|
|
|
async def test_openai_responses_compact_stateful_mode(allow_model_requests: None, openai_api_key: str):
|
|
"""Stateful `OpenAICompaction` rides along on the regular /responses call.
|
|
|
|
Sets `context_management: [{'type': 'compaction', ...}]` on the request. When
|
|
OpenAI triggers compaction server-side, the returned `ResponseCompactionItem`
|
|
is mapped to a `CompactionPart` on the `ModelResponse` so it can be
|
|
round-tripped via the `input` array on subsequent requests (or chained via
|
|
`previous_response_id`).
|
|
"""
|
|
from pydantic_ai.models.openai import OpenAICompaction
|
|
|
|
model = OpenAIResponsesModel('gpt-4.1', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(
|
|
model=model,
|
|
# 1000 is the lowest threshold OpenAI accepts; a handful of ~300-word turns crosses it.
|
|
capabilities=[OpenAICompaction(token_threshold=1000)],
|
|
)
|
|
|
|
message_history: list[Any] = []
|
|
last_output = ''
|
|
for question in [
|
|
'Tell me a 300-word story about a fox exploring a forest. Be very descriptive.',
|
|
'Now a 300-word story about a rabbit in a meadow. Be very descriptive.',
|
|
'Now a 300-word story about a bear in a cave. Be very descriptive.',
|
|
'What is 2+2?',
|
|
]:
|
|
result = await agent.run(question, message_history=message_history)
|
|
message_history = result.all_messages()
|
|
last_output = result.output
|
|
|
|
assert '4' in last_output
|
|
|
|
# Trace: which message kinds, which part kinds per response, and where the
|
|
# server-side compaction was emitted. Snapshotting the full all_messages()
|
|
# would be hundreds of lines of story prose; this condensed trace catches
|
|
# the regressions that matter (compaction emitted, parts round-tripped into
|
|
# subsequent requests, no orphan parts).
|
|
trace = [
|
|
(
|
|
type(msg).__name__,
|
|
[type(p).__name__ for p in msg.parts],
|
|
)
|
|
for msg in message_history
|
|
]
|
|
assert trace == snapshot(
|
|
[
|
|
('ModelRequest', ['UserPromptPart']),
|
|
('ModelResponse', ['TextPart']),
|
|
('ModelRequest', ['UserPromptPart']),
|
|
('ModelResponse', ['TextPart']),
|
|
('ModelRequest', ['UserPromptPart']),
|
|
('ModelResponse', ['TextPart', 'CompactionPart']),
|
|
('ModelRequest', ['UserPromptPart']),
|
|
('ModelResponse', ['TextPart']),
|
|
]
|
|
)
|
|
|
|
compaction_parts = [
|
|
part
|
|
for msg in message_history
|
|
if isinstance(msg, ModelResponse)
|
|
for part in msg.parts
|
|
if isinstance(part, CompactionPart)
|
|
]
|
|
compaction = compaction_parts[0]
|
|
assert compaction.provider_name == 'openai'
|
|
assert compaction.provider_details is not None
|
|
assert 'encrypted_content' in compaction.provider_details
|
|
|
|
|
|
async def test_openai_responses_phase_non_streamed(allow_model_requests: None):
|
|
"""`phase` on ResponseOutputMessage is captured into TextPart.provider_details."""
|
|
c = response_message(
|
|
[
|
|
ResponseOutputMessage.model_construct(
|
|
id='msg_commentary',
|
|
content=cast(
|
|
list[Content],
|
|
[ResponseOutputText(text='Looking it up...', type='output_text', annotations=[])],
|
|
),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
phase='commentary',
|
|
),
|
|
ResponseOutputMessage.model_construct(
|
|
id='msg_final',
|
|
content=cast(
|
|
list[Content],
|
|
[ResponseOutputText(text='Paris.', type='output_text', annotations=[])],
|
|
),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
phase='final_answer',
|
|
),
|
|
]
|
|
)
|
|
mock_client = MockOpenAIResponses.create_mock(c)
|
|
model = OpenAIResponsesModel('gpt-5.5', provider=OpenAIProvider(openai_client=mock_client))
|
|
agent = Agent(model=model)
|
|
|
|
result = await agent.run('What is the capital of France?')
|
|
response = message(result.all_messages(), ModelResponse, index=-1)
|
|
text_parts = [p for p in response.parts if isinstance(p, TextPart)]
|
|
assert [(p.id, p.content, (p.provider_details or {}).get('phase')) for p in text_parts] == snapshot(
|
|
[
|
|
('msg_commentary', 'Looking it up...', 'commentary'),
|
|
('msg_final', 'Paris.', 'final_answer'),
|
|
]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_phase_streamed(allow_model_requests: None):
|
|
"""`phase` on streamed ResponseOutputMessage is captured into TextPart.provider_details."""
|
|
base_response = resp.Response(
|
|
id='resp_001',
|
|
model='gpt-5.5',
|
|
object='response',
|
|
created_at=1704067200,
|
|
output=[],
|
|
parallel_tool_calls=True,
|
|
tool_choice='auto',
|
|
tools=[],
|
|
)
|
|
|
|
stream: list[resp.ResponseStreamEvent] = [
|
|
resp.ResponseCreatedEvent(response=base_response, type='response.created', sequence_number=0),
|
|
resp.ResponseInProgressEvent(response=base_response, type='response.in_progress', sequence_number=1),
|
|
resp.ResponseOutputItemAddedEvent(
|
|
item=ResponseOutputMessage.model_construct(
|
|
id='msg_001',
|
|
content=[],
|
|
role='assistant',
|
|
status='in_progress',
|
|
type='message',
|
|
phase='final_answer',
|
|
),
|
|
output_index=0,
|
|
type='response.output_item.added',
|
|
sequence_number=2,
|
|
),
|
|
resp.ResponseContentPartAddedEvent(
|
|
content_index=0,
|
|
item_id='msg_001',
|
|
output_index=0,
|
|
part=resp.ResponseOutputText(text='', type='output_text', annotations=[]),
|
|
type='response.content_part.added',
|
|
sequence_number=3,
|
|
),
|
|
resp.ResponseTextDeltaEvent(
|
|
content_index=0,
|
|
delta='Paris.',
|
|
item_id='msg_001',
|
|
output_index=0,
|
|
type='response.output_text.delta',
|
|
sequence_number=4,
|
|
logprobs=[],
|
|
),
|
|
resp.ResponseTextDoneEvent(
|
|
content_index=0,
|
|
item_id='msg_001',
|
|
output_index=0,
|
|
text='Paris.',
|
|
type='response.output_text.done',
|
|
sequence_number=5,
|
|
logprobs=[],
|
|
),
|
|
resp.ResponseOutputItemDoneEvent(
|
|
item=ResponseOutputMessage.model_construct(
|
|
id='msg_001',
|
|
content=cast(list[Content], [ResponseOutputText(text='Paris.', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
phase='final_answer',
|
|
),
|
|
output_index=0,
|
|
type='response.output_item.done',
|
|
sequence_number=6,
|
|
),
|
|
resp.ResponseCompletedEvent(
|
|
response=base_response.model_copy(update={'status': 'completed'}),
|
|
type='response.completed',
|
|
sequence_number=7,
|
|
),
|
|
]
|
|
|
|
mock_client = MockOpenAIResponses.create_mock_stream(stream)
|
|
model = OpenAIResponsesModel('gpt-5.5', provider=OpenAIProvider(openai_client=mock_client))
|
|
agent = Agent(model=model)
|
|
|
|
async with agent.run_stream('What is the capital of France?') as result:
|
|
await result.get_output()
|
|
|
|
response = message(result.all_messages(), ModelResponse, index=-1)
|
|
text_parts = [p for p in response.parts if isinstance(p, TextPart)]
|
|
assert len(text_parts) == 1
|
|
assert text_parts[0].provider_details == snapshot({'phase': 'final_answer'})
|
|
|
|
|
|
async def test_openai_responses_phase_round_trip(allow_model_requests: None):
|
|
"""When the profile supports phase, it's sent back on the assistant ResponseOutputMessageParam."""
|
|
mock_client = MockOpenAIResponses.create_mock(
|
|
response_message(
|
|
[
|
|
ResponseOutputMessage.model_construct(
|
|
id='msg_999',
|
|
content=cast(
|
|
list[Content],
|
|
[ResponseOutputText(text='ok', type='output_text', annotations=[])],
|
|
),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
phase='final_answer',
|
|
),
|
|
]
|
|
)
|
|
)
|
|
model = OpenAIResponsesModel(
|
|
'gpt-5.5',
|
|
provider=OpenAIProvider(openai_client=mock_client),
|
|
settings=OpenAIResponsesModelSettings(openai_send_reasoning_ids=True),
|
|
)
|
|
agent = Agent(model=model)
|
|
|
|
history: list[ModelRequest | ModelResponse] = [
|
|
ModelRequest(parts=[UserPromptPart(content='Hi')]),
|
|
ModelResponse(
|
|
parts=[
|
|
TextPart(
|
|
content='Working on it...',
|
|
id='msg_a',
|
|
provider_name='openai',
|
|
provider_details={'phase': 'commentary'},
|
|
),
|
|
TextPart(
|
|
content='All done.',
|
|
id='msg_b',
|
|
provider_name='openai',
|
|
provider_details={'phase': 'final_answer'},
|
|
),
|
|
],
|
|
model_name='gpt-5.5',
|
|
provider_name='openai',
|
|
),
|
|
]
|
|
|
|
await agent.run('And again?', message_history=history)
|
|
|
|
sent_input = cast(list[dict[str, Any]], get_mock_responses_kwargs(mock_client)[0]['input'])
|
|
assistant_messages = [m for m in sent_input if isinstance(m, dict) and m.get('role') == 'assistant']
|
|
assert [(m['id'], m.get('phase')) for m in assistant_messages] == snapshot(
|
|
[('msg_a', 'commentary'), ('msg_b', 'final_answer')]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_phase_skipped_when_profile_unsupported(allow_model_requests: None):
|
|
"""When the profile does NOT support phase, it must not leak into the request payload."""
|
|
mock_client = MockOpenAIResponses.create_mock(
|
|
response_message(
|
|
[
|
|
ResponseOutputMessage.model_construct(
|
|
id='msg_999',
|
|
content=cast(
|
|
list[Content],
|
|
[ResponseOutputText(text='ok', type='output_text', annotations=[])],
|
|
),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
),
|
|
]
|
|
)
|
|
)
|
|
# gpt-5.2 doesn't support phase; verify we don't send the field even if a previous turn carried it.
|
|
model = OpenAIResponsesModel(
|
|
'gpt-5.2',
|
|
provider=OpenAIProvider(openai_client=mock_client),
|
|
settings=OpenAIResponsesModelSettings(openai_send_reasoning_ids=True),
|
|
)
|
|
agent = Agent(model=model)
|
|
|
|
history: list[ModelRequest | ModelResponse] = [
|
|
ModelRequest(parts=[UserPromptPart(content='Hi')]),
|
|
ModelResponse(
|
|
parts=[
|
|
TextPart(
|
|
content='Done.',
|
|
id='msg_a',
|
|
provider_name='openai',
|
|
provider_details={'phase': 'final_answer'},
|
|
),
|
|
],
|
|
model_name='gpt-5.2',
|
|
provider_name='openai',
|
|
),
|
|
]
|
|
|
|
await agent.run('And again?', message_history=history)
|
|
|
|
sent_input = cast(list[dict[str, Any]], get_mock_responses_kwargs(mock_client)[0]['input'])
|
|
assistant_messages = [m for m in sent_input if isinstance(m, dict) and m.get('role') == 'assistant']
|
|
assert assistant_messages
|
|
assert all('phase' not in m for m in assistant_messages)
|
|
|
|
|
|
async def test_openai_responses_phase_round_trip_without_item_id(allow_model_requests: None):
|
|
"""Phase travels via `EasyInputMessageParam` when item ids aren't being sent."""
|
|
mock_client = MockOpenAIResponses.create_mock(
|
|
response_message(
|
|
[
|
|
ResponseOutputMessage.model_construct(
|
|
id='msg_999',
|
|
content=cast(list[Content], [ResponseOutputText(text='ok', type='output_text', annotations=[])]),
|
|
role='assistant',
|
|
status='completed',
|
|
type='message',
|
|
phase='final_answer',
|
|
),
|
|
]
|
|
)
|
|
)
|
|
model = OpenAIResponsesModel(
|
|
'gpt-5.5',
|
|
provider=OpenAIProvider(openai_client=mock_client),
|
|
settings=OpenAIResponsesModelSettings(openai_send_reasoning_ids=False),
|
|
)
|
|
agent = Agent(model=model)
|
|
|
|
history: list[ModelRequest | ModelResponse] = [
|
|
ModelRequest(parts=[UserPromptPart(content='Hi')]),
|
|
ModelResponse(
|
|
parts=[
|
|
TextPart(
|
|
content='Working on it...',
|
|
id='msg_a',
|
|
provider_name='openai',
|
|
provider_details={'phase': 'commentary'},
|
|
),
|
|
],
|
|
model_name='gpt-5.5',
|
|
provider_name='openai',
|
|
),
|
|
]
|
|
|
|
await agent.run('And again?', message_history=history)
|
|
|
|
sent_input = cast(list[dict[str, Any]], get_mock_responses_kwargs(mock_client)[0]['input'])
|
|
assistant_messages = [m for m in sent_input if isinstance(m, dict) and m.get('role') == 'assistant']
|
|
# No item id was sent, so we used EasyInputMessageParam — but phase still rides along.
|
|
assert [(m.get('id'), m.get('phase'), m.get('type')) for m in assistant_messages] == snapshot(
|
|
[(None, 'commentary', None)]
|
|
)
|
|
|
|
|
|
async def test_openai_responses_phase_live(allow_model_requests: None, openai_api_key: str):
|
|
"""Real cassette: gpt-5.5 sets `phase` on the preamble (commentary) and the final answer."""
|
|
model = OpenAIResponsesModel('gpt-5.5', provider=OpenAIProvider(api_key=openai_api_key))
|
|
agent = Agent(
|
|
model=model,
|
|
instructions='Briefly narrate what you are about to do before calling each tool.',
|
|
)
|
|
|
|
@agent.tool_plain
|
|
async def get_capital(country: str) -> str:
|
|
return 'Potato City'
|
|
|
|
result = await agent.run('What is the capital of PotatoLand?')
|
|
text_parts_with_phase = [
|
|
(part.content, (part.provider_details or {}).get('phase'))
|
|
for msg in result.all_messages()
|
|
if isinstance(msg, ModelResponse)
|
|
for part in msg.parts
|
|
if isinstance(part, TextPart)
|
|
]
|
|
assert text_parts_with_phase == snapshot(
|
|
[
|
|
('I\'ll check the capital lookup tool for "PotatoLand."', 'commentary'),
|
|
('The capital of PotatoLand is **Potato City**.', 'final_answer'),
|
|
]
|
|
)
|
|
|
|
|
|
def test_openai_responses_phase_profile_flag():
|
|
"""Profile flag tracks the documented set of supporting models."""
|
|
assert openai_model_profile('gpt-5.6-sol').get('openai_supports_phase', False) is True
|
|
assert openai_model_profile('gpt-5.5').get('openai_supports_phase', False) is True
|
|
assert openai_model_profile('gpt-5.4').get('openai_supports_phase', False) is True
|
|
assert openai_model_profile('gpt-5.3-codex').get('openai_supports_phase', False) is True
|
|
assert openai_model_profile('gpt-5.3').get('openai_supports_phase', False) is False
|
|
assert openai_model_profile('gpt-5.2').get('openai_supports_phase', False) is False
|
|
assert openai_model_profile('gpt-5').get('openai_supports_phase', False) is False
|
|
assert openai_model_profile('gpt-4o').get('openai_supports_phase', False) is False
|