llama-index-llms-openai 0.3.1__py3-none-any.whl → 0.3.3__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.
@@ -2,10 +2,10 @@ import functools
2
2
  from typing import (
3
3
  TYPE_CHECKING,
4
4
  Any,
5
- Generator,
6
5
  Awaitable,
7
6
  Callable,
8
7
  Dict,
8
+ Generator,
9
9
  List,
10
10
  Optional,
11
11
  Protocol,
@@ -18,6 +18,8 @@ from typing import (
18
18
 
19
19
  import httpx
20
20
  import tiktoken
21
+
22
+ import llama_index.core.instrumentation as instrument
21
23
  from llama_index.core.base.llms.generic_utils import (
22
24
  achat_to_completion_decorator,
23
25
  acompletion_to_chat_decorator,
@@ -39,7 +41,11 @@ from llama_index.core.base.llms.types import (
39
41
  LLMMetadata,
40
42
  MessageRole,
41
43
  )
42
- from llama_index.core.bridge.pydantic import Field, PrivateAttr
44
+ from llama_index.core.bridge.pydantic import (
45
+ BaseModel,
46
+ Field,
47
+ PrivateAttr,
48
+ )
43
49
  from llama_index.core.callbacks import CallbackManager
44
50
  from llama_index.core.constants import (
45
51
  DEFAULT_TEMPERATURE,
@@ -50,7 +56,8 @@ from llama_index.core.llms.callbacks import (
50
56
  )
51
57
  from llama_index.core.llms.function_calling import FunctionCallingLLM
52
58
  from llama_index.core.llms.llm import ToolSelection
53
- from llama_index.core.types import BaseOutputParser, PydanticProgramMode, Model
59
+ from llama_index.core.llms.utils import parse_partial_json
60
+ from llama_index.core.types import BaseOutputParser, Model, PydanticProgramMode
54
61
  from llama_index.llms.openai.utils import (
55
62
  O1_MODELS,
56
63
  OpenAIToolCall,
@@ -62,14 +69,10 @@ from llama_index.llms.openai.utils import (
62
69
  is_function_calling_model,
63
70
  openai_modelname_to_contextsize,
64
71
  resolve_openai_credentials,
65
- to_openai_message_dicts,
66
72
  resolve_tool_choice,
73
+ to_openai_message_dicts,
67
74
  update_tool_calls,
68
75
  )
69
- from llama_index.core.bridge.pydantic import (
70
- BaseModel,
71
- )
72
-
73
76
  from openai import AsyncOpenAI, AzureOpenAI
74
77
  from openai import OpenAI as SyncOpenAI
75
78
  from openai.types.chat.chat_completion_chunk import (
@@ -77,9 +80,6 @@ from openai.types.chat.chat_completion_chunk import (
77
80
  ChoiceDelta,
78
81
  ChoiceDeltaToolCall,
79
82
  )
80
- from llama_index.core.llms.utils import parse_partial_json
81
-
82
- import llama_index.core.instrumentation as instrument
83
83
 
84
84
  dispatcher = instrument.get_dispatcher(__name__)
85
85
 
@@ -89,7 +89,7 @@ if TYPE_CHECKING:
89
89
  DEFAULT_OPENAI_MODEL = "gpt-3.5-turbo"
90
90
 
91
91
 
92
- def llm_retry_decorator(f: Callable[[Any], Any]) -> Callable[[Any], Any]:
92
+ def llm_retry_decorator(f: Callable[..., Any]) -> Callable[..., Any]:
93
93
  @functools.wraps(f)
94
94
  def wrapper(self, *args: Any, **kwargs: Any) -> Any:
95
95
  max_retries = getattr(self, "max_retries", 0)
@@ -112,7 +112,7 @@ def llm_retry_decorator(f: Callable[[Any], Any]) -> Callable[[Any], Any]:
112
112
  class Tokenizer(Protocol):
113
113
  """Tokenizers support an encode function that returns a list of ints."""
114
114
 
115
- def encode(self, text: str) -> List[int]:
115
+ def encode(self, text: str) -> List[int]: # fmt: skip
116
116
  ...
117
117
 
118
118
 
@@ -828,7 +828,7 @@ class OpenAI(FunctionCallingLLM):
828
828
 
829
829
  def _prepare_chat_with_tools(
830
830
  self,
831
- tools: List["BaseTool"],
831
+ tools: Sequence["BaseTool"],
832
832
  user_msg: Optional[Union[str, ChatMessage]] = None,
833
833
  chat_history: Optional[List[ChatMessage]] = None,
834
834
  verbose: bool = False,
@@ -850,9 +850,8 @@ class OpenAI(FunctionCallingLLM):
850
850
  for tool_spec in tool_specs:
851
851
  if tool_spec["type"] == "function":
852
852
  tool_spec["function"]["strict"] = strict
853
- tool_spec["function"]["parameters"][
854
- "additionalProperties"
855
- ] = False # in current openai 1.40.0 it is always false.
853
+ # in current openai 1.40.0 it is always false.
854
+ tool_spec["function"]["parameters"]["additionalProperties"] = False
856
855
 
857
856
  if isinstance(user_msg, str):
858
857
  user_msg = ChatMessage(role=MessageRole.USER, content=user_msg)
@@ -871,7 +870,7 @@ class OpenAI(FunctionCallingLLM):
871
870
  def _validate_chat_with_tools_response(
872
871
  self,
873
872
  response: ChatResponse,
874
- tools: List["BaseTool"],
873
+ tools: Sequence["BaseTool"],
875
874
  allow_parallel_tool_calls: bool = False,
876
875
  **kwargs: Any,
877
876
  ) -> ChatResponse:
@@ -926,8 +925,10 @@ class OpenAI(FunctionCallingLLM):
926
925
  ) -> BaseModel:
927
926
  """Structured predict."""
928
927
  llm_kwargs = llm_kwargs or {}
928
+ all_kwargs = {**llm_kwargs, **kwargs}
929
+
929
930
  llm_kwargs["tool_choice"] = (
930
- "required" if "tool_choice" not in llm_kwargs else llm_kwargs["tool_choice"]
931
+ "required" if "tool_choice" not in all_kwargs else all_kwargs["tool_choice"]
931
932
  )
932
933
  # by default structured prediction uses function calling to extract structured outputs
933
934
  # here we force tool_choice to be required
@@ -939,8 +940,10 @@ class OpenAI(FunctionCallingLLM):
939
940
  ) -> BaseModel:
940
941
  """Structured predict."""
941
942
  llm_kwargs = llm_kwargs or {}
943
+ all_kwargs = {**llm_kwargs, **kwargs}
944
+
942
945
  llm_kwargs["tool_choice"] = (
943
- "required" if "tool_choice" not in llm_kwargs else llm_kwargs["tool_choice"]
946
+ "required" if "tool_choice" not in all_kwargs else all_kwargs["tool_choice"]
944
947
  )
945
948
  # by default structured prediction uses function calling to extract structured outputs
946
949
  # here we force tool_choice to be required
@@ -952,22 +955,28 @@ class OpenAI(FunctionCallingLLM):
952
955
  ) -> Generator[Union[Model, List[Model]], None, None]:
953
956
  """Stream structured predict."""
954
957
  llm_kwargs = llm_kwargs or {}
958
+ all_kwargs = {**llm_kwargs, **kwargs}
959
+
955
960
  llm_kwargs["tool_choice"] = (
956
- "required" if "tool_choice" not in llm_kwargs else llm_kwargs["tool_choice"]
961
+ "required" if "tool_choice" not in all_kwargs else all_kwargs["tool_choice"]
957
962
  )
958
963
  # by default structured prediction uses function calling to extract structured outputs
959
964
  # here we force tool_choice to be required
960
965
  return super().stream_structured_predict(*args, llm_kwargs=llm_kwargs, **kwargs)
961
966
 
962
967
  @dispatcher.span
963
- def stream_structured_predict(
968
+ async def astream_structured_predict(
964
969
  self, *args: Any, llm_kwargs: Optional[Dict[str, Any]] = None, **kwargs: Any
965
970
  ) -> Generator[Union[Model, List[Model]], None, None]:
966
971
  """Stream structured predict."""
967
972
  llm_kwargs = llm_kwargs or {}
973
+ all_kwargs = {**llm_kwargs, **kwargs}
974
+
968
975
  llm_kwargs["tool_choice"] = (
969
- "required" if "tool_choice" not in llm_kwargs else llm_kwargs["tool_choice"]
976
+ "required" if "tool_choice" not in all_kwargs else all_kwargs["tool_choice"]
970
977
  )
971
978
  # by default structured prediction uses function calling to extract structured outputs
972
979
  # here we force tool_choice to be required
973
- return super().stream_structured_predict(*args, llm_kwargs=llm_kwargs, **kwargs)
980
+ return await super().astream_structured_predict(
981
+ *args, llm_kwargs=llm_kwargs, **kwargs
982
+ )
@@ -3,9 +3,6 @@ import os
3
3
  from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union
4
4
 
5
5
  from deprecated import deprecated
6
- from llama_index.core.base.llms.types import ChatMessage, LogProb, CompletionResponse
7
- from llama_index.core.bridge.pydantic import BaseModel
8
- from llama_index.core.base.llms.generic_utils import get_from_param_or_env
9
6
  from tenacity import (
10
7
  before_sleep_log,
11
8
  retry,
@@ -18,12 +15,19 @@ from tenacity import (
18
15
  from tenacity.stop import stop_base
19
16
 
20
17
  import openai
18
+ from llama_index.core.base.llms.generic_utils import get_from_param_or_env
19
+ from llama_index.core.base.llms.types import (
20
+ ChatMessage,
21
+ ImageBlock,
22
+ LogProb,
23
+ TextBlock,
24
+ )
25
+ from llama_index.core.bridge.pydantic import BaseModel
21
26
  from openai.types.chat import ChatCompletionMessageParam, ChatCompletionMessageToolCall
22
27
  from openai.types.chat.chat_completion_chunk import ChoiceDeltaToolCall
23
28
  from openai.types.chat.chat_completion_message import ChatCompletionMessage
24
29
  from openai.types.chat.chat_completion_token_logprob import ChatCompletionTokenLogprob
25
30
  from openai.types.completion_choice import Logprobs
26
- from openai.types.completion import Completion
27
31
 
28
32
  DEFAULT_OPENAI_API_TYPE = "open_ai"
29
33
  DEFAULT_OPENAI_API_BASE = "https://api.openai.com/v1"
@@ -190,7 +194,8 @@ def create_retry_decorator(
190
194
 
191
195
 
192
196
  def openai_modelname_to_contextsize(modelname: str) -> int:
193
- """Calculate the maximum number of tokens possible to generate for a model.
197
+ """
198
+ Calculate the maximum number of tokens possible to generate for a model.
194
199
 
195
200
  Args:
196
201
  modelname: The modelname we want to know the context size for.
@@ -250,10 +255,35 @@ def is_function_calling_model(model: str) -> bool:
250
255
  def to_openai_message_dict(
251
256
  message: ChatMessage, drop_none: bool = False, model: Optional[str] = None
252
257
  ) -> ChatCompletionMessageParam:
253
- """Convert generic message to OpenAI message dict."""
258
+ """Convert a ChatMessage to an OpenAI message dict."""
259
+ content = []
260
+ for block in message.blocks:
261
+ if isinstance(block, TextBlock):
262
+ content.append({"type": "text", "text": block.text})
263
+ elif isinstance(block, ImageBlock):
264
+ if block.url:
265
+ content.append(
266
+ {"type": "image_url", "image_url": {"url": str(block.url)}}
267
+ )
268
+ else:
269
+ img_bytes = block.resolve_image(as_base64=True).read()
270
+ img_str = img_bytes.decode("utf-8")
271
+ content.append(
272
+ {
273
+ "type": "image_url",
274
+ "image_url": {
275
+ "url": f"data:{block.image_mimetype};base64,{img_str}",
276
+ "detail": block.detail or "low",
277
+ },
278
+ }
279
+ )
280
+ else:
281
+ msg = f"Unsupported content block type: {type(block).__name__}"
282
+ raise ValueError(msg)
283
+
254
284
  message_dict = {
255
285
  "role": message.role.value,
256
- "content": message.content,
286
+ "content": content,
257
287
  }
258
288
 
259
289
  # TODO: O1 models do not support system prompts
@@ -314,7 +344,7 @@ def from_openai_token_logprob(
314
344
  LogProb(token=el.token, logprob=el.logprob, bytes=el.bytes or [])
315
345
  for el in openai_token_logprob.top_logprobs
316
346
  ]
317
- except Exception as e:
347
+ except Exception:
318
348
  print(openai_token_logprob)
319
349
  raise
320
350
  return result
@@ -332,7 +362,7 @@ def from_openai_token_logprobs(
332
362
 
333
363
 
334
364
  def from_openai_completion_logprob(
335
- openai_completion_logprob: Dict[str, float]
365
+ openai_completion_logprob: Dict[str, float],
336
366
  ) -> List[LogProb]:
337
367
  """Convert openai completion logprobs to generic list of LogProb."""
338
368
  return [
@@ -354,11 +384,6 @@ def from_openai_completion_logprobs(
354
384
  return result
355
385
 
356
386
 
357
- def from_openai_completion(openai_completion: Completion) -> CompletionResponse:
358
- """Convert openai completion to CompletionResponse."""
359
- text = openai_completion.choices[0].text
360
-
361
-
362
387
  def from_openai_messages(
363
388
  openai_messages: Sequence[ChatCompletionMessage],
364
389
  ) -> List[ChatMessage]:
@@ -370,13 +395,32 @@ def from_openai_message_dict(message_dict: dict) -> ChatMessage:
370
395
  """Convert openai message dict to generic message."""
371
396
  role = message_dict["role"]
372
397
  # NOTE: Azure OpenAI returns function calling messages without a content key
373
- content = message_dict.get("content", None)
398
+ content = message_dict.get("content")
399
+ blocks = []
400
+ if isinstance(content, list):
401
+ for elem in content:
402
+ t = elem.get("type")
403
+ if t == "text":
404
+ blocks.append(TextBlock(text=elem.get("text")))
405
+ elif t == "image_url":
406
+ img = elem["image_url"]["url"]
407
+ detail = elem["image_url"]["detail"]
408
+ if img.startswith("data:"):
409
+ blocks.append(ImageBlock(image=img, detail=detail))
410
+ else:
411
+ blocks.append(ImageBlock(url=img, detail=detail))
412
+ else:
413
+ msg = f"Unsupported message type: {t}"
414
+ raise ValueError(msg)
415
+ content = None
374
416
 
375
417
  additional_kwargs = message_dict.copy()
376
418
  additional_kwargs.pop("role")
377
419
  additional_kwargs.pop("content", None)
378
420
 
379
- return ChatMessage(role=role, content=content, additional_kwargs=additional_kwargs)
421
+ return ChatMessage(
422
+ role=role, content=content, additional_kwargs=additional_kwargs, blocks=blocks
423
+ )
380
424
 
381
425
 
382
426
  def from_openai_message_dicts(message_dicts: Sequence[dict]) -> List[ChatMessage]:
@@ -386,7 +430,8 @@ def from_openai_message_dicts(message_dicts: Sequence[dict]) -> List[ChatMessage
386
430
 
387
431
  @deprecated("Deprecated in favor of `to_openai_tool`, which should be used instead.")
388
432
  def to_openai_function(pydantic_class: Type[BaseModel]) -> Dict[str, Any]:
389
- """Deprecated in favor of `to_openai_tool`.
433
+ """
434
+ Deprecated in favor of `to_openai_tool`.
390
435
 
391
436
  Convert pydantic class to OpenAI function.
392
437
  """
@@ -412,7 +457,8 @@ def resolve_openai_credentials(
412
457
  api_base: Optional[str] = None,
413
458
  api_version: Optional[str] = None,
414
459
  ) -> Tuple[Optional[str], str, str]:
415
- """ "Resolve OpenAI credentials.
460
+ """
461
+ "Resolve OpenAI credentials.
416
462
 
417
463
  The order of precedence is:
418
464
  1. param
@@ -443,7 +489,8 @@ def validate_openai_api_key(api_key: Optional[str] = None) -> None:
443
489
 
444
490
 
445
491
  def resolve_tool_choice(tool_choice: Union[str, dict] = "auto") -> Union[str, dict]:
446
- """Resolve tool choice.
492
+ """
493
+ Resolve tool choice.
447
494
 
448
495
  If tool_choice is a function name string, return the appropriate dict.
449
496
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: llama-index-llms-openai
3
- Version: 0.3.1
3
+ Version: 0.3.3
4
4
  Summary: llama-index llms openai integration
5
5
  License: MIT
6
6
  Author: llama-index
@@ -11,7 +11,7 @@ Classifier: Programming Language :: Python :: 3.9
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
- Requires-Dist: llama-index-core (>=0.12.0,<0.13.0)
14
+ Requires-Dist: llama-index-core (>=0.12.4,<0.13.0)
15
15
  Requires-Dist: openai (>=1.40.0,<2.0.0)
16
16
  Description-Content-Type: text/markdown
17
17
 
@@ -0,0 +1,6 @@
1
+ llama_index/llms/openai/__init__.py,sha256=vm3cIBSGkBFlE77GyfyN0EhpJcnJZN95QMhPN53EkbE,148
2
+ llama_index/llms/openai/base.py,sha256=V__JjM8FSTuvQdeb-kqJOzIX3d1v9x4bbOGDxs-O56s,35637
3
+ llama_index/llms/openai/utils.py,sha256=bQecgCnmtsMJAncuSqVrBS3aLU59kY4_0f0kR_jDu8o,17654
4
+ llama_index_llms_openai-0.3.3.dist-info/METADATA,sha256=-iwROPhZcCSZJl9SbQ15tVZB0JgkJFFKWTA_TnqGQ5Q,3320
5
+ llama_index_llms_openai-0.3.3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
6
+ llama_index_llms_openai-0.3.3.dist-info/RECORD,,
@@ -1,6 +0,0 @@
1
- llama_index/llms/openai/__init__.py,sha256=vm3cIBSGkBFlE77GyfyN0EhpJcnJZN95QMhPN53EkbE,148
2
- llama_index/llms/openai/base.py,sha256=wg509NgBHVLgB1Z9bevMjJenu3BFf4WZvlGP40eVhj8,35460
3
- llama_index/llms/openai/utils.py,sha256=3fHw4U-fRa6yADE5DjFbJWs6gagSt3-aqUn8oHdm8Ec,16180
4
- llama_index_llms_openai-0.3.1.dist-info/METADATA,sha256=wwtRg7GXHqRsgJx2UmAUbUdZfVoO2rhTWX7vgrlhqGg,3320
5
- llama_index_llms_openai-0.3.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
6
- llama_index_llms_openai-0.3.1.dist-info/RECORD,,