llama-index-llms-openai 0.3.27__py3-none-any.whl → 0.3.29__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.
@@ -1,3 +1,4 @@
1
1
  from llama_index.llms.openai.base import AsyncOpenAI, OpenAI, SyncOpenAI, Tokenizer
2
+ from llama_index.llms.openai.responses import OpenAIResponses
2
3
 
3
- __all__ = ["OpenAI", "Tokenizer", "SyncOpenAI", "AsyncOpenAI"]
4
+ __all__ = ["OpenAI", "OpenAIResponses", "Tokenizer", "SyncOpenAI", "AsyncOpenAI"]
@@ -2,6 +2,7 @@ import functools
2
2
  from typing import (
3
3
  TYPE_CHECKING,
4
4
  Any,
5
+ AsyncGenerator,
5
6
  Awaitable,
6
7
  Callable,
7
8
  Dict,
@@ -11,6 +12,7 @@ from typing import (
11
12
  Optional,
12
13
  Protocol,
13
14
  Sequence,
15
+ Type,
14
16
  Union,
15
17
  cast,
16
18
  get_args,
@@ -43,7 +45,6 @@ from llama_index.core.base.llms.types import (
43
45
  MessageRole,
44
46
  )
45
47
  from llama_index.core.bridge.pydantic import (
46
- BaseModel,
47
48
  Field,
48
49
  PrivateAttr,
49
50
  )
@@ -56,9 +57,11 @@ from llama_index.core.llms.callbacks import (
56
57
  llm_completion_callback,
57
58
  )
58
59
  from llama_index.core.llms.function_calling import FunctionCallingLLM
59
- from llama_index.core.llms.llm import ToolSelection
60
+ from llama_index.core.llms.llm import ToolSelection, Model
60
61
  from llama_index.core.llms.utils import parse_partial_json
61
- from llama_index.core.types import BaseOutputParser, Model, PydanticProgramMode
62
+ from llama_index.core.prompts import PromptTemplate
63
+ from llama_index.core.program.utils import FlexibleModel
64
+ from llama_index.core.types import BaseOutputParser, PydanticProgramMode
62
65
  from llama_index.llms.openai.utils import (
63
66
  O1_MODELS,
64
67
  OpenAIToolCall,
@@ -991,62 +994,80 @@ class OpenAI(FunctionCallingLLM):
991
994
 
992
995
  @dispatcher.span
993
996
  def structured_predict(
994
- self, *args: Any, llm_kwargs: Optional[Dict[str, Any]] = None, **kwargs: Any
995
- ) -> BaseModel:
997
+ self,
998
+ output_cls: Type[Model],
999
+ prompt: PromptTemplate,
1000
+ llm_kwargs: Optional[Dict[str, Any]] = None,
1001
+ **prompt_args: Any,
1002
+ ) -> Model:
996
1003
  """Structured predict."""
997
1004
  llm_kwargs = llm_kwargs or {}
998
- all_kwargs = {**llm_kwargs, **kwargs}
999
1005
 
1000
1006
  llm_kwargs["tool_choice"] = (
1001
- "required" if "tool_choice" not in all_kwargs else all_kwargs["tool_choice"]
1007
+ "required" if "tool_choice" not in llm_kwargs else llm_kwargs["tool_choice"]
1002
1008
  )
1003
1009
  # by default structured prediction uses function calling to extract structured outputs
1004
1010
  # here we force tool_choice to be required
1005
- return super().structured_predict(*args, llm_kwargs=llm_kwargs, **kwargs)
1011
+ return super().structured_predict(
1012
+ output_cls, prompt, llm_kwargs=llm_kwargs, **prompt_args
1013
+ )
1006
1014
 
1007
1015
  @dispatcher.span
1008
1016
  async def astructured_predict(
1009
- self, *args: Any, llm_kwargs: Optional[Dict[str, Any]] = None, **kwargs: Any
1010
- ) -> BaseModel:
1017
+ self,
1018
+ output_cls: Type[Model],
1019
+ prompt: PromptTemplate,
1020
+ llm_kwargs: Optional[Dict[str, Any]] = None,
1021
+ **prompt_args: Any,
1022
+ ) -> Model:
1011
1023
  """Structured predict."""
1012
1024
  llm_kwargs = llm_kwargs or {}
1013
- all_kwargs = {**llm_kwargs, **kwargs}
1014
1025
 
1015
1026
  llm_kwargs["tool_choice"] = (
1016
- "required" if "tool_choice" not in all_kwargs else all_kwargs["tool_choice"]
1027
+ "required" if "tool_choice" not in llm_kwargs else llm_kwargs["tool_choice"]
1017
1028
  )
1018
1029
  # by default structured prediction uses function calling to extract structured outputs
1019
1030
  # here we force tool_choice to be required
1020
- return await super().astructured_predict(*args, llm_kwargs=llm_kwargs, **kwargs)
1031
+ return await super().astructured_predict(
1032
+ output_cls, prompt, llm_kwargs=llm_kwargs, **prompt_args
1033
+ )
1021
1034
 
1022
1035
  @dispatcher.span
1023
1036
  def stream_structured_predict(
1024
- self, *args: Any, llm_kwargs: Optional[Dict[str, Any]] = None, **kwargs: Any
1025
- ) -> Generator[Union[Model, List[Model]], None, None]:
1037
+ self,
1038
+ output_cls: Type[Model],
1039
+ prompt: PromptTemplate,
1040
+ llm_kwargs: Optional[Dict[str, Any]] = None,
1041
+ **prompt_args: Any,
1042
+ ) -> Generator[Union[Model, FlexibleModel], None, None]:
1026
1043
  """Stream structured predict."""
1027
1044
  llm_kwargs = llm_kwargs or {}
1028
- all_kwargs = {**llm_kwargs, **kwargs}
1029
1045
 
1030
1046
  llm_kwargs["tool_choice"] = (
1031
- "required" if "tool_choice" not in all_kwargs else all_kwargs["tool_choice"]
1047
+ "required" if "tool_choice" not in llm_kwargs else llm_kwargs["tool_choice"]
1032
1048
  )
1033
1049
  # by default structured prediction uses function calling to extract structured outputs
1034
1050
  # here we force tool_choice to be required
1035
- return super().stream_structured_predict(*args, llm_kwargs=llm_kwargs, **kwargs)
1051
+ return super().stream_structured_predict(
1052
+ output_cls, prompt, llm_kwargs=llm_kwargs, **prompt_args
1053
+ )
1036
1054
 
1037
1055
  @dispatcher.span
