openai-agents 0.2.7__py3-none-any.whl → 0.2.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/items.py +1 -2
- agents/realtime/openai_realtime.py +4 -1
- agents/realtime/session.py +54 -2
- agents/run.py +105 -11
- {openai_agents-0.2.7.dist-info → openai_agents-0.2.8.dist-info}/METADATA +1 -1
- {openai_agents-0.2.7.dist-info → openai_agents-0.2.8.dist-info}/RECORD +8 -8
- {openai_agents-0.2.7.dist-info → openai_agents-0.2.8.dist-info}/WHEEL +0 -0
- {openai_agents-0.2.7.dist-info → openai_agents-0.2.8.dist-info}/licenses/LICENSE +0 -0
agents/items.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import abc
|
|
4
|
-
import copy
|
|
5
4
|
from dataclasses import dataclass
|
|
6
5
|
from typing import TYPE_CHECKING, Any, Generic, Literal, TypeVar, Union
|
|
7
6
|
|
|
@@ -277,7 +276,7 @@ class ItemHelpers:
|
|
|
277
276
|
"role": "user",
|
|
278
277
|
}
|
|
279
278
|
]
|
|
280
|
-
return copy
|
|
279
|
+
return input.copy()
|
|
281
280
|
|
|
282
281
|
@classmethod
|
|
283
282
|
def text_message_outputs(cls, items: list[RunItem]) -> str:
|
|
@@ -170,7 +170,10 @@ class OpenAIRealtimeWebSocketModel(RealtimeModel):
|
|
|
170
170
|
"OpenAI-Beta": "realtime=v1",
|
|
171
171
|
}
|
|
172
172
|
self._websocket = await websockets.connect(
|
|
173
|
-
url,
|
|
173
|
+
url,
|
|
174
|
+
user_agent_header=_USER_AGENT,
|
|
175
|
+
additional_headers=headers,
|
|
176
|
+
max_size=None, # Allow any size of message
|
|
174
177
|
)
|
|
175
178
|
self._websocket_task = asyncio.create_task(self._listen_for_messages())
|
|
176
179
|
await self._update_session_config(model_settings)
|
agents/realtime/session.py
CHANGED
|
@@ -10,6 +10,7 @@ from typing_extensions import assert_never
|
|
|
10
10
|
from ..agent import Agent
|
|
11
11
|
from ..exceptions import ModelBehaviorError, UserError
|
|
12
12
|
from ..handoffs import Handoff
|
|
13
|
+
from ..logger import logger
|
|
13
14
|
from ..run_context import RunContextWrapper, TContext
|
|
14
15
|
from ..tool import FunctionTool
|
|
15
16
|
from ..tool_context import ToolContext
|
|
@@ -33,7 +34,7 @@ from .events import (
|
|
|
33
34
|
RealtimeToolStart,
|
|
34
35
|
)
|
|
35
36
|
from .handoffs import realtime_handoff
|
|
36
|
-
from .items import InputAudio, InputText, RealtimeItem
|
|
37
|
+
from .items import AssistantAudio, InputAudio, InputText, RealtimeItem
|
|
37
38
|
from .model import RealtimeModel, RealtimeModelConfig, RealtimeModelListener
|
|
38
39
|
from .model_events import (
|
|
39
40
|
RealtimeModelEvent,
|
|
@@ -246,7 +247,58 @@ class RealtimeSession(RealtimeModelListener):
|
|
|
246
247
|
self._enqueue_guardrail_task(self._item_transcripts[item_id], event.response_id)
|
|
247
248
|
elif event.type == "item_updated":
|
|
248
249
|
is_new = not any(item.item_id == event.item.item_id for item in self._history)
|
|
249
|
-
|
|
250
|
+
|
|
251
|
+
# Preserve previously known transcripts when updating existing items.
|
|
252
|
+
# This prevents transcripts from disappearing when an item is later
|
|
253
|
+
# retrieved without transcript fields populated.
|
|
254
|
+
incoming_item = event.item
|
|
255
|
+
existing_item = next(
|
|
256
|
+
(i for i in self._history if i.item_id == incoming_item.item_id), None
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
if (
|
|
260
|
+
existing_item is not None
|
|
261
|
+
and existing_item.type == "message"
|
|
262
|
+
and incoming_item.type == "message"
|
|
263
|
+
):
|
|
264
|
+
try:
|
|
265
|
+
# Merge transcripts for matching content indices
|
|
266
|
+
existing_content = existing_item.content
|
|
267
|
+
new_content = []
|
|
268
|
+
for idx, entry in enumerate(incoming_item.content):
|
|
269
|
+
# Only attempt to preserve for audio-like content
|
|
270
|
+
if entry.type in ("audio", "input_audio"):
|
|
271
|
+
# Use tuple form for Python 3.9 compatibility
|
|
272
|
+
assert isinstance(entry, (InputAudio, AssistantAudio))
|
|
273
|
+
# Determine if transcript is missing/empty on the incoming entry
|
|
274
|
+
entry_transcript = entry.transcript
|
|
275
|
+
if not entry_transcript:
|
|
276
|
+
preserved: str | None = None
|
|
277
|
+
# First prefer any transcript from the existing history item
|
|
278
|
+
if idx < len(existing_content):
|
|
279
|
+
this_content = existing_content[idx]
|
|
280
|
+
if isinstance(this_content, AssistantAudio) or isinstance(
|
|
281
|
+
this_content, InputAudio
|
|
282
|
+
):
|
|
283
|
+
preserved = this_content.transcript
|
|
284
|
+
|
|
285
|
+
# If still missing and this is an assistant item, fall back to
|
|
286
|
+
# accumulated transcript deltas tracked during the turn.
|
|
287
|
+
if not preserved and incoming_item.role == "assistant":
|
|
288
|
+
preserved = self._item_transcripts.get(incoming_item.item_id)
|
|
289
|
+
|
|
290
|
+
if preserved:
|
|
291
|
+
entry = entry.model_copy(update={"transcript": preserved})
|
|
292
|
+
|
|
293
|
+
new_content.append(entry)
|
|
294
|
+
|
|
295
|
+
if new_content:
|
|
296
|
+
incoming_item = incoming_item.model_copy(update={"content": new_content})
|
|
297
|
+
except Exception:
|
|
298
|
+
logger.error("Error merging transcripts", exc_info=True)
|
|
299
|
+
pass
|
|
300
|
+
|
|
301
|
+
self._history = self._get_new_history(self._history, incoming_item)
|
|
250
302
|
if is_new:
|
|
251
303
|
new_item = next(
|
|
252
304
|
item for item in self._history if item.item_id == event.item.item_id
|
agents/run.py
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
-
import copy
|
|
5
4
|
import inspect
|
|
6
5
|
from dataclasses import dataclass, field
|
|
7
|
-
from typing import Any, Generic, cast
|
|
6
|
+
from typing import Any, Callable, Generic, cast
|
|
8
7
|
|
|
9
8
|
from openai.types.responses import ResponseCompletedEvent
|
|
10
9
|
from openai.types.responses.response_prompt_param import (
|
|
@@ -56,6 +55,7 @@ from .tracing import Span, SpanError, agent_span, get_current_trace, trace
|
|
|
56
55
|
from .tracing.span_data import AgentSpanData
|
|
57
56
|
from .usage import Usage
|
|
58
57
|
from .util import _coro, _error_tracing
|
|
58
|
+
from .util._types import MaybeAwaitable
|
|
59
59
|
|
|
60
60
|
DEFAULT_MAX_TURNS = 10
|
|
61
61
|
|
|
@@ -81,6 +81,27 @@ def get_default_agent_runner() -> AgentRunner:
|
|
|
81
81
|
return DEFAULT_AGENT_RUNNER
|
|
82
82
|
|
|
83
83
|
|
|
84
|
+
@dataclass
|
|
85
|
+
class ModelInputData:
|
|
86
|
+
"""Container for the data that will be sent to the model."""
|
|
87
|
+
|
|
88
|
+
input: list[TResponseInputItem]
|
|
89
|
+
instructions: str | None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass
|
|
93
|
+
class CallModelData(Generic[TContext]):
|
|
94
|
+
"""Data passed to `RunConfig.call_model_input_filter` prior to model call."""
|
|
95
|
+
|
|
96
|
+
model_data: ModelInputData
|
|
97
|
+
agent: Agent[TContext]
|
|
98
|
+
context: TContext | None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# Type alias for the optional input filter callback
|
|
102
|
+
CallModelInputFilter = Callable[[CallModelData[Any]], MaybeAwaitable[ModelInputData]]
|
|
103
|
+
|
|
104
|
+
|
|
84
105
|
@dataclass
|
|
85
106
|
class RunConfig:
|
|
86
107
|
"""Configures settings for the entire agent run."""
|
|
@@ -139,6 +160,16 @@ class RunConfig:
|
|
|
139
160
|
An optional dictionary of additional metadata to include with the trace.
|
|
140
161
|
"""
|
|
141
162
|
|
|
163
|
+
call_model_input_filter: CallModelInputFilter | None = None
|
|
164
|
+
"""
|
|
165
|
+
Optional callback that is invoked immediately before calling the model. It receives the current
|
|
166
|
+
agent, context and the model input (instructions and input items), and must return a possibly
|
|
167
|
+
modified `ModelInputData` to use for the model call.
|
|
168
|
+
|
|
169
|
+
This allows you to edit the input sent to the model e.g. to stay within a token limit.
|
|
170
|
+
For example, you can use this to add a system prompt to the input.
|
|
171
|
+
"""
|
|
172
|
+
|
|
142
173
|
|
|
143
174
|
class RunOptions(TypedDict, Generic[TContext]):
|
|
144
175
|
"""Arguments for ``AgentRunner`` methods."""
|
|
@@ -355,7 +386,7 @@ class AgentRunner:
|
|
|
355
386
|
disabled=run_config.tracing_disabled,
|
|
356
387
|
):
|
|
357
388
|
current_turn = 0
|
|
358
|
-
original_input: str | list[TResponseInputItem] =
|
|
389
|
+
original_input: str | list[TResponseInputItem] = _copy_str_or_list(prepared_input)
|
|
359
390
|
generated_items: list[RunItem] = []
|
|
360
391
|
model_responses: list[ModelResponse] = []
|
|
361
392
|
|
|
@@ -414,7 +445,7 @@ class AgentRunner:
|
|
|
414
445
|
starting_agent,
|
|
415
446
|
starting_agent.input_guardrails
|
|
416
447
|
+ (run_config.input_guardrails or []),
|
|
417
|
-
|
|
448
|
+
_copy_str_or_list(prepared_input),
|
|
418
449
|
context_wrapper,
|
|
419
450
|
),
|
|
420
451
|
self._run_single_turn(
|
|
@@ -562,7 +593,7 @@ class AgentRunner:
|
|
|
562
593
|
)
|
|
563
594
|
|
|
564
595
|
streamed_result = RunResultStreaming(
|
|
565
|
-
input=
|
|
596
|
+
input=_copy_str_or_list(input),
|
|
566
597
|
new_items=[],
|
|
567
598
|
current_agent=starting_agent,
|
|
568
599
|
raw_responses=[],
|
|
@@ -593,6 +624,47 @@ class AgentRunner:
|
|
|
593
624
|
)
|
|
594
625
|
return streamed_result
|
|
595
626
|
|
|
627
|
+
@classmethod
|
|
628
|
+
async def _maybe_filter_model_input(
|
|
629
|
+
cls,
|
|
630
|
+
*,
|
|
631
|
+
agent: Agent[TContext],
|
|
632
|
+
run_config: RunConfig,
|
|
633
|
+
context_wrapper: RunContextWrapper[TContext],
|
|
634
|
+
input_items: list[TResponseInputItem],
|
|
635
|
+
system_instructions: str | None,
|
|
636
|
+
) -> ModelInputData:
|
|
637
|
+
"""Apply optional call_model_input_filter to modify model input.
|
|
638
|
+
|
|
639
|
+
Returns a `ModelInputData` that will be sent to the model.
|
|
640
|
+
"""
|
|
641
|
+
effective_instructions = system_instructions
|
|
642
|
+
effective_input: list[TResponseInputItem] = input_items
|
|
643
|
+
|
|
644
|
+
if run_config.call_model_input_filter is None:
|
|
645
|
+
return ModelInputData(input=effective_input, instructions=effective_instructions)
|
|
646
|
+
|
|
647
|
+
try:
|
|
648
|
+
model_input = ModelInputData(
|
|
649
|
+
input=effective_input.copy(),
|
|
650
|
+
instructions=effective_instructions,
|
|
651
|
+
)
|
|
652
|
+
filter_payload: CallModelData[TContext] = CallModelData(
|
|
653
|
+
model_data=model_input,
|
|
654
|
+
agent=agent,
|
|
655
|
+
context=context_wrapper.context,
|
|
656
|
+
)
|
|
657
|
+
maybe_updated = run_config.call_model_input_filter(filter_payload)
|
|
658
|
+
updated = await maybe_updated if inspect.isawaitable(maybe_updated) else maybe_updated
|
|
659
|
+
if not isinstance(updated, ModelInputData):
|
|
660
|
+
raise UserError("call_model_input_filter must return a ModelInputData instance")
|
|
661
|
+
return updated
|
|
662
|
+
except Exception as e:
|
|
663
|
+
_error_tracing.attach_error_to_current_span(
|
|
664
|
+
SpanError(message="Error in call_model_input_filter", data={"error": str(e)})
|
|
665
|
+
)
|
|
666
|
+
raise
|
|
667
|
+
|
|
596
668
|
@classmethod
|
|
597
669
|
async def _run_input_guardrails_with_queue(
|
|
598
670
|
cls,
|
|
@@ -713,7 +785,7 @@ class AgentRunner:
|
|
|
713
785
|
cls._run_input_guardrails_with_queue(
|
|
714
786
|
starting_agent,
|
|
715
787
|
starting_agent.input_guardrails + (run_config.input_guardrails or []),
|
|
716
|
-
|
|
788
|
+
ItemHelpers.input_to_new_input_list(prepared_input),
|
|
717
789
|
context_wrapper,
|
|
718
790
|
streamed_result,
|
|
719
791
|
current_span,
|
|
@@ -863,10 +935,18 @@ class AgentRunner:
|
|
|
863
935
|
input = ItemHelpers.input_to_new_input_list(streamed_result.input)
|
|
864
936
|
input.extend([item.to_input_item() for item in streamed_result.new_items])
|
|
865
937
|
|
|
938
|
+
filtered = await cls._maybe_filter_model_input(
|
|
939
|
+
agent=agent,
|
|
940
|
+
run_config=run_config,
|
|
941
|
+
context_wrapper=context_wrapper,
|
|
942
|
+
input_items=input,
|
|
943
|
+
system_instructions=system_prompt,
|
|
944
|
+
)
|
|
945
|
+
|
|
866
946
|
# 1. Stream the output events
|
|
867
947
|
async for event in model.stream_response(
|
|
868
|
-
|
|
869
|
-
input,
|
|
948
|
+
filtered.instructions,
|
|
949
|
+
filtered.input,
|
|
870
950
|
model_settings,
|
|
871
951
|
all_tools,
|
|
872
952
|
output_schema,
|
|
@@ -1034,7 +1114,6 @@ class AgentRunner:
|
|
|
1034
1114
|
run_config: RunConfig,
|
|
1035
1115
|
tool_use_tracker: AgentToolUseTracker,
|
|
1036
1116
|
) -> SingleStepResult:
|
|
1037
|
-
|
|
1038
1117
|
original_input = streamed_result.input
|
|
1039
1118
|
pre_step_items = streamed_result.new_items
|
|
1040
1119
|
event_queue = streamed_result._event_queue
|
|
@@ -1161,13 +1240,22 @@ class AgentRunner:
|
|
|
1161
1240
|
previous_response_id: str | None,
|
|
1162
1241
|
prompt_config: ResponsePromptParam | None,
|
|
1163
1242
|
) -> ModelResponse:
|
|
1243
|
+
# Allow user to modify model input right before the call, if configured
|
|
1244
|
+
filtered = await cls._maybe_filter_model_input(
|
|
1245
|
+
agent=agent,
|
|
1246
|
+
run_config=run_config,
|
|
1247
|
+
context_wrapper=context_wrapper,
|
|
1248
|
+
input_items=input,
|
|
1249
|
+
system_instructions=system_prompt,
|
|
1250
|
+
)
|
|
1251
|
+
|
|
1164
1252
|
model = cls._get_model(agent, run_config)
|
|
1165
1253
|
model_settings = agent.model_settings.resolve(run_config.model_settings)
|
|
1166
1254
|
model_settings = RunImpl.maybe_reset_tool_choice(agent, tool_use_tracker, model_settings)
|
|
1167
1255
|
|
|
1168
1256
|
new_response = await model.get_response(
|
|
1169
|
-
system_instructions=
|
|
1170
|
-
input=input,
|
|
1257
|
+
system_instructions=filtered.instructions,
|
|
1258
|
+
input=filtered.input,
|
|
1171
1259
|
model_settings=model_settings,
|
|
1172
1260
|
tools=all_tools,
|
|
1173
1261
|
output_schema=output_schema,
|
|
@@ -1287,3 +1375,9 @@ class AgentRunner:
|
|
|
1287
1375
|
|
|
1288
1376
|
|
|
1289
1377
|
DEFAULT_AGENT_RUNNER = AgentRunner()
|
|
1378
|
+
|
|
1379
|
+
|
|
1380
|
+
def _copy_str_or_list(input: str | list[TResponseInputItem]) -> str | list[TResponseInputItem]:
|
|
1381
|
+
if isinstance(input, str):
|
|
1382
|
+
return input
|
|
1383
|
+
return input.copy()
|
|
@@ -9,7 +9,7 @@ agents/exceptions.py,sha256=NHMdHE0cZ6AdA6UgUylTzVHAX05Ol1CkO814a0FdZcs,2862
|
|
|
9
9
|
agents/function_schema.py,sha256=yZ3PEOmfy836Me_W4QlItMeFq2j4BtpuI2FmQswbIcQ,13590
|
|
10
10
|
agents/guardrail.py,sha256=7P-kd9rKPhgB8rtI31MCV5ho4ZrEaNCQxHvE8IK3EOk,9582
|
|
11
11
|
agents/handoffs.py,sha256=31-rQ-iMWlWNd93ivgTTSMGkqlariXrNfWI_udMWt7s,11409
|
|
12
|
-
agents/items.py,sha256=
|
|
12
|
+
agents/items.py,sha256=aHo7KTXZLBcHSrKHWDaBB6L7XmBCAIekG5e0xOIhkyM,9828
|
|
13
13
|
agents/lifecycle.py,sha256=sJwESHBHbml7rSYH360-P6x1bLyENcQWm4bT4rQcbuo,3129
|
|
14
14
|
agents/logger.py,sha256=p_ef7vWKpBev5FFybPJjhrCCQizK08Yy1A2EDO1SNNg,60
|
|
15
15
|
agents/model_settings.py,sha256=7zGEGxfXtRHlst9qYngYJc5mkr2l_mi5YuQDGiQ-qXM,6485
|
|
@@ -17,7 +17,7 @@ agents/prompts.py,sha256=Ss5y_7s2HFcRAOAKu4WTxQszs5ybI8TfbxgEYdnj9sg,2231
|
|
|
17
17
|
agents/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
18
18
|
agents/repl.py,sha256=FKZlkGfw6QxItTkjFkCAQwXuV_pn69DIamGd3PiKQFk,2361
|
|
19
19
|
agents/result.py,sha256=YCGYHoc5X1_vLKu5QiK6F8C1ZXI3tTfLXaZoqbYgUMA,10753
|
|
20
|
-
agents/run.py,sha256=
|
|
20
|
+
agents/run.py,sha256=lPp-nZWKA0gg0E2ace94zwFo-FmQK7Fj_cXcrvAoFlQ,55978
|
|
21
21
|
agents/run_context.py,sha256=vuSUQM8O4CLensQY27-22fOqECnw7yvwL9U3WO8b_bk,851
|
|
22
22
|
agents/stream_events.py,sha256=VFyTu-DT3ZMnHLtMbg-X_lxec0doQxNfx-hVxLB0BpI,1700
|
|
23
23
|
agents/strict_schema.py,sha256=_KuEJkglmq-Fj3HSeYP4WqTvqrxbSKu6gezfz5Brhh0,5775
|
|
@@ -60,9 +60,9 @@ agents/realtime/items.py,sha256=psT6AH65qmngmPsgwk6CXacVo5tEDYq0Za3EitHFpTA,5052
|
|
|
60
60
|
agents/realtime/model.py,sha256=RJBA8-Dkd2JTqGzbKacoX4dN_qTWn_p7npL73To3ymw,6143
|
|
61
61
|
agents/realtime/model_events.py,sha256=X7UrUU_g4u5gWaf2mUesJJ-Ik1Z1QE0Z-ZP7kDmX1t0,4034
|
|
62
62
|
agents/realtime/model_inputs.py,sha256=OW2bn3wD5_pXLunDUf35jhG2q_bTKbC_D7Qu-83aOEA,2243
|
|
63
|
-
agents/realtime/openai_realtime.py,sha256=
|
|
63
|
+
agents/realtime/openai_realtime.py,sha256=20yHhG-KAGeXY0M0ucty0wpRqXSUVLvoL5cs663NGrI,30201
|
|
64
64
|
agents/realtime/runner.py,sha256=KfU7utmc9QFH2htIKN2IN9H-5EnB0qN9ezmvlRTnOm4,2511
|
|
65
|
-
agents/realtime/session.py,sha256=
|
|
65
|
+
agents/realtime/session.py,sha256=aGifsl_4LYGZBwXneaOo-H_46fdQs-CAtQD6DmJY2Uo,26560
|
|
66
66
|
agents/tracing/__init__.py,sha256=5HO_6na5S6EwICgwl50OMtxiIIosUrqalhvldlYvSVc,2991
|
|
67
67
|
agents/tracing/create.py,sha256=xpJ4ZRnGyUDPKoVVkA_8hmdhtwOKGhSkwRco2AQIhAo,18003
|
|
68
68
|
agents/tracing/logger.py,sha256=J4KUDRSGa7x5UVfUwWe-gbKwoaq8AeETRqkPt3QvtGg,68
|
|
@@ -97,7 +97,7 @@ agents/voice/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
|
|
|
97
97
|
agents/voice/models/openai_model_provider.py,sha256=Khn0uT-VhsEbe7_OhBMGFQzXNwL80gcWZyTHl3CaBII,3587
|
|
98
98
|
agents/voice/models/openai_stt.py,sha256=LcVDS7f1pmbm--PWX-IaV9uLg9uv5_L3vSCbVnTJeGs,16864
|
|
99
99
|
agents/voice/models/openai_tts.py,sha256=4KoLQuFDHKu5a1VTJlu9Nj3MHwMlrn9wfT_liJDJ2dw,1477
|
|
100
|
-
openai_agents-0.2.
|
|
101
|
-
openai_agents-0.2.
|
|
102
|
-
openai_agents-0.2.
|
|
103
|
-
openai_agents-0.2.
|
|
100
|
+
openai_agents-0.2.8.dist-info/METADATA,sha256=MqNemwQZlvUlnyqVHU1Bxor4liYOsBa4VcE_UJllEas,12104
|
|
101
|
+
openai_agents-0.2.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
102
|
+
openai_agents-0.2.8.dist-info/licenses/LICENSE,sha256=E994EspT7Krhy0qGiES7WYNzBHrh1YDk3r--8d1baRU,1063
|
|
103
|
+
openai_agents-0.2.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|