Skip to content

Commit 546fd2c

Browse files
authored
Add OpenAI built-in tools (#1327)
1 parent 6d7c53a commit 546fd2c

File tree

3 files changed

+189
-8
lines changed

3 files changed

+189
-8
lines changed

pydantic_ai_slim/pydantic_ai/models/openai.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import base64
44
import warnings
5-
from collections.abc import AsyncIterable, AsyncIterator
5+
from collections.abc import AsyncIterable, AsyncIterator, Sequence
66
from contextlib import asynccontextmanager
77
from dataclasses import dataclass, field
88
from datetime import datetime, timezone
@@ -55,6 +55,7 @@
5555
)
5656
from openai.types.chat.chat_completion_content_part_image_param import ImageURL
5757
from openai.types.chat.chat_completion_content_part_input_audio_param import InputAudio
58+
from openai.types.responses import ComputerToolParam, FileSearchToolParam, WebSearchToolParam
5859
from openai.types.responses.response_input_param import FunctionCallOutput, Message
5960
from openai.types.shared import ReasoningEffort
6061
from openai.types.shared_params import Reasoning
@@ -64,6 +65,14 @@
6465
'you can use the `openai` optional group — `pip install "pydantic-ai-slim[openai]"`'
6566
) from _import_error
6667

68+
__all__ = (
69+
'OpenAIModel',
70+
'OpenAIResponsesModel',
71+
'OpenAIModelSettings',
72+
'OpenAIResponsesModelSettings',
73+
'OpenAIModelName',
74+
)
75+
6776
OpenAIModelName = Union[str, ChatModel]
6877
"""
6978
Possible OpenAI model names.
@@ -100,6 +109,19 @@ class OpenAIModelSettings(ModelSettings, total=False):
100109
"""
101110

102111

112+
class OpenAIResponsesModelSettings(OpenAIModelSettings, total=False):
113+
"""Settings used for an OpenAI Responses model request.
114+
115+
ALL FIELDS MUST BE `openai_` PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS.
116+
"""
117+
118+
openai_builtin_tools: Sequence[FileSearchToolParam | WebSearchToolParam | ComputerToolParam]
119+
"""The provided OpenAI built-in tools to use.
120+
121+
See [OpenAI's built-in tools](https://platform.openai.com/docs/guides/tools?api-mode=responses) for more details.
122+
"""
123+
124+
103125
@dataclass(init=False)
104126
class OpenAIModel(Model):
105127
"""A model that uses the OpenAI API.
@@ -417,6 +439,8 @@ class OpenAIResponsesModel(Model):
417439
- [File search](https://platform.openai.com/docs/guides/tools-file-search)
418440
- [Computer use](https://platform.openai.com/docs/guides/tools-computer-use)
419441
442+
Use the `openai_builtin_tools` setting to add these tools to your model.
443+
420444
If you are interested in the differences between the Responses API and the Chat Completions API,
421445
see the [OpenAI API docs](https://platform.openai.com/docs/guides/responses-vs-chat-completions).
422446
"""
@@ -462,7 +486,7 @@ async def request(
462486
) -> tuple[ModelResponse, usage.Usage]:
463487
check_allow_model_requests()
464488
response = await self._responses_create(
465-
messages, False, cast(OpenAIModelSettings, model_settings or {}), model_request_parameters
489+
messages, False, cast(OpenAIResponsesModelSettings, model_settings or {}), model_request_parameters
466490
)
467491
return self._process_response(response), _map_usage(response)
468492

@@ -475,7 +499,7 @@ async def request_stream(
475499
) -> AsyncIterator[StreamedResponse]:
476500
check_allow_model_requests()
477501
response = await self._responses_create(
478-
messages, True, cast(OpenAIModelSettings, model_settings or {}), model_request_parameters
502+
messages, True, cast(OpenAIResponsesModelSettings, model_settings or {}), model_request_parameters
479503
)
480504
async with response:
481505
yield await self._process_streamed_response(response)
@@ -511,7 +535,7 @@ async def _responses_create(
511535
self,
512536
messages: list[ModelRequest | ModelResponse],
513537
stream: Literal[False],
514-
model_settings: OpenAIModelSettings,
538+
model_settings: OpenAIResponsesModelSettings,
515539
model_request_parameters: ModelRequestParameters,
516540
) -> responses.Response: ...
517541