1038
1056
  async def astream_structured_predict(
1039
- self, *args: Any, llm_kwargs: Optional[Dict[str, Any]] = None, **kwargs: Any
1040
- ) -> Generator[Union[Model, List[Model]], None, None]:
1057
+ self,
1058
+ output_cls: Type[Model],
1059
+ prompt: PromptTemplate,
1060
+ llm_kwargs: Optional[Dict[str, Any]] = None,
1061
+ **prompt_args: Any,
1062
+ ) -> AsyncGenerator[Union[Model, FlexibleModel], None]:
1041
1063
  """Stream structured predict."""
1042
1064
  llm_kwargs = llm_kwargs or {}
1043
- all_kwargs = {**llm_kwargs, **kwargs}
1044
1065
 
1045
1066
  llm_kwargs["tool_choice"] = (
1046
- "required" if "tool_choice" not in all_kwargs else all_kwargs["tool_choice"]
1067
+ "required" if "tool_choice" not in llm_kwargs else llm_kwargs["tool_choice"]
1047
1068
  )
1048
1069
  # by default structured prediction uses function calling to extract structured outputs
1049
1070
  # here we force tool_choice to be required
1050
1071
  return await super().astream_structured_predict(
1051
- *args, llm_kwargs=llm_kwargs, **kwargs
1072
+ output_cls, prompt, llm_kwargs=llm_kwargs, **prompt_args
1052
1073
  )
