openai-agents 0.2.8__py3-none-any.whl → 0.6.8__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.
- agents/__init__.py +105 -4
- agents/_debug.py +15 -4
- agents/_run_impl.py +1203 -96
- agents/agent.py +164 -19
- agents/apply_diff.py +329 -0
- agents/editor.py +47 -0
- agents/exceptions.py +35 -0
- agents/extensions/experimental/__init__.py +6 -0
- agents/extensions/experimental/codex/__init__.py +92 -0
- agents/extensions/experimental/codex/codex.py +89 -0
- agents/extensions/experimental/codex/codex_options.py +35 -0
- agents/extensions/experimental/codex/codex_tool.py +1142 -0
- agents/extensions/experimental/codex/events.py +162 -0
- agents/extensions/experimental/codex/exec.py +263 -0
- agents/extensions/experimental/codex/items.py +245 -0
- agents/extensions/experimental/codex/output_schema_file.py +50 -0
- agents/extensions/experimental/codex/payloads.py +31 -0
- agents/extensions/experimental/codex/thread.py +214 -0
- agents/extensions/experimental/codex/thread_options.py +54 -0
- agents/extensions/experimental/codex/turn_options.py +36 -0
- agents/extensions/handoff_filters.py +13 -1
- agents/extensions/memory/__init__.py +120 -0
- agents/extensions/memory/advanced_sqlite_session.py +1285 -0
- agents/extensions/memory/async_sqlite_session.py +239 -0
- agents/extensions/memory/dapr_session.py +423 -0
- agents/extensions/memory/encrypt_session.py +185 -0
- agents/extensions/memory/redis_session.py +261 -0
- agents/extensions/memory/sqlalchemy_session.py +334 -0
- agents/extensions/models/litellm_model.py +449 -36
- agents/extensions/models/litellm_provider.py +3 -1
- agents/function_schema.py +47 -5
- agents/guardrail.py +16 -2
- agents/{handoffs.py → handoffs/__init__.py} +89 -47
- agents/handoffs/history.py +268 -0
- agents/items.py +237 -11
- agents/lifecycle.py +75 -14
- agents/mcp/server.py +280 -37
- agents/mcp/util.py +24 -3
- agents/memory/__init__.py +22 -2
- agents/memory/openai_conversations_session.py +91 -0
- agents/memory/openai_responses_compaction_session.py +249 -0
- agents/memory/session.py +19 -261
- agents/memory/sqlite_session.py +275 -0
- agents/memory/util.py +20 -0
- agents/model_settings.py +14 -3
- agents/models/__init__.py +13 -0
- agents/models/chatcmpl_converter.py +303 -50
- agents/models/chatcmpl_helpers.py +63 -0
- agents/models/chatcmpl_stream_handler.py +290 -68
- agents/models/default_models.py +58 -0
- agents/models/interface.py +4 -0
- agents/models/openai_chatcompletions.py +103 -49
- agents/models/openai_provider.py +10 -4
- agents/models/openai_responses.py +162 -46
- agents/realtime/__init__.py +4 -0
- agents/realtime/_util.py +14 -3
- agents/realtime/agent.py +7 -0
- agents/realtime/audio_formats.py +53 -0
- agents/realtime/config.py +78 -10
- agents/realtime/events.py +18 -0
- agents/realtime/handoffs.py +2 -2
- agents/realtime/items.py +17 -1
- agents/realtime/model.py +13 -0
- agents/realtime/model_events.py +12 -0
- agents/realtime/model_inputs.py +18 -1
- agents/realtime/openai_realtime.py +696 -150
- agents/realtime/session.py +243 -23
- agents/repl.py +7 -3
- agents/result.py +197 -38
- agents/run.py +949 -168
- agents/run_context.py +13 -2
- agents/stream_events.py +1 -0
- agents/strict_schema.py +14 -0
- agents/tool.py +413 -15
- agents/tool_context.py +22 -1
- agents/tool_guardrails.py +279 -0
- agents/tracing/__init__.py +2 -0
- agents/tracing/config.py +9 -0
- agents/tracing/create.py +4 -0
- agents/tracing/processor_interface.py +84 -11
- agents/tracing/processors.py +65 -54
- agents/tracing/provider.py +64 -7
- agents/tracing/spans.py +105 -0
- agents/tracing/traces.py +116 -16
- agents/usage.py +134 -12
- agents/util/_json.py +19 -1
- agents/util/_transforms.py +12 -2
- agents/voice/input.py +5 -4
- agents/voice/models/openai_stt.py +17 -9
- agents/voice/pipeline.py +2 -0
- agents/voice/pipeline_config.py +4 -0
- {openai_agents-0.2.8.dist-info → openai_agents-0.6.8.dist-info}/METADATA +44 -19
- openai_agents-0.6.8.dist-info/RECORD +134 -0
- {openai_agents-0.2.8.dist-info → openai_agents-0.6.8.dist-info}/WHEEL +1 -1
- openai_agents-0.2.8.dist-info/RECORD +0 -103
- {openai_agents-0.2.8.dist-info → openai_agents-0.6.8.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from collections.abc import AsyncIterator
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Any
|
|
5
6
|
|
|
6
7
|
from openai import AsyncStream
|
|
7
8
|
from openai.types.chat import ChatCompletionChunk
|
|
@@ -28,14 +29,21 @@ from openai.types.responses import (
|
|
|
28
29
|
ResponseTextDeltaEvent,
|
|
29
30
|
ResponseUsage,
|
|
30
31
|
)
|
|
31
|
-
from openai.types.responses.response_reasoning_item import Summary
|
|
32
|
+
from openai.types.responses.response_reasoning_item import Content, Summary
|
|
32
33
|
from openai.types.responses.response_reasoning_summary_part_added_event import (
|
|
33
34
|
Part as AddedEventPart,
|
|
34
35
|
)
|
|
35
36
|
from openai.types.responses.response_reasoning_summary_part_done_event import Part as DoneEventPart
|
|
37
|
+
from openai.types.responses.response_reasoning_text_delta_event import (
|
|
38
|
+
ResponseReasoningTextDeltaEvent,
|
|
39
|
+
)
|
|
40
|
+
from openai.types.responses.response_reasoning_text_done_event import (
|
|
41
|
+
ResponseReasoningTextDoneEvent,
|
|
42
|
+
)
|
|
36
43
|
from openai.types.responses.response_usage import InputTokensDetails, OutputTokensDetails
|
|
37
44
|
|
|
38
45
|
from ..items import TResponseStreamEvent
|
|
46
|
+
from .chatcmpl_helpers import ChatCmplHelpers
|
|
39
47
|
from .fake_id import FAKE_RESPONSES_ID
|
|
40
48
|
|
|
41
49
|
|
|
@@ -56,6 +64,11 @@ class StreamingState:
|
|
|
56
64
|
# Fields for real-time function call streaming
|
|
57
65
|
function_call_streaming: dict[int, bool] = field(default_factory=dict)
|
|
58
66
|
function_call_output_idx: dict[int, int] = field(default_factory=dict)
|
|
67
|
+
# Store accumulated thinking text and signature for Anthropic compatibility
|
|
68
|
+
thinking_text: str = ""
|
|
69
|
+
thinking_signature: str | None = None
|
|
70
|
+
# Store provider data for all output items
|
|
71
|
+
provider_data: dict[str, Any] = field(default_factory=dict)
|
|
59
72
|
|
|
60
73
|
|
|
61
74
|
class SequenceNumber:
|
|
@@ -74,7 +87,17 @@ class ChatCmplStreamHandler:
|
|
|
74
87
|
cls,
|
|
75
88
|
response: Response,
|
|
76
89
|
stream: AsyncStream[ChatCompletionChunk],
|
|
90
|
+
model: str | None = None,
|
|
77
91
|
) -> AsyncIterator[TResponseStreamEvent]:
|
|
92
|
+
"""
|
|
93
|
+
Handle a streaming chat completion response and yield response events.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
response: The initial Response object to populate with streamed data
|
|
97
|
+
stream: The async stream of chat completion chunks from the model
|
|
98
|
+
model: The source model that is generating this stream. Used to handle
|
|
99
|
+
provider-specific stream processing.
|
|
100
|
+
"""
|
|
78
101
|
usage: CompletionUsage | None = None
|
|
79
102
|
state = StreamingState()
|
|
80
103
|
sequence_number = SequenceNumber()
|
|
@@ -88,31 +111,52 @@ class ChatCmplStreamHandler:
|
|
|
88
111
|
)
|
|
89
112
|
|
|
90
113
|
# This is always set by the OpenAI API, but not by others e.g. LiteLLM
|
|
91
|
-
|
|
114
|
+
# Only update when chunk has usage data (not always in the last chunk)
|
|
115
|
+
if hasattr(chunk, "usage") and chunk.usage is not None:
|
|
116
|
+
usage = chunk.usage
|
|
92
117
|
|
|
93
118
|
if not chunk.choices or not chunk.choices[0].delta:
|
|
94
119
|
continue
|
|
95
120
|
|
|
96
|
-
|
|
121
|
+
# Build provider_data for non-OpenAI Responses API endpoints format
|
|
122
|
+
if model:
|
|
123
|
+
state.provider_data["model"] = model
|
|
124
|
+
elif hasattr(chunk, "model") and chunk.model:
|
|
125
|
+
state.provider_data["model"] = chunk.model
|
|
126
|
+
|
|
127
|
+
if hasattr(chunk, "id") and chunk.id:
|
|
128
|
+
state.provider_data["response_id"] = chunk.id
|
|
97
129
|
|
|
98
|
-
|
|
130
|
+
delta = chunk.choices[0].delta
|
|
131
|
+
choice_logprobs = chunk.choices[0].logprobs
|
|
132
|
+
|
|
133
|
+
# Handle thinking blocks from Anthropic (for preserving signatures)
|
|
134
|
+
if hasattr(delta, "thinking_blocks") and delta.thinking_blocks:
|
|
135
|
+
for block in delta.thinking_blocks:
|
|
136
|
+
if isinstance(block, dict):
|
|
137
|
+
# Accumulate thinking text
|
|
138
|
+
thinking_text = block.get("thinking", "")
|
|
139
|
+
if thinking_text:
|
|
140
|
+
state.thinking_text += thinking_text
|
|
141
|
+
# Store signature if present
|
|
142
|
+
signature = block.get("signature")
|
|
143
|
+
if signature:
|
|
144
|
+
state.thinking_signature = signature
|
|
145
|
+
|
|
146
|
+
# Handle reasoning content for reasoning summaries
|
|
99
147
|
if hasattr(delta, "reasoning_content"):
|
|
100
148
|
reasoning_content = delta.reasoning_content
|
|
101
149
|
if reasoning_content and not state.reasoning_content_index_and_output:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
summary=[Summary(text="", type="summary_text")],
|
|
107
|
-
type="reasoning",
|
|
108
|
-
),
|
|
150
|
+
reasoning_item = ResponseReasoningItem(
|
|
151
|
+
id=FAKE_RESPONSES_ID,
|
|
152
|
+
summary=[Summary(text="", type="summary_text")],
|
|
153
|
+
type="reasoning",
|
|
109
154
|
)
|
|
155
|
+
if state.provider_data:
|
|
156
|
+
reasoning_item.provider_data = state.provider_data.copy() # type: ignore[attr-defined]
|
|
157
|
+
state.reasoning_content_index_and_output = (0, reasoning_item)
|
|
110
158
|
yield ResponseOutputItemAddedEvent(
|
|
111
|
-
item=
|
|
112
|
-
id=FAKE_RESPONSES_ID,
|
|
113
|
-
summary=[Summary(text="", type="summary_text")],
|
|
114
|
-
type="reasoning",
|
|
115
|
-
),
|
|
159
|
+
item=reasoning_item,
|
|
116
160
|
output_index=0,
|
|
117
161
|
type="response.output_item.added",
|
|
118
162
|
sequence_number=sequence_number.get_and_increment(),
|
|
@@ -128,6 +172,12 @@ class ChatCmplStreamHandler:
|
|
|
128
172
|
)
|
|
129
173
|
|
|
130
174
|
if reasoning_content and state.reasoning_content_index_and_output:
|
|
175
|
+
# Ensure summary list has at least one element
|
|
176
|
+
if not state.reasoning_content_index_and_output[1].summary:
|
|
177
|
+
state.reasoning_content_index_and_output[1].summary = [
|
|
178
|
+
Summary(text="", type="summary_text")
|
|
179
|
+
]
|
|
180
|
+
|
|
131
181
|
yield ResponseReasoningSummaryTextDeltaEvent(
|
|
132
182
|
delta=reasoning_content,
|
|
133
183
|
item_id=FAKE_RESPONSES_ID,
|
|
@@ -138,10 +188,50 @@ class ChatCmplStreamHandler:
|
|
|
138
188
|
)
|
|
139
189
|
|
|
140
190
|
# Create a new summary with updated text
|
|
141
|
-
|
|
142
|
-
updated_text =
|
|
143
|
-
|
|
144
|
-
state.reasoning_content_index_and_output[1].summary[0] =
|
|
191
|
+
current_content = state.reasoning_content_index_and_output[1].summary[0]
|
|
192
|
+
updated_text = current_content.text + reasoning_content
|
|
193
|
+
new_content = Summary(text=updated_text, type="summary_text")
|
|
194
|
+
state.reasoning_content_index_and_output[1].summary[0] = new_content
|
|
195
|
+
|
|
196
|
+
# Handle reasoning content from 3rd party platforms
|
|
197
|
+
if hasattr(delta, "reasoning"):
|
|
198
|
+
reasoning_text = delta.reasoning
|
|
199
|
+
if reasoning_text and not state.reasoning_content_index_and_output:
|
|
200
|
+
reasoning_item = ResponseReasoningItem(
|
|
201
|
+
id=FAKE_RESPONSES_ID,
|
|
202
|
+
summary=[],
|
|
203
|
+
content=[Content(text="", type="reasoning_text")],
|
|
204
|
+
type="reasoning",
|
|
205
|
+
)
|
|
206
|
+
if state.provider_data:
|
|
207
|
+
reasoning_item.provider_data = state.provider_data.copy() # type: ignore[attr-defined]
|
|
208
|
+
state.reasoning_content_index_and_output = (0, reasoning_item)
|
|
209
|
+
yield ResponseOutputItemAddedEvent(
|
|
210
|
+
item=reasoning_item,
|
|
211
|
+
output_index=0,
|
|
212
|
+
type="response.output_item.added",
|
|
213
|
+
sequence_number=sequence_number.get_and_increment(),
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
if reasoning_text and state.reasoning_content_index_and_output:
|
|
217
|
+
yield ResponseReasoningTextDeltaEvent(
|
|
218
|
+
delta=reasoning_text,
|
|
219
|
+
item_id=FAKE_RESPONSES_ID,
|
|
220
|
+
output_index=0,
|
|
221
|
+
content_index=0,
|
|
222
|
+
type="response.reasoning_text.delta",
|
|
223
|
+
sequence_number=sequence_number.get_and_increment(),
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
# Create a new summary with updated text
|
|
227
|
+
if not state.reasoning_content_index_and_output[1].content:
|
|
228
|
+
state.reasoning_content_index_and_output[1].content = [
|
|
229
|
+
Content(text="", type="reasoning_text")
|
|
230
|
+
]
|
|
231
|
+
current_text = state.reasoning_content_index_and_output[1].content[0]
|
|
232
|
+
updated_text = current_text.text + reasoning_text
|
|
233
|
+
new_text_content = Content(text=updated_text, type="reasoning_text")
|
|
234
|
+
state.reasoning_content_index_and_output[1].content[0] = new_text_content
|
|
145
235
|
|
|
146
236
|
# Handle regular content
|
|
147
237
|
if delta.content is not None:
|
|
@@ -158,6 +248,7 @@ class ChatCmplStreamHandler:
|
|
|
158
248
|
text="",
|
|
159
249
|
type="output_text",
|
|
160
250
|
annotations=[],
|
|
251
|
+
logprobs=[],
|
|
161
252
|
),
|
|
162
253
|
)
|
|
163
254
|
# Start a new assistant message stream
|
|
@@ -168,6 +259,8 @@ class ChatCmplStreamHandler:
|
|
|
168
259
|
type="message",
|
|
169
260
|
status="in_progress",
|
|
170
261
|
)
|
|
262
|
+
if state.provider_data:
|
|
263
|
+
assistant_item.provider_data = state.provider_data.copy() # type: ignore[attr-defined]
|
|
171
264
|
# Notify consumers of the start of a new output message + first content part
|
|
172
265
|
yield ResponseOutputItemAddedEvent(
|
|
173
266
|
item=assistant_item,
|
|
@@ -185,10 +278,20 @@ class ChatCmplStreamHandler:
|
|
|
185
278
|
text="",
|
|
186
279
|
type="output_text",
|
|
187
280
|
annotations=[],
|
|
281
|
+
logprobs=[],
|
|
188
282
|
),
|
|
189
283
|
type="response.content_part.added",
|
|
190
284
|
sequence_number=sequence_number.get_and_increment(),
|
|
191
285
|
)
|
|
286
|
+
delta_logprobs = (
|
|
287
|
+
ChatCmplHelpers.convert_logprobs_for_text_delta(
|
|
288
|
+
choice_logprobs.content if choice_logprobs else None
|
|
289
|
+
)
|
|
290
|
+
or []
|
|
291
|
+
)
|
|
292
|
+
output_logprobs = ChatCmplHelpers.convert_logprobs_for_output_text(
|
|
293
|
+
choice_logprobs.content if choice_logprobs else None
|
|
294
|
+
)
|
|
192
295
|
# Emit the delta for this segment of content
|
|
193
296
|
yield ResponseTextDeltaEvent(
|
|
194
297
|
content_index=state.text_content_index_and_output[0],
|
|
@@ -198,10 +301,15 @@ class ChatCmplStreamHandler:
|
|
|
198
301
|
is not None, # fixed 0 -> 0 or 1
|
|
199
302
|
type="response.output_text.delta",
|
|
200
303
|
sequence_number=sequence_number.get_and_increment(),
|
|
201
|
-
logprobs=
|
|
304
|
+
logprobs=delta_logprobs,
|
|
202
305
|
)
|
|
203
306
|
# Accumulate the text into the response part
|
|
204
307
|
state.text_content_index_and_output[1].text += delta.content
|
|
308
|
+
if output_logprobs:
|
|
309
|
+
existing_logprobs = state.text_content_index_and_output[1].logprobs or []
|
|
310
|
+
state.text_content_index_and_output[1].logprobs = (
|
|
311
|
+
existing_logprobs + output_logprobs
|
|
312
|
+
)
|
|
205
313
|
|
|
206
314
|
# Handle refusals (model declines to answer)
|
|
207
315
|
# This is always set by the OpenAI API, but not by others e.g. LiteLLM
|
|
@@ -225,6 +333,8 @@ class ChatCmplStreamHandler:
|
|
|
225
333
|
type="message",
|
|
226
334
|
status="in_progress",
|
|
227
335
|
)
|
|
336
|
+
if state.provider_data:
|
|
337
|
+
assistant_item.provider_data = state.provider_data.copy() # type: ignore[attr-defined]
|
|
228
338
|
# Notify downstream that assistant message + first content part are starting
|
|
229
339
|
yield ResponseOutputItemAddedEvent(
|
|
230
340
|
item=assistant_item,
|
|
@@ -236,12 +346,10 @@ class ChatCmplStreamHandler:
|
|
|
236
346
|
yield ResponseContentPartAddedEvent(
|
|
237
347
|
content_index=state.refusal_content_index_and_output[0],
|
|
238
348
|
item_id=FAKE_RESPONSES_ID,
|
|
239
|
-
output_index=state.reasoning_content_index_and_output
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
type="output_text",
|
|
244
|
-
annotations=[],
|
|
349
|
+
output_index=(1 if state.reasoning_content_index_and_output else 0),
|
|
350
|
+
part=ResponseOutputRefusal(
|
|
351
|
+
refusal="",
|
|
352
|
+
type="refusal",
|
|
245
353
|
),
|
|
246
354
|
type="response.content_part.added",
|
|
247
355
|
sequence_number=sequence_number.get_and_increment(),
|
|
@@ -284,7 +392,60 @@ class ChatCmplStreamHandler:
|
|
|
284
392
|
state.function_calls[tc_delta.index].name = tc_function.name
|
|
285
393
|
|
|
286
394
|
if tc_delta.id:
|
|
287
|
-
|
|
395
|
+
# Clean up litellm's addition of __thought__ suffix to tool_call.id for
|
|
396
|
+
# Gemini models. See: https://github.com/BerriAI/litellm/pull/16895
|
|
397
|
+
# This suffix is redundant since we can get thought_signature from
|
|
398
|
+
# provider_specific_fields, and this hack causes validation errors when
|
|
399
|
+
# cross-model passing to other models.
|
|
400
|
+
tool_call_id = tc_delta.id
|
|
401
|
+
if model and "gemini" in model.lower() and "__thought__" in tool_call_id:
|
|
402
|
+
tool_call_id = tool_call_id.split("__thought__")[0]
|
|
403
|
+
|
|
404
|
+
state.function_calls[tc_delta.index].call_id = tool_call_id
|
|
405
|
+
|
|
406
|
+
# Initialize provider_data for this function call from state.provider_data
|
|
407
|
+
if not hasattr(state.function_calls[tc_delta.index], "provider_data"):
|
|
408
|
+
if state.provider_data:
|
|
409
|
+
state.function_calls[
|
|
410
|
+
tc_delta.index
|
|
411
|
+
].provider_data = state.provider_data.copy() # type: ignore[attr-defined]
|
|
412
|
+
|
|
413
|
+
# Capture provider_specific_fields data from LiteLLM
|
|
414
|
+
if (
|
|
415
|
+
hasattr(tc_delta, "provider_specific_fields")
|
|
416
|
+
and tc_delta.provider_specific_fields
|
|
417
|
+
):
|
|
418
|
+
# Handle Gemini thought_signatures
|
|
419
|
+
if model and "gemini" in model.lower():
|
|
420
|
+
provider_specific_fields = tc_delta.provider_specific_fields
|
|
421
|
+
if isinstance(provider_specific_fields, dict):
|
|
422
|
+
thought_sig = provider_specific_fields.get("thought_signature")
|
|
423
|
+
if thought_sig:
|
|
424
|
+
# Start with state.provider_data, then add thought_signature
|
|
425
|
+
func_provider_data = (
|
|
426
|
+
state.provider_data.copy() if state.provider_data else {}
|
|
427
|
+
)
|
|
428
|
+
func_provider_data["thought_signature"] = thought_sig
|
|
429
|
+
state.function_calls[
|
|
430
|
+
tc_delta.index
|
|
431
|
+
].provider_data = func_provider_data # type: ignore[attr-defined]
|
|
432
|
+
|
|
433
|
+
# Capture extra_content data from Google's chatcmpl endpoint
|
|
434
|
+
if hasattr(tc_delta, "extra_content") and tc_delta.extra_content:
|
|
435
|
+
extra_content = tc_delta.extra_content
|
|
436
|
+
if isinstance(extra_content, dict):
|
|
437
|
+
google_fields = extra_content.get("google")
|
|
438
|
+
if google_fields and isinstance(google_fields, dict):
|
|
439
|
+
thought_sig = google_fields.get("thought_signature")
|
|
440
|
+
if thought_sig:
|
|
441
|
+
# Start with state.provider_data, then add thought_signature
|
|
442
|
+
func_provider_data = (
|
|
443
|
+
state.provider_data.copy() if state.provider_data else {}
|
|
444
|
+
)
|
|
445
|
+
func_provider_data["thought_signature"] = thought_sig
|
|
446
|
+
state.function_calls[
|
|
447
|
+
tc_delta.index
|
|
448
|
+
].provider_data = func_provider_data # type: ignore[attr-defined]
|
|
288
449
|
|
|
289
450
|
function_call = state.function_calls[tc_delta.index]
|
|
290
451
|
|
|
@@ -315,14 +476,28 @@ class ChatCmplStreamHandler:
|
|
|
315
476
|
)
|
|
316
477
|
|
|
317
478
|
# Send initial function call added event
|
|
479
|
+
func_call_item = ResponseFunctionToolCall(
|
|
480
|
+
id=FAKE_RESPONSES_ID,
|
|
481
|
+
call_id=function_call.call_id,
|
|
482
|
+
arguments="", # Start with empty arguments
|
|
483
|
+
name=function_call.name,
|
|
484
|
+
type="function_call",
|
|
485
|
+
)
|
|
486
|
+
# Merge provider_data from state and function_call (e.g. thought_signature)
|
|
487
|
+
if state.provider_data or (
|
|
488
|
+
hasattr(function_call, "provider_data") and function_call.provider_data
|
|
489
|
+
):
|
|
490
|
+
merged_provider_data = (
|
|
491
|
+
state.provider_data.copy() if state.provider_data else {}
|
|
492
|
+
)
|
|
493
|
+
if (
|
|
494
|
+
hasattr(function_call, "provider_data")
|
|
495
|
+
and function_call.provider_data
|
|
496
|
+
):
|
|
497
|
+
merged_provider_data.update(function_call.provider_data)
|
|
498
|
+
func_call_item.provider_data = merged_provider_data # type: ignore[attr-defined]
|
|
318
499
|
yield ResponseOutputItemAddedEvent(
|
|
319
|
-
item=
|
|
320
|
-
id=FAKE_RESPONSES_ID,
|
|
321
|
-
call_id=function_call.call_id,
|
|
322
|
-
arguments="", # Start with empty arguments
|
|
323
|
-
name=function_call.name,
|
|
324
|
-
type="function_call",
|
|
325
|
-
),
|
|
500
|
+
item=func_call_item,
|
|
326
501
|
output_index=function_call_starting_index,
|
|
327
502
|
type="response.output_item.added",
|
|
328
503
|
sequence_number=sequence_number.get_and_increment(),
|
|
@@ -344,17 +519,30 @@ class ChatCmplStreamHandler:
|
|
|
344
519
|
)
|
|
345
520
|
|
|
346
521
|
if state.reasoning_content_index_and_output:
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
522
|
+
if (
|
|
523
|
+
state.reasoning_content_index_and_output[1].summary
|
|
524
|
+
and len(state.reasoning_content_index_and_output[1].summary) > 0
|
|
525
|
+
):
|
|
526
|
+
yield ResponseReasoningSummaryPartDoneEvent(
|
|
527
|
+
item_id=FAKE_RESPONSES_ID,
|
|
528
|
+
output_index=0,
|
|
529
|
+
summary_index=0,
|
|
530
|
+
part=DoneEventPart(
|
|
531
|
+
text=state.reasoning_content_index_and_output[1].summary[0].text,
|
|
532
|
+
type="summary_text",
|
|
533
|
+
),
|
|
534
|
+
type="response.reasoning_summary_part.done",
|
|
535
|
+
sequence_number=sequence_number.get_and_increment(),
|
|
536
|
+
)
|
|
537
|
+
elif state.reasoning_content_index_and_output[1].content is not None:
|
|
538
|
+
yield ResponseReasoningTextDoneEvent(
|
|
539
|
+
item_id=FAKE_RESPONSES_ID,
|
|
540
|
+
output_index=0,
|
|
541
|
+
content_index=0,
|
|
542
|
+
text=state.reasoning_content_index_and_output[1].content[0].text,
|
|
543
|
+
type="response.reasoning_text.done",
|
|
544
|
+
sequence_number=sequence_number.get_and_increment(),
|
|
545
|
+
)
|
|
358
546
|
yield ResponseOutputItemDoneEvent(
|
|
359
547
|
item=state.reasoning_content_index_and_output[1],
|
|
360
548
|
output_index=0,
|
|
@@ -397,14 +585,27 @@ class ChatCmplStreamHandler:
|
|
|
397
585
|
if state.function_call_streaming.get(index, False):
|
|
398
586
|
# Function call was streamed, just send the completion event
|
|
399
587
|
output_index = state.function_call_output_idx[index]
|
|
588
|
+
|
|
589
|
+
# Build function call kwargs, include provider_data if present
|
|
590
|
+
func_call_kwargs: dict[str, Any] = {
|
|
591
|
+
"id": FAKE_RESPONSES_ID,
|
|
592
|
+
"call_id": function_call.call_id,
|
|
593
|
+
"arguments": function_call.arguments,
|
|
594
|
+
"name": function_call.name,
|
|
595
|
+
"type": "function_call",
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
# Merge provider_data from state and function_call (e.g. thought_signature)
|
|
599
|
+
if state.provider_data or (
|
|
600
|
+
hasattr(function_call, "provider_data") and function_call.provider_data
|
|
601
|
+
):
|
|
602
|
+
merged_provider_data = state.provider_data.copy() if state.provider_data else {}
|
|
603
|
+
if hasattr(function_call, "provider_data") and function_call.provider_data:
|
|
604
|
+
merged_provider_data.update(function_call.provider_data)
|
|
605
|
+
func_call_kwargs["provider_data"] = merged_provider_data
|
|
606
|
+
|
|
400
607
|
yield ResponseOutputItemDoneEvent(
|
|
401
|
-
item=ResponseFunctionToolCall(
|
|
402
|
-
id=FAKE_RESPONSES_ID,
|
|
403
|
-
call_id=function_call.call_id,
|
|
404
|
-
arguments=function_call.arguments,
|
|
405
|
-
name=function_call.name,
|
|
406
|
-
type="function_call",
|
|
407
|
-
),
|
|
608
|
+
item=ResponseFunctionToolCall(**func_call_kwargs),
|
|
408
609
|
output_index=output_index,
|
|
409
610
|
type="response.output_item.done",
|
|
410
611
|
sequence_number=sequence_number.get_and_increment(),
|
|
@@ -425,15 +626,27 @@ class ChatCmplStreamHandler:
|
|
|
425
626
|
1 for streaming in state.function_call_streaming.values() if streaming
|
|
426
627
|
)
|
|
427
628
|
|
|
629
|
+
# Build function call kwargs, include provider_data if present
|
|
630
|
+
fallback_func_call_kwargs: dict[str, Any] = {
|
|
631
|
+
"id": FAKE_RESPONSES_ID,
|
|
632
|
+
"call_id": function_call.call_id,
|
|
633
|
+
"arguments": function_call.arguments,
|
|
634
|
+
"name": function_call.name,
|
|
635
|
+
"type": "function_call",
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
# Merge provider_data from state and function_call (e.g. thought_signature)
|
|
639
|
+
if state.provider_data or (
|
|
640
|
+
hasattr(function_call, "provider_data") and function_call.provider_data
|
|
641
|
+
):
|
|
642
|
+
merged_provider_data = state.provider_data.copy() if state.provider_data else {}
|
|
643
|
+
if hasattr(function_call, "provider_data") and function_call.provider_data:
|
|
644
|
+
merged_provider_data.update(function_call.provider_data)
|
|
645
|
+
fallback_func_call_kwargs["provider_data"] = merged_provider_data
|
|
646
|
+
|
|
428
647
|
# Send all events at once (backward compatibility)
|
|
429
648
|
yield ResponseOutputItemAddedEvent(
|
|
430
|
-
item=ResponseFunctionToolCall(
|
|
431
|
-
id=FAKE_RESPONSES_ID,
|
|
432
|
-
call_id=function_call.call_id,
|
|
433
|
-
arguments=function_call.arguments,
|
|
434
|
-
name=function_call.name,
|
|
435
|
-
type="function_call",
|
|
436
|
-
),
|
|
649
|
+
item=ResponseFunctionToolCall(**fallback_func_call_kwargs),
|
|
437
650
|
output_index=fallback_starting_index,
|
|
438
651
|
type="response.output_item.added",
|
|
439
652
|
sequence_number=sequence_number.get_and_increment(),
|
|
@@ -446,13 +659,7 @@ class ChatCmplStreamHandler:
|
|
|
446
659
|
sequence_number=sequence_number.get_and_increment(),
|
|
447
660
|
)
|
|
448
661
|
yield ResponseOutputItemDoneEvent(
|
|
449
|
-
item=ResponseFunctionToolCall(
|
|
450
|
-
id=FAKE_RESPONSES_ID,
|
|
451
|
-
call_id=function_call.call_id,
|
|
452
|
-
arguments=function_call.arguments,
|
|
453
|
-
name=function_call.name,
|
|
454
|
-
type="function_call",
|
|
455
|
-
),
|
|
662
|
+
item=ResponseFunctionToolCall(**fallback_func_call_kwargs),
|
|
456
663
|
output_index=fallback_starting_index,
|
|
457
664
|
type="response.output_item.done",
|
|
458
665
|
sequence_number=sequence_number.get_and_increment(),
|
|
@@ -463,7 +670,19 @@ class ChatCmplStreamHandler:
|
|
|
463
670
|
|
|
464
671
|
# include Reasoning item if it exists
|
|
465
672
|
if state.reasoning_content_index_and_output:
|
|
466
|
-
|
|
673
|
+
reasoning_item = state.reasoning_content_index_and_output[1]
|
|
674
|
+
# Store thinking text in content and signature in encrypted_content
|
|
675
|
+
if state.thinking_text:
|
|
676
|
+
# Add thinking text as a Content object
|
|
677
|
+
if not reasoning_item.content:
|
|
678
|
+
reasoning_item.content = []
|
|
679
|
+
reasoning_item.content.append(
|
|
680
|
+
Content(text=state.thinking_text, type="reasoning_text")
|
|
681
|
+
)
|
|
682
|
+
# Store signature in encrypted_content
|
|
683
|
+
if state.thinking_signature:
|
|
684
|
+
reasoning_item.encrypted_content = state.thinking_signature
|
|
685
|
+
outputs.append(reasoning_item)
|
|
467
686
|
|
|
468
687
|
# include text or refusal content if they exist
|
|
469
688
|
if state.text_content_index_and_output or state.refusal_content_index_and_output:
|
|
@@ -474,6 +693,8 @@ class ChatCmplStreamHandler:
|
|
|
474
693
|
type="message",
|
|
475
694
|
status="completed",
|
|
476
695
|
)
|
|
696
|
+
if state.provider_data:
|
|
697
|
+
assistant_msg.provider_data = state.provider_data.copy() # type: ignore[attr-defined]
|
|
477
698
|
if state.text_content_index_and_output:
|
|
478
699
|
assistant_msg.content.append(state.text_content_index_and_output[1])
|
|
479
700
|
if state.refusal_content_index_and_output:
|
|
@@ -494,6 +715,7 @@ class ChatCmplStreamHandler:
|
|
|
494
715
|
|
|
495
716
|
final_response = response.model_copy()
|
|
496
717
|
final_response.output = outputs
|
|
718
|
+
|
|
497
719
|
final_response.usage = (
|
|
498
720
|
ResponseUsage(
|
|
499
721
|
input_tokens=usage.prompt_tokens or 0,
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import os
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from openai.types.shared.reasoning import Reasoning
|
|
6
|
+
|
|
7
|
+
from agents.model_settings import ModelSettings
|
|
8
|
+
|
|
9
|
+
OPENAI_DEFAULT_MODEL_ENV_VARIABLE_NAME = "OPENAI_DEFAULT_MODEL"
|
|
10
|
+
|
|
11
|
+
# discourage directly accessing this constant
|
|
12
|
+
# use the get_default_model and get_default_model_settings() functions instead
|
|
13
|
+
_GPT_5_DEFAULT_MODEL_SETTINGS: ModelSettings = ModelSettings(
|
|
14
|
+
# We chose "low" instead of "minimal" because some of the built-in tools
|
|
15
|
+
# (e.g., file search, image generation, etc.) do not support "minimal"
|
|
16
|
+
# If you want to use "minimal" reasoning effort, you can pass your own model settings
|
|
17
|
+
reasoning=Reasoning(effort="low"),
|
|
18
|
+
verbosity="low",
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def gpt_5_reasoning_settings_required(model_name: str) -> bool:
|
|
23
|
+
"""
|
|
24
|
+
Returns True if the model name is a GPT-5 model and reasoning settings are required.
|
|
25
|
+
"""
|
|
26
|
+
if model_name.startswith("gpt-5-chat"):
|
|
27
|
+
# gpt-5-chat-latest does not require reasoning settings
|
|
28
|
+
return False
|
|
29
|
+
# matches any of gpt-5 models
|
|
30
|
+
return model_name.startswith("gpt-5")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def is_gpt_5_default() -> bool:
|
|
34
|
+
"""
|
|
35
|
+
Returns True if the default model is a GPT-5 model.
|
|
36
|
+
This is used to determine if the default model settings are compatible with GPT-5 models.
|
|
37
|
+
If the default model is not a GPT-5 model, the model settings are compatible with other models.
|
|
38
|
+
"""
|
|
39
|
+
return gpt_5_reasoning_settings_required(get_default_model())
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_default_model() -> str:
|
|
43
|
+
"""
|
|
44
|
+
Returns the default model name.
|
|
45
|
+
"""
|
|
46
|
+
return os.getenv(OPENAI_DEFAULT_MODEL_ENV_VARIABLE_NAME, "gpt-4.1").lower()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def get_default_model_settings(model: Optional[str] = None) -> ModelSettings:
|
|
50
|
+
"""
|
|
51
|
+
Returns the default model settings.
|
|
52
|
+
If the default model is a GPT-5 model, returns the GPT-5 default model settings.
|
|
53
|
+
Otherwise, returns the legacy default model settings.
|
|
54
|
+
"""
|
|
55
|
+
_model = model if model is not None else get_default_model()
|
|
56
|
+
if gpt_5_reasoning_settings_required(_model):
|
|
57
|
+
return copy.deepcopy(_GPT_5_DEFAULT_MODEL_SETTINGS)
|
|
58
|
+
return ModelSettings()
|
agents/models/interface.py
CHANGED
|
@@ -48,6 +48,7 @@ class Model(abc.ABC):
|
|
|
48
48
|
tracing: ModelTracing,
|
|
49
49
|
*,
|
|
50
50
|
previous_response_id: str | None,
|
|
51
|
+
conversation_id: str | None,
|
|
51
52
|
prompt: ResponsePromptParam | None,
|
|
52
53
|
) -> ModelResponse:
|
|
53
54
|
"""Get a response from the model.
|
|
@@ -62,6 +63,7 @@ class Model(abc.ABC):
|
|
|
62
63
|
tracing: Tracing configuration.
|
|
63
64
|
previous_response_id: the ID of the previous response. Generally not used by the model,
|
|
64
65
|
except for the OpenAI Responses API.
|
|
66
|
+
conversation_id: The ID of the stored conversation, if any.
|
|
65
67
|
prompt: The prompt config to use for the model.
|
|
66
68
|
|
|
67
69
|
Returns:
|
|
@@ -81,6 +83,7 @@ class Model(abc.ABC):
|
|
|
81
83
|
tracing: ModelTracing,
|
|
82
84
|
*,
|
|
83
85
|
previous_response_id: str | None,
|
|
86
|
+
conversation_id: str | None,
|
|
84
87
|
prompt: ResponsePromptParam | None,
|
|
85
88
|
) -> AsyncIterator[TResponseStreamEvent]:
|
|
86
89
|
"""Stream a response from the model.
|
|
@@ -95,6 +98,7 @@ class Model(abc.ABC):
|
|
|
95
98
|
tracing: Tracing configuration.
|
|
96
99
|
previous_response_id: the ID of the previous response. Generally not used by the model,
|
|
97
100
|
except for the OpenAI Responses API.
|
|
101
|
+
conversation_id: The ID of the stored conversation, if any.
|
|
98
102
|
prompt: The prompt config to use for the model.
|
|
99
103
|
|
|
100
104
|
Returns:
|