openai-agents 0.3.2__py3-none-any.whl → 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of openai-agents might be problematic. Click here for more details.

agents/mcp/server.py CHANGED
@@ -11,6 +11,7 @@ from typing import TYPE_CHECKING, Any, Callable, Literal, TypeVar
11
11
 
12
12
  from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
13
13
  from mcp import ClientSession, StdioServerParameters, Tool as MCPTool, stdio_client
14
+ from mcp.client.session import MessageHandlerFnT
14
15
  from mcp.client.sse import sse_client
15
16
  from mcp.client.streamable_http import GetSessionIdCallback, streamablehttp_client
16
17
  from mcp.shared.message import SessionMessage
@@ -20,7 +21,7 @@ from typing_extensions import NotRequired, TypedDict
20
21
  from ..exceptions import UserError
21
22
  from ..logger import logger
22
23
  from ..run_context import RunContextWrapper
23
- from .util import ToolFilter, ToolFilterContext, ToolFilterStatic
24
+ from .util import HttpClientFactory, ToolFilter, ToolFilterContext, ToolFilterStatic
24
25
 
25
26
  T = TypeVar("T")
26
27
 
@@ -103,6 +104,7 @@ class _MCPServerWithClientSession(MCPServer, abc.ABC):
103
104
  use_structured_content: bool = False,
104
105
  max_retry_attempts: int = 0,
105
106
  retry_backoff_seconds_base: float = 1.0,
107
+ message_handler: MessageHandlerFnT | None = None,
106
108
  ):
107
109
  """
108
110
  Args:
@@ -124,6 +126,8 @@ class _MCPServerWithClientSession(MCPServer, abc.ABC):
124
126
  Defaults to no retries.
125
127
  retry_backoff_seconds_base: The base delay, in seconds, used for exponential
126
128
  backoff between retries.
129
+ message_handler: Optional handler invoked for session messages as delivered by the
130
+ ClientSession.
127
131
  """
128
132
  super().__init__(use_structured_content=use_structured_content)
129
133
  self.session: ClientSession | None = None
@@ -135,6 +139,7 @@ class _MCPServerWithClientSession(MCPServer, abc.ABC):
135
139
  self.client_session_timeout_seconds = client_session_timeout_seconds
136
140
  self.max_retry_attempts = max_retry_attempts
137
141
  self.retry_backoff_seconds_base = retry_backoff_seconds_base
142
+ self.message_handler = message_handler
138
143
 
139
144
  # The cache is always dirty at startup, so that we fetch tools at least once
140
145
  self._cache_dirty = True
@@ -272,6 +277,7 @@ class _MCPServerWithClientSession(MCPServer, abc.ABC):
272
277
  timedelta(seconds=self.client_session_timeout_seconds)
273
278
  if self.client_session_timeout_seconds
274
279
  else None,
280
+ message_handler=self.message_handler,
275
281
  )
276
282
  )
277
283
  server_result = await session.initialize()
@@ -394,6 +400,7 @@ class MCPServerStdio(_MCPServerWithClientSession):
394
400
  use_structured_content: bool = False,
395
401
  max_retry_attempts: int = 0,
396
402
  retry_backoff_seconds_base: float = 1.0,
403
+ message_handler: MessageHandlerFnT | None = None,
397
404
  ):
398
405
  """Create a new MCP server based on the stdio transport.
399
406
 
@@ -421,6 +428,8 @@ class MCPServerStdio(_MCPServerWithClientSession):
421
428
  Defaults to no retries.
422
429
  retry_backoff_seconds_base: The base delay, in seconds, for exponential
423
430
  backoff between retries.
431
+ message_handler: Optional handler invoked for session messages as delivered by the
432
+ ClientSession.
424
433
  """
425
434
  super().__init__(
426
435
  cache_tools_list,
@@ -429,6 +438,7 @@ class MCPServerStdio(_MCPServerWithClientSession):
429
438
  use_structured_content,
430
439
  max_retry_attempts,
431
440
  retry_backoff_seconds_base,
441
+ message_handler=message_handler,
432
442
  )
433
443
 
434
444
  self.params = StdioServerParameters(
@@ -492,6 +502,7 @@ class MCPServerSse(_MCPServerWithClientSession):
492
502
  use_structured_content: bool = False,
493
503
  max_retry_attempts: int = 0,
494
504
  retry_backoff_seconds_base: float = 1.0,
505
+ message_handler: MessageHandlerFnT | None = None,
495
506
  ):