@@ -0,0 +1,952 @@
1
+ import functools
2
+ import httpx
3
+ import tiktoken
4
+ from openai import AsyncOpenAI, AzureOpenAI
5
+ from openai import OpenAI as SyncOpenAI
6
+ from openai.types.responses import (
7
+ Response,
8
+ ResponseStreamEvent,
9
+ ResponseCompletedEvent,
10
+ ResponseCreatedEvent,
11
+ ResponseFileSearchCallCompletedEvent,
12
+ ResponseFunctionCallArgumentsDeltaEvent,
13
+ ResponseFunctionCallArgumentsDoneEvent,
14
+ ResponseInProgressEvent,
15
+ ResponseOutputItemAddedEvent,
16
+ ResponseTextAnnotationDeltaEvent,
17
+ ResponseTextDeltaEvent,
18
+ ResponseWebSearchCallCompletedEvent,
19
+ ResponseOutputItem,
20
+ ResponseOutputMessage,
21
+ ResponseFileSearchToolCall,
22
+ ResponseFunctionToolCall,
23
+ ResponseFunctionWebSearch,
24
+ ResponseComputerToolCall,
25
+ ResponseReasoningItem,
26
+ )
27
+ from typing import (
28
+ TYPE_CHECKING,
29
+ Any,
30
+ AsyncGenerator,
31
+ Callable,
32
+ Dict,
33
+ Generator,
34
+ List,
35
+ Literal,
36
+ Optional,
37
+ Protocol,
38
+ Sequence,
39
+ Tuple,
40
+ Type,
41
+ Union,
42
+ runtime_checkable,
43
+ )
44
+
45
+ import llama_index.core.instrumentation as instrument
46
+ from llama_index.core.base.llms.generic_utils import (
47
+ achat_to_completion_decorator,
48
+ astream_chat_to_completion_decorator,
49
+ chat_to_completion_decorator,
50
+ stream_chat_to_completion_decorator,
51
+ )
52
+ from llama_index.core.base.llms.types import (
53
+ ChatMessage,
54
+ ChatResponse,
55
+ ChatResponseAsyncGen,
56
+ ChatResponseGen,
57
+ CompletionResponse,
58
+ CompletionResponseAsyncGen,
59
+ CompletionResponseGen,
60
+ LLMMetadata,
61
+ MessageRole,
62
+ TextBlock,
63
+ )
64
+ from llama_index.core.bridge.pydantic import (
65
+ Field,
66
+ PrivateAttr,
67
+ )
68
+ from llama_index.core.constants import (
69
+ DEFAULT_TEMPERATURE,
70
+ )
71
+ from llama_index.core.llms.callbacks import (
72
+ llm_chat_callback,
73
+ llm_completion_callback,
74
+ )
75
+ from llama_index.core.llms.function_calling import FunctionCallingLLM
76
+ from llama_index.core.llms.llm import ToolSelection, Model
77
+ from llama_index.core.llms.utils import parse_partial_json
78
+ from llama_index.core.prompts import PromptTemplate
79
+ from llama_index.core.program.utils import FlexibleModel
80
+ from llama_index.llms.openai.utils import (
81
+ O1_MODELS,
82
+ create_retry_decorator,
83
+ is_function_calling_model,
84
+ openai_modelname_to_contextsize,
85
+ resolve_openai_credentials,
86
+ resolve_tool_choice,
87
+ to_openai_message_dicts,
88
+ )
89
+
90
+
91
+ dispatcher = instrument.get_dispatcher(__name__)
92
+
93
+ if TYPE_CHECKING:
94
+ from llama_index.core.tools.types import BaseTool
95
+
96
+ DEFAULT_OPENAI_MODEL = "gpt-4o-mini"
97
+
98
+
99
+ def llm_retry_decorator(f: Callable[..., Any]) -> Callable[..., Any]:
100
+ @functools.wraps(f)
101
+ def wrapper(self, *args: Any, **kwargs: Any) -> Any:
102
+ max_retries = getattr(self, "max_retries", 0)
103
+ if max_retries <= 0:
104
+ return f(self, *args, **kwargs)
105
+
106
+ retry = create_retry_decorator(
107
+ max_retries=max_retries,
108
+ random_exponential=True,
109
+ stop_after_delay_seconds=60,
110
+ min_seconds=1,
111
+ max_seconds=20,
112
+ )
113
+ return retry(f)(self, *args, **kwargs)
114
+
115
+ return wrapper
116
+
117
+
118
+ @runtime_checkable
119
+ class Tokenizer(Protocol):
120
+ """Tokenizers support an encode function that returns a list of ints."""
121
+
122
+ def encode(self, text: str) -> List[int]: # fmt: skip
123
+ ...
124
+
125
+
126
+ def force_single_tool_call(response: ChatResponse) -> None:
127
+ tool_calls = response.message.additional_kwargs.get("tool_calls", [])
128
+ if len(tool_calls) > 1:
129
+ response.message.additional_kwargs["tool_calls"] = [tool_calls[0]]
130
+
131
+
132
+ class OpenAIResponses(FunctionCallingLLM):
133
+ """
134
+ OpenAI Responses LLM.
135
+
136
+ Args:
137
+ model: name of the OpenAI model to use.
138
+ temperature: a float from 0 to 1 controlling randomness in generation; higher will lead to more creative, less deterministic responses.
139
+ max_output_tokens: the maximum number of tokens to generate.
140
+ include: Additional output data to include in the model response.
141
+ instructions: Instructions for the model to follow.
142
+ track_previous_responses: Whether to track previous responses. If true, the LLM class will statefully track previous responses.
143
+ store: Whether to store previous responses in OpenAI's storage.
144
+ built_in_tools: The built-in tools to use for the model to augment responses.
145
+ truncation: Whether to auto-truncate the input if it exceeds the model's context window.
146
+ user: An optional identifier to help track the user's requests for abuse.
147
+ strict: Whether to enforce strict validation of the structured output.
148
+ additional_kwargs: Add additional parameters to OpenAI request body.
149
+ max_retries: How many times to retry the API call if it fails.
150
+ timeout: How long to wait, in seconds, for an API call before failing.
151
+ api_key: Your OpenAI api key
152
+ api_base: The base URL of the API to call
153
+ api_version: the version of the API to call
154
+ default_headers: override the default headers for API requests.
155
+ http_client: pass in your own httpx.Client instance.
156
+ async_http_client: pass in your own httpx.AsyncClient instance.
157
+
158
+ Examples:
159
+ `pip install llama-index-llms-openai`
160
+
161
+ ```python
162
+ from llama_index.llms.openai import OpenAIResponses
163
+
164
+ llm = OpenAIResponses(model="gpt-4o-mini", api_key="sk-...")
165
+
166
+ response = llm.complete("Hi, write a short story")
167
+ print(response.text)
168
+ ```
169
+ """
170
+
171
+ model: str = Field(
172
+ default=DEFAULT_OPENAI_MODEL, description="The OpenAI model to use."
173
+ )
174
+ temperature: float = Field(
175
+ default=DEFAULT_TEMPERATURE,
176
+ description="The temperature to use during generation.",
177
+ ge=0.0,
178
+ le=2.0,
179
+ )
180
+ top_p: float = Field(
181
+ default=1.0,
182
+ description="The top-p value to use during generation.",
183
+ ge=0.0,
184
+ le=1.0,
185
+ )
186
+ max_output_tokens: Optional[int] = Field(
187
+ description="The maximum number of tokens to generate.",
188
+ gt=0,
189
+ )
190
+ include: Optional[List[str]] = Field(
191
+ default=None,
192
+ description="Additional output data to include in the model response.",
193
+ )
194
+ instructions: Optional[str] = Field(
195
+ default=None,
196
+ description="Instructions for the model to follow.",
197
+ )
198
+ track_previous_responses: bool = Field(
199
+ default=False,
200
+ description="Whether to track previous responses. If true, the LLM class will statefully track previous responses.",
201
+ )
202
+ store: bool = Field(
203
+ default=False,
204
+ description="Whether to store previous responses in OpenAI's storage.",
205
+ )
206
+ built_in_tools: Optional[List[dict]] = Field(
207
+ default=None,
208
+ description="The built-in tools to use for the model to augment responses.",
209
+ )
210
+ truncation: str = Field(
211
+ default="disabled",
212
+ description="Whether to auto-truncate the input if it exceeds the model's context window.",
213
+ )
214
+ user: Optional[str] = Field(
215
+ default=None,
216
+ description="An optional identifier to help track the user's requests for abuse.",
217
+ )
218
+ call_metadata: Optional[Dict[str, Any]] = Field(
219
+ default=None,
220
+ description="Metadata to include in the API call.",
221
+ )
222
+ additional_kwargs: Dict[str, Any] = Field(
223
+ default_factory=dict,
224
+ description="Additional kwargs for the OpenAI API at inference time.",
225
+ )
226
+ max_retries: int = Field(
227
+ default=3,
228
+ description="The maximum number of API retries.",
229
+ ge=0,
230
+ )
231
+ timeout: float = Field(
232
+ default=60.0,
233
+ description="The timeout, in seconds, for API requests.",
234
+ ge=0,
235
+ )
236
+ strict: bool = Field(
237
+ default=False,
238
+ description="Whether to enforce strict validation of the structured output.",
239
+ )
240
+ default_headers: Optional[Dict[str, str]] = Field(
241
+ default=None, description="The default headers for API requests."
242
+ )
243
+ api_key: str = Field(default=None, description="The OpenAI API key.")
244
+ api_base: str = Field(description="The base URL for OpenAI API.")
245
+ api_version: str = Field(description="The API version for OpenAI API.")
246
+ reasoning_effort: Optional[Literal["low", "medium", "high"]] = Field(
247
+ default=None,
248
+ description="The effort to use for reasoning models.",
249
+ )
250
+
251
+ _client: SyncOpenAI = PrivateAttr()
252
+ _aclient: AsyncOpenAI = PrivateAttr()
253
+ _http_client: Optional[httpx.Client] = PrivateAttr()
254
+ _async_http_client: Optional[httpx.AsyncClient] = PrivateAttr()
255
+ _previous_response_id: Optional[str] = PrivateAttr()
256
+
257
+ def __init__(
258
+ self,
259
+ model: str = DEFAULT_OPENAI_MODEL,
260
+ temperature: float = DEFAULT_TEMPERATURE,
261
+ max_output_tokens: Optional[int] = None,
262
+ reasoning_effort: Optional[Literal["low", "medium", "high"]] = None,
263
+ include: Optional[List[str]] = None,
264
+ instructions: Optional[str] = None,
265
+ track_previous_responses: bool = False,
266
+ store: bool = False,
267
+ built_in_tools: Optional[List[dict]] = None,
268
+ truncation: str = "disabled",
269
+ user: Optional[str] = None,
270
+ previous_response_id: Optional[str] = None,
271
+ call_metadata: Optional[Dict[str, Any]] = None,
272
+ strict: bool = False,
273
+ additional_kwargs: Optional[Dict[str, Any]] = None,
274
+ max_retries: int = 3,
275
+ timeout: float = 60.0,
276
+ api_key: Optional[str] = None,
277
+ api_base: Optional[str] = None,
278
+ api_version: Optional[str] = None,
279
+ default_headers: Optional[Dict[str, str]] = None,
280
+ http_client: Optional[httpx.Client] = None,
281
+ async_http_client: Optional[httpx.AsyncClient] = None,
282
+ openai_client: Optional[SyncOpenAI] = None,
283
+ async_openai_client: Optional[AsyncOpenAI] = None,
284
+ **kwargs: Any,
285
+ ) -> None:
286
+ additional_kwargs = additional_kwargs or {}
287
+
288
+ api_key, api_base, api_version = resolve_openai_credentials(
289
+ api_key=api_key,
290
+ api_base=api_base,
291
+ api_version=api_version,
292
+ )
293
+
294
+ # TODO: Temp forced to 1.0 for o1
295
+ if model in O1_MODELS:
296
+ temperature = 1.0
297
+
298
+ super().__init__(
299
+ model=model,
300
+ temperature=temperature,
301
+ max_output_tokens=max_output_tokens,
302
+ reasoning_effort=reasoning_effort,
303
+ include=include,
304
+ instructions=instructions,
305
+ track_previous_responses=track_previous_responses,
306
+ store=store,
307
+ built_in_tools=built_in_tools,
308
+ truncation=truncation,
309
+ user=user,
310
+ additional_kwargs=additional_kwargs,
311
+ max_retries=max_retries,
312
+ api_key=api_key,
313
+ api_version=api_version,
314
+ api_base=api_base,
315
+ timeout=timeout,
316
+ default_headers=default_headers,
317
+ call_metadata=call_metadata,
318
+ strict=strict,
319
+ **kwargs,
320
+ )
321
+
322
+ self._previous_response_id = previous_response_id
323
+
324
+ # store is set to true if track_previous_responses is true
325
+ if self.track_previous_responses:
326
+ self.store = True
327
+
328
+ self._http_client = http_client
329
+ self._async_http_client = async_http_client
330
+ self._client = openai_client or SyncOpenAI(**self._get_credential_kwargs())
331
+ self._aclient = async_openai_client or AsyncOpenAI(
332
+ **self._get_credential_kwargs(is_async=True)
333
+ )
334
+
335
+ @classmethod
336
+ def class_name(cls) -> str:
337
+ return "openai_responses_llm"
338
+
339
+ @property
340
+ def metadata(self) -> LLMMetadata:
341
+ return LLMMetadata(
342
+ context_window=openai_modelname_to_contextsize(self._get_model_name()),
343
+ num_output=self.max_output_tokens or -1,
344
+ is_chat_model=True,
345
+ is_function_calling_model=is_function_calling_model(
346
+ model=self._get_model_name()
347
+ ),
348
+ model_name=self.model,
349
+ )
350
+
351
+ @property
352
+ def _tokenizer(self) -> Optional[Tokenizer]:
353
+ """
354
+ Get a tokenizer for this model, or None if a tokenizing method is unknown.
355
+
356
+ OpenAI can do this using the tiktoken package, subclasses may not have
357
+ this convenience.
358
+ """
359
+ return tiktoken.encoding_for_model(self._get_model_name())
360
+
361
+ def _get_model_name(self) -> str:
362
+ model_name = self.model
363
+ if "ft-" in model_name: # legacy fine-tuning
364
+ model_name = model_name.split(":")[0]
365
+ elif model_name.startswith("ft:"):
366
+ model_name = model_name.split(":")[1]
367
+ return model_name
368
+
369
+ def _is_azure_client(self) -> bool:
370
+ return isinstance(self._get_client(), AzureOpenAI)
371
+
372
+ def _get_credential_kwargs(self, is_async: bool = False) -> Dict[str, Any]:
373
+ return {
374
+ "api_key": self.api_key,
375
+ "base_url": self.api_base,
376
+ "max_retries": self.max_retries,
377
+ "timeout": self.timeout,
378
+ "default_headers": self.default_headers,
379
+ "http_client": self._async_http_client if is_async else self._http_client,
380
+ }
381
+
382
+ def _get_model_kwargs(self, **kwargs: Any) -> Dict[str, Any]:
383
+ model_kwargs = {
384
+ "model": self.model,
385
+ "include": self.include,
386
+ "instructions": self.instructions,
387
+ "max_output_tokens": self.max_output_tokens,
388
+ "metadata": self.call_metadata,
389
+ "previous_response_id": self._previous_response_id,
390
+ "store": self.store,
391
+ "temperature": self.temperature,
392
+ "tools": self.built_in_tools,
393
+ "top_p": self.top_p,
394
+ "truncation": self.truncation,
395
+ "user": self.user,
396
+ }
397
+
398
+ if self.model in O1_MODELS and self.reasoning_effort is not None:
399
+ # O1 models support reasoning_effort of low, medium, high
400
+ model_kwargs["reasoning_effort"] = {"effort": self.reasoning_effort}
401
+
402
+ # add tools or extend openai tools
403
+ if "tools" in kwargs:
404
+ if isinstance(model_kwargs["tools"], list):
405
+ model_kwargs["tools"].extend(kwargs.pop("tools"))
406
+ else:
407
+ model_kwargs["tools"] = kwargs.pop("tools")
408
+
409
+ # priority is class args > additional_kwargs > runtime args
410
+ model_kwargs.update(self.additional_kwargs)
411
+
412
+ kwargs = kwargs or {}
413
+ model_kwargs.update(kwargs)
414
+
415
+ return model_kwargs
416
+
417
+ @llm_chat_callback()
418
+ def chat(self, messages: Sequence[ChatMessage], **kwargs: Any) -> ChatResponse:
419
+ return self._chat(messages, **kwargs)
420
+
421
+ @llm_chat_callback()
422
+ def stream_chat(
423
+ self, messages: Sequence[ChatMessage], **kwargs: Any
424
+ ) -> ChatResponseGen:
425
+ return self._stream_chat(messages, **kwargs)
426
+
427
+ @llm_completion_callback()
428
+ def complete(
429
+ self, prompt: str, formatted: bool = False, **kwargs: Any
430
+ ) -> CompletionResponse:
431
+ complete_fn = chat_to_completion_decorator(self._chat)
432
+
433
+ return complete_fn(prompt, **kwargs)
434
+
435
+ @llm_completion_callback()
436
+ def stream_complete(
437
+ self, prompt: str, formatted: bool = False, **kwargs: Any
438
+ ) -> CompletionResponseGen:
439
+ stream_complete_fn = stream_chat_to_completion_decorator(self._stream_chat)
440
+
441
+ return stream_complete_fn(prompt, **kwargs)
442
+
443
+ def _parse_response_output(self, output: List[ResponseOutputItem]) -> ChatResponse:
444
+ message = ChatMessage(role=MessageRole.ASSISTANT, blocks=[])
445
+ additional_kwargs = {"built_in_tool_calls": []}
446
+ tool_calls = []
447
+ for item in output:
448
+ if isinstance(item, ResponseOutputMessage):
449
+ blocks = []
450
+ for part in item.content:
451
+ if hasattr(part, "text"):
452
+ blocks.append(TextBlock(text=part.text))
453
+ if hasattr(part, "annotations"):
454
+ additional_kwargs["annotations"] = part.annotations
455
+ if hasattr(part, "refusal"):
456
+ additional_kwargs["refusal"] = part.refusal
457
+
458
+ message.blocks.extend(blocks)
459
+ elif isinstance(item, ResponseFileSearchToolCall):
460
+ additional_kwargs["built_in_tool_calls"].append(item)
461
+ elif isinstance(item, ResponseFunctionToolCall):
462
+ tool_calls.append(item)
463
+ elif isinstance(item, ResponseFunctionWebSearch):
464
+ additional_kwargs["built_in_tool_calls"].append(item)
465
+ elif isinstance(item, ResponseComputerToolCall):
466
+ additional_kwargs["built_in_tool_calls"].append(item)
467
+ elif isinstance(item, ResponseReasoningItem):
468
+ additional_kwargs["reasoning"] = item
469
+
470
+ if tool_calls and message:
471
+ message.additional_kwargs["tool_calls"] = tool_calls
472
+
473
+ return ChatResponse(message=message, additional_kwargs=additional_kwargs)
474
+
475
+ @llm_retry_decorator
476
+ def _chat(self, messages: Sequence[ChatMessage], **kwargs: Any) -> ChatResponse:
477
+ message_dicts = to_openai_message_dicts(
478
+ messages,
479
+ model=self.model,
480
+ is_responses_api=True,
481
+ )
482
+
483
+ response: Response = self._client.responses.create(
484
+ input=message_dicts,
485
+ stream=False,
486
+ **self._get_model_kwargs(**kwargs),
487
+ )
488
+
489
+ if self.track_previous_responses:
490
+ self._previous_response_id = response.id
491
+
492
+ chat_response = self._parse_response_output(response.output)
493
+ chat_response.raw = response
494
+ chat_response.additional_kwargs["usage"] = response.usage
495
+
496
+ return chat_response
497
+
498
+ @staticmethod
499
+ def process_response_event(
500
+ event: ResponseStreamEvent,
501
+ content: str,
502
+ tool_calls: List[ResponseFunctionToolCall],
503
+ built_in_tool_calls: List[Any],
504
+ additional_kwargs: Dict[str, Any],
505
+ current_tool_call: Optional[ResponseFunctionToolCall],
506
+ track_previous_responses: bool,
507
+ previous_response_id: Optional[str] = None,
508
+ ) -> Tuple[
509
+ str,
510
+ List[ResponseFunctionToolCall],
511
+ List[Any],
512
+ Dict[str, Any],
513
+ Optional[ResponseFunctionToolCall],
514
+ Optional[str],
515
+ str,
516
+ ]:
517
+ """
518
+ Process a ResponseStreamEvent and update the state accordingly.
519
+
520
+ Args:
521
+ event: The response stream event to process
522
+ content: Current accumulated content string
523
+ tool_calls: List of completed tool calls
524
+ built_in_tool_calls: List of built-in tool calls
525
+ additional_kwargs: Additional keyword arguments to include in ChatResponse
526
+ current_tool_call: The currently in-progress tool call, if any
527
+ track_previous_responses: Whether to track previous response IDs
528
+ previous_response_id: Previous response ID if tracking
529
+
530
+ Returns:
531
+ A tuple containing the updated state:
532
+ (content, tool_calls, built_in_tool_calls, additional_kwargs, current_tool_call, updated_previous_response_id, delta)
533
+ """
534
+ delta = ""
535
+ updated_previous_response_id = previous_response_id
536
+
537
+ if isinstance(event, ResponseCreatedEvent) or isinstance(
538
+ event, ResponseInProgressEvent
539
+ ):
540
+ # Initial events, track the response id
541
+ if track_previous_responses:
542
+ updated_previous_response_id = event.response.id
543
+ elif isinstance(event, ResponseOutputItemAddedEvent):
544
+ # New output item (message, tool call, etc.)
545
+ if isinstance(event.item, ResponseFunctionToolCall):
546
+ current_tool_call = event.item
547
+ elif isinstance(event, ResponseTextDeltaEvent):
548
+ # Text content is being added
549
+ delta = event.delta
550
+ content += delta
551
+ elif isinstance(event, ResponseFunctionCallArgumentsDeltaEvent):
552
+ # Function call arguments are being streamed
553
+ if current_tool_call is not None:
554
+ current_tool_call.arguments += event.delta
555
+ elif isinstance(event, ResponseFunctionCallArgumentsDoneEvent):
556
+ # Function call arguments are complete
557
+ if current_tool_call is not None:
558
+ current_tool_call.arguments = event.arguments
559
+ current_tool_call.status = "completed"
560
+
561
+ # append a copy of the tool call to the list
562
+ tool_calls.append(
563
+ ResponseFunctionToolCall(**current_tool_call.model_dump())
564
+ )
565
+
566
+ # clear the current tool call
567
+ current_tool_call = None
568
+ elif isinstance(event, ResponseTextAnnotationDeltaEvent):
569
+ # Annotations for the text
570
+ annotations = additional_kwargs.get("annotations", [])
571
+ annotations.append(event.annotation)
572
+ additional_kwargs["annotations"] = annotations
573
+ elif isinstance(event, ResponseFileSearchCallCompletedEvent):
574
+ # File search tool call completed
575
+ built_in_tool_calls.append(event)
576
+ elif isinstance(event, ResponseWebSearchCallCompletedEvent):
577
+ # Web search tool call completed
578
+ built_in_tool_calls.append(event)
579
+ elif isinstance(event, ResponseReasoningItem):
580
+ # Reasoning information
581
+ additional_kwargs["reasoning"] = event
582
+ elif isinstance(event, ResponseCompletedEvent):
583
+ # Response is complete
584
+ if hasattr(event, "response") and hasattr(event.response, "usage"):
585
+ additional_kwargs["usage"] = event.response.usage
586
+
587
+ return (
588
+ content,
589
+ tool_calls,
590
+ built_in_tool_calls,
591
+ additional_kwargs,
592
+ current_tool_call,
593
+ updated_previous_response_id,
594
+ delta,
595
+ )
596
+
597
+ @llm_retry_decorator
598
+ def _stream_chat(
599
+ self, messages: Sequence[ChatMessage], **kwargs: Any
600
+ ) -> ChatResponseGen:
601
+ message_dicts = to_openai_message_dicts(
602
+ messages,
603
+ model=self.model,
604
+ is_responses_api=True,
605
+ )
606
+
607
+ def gen() -> ChatResponseGen:
608
+ content = ""
609
+ tool_calls = []
610
+ built_in_tool_calls = []
611
+ additional_kwargs = {"built_in_tool_calls": []}
612
+ current_tool_call: Optional[ResponseFunctionToolCall] = None
613
+ local_previous_response_id = self._previous_response_id
614
+
615
+ for event in self._client.responses.create(
616
+ input=message_dicts,
617
+ stream=True,
618
+ **self._get_model_kwargs(**kwargs),
619
+ ):
620
+ # Process the event and update state
621
+ (
622
+ content,
623
+ tool_calls,
624
+ built_in_tool_calls,
625
+ additional_kwargs,
626
+ current_tool_call,
627
+ local_previous_response_id,
628
+ delta,
629
+ ) = OpenAIResponses.process_response_event(
630
+ event=event,
631
+ content=content,
632
+ tool_calls=tool_calls,
633
+ built_in_tool_calls=built_in_tool_calls,
634
+ additional_kwargs=additional_kwargs,
635
+ current_tool_call=current_tool_call,
636
+ track_previous_responses=self.track_previous_responses,
637
+ previous_response_id=local_previous_response_id,
638
+ )
639
+
640
+ if (
641
+ self.track_previous_responses
642
+ and local_previous_response_id != self._previous_response_id
643
+ ):
644
+ self._previous_response_id = local_previous_response_id
645
+
646
+ if built_in_tool_calls:
647
+ additional_kwargs["built_in_tool_calls"] = built_in_tool_calls
648
+
649
+ # For any event, yield a ChatResponse with the current state
650
+ yield ChatResponse(
651
+ message=ChatMessage(
652
+ role=MessageRole.ASSISTANT,
653
+ content=content,
654
+ additional_kwargs={"tool_calls": tool_calls}
655
+ if tool_calls
656
+ else {},
657
+ ),
658
+ delta=delta,
659
+ raw=event,
660
+ additional_kwargs=additional_kwargs,
661
+ )
662
+
663
+ return gen()
664
+
665
+ # ===== Async Endpoints =====
666
+ @llm_chat_callback()
667
+ async def achat(
668
+ self,
669
+ messages: Sequence[ChatMessage],
670
+ **kwargs: Any,
671
+ ) -> ChatResponse:
672
+ return await self._achat(messages, **kwargs)
673
+
674
+ @llm_chat_callback()
675
+ async def astream_chat(
676
+ self,
677
+ messages: Sequence[ChatMessage],
678
+ **kwargs: Any,
679
+ ) -> ChatResponseAsyncGen:
680
+ return await self._astream_chat(messages, **kwargs)
681
+
682
+ @llm_completion_callback()
683
+ async def acomplete(
684
+ self, prompt: str, formatted: bool = False, **kwargs: Any
685
+ ) -> CompletionResponse:
686
+ acomplete_fn = achat_to_completion_decorator(self._achat)
687
+
688
+ return await acomplete_fn(prompt, **kwargs)
689
+
690
+ @llm_completion_callback()
691
+ async def astream_complete(
692
+ self, prompt: str, formatted: bool = False, **kwargs: Any
693
+ ) -> CompletionResponseAsyncGen:
694
+ astream_complete_fn = astream_chat_to_completion_decorator(self._astream_chat)
695
+
696
+ return await astream_complete_fn(prompt, **kwargs)
697
+
698
+ @llm_retry_decorator
699
+ async def _achat(
700
+ self, messages: Sequence[ChatMessage], **kwargs: Any
701
+ ) -> ChatResponse:
702
+ message_dicts = to_openai_message_dicts(
703
+ messages,
704
+ model=self.model,
705
+ is_responses_api=True,
706
+ )
707
+
708
+ response: Response = await self._aclient.responses.create(
709
+ input=message_dicts,
710
+ stream=False,
711
+ **self._get_model_kwargs(**kwargs),
712
+ )
713
+
714
+ if self.track_previous_responses:
715
+ self._previous_response_id = response.id
716
+
717
+ chat_response = self._parse_response_output(response.output)
718
+ chat_response.raw = response
719
+ chat_response.additional_kwargs["usage"] = response.usage
720
+
721
+ return chat_response
722
+
723
+ @llm_retry_decorator
724
+ async def _astream_chat(
725
+ self, messages: Sequence[ChatMessage], **kwargs: Any
726
+ ) -> ChatResponseAsyncGen:
727
+ message_dicts = to_openai_message_dicts(
728
+ messages,
729
+ model=self.model,
730
+ is_responses_api=True,
731
+ )
732
+
733
+ async def gen() -> ChatResponseAsyncGen:
734
+ content = ""
735
+ tool_calls = []
736
+ built_in_tool_calls = []
737
+ additional_kwargs = {"built_in_tool_calls": []}
738
+ current_tool_call: Optional[ResponseFunctionToolCall] = None
739
+ local_previous_response_id = self._previous_response_id
740
+
741
+ response_stream = await self._aclient.responses.create(
742
+ input=message_dicts,
743
+ stream=True,
744
+ **self._get_model_kwargs(**kwargs),
745
+ )
746
+
747
+ async for event in response_stream:
748
+ # Process the event and update state
749
+ (
750
+ content,
751
+ tool_calls,
752
+ built_in_tool_calls,
753
+ additional_kwargs,
754
+ current_tool_call,
755
+ local_previous_response_id,
756
+ delta,
757
+ ) = OpenAIResponses.process_response_event(
758
+ event=event,
759
+ content=content,
760
+ tool_calls=tool_calls,
761
+ built_in_tool_calls=built_in_tool_calls,
762
+ additional_kwargs=additional_kwargs,
763
+ current_tool_call=current_tool_call,
764
+ track_previous_responses=self.track_previous_responses,
765
+ previous_response_id=local_previous_response_id,
766
+ )
767
+
768
+ if (
769
+ self.track_previous_responses
770
+ and local_previous_response_id != self._previous_response_id
771
+ ):
772
+ self._previous_response_id = local_previous_response_id
773
+
774
+ if built_in_tool_calls:
775
+ additional_kwargs["built_in_tool_calls"] = built_in_tool_calls
776
+
777
+ # For any event, yield a ChatResponse with the current state
778
+ yield ChatResponse(
779
+ message=ChatMessage(
780
+ role=MessageRole.ASSISTANT,
781
+ content=content,
782
+ additional_kwargs={"tool_calls": tool_calls}
783
+ if tool_calls
784
+ else {},
785
+ ),
786
+ delta=delta,
787
+ raw=event,
788
+ additional_kwargs=additional_kwargs,
789
+ )
790
+
791
+ return gen()
792
+
793
+ def _prepare_chat_with_tools(
794
+ self,
795
+ tools: Sequence["BaseTool"],
796
+ user_msg: Optional[Union[str, ChatMessage]] = None,
797
+ chat_history: Optional[List[ChatMessage]] = None,
798
+ allow_parallel_tool_calls: bool = True,
799
+ tool_choice: Union[str, dict] = "auto",
800
+ verbose: bool = False,
801
+ strict: Optional[bool] = None,
802
+ **kwargs: Any,
803
+ ) -> Dict[str, Any]:
804
+ """Predict and call the tool."""
805
+
806
+ # openai responses api has a slightly different tool spec format
807
+ tool_specs = [
808
+ {"type": "function", **tool.metadata.to_openai_tool()["function"]}
809
+ for tool in tools
810
+ ]
811
+
812
+ if strict is not None:
813
+ strict = strict
814
+ else:
815
+ strict = self.strict
816
+
817
+ if strict:
818
+ for tool_spec in tool_specs:
819
+ tool_spec["strict"] = True
820
+ tool_spec["parameters"]["additionalProperties"] = False
821
+
822
+ if isinstance(user_msg, str):
823
+ user_msg = ChatMessage(role=MessageRole.USER, content=user_msg)
824
+
825
+ messages = chat_history or []
826
+ if user_msg:
827
+ messages.append(user_msg)
828
+
829
+ return {
830
+ "messages": messages,
831
+ "tools": tool_specs or None,
832
+ "tool_choice": resolve_tool_choice(tool_choice) if tool_specs else None,
833
+ "parallel_tool_calls": allow_parallel_tool_calls,
834
+ **kwargs,
835
+ }
836
+
837
+ def get_tool_calls_from_response(
838
+ self,
839
+ response: "ChatResponse",
840
+ error_on_no_tool_call: bool = True,
841
+ **kwargs: Any,
842
+ ) -> List[ToolSelection]:
843
+ """Predict and call the tool."""
844
+ tool_calls: List[
845
+ ResponseFunctionToolCall
846
+ ] = response.message.additional_kwargs.get("tool_calls", [])
847
+
848
+ if len(tool_calls) < 1:
849
+ if error_on_no_tool_call:
850
+ raise ValueError(
851
+ f"Expected at least one tool call, but got {len(tool_calls)} tool calls."
852
+ )
853
+ else:
854
+ return []
855
+
856
+ tool_selections = []
857
+ for tool_call in tool_calls:
858
+ # this should handle both complete and partial jsons
859
+ try:
860
+ argument_dict = parse_partial_json(tool_call.arguments)
861
+ except ValueError:
862
+ argument_dict = {}
863
+
864
+ tool_selections.append(
865
+ ToolSelection(
866
+ tool_id=tool_call.call_id,
867
+ tool_name=tool_call.name,
868
+ tool_kwargs=argument_dict,
869
+ )
870
+ )
871
+
872
+ return tool_selections
873
+
874
+ @dispatcher.span
875
+ def structured_predict(
876
+ self,
877
+ output_cls: Type[Model],
878
+ prompt: PromptTemplate,
879
+ llm_kwargs: Optional[Dict[str, Any]] = None,
880
+ **prompt_args: Any,
881
+ ) -> Model:
882
+ """Structured predict."""
883
+ llm_kwargs = llm_kwargs or {}
884
+
885
+ llm_kwargs["tool_choice"] = (
886
+ "required" if "tool_choice" not in llm_kwargs else llm_kwargs["tool_choice"]
887
+ )
888
+ # by default structured prediction uses function calling to extract structured outputs
889
+ # here we force tool_choice to be required
890
+ return super().structured_predict(
891
+ output_cls, prompt, llm_kwargs=llm_kwargs, **prompt_args
892
+ )
893
+
894
+ @dispatcher.span
895
+ async def astructured_predict(
896
+ self,
897
+ output_cls: Type[Model],
898
+ prompt: PromptTemplate,
899
+ llm_kwargs: Optional[Dict[str, Any]] = None,
900
+ **prompt_args: Any,
901
+ ) -> Model:
902
+ """Structured predict."""
903
+ llm_kwargs = llm_kwargs or {}
904
+
905
+ llm_kwargs["tool_choice"] = (
906
+ "required" if "tool_choice" not in llm_kwargs else llm_kwargs["tool_choice"]
907
+ )
908
+ # by default structured prediction uses function calling to extract structured outputs
909
+ # here we force tool_choice to be required
910
+ return await super().astructured_predict(
911
+ output_cls, prompt, llm_kwargs=llm_kwargs, **prompt_args
912
+ )
913
+
914
+ @dispatcher.span
915
+ def stream_structured_predict(
916
+ self,
917
+ output_cls: Type[Model],
918
+ prompt: PromptTemplate,
919
+ llm_kwargs: Optional[Dict[str, Any]] = None,
920
+ **prompt_args: Any,
921
+ ) -> Generator[Union[Model, FlexibleModel], None, None]:
922
+ """Stream structured predict."""
923
+ llm_kwargs = llm_kwargs or {}
924
+
925
+ llm_kwargs["tool_choice"] = (
926
+ "required" if "tool_choice" not in llm_kwargs else llm_kwargs["tool_choice"]
927
+ )
928
+ # by default structured prediction uses function calling to extract structured outputs
929
+ # here we force tool_choice to be required
930
+ return super().stream_structured_predict(
931
+ output_cls, prompt, llm_kwargs=llm_kwargs, **prompt_args
932
+ )
933
+
934
+ @dispatcher.span
935
+ async def astream_structured_predict(
936
+ self,
937
+ output_cls: Type[Model],
938
+ prompt: PromptTemplate,
939
+ llm_kwargs: Optional[Dict[str, Any]] = None,
940
+ **prompt_args: Any,
941
+ ) -> AsyncGenerator[Union[Model, FlexibleModel], None]:
942
+ """Stream structured predict."""
943
+ llm_kwargs = llm_kwargs or {}
944
+
945
+ llm_kwargs["tool_choice"] = (
946
+ "required" if "tool_choice" not in llm_kwargs else llm_kwargs["tool_choice"]
947
+ )
948
+ # by default structured prediction uses function calling to extract structured outputs
949
+ # here we force tool_choice to be required
950
+ return await super().astream_structured_predict(
951
+ output_cls, prompt, llm_kwargs=llm_kwargs, **prompt_args
952
+ )
@@ -391,20 +391,142 @@ def to_openai_message_dict(
391
391
  return message_dict # type: ignore
392
392
 
393
393
 
394
+ def to_openai_responses_message_dict(
395
+ message: ChatMessage,
396
+ drop_none: bool = False,
397
+ model: Optional[str] = None,
398
+ ) -> Union[Dict[str, Any], List[Dict[str, Any]]]:
399
+ """Convert a ChatMessage to an OpenAI message dict."""
400
+ content = []
401
+ content_txt = ""
402
+
403
+ for block in message.blocks:
404
+ if isinstance(block, TextBlock):
405
+ content.append({"type": "input_text", "text": block.text})
406
+ content_txt += block.text
407
+ elif isinstance(block, ImageBlock):
408
+ if block.url:
409
+ content.append(
410
+ {
411
+ "type": "input_image",
412
+ "image_url": str(block.url),
413
+ "detail": block.detail or "auto",
414
+ }
415
+ )
416
+ else:
417
+ img_bytes = block.resolve_image(as_base64=True).read()
418
+ img_str = img_bytes.decode("utf-8")
419
+ content.append(
420
+ {
421
+ "type": "input_image",
422
+ "image_url": f"data:{block.image_mimetype};base64,{img_str}",
423
+ "detail": block.detail or "auto",
424
+ }
425
+ )
426
+ else:
427
+ msg = f"Unsupported content block type: {type(block).__name__}"
428
+ raise ValueError(msg)
429
+
430
+ # NOTE: Sending a null value (None) for Tool Message to OpenAI will cause error
431
+ # It's only Allowed to send None if it's an Assistant Message and either a function call or tool calls were performed
432
+ # Reference: https://platform.openai.com/docs/api-reference/chat/create
433
+ content_txt = (
434
+ None
435
+ if content_txt == ""
436
+ and message.role == MessageRole.ASSISTANT
437
+ and (
438
+ "function_call" in message.additional_kwargs
439
+ or "tool_calls" in message.additional_kwargs
440
+ )
441
+ else content_txt
442
+ )
443
+
444
+ # NOTE: Despite what the openai docs say, if the role is ASSISTANT, SYSTEM
445
+ # or TOOL, 'content' cannot be a list and must be string instead.
446
+ # Furthermore, if all blocks are text blocks, we can use the content_txt
447
+ # as the content. This will avoid breaking openai-like APIs.
448
+ if message.role.value == "tool":
449
+ call_id = message.additional_kwargs.get(
450
+ "tool_call_id", message.additional_kwargs.get("call_id")
451
+ )
452
+ if call_id is None:
453
+ raise ValueError(
454
+ "tool_call_id or call_id is required in additional_kwargs for tool messages"
455
+ )
456
+
457
+ message_dict = {
458
+ "type": "function_call_output",
459
+ "output": content_txt,
460
+ "call_id": call_id,
461
+ }
462
+
463
+ return message_dict
464
+ elif "tool_calls" in message.additional_kwargs:
465
+ message_dicts = [
466
+ tool_call if isinstance(tool_call, dict) else tool_call.model_dump()
467
+ for tool_call in message.additional_kwargs["tool_calls"]
468
+ ]
469
+
470
+ return message_dicts
471
+ else:
472
+ message_dict = {
473
+ "role": message.role.value,
474
+ "content": (
475
+ content_txt
476
+ if message.role.value in ("assistant", "system", "developer")
477
+ or all(isinstance(block, TextBlock) for block in message.blocks)
478
+ else content
479
+ ),
480
+ }
481
+
482
+ # TODO: O1 models do not support system prompts
483
+ if (
484
+ model is not None
485
+ and model in O1_MODELS
486
+ and model not in O1_MODELS_WITHOUT_FUNCTION_CALLING
487
+ ):
488
+ if message_dict["role"] == "system":
489
+ message_dict["role"] = "developer"
490
+
491
+ null_keys = [key for key, value in message_dict.items() if value is None]
492
+ # if drop_none is True, remove keys with None values
493
+ if drop_none:
494
+ for key in null_keys:
495
+ message_dict.pop(key)
496
+
497
+ return message_dict # type: ignore
498
+
499
+
394
500
  def to_openai_message_dicts(
395
501
  messages: Sequence[ChatMessage],
396
502
  drop_none: bool = False,
397
503
  model: Optional[str] = None,
504
+ is_responses_api: bool = False,
398
505
  ) -> List[ChatCompletionMessageParam]:
399
506
  """Convert generic messages to OpenAI message dicts."""
400
- return [
401
- to_openai_message_dict(
402
- message,
403
- drop_none=drop_none,
404
- model=model,
405
- )
406
- for message in messages
407
- ]
507
+ if is_responses_api:
508
+ final_message_dicts = []
509
+ for message in messages:
510
+ message_dicts = to_openai_responses_message_dict(
511
+ message,
512
+ drop_none=drop_none,
513
+ model=model,
514
+ )
515
+ if isinstance(message_dicts, list):
516
+ final_message_dicts.extend(message_dicts)
517
+ else:
518
+ final_message_dicts.append(message_dicts)
519
+
520
+ return final_message_dicts
521
+ else:
522
+ return [
523
+ to_openai_message_dict(
524
+ message,
525
+ drop_none=drop_none,
526
+ model=model,
527
+ )
528
+ for message in messages
529
+ ]
408
530
 
409
531
 
410
532
  def from_openai_message(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: llama-index-llms-openai
3
- Version: 0.3.27
3
+ Version: 0.3.29
4
4
  Summary: llama-index llms openai integration
5
5
  License: MIT
6
6
  Author: llama-index
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
14
  Requires-Dist: llama-index-core (>=0.12.17,<0.13.0)
15
- Requires-Dist: openai (>=1.58.1,<2.0.0)
15
+ Requires-Dist: openai (>=1.66.3,<2.0.0)
16
16
  Description-Content-Type: text/markdown
17
17
 
18
18
  # LlamaIndex Llms Integration: Openai
@@ -0,0 +1,9 @@
1
+ llama_index/llms/openai/__init__.py,sha256=8nmgixeXifQ4eVSgtCic54WxXqrrpXQPL4rhACWCSFs,229
2
+ llama_index/llms/openai/base.py,sha256=RjkISrh-RvbrQWOfdNdH4nimDQN0byUFm_n6r703jdM,38609
3
+ llama_index/llms/openai/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ llama_index/llms/openai/responses.py,sha256=TikqnpW-UQmgjmMYGznsBx7eEo5prNIaxE81RO1ZZjE,34465
5
+ llama_index/llms/openai/utils.py,sha256=qp9qpXY7HbUnUsVDx6TgK98feibzTRi-bLdq_F3S0fo,26017
6
+ llama_index_llms_openai-0.3.29.dist-info/LICENSE,sha256=JPQLUZD9rKvCTdu192Nk0V5PAwklIg6jANii3UmTyMs,1065
7
+ llama_index_llms_openai-0.3.29.dist-info/METADATA,sha256=g0FtAtfto485JxdoqCDwulLfoKYoPcglr1ETMZ_lFjg,3322
8
+ llama_index_llms_openai-0.3.29.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
9
+ llama_index_llms_openai-0.3.29.dist-info/RECORD,,
@@ -1,8 +0,0 @@
1
- llama_index/llms/openai/__init__.py,sha256=vm3cIBSGkBFlE77GyfyN0EhpJcnJZN95QMhPN53EkbE,148
2
- llama_index/llms/openai/base.py,sha256=zQAB6ch5acoKnMmKFk6Ro06-EM3ICnISZ2bhgAlZlCg,38236
3
- llama_index/llms/openai/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- llama_index/llms/openai/utils.py,sha256=n7GEv864j34idRUR2ouu0McSBqBTVX2Tko9vf1YOl-k,21624
5
- llama_index_llms_openai-0.3.27.dist-info/LICENSE,sha256=JPQLUZD9rKvCTdu192Nk0V5PAwklIg6jANii3UmTyMs,1065
6
- llama_index_llms_openai-0.3.27.dist-info/METADATA,sha256=usLx57-5JN41t-PFQnQlajCi0NHHYl0uY25F8LhtCts,3322
7
- llama_index_llms_openai-0.3.27.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
8
- llama_index_llms_openai-0.3.27.dist-info/RECORD,,