llama-index-llms-openai 0.6.3__tar.gz → 0.6.5__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.
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llama-index-llms-openai
3
- Version: 0.6.3
3
+ Version: 0.6.5
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.3
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
 
@@ -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 = response.message.additional_kwargs.get("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.additional_kwargs["tool_calls"] = [tool_calls[0]]
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
- content=content,
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
- content=content,
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 = response.message.additional_kwargs.get("tool_calls", [])
964
-
965
- if len(tool_calls) < 1:
966
- if error_on_no_tool_call:
967
- raise ValueError(
968
- f"Expected at least one tool call, but got {len(tool_calls)} tool calls."
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
- tool_selections = []
974
- for tool_call in tool_calls:
975
- if tool_call.type != "function":
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
- # this should handle both complete and partial jsons
979
- try:
980
- argument_dict = parse_partial_json(tool_call.function.arguments)
981
- except (ValueError, TypeError, JSONDecodeError):
982
- argument_dict = {}
983
-
984
- tool_selections.append(
985
- ToolSelection(
986
- tool_id=tool_call.id,
987
- tool_name=tool_call.function.name,
988
- tool_kwargs=argument_dict,
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
- return tool_selections
1057
+ return tool_selections
993
1058
 
994
1059
  def _prepare_schema(
995
1060
  self, llm_kwargs: Optional[Dict[str, Any]], output_cls: Type[Model]
@@ -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 = response.message.additional_kwargs.get("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.additional_kwargs["tool_calls"] = [tool_calls[0]]
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
- tool_calls.append(item)
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
- # append a copy of the tool call to the list
614
- tool_calls.append(
615
- ResponseFunctionToolCall(**current_tool_call.model_dump())
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[ResponseFunctionToolCall] = (
919
- response.message.additional_kwargs.get("tool_calls", [])
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.arguments)
935
- except ValueError:
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.call_id,
941
- tool_name=tool_call.name,
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
  )
@@ -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
 
@@ -199,9 +201,9 @@ JSON_SCHEMA_MODELS = [
199
201
  "o1-pro",
200
202
  "o3",
201
203
  "o3-mini",
202
- "gpt-4.1",
203
204
  "gpt-4o",
204
205
  "gpt-4.1",
206
+ "gpt-5",
205
207
  ]
206
208
 
207
209
 
@@ -398,6 +400,30 @@ def to_openai_message_dict(
398
400
  },
399
401
  }
400
402
  )
403
+ elif isinstance(block, ToolCallBlock):
404
+ try:
405
+ function_dict = {
406
+ "type": "function",
407
+ "function": {
408
+ "name": block.tool_name,
409
+ "arguments": block.tool_kwargs,
410
+ },
411
+ "id": block.tool_call_id,
412
+ }
413
+
414
+ if len(content) == 0 or content[-1]["type"] != "text":
415
+ content.append(
416
+ {"type": "text", "text": "", "tool_calls": [function_dict]}
417
+ )
418
+ elif content[-1]["type"] == "text" and "tool_calls" in content[-1]:
419
+ content[-1]["tool_calls"].append(function_dict)
420
+ elif content[-1]["type"] == "text" and "tool_calls" not in content[-1]:
421
+ content[-1]["tool_calls"] = [function_dict]
422
+ except Exception:
423
+ logger.warning(
424
+ f"It was not possible to convert ToolCallBlock with call id {block.tool_call_id or '`no call id`'} to a valid message, skipping..."
425
+ )
426
+ continue
401
427
  else:
402
428
  msg = f"Unsupported content block type: {type(block).__name__}"
403
429
  raise ValueError(msg)
@@ -405,6 +431,9 @@ def to_openai_message_dict(
405
431
  # NOTE: Sending a null value (None) for Tool Message to OpenAI will cause error
406
432
  # It's only Allowed to send None if it's an Assistant Message and either a function call or tool calls were performed
407
433
  # Reference: https://platform.openai.com/docs/api-reference/chat/create
434
+ already_has_tool_calls = any(
435
+ isinstance(block, ToolCallBlock) for block in message.blocks
436
+ )
408
437
  content_txt = (
409
438
  None
410
439
  if content_txt == ""
@@ -412,6 +441,7 @@ def to_openai_message_dict(
412
441
  and (
413
442
  "function_call" in message.additional_kwargs
414
443
  or "tool_calls" in message.additional_kwargs
444
+ or already_has_tool_calls
415
445
  )
416
446
  else content_txt
417
447
  )
@@ -437,6 +467,13 @@ def to_openai_message_dict(
437
467
  else content
438
468
  ),
439
469
  }
470
+ if already_has_tool_calls:
471
+ existing_tool_calls = []
472
+ for c in content:
473
+ existing_tool_calls.extend(c.get("tool_calls", []))
474
+
475
+ if existing_tool_calls:
476
+ message_dict["tool_calls"] = existing_tool_calls
440
477
 
441
478
  # TODO: O1 models do not support system prompts
442
479
  if (
@@ -447,10 +484,14 @@ def to_openai_message_dict(
447
484
  if message_dict["role"] == "system":
448
485
  message_dict["role"] = "developer"
449
486
 
450
- # NOTE: openai messages have additional arguments:
451
- # - function messages have `name`
452
- # - assistant messages have optional `function_call`
453
- message_dict.update(message.additional_kwargs)
487
+ if (
488
+ "tool_calls" in message.additional_kwargs
489
+ or "function_call" in message.additional_kwargs
490
+ ) and not already_has_tool_calls:
491
+ message_dict.update(message.additional_kwargs)
492
+
493
+ if "tool_call_id" in message.additional_kwargs:
494
+ message_dict["tool_call_id"] = message.additional_kwargs["tool_call_id"]
454
495
 
455
496
  null_keys = [key for key, value in message_dict.items() if value is None]
456
497
  # if drop_none is True, remove keys with None values
@@ -469,6 +510,8 @@ def to_openai_responses_message_dict(
469
510
  """Convert a ChatMessage to an OpenAI message dict."""
470
511
  content = []
471
512
  content_txt = ""
513
+ tool_calls = []
514
+ reasoning = []
472
515
 
473
516
  for block in message.blocks:
474
517
  if isinstance(block, TextBlock):
@@ -512,13 +555,41 @@ def to_openai_responses_message_dict(
512
555
  }
513
556
  )
514
557
  elif isinstance(block, ThinkingBlock):
515
- if block.content:
516
- content.append({"type": "output_text", "text": block.content})
517
- content_txt += block.content
558
+ if block.content and "id" in block.additional_information:
559
+ reasoning.append(
560
+ {
561
+ "type": "reasoning",
562
+ "id": block.additional_information["id"],
563
+ "summary": [
564
+ {"type": "summary_text", "text": block.content or ""}
565
+ ],
566
+ }
567
+ )
568
+ elif isinstance(block, ToolCallBlock):
569
+ tool_calls.extend(
570
+ [
571
+ {
572
+ "type": "function_call",
573
+ "arguments": block.tool_kwargs,
574
+ "call_id": block.tool_call_id,
575
+ "name": block.tool_name,
576
+ }
577
+ ]
578
+ )
518
579
  else:
519
580
  msg = f"Unsupported content block type: {type(block).__name__}"
520
581
  raise ValueError(msg)
521
582
 
583
+ if "tool_calls" in message.additional_kwargs:
584
+ message_dicts = [
585
+ tool_call if isinstance(tool_call, dict) else tool_call.model_dump()
586
+ for tool_call in message.additional_kwargs["tool_calls"]
587
+ ]
588
+
589
+ return [*reasoning, *message_dicts]
590
+ elif tool_calls:
591
+ return [*reasoning, *tool_calls]
592
+
522
593
  # NOTE: Sending a null value (None) for Tool Message to OpenAI will cause error
523
594
  # It's only Allowed to send None if it's an Assistant Message and either a function call or tool calls were performed
524
595
  # Reference: https://platform.openai.com/docs/api-reference/chat/create
@@ -553,13 +624,6 @@ def to_openai_responses_message_dict(
553
624
  }
554
625
 
555
626
  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
627
 
564
628
  # there are some cases (like image generation or MCP tool call) that only support the string input
565
629
  # 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 +660,9 @@ def to_openai_responses_message_dict(
596
660
  for key in null_keys:
597
661
  message_dict.pop(key)
598
662
 
663
+ if reasoning:
664
+ return [*reasoning, message_dict]
665
+
599
666
  return message_dict # type: ignore
600
667
 
601
668
 
@@ -648,13 +715,22 @@ def from_openai_message(
648
715
  role = openai_message.role
649
716
  # NOTE: Azure OpenAI returns function calling messages without a content key
650
717
  if "text" in modalities and openai_message.content:
651
- blocks = [TextBlock(text=openai_message.content or "")]
718
+ blocks: List[ContentBlock] = [TextBlock(text=openai_message.content or "")]
652
719
  else:
653
- blocks = []
720
+ blocks: List[ContentBlock] = []
654
721
 
655
722
  additional_kwargs: Dict[str, Any] = {}
656
723
  if openai_message.tool_calls:
657
724
  tool_calls: List[ChatCompletionMessageToolCall] = openai_message.tool_calls
725
+ for tool_call in tool_calls:
726
+ if tool_call.function:
727
+ blocks.append(
728
+ ToolCallBlock(
729
+ tool_call_id=tool_call.id,
730
+ tool_name=tool_call.function.name or "",
731
+ tool_kwargs=tool_call.function.arguments or {},
732
+ )
733
+ )
658
734
  additional_kwargs.update(tool_calls=tool_calls)
659
735
 
660
736
  if openai_message.audio and "audio" in modalities:
@@ -742,6 +818,14 @@ def from_openai_message_dict(message_dict: dict) -> ChatMessage:
742
818
  blocks.append(ImageBlock(image=img, detail=detail))
743
819
  else:
744
820
  blocks.append(ImageBlock(url=img, detail=detail))
821
+ elif t == "function_call":
822
+ blocks.append(
823
+ ToolCallBlock(
824
+ tool_call_id=elem.get("call_id"),
825
+ tool_name=elem.get("name", ""),
826
+ tool_kwargs=elem.get("arguments", {}),
827
+ )
828
+ )
745
829
  else:
746
830
  msg = f"Unsupported message type: {t}"
747
831
  raise ValueError(msg)
@@ -27,13 +27,13 @@ dev = [
27
27
 
28
28
  [project]
29
29
  name = "llama-index-llms-openai"
30
- version = "0.6.3"
30
+ version = "0.6.5"
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.3,<0.15"]
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