@@ -520,18 +544,19 @@ async def _responses_create(
520544
self,
521545
messages: list[ModelRequest | ModelResponse],
522546
stream: Literal[True],
523-
model_settings: OpenAIModelSettings,
547+
model_settings: OpenAIResponsesModelSettings,
524548
model_request_parameters: ModelRequestParameters,
525549
) -> AsyncStream[responses.ResponseStreamEvent]: ...
526550

527551
async def _responses_create(
528552
self,
529553
messages: list[ModelRequest | ModelResponse],
530554
stream: bool,
531-
model_settings: OpenAIModelSettings,
555+
model_settings: OpenAIResponsesModelSettings,
532556
model_request_parameters: ModelRequestParameters,
533557
) -> responses.Response | AsyncStream[responses.ResponseStreamEvent]:
534558
tools = self._get_tools(model_request_parameters)
559+
tools = list(model_settings.get('openai_builtin_tools', [])) + tools
535560

536561
# standalone function to make it easier to override
537562
if not tools:
@@ -848,7 +873,7 @@ def _map_usage(response: chat.ChatCompletion | ChatCompletionChunk | responses.R
848873
},
849874
)
850875
else:
851-
details: dict[str, int] = {}
876+
details = {}
852877
if response_usage.completion_tokens_details is not None:
853878
details.update(response_usage.completion_tokens_details.model_dump(exclude_none=True))
854879
if response_usage.prompt_tokens_details is not None:
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
interactions:
2+
- request:
3+
headers:
4+
accept:
5+
- application/json
6+
accept-encoding:
7+
- gzip, deflate
8+
connection:
9+
- keep-alive
10+
content-length:
11+
- '217'
12+
content-type:
13+
- application/json
14+
host:
15+
- api.openai.com
16+
method: POST
17+
parsed_body:
18+
input:
19+
- content: Give me the best news about LLMs from the last 24 hours. Be short.
20+
role: user
21+
instructions: ''
22+
model: gpt-4o
23+
stream: false
24+
tool_choice: auto
25+
tools:
26+
- type: web_search_preview
27+
uri: https://api.openai.com/v1/responses
28+
response:
29+
headers:
30+
alt-svc:
31+
- h3=":443"; ma=86400
32+
connection:
33+
- keep-alive
34+
content-length:
35+
- '3210'
36+
content-type:
37+
- application/json
38+
openai-organization:
39+
- pydantic-28gund
40+
openai-processing-ms:
41+
- '2843'
42+
openai-version:
43+
- '2020-10-01'
44+
strict-transport-security:
45+
- max-age=31536000; includeSubDomains; preload
46+
transfer-encoding:
47+
- chunked
48+
parsed_body:
49+
created_at: 1743506361
50+
error: null
51+
id: resp_67ebcbb93728819197f923ff16e98bce04f5055a2a33abc3
52+
incomplete_details: null
53+
instructions: ''
54+
max_output_tokens: null
55+
metadata: {}
56+
model: gpt-4o-2024-08-06
57+
object: response
58+
output:
59+
- id: ws_67ebcbb9ab4481918bebf63b66dbb67c04f5055a2a33abc3
60+
status: completed
61+
type: web_search_call
62+
- content:
63+
- annotations:
64+
- end_index: 571
65+
start_index: 404
66+
title: OpenAI plans to release open-weight language model in coming months
67+
type: url_citation
68+
url: https://www.reuters.com/technology/artificial-intelligence/openai-plans-release-open-weight-language-model-coming-months-2025-03-31/?utm_source=openai
69+
- end_index: 846
70+
start_index: 625
71+
title: OpenAI plans to release open-weight language model in coming months
72+
type: url_citation
73+
url: https://www.reuters.com/technology/artificial-intelligence/openai-plans-release-open-weight-language-model-coming-months-2025-03-31/?utm_source=openai
74+
text: "In the past 24 hours, OpenAI announced plans to release its first open-weight language model with reasoning
75+
capabilities since GPT-2. This model will allow developers to fine-tune it for specific applications without needing
76+
the original training data. To gather feedback and refine the model, OpenAI will host developer events starting
77+
in San Francisco and expanding to Europe and Asia-Pacific regions. ([reuters.com](https://www.reuters.com/technology/artificial-intelligence/openai-plans-release-open-weight-language-model-coming-months-2025-03-31/?utm_source=openai))\n\n\n##
78+
OpenAI to Release Open-Weight Language Model:\n- [OpenAI plans to release open-weight language model in coming
79+
months](https://www.reuters.com/technology/artificial-intelligence/openai-plans-release-open-weight-language-model-coming-months-2025-03-31/?utm_source=openai) "
80+
type: output_text
81+
id: msg_67ebcbbaf988819192d44919020b82e704f5055a2a33abc3
82+
role: assistant
83+
status: completed
84+
type: message
85+
parallel_tool_calls: true
86+
previous_response_id: null
87+
reasoning:
88+
effort: null
89+
generate_summary: null
90+
status: completed
91+
store: true
92+
temperature: 1.0
93+
text:
94+
format:
95+
type: text
96+
tool_choice: auto
97+
tools:
98+
- search_context_size: medium
99+
type: web_search_preview
100+
user_location:
101+
city: null
102+
country: US
103+
region: null
104+
timezone: null
105+
type: approximate
106+
top_p: 1.0
107+
truncation: disabled
108+
usage:
109+
input_tokens: 320
110+
input_tokens_details:
111+
cached_tokens: 0
112+
output_tokens: 200
113+
output_tokens_details:
114+
reasoning_tokens: 0
115+
total_tokens: 520
116+
user: null
117+
status:
118+
code: 200
119+
message: OK
120+
version: 1

tests/models/test_openai_responses.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from ..conftest import IsDatetime, IsStr, TestEnv, try_import
2323

2424
with try_import() as imports_successful:
25-
from pydantic_ai.models.openai import OpenAIModelSettings, OpenAIResponsesModel
25+
from pydantic_ai.models.openai import OpenAIModelSettings, OpenAIResponsesModel, OpenAIResponsesModelSettings
2626
from pydantic_ai.providers.openai import OpenAIProvider
2727

2828
pytestmark = [
@@ -286,3 +286,39 @@ async def test_openai_responses_model_http_error(allow_model_requests: None, ope
286286
with pytest.raises(ModelHTTPError):
287287
async with agent.run_stream('What is the capital of France?'):
288288
...
289+
290+
291+
async def test_openai_responses_model_builtin_tools(allow_model_requests: None, openai_api_key: str):
292+
model = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key))
293+
settings = OpenAIResponsesModelSettings(openai_builtin_tools=[{'type': 'web_search_preview'}])
294+
agent = Agent(model=model, model_settings=settings)
295+
result = await agent.run('Give me the best news about LLMs from the last 24 hours. Be short.')
296+
297+
# NOTE: We don't have the tool call because OpenAI calls the tool internally.
298+
assert result.all_messages() == snapshot(
299+
[
300+
ModelRequest(
301+
parts=[
302+
UserPromptPart(
303+
content='Give me the best news about LLMs from the last 24 hours. Be short.',
304+
timestamp=IsDatetime(),
305+
)
306+
]
307+
),
308+
ModelResponse(
309+
parts=[
310+
TextPart(
311+
content="""\
312+
In the past 24 hours, OpenAI announced plans to release its first open-weight language model with reasoning capabilities since GPT-2. This model will allow developers to fine-tune it for specific applications without needing the original training data. To gather feedback and refine the model, OpenAI will host developer events starting in San Francisco and expanding to Europe and Asia-Pacific regions. ([reuters.com](https://www.reuters.com/technology/artificial-intelligence/openai-plans-release-open-weight-language-model-coming-months-2025-03-31/?utm_source=openai))
313+
314+
315+
## OpenAI to Release Open-Weight Language Model:
316+
- [OpenAI plans to release open-weight language model in coming months](https://www.reuters.com/technology/artificial-intelligence/openai-plans-release-open-weight-language-model-coming-months-2025-03-31/?utm_source=openai) \
317+
"""
318+
)
319+
],
320+
model_name='gpt-4o-2024-08-06',
321+
timestamp=IsDatetime(),
322+
),
323+
]
324+
)

0 commit comments

Comments
 (0)