496
507
  """Create a new MCP server based on the HTTP with SSE transport.
497
508
 
@@ -521,6 +532,8 @@ class MCPServerSse(_MCPServerWithClientSession):
521
532
  Defaults to no retries.
522
533
  retry_backoff_seconds_base: The base delay, in seconds, for exponential
523
534
  backoff between retries.
535
+ message_handler: Optional handler invoked for session messages as delivered by the
536
+ ClientSession.
524
537
  """
525
538
  super().__init__(
526
539
  cache_tools_list,
@@ -529,6 +542,7 @@ class MCPServerSse(_MCPServerWithClientSession):
529
542
  use_structured_content,
530
543
  max_retry_attempts,
531
544
  retry_backoff_seconds_base,
545
+ message_handler=message_handler,
532
546
  )
533
547
 
534
548
  self.params = params
@@ -575,6 +589,9 @@ class MCPServerStreamableHttpParams(TypedDict):
575
589
  terminate_on_close: NotRequired[bool]
576
590
  """Terminate on close"""
577
591
 
592
+ httpx_client_factory: NotRequired[HttpClientFactory]
593
+ """Custom HTTP client factory for configuring httpx.AsyncClient behavior."""
594
+
578
595
 
579
596
  class MCPServerStreamableHttp(_MCPServerWithClientSession):
580
597
  """MCP server implementation that uses the Streamable HTTP transport. See the [spec]
@@ -592,14 +609,15 @@ class MCPServerStreamableHttp(_MCPServerWithClientSession):
592
609
  use_structured_content: bool = False,
593
610
  max_retry_attempts: int = 0,
594
611
  retry_backoff_seconds_base: float = 1.0,
612
+ message_handler: MessageHandlerFnT | None = None,
595
613
  ):
596
614
  """Create a new MCP server based on the Streamable HTTP transport.
597
615
 
598
616
  Args:
599
617
  params: The params that configure the server. This includes the URL of the server,
600
- the headers to send to the server, the timeout for the HTTP request, and the
601
- timeout for the Streamable HTTP connection and whether we need to
602
- terminate on close.
618
+ the headers to send to the server, the timeout for the HTTP request, the
619
+ timeout for the Streamable HTTP connection, whether we need to
620
+ terminate on close, and an optional custom HTTP client factory.
603
621
 
604
622
  cache_tools_list: Whether to cache the tools list. If `True`, the tools list will be
605
623
  cached and only fetched from the server once. If `False`, the tools list will be
@@ -622,6 +640,8 @@ class MCPServerStreamableHttp(_MCPServerWithClientSession):
622
640
  Defaults to no retries.
623
641
  retry_backoff_seconds_base: The base delay, in seconds, for exponential
624
642
  backoff between retries.
