openai-agents 0.4.1__py3-none-any.whl → 0.4.2__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.

@@ -110,18 +110,26 @@ class LitellmModel(Model):
110
110
  prompt=prompt,
111
111
  )
112
112
 
113
- assert isinstance(response.choices[0], litellm.types.utils.Choices)
113
+ message: litellm.types.utils.Message | None = None
114
+ first_choice: litellm.types.utils.Choices | None = None
115
+ if response.choices and len(response.choices) > 0:
116
+ choice = response.choices[0]
117
+ if isinstance(choice, litellm.types.utils.Choices):
118
+ first_choice = choice
119
+ message = first_choice.message
114
120
 
115
121
  if _debug.DONT_LOG_MODEL_DATA:
116
122
  logger.debug("Received model response")
117
123
  else:
118
- logger.debug(
119
- f"""LLM resp:\n{
120
- json.dumps(
121
- response.choices[0].message.model_dump(), indent=2, ensure_ascii=False
122
- )
123
- }\n"""
124
- )
124
+ if message is not None:
125
+ logger.debug(
126
+ f"""LLM resp:\n{
127
+ json.dumps(message.model_dump(), indent=2, ensure_ascii=False)
128
+ }\n"""
129
+ )
130
+ else:
131
+ finish_reason = first_choice.finish_reason if first_choice else "-"
132
+ logger.debug(f"LLM resp had no message. finish_reason: {finish_reason}")
125
133
 
126
134
  if hasattr(response, "usage"):
127
135
  response_usage = response.usage
@@ -152,14 +160,20 @@ class LitellmModel(Model):
152
160
  logger.warning("No usage information returned from Litellm")
153
161
 
154
162
  if tracing.include_data():
155
- span_generation.span_data.output = [response.choices[0].message.model_dump()]
163
+ span_generation.span_data.output = (
164
+ [message.model_dump()] if message is not None else []
165
+ )
156
166
  span_generation.span_data.usage = {
157
167
  "input_tokens": usage.input_tokens,
158
168
  "output_tokens": usage.output_tokens,
159
169
  }
160
170
 
161
- items = Converter.message_to_output_items(
162
- LitellmConverter.convert_message_to_openai(response.choices[0].message)
171
+ items = (
172
+ Converter.message_to_output_items(
173
+ LitellmConverter.convert_message_to_openai(message)
174
+ )
175
+ if message is not None
176
+ else []
163
177
  )
164
178
 
165
179
  return ModelResponse(
@@ -326,6 +340,23 @@ class LitellmModel(Model):
326
340
  )
327
341
 
328
342
  reasoning_effort = model_settings.reasoning.effort if model_settings.reasoning else None
343
+ # Enable developers to pass non-OpenAI compatible reasoning_effort data like "none"
344
+ # Priority order:
345
+ # 1. model_settings.reasoning.effort
346
+ # 2. model_settings.extra_body["reasoning_effort"]
347
+ # 3. model_settings.extra_args["reasoning_effort"]
348
+ if (
349
+ reasoning_effort is None # Unset in model_settings
350
+ and isinstance(model_settings.extra_body, dict)
351
+ and "reasoning_effort" in model_settings.extra_body
352
+ ):
353
+ reasoning_effort = model_settings.extra_body["reasoning_effort"]
354
+ if (
355
+ reasoning_effort is None # Unset in both model_settings and model_settings.extra_body
356
+ and model_settings.extra_args
357
+ and "reasoning_effort" in model_settings.extra_args
358
+ ):
359
+ reasoning_effort = model_settings.extra_args["reasoning_effort"]
329
360
 
330
361
  stream_options = None
331
362
  if stream and model_settings.include_usage is not None:
@@ -343,6 +374,9 @@ class LitellmModel(Model):
343
374
  if model_settings.extra_args:
344
375
  extra_kwargs.update(model_settings.extra_args)
345
376
 
377
+ # Prevent duplicate reasoning_effort kwargs when it was promoted to a top-level argument.
378
+ extra_kwargs.pop("reasoning_effort", None)
379
+
346
380
  ret = await litellm.acompletion(
347
381
  model=self.model,
348
382
  messages=converted_messages,
@@ -150,6 +150,12 @@ class ChatCmplStreamHandler:
150
150
  )
151
151
 
152
152
  if reasoning_content and state.reasoning_content_index_and_output:
153
+ # Ensure summary list has at least one element
154
+ if not state.reasoning_content_index_and_output[1].summary:
155
+ state.reasoning_content_index_and_output[1].summary = [
156
+ Summary(text="", type="summary_text")
157
+ ]
158
+
153
159
  yield ResponseReasoningSummaryTextDeltaEvent(
154
160
  delta=reasoning_content,
155
161
  item_id=FAKE_RESPONSES_ID,
@@ -201,7 +207,7 @@ class ChatCmplStreamHandler:
201
207
  )
202
208
 
203
209
  # Create a new summary with updated text
204
- if state.reasoning_content_index_and_output[1].content is None:
210
+ if not state.reasoning_content_index_and_output[1].content:
205
211
  state.reasoning_content_index_and_output[1].content = [
206
212
  Content(text="", type="reasoning_text")
207
213
  ]
agents/realtime/config.py CHANGED
@@ -184,6 +184,9 @@ class RealtimeRunConfig(TypedDict):
184
184
  tracing_disabled: NotRequired[bool]
185
185
  """Whether tracing is disabled for this run."""
186
186
 
187
+ async_tool_calls: NotRequired[bool]
188
+ """Whether function tool calls should run asynchronously. Defaults to True."""
189
+
187
190
  # TODO (rm) Add history audio storage config
188
191
 
189
192
 
@@ -112,7 +112,7 @@ class RealtimeSession(RealtimeModelListener):
112
112
  }
