openai-agents 0.0.11__py3-none-any.whl → 0.0.13__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/__init__.py +2 -1
- agents/_run_impl.py +3 -3
- agents/agent.py +9 -2
- agents/agent_output.py +58 -8
- agents/extensions/models/__init__.py +0 -0
- agents/extensions/models/litellm_model.py +381 -0
- agents/extensions/models/litellm_provider.py +21 -0
- agents/extensions/visualization.py +1 -1
- agents/mcp/server.py +24 -5
- agents/model_settings.py +21 -2
- agents/models/chatcmpl_converter.py +466 -0
- agents/models/chatcmpl_helpers.py +37 -0
- agents/models/chatcmpl_stream_handler.py +292 -0
- agents/models/interface.py +3 -3
- agents/models/multi_provider.py +144 -0
- agents/models/openai_chatcompletions.py +35 -781
- agents/models/openai_responses.py +9 -9
- agents/result.py +24 -9
- agents/run.py +16 -13
- agents/tracing/processors.py +6 -0
- agents/tracing/span_data.py +1 -1
- {openai_agents-0.0.11.dist-info → openai_agents-0.0.13.dist-info}/METADATA +5 -5
- {openai_agents-0.0.11.dist-info → openai_agents-0.0.13.dist-info}/RECORD +25 -18
- {openai_agents-0.0.11.dist-info → openai_agents-0.0.13.dist-info}/WHEEL +0 -0
- {openai_agents-0.0.11.dist-info → openai_agents-0.0.13.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,73 +1,28 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import dataclasses
|
|
4
3
|
import json
|
|
5
4
|
import time
|
|
6
|
-
from collections.abc import AsyncIterator
|
|
7
|
-
from dataclasses import dataclass, field
|
|
5
|
+
from collections.abc import AsyncIterator
|
|
8
6
|
from typing import TYPE_CHECKING, Any, Literal, cast, overload
|
|
9
7
|
|
|
10
|
-
from openai import NOT_GIVEN, AsyncOpenAI, AsyncStream
|
|
8
|
+
from openai import NOT_GIVEN, AsyncOpenAI, AsyncStream
|
|
11
9
|
from openai.types import ChatModel
|
|
12
|
-
from openai.types.chat import
|
|
13
|
-
|
|
14
|
-
ChatCompletionAssistantMessageParam,
|
|
15
|
-
ChatCompletionChunk,
|
|
16
|
-
ChatCompletionContentPartImageParam,
|
|
17
|
-
ChatCompletionContentPartParam,
|
|
18
|
-
ChatCompletionContentPartTextParam,
|
|
19
|
-
ChatCompletionDeveloperMessageParam,
|
|
20
|
-
ChatCompletionMessage,
|
|
21
|
-
ChatCompletionMessageParam,
|
|
22
|
-
ChatCompletionMessageToolCallParam,
|
|
23
|
-
ChatCompletionSystemMessageParam,
|
|
24
|
-
ChatCompletionToolChoiceOptionParam,
|
|
25
|
-
ChatCompletionToolMessageParam,
|
|
26
|
-
ChatCompletionUserMessageParam,
|
|
27
|
-
)
|
|
28
|
-
from openai.types.chat.chat_completion_tool_param import ChatCompletionToolParam
|
|
29
|
-
from openai.types.chat.completion_create_params import ResponseFormat
|
|
30
|
-
from openai.types.completion_usage import CompletionUsage
|
|
31
|
-
from openai.types.responses import (
|
|
32
|
-
EasyInputMessageParam,
|
|
33
|
-
Response,
|
|
34
|
-
ResponseCompletedEvent,
|
|
35
|
-
ResponseContentPartAddedEvent,
|
|
36
|
-
ResponseContentPartDoneEvent,
|
|
37
|
-
ResponseCreatedEvent,
|
|
38
|
-
ResponseFileSearchToolCallParam,
|
|
39
|
-
ResponseFunctionCallArgumentsDeltaEvent,
|
|
40
|
-
ResponseFunctionToolCall,
|
|
41
|
-
ResponseFunctionToolCallParam,
|
|
42
|
-
ResponseInputContentParam,
|
|
43
|
-
ResponseInputImageParam,
|
|
44
|
-
ResponseInputTextParam,
|
|
45
|
-
ResponseOutputItem,
|
|
46
|
-
ResponseOutputItemAddedEvent,
|
|
47
|
-
ResponseOutputItemDoneEvent,
|
|
48
|
-
ResponseOutputMessage,
|
|
49
|
-
ResponseOutputMessageParam,
|
|
50
|
-
ResponseOutputRefusal,
|
|
51
|
-
ResponseOutputText,
|
|
52
|
-
ResponseRefusalDeltaEvent,
|
|
53
|
-
ResponseTextDeltaEvent,
|
|
54
|
-
ResponseUsage,
|
|
55
|
-
)
|
|
56
|
-
from openai.types.responses.response_input_param import FunctionCallOutput, ItemReference, Message
|
|
57
|
-
from openai.types.responses.response_usage import InputTokensDetails, OutputTokensDetails
|
|
10
|
+
from openai.types.chat import ChatCompletion, ChatCompletionChunk
|
|
11
|
+
from openai.types.responses import Response
|
|
58
12
|
|
|
59
13
|
from .. import _debug
|
|
60
|
-
from ..agent_output import
|
|
61
|
-
from ..exceptions import AgentsException, UserError
|
|
14
|
+
from ..agent_output import AgentOutputSchemaBase
|
|
62
15
|
from ..handoffs import Handoff
|
|
63
|
-
from ..items import ModelResponse, TResponseInputItem,
|
|
16
|
+
from ..items import ModelResponse, TResponseInputItem, TResponseStreamEvent
|
|
64
17
|
from ..logger import logger
|
|
65
|
-
from ..tool import
|
|
18
|
+
from ..tool import Tool
|
|
66
19
|
from ..tracing import generation_span
|
|
67
20
|
from ..tracing.span_data import GenerationSpanData
|
|
68
21
|
from ..tracing.spans import Span
|
|
69
22
|
from ..usage import Usage
|
|
70
|
-
from
|
|
23
|
+
from .chatcmpl_converter import Converter
|
|
24
|
+
from .chatcmpl_helpers import HEADERS, ChatCmplHelpers
|
|
25
|
+
from .chatcmpl_stream_handler import ChatCmplStreamHandler
|
|
71
26
|
from .fake_id import FAKE_RESPONSES_ID
|
|
72
27
|
from .interface import Model, ModelTracing
|
|
73
28
|
|
|
@@ -75,18 +30,6 @@ if TYPE_CHECKING:
|
|
|
75
30
|
from ..model_settings import ModelSettings
|
|
76
31
|
|
|
77
32
|
|
|
78
|
-
_USER_AGENT = f"Agents/Python {__version__}"
|
|
79
|
-
_HEADERS = {"User-Agent": _USER_AGENT}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
@dataclass
|
|
83
|
-
class _StreamingState:
|
|
84
|
-
started: bool = False
|
|
85
|
-
text_content_index_and_output: tuple[int, ResponseOutputText] | None = None
|
|
86
|
-
refusal_content_index_and_output: tuple[int, ResponseOutputRefusal] | None = None
|
|
87
|
-
function_calls: dict[int, ResponseFunctionToolCall] = field(default_factory=dict)
|
|
88
|
-
|
|
89
|
-
|
|
90
33
|
class OpenAIChatCompletionsModel(Model):
|
|
91
34
|
def __init__(
|
|
92
35
|
self,
|
|
@@ -105,15 +48,14 @@ class OpenAIChatCompletionsModel(Model):
|
|
|
105
48
|
input: str | list[TResponseInputItem],
|
|
106
49
|
model_settings: ModelSettings,
|
|
107
50
|
tools: list[Tool],
|
|
108
|
-
output_schema:
|
|
51
|
+
output_schema: AgentOutputSchemaBase | None,
|
|
109
52
|
handoffs: list[Handoff],
|
|
110
53
|
tracing: ModelTracing,
|
|
111
54
|
previous_response_id: str | None,
|
|
112
55
|
) -> ModelResponse:
|
|
113
56
|
with generation_span(
|
|
114
57
|
model=str(self.model),
|
|
115
|
-
model_config=
|
|
116
|
-
| {"base_url": str(self._client.base_url)},
|
|
58
|
+
model_config=model_settings.to_json_dict() | {"base_url": str(self._client.base_url)},
|
|
117
59
|
disabled=tracing.is_disabled(),
|
|
118
60
|
) as span_generation:
|
|
119
61
|
response = await self._fetch_response(
|
|
@@ -152,7 +94,7 @@ class OpenAIChatCompletionsModel(Model):
|
|
|
152
94
|
"output_tokens": usage.output_tokens,
|
|
153
95
|
}
|
|
154
96
|
|
|
155
|
-
items =
|
|
97
|
+
items = Converter.message_to_output_items(response.choices[0].message)
|
|
156
98
|
|
|
157
99
|
return ModelResponse(
|
|
158
100
|
output=items,
|
|
@@ -166,7 +108,7 @@ class OpenAIChatCompletionsModel(Model):
|
|
|
166
108
|
input: str | list[TResponseInputItem],
|
|
167
109
|
model_settings: ModelSettings,
|
|
168
110
|
tools: list[Tool],
|
|
169
|
-
output_schema:
|
|
111
|
+
output_schema: AgentOutputSchemaBase | None,
|
|
170
112
|
handoffs: list[Handoff],
|
|
171
113
|
tracing: ModelTracing,
|
|
172
114
|
*,
|
|
@@ -177,8 +119,7 @@ class OpenAIChatCompletionsModel(Model):
|
|
|
177
119
|
"""
|
|
178
120
|
with generation_span(
|
|
179
121
|
model=str(self.model),
|
|
180
|
-
model_config=
|
|
181
|
-
| {"base_url": str(self._client.base_url)},
|
|
122
|
+
model_config=model_settings.to_json_dict() | {"base_url": str(self._client.base_url)},
|
|
182
123
|
disabled=tracing.is_disabled(),
|
|
183
124
|
) as span_generation:
|
|
184
125
|
response, stream = await self._fetch_response(
|
|
@@ -193,257 +134,20 @@ class OpenAIChatCompletionsModel(Model):
|
|
|
193
134
|
stream=True,
|
|
194
135
|
)
|
|
195
136
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
async for chunk in stream:
|
|
200
|
-
if not state.started:
|
|
201
|
-
state.started = True
|
|
202
|
-
yield ResponseCreatedEvent(
|
|
203
|
-
response=response,
|
|
204
|
-
type="response.created",
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
# The usage is only available in the last chunk
|
|
208
|
-
usage = chunk.usage
|
|
209
|
-
|
|
210
|
-
if not chunk.choices or not chunk.choices[0].delta:
|
|
211
|
-
continue
|
|
212
|
-
|
|
213
|
-
delta = chunk.choices[0].delta
|
|
214
|
-
|
|
215
|
-
# Handle text
|
|
216
|
-
if delta.content:
|
|
217
|
-
if not state.text_content_index_and_output:
|
|
218
|
-
# Initialize a content tracker for streaming text
|
|
219
|
-
state.text_content_index_and_output = (
|
|
220
|
-
0 if not state.refusal_content_index_and_output else 1,
|
|
221
|
-
ResponseOutputText(
|
|
222
|
-
text="",
|
|
223
|
-
type="output_text",
|
|
224
|
-
annotations=[],
|
|
225
|
-
),
|
|
226
|
-
)
|
|
227
|
-
# Start a new assistant message stream
|
|
228
|
-
assistant_item = ResponseOutputMessage(
|
|
229
|
-
id=FAKE_RESPONSES_ID,
|
|
230
|
-
content=[],
|
|
231
|
-
role="assistant",
|
|
232
|
-
type="message",
|
|
233
|
-
status="in_progress",
|
|
234
|
-
)
|
|
235
|
-
# Notify consumers of the start of a new output message + first content part
|
|
236
|
-
yield ResponseOutputItemAddedEvent(
|
|
237
|
-
item=assistant_item,
|
|
238
|
-
output_index=0,
|
|
239
|
-
type="response.output_item.added",
|
|
240
|
-
)
|
|
241
|
-
yield ResponseContentPartAddedEvent(
|
|
242
|
-
content_index=state.text_content_index_and_output[0],
|
|
243
|
-
item_id=FAKE_RESPONSES_ID,
|
|
244
|
-
output_index=0,
|
|
245
|
-
part=ResponseOutputText(
|
|
246
|
-
text="",
|
|
247
|
-
type="output_text",
|
|
248
|
-
annotations=[],
|
|
249
|
-
),
|
|
250
|
-
type="response.content_part.added",
|
|
251
|
-
)
|
|
252
|
-
# Emit the delta for this segment of content
|
|
253
|
-
yield ResponseTextDeltaEvent(
|
|
254
|
-
content_index=state.text_content_index_and_output[0],
|
|
255
|
-
delta=delta.content,
|
|
256
|
-
item_id=FAKE_RESPONSES_ID,
|
|
257
|
-
output_index=0,
|
|
258
|
-
type="response.output_text.delta",
|
|
259
|
-
)
|
|
260
|
-
# Accumulate the text into the response part
|
|
261
|
-
state.text_content_index_and_output[1].text += delta.content
|
|
262
|
-
|
|
263
|
-
# Handle refusals (model declines to answer)
|
|
264
|
-
if delta.refusal:
|
|
265
|
-
if not state.refusal_content_index_and_output:
|
|
266
|
-
# Initialize a content tracker for streaming refusal text
|
|
267
|
-
state.refusal_content_index_and_output = (
|
|
268
|
-
0 if not state.text_content_index_and_output else 1,
|
|
269
|
-
ResponseOutputRefusal(refusal="", type="refusal"),
|
|
270
|
-
)
|
|
271
|
-
# Start a new assistant message if one doesn't exist yet (in-progress)
|
|
272
|
-
assistant_item = ResponseOutputMessage(
|
|
273
|
-
id=FAKE_RESPONSES_ID,
|
|
274
|
-
content=[],
|
|
275
|
-
role="assistant",
|
|
276
|
-
type="message",
|
|
277
|
-
status="in_progress",
|
|
278
|
-
)
|
|
279
|
-
# Notify downstream that assistant message + first content part are starting
|
|
280
|
-
yield ResponseOutputItemAddedEvent(
|
|
281
|
-
item=assistant_item,
|
|
282
|
-
output_index=0,
|
|
283
|
-
type="response.output_item.added",
|
|
284
|
-
)
|
|
285
|
-
yield ResponseContentPartAddedEvent(
|
|
286
|
-
content_index=state.refusal_content_index_and_output[0],
|
|
287
|
-
item_id=FAKE_RESPONSES_ID,
|
|
288
|
-
output_index=0,
|
|
289
|
-
part=ResponseOutputText(
|
|
290
|
-
text="",
|
|
291
|
-
type="output_text",
|
|
292
|
-
annotations=[],
|
|
293
|
-
),
|
|
294
|
-
type="response.content_part.added",
|
|
295
|
-
)
|
|
296
|
-
# Emit the delta for this segment of refusal
|
|
297
|
-
yield ResponseRefusalDeltaEvent(
|
|
298
|
-
content_index=state.refusal_content_index_and_output[0],
|
|
299
|
-
delta=delta.refusal,
|
|
300
|
-
item_id=FAKE_RESPONSES_ID,
|
|
301
|
-
output_index=0,
|
|
302
|
-
type="response.refusal.delta",
|
|
303
|
-
)
|
|
304
|
-
# Accumulate the refusal string in the output part
|
|
305
|
-
state.refusal_content_index_and_output[1].refusal += delta.refusal
|
|
306
|
-
|
|
307
|
-
# Handle tool calls
|
|
308
|
-
# Because we don't know the name of the function until the end of the stream, we'll
|
|
309
|
-
# save everything and yield events at the end
|
|
310
|
-
if delta.tool_calls:
|
|
311
|
-
for tc_delta in delta.tool_calls:
|
|
312
|
-
if tc_delta.index not in state.function_calls:
|
|
313
|
-
state.function_calls[tc_delta.index] = ResponseFunctionToolCall(
|
|
314
|
-
id=FAKE_RESPONSES_ID,
|
|
315
|
-
arguments="",
|
|
316
|
-
name="",
|
|
317
|
-
type="function_call",
|
|
318
|
-
call_id="",
|
|
319
|
-
)
|
|
320
|
-
tc_function = tc_delta.function
|
|
321
|
-
|
|
322
|
-
state.function_calls[tc_delta.index].arguments += (
|
|
323
|
-
tc_function.arguments if tc_function else ""
|
|
324
|
-
) or ""
|
|
325
|
-
state.function_calls[tc_delta.index].name += (
|
|
326
|
-
tc_function.name if tc_function else ""
|
|
327
|
-
) or ""
|
|
328
|
-
state.function_calls[tc_delta.index].call_id += tc_delta.id or ""
|
|
329
|
-
|
|
330
|
-
function_call_starting_index = 0
|
|
331
|
-
if state.text_content_index_and_output:
|
|
332
|
-
function_call_starting_index += 1
|
|
333
|
-
# Send end event for this content part
|
|
334
|
-
yield ResponseContentPartDoneEvent(
|
|
335
|
-
content_index=state.text_content_index_and_output[0],
|
|
336
|
-
item_id=FAKE_RESPONSES_ID,
|
|
337
|
-
output_index=0,
|
|
338
|
-
part=state.text_content_index_and_output[1],
|
|
339
|
-
type="response.content_part.done",
|
|
340
|
-
)
|
|
341
|
-
|
|
342
|
-
if state.refusal_content_index_and_output:
|
|
343
|
-
function_call_starting_index += 1
|
|
344
|
-
# Send end event for this content part
|
|
345
|
-
yield ResponseContentPartDoneEvent(
|
|
346
|
-
content_index=state.refusal_content_index_and_output[0],
|
|
347
|
-
item_id=FAKE_RESPONSES_ID,
|
|
348
|
-
output_index=0,
|
|
349
|
-
part=state.refusal_content_index_and_output[1],
|
|
350
|
-
type="response.content_part.done",
|
|
351
|
-
)
|
|
352
|
-
|
|
353
|
-
# Actually send events for the function calls
|
|
354
|
-
for function_call in state.function_calls.values():
|
|
355
|
-
# First, a ResponseOutputItemAdded for the function call
|
|
356
|
-
yield ResponseOutputItemAddedEvent(
|
|
357
|
-
item=ResponseFunctionToolCall(
|
|
358
|
-
id=FAKE_RESPONSES_ID,
|
|
359
|
-
call_id=function_call.call_id,
|
|
360
|
-
arguments=function_call.arguments,
|
|
361
|
-
name=function_call.name,
|
|
362
|
-
type="function_call",
|
|
363
|
-
),
|
|
364
|
-
output_index=function_call_starting_index,
|
|
365
|
-
type="response.output_item.added",
|
|
366
|
-
)
|
|
367
|
-
# Then, yield the args
|
|
368
|
-
yield ResponseFunctionCallArgumentsDeltaEvent(
|
|
369
|
-
delta=function_call.arguments,
|
|
370
|
-
item_id=FAKE_RESPONSES_ID,
|
|
371
|
-
output_index=function_call_starting_index,
|
|
372
|
-
type="response.function_call_arguments.delta",
|
|
373
|
-
)
|
|
374
|
-
# Finally, the ResponseOutputItemDone
|
|
375
|
-
yield ResponseOutputItemDoneEvent(
|
|
376
|
-
item=ResponseFunctionToolCall(
|
|
377
|
-
id=FAKE_RESPONSES_ID,
|
|
378
|
-
call_id=function_call.call_id,
|
|
379
|
-
arguments=function_call.arguments,
|
|
380
|
-
name=function_call.name,
|
|
381
|
-
type="function_call",
|
|
382
|
-
),
|
|
383
|
-
output_index=function_call_starting_index,
|
|
384
|
-
type="response.output_item.done",
|
|
385
|
-
)
|
|
137
|
+
final_response: Response | None = None
|
|
138
|
+
async for chunk in ChatCmplStreamHandler.handle_stream(response, stream):
|
|
139
|
+
yield chunk
|
|
386
140
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
if state.text_content_index_and_output or state.refusal_content_index_and_output:
|
|
390
|
-
assistant_msg = ResponseOutputMessage(
|
|
391
|
-
id=FAKE_RESPONSES_ID,
|
|
392
|
-
content=[],
|
|
393
|
-
role="assistant",
|
|
394
|
-
type="message",
|
|
395
|
-
status="completed",
|
|
396
|
-
)
|
|
397
|
-
if state.text_content_index_and_output:
|
|
398
|
-
assistant_msg.content.append(state.text_content_index_and_output[1])
|
|
399
|
-
if state.refusal_content_index_and_output:
|
|
400
|
-
assistant_msg.content.append(state.refusal_content_index_and_output[1])
|
|
401
|
-
outputs.append(assistant_msg)
|
|
402
|
-
|
|
403
|
-
# send a ResponseOutputItemDone for the assistant message
|
|
404
|
-
yield ResponseOutputItemDoneEvent(
|
|
405
|
-
item=assistant_msg,
|
|
406
|
-
output_index=0,
|
|
407
|
-
type="response.output_item.done",
|
|
408
|
-
)
|
|
409
|
-
|
|
410
|
-
for function_call in state.function_calls.values():
|
|
411
|
-
outputs.append(function_call)
|
|
412
|
-
|
|
413
|
-
final_response = response.model_copy()
|
|
414
|
-
final_response.output = outputs
|
|
415
|
-
final_response.usage = (
|
|
416
|
-
ResponseUsage(
|
|
417
|
-
input_tokens=usage.prompt_tokens,
|
|
418
|
-
output_tokens=usage.completion_tokens,
|
|
419
|
-
total_tokens=usage.total_tokens,
|
|
420
|
-
output_tokens_details=OutputTokensDetails(
|
|
421
|
-
reasoning_tokens=usage.completion_tokens_details.reasoning_tokens
|
|
422
|
-
if usage.completion_tokens_details
|
|
423
|
-
and usage.completion_tokens_details.reasoning_tokens
|
|
424
|
-
else 0
|
|
425
|
-
),
|
|
426
|
-
input_tokens_details=InputTokensDetails(
|
|
427
|
-
cached_tokens=usage.prompt_tokens_details.cached_tokens
|
|
428
|
-
if usage.prompt_tokens_details and usage.prompt_tokens_details.cached_tokens
|
|
429
|
-
else 0
|
|
430
|
-
),
|
|
431
|
-
)
|
|
432
|
-
if usage
|
|
433
|
-
else None
|
|
434
|
-
)
|
|
141
|
+
if chunk.type == "response.completed":
|
|
142
|
+
final_response = chunk.response
|
|
435
143
|
|
|
436
|
-
|
|
437
|
-
response=final_response,
|
|
438
|
-
type="response.completed",
|
|
439
|
-
)
|
|
440
|
-
if tracing.include_data():
|
|
144
|
+
if tracing.include_data() and final_response:
|
|
441
145
|
span_generation.span_data.output = [final_response.model_dump()]
|
|
442
146
|
|
|
443
|
-
if usage:
|
|
147
|
+
if final_response and final_response.usage:
|
|
444
148
|
span_generation.span_data.usage = {
|
|
445
|
-
"input_tokens": usage.
|
|
446
|
-
"output_tokens": usage.
|
|
149
|
+
"input_tokens": final_response.usage.input_tokens,
|
|
150
|
+
"output_tokens": final_response.usage.output_tokens,
|
|
447
151
|
}
|
|
448
152
|
|
|
449
153
|
@overload
|
|
@@ -453,7 +157,7 @@ class OpenAIChatCompletionsModel(Model):
|
|
|
453
157
|
input: str | list[TResponseInputItem],
|
|
454
158
|
model_settings: ModelSettings,
|
|
455
159
|
tools: list[Tool],
|
|
456
|
-
output_schema:
|
|
160
|
+
output_schema: AgentOutputSchemaBase | None,
|
|
457
161
|
handoffs: list[Handoff],
|
|
458
162
|
span: Span[GenerationSpanData],
|
|
459
163
|
tracing: ModelTracing,
|
|
@@ -467,7 +171,7 @@ class OpenAIChatCompletionsModel(Model):
|
|
|
467
171
|
input: str | list[TResponseInputItem],
|
|
468
172
|
model_settings: ModelSettings,
|
|
469
173
|
tools: list[Tool],
|
|
470
|
-
output_schema:
|
|
174
|
+
output_schema: AgentOutputSchemaBase | None,
|
|
471
175
|
handoffs: list[Handoff],
|
|
472
176
|
span: Span[GenerationSpanData],
|
|
473
177
|
tracing: ModelTracing,
|
|
@@ -480,13 +184,13 @@ class OpenAIChatCompletionsModel(Model):
|
|
|
480
184
|
input: str | list[TResponseInputItem],
|
|
481
185
|
model_settings: ModelSettings,
|
|
482
186
|
tools: list[Tool],
|
|
483
|
-
output_schema:
|
|
187
|
+
output_schema: AgentOutputSchemaBase | None,
|
|
484
188
|
handoffs: list[Handoff],
|
|
485
189
|
span: Span[GenerationSpanData],
|
|
486
190
|
tracing: ModelTracing,
|
|
487
191
|
stream: bool = False,
|
|
488
192
|
) -> ChatCompletion | tuple[Response, AsyncStream[ChatCompletionChunk]]:
|
|
489
|
-
converted_messages =
|
|
193
|
+
converted_messages = Converter.items_to_messages(input)
|
|
490
194
|
|
|
491
195
|
if system_instructions:
|
|
492
196
|
converted_messages.insert(
|
|
@@ -506,13 +210,13 @@ class OpenAIChatCompletionsModel(Model):
|
|
|
506
210
|
if model_settings.parallel_tool_calls is False
|
|
507
211
|
else NOT_GIVEN
|
|
508
212
|
)
|
|
509
|
-
tool_choice =
|
|
510
|
-
response_format =
|
|
213
|
+
tool_choice = Converter.convert_tool_choice(model_settings.tool_choice)
|
|
214
|
+
response_format = Converter.convert_response_format(output_schema)
|
|
511
215
|
|
|
512
|
-
converted_tools = [
|
|
216
|
+
converted_tools = [Converter.tool_to_openai(tool) for tool in tools] if tools else []
|
|
513
217
|
|
|
514
218
|
for handoff in handoffs:
|
|
515
|
-
converted_tools.append(
|
|
219
|
+
converted_tools.append(Converter.convert_handoff_tool(handoff))
|
|
516
220
|
|
|
517
221
|
if _debug.DONT_LOG_MODEL_DATA:
|
|
518
222
|
logger.debug("Calling LLM")
|
|
@@ -526,9 +230,9 @@ class OpenAIChatCompletionsModel(Model):
|
|
|
526
230
|
)
|
|
527
231
|
|
|
528
232
|
reasoning_effort = model_settings.reasoning.effort if model_settings.reasoning else None
|
|
529
|
-
store =
|
|
233
|
+
store = ChatCmplHelpers.get_store_param(self._get_client(), model_settings)
|
|
530
234
|
|
|
531
|
-
stream_options =
|
|
235
|
+
stream_options = ChatCmplHelpers.get_stream_options_param(
|
|
532
236
|
self._get_client(), model_settings, stream=stream
|
|
533
237
|
)
|
|
534
238
|
|
|
@@ -548,7 +252,7 @@ class OpenAIChatCompletionsModel(Model):
|
|
|
548
252
|
stream_options=self._non_null_or_not_given(stream_options),
|
|
549
253
|
store=self._non_null_or_not_given(store),
|
|
550
254
|
reasoning_effort=self._non_null_or_not_given(reasoning_effort),
|
|
551
|
-
extra_headers=
|
|
255
|
+
extra_headers={ **HEADERS, **(model_settings.extra_headers or {}) },
|
|
552
256
|
extra_query=model_settings.extra_query,
|
|
553
257
|
extra_body=model_settings.extra_body,
|
|
554
258
|
metadata=self._non_null_or_not_given(model_settings.metadata),
|
|
@@ -578,453 +282,3 @@ class OpenAIChatCompletionsModel(Model):
|
|
|
578
282
|
if self._client is None:
|
|
579
283
|
self._client = AsyncOpenAI()
|
|
580
284
|
return self._client
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
class _Converter:
|
|
584
|
-
@classmethod
|
|
585
|
-
def is_openai(cls, client: AsyncOpenAI):
|
|
586
|
-
return str(client.base_url).startswith("https://api.openai.com")
|
|
587
|
-
|
|
588
|
-
@classmethod
|
|
589
|
-
def get_store_param(cls, client: AsyncOpenAI, model_settings: ModelSettings) -> bool | None:
|
|
590
|
-
# Match the behavior of Responses where store is True when not given
|
|
591
|
-
default_store = True if cls.is_openai(client) else None
|
|
592
|
-
return model_settings.store if model_settings.store is not None else default_store
|
|
593
|
-
|
|
594
|
-
@classmethod
|
|
595
|
-
def get_stream_options_param(
|
|
596
|
-
cls, client: AsyncOpenAI, model_settings: ModelSettings, stream: bool
|
|
597
|
-
) -> dict[str, bool] | None:
|
|
598
|
-
if not stream:
|
|
599
|
-
return None
|
|
600
|
-
|
|
601
|
-
default_include_usage = True if cls.is_openai(client) else None
|
|
602
|
-
include_usage = (
|
|
603
|
-
model_settings.include_usage
|
|
604
|
-
if model_settings.include_usage is not None
|
|
605
|
-
else default_include_usage
|
|
606
|
-
)
|
|
607
|
-
stream_options = {"include_usage": include_usage} if include_usage is not None else None
|
|
608
|
-
return stream_options
|
|
609
|
-
|
|
610
|
-
@classmethod
|
|
611
|
-
def convert_tool_choice(
|
|
612
|
-
cls, tool_choice: Literal["auto", "required", "none"] | str | None
|
|
613
|
-
) -> ChatCompletionToolChoiceOptionParam | NotGiven:
|
|
614
|
-
if tool_choice is None:
|
|
615
|
-
return NOT_GIVEN
|
|
616
|
-
elif tool_choice == "auto":
|
|
617
|
-
return "auto"
|
|
618
|
-
elif tool_choice == "required":
|
|
619
|
-
return "required"
|
|
620
|
-
elif tool_choice == "none":
|
|
621
|
-
return "none"
|
|
622
|
-
else:
|
|
623
|
-
return {
|
|
624
|
-
"type": "function",
|
|
625
|
-
"function": {
|
|
626
|
-
"name": tool_choice,
|
|
627
|
-
},
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
@classmethod
|
|
631
|
-
def convert_response_format(
|
|
632
|
-
cls, final_output_schema: AgentOutputSchema | None
|
|
633
|
-
) -> ResponseFormat | NotGiven:
|
|
634
|
-
if not final_output_schema or final_output_schema.is_plain_text():
|
|
635
|
-
return NOT_GIVEN
|
|
636
|
-
|
|
637
|
-
return {
|
|
638
|
-
"type": "json_schema",
|
|
639
|
-
"json_schema": {
|
|
640
|
-
"name": "final_output",
|
|
641
|
-
"strict": final_output_schema.strict_json_schema,
|
|
642
|
-
"schema": final_output_schema.json_schema(),
|
|
643
|
-
},
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
@classmethod
|
|
647
|
-
def message_to_output_items(cls, message: ChatCompletionMessage) -> list[TResponseOutputItem]:
|
|
648
|
-
items: list[TResponseOutputItem] = []
|
|
649
|
-
|
|
650
|
-
message_item = ResponseOutputMessage(
|
|
651
|
-
id=FAKE_RESPONSES_ID,
|
|
652
|
-
content=[],
|
|
653
|
-
role="assistant",
|
|
654
|
-
type="message",
|
|
655
|
-
status="completed",
|
|
656
|
-
)
|
|
657
|
-
if message.content:
|
|
658
|
-
message_item.content.append(
|
|
659
|
-
ResponseOutputText(text=message.content, type="output_text", annotations=[])
|
|
660
|
-
)
|
|
661
|
-
if message.refusal:
|
|
662
|
-
message_item.content.append(
|
|
663
|
-
ResponseOutputRefusal(refusal=message.refusal, type="refusal")
|
|
664
|
-
)
|
|
665
|
-
if message.audio:
|
|
666
|
-
raise AgentsException("Audio is not currently supported")
|
|
667
|
-
|
|
668
|
-
if message_item.content:
|
|
669
|
-
items.append(message_item)
|
|
670
|
-
|
|
671
|
-
if message.tool_calls:
|
|
672
|
-
for tool_call in message.tool_calls:
|
|
673
|
-
items.append(
|
|
674
|
-
ResponseFunctionToolCall(
|
|
675
|
-
id=FAKE_RESPONSES_ID,
|
|
676
|
-
call_id=tool_call.id,
|
|
677
|
-
arguments=tool_call.function.arguments,
|
|
678
|
-
name=tool_call.function.name,
|
|
679
|
-
type="function_call",
|
|
680
|
-
)
|
|
681
|
-
)
|
|
682
|
-
|
|
683
|
-
return items
|
|
684
|
-
|
|
685
|
-
@classmethod
|
|
686
|
-
def maybe_easy_input_message(cls, item: Any) -> EasyInputMessageParam | None:
|
|
687
|
-
if not isinstance(item, dict):
|
|
688
|
-
return None
|
|
689
|
-
|
|
690
|
-
keys = item.keys()
|
|
691
|
-
# EasyInputMessageParam only has these two keys
|
|
692
|
-
if keys != {"content", "role"}:
|
|
693
|
-
return None
|
|
694
|
-
|
|
695
|
-
role = item.get("role", None)
|
|
696
|
-
if role not in ("user", "assistant", "system", "developer"):
|
|
697
|
-
return None
|
|
698
|
-
|
|
699
|
-
if "content" not in item:
|
|
700
|
-
return None
|
|
701
|
-
|
|
702
|
-
return cast(EasyInputMessageParam, item)
|
|
703
|
-
|
|
704
|
-
@classmethod
|
|
705
|
-
def maybe_input_message(cls, item: Any) -> Message | None:
|
|
706
|
-
if (
|
|
707
|
-
isinstance(item, dict)
|
|
708
|
-
and item.get("type") == "message"
|
|
709
|
-
and item.get("role")
|
|
710
|
-
in (
|
|
711
|
-
"user",
|
|
712
|
-
"system",
|
|
713
|
-
"developer",
|
|
714
|
-
)
|
|
715
|
-
):
|
|
716
|
-
return cast(Message, item)
|
|
717
|
-
|
|
718
|
-
return None
|
|
719
|
-
|
|
720
|
-
@classmethod
|
|
721
|
-
def maybe_file_search_call(cls, item: Any) -> ResponseFileSearchToolCallParam | None:
|
|
722
|
-
if isinstance(item, dict) and item.get("type") == "file_search_call":
|
|
723
|
-
return cast(ResponseFileSearchToolCallParam, item)
|
|
724
|
-
return None
|
|
725
|
-
|
|
726
|
-
@classmethod
|
|
727
|
-
def maybe_function_tool_call(cls, item: Any) -> ResponseFunctionToolCallParam | None:
|
|
728
|
-
if isinstance(item, dict) and item.get("type") == "function_call":
|
|
729
|
-
return cast(ResponseFunctionToolCallParam, item)
|
|
730
|
-
return None
|
|
731
|
-
|
|
732
|
-
@classmethod
|
|
733
|
-
def maybe_function_tool_call_output(
|
|
734
|
-
cls,
|
|
735
|
-
item: Any,
|
|
736
|
-
) -> FunctionCallOutput | None:
|
|
737
|
-
if isinstance(item, dict) and item.get("type") == "function_call_output":
|
|
738
|
-
return cast(FunctionCallOutput, item)
|
|
739
|
-
return None
|
|
740
|
-
|
|
741
|
-
@classmethod
|
|
742
|
-
def maybe_item_reference(cls, item: Any) -> ItemReference | None:
|
|
743
|
-
if isinstance(item, dict) and item.get("type") == "item_reference":
|
|
744
|
-
return cast(ItemReference, item)
|
|
745
|
-
return None
|
|
746
|
-
|
|
747
|
-
@classmethod
|
|
748
|
-
def maybe_response_output_message(cls, item: Any) -> ResponseOutputMessageParam | None:
|
|
749
|
-
# ResponseOutputMessage is only used for messages with role assistant
|
|
750
|
-
if (
|
|
751
|
-
isinstance(item, dict)
|
|
752
|
-
and item.get("type") == "message"
|
|
753
|
-
and item.get("role") == "assistant"
|
|
754
|
-
):
|
|
755
|
-
return cast(ResponseOutputMessageParam, item)
|
|
756
|
-
return None
|
|
757
|
-
|
|
758
|
-
@classmethod
|
|
759
|
-
def extract_text_content(
|
|
760
|
-
cls, content: str | Iterable[ResponseInputContentParam]
|
|
761
|
-
) -> str | list[ChatCompletionContentPartTextParam]:
|
|
762
|
-
all_content = cls.extract_all_content(content)
|
|
763
|
-
if isinstance(all_content, str):
|
|
764
|
-
return all_content
|
|
765
|
-
out: list[ChatCompletionContentPartTextParam] = []
|
|
766
|
-
for c in all_content:
|
|
767
|
-
if c.get("type") == "text":
|
|
768
|
-
out.append(cast(ChatCompletionContentPartTextParam, c))
|
|
769
|
-
return out
|
|
770
|
-
|
|
771
|
-
@classmethod
|
|
772
|
-
def extract_all_content(
|
|
773
|
-
cls, content: str | Iterable[ResponseInputContentParam]
|
|
774
|
-
) -> str | list[ChatCompletionContentPartParam]:
|
|
775
|
-
if isinstance(content, str):
|
|
776
|
-
return content
|
|
777
|
-
out: list[ChatCompletionContentPartParam] = []
|
|
778
|
-
|
|
779
|
-
for c in content:
|
|
780
|
-
if isinstance(c, dict) and c.get("type") == "input_text":
|
|
781
|
-
casted_text_param = cast(ResponseInputTextParam, c)
|
|
782
|
-
out.append(
|
|
783
|
-
ChatCompletionContentPartTextParam(
|
|
784
|
-
type="text",
|
|
785
|
-
text=casted_text_param["text"],
|
|
786
|
-
)
|
|
787
|
-
)
|
|
788
|
-
elif isinstance(c, dict) and c.get("type") == "input_image":
|
|
789
|
-
casted_image_param = cast(ResponseInputImageParam, c)
|
|
790
|
-
if "image_url" not in casted_image_param or not casted_image_param["image_url"]:
|
|
791
|
-
raise UserError(
|
|
792
|
-
f"Only image URLs are supported for input_image {casted_image_param}"
|
|
793
|
-
)
|
|
794
|
-
out.append(
|
|
795
|
-
ChatCompletionContentPartImageParam(
|
|
796
|
-
type="image_url",
|
|
797
|
-
image_url={
|
|
798
|
-
"url": casted_image_param["image_url"],
|
|
799
|
-
"detail": casted_image_param["detail"],
|
|
800
|
-
},
|
|
801
|
-
)
|
|
802
|
-
)
|
|
803
|
-
elif isinstance(c, dict) and c.get("type") == "input_file":
|
|
804
|
-
raise UserError(f"File uploads are not supported for chat completions {c}")
|
|
805
|
-
else:
|
|
806
|
-
raise UserError(f"Unknown content: {c}")
|
|
807
|
-
return out
|
|
808
|
-
|
|
809
|
-
@classmethod
|
|
810
|
-
def items_to_messages(
|
|
811
|
-
cls,
|
|
812
|
-
items: str | Iterable[TResponseInputItem],
|
|
813
|
-
) -> list[ChatCompletionMessageParam]:
|
|
814
|
-
"""
|
|
815
|
-
Convert a sequence of 'Item' objects into a list of ChatCompletionMessageParam.
|
|
816
|
-
|
|
817
|
-
Rules:
|
|
818
|
-
- EasyInputMessage or InputMessage (role=user) => ChatCompletionUserMessageParam
|
|
819
|
-
- EasyInputMessage or InputMessage (role=system) => ChatCompletionSystemMessageParam
|
|
820
|
-
- EasyInputMessage or InputMessage (role=developer) => ChatCompletionDeveloperMessageParam
|
|
821
|
-
- InputMessage (role=assistant) => Start or flush a ChatCompletionAssistantMessageParam
|
|
822
|
-
- response_output_message => Also produces/flushes a ChatCompletionAssistantMessageParam
|
|
823
|
-
- tool calls get attached to the *current* assistant message, or create one if none.
|
|
824
|
-
- tool outputs => ChatCompletionToolMessageParam
|
|
825
|
-
"""
|
|
826
|
-
|
|
827
|
-
if isinstance(items, str):
|
|
828
|
-
return [
|
|
829
|
-
ChatCompletionUserMessageParam(
|
|
830
|
-
role="user",
|
|
831
|
-
content=items,
|
|
832
|
-
)
|
|
833
|
-
]
|
|
834
|
-
|
|
835
|
-
result: list[ChatCompletionMessageParam] = []
|
|
836
|
-
current_assistant_msg: ChatCompletionAssistantMessageParam | None = None
|
|
837
|
-
|
|
838
|
-
def flush_assistant_message() -> None:
|
|
839
|
-
nonlocal current_assistant_msg
|
|
840
|
-
if current_assistant_msg is not None:
|
|
841
|
-
# The API doesn't support empty arrays for tool_calls
|
|
842
|
-
if not current_assistant_msg.get("tool_calls"):
|
|
843
|
-
del current_assistant_msg["tool_calls"]
|
|
844
|
-
result.append(current_assistant_msg)
|
|
845
|
-
current_assistant_msg = None
|
|
846
|
-
|
|
847
|
-
def ensure_assistant_message() -> ChatCompletionAssistantMessageParam:
|
|
848
|
-
nonlocal current_assistant_msg
|
|
849
|
-
if current_assistant_msg is None:
|
|
850
|
-
current_assistant_msg = ChatCompletionAssistantMessageParam(role="assistant")
|
|
851
|
-
current_assistant_msg["tool_calls"] = []
|
|
852
|
-
return current_assistant_msg
|
|
853
|
-
|
|
854
|
-
for item in items:
|
|
855
|
-
# 1) Check easy input message
|
|
856
|
-
if easy_msg := cls.maybe_easy_input_message(item):
|
|
857
|
-
role = easy_msg["role"]
|
|
858
|
-
content = easy_msg["content"]
|
|
859
|
-
|
|
860
|
-
if role == "user":
|
|
861
|
-
flush_assistant_message()
|
|
862
|
-
msg_user: ChatCompletionUserMessageParam = {
|
|
863
|
-
"role": "user",
|
|
864
|
-
"content": cls.extract_all_content(content),
|
|
865
|
-
}
|
|
866
|
-
result.append(msg_user)
|
|
867
|
-
elif role == "system":
|
|
868
|
-
flush_assistant_message()
|
|
869
|
-
msg_system: ChatCompletionSystemMessageParam = {
|
|
870
|
-
"role": "system",
|
|
871
|
-
"content": cls.extract_text_content(content),
|
|
872
|
-
}
|
|
873
|
-
result.append(msg_system)
|
|
874
|
-
elif role == "developer":
|
|
875
|
-
flush_assistant_message()
|
|
876
|
-
msg_developer: ChatCompletionDeveloperMessageParam = {
|
|
877
|
-
"role": "developer",
|
|
878
|
-
"content": cls.extract_text_content(content),
|
|
879
|
-
}
|
|
880
|
-
result.append(msg_developer)
|
|
881
|
-
elif role == "assistant":
|
|
882
|
-
flush_assistant_message()
|
|
883
|
-
msg_assistant: ChatCompletionAssistantMessageParam = {
|
|
884
|
-
"role": "assistant",
|
|
885
|
-
"content": cls.extract_text_content(content),
|
|
886
|
-
}
|
|
887
|
-
result.append(msg_assistant)
|
|
888
|
-
else:
|
|
889
|
-
raise UserError(f"Unexpected role in easy_input_message: {role}")
|
|
890
|
-
|
|
891
|
-
# 2) Check input message
|
|
892
|
-
elif in_msg := cls.maybe_input_message(item):
|
|
893
|
-
role = in_msg["role"]
|
|
894
|
-
content = in_msg["content"]
|
|
895
|
-
flush_assistant_message()
|
|
896
|
-
|
|
897
|
-
if role == "user":
|
|
898
|
-
msg_user = {
|
|
899
|
-
"role": "user",
|
|
900
|
-
"content": cls.extract_all_content(content),
|
|
901
|
-
}
|
|
902
|
-
result.append(msg_user)
|
|
903
|
-
elif role == "system":
|
|
904
|
-
msg_system = {
|
|
905
|
-
"role": "system",
|
|
906
|
-
"content": cls.extract_text_content(content),
|
|
907
|
-
}
|
|
908
|
-
result.append(msg_system)
|
|
909
|
-
elif role == "developer":
|
|
910
|
-
msg_developer = {
|
|
911
|
-
"role": "developer",
|
|
912
|
-
"content": cls.extract_text_content(content),
|
|
913
|
-
}
|
|
914
|
-
result.append(msg_developer)
|
|
915
|
-
else:
|
|
916
|
-
raise UserError(f"Unexpected role in input_message: {role}")
|
|
917
|
-
|
|
918
|
-
# 3) response output message => assistant
|
|
919
|
-
elif resp_msg := cls.maybe_response_output_message(item):
|
|
920
|
-
flush_assistant_message()
|
|
921
|
-
new_asst = ChatCompletionAssistantMessageParam(role="assistant")
|
|
922
|
-
contents = resp_msg["content"]
|
|
923
|
-
|
|
924
|
-
text_segments = []
|
|
925
|
-
for c in contents:
|
|
926
|
-
if c["type"] == "output_text":
|
|
927
|
-
text_segments.append(c["text"])
|
|
928
|
-
elif c["type"] == "refusal":
|
|
929
|
-
new_asst["refusal"] = c["refusal"]
|
|
930
|
-
elif c["type"] == "output_audio":
|
|
931
|
-
# Can't handle this, b/c chat completions expects an ID which we dont have
|
|
932
|
-
raise UserError(
|
|
933
|
-
f"Only audio IDs are supported for chat completions, but got: {c}"
|
|
934
|
-
)
|
|
935
|
-
else:
|
|
936
|
-
raise UserError(f"Unknown content type in ResponseOutputMessage: {c}")
|
|
937
|
-
|
|
938
|
-
if text_segments:
|
|
939
|
-
combined = "\n".join(text_segments)
|
|
940
|
-
new_asst["content"] = combined
|
|
941
|
-
|
|
942
|
-
new_asst["tool_calls"] = []
|
|
943
|
-
current_assistant_msg = new_asst
|
|
944
|
-
|
|
945
|
-
# 4) function/file-search calls => attach to assistant
|
|
946
|
-
elif file_search := cls.maybe_file_search_call(item):
|
|
947
|
-
asst = ensure_assistant_message()
|
|
948
|
-
tool_calls = list(asst.get("tool_calls", []))
|
|
949
|
-
new_tool_call = ChatCompletionMessageToolCallParam(
|
|
950
|
-
id=file_search["id"],
|
|
951
|
-
type="function",
|
|
952
|
-
function={
|
|
953
|
-
"name": "file_search_call",
|
|
954
|
-
"arguments": json.dumps(
|
|
955
|
-
{
|
|
956
|
-
"queries": file_search.get("queries", []),
|
|
957
|
-
"status": file_search.get("status"),
|
|
958
|
-
}
|
|
959
|
-
),
|
|
960
|
-
},
|
|
961
|
-
)
|
|
962
|
-
tool_calls.append(new_tool_call)
|
|
963
|
-
asst["tool_calls"] = tool_calls
|
|
964
|
-
|
|
965
|
-
elif func_call := cls.maybe_function_tool_call(item):
|
|
966
|
-
asst = ensure_assistant_message()
|
|
967
|
-
tool_calls = list(asst.get("tool_calls", []))
|
|
968
|
-
arguments = func_call["arguments"] if func_call["arguments"] else "{}"
|
|
969
|
-
new_tool_call = ChatCompletionMessageToolCallParam(
|
|
970
|
-
id=func_call["call_id"],
|
|
971
|
-
type="function",
|
|
972
|
-
function={
|
|
973
|
-
"name": func_call["name"],
|
|
974
|
-
"arguments": arguments,
|
|
975
|
-
},
|
|
976
|
-
)
|
|
977
|
-
tool_calls.append(new_tool_call)
|
|
978
|
-
asst["tool_calls"] = tool_calls
|
|
979
|
-
# 5) function call output => tool message
|
|
980
|
-
elif func_output := cls.maybe_function_tool_call_output(item):
|
|
981
|
-
flush_assistant_message()
|
|
982
|
-
msg: ChatCompletionToolMessageParam = {
|
|
983
|
-
"role": "tool",
|
|
984
|
-
"tool_call_id": func_output["call_id"],
|
|
985
|
-
"content": func_output["output"],
|
|
986
|
-
}
|
|
987
|
-
result.append(msg)
|
|
988
|
-
|
|
989
|
-
# 6) item reference => handle or raise
|
|
990
|
-
elif item_ref := cls.maybe_item_reference(item):
|
|
991
|
-
raise UserError(
|
|
992
|
-
f"Encountered an item_reference, which is not supported: {item_ref}"
|
|
993
|
-
)
|
|
994
|
-
|
|
995
|
-
# 7) If we haven't recognized it => fail or ignore
|
|
996
|
-
else:
|
|
997
|
-
raise UserError(f"Unhandled item type or structure: {item}")
|
|
998
|
-
|
|
999
|
-
flush_assistant_message()
|
|
1000
|
-
return result
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
class ToolConverter:
|
|
1004
|
-
@classmethod
|
|
1005
|
-
def to_openai(cls, tool: Tool) -> ChatCompletionToolParam:
|
|
1006
|
-
if isinstance(tool, FunctionTool):
|
|
1007
|
-
return {
|
|
1008
|
-
"type": "function",
|
|
1009
|
-
"function": {
|
|
1010
|
-
"name": tool.name,
|
|
1011
|
-
"description": tool.description or "",
|
|
1012
|
-
"parameters": tool.params_json_schema,
|
|
1013
|
-
},
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
raise UserError(
|
|
1017
|
-
f"Hosted tools are not supported with the ChatCompletions API. Got tool type: "
|
|
1018
|
-
f"{type(tool)}, tool: {tool}"
|
|
1019
|
-
)
|
|
1020
|
-
|
|
1021
|
-
@classmethod
|
|
1022
|
-
def convert_handoff_tool(cls, handoff: Handoff[Any]) -> ChatCompletionToolParam:
|
|
1023
|
-
return {
|
|
1024
|
-
"type": "function",
|
|
1025
|
-
"function": {
|
|
1026
|
-
"name": handoff.tool_name,
|
|
1027
|
-
"description": handoff.tool_description,
|
|
1028
|
-
"parameters": handoff.input_json_schema,
|
|
1029
|
-
},
|
|
1030
|
-
}
|