llama-index-llms-openai 0.6.4__tar.gz → 0.6.6__tar.gz
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.
- {llama_index_llms_openai-0.6.4 → llama_index_llms_openai-0.6.6}/PKG-INFO +2 -2
- {llama_index_llms_openai-0.6.4 → llama_index_llms_openai-0.6.6}/llama_index/llms/openai/base.py +94 -29
- {llama_index_llms_openai-0.6.4 → llama_index_llms_openai-0.6.6}/llama_index/llms/openai/responses.py +33 -33
- {llama_index_llms_openai-0.6.4 → llama_index_llms_openai-0.6.6}/llama_index/llms/openai/utils.py +102 -17
- {llama_index_llms_openai-0.6.4 → llama_index_llms_openai-0.6.6}/pyproject.toml +2 -2
- {llama_index_llms_openai-0.6.4 → llama_index_llms_openai-0.6.6}/.gitignore +0 -0
- {llama_index_llms_openai-0.6.4 → llama_index_llms_openai-0.6.6}/LICENSE +0 -0
- {llama_index_llms_openai-0.6.4 → llama_index_llms_openai-0.6.6}/README.md +0 -0
- {llama_index_llms_openai-0.6.4 → llama_index_llms_openai-0.6.6}/llama_index/llms/openai/__init__.py +0 -0
- {llama_index_llms_openai-0.6.4 → llama_index_llms_openai-0.6.6}/llama_index/llms/openai/py.typed +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: llama-index-llms-openai
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.6
|
|
4
4
|
Summary: llama-index llms openai integration
|
|
5
5
|
Author: llama-index
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
License-File: LICENSE
|
|
8
8
|
Requires-Python: <4.0,>=3.9
|
|
9
|
-
Requires-Dist: llama-index-core<0.15,>=0.14.
|
|
9
|
+
Requires-Dist: llama-index-core<0.15,>=0.14.5
|
|
10
10
|
Requires-Dist: openai<2,>=1.108.1
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
|
{llama_index_llms_openai-0.6.4 → llama_index_llms_openai-0.6.6}/llama_index/llms/openai/base.py
RENAMED
|
@@ -43,6 +43,8 @@ from llama_index.core.base.llms.types import (
|
|
|
43
43
|
CompletionResponseGen,
|
|
44
44
|
LLMMetadata,
|
|
45
45
|
MessageRole,
|
|
46
|
+
ToolCallBlock,
|
|
47
|
+
TextBlock,
|
|
46
48
|
)
|
|
47
49
|
from llama_index.core.bridge.pydantic import (
|
|
48
50
|
Field,
|
|
@@ -121,9 +123,15 @@ class Tokenizer(Protocol):
|
|
|
121
123
|
|
|
122
124
|
|
|
123
125
|
def force_single_tool_call(response: ChatResponse) -> None:
|
|
124
|
-
tool_calls =
|
|
126
|
+
tool_calls = [
|
|
127
|
+
block for block in response.message.blocks if isinstance(block, ToolCallBlock)
|
|
128
|
+
]
|
|
125
129
|
if len(tool_calls) > 1:
|
|
126
|
-
response.message.
|
|
130
|
+
response.message.blocks = [
|
|
131
|
+
block
|
|
132
|
+
for block in response.message.blocks
|
|
133
|
+
if not isinstance(block, ToolCallBlock)
|
|
134
|
+
] + [tool_calls[0]]
|
|
127
135
|
|
|
128
136
|
|
|
129
137
|
class OpenAI(FunctionCallingLLM):
|
|
@@ -528,6 +536,7 @@ class OpenAI(FunctionCallingLLM):
|
|
|
528
536
|
messages=message_dicts,
|
|
529
537
|
**self._get_model_kwargs(stream=True, **kwargs),
|
|
530
538
|
):
|
|
539
|
+
blocks = []
|
|
531
540
|
response = cast(ChatCompletionChunk, response)
|
|
532
541
|
if len(response.choices) > 0:
|
|
533
542
|
delta = response.choices[0].delta
|
|
@@ -545,17 +554,27 @@ class OpenAI(FunctionCallingLLM):
|
|
|
545
554
|
role = delta.role or MessageRole.ASSISTANT
|
|
546
555
|
content_delta = delta.content or ""
|
|
547
556
|
content += content_delta
|
|
557
|
+
blocks.append(TextBlock(text=content))
|
|
548
558
|
|
|
549
559
|
additional_kwargs = {}
|
|
550
560
|
if is_function:
|
|
551
561
|
tool_calls = update_tool_calls(tool_calls, delta.tool_calls)
|
|
552
562
|
if tool_calls:
|
|
553
563
|
additional_kwargs["tool_calls"] = tool_calls
|
|
564
|
+
for tool_call in tool_calls:
|
|
565
|
+
if tool_call.function:
|
|
566
|
+
blocks.append(
|
|
567
|
+
ToolCallBlock(
|
|
568
|
+
tool_call_id=tool_call.id,
|
|
569
|
+
tool_kwargs=tool_call.function.arguments or {},
|
|
570
|
+
tool_name=tool_call.function.name or "",
|
|
571
|
+
)
|
|
572
|
+
)
|
|
554
573
|
|
|
555
574
|
yield ChatResponse(
|
|
556
575
|
message=ChatMessage(
|
|
557
576
|
role=role,
|
|
558
|
-
|
|
577
|
+
blocks=blocks,
|
|
559
578
|
additional_kwargs=additional_kwargs,
|
|
560
579
|
),
|
|
561
580
|
delta=content_delta,
|
|
@@ -785,6 +804,7 @@ class OpenAI(FunctionCallingLLM):
|
|
|
785
804
|
messages=message_dicts,
|
|
786
805
|
**self._get_model_kwargs(stream=True, **kwargs),
|
|
787
806
|
):
|
|
807
|
+
blocks = []
|
|
788
808
|
response = cast(ChatCompletionChunk, response)
|
|
789
809
|
if len(response.choices) > 0:
|
|
790
810
|
# check if the first chunk has neither content nor tool_calls
|
|
@@ -812,17 +832,27 @@ class OpenAI(FunctionCallingLLM):
|
|
|
812
832
|
role = delta.role or MessageRole.ASSISTANT
|
|
813
833
|
content_delta = delta.content or ""
|
|
814
834
|
content += content_delta
|
|
835
|
+
blocks.append(TextBlock(text=content))
|
|
815
836
|
|
|
816
837
|
additional_kwargs = {}
|
|
817
838
|
if is_function:
|
|
818
839
|
tool_calls = update_tool_calls(tool_calls, delta.tool_calls)
|
|
819
840
|
if tool_calls:
|
|
820
841
|
additional_kwargs["tool_calls"] = tool_calls
|
|
842
|
+
for tool_call in tool_calls:
|
|
843
|
+
if tool_call.function:
|
|
844
|
+
blocks.append(
|
|
845
|
+
ToolCallBlock(
|
|
846
|
+
tool_call_id=tool_call.id,
|
|
847
|
+
tool_kwargs=tool_call.function.arguments or {},
|
|
848
|
+
tool_name=tool_call.function.name or "",
|
|
849
|
+
)
|
|
850
|
+
)
|
|
821
851
|
|
|
822
852
|
yield ChatResponse(
|
|
823
853
|
message=ChatMessage(
|
|
824
854
|
role=role,
|
|
825
|
-
|
|
855
|
+
blocks=blocks,
|
|
826
856
|
additional_kwargs=additional_kwargs,
|
|
827
857
|
),
|
|
828
858
|
delta=content_delta,
|
|
@@ -960,36 +990,71 @@ class OpenAI(FunctionCallingLLM):
|
|
|
960
990
|
**kwargs: Any,
|
|
961
991
|
) -> List[ToolSelection]:
|
|
962
992
|
"""Predict and call the tool."""
|
|
963
|
-
tool_calls =
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
if
|
|
967
|
-
|
|
968
|
-
|
|
993
|
+
tool_calls = [
|
|
994
|
+
block
|
|
995
|
+
for block in response.message.blocks
|
|
996
|
+
if isinstance(block, ToolCallBlock)
|
|
997
|
+
]
|
|
998
|
+
if tool_calls:
|
|
999
|
+
if len(tool_calls) < 1:
|
|
1000
|
+
if error_on_no_tool_call:
|
|
1001
|
+
raise ValueError(
|
|
1002
|
+
f"Expected at least one tool call, but got {len(tool_calls)} tool calls."
|
|
1003
|
+
)
|
|
1004
|
+
else:
|
|
1005
|
+
return []
|
|
1006
|
+
|
|
1007
|
+
tool_selections = []
|
|
1008
|
+
for tool_call in tool_calls:
|
|
1009
|
+
# this should handle both complete and partial jsons
|
|
1010
|
+
try:
|
|
1011
|
+
if isinstance(tool_call.tool_kwargs, str):
|
|
1012
|
+
argument_dict = parse_partial_json(tool_call.tool_kwargs)
|
|
1013
|
+
else:
|
|
1014
|
+
argument_dict = tool_call.tool_kwargs
|
|
1015
|
+
except (ValueError, TypeError, JSONDecodeError):
|
|
1016
|
+
argument_dict = {}
|
|
1017
|
+
|
|
1018
|
+
tool_selections.append(
|
|
1019
|
+
ToolSelection(
|
|
1020
|
+
tool_id=tool_call.tool_call_id or "",
|
|
1021
|
+
tool_name=tool_call.tool_name,
|
|
1022
|
+
tool_kwargs=argument_dict,
|
|
1023
|
+
)
|
|
969
1024
|
)
|
|
970
|
-
else:
|
|
971
|
-
return []
|
|
972
1025
|
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
raise ValueError("Invalid tool type. Unsupported by OpenAI llm")
|
|
1026
|
+
return tool_selections
|
|
1027
|
+
else: # keep it backward-compatible
|
|
1028
|
+
tool_calls = response.message.additional_kwargs.get("tool_calls", [])
|
|
977
1029
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1030
|
+
if len(tool_calls) < 1:
|
|
1031
|
+
if error_on_no_tool_call:
|
|
1032
|
+
raise ValueError(
|
|
1033
|
+
f"Expected at least one tool call, but got {len(tool_calls)} tool calls."
|
|
1034
|
+
)
|
|
1035
|
+
else:
|
|
1036
|
+
return []
|
|
1037
|
+
|
|
1038
|
+
tool_selections = []
|
|
1039
|
+
for tool_call in tool_calls:
|
|
1040
|
+
if tool_call.type != "function":
|
|
1041
|
+
raise ValueError("Invalid tool type. Unsupported by OpenAI llm")
|
|
1042
|
+
|
|
1043
|
+
# this should handle both complete and partial jsons
|
|
1044
|
+
try:
|
|
1045
|
+
argument_dict = parse_partial_json(tool_call.function.arguments)
|
|
1046
|
+
except (ValueError, TypeError, JSONDecodeError):
|
|
1047
|
+
argument_dict = {}
|
|
1048
|
+
|
|
1049
|
+
tool_selections.append(
|
|
1050
|
+
ToolSelection(
|
|
1051
|
+
tool_id=tool_call.id,
|
|
1052
|
+
tool_name=tool_call.function.name,
|
|
1053
|
+
tool_kwargs=argument_dict,
|
|
1054
|
+
)
|
|
989
1055
|
)
|
|
990
|
-
)
|
|
991
1056
|
|
|
992
|
-
|
|
1057
|
+
return tool_selections
|
|
993
1058
|
|
|
994
1059
|
def _prepare_schema(
|
|
995
1060
|
self, llm_kwargs: Optional[Dict[str, Any]], output_cls: Type[Model]
|
{llama_index_llms_openai-0.6.4 → llama_index_llms_openai-0.6.6}/llama_index/llms/openai/responses.py
RENAMED
|
@@ -44,6 +44,7 @@ from typing import (
|
|
|
44
44
|
Type,
|
|
45
45
|
Union,
|
|
46
46
|
runtime_checkable,
|
|
47
|
+
cast,
|
|
47
48
|
)
|
|
48
49
|
|
|
49
50
|
import llama_index.core.instrumentation as instrument
|
|
@@ -67,6 +68,7 @@ from llama_index.core.base.llms.types import (
|
|
|
67
68
|
TextBlock,
|
|
68
69
|
ImageBlock,
|
|
69
70
|
ThinkingBlock,
|
|
71
|
+
ToolCallBlock,
|
|
70
72
|
)
|
|
71
73
|
from llama_index.core.bridge.pydantic import (
|
|
72
74
|
Field,
|
|
@@ -131,9 +133,15 @@ class Tokenizer(Protocol):
|
|
|
131
133
|
|
|
132
134
|
|
|
133
135
|
def force_single_tool_call(response: ChatResponse) -> None:
|
|
134
|
-
tool_calls =
|
|
136
|
+
tool_calls = [
|
|
137
|
+
block for block in response.message.blocks if isinstance(block, ToolCallBlock)
|
|
138
|
+
]
|
|
135
139
|
if len(tool_calls) > 1:
|
|
136
|
-
response.message.
|
|
140
|
+
response.message.blocks = [
|
|
141
|
+
block
|
|
142
|
+
for block in response.message.blocks
|
|
143
|
+
if not isinstance(block, ToolCallBlock)
|
|
144
|
+
] + [tool_calls[0]]
|
|
137
145
|
|
|
138
146
|
|
|
139
147
|
class OpenAIResponses(FunctionCallingLLM):
|
|
@@ -454,7 +462,6 @@ class OpenAIResponses(FunctionCallingLLM):
|
|
|
454
462
|
def _parse_response_output(output: List[ResponseOutputItem]) -> ChatResponse:
|
|
455
463
|
message = ChatMessage(role=MessageRole.ASSISTANT, blocks=[])
|
|
456
464
|
additional_kwargs = {"built_in_tool_calls": []}
|
|
457
|
-
tool_calls = []
|
|
458
465
|
blocks: List[ContentBlock] = []
|
|
459
466
|
for item in output:
|
|
460
467
|
if isinstance(item, ResponseOutputMessage):
|
|
@@ -481,7 +488,13 @@ class OpenAIResponses(FunctionCallingLLM):
|
|
|
481
488
|
elif isinstance(item, ResponseFileSearchToolCall):
|
|
482
489
|
additional_kwargs["built_in_tool_calls"].append(item)
|
|
483
490
|
elif isinstance(item, ResponseFunctionToolCall):
|
|
484
|
-
|
|
491
|
+
message.blocks.append(
|
|
492
|
+
ToolCallBlock(
|
|
493
|
+
tool_name=item.name,
|
|
494
|
+
tool_call_id=item.call_id,
|
|
495
|
+
tool_kwargs=item.arguments,
|
|
496
|
+
)
|
|
497
|
+
)
|
|
485
498
|
elif isinstance(item, ResponseFunctionWebSearch):
|
|
486
499
|
additional_kwargs["built_in_tool_calls"].append(item)
|
|
487
500
|
elif isinstance(item, ResponseComputerToolCall):
|
|
@@ -504,9 +517,6 @@ class OpenAIResponses(FunctionCallingLLM):
|
|
|
504
517
|
)
|
|
505
518
|
)
|
|
506
519
|
|
|
507
|
-
if tool_calls and message:
|
|
508
|
-
message.additional_kwargs["tool_calls"] = tool_calls
|
|
509
|
-
|
|
510
520
|
return ChatResponse(message=message, additional_kwargs=additional_kwargs)
|
|
511
521
|
|
|
512
522
|
@llm_retry_decorator
|
|
@@ -542,7 +552,6 @@ class OpenAIResponses(FunctionCallingLLM):
|
|
|
542
552
|
@staticmethod
|
|
543
553
|
def process_response_event(
|
|
544
554
|
event: ResponseStreamEvent,
|
|
545
|
-
tool_calls: List[ResponseFunctionToolCall],
|
|
546
555
|
built_in_tool_calls: List[Any],
|
|
547
556
|
additional_kwargs: Dict[str, Any],
|
|
548
557
|
current_tool_call: Optional[ResponseFunctionToolCall],
|
|
@@ -550,7 +559,6 @@ class OpenAIResponses(FunctionCallingLLM):
|
|
|
550
559
|
previous_response_id: Optional[str] = None,
|
|
551
560
|
) -> Tuple[
|
|
552
561
|
List[ContentBlock],
|
|
553
|
-
List[ResponseFunctionToolCall],
|
|
554
562
|
List[Any],
|
|
555
563
|
Dict[str, Any],
|
|
556
564
|
Optional[ResponseFunctionToolCall],
|
|
@@ -591,6 +599,7 @@ class OpenAIResponses(FunctionCallingLLM):
|
|
|
591
599
|
elif isinstance(event, ResponseTextDeltaEvent):
|
|
592
600
|
# Text content is being added
|
|
593
601
|
delta = event.delta
|
|
602
|
+
blocks.append(TextBlock(text=delta))
|
|
594
603
|
elif isinstance(event, ResponseImageGenCallPartialImageEvent):
|
|
595
604
|
# Partial image
|
|
596
605
|
if event.partial_image_b64:
|
|
@@ -609,10 +618,12 @@ class OpenAIResponses(FunctionCallingLLM):
|
|
|
609
618
|
if current_tool_call is not None:
|
|
610
619
|
current_tool_call.arguments = event.arguments
|
|
611
620
|
current_tool_call.status = "completed"
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
621
|
+
blocks.append(
|
|
622
|
+
ToolCallBlock(
|
|
623
|
+
tool_name=current_tool_call.name,
|
|
624
|
+
tool_kwargs=current_tool_call.arguments,
|
|
625
|
+
tool_call_id=current_tool_call.call_id,
|
|
626
|
+
)
|
|
616
627
|
)
|
|
617
628
|
|
|
618
629
|
# clear the current tool call
|
|
@@ -658,7 +669,6 @@ class OpenAIResponses(FunctionCallingLLM):
|
|
|
658
669
|
|
|
659
670
|
return (
|
|
660
671
|
blocks,
|
|
661
|
-
tool_calls,
|
|
662
672
|
built_in_tool_calls,
|
|
663
673
|
additional_kwargs,
|
|
664
674
|
current_tool_call,
|
|
@@ -677,7 +687,6 @@ class OpenAIResponses(FunctionCallingLLM):
|
|
|
677
687
|
)
|
|
678
688
|
|
|
679
689
|
def gen() -> ChatResponseGen:
|
|
680
|
-
tool_calls = []
|
|
681
690
|
built_in_tool_calls = []
|
|
682
691
|
additional_kwargs = {"built_in_tool_calls": []}
|
|
683
692
|
current_tool_call: Optional[ResponseFunctionToolCall] = None
|
|
@@ -691,7 +700,6 @@ class OpenAIResponses(FunctionCallingLLM):
|
|
|
691
700
|
# Process the event and update state
|
|
692
701
|
(
|
|
693
702
|
blocks,
|
|
694
|
-
tool_calls,
|
|
695
703
|
built_in_tool_calls,
|
|
696
704
|
additional_kwargs,
|
|
697
705
|
current_tool_call,
|
|
@@ -699,7 +707,6 @@ class OpenAIResponses(FunctionCallingLLM):
|
|
|
699
707
|
delta,
|
|
700
708
|
) = OpenAIResponses.process_response_event(
|
|
701
709
|
event=event,
|
|
702
|
-
tool_calls=tool_calls,
|
|
703
710
|
built_in_tool_calls=built_in_tool_calls,
|
|
704
711
|
additional_kwargs=additional_kwargs,
|
|
705
712
|
current_tool_call=current_tool_call,
|
|
@@ -721,9 +728,6 @@ class OpenAIResponses(FunctionCallingLLM):
|
|
|
721
728
|
message=ChatMessage(
|
|
722
729
|
role=MessageRole.ASSISTANT,
|
|
723
730
|
blocks=blocks,
|
|
724
|
-
additional_kwargs={"tool_calls": tool_calls}
|
|
725
|
-
if tool_calls
|
|
726
|
-
else {},
|
|
727
731
|
),
|
|
728
732
|
delta=delta,
|
|
729
733
|
raw=event,
|
|
@@ -801,7 +805,6 @@ class OpenAIResponses(FunctionCallingLLM):
|
|
|
801
805
|
)
|
|
802
806
|
|
|
803
807
|
async def gen() -> ChatResponseAsyncGen:
|
|
804
|
-
tool_calls = []
|
|
805
808
|
built_in_tool_calls = []
|
|
806
809
|
additional_kwargs = {"built_in_tool_calls": []}
|
|
807
810
|
current_tool_call: Optional[ResponseFunctionToolCall] = None
|
|
@@ -817,7 +820,6 @@ class OpenAIResponses(FunctionCallingLLM):
|
|
|
817
820
|
# Process the event and update state
|
|
818
821
|
(
|
|
819
822
|
blocks,
|
|
820
|
-
tool_calls,
|
|
821
823
|
built_in_tool_calls,
|
|
822
824
|
additional_kwargs,
|
|
823
825
|
current_tool_call,
|
|
@@ -825,7 +827,6 @@ class OpenAIResponses(FunctionCallingLLM):
|
|
|
825
827
|
delta,
|
|
826
828
|
) = OpenAIResponses.process_response_event(
|
|
827
829
|
event=event,
|
|
828
|
-
tool_calls=tool_calls,
|
|
829
830
|
built_in_tool_calls=built_in_tool_calls,
|
|
830
831
|
additional_kwargs=additional_kwargs,
|
|
831
832
|
current_tool_call=current_tool_call,
|
|
@@ -847,9 +848,6 @@ class OpenAIResponses(FunctionCallingLLM):
|
|
|
847
848
|
message=ChatMessage(
|
|
848
849
|
role=MessageRole.ASSISTANT,
|
|
849
850
|
blocks=blocks,
|
|
850
|
-
additional_kwargs={"tool_calls": tool_calls}
|
|
851
|
-
if tool_calls
|
|
852
|
-
else {},
|
|
853
851
|
),
|
|
854
852
|
delta=delta,
|
|
855
853
|
raw=event,
|
|
@@ -915,9 +913,11 @@ class OpenAIResponses(FunctionCallingLLM):
|
|
|
915
913
|
**kwargs: Any,
|
|
916
914
|
) -> List[ToolSelection]:
|
|
917
915
|
"""Predict and call the tool."""
|
|
918
|
-
tool_calls: List[
|
|
919
|
-
|
|
920
|
-
|
|
916
|
+
tool_calls: List[ToolCallBlock] = [
|
|
917
|
+
block
|
|
918
|
+
for block in response.message.blocks
|
|
919
|
+
if isinstance(block, ToolCallBlock)
|
|
920
|
+
]
|
|
921
921
|
|
|
922
922
|
if len(tool_calls) < 1:
|
|
923
923
|
if error_on_no_tool_call:
|
|
@@ -931,14 +931,14 @@ class OpenAIResponses(FunctionCallingLLM):
|
|
|
931
931
|
for tool_call in tool_calls:
|
|
932
932
|
# this should handle both complete and partial jsons
|
|
933
933
|
try:
|
|
934
|
-
argument_dict = parse_partial_json(tool_call.
|
|
935
|
-
except
|
|
934
|
+
argument_dict = parse_partial_json(cast(str, tool_call.tool_kwargs))
|
|
935
|
+
except Exception:
|
|
936
936
|
argument_dict = {}
|
|
937
937
|
|
|
938
938
|
tool_selections.append(
|
|
939
939
|
ToolSelection(
|
|
940
|
-
tool_id=tool_call.
|
|
941
|
-
tool_name=tool_call.
|
|
940
|
+
tool_id=tool_call.tool_call_id or "",
|
|
941
|
+
tool_name=tool_call.tool_name,
|
|
942
942
|
tool_kwargs=argument_dict,
|
|
943
943
|
)
|
|
944
944
|
)
|
{llama_index_llms_openai-0.6.4 → llama_index_llms_openai-0.6.6}/llama_index/llms/openai/utils.py
RENAMED
|
@@ -30,6 +30,8 @@ from llama_index.core.base.llms.types import (
|
|
|
30
30
|
AudioBlock,
|
|
31
31
|
DocumentBlock,
|
|
32
32
|
ThinkingBlock,
|
|
33
|
+
ToolCallBlock,
|
|
34
|
+
ContentBlock,
|
|
33
35
|
)
|
|
34
36
|
from llama_index.core.bridge.pydantic import BaseModel
|
|
35
37
|
|
|
@@ -61,7 +63,6 @@ O1_MODELS: Dict[str, int] = {
|
|
|
61
63
|
"gpt-5-mini-2025-08-07": 400000,
|
|
62
64
|
"gpt-5-nano": 400000,
|
|
63
65
|
"gpt-5-nano-2025-08-07": 400000,
|
|
64
|
-
"gpt-5-chat-latest": 400000,
|
|
65
66
|
"gpt-5-pro": 400000,
|
|
66
67
|
"gpt-5-pro-2025-10-06": 400000,
|
|
67
68
|
}
|
|
@@ -117,6 +118,8 @@ GPT4_MODELS: Dict[str, int] = {
|
|
|
117
118
|
"gpt-4.1-2025-04-14": 1047576,
|
|
118
119
|
"gpt-4.1-mini-2025-04-14": 1047576,
|
|
119
120
|
"gpt-4.1-nano-2025-04-14": 1047576,
|
|
121
|
+
# Latest GPT-5-chat supports setting temperature, so putting it here
|
|
122
|
+
"gpt-5-chat-latest": 128000,
|
|
120
123
|
}
|
|
121
124
|
|
|
122
125
|
AZURE_TURBO_MODELS: Dict[str, int] = {
|
|
@@ -398,6 +401,30 @@ def to_openai_message_dict(
|
|
|
398
401
|
},
|
|
399
402
|
}
|
|
400
403
|
)
|
|
404
|
+
elif isinstance(block, ToolCallBlock):
|
|
405
|
+
try:
|
|
406
|
+
function_dict = {
|
|
407
|
+
"type": "function",
|
|
408
|
+
"function": {
|
|
409
|
+
"name": block.tool_name,
|
|
410
|
+
"arguments": block.tool_kwargs,
|
|
411
|
+
},
|
|
412
|
+
"id": block.tool_call_id,
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if len(content) == 0 or content[-1]["type"] != "text":
|
|
416
|
+
content.append(
|
|
417
|
+
{"type": "text", "text": "", "tool_calls": [function_dict]}
|
|
418
|
+
)
|
|
419
|
+
elif content[-1]["type"] == "text" and "tool_calls" in content[-1]:
|
|
420
|
+
content[-1]["tool_calls"].append(function_dict)
|
|
421
|
+
elif content[-1]["type"] == "text" and "tool_calls" not in content[-1]:
|
|
422
|
+
content[-1]["tool_calls"] = [function_dict]
|
|
423
|
+
except Exception:
|
|
424
|
+
logger.warning(
|
|
425
|
+
f"It was not possible to convert ToolCallBlock with call id {block.tool_call_id or '`no call id`'} to a valid message, skipping..."
|
|
426
|
+
)
|
|
427
|
+
continue
|
|
401
428
|
else:
|
|
402
429
|
msg = f"Unsupported content block type: {type(block).__name__}"
|
|
403
430
|
raise ValueError(msg)
|
|
@@ -405,6 +432,9 @@ def to_openai_message_dict(
|
|
|
405
432
|
# NOTE: Sending a null value (None) for Tool Message to OpenAI will cause error
|
|
406
433
|
# It's only Allowed to send None if it's an Assistant Message and either a function call or tool calls were performed
|
|
407
434
|
# Reference: https://platform.openai.com/docs/api-reference/chat/create
|
|
435
|
+
already_has_tool_calls = any(
|
|
436
|
+
isinstance(block, ToolCallBlock) for block in message.blocks
|
|
437
|
+
)
|
|
408
438
|
content_txt = (
|
|
409
439
|
None
|
|
410
440
|
if content_txt == ""
|
|
@@ -412,6 +442,7 @@ def to_openai_message_dict(
|
|
|
412
442
|
and (
|
|
413
443
|
"function_call" in message.additional_kwargs
|
|
414
444
|
or "tool_calls" in message.additional_kwargs
|
|
445
|
+
or already_has_tool_calls
|
|
415
446
|
)
|
|
416
447
|
else content_txt
|
|
417
448
|
)
|
|
@@ -437,6 +468,13 @@ def to_openai_message_dict(
|
|
|
437
468
|
else content
|
|
438
469
|
),
|
|
439
470
|
}
|
|
471
|
+
if already_has_tool_calls:
|
|
472
|
+
existing_tool_calls = []
|
|
473
|
+
for c in content:
|
|
474
|
+
existing_tool_calls.extend(c.get("tool_calls", []))
|
|
475
|
+
|
|
476
|
+
if existing_tool_calls:
|
|
477
|
+
message_dict["tool_calls"] = existing_tool_calls
|
|
440
478
|
|
|
441
479
|
# TODO: O1 models do not support system prompts
|
|
442
480
|
if (
|
|
@@ -447,10 +485,14 @@ def to_openai_message_dict(
|
|
|
447
485
|
if message_dict["role"] == "system":
|
|
448
486
|
message_dict["role"] = "developer"
|
|
449
487
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
488
|
+
if (
|
|
489
|
+
"tool_calls" in message.additional_kwargs
|
|
490
|
+
or "function_call" in message.additional_kwargs
|
|
491
|
+
) and not already_has_tool_calls:
|
|
492
|
+
message_dict.update(message.additional_kwargs)
|
|
493
|
+
|
|
494
|
+
if "tool_call_id" in message.additional_kwargs:
|
|
495
|
+
message_dict["tool_call_id"] = message.additional_kwargs["tool_call_id"]
|
|
454
496
|
|
|
455
497
|
null_keys = [key for key, value in message_dict.items() if value is None]
|
|
456
498
|
# if drop_none is True, remove keys with None values
|
|
@@ -469,6 +511,8 @@ def to_openai_responses_message_dict(
|
|
|
469
511
|
"""Convert a ChatMessage to an OpenAI message dict."""
|
|
470
512
|
content = []
|
|
471
513
|
content_txt = ""
|
|
514
|
+
tool_calls = []
|
|
515
|
+
reasoning = []
|
|
472
516
|
|
|
473
517
|
for block in message.blocks:
|
|
474
518
|
if isinstance(block, TextBlock):
|
|
@@ -512,13 +556,41 @@ def to_openai_responses_message_dict(
|
|
|
512
556
|
}
|
|
513
557
|
)
|
|
514
558
|
elif isinstance(block, ThinkingBlock):
|
|
515
|
-
if block.content:
|
|
516
|
-
|
|
517
|
-
|
|
559
|
+
if block.content and "id" in block.additional_information:
|
|
560
|
+
reasoning.append(
|
|
561
|
+
{
|
|
562
|
+
"type": "reasoning",
|
|
563
|
+
"id": block.additional_information["id"],
|
|
564
|
+
"summary": [
|
|
565
|
+
{"type": "summary_text", "text": block.content or ""}
|
|
566
|
+
],
|
|
567
|
+
}
|
|
568
|
+
)
|
|
569
|
+
elif isinstance(block, ToolCallBlock):
|
|
570
|
+
tool_calls.extend(
|
|
571
|
+
[
|
|
572
|
+
{
|
|
573
|
+
"type": "function_call",
|
|
574
|
+
"arguments": block.tool_kwargs,
|
|
575
|
+
"call_id": block.tool_call_id,
|
|
576
|
+
"name": block.tool_name,
|
|
577
|
+
}
|
|
578
|
+
]
|
|
579
|
+
)
|
|
518
580
|
else:
|
|
519
581
|
msg = f"Unsupported content block type: {type(block).__name__}"
|
|
520
582
|
raise ValueError(msg)
|
|
521
583
|
|
|
584
|
+
if "tool_calls" in message.additional_kwargs:
|
|
585
|
+
message_dicts = [
|
|
586
|
+
tool_call if isinstance(tool_call, dict) else tool_call.model_dump()
|
|
587
|
+
for tool_call in message.additional_kwargs["tool_calls"]
|
|
588
|
+
]
|
|
589
|
+
|
|
590
|
+
return [*reasoning, *message_dicts]
|
|
591
|
+
elif tool_calls:
|
|
592
|
+
return [*reasoning, *tool_calls]
|
|
593
|
+
|
|
522
594
|
# NOTE: Sending a null value (None) for Tool Message to OpenAI will cause error
|
|
523
595
|
# It's only Allowed to send None if it's an Assistant Message and either a function call or tool calls were performed
|
|
524
596
|
# Reference: https://platform.openai.com/docs/api-reference/chat/create
|
|
@@ -553,13 +625,6 @@ def to_openai_responses_message_dict(
|
|
|
553
625
|
}
|
|
554
626
|
|
|
555
627
|
return message_dict
|
|
556
|
-
elif "tool_calls" in message.additional_kwargs:
|
|
557
|
-
message_dicts = [
|
|
558
|
-
tool_call if isinstance(tool_call, dict) else tool_call.model_dump()
|
|
559
|
-
for tool_call in message.additional_kwargs["tool_calls"]
|
|
560
|
-
]
|
|
561
|
-
|
|
562
|
-
return message_dicts
|
|
563
628
|
|
|
564
629
|
# there are some cases (like image generation or MCP tool call) that only support the string input
|
|
565
630
|
# this is why, if context_txt is a non-empty string, all the blocks are TextBlocks and the role is user, we return directly context_txt
|
|
@@ -596,6 +661,9 @@ def to_openai_responses_message_dict(
|
|
|
596
661
|
for key in null_keys:
|
|
597
662
|
message_dict.pop(key)
|
|
598
663
|
|
|
664
|
+
if reasoning:
|
|
665
|
+
return [*reasoning, message_dict]
|
|
666
|
+
|
|
599
667
|
return message_dict # type: ignore
|
|
600
668
|
|
|
601
669
|
|
|
@@ -648,13 +716,22 @@ def from_openai_message(
|
|
|
648
716
|
role = openai_message.role
|
|
649
717
|
# NOTE: Azure OpenAI returns function calling messages without a content key
|
|
650
718
|
if "text" in modalities and openai_message.content:
|
|
651
|
-
blocks = [TextBlock(text=openai_message.content or "")]
|
|
719
|
+
blocks: List[ContentBlock] = [TextBlock(text=openai_message.content or "")]
|
|
652
720
|
else:
|
|
653
|
-
blocks = []
|
|
721
|
+
blocks: List[ContentBlock] = []
|
|
654
722
|
|
|
655
723
|
additional_kwargs: Dict[str, Any] = {}
|
|
656
724
|
if openai_message.tool_calls:
|
|
657
725
|
tool_calls: List[ChatCompletionMessageToolCall] = openai_message.tool_calls
|
|
726
|
+
for tool_call in tool_calls:
|
|
727
|
+
if tool_call.function:
|
|
728
|
+
blocks.append(
|
|
729
|
+
ToolCallBlock(
|
|
730
|
+
tool_call_id=tool_call.id,
|
|
731
|
+
tool_name=tool_call.function.name or "",
|
|
732
|
+
tool_kwargs=tool_call.function.arguments or {},
|
|
733
|
+
)
|
|
734
|
+
)
|
|
658
735
|
additional_kwargs.update(tool_calls=tool_calls)
|
|
659
736
|
|
|
660
737
|
if openai_message.audio and "audio" in modalities:
|
|
@@ -742,6 +819,14 @@ def from_openai_message_dict(message_dict: dict) -> ChatMessage:
|
|
|
742
819
|
blocks.append(ImageBlock(image=img, detail=detail))
|
|
743
820
|
else:
|
|
744
821
|
blocks.append(ImageBlock(url=img, detail=detail))
|
|
822
|
+
elif t == "function_call":
|
|
823
|
+
blocks.append(
|
|
824
|
+
ToolCallBlock(
|
|
825
|
+
tool_call_id=elem.get("call_id"),
|
|
826
|
+
tool_name=elem.get("name", ""),
|
|
827
|
+
tool_kwargs=elem.get("arguments", {}),
|
|
828
|
+
)
|
|
829
|
+
)
|
|
745
830
|
else:
|
|
746
831
|
msg = f"Unsupported message type: {t}"
|
|
747
832
|
raise ValueError(msg)
|
|
@@ -27,13 +27,13 @@ dev = [
|
|
|
27
27
|
|
|
28
28
|
[project]
|
|
29
29
|
name = "llama-index-llms-openai"
|
|
30
|
-
version = "0.6.
|
|
30
|
+
version = "0.6.6"
|
|
31
31
|
description = "llama-index llms openai integration"
|
|
32
32
|
authors = [{name = "llama-index"}]
|
|
33
33
|
requires-python = ">=3.9,<4.0"
|
|
34
34
|
readme = "README.md"
|
|
35
35
|
license = "MIT"
|
|
36
|
-
dependencies = ["openai>=1.108.1,<2", "llama-index-core>=0.14.
|
|
36
|
+
dependencies = ["openai>=1.108.1,<2", "llama-index-core>=0.14.5,<0.15"]
|
|
37
37
|
|
|
38
38
|
[tool.codespell]
|
|
39
39
|
check-filenames = true
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{llama_index_llms_openai-0.6.4 → llama_index_llms_openai-0.6.6}/llama_index/llms/openai/__init__.py
RENAMED
|
File without changes
|
{llama_index_llms_openai-0.6.4 → llama_index_llms_openai-0.6.6}/llama_index/llms/openai/py.typed
RENAMED
|
File without changes
|