113
113
  self._event_queue: asyncio.Queue[RealtimeSessionEvent] = asyncio.Queue()
114
114
  self._closed = False
115
- self._stored_exception: Exception | None = None
115
+ self._stored_exception: BaseException | None = None
116
116
 
117
117
  # Guardrails state tracking
118
118
  self._interrupted_response_ids: set[str] = set()
@@ -123,6 +123,8 @@ class RealtimeSession(RealtimeModelListener):
123
123
  )
124
124
 
125
125
  self._guardrail_tasks: set[asyncio.Task[Any]] = set()
126
+ self._tool_call_tasks: set[asyncio.Task[Any]] = set()
127
+ self._async_tool_calls: bool = bool(self._run_config.get("async_tool_calls", True))
126
128
 
127
129
  @property
128
130
  def model(self) -> RealtimeModel:
@@ -216,7 +218,11 @@ class RealtimeSession(RealtimeModelListener):
216
218
  if event.type == "error":
217
219
  await self._put_event(RealtimeError(info=self._event_info, error=event.error))
218
220
  elif event.type == "function_call":
219
- await self._handle_tool_call(event)
221
+ agent_snapshot = self._current_agent
222
+ if self._async_tool_calls:
223
+ self._enqueue_tool_call_task(event, agent_snapshot)
224
+ else:
225
+ await self._handle_tool_call(event, agent_snapshot=agent_snapshot)
220
226
  elif event.type == "audio":