643
+ message_handler: Optional handler invoked for session messages as delivered by the
644
+ ClientSession.
625
645
  """
626
646
  super().__init__(
627
647
  cache_tools_list,
@@ -630,6 +650,7 @@ class MCPServerStreamableHttp(_MCPServerWithClientSession):
630
650
  use_structured_content,
631
651
  max_retry_attempts,
632
652
  retry_backoff_seconds_base,
653
+ message_handler=message_handler,
633
654
  )
634
655
 
635
656
  self.params = params
@@ -645,13 +666,24 @@ class MCPServerStreamableHttp(_MCPServerWithClientSession):
645
666
  ]
646
667
  ]:
647
668
  """Create the streams for the server."""
648
- return streamablehttp_client(
649
- url=self.params["url"],
650
- headers=self.params.get("headers", None),
651
- timeout=self.params.get("timeout", 5),
652
- sse_read_timeout=self.params.get("sse_read_timeout", 60 * 5),
653
- terminate_on_close=self.params.get("terminate_on_close", True),
654
- )
669
+ # Only pass httpx_client_factory if it's provided
670
+ if "httpx_client_factory" in self.params:
671
+ return streamablehttp_client(
672
+ url=self.params["url"],
673
+ headers=self.params.get("headers", None),
674
+ timeout=self.params.get("timeout", 5),
675
+ sse_read_timeout=self.params.get("sse_read_timeout", 60 * 5),
676
+ terminate_on_close=self.params.get("terminate_on_close", True),
677
+ httpx_client_factory=self.params["httpx_client_factory"],
678
+ )
679
+ else:
680
+ return streamablehttp_client(
681
+ url=self.params["url"],
682
+ headers=self.params.get("headers", None),
683
+ timeout=self.params.get("timeout", 5),
684
+ sse_read_timeout=self.params.get("sse_read_timeout", 60 * 5),
685
+ terminate_on_close=self.params.get("terminate_on_close", True),
686
+ )
655
687
 
656
688
  @property
657
689
  def name(self) -> str:
agents/mcp/util.py CHANGED
@@ -1,8 +1,9 @@
1
1
  import functools
2
2
  import json
3
3
  from dataclasses import dataclass
4
- from typing import TYPE_CHECKING, Any, Callable, Optional, Union
4
+ from typing import TYPE_CHECKING, Any, Callable, Optional, Protocol, Union
5
5
 
6
+ import httpx
6
7
  from typing_extensions import NotRequired, TypedDict
7
8
 
8
9
  from .. import _debug
@@ -21,6 +22,21 @@ if TYPE_CHECKING:
21
22
  from .server import MCPServer
22
23
 
23
24
 
25
+ class HttpClientFactory(Protocol):
26
+ """Protocol for HTTP client factory functions.
27
+
28
+ This interface matches the MCP SDK's McpHttpClientFactory but is defined locally
29
+ to avoid accessing internal MCP SDK modules.
30
+ """
31
+
32
+ def __call__(
33
+ self,
34
+ headers: Optional[dict[str, str]] = None,
35
+ timeout: Optional[httpx.Timeout] = None,
36
+ auth: Optional[httpx.Auth] = None,
37
+ ) -> httpx.AsyncClient: ...
38
+
39
+
24
40
  @dataclass
25
41
  class ToolFilterContext:
26
42
  """Context information available to tool filter functions."""
@@ -50,7 +50,7 @@ class OpenAIConversationsSession(SessionABC):
50
50
  order="asc",
51
51
  ):
52
52
  # calling model_dump() to make this serializable
53
- all_items.append(item.model_dump())
53
+ all_items.append(item.model_dump(exclude_unset=True))
54
54
  else:
55
55
  async for item in self._openai_client.conversations.items.list(
56
56
  conversation_id=session_id,
@@ -58,7 +58,7 @@ class OpenAIConversationsSession(SessionABC):
58
58
  order="desc",
59
59
  ):
60
60
  # calling model_dump() to make this serializable
61
- all_items.append(item.model_dump())
61
+ all_items.append(item.model_dump(exclude_unset=True))
62
62
  if limit is not None and len(all_items) >= limit:
63
63
  break
64
64
  all_items.reverse()
@@ -2,12 +2,13 @@ from __future__ import annotations
2
2
 
3
3
  import json
4
4
  from collections.abc import Iterable
5
- from typing import Any, Literal, cast
5
+ from typing import Any, Literal, Union, cast
6
6
 
7
- from openai import NOT_GIVEN, NotGiven
7
+ from openai import Omit, omit
8
8
  from openai.types.chat import (
9
9
  ChatCompletionAssistantMessageParam,
10
10
  ChatCompletionContentPartImageParam,
11
+ ChatCompletionContentPartInputAudioParam,
11
12
  ChatCompletionContentPartParam,
12
13
  ChatCompletionContentPartTextParam,
13
14
  ChatCompletionDeveloperMessageParam,
@@ -27,6 +28,7 @@ from openai.types.responses import (
27
28
  ResponseFileSearchToolCallParam,
28
29
  ResponseFunctionToolCall,
29
30
  ResponseFunctionToolCallParam,
31
+ ResponseInputAudioParam,
30
32
  ResponseInputContentParam,
31
33
  ResponseInputFileParam,
32
34
  ResponseInputImageParam,
@@ -54,9 +56,9 @@ class Converter:
54
56
  @classmethod
55
57
  def convert_tool_choice(
56
58
  cls, tool_choice: Literal["auto", "required", "none"] | str | MCPToolChoice | None
57
- ) -> ChatCompletionToolChoiceOptionParam | NotGiven:
59
+ ) -> ChatCompletionToolChoiceOptionParam | Omit:
58
60
  if tool_choice is None:
59
- return NOT_GIVEN
61
+ return omit
60
62
  elif isinstance(tool_choice, MCPToolChoice):
61
63
  raise UserError("MCPToolChoice is not supported for Chat Completions models")
62
64
  elif tool_choice == "auto":
@@ -76,9 +78,9 @@ class Converter:
76
78
  @classmethod
77
79
  def convert_response_format(
78
80
  cls, final_output_schema: AgentOutputSchemaBase | None
79
- ) -> ResponseFormat | NotGiven:
81
+ ) -> ResponseFormat | Omit:
80
82
  if not final_output_schema or final_output_schema.is_plain_text():
81
- return NOT_GIVEN
83
+ return omit
82
84
 
83
85
  return {
84
86
  "type": "json_schema",
@@ -287,23 +289,44 @@ class Converter:
287
289
  },
288
290
  )
289
291
  )
292
+ elif isinstance(c, dict) and c.get("type") == "input_audio":
293
+ casted_audio_param = cast(ResponseInputAudioParam, c)
294
+ audio_payload = casted_audio_param.get("input_audio")
295
+ if not audio_payload:
296
+ raise UserError(
297
+ f"Only audio data is supported for input_audio {casted_audio_param}"
298
+ )
299
+ if not isinstance(audio_payload, dict):
300
+ raise UserError(
301
+ f"input_audio must provide audio data and format {casted_audio_param}"
302
+ )
303
+ audio_data = audio_payload.get("data")
304
+ audio_format = audio_payload.get("format")
305
+ if not audio_data or not audio_format:
306
+ raise UserError(
307
+ f"input_audio requires both data and format {casted_audio_param}"
308
+ )
309
+ out.append(
310
+ ChatCompletionContentPartInputAudioParam(
311
+ type="input_audio",
312
+ input_audio={
313
+ "data": audio_data,
314
+ "format": audio_format,
315
+ },
316
+ )
317
+ )
290
318
  elif isinstance(c, dict) and c.get("type") == "input_file":
291
319
  casted_file_param = cast(ResponseInputFileParam, c)
292
320
  if "file_data" not in casted_file_param or not casted_file_param["file_data"]:
293
321
  raise UserError(
294
322
  f"Only file_data is supported for input_file {casted_file_param}"
295
323
  )
296
- if "filename" not in casted_file_param or not casted_file_param["filename"]:
297
- raise UserError(f"filename must be provided for input_file {casted_file_param}")
298
- out.append(
299
- File(
300
- type="file",
301
- file=FileFile(
302
- file_data=casted_file_param["file_data"],
303
- filename=casted_file_param["filename"],
304
- ),
305
- )
306
- )
324
+ filedata = FileFile(file_data=casted_file_param["file_data"])
325
+
326
+ if "filename" in casted_file_param and casted_file_param["filename"]:
327
+ filedata["filename"] = casted_file_param["filename"]
328
+
329
+ out.append(File(type="file", file=filedata))
307
330
  else:
308
331
  raise UserError(f"Unknown content: {c}")
309
332
  return out
@@ -511,10 +534,13 @@ class Converter:
511
534
  # 5) function call output => tool message
512
535
  elif func_output := cls.maybe_function_tool_call_output(item):
513
536
  flush_assistant_message()
537
+ output_content = cast(
538
+ Union[str, Iterable[ResponseInputContentParam]], func_output["output"]
539
+ )
514
540
  msg: ChatCompletionToolMessageParam = {
515
541
  "role": "tool",
516
542
  "tool_call_id": func_output["call_id"],
517
- "content": func_output["output"],
543
+ "content": cls.extract_text_content(output_content),
518
544
  }
519
545
  result.append(msg)
520
546
 
@@ -533,7 +559,7 @@ class Converter:
533
559
 
534
560
  if content_items and preserve_thinking_blocks:
535
561
  # Reconstruct thinking blocks from content and signature
536
- pending_thinking_blocks = []
562
+ reconstructed_thinking_blocks = []
537
563
  for content_item in content_items:
538
564
  if (
539
565
  isinstance(content_item, dict)
@@ -546,7 +572,11 @@ class Converter:
546
572
  # Add signatures if available
547
573
  if signatures:
548
574
  thinking_block["signature"] = signatures.pop(0)
549
- pending_thinking_blocks.append(thinking_block)
575
+ reconstructed_thinking_blocks.append(thinking_block)
576
+
577
+ # Store thinking blocks as pending for the next assistant message
578
+ # This preserves the original behavior
579
+ pending_thinking_blocks = reconstructed_thinking_blocks
550
580
 
551
581
  # 8) If we haven't recognized it => fail or ignore
552
582
  else:
@@ -3,9 +3,9 @@ from __future__ import annotations
3
3
  import json
4
4
  import time
5
5
  from collections.abc import AsyncIterator
6
- from typing import TYPE_CHECKING, Any, Literal, overload
6
+ from typing import TYPE_CHECKING, Any, Literal, cast, overload
7
7
 
8
- from openai import NOT_GIVEN, AsyncOpenAI, AsyncStream
8
+ from openai import AsyncOpenAI, AsyncStream, Omit, omit
9
9
  from openai.types import ChatModel
10
10
  from openai.types.chat import ChatCompletion, ChatCompletionChunk, ChatCompletionMessage
11
11
  from openai.types.chat.chat_completion import Choice
@@ -44,8 +44,8 @@ class OpenAIChatCompletionsModel(Model):
44
44
  self.model = model
45
45
  self._client = openai_client
46
46
 
47
- def _non_null_or_not_given(self, value: Any) -> Any:
48
- return value if value is not None else NOT_GIVEN
47
+ def _non_null_or_omit(self, value: Any) -> Any:
48
+ return value if value is not None else omit
49
49
 
50
50
  async def get_response(
51
51
  self,
@@ -243,13 +243,12 @@ class OpenAIChatCompletionsModel(Model):
243
243
  if tracing.include_data():
244
244
  span.span_data.input = converted_messages
245
245
 
246
- parallel_tool_calls = (
247
- True
248
- if model_settings.parallel_tool_calls and tools and len(tools) > 0
249
- else False
250
- if model_settings.parallel_tool_calls is False
251
- else NOT_GIVEN
252
- )
246
+ if model_settings.parallel_tool_calls and tools:
247
+ parallel_tool_calls: bool | Omit = True
248
+ elif model_settings.parallel_tool_calls is False:
249
+ parallel_tool_calls = False
250
+ else:
251
+ parallel_tool_calls = omit
253
252
  tool_choice = Converter.convert_tool_choice(model_settings.tool_choice)
254
253
  response_format = Converter.convert_response_format(output_schema)
255
254
 
@@ -259,6 +258,7 @@ class OpenAIChatCompletionsModel(Model):
259
258
  converted_tools.append(Converter.convert_handoff_tool(handoff))
260
259
 
261
260
  converted_tools = _to_dump_compatible(converted_tools)
261
+ tools_param = converted_tools if converted_tools else omit
262
262
 
263
263
  if _debug.DONT_LOG_MODEL_DATA:
264
264
  logger.debug("Calling LLM")
@@ -288,28 +288,30 @@ class OpenAIChatCompletionsModel(Model):
288
288
  self._get_client(), model_settings, stream=stream
289
289
  )
290
290
 
291
+ stream_param: Literal[True] | Omit = True if stream else omit
292
+
291
293
  ret = await self._get_client().chat.completions.create(
292
294
  model=self.model,
293
295
  messages=converted_messages,
294
- tools=converted_tools or NOT_GIVEN,
295
- temperature=self._non_null_or_not_given(model_settings.temperature),
296
- top_p=self._non_null_or_not_given(model_settings.top_p),
297
- frequency_penalty=self._non_null_or_not_given(model_settings.frequency_penalty),
298
- presence_penalty=self._non_null_or_not_given(model_settings.presence_penalty),
299
- max_tokens=self._non_null_or_not_given(model_settings.max_tokens),
296
+ tools=tools_param,
297
+ temperature=self._non_null_or_omit(model_settings.temperature),
298
+ top_p=self._non_null_or_omit(model_settings.top_p),
299
+ frequency_penalty=self._non_null_or_omit(model_settings.frequency_penalty),
300
+ presence_penalty=self._non_null_or_omit(model_settings.presence_penalty),
301
+ max_tokens=self._non_null_or_omit(model_settings.max_tokens),
300
302
  tool_choice=tool_choice,
301
303
  response_format=response_format,
302
304
  parallel_tool_calls=parallel_tool_calls,
303
- stream=stream,
304
- stream_options=self._non_null_or_not_given(stream_options),
305
- store=self._non_null_or_not_given(store),
306
- reasoning_effort=self._non_null_or_not_given(reasoning_effort),
307
- verbosity=self._non_null_or_not_given(model_settings.verbosity),
308
- top_logprobs=self._non_null_or_not_given(model_settings.top_logprobs),
305
+ stream=cast(Any, stream_param),
306
+ stream_options=self._non_null_or_omit(stream_options),
307
+ store=self._non_null_or_omit(store),
308
+ reasoning_effort=self._non_null_or_omit(reasoning_effort),
309
+ verbosity=self._non_null_or_omit(model_settings.verbosity),
310
+ top_logprobs=self._non_null_or_omit(model_settings.top_logprobs),
309
311
  extra_headers=self._merge_headers(model_settings),
310
312
  extra_query=model_settings.extra_query,
311
313
  extra_body=model_settings.extra_body,
312
- metadata=self._non_null_or_not_given(model_settings.metadata),
314
+ metadata=self._non_null_or_omit(model_settings.metadata),
313
315
  **(model_settings.extra_args or {}),
314
316
  )
315
317
 
@@ -319,14 +321,13 @@ class OpenAIChatCompletionsModel(Model):
319
321
  responses_tool_choice = OpenAIResponsesConverter.convert_tool_choice(
320
322
  model_settings.tool_choice
321
323
  )
322
- if responses_tool_choice is None or responses_tool_choice == NOT_GIVEN:
324
+ if responses_tool_choice is None or responses_tool_choice is omit:
323
325
  # For Responses API data compatibility with Chat Completions patterns,
324
326
  # we need to set "none" if tool_choice is absent.
325
327
  # Without this fix, you'll get the following error:
326
328
  # pydantic_core._pydantic_core.ValidationError: 4 validation errors for Response
327
329
  # tool_choice.literal['none','auto','required']
328
330
  # Input should be 'none', 'auto' or 'required'
329
- # [type=literal_error, input_value=NOT_GIVEN, input_type=NotGiven]
330
331
  # see also: https://github.com/openai/openai-agents-python/issues/980
331
332
  responses_tool_choice = "auto"
332
333
 
@@ -4,9 +4,9 @@ import json
4
4
  from collections.abc import AsyncIterator
5
5
  from contextvars import ContextVar
6
6
  from dataclasses import dataclass
7
- from typing import TYPE_CHECKING, Any, Literal, cast, overload
7
+ from typing import TYPE_CHECKING, Any, Literal, Union, cast, overload
8
8
 
9
- from openai import NOT_GIVEN, APIStatusError, AsyncOpenAI, AsyncStream, NotGiven
9
+ from openai import APIStatusError, AsyncOpenAI, AsyncStream, Omit, omit
10
10
  from openai.types import ChatModel
11
11
  from openai.types.responses import (
12
12
  Response,
@@ -69,8 +69,8 @@ class OpenAIResponsesModel(Model):
69
69
  self.model = model
70
70
  self._client = openai_client
71
71
 
72
- def _non_null_or_not_given(self, value: Any) -> Any:
73
- return value if value is not None else NOT_GIVEN
72
+ def _non_null_or_omit(self, value: Any) -> Any:
73
+ return value if value is not None else omit
74
74
 
75
75
  async def get_response(
76
76
  self,
@@ -249,13 +249,12 @@ class OpenAIResponsesModel(Model):
249
249
  list_input = ItemHelpers.input_to_new_input_list(input)
250
250
  list_input = _to_dump_compatible(list_input)
251
251
 
252
- parallel_tool_calls = (
253
- True
254
- if model_settings.parallel_tool_calls and tools and len(tools) > 0
255
- else False
256
- if model_settings.parallel_tool_calls is False
257
- else NOT_GIVEN
258
- )
252
+ if model_settings.parallel_tool_calls and tools:
253
+ parallel_tool_calls: bool | Omit = True
254
+ elif model_settings.parallel_tool_calls is False:
255
+ parallel_tool_calls = False
256
+ else:
257
+ parallel_tool_calls = omit
259
258
 
260
259
  tool_choice = Converter.convert_tool_choice(model_settings.tool_choice)
261
260
  converted_tools = Converter.convert_tools(tools, handoffs)
@@ -297,36 +296,39 @@ class OpenAIResponsesModel(Model):
297
296
  if model_settings.top_logprobs is not None:
298
297
  extra_args["top_logprobs"] = model_settings.top_logprobs
299
298
  if model_settings.verbosity is not None:
300
- if response_format != NOT_GIVEN:
299
+ if response_format is not omit:
301
300
  response_format["verbosity"] = model_settings.verbosity # type: ignore [index]
302
301
  else:
303
302
  response_format = {"verbosity": model_settings.verbosity}
304
303
 
305
- return await self._client.responses.create(
306
- previous_response_id=self._non_null_or_not_given(previous_response_id),
307
- conversation=self._non_null_or_not_given(conversation_id),
308
- instructions=self._non_null_or_not_given(system_instructions),
304
+ stream_param: Literal[True] | Omit = True if stream else omit
305
+
306
+ response = await self._client.responses.create(
307
+ previous_response_id=self._non_null_or_omit(previous_response_id),
308
+ conversation=self._non_null_or_omit(conversation_id),
309
+ instructions=self._non_null_or_omit(system_instructions),
309
310
  model=self.model,
310
311
  input=list_input,
311
312
  include=include,
312
313
  tools=converted_tools_payload,
313
- prompt=self._non_null_or_not_given(prompt),
314
- temperature=self._non_null_or_not_given(model_settings.temperature),
315
- top_p=self._non_null_or_not_given(model_settings.top_p),
316
- truncation=self._non_null_or_not_given(model_settings.truncation),
317
- max_output_tokens=self._non_null_or_not_given(model_settings.max_tokens),
314
+ prompt=self._non_null_or_omit(prompt),
315
+ temperature=self._non_null_or_omit(model_settings.temperature),
316
+ top_p=self._non_null_or_omit(model_settings.top_p),
317
+ truncation=self._non_null_or_omit(model_settings.truncation),
318
+ max_output_tokens=self._non_null_or_omit(model_settings.max_tokens),
318
319
  tool_choice=tool_choice,
319
320
  parallel_tool_calls=parallel_tool_calls,
320
- stream=stream,
321
+ stream=cast(Any, stream_param),
321
322
  extra_headers=self._merge_headers(model_settings),
322
323
  extra_query=model_settings.extra_query,
323
324
  extra_body=model_settings.extra_body,
324
325
  text=response_format,
325
- store=self._non_null_or_not_given(model_settings.store),
326
- reasoning=self._non_null_or_not_given(model_settings.reasoning),
327
- metadata=self._non_null_or_not_given(model_settings.metadata),
326
+ store=self._non_null_or_omit(model_settings.store),
327
+ reasoning=self._non_null_or_omit(model_settings.reasoning),
328
+ metadata=self._non_null_or_omit(model_settings.metadata),
328
329
  **extra_args,
329
330
  )
331
+ return cast(Union[Response, AsyncStream[ResponseStreamEvent]], response)
330
332
 
331
333
  def _get_client(self) -> AsyncOpenAI:
332
334
  if self._client is None:
@@ -351,9 +353,9 @@ class Converter:
351
353
  @classmethod
352
354
  def convert_tool_choice(
353
355
  cls, tool_choice: Literal["auto", "required", "none"] | str | MCPToolChoice | None
354
- ) -> response_create_params.ToolChoice | NotGiven:
356
+ ) -> response_create_params.ToolChoice | Omit:
355
357
  if tool_choice is None:
356
- return NOT_GIVEN
358
+ return omit
357
359
  elif isinstance(tool_choice, MCPToolChoice):
358
360
  return {
359
361
  "server_label": tool_choice.server_label,
@@ -404,9 +406,9 @@ class Converter:
404
406
  @classmethod
405
407
  def get_response_format(
406
408
  cls, output_schema: AgentOutputSchemaBase | None
407
- ) -> ResponseTextConfigParam | NotGiven:
409
+ ) -> ResponseTextConfigParam | Omit:
408
410
  if output_schema is None or output_schema.is_plain_text():
409
- return NOT_GIVEN
411
+ return omit
410
412
  else:
411
413
  return {
412
414
  "format": {
@@ -13,10 +13,10 @@ from ..strict_schema import ensure_strict_json_schema
13
13
  from ..tracing.spans import SpanError
14
14
  from ..util import _error_tracing, _json
15
15
  from ..util._types import MaybeAwaitable
16
+ from . import RealtimeAgent
16
17
 
17
18
  if TYPE_CHECKING:
18
19
  from ..agent import AgentBase
19
- from . import RealtimeAgent
20
20
 
21
21
 
22
22
  # The handoff input type is the type of data passed when the agent is called via a handoff.