221
227
  await self._put_event(
222
228
  RealtimeAudio(
@@ -384,11 +390,17 @@ class RealtimeSession(RealtimeModelListener):
384
390
  """Put an event into the queue."""
385
391
  await self._event_queue.put(event)
386
392
 
387
- async def _handle_tool_call(self, event: RealtimeModelToolCallEvent) -> None:
393
+ async def _handle_tool_call(
394
+ self,
395
+ event: RealtimeModelToolCallEvent,
396
+ *,
397
+ agent_snapshot: RealtimeAgent | None = None,
398
+ ) -> None:
388
399
  """Handle a tool call event."""
400
+ agent = agent_snapshot or self._current_agent
389
401
  tools, handoffs = await asyncio.gather(
390
- self._current_agent.get_all_tools(self._context_wrapper),
391
- self._get_handoffs(self._current_agent, self._context_wrapper),
402
+ agent.get_all_tools(self._context_wrapper),
403
+ self._get_handoffs(agent, self._context_wrapper),
392
404
  )
393
405
  function_map = {tool.name: tool for tool in tools if isinstance(tool, FunctionTool)}
394
406
  handoff_map = {handoff.tool_name: handoff for handoff in handoffs}
@@ -398,7 +410,7 @@ class RealtimeSession(RealtimeModelListener):
398
410
  RealtimeToolStart(
399
411
  info=self._event_info,
400
412
  tool=function_map[event.name],
401
- agent=self._current_agent,
413
+ agent=agent,
402
414
  )
403
415
  )
404
416
 
@@ -423,7 +435,7 @@ class RealtimeSession(RealtimeModelListener):
423
435
  info=self._event_info,
424
436
  tool=func_tool,
425
437
  output=result,
426
- agent=self._current_agent,
438
+ agent=agent,
427
439
  )
428
440
  )
429
441
  elif event.name in handoff_map:
@@ -444,7 +456,7 @@ class RealtimeSession(RealtimeModelListener):
444
456
  )
445
457
 
446
458
  # Store previous agent for event
447
- previous_agent = self._current_agent
459
+ previous_agent = agent
448
460
 
449
461
  # Update current agent
450
462
  self._current_agent = result
@@ -752,10 +764,49 @@ class RealtimeSession(RealtimeModelListener):
752
764
  task.cancel()
753
765
  self._guardrail_tasks.clear()
754
766
 
767
+ def _enqueue_tool_call_task(
768
+ self, event: RealtimeModelToolCallEvent, agent_snapshot: RealtimeAgent
769
+ ) -> None:
770
+ """Run tool calls in the background to avoid blocking realtime transport."""
771
+ task = asyncio.create_task(self._handle_tool_call(event, agent_snapshot=agent_snapshot))
772
+ self._tool_call_tasks.add(task)
773
+ task.add_done_callback(self._on_tool_call_task_done)
774
+
775
+ def _on_tool_call_task_done(self, task: asyncio.Task[Any]) -> None:
776
+ self._tool_call_tasks.discard(task)
777
+
778
+ if task.cancelled():
779
+ return
780
+
781
+ exception = task.exception()
782
+ if exception is None:
783
+ return
784
+
785
+ logger.exception("Realtime tool call task failed", exc_info=exception)
786
+
787
+ if self._stored_exception is None:
788
+ self._stored_exception = exception
789
+
790
+ asyncio.create_task(
791
+ self._put_event(
792
+ RealtimeError(
793
+ info=self._event_info,
794
+ error={"message": f"Tool call task failed: {exception}"},
795
+ )
796
+ )
797
+ )
798
+
799
+ def _cleanup_tool_call_tasks(self) -> None:
800
+ for task in self._tool_call_tasks:
801
+ if not task.done():
802
+ task.cancel()
803
+ self._tool_call_tasks.clear()
804
+
755
805
  async def _cleanup(self) -> None:
756
806
  """Clean up all resources and mark session as closed."""
757
807
  # Cancel and cleanup guardrail tasks
758
808
  self._cleanup_guardrail_tasks()
809
+ self._cleanup_tool_call_tasks()
759
810
 
760
811
  # Remove ourselves as a listener
761
812
  self._model.remove_listener(self)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openai-agents
3
- Version: 0.4.1
3
+ Version: 0.4.2
4
4
  Summary: OpenAI Agents SDK
5
5
  Project-URL: Homepage, https://openai.github.io/openai-agents-python/
6
6
  Project-URL: Repository, https://github.com/openai/openai-agents-python
@@ -44,7 +44,7 @@ Requires-Dist: numpy<3,>=2.2.0; (python_version >= '3.10') and extra == 'voice'
44
44
  Requires-Dist: websockets<16,>=15.0; extra == 'voice'
45
45
  Description-Content-Type: text/markdown
46
46
 
47
- # OpenAI Agents SDK
47
+ # OpenAI Agents SDK [![PyPI](https://img.shields.io/pypi/v/openai-agents?label=pypi%20package)](https://pypi.org/project/openai-agents/)
48
48
 
49
49
  The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows. It is provider-agnostic, supporting the OpenAI Responses and Chat Completions APIs, as well as 100+ other LLMs.
50
50
 
@@ -36,7 +36,7 @@ agents/extensions/memory/encrypt_session.py,sha256=PVnZIEj50bjUq16OLnMKrbZiinLkr
36
36
  agents/extensions/memory/redis_session.py,sha256=JwXY6zUTMgq9bRezlyFZ4Tze7DO7T0hioTc23qjSHjU,9838
37
37
  agents/extensions/memory/sqlalchemy_session.py,sha256=fnlZkNF_XZekP44uhiR4rjlCkwG7JJEiFm35TJfiCtc,12325
38
38
  agents/extensions/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
- agents/extensions/models/litellm_model.py,sha256=JwJqURCfazbhqV--yoopri27U3JvIk8BH7cVzpGhbKw,23949
39
+ agents/extensions/models/litellm_model.py,sha256=hbQFhAeEF5eHdyu5Q-7HNYFEhmn0KFK2KAcfYA10vqc,25621
40
40
  agents/extensions/models/litellm_provider.py,sha256=ZHgh1nMoEvA7NpawkzLh3JDuDFtwXUV94Rs7UrwWqAk,1083
41
41
  agents/mcp/__init__.py,sha256=yHmmYlrmEHzUas1inRLKL2iPqbb_-107G3gKe_tyg4I,750
42
42
  agents/mcp/server.py,sha256=cby0KKKKRhuWCydr4llERPL72Z94uV-SV3LLAcgcWTk,28435
@@ -50,7 +50,7 @@ agents/models/__init__.py,sha256=E0XVqWayVAsFqxucDLBW30siaqfNQsVrAnfidG_C3ok,287
50
50
  agents/models/_openai_shared.py,sha256=4Ngwo2Fv2RXY61Pqck1cYPkSln2tDnb8Ai-ao4QG-iE,836
51
51
  agents/models/chatcmpl_converter.py,sha256=qEobLnIJjrK6WRi_tsVkrDrGq78EGro3MZXlVMpMK2c,26011
52
52
  agents/models/chatcmpl_helpers.py,sha256=YC2krp_-uBgRCrCEImLjNvONTWRWfwLlPKHI4kBmNXE,1483
53
- agents/models/chatcmpl_stream_handler.py,sha256=r8nc-4hJg1plw87y24MD48O23xnfC_2gHKowtOYgO3M,28896
53
+ agents/models/chatcmpl_stream_handler.py,sha256=1h0esxmnlBk9NwDjjwSlWYzjzuMgIpMLtRU9kaszfyg,29212
54
54
  agents/models/default_models.py,sha256=mlvBePn8H4UkHo7lN-wh7A3k2ciLgBUFKpROQxzdTfs,2098
55
55
  agents/models/fake_id.py,sha256=lbXjUUSMeAQ8eFx4V5QLUnBClHE6adJlYYav55RlG5w,268
56
56
  agents/models/interface.py,sha256=-AFUHC8iRuGZmtQwguDw4s-M4OPL2y2mct4TAmWvVrU,4057
@@ -64,7 +64,7 @@ agents/realtime/_default_tracker.py,sha256=4OMxBvD1MnZmMn6JZYKL42uWhVzvK6NdDLDfP
64
64
  agents/realtime/_util.py,sha256=ehBzUN1RTD2m2TXq73Jm4WohQzJ6y_MfnF5MaK8uu14,341
65
65
  agents/realtime/agent.py,sha256=bkegBJ_lc3z3NtnlIyEkVZFxZWBJwVjsQVzpQZAu7PM,4283
66
66
  agents/realtime/audio_formats.py,sha256=DBUWVVff4XY5BT6Mol86tF4PFMp5OIS3LmAbqUmQn_k,1019
67
- agents/realtime/config.py,sha256=ud0GK8ZbcnKRC4oGZNwpsiZI8TZ1OdTSMADfFtM8Z6I,6948
67
+ agents/realtime/config.py,sha256=vnjgkeZXcOSLFopoAiGj4Vki_75pEJIKTagJtQpCWmg,7072
68
68
  agents/realtime/events.py,sha256=eANiNNyYlp_1Ybdl-MOwXRVTDtrK9hfgn6iw0xNxnaY,5889
69
69
  agents/realtime/handoffs.py,sha256=iJ4lr5RVdDkw5W3_AOGB_Az-hlRt1CoFFFNFDfd3ues,6698
70
70
  agents/realtime/items.py,sha256=5EG768FkKpbk-dhe4b_7BfFpdUEFWtxoiVUtNI9KXsc,5517
@@ -73,7 +73,7 @@ agents/realtime/model_events.py,sha256=2NKofzLszKHwtlcsogsNnH6hdeFfO7S96yWDB4Alx
73
73
  agents/realtime/model_inputs.py,sha256=-pl8Oj0WVrA5Gt-dqP5Va3ZHqXyIXpsjMsf9UL-suEY,2789
74
74
  agents/realtime/openai_realtime.py,sha256=jN3OvcEQt9X-59t6InllkOOEd8Tdw69K5vuKfXBeObg,44763
75
75
  agents/realtime/runner.py,sha256=KfU7utmc9QFH2htIKN2IN9H-5EnB0qN9ezmvlRTnOm4,2511
76
- agents/realtime/session.py,sha256=gvEgRJHtQfVqyZkIB1JKcYyqXXjkhwZJ8rwsEgZMTeE,35225
76
+ agents/realtime/session.py,sha256=79WqKWwGOsutQRLs7fDsijE-OxEJjGm-aOpjL5F7Fn8,36983
77
77
  agents/tracing/__init__.py,sha256=5HO_6na5S6EwICgwl50OMtxiIIosUrqalhvldlYvSVc,2991
78
78
  agents/tracing/create.py,sha256=xpJ4ZRnGyUDPKoVVkA_8hmdhtwOKGhSkwRco2AQIhAo,18003
79
79
  agents/tracing/logger.py,sha256=J4KUDRSGa7x5UVfUwWe-gbKwoaq8AeETRqkPt3QvtGg,68
@@ -108,7 +108,7 @@ agents/voice/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
108
108
  agents/voice/models/openai_model_provider.py,sha256=Khn0uT-VhsEbe7_OhBMGFQzXNwL80gcWZyTHl3CaBII,3587
109
109
  agents/voice/models/openai_stt.py,sha256=Lb_F9160VNKDHXZ9zylSzeig7sB8lBjiYhQLDZsp6NQ,17257
110
110
  agents/voice/models/openai_tts.py,sha256=4KoLQuFDHKu5a1VTJlu9Nj3MHwMlrn9wfT_liJDJ2dw,1477
111
- openai_agents-0.4.1.dist-info/METADATA,sha256=zTH3g8bgaelHCJ_80grQkZvhJ5Zm3Lzz1BuAsG3xtzY,12929
112
- openai_agents-0.4.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
113
- openai_agents-0.4.1.dist-info/licenses/LICENSE,sha256=E994EspT7Krhy0qGiES7WYNzBHrh1YDk3r--8d1baRU,1063
114
- openai_agents-0.4.1.dist-info/RECORD,,
111
+ openai_agents-0.4.2.dist-info/METADATA,sha256=UUyVoFXNYwTLrBnkpo7MFwT73-kJH0rQX53xwF3pFXw,13046
112
+ openai_agents-0.4.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
113
+ openai_agents-0.4.2.dist-info/licenses/LICENSE,sha256=E994EspT7Krhy0qGiES7WYNzBHrh1YDk3r--8d1baRU,1063
114
+ openai_agents-0.4.2.dist-info/RECORD,,