llama-index-llms-bedrock-converse 0.10.7__tar.gz → 0.11.1__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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llama-index-llms-bedrock-converse
3
- Version: 0.10.7
3
+ Version: 0.11.1
4
4
  Summary: llama-index llms bedrock converse integration
5
5
  Author-email: Your Name <you@example.com>
6
6
  License-Expression: MIT
@@ -8,7 +8,7 @@ License-File: LICENSE
8
8
  Requires-Python: <4.0,>=3.9
9
9
  Requires-Dist: aioboto3<16,>=15.0.0
10
10
  Requires-Dist: boto3<2,>=1.38.27
11
- Requires-Dist: llama-index-core<0.15,>=0.14.3
11
+ Requires-Dist: llama-index-core<0.15,>=0.14.5
12
12
  Description-Content-Type: text/markdown
13
13
 
14
14
  # LlamaIndex Llms Integration: Bedrock Converse
@@ -24,6 +24,7 @@ from llama_index.core.base.llms.types import (
24
24
  MessageRole,
25
25
  TextBlock,
26
26
  ThinkingBlock,
27
+ ToolCallBlock,
27
28
  )
28
29
  from llama_index.core.bridge.pydantic import Field, PrivateAttr
29
30
  from llama_index.core.callbacks import CallbackManager
@@ -365,7 +366,7 @@ class BedrockConverse(FunctionCallingLLM):
365
366
  def _get_content_and_tool_calls(
366
367
  self, response: Optional[Dict[str, Any]] = None, content: Dict[str, Any] = None
367
368
  ) -> Tuple[
368
- List[Union[TextBlock, ThinkingBlock]], Dict[str, Any], List[str], List[str]
369
+ List[Union[TextBlock, ThinkingBlock, ToolCallBlock]], List[str], List[str]
369
370
  ]:
370
371
  assert response is not None or content is not None, (
371
372
  f"Either response or content must be provided. Got response: {response}, content: {content}"
@@ -373,10 +374,9 @@ class BedrockConverse(FunctionCallingLLM):
373
374
  assert response is None or content is None, (
374
375
  f"Only one of response or content should be provided. Got response: {response}, content: {content}"
375
376
  )
376
- tool_calls = []
377
377
  tool_call_ids = []
378
378
  status = []
379
- blocks = []
379
+ blocks: List[TextBlock | ThinkingBlock | ToolCallBlock] = []
380
380
  if content is not None:
381
381
  content_list = [content]
382
382
  else:
@@ -401,7 +401,13 @@ class BedrockConverse(FunctionCallingLLM):
401
401
  tool_usage["toolUseId"] = content_block["toolUseId"]
402
402
  if "name" not in tool_usage:
403
403
  tool_usage["name"] = content_block["name"]
404
- tool_calls.append(tool_usage)
404
+ blocks.append(
405
+ ToolCallBlock(
406
+ tool_name=tool_usage.get("name", ""),
407
+ tool_call_id=tool_usage.get("toolUseId"),
408
+ tool_kwargs=tool_usage.get("input", {}),
409
+ )
410
+ )
405
411
  if tool_result := content_block.get("toolResult", None):
406
412
  for tool_result_content in tool_result["content"]:
407
413
  if text := tool_result_content.get("text", None):
@@ -409,7 +415,7 @@ class BedrockConverse(FunctionCallingLLM):
409
415
  tool_call_ids.append(tool_result_content.get("toolUseId", ""))
410
416
  status.append(tool_result.get("status", ""))
411
417
 
412
- return blocks, tool_calls, tool_call_ids, status
418
+ return blocks, tool_call_ids, status
413
419
 
414
420
  @llm_chat_callback()
415
421
  def chat(self, messages: Sequence[ChatMessage], **kwargs: Any) -> ChatResponse:
@@ -436,16 +442,13 @@ class BedrockConverse(FunctionCallingLLM):
436
442
  **all_kwargs,
437
443
  )
438
444
 
439
- blocks, tool_calls, tool_call_ids, status = self._get_content_and_tool_calls(
440
- response
441
- )
445
+ blocks, tool_call_ids, status = self._get_content_and_tool_calls(response)
442
446
 
443
447
  return ChatResponse(
444
448
  message=ChatMessage(
445
449
  role=MessageRole.ASSISTANT,
446
450
  blocks=blocks,
447
451
  additional_kwargs={
448
- "tool_calls": tool_calls,
449
452
  "tool_call_id": tool_call_ids,
450
453
  "status": status,
451
454
  },
@@ -540,7 +543,7 @@ class BedrockConverse(FunctionCallingLLM):
540
543
  current_tool_call, tool_use_delta
541
544
  )
542
545
 
543
- blocks: List[Union[TextBlock, ThinkingBlock]] = [
546
+ blocks: List[Union[TextBlock, ThinkingBlock, ToolCallBlock]] = [
544
547
  TextBlock(text=content.get("text", ""))
545
548
  ]
546
549
  if thinking != "":
@@ -553,13 +556,21 @@ class BedrockConverse(FunctionCallingLLM):
553
556
  },
554
557
  ),
555
558
  )
559
+ if tool_calls:
560
+ for tool_call in tool_calls:
561
+ blocks.append(
562
+ ToolCallBlock(
563
+ tool_kwargs=tool_call.get("input", {}),
564
+ tool_name=tool_call.get("name", ""),
565
+ tool_call_id=tool_call.get("toolUseId"),
566
+ )
567
+ )
556
568
 
557
569
  yield ChatResponse(
558
570
  message=ChatMessage(
559
571
  role=role,
560
572
  blocks=blocks,
561
573
  additional_kwargs={
562
- "tool_calls": tool_calls,
563
574
  "tool_call_id": [
564
575
  tc.get("toolUseId", "") for tc in tool_calls
565
576
  ],
@@ -579,7 +590,7 @@ class BedrockConverse(FunctionCallingLLM):
579
590
  # Add to our list of tool calls
580
591
  tool_calls.append(current_tool_call)
581
592
 
582
- blocks: List[Union[TextBlock, ThinkingBlock]] = [
593
+ blocks: List[Union[TextBlock, ThinkingBlock, ToolCallBlock]] = [
583
594
  TextBlock(text=content.get("text", ""))
584
595
  ]
585
596
  if thinking != "":
@@ -593,12 +604,21 @@ class BedrockConverse(FunctionCallingLLM):
593
604
  ),
594
605
  )
595
606
 
607
+ if tool_calls:
608
+ for tool_call in tool_calls:
609
+ blocks.append(
610
+ ToolCallBlock(
611
+ tool_kwargs=tool_call.get("input", {}),
612
+ tool_name=tool_call.get("name", ""),
613
+ tool_call_id=tool_call.get("toolUseId"),
614
+ )
615
+ )
616
+
596
617
  yield ChatResponse(
597
618
  message=ChatMessage(
598
619
  role=role,
599
620
  blocks=blocks,
600
621
  additional_kwargs={
601
- "tool_calls": tool_calls,
602
622
  "tool_call_id": [
603
623
  tc.get("toolUseId", "") for tc in tool_calls
604
624
  ],
@@ -615,7 +635,7 @@ class BedrockConverse(FunctionCallingLLM):
615
635
  # Handle metadata event - this contains the final token usage
616
636
  if usage := metadata.get("usage"):
617
637
  # Yield a final response with correct token usage
618
- blocks: List[Union[TextBlock, ThinkingBlock]] = [
638
+ blocks: List[Union[TextBlock, ThinkingBlock, ToolCallBlock]] = [
619
639
  TextBlock(text=content.get("text", ""))
620
640
  ]
621
641
  if thinking != "":
@@ -628,13 +648,21 @@ class BedrockConverse(FunctionCallingLLM):
628
648
  },
629
649
  ),
630
650
  )
651
+ if tool_calls:
652
+ for tool_call in tool_calls:
653
+ blocks.append(
654
+ ToolCallBlock(
655
+ tool_kwargs=tool_call.get("input", {}),
656
+ tool_name=tool_call.get("name", ""),
657
+ tool_call_id=tool_call.get("toolUseId"),
658
+ )
659
+ )
631
660
 
632
661
  yield ChatResponse(
633
662
  message=ChatMessage(
634
663
  role=role,
635
664
  blocks=blocks,
636
665
  additional_kwargs={
637
- "tool_calls": tool_calls,
638
666
  "tool_call_id": [
639
667
  tc.get("toolUseId", "") for tc in tool_calls
640
668
  ],
@@ -685,16 +713,13 @@ class BedrockConverse(FunctionCallingLLM):
685
713
  **all_kwargs,
686
714
  )
687
715
 
688
- blocks, tool_calls, tool_call_ids, status = self._get_content_and_tool_calls(
689
- response
690
- )
716
+ blocks, tool_call_ids, status = self._get_content_and_tool_calls(response)
691
717
 
692
718
  return ChatResponse(
693
719
  message=ChatMessage(
694
720
  role=MessageRole.ASSISTANT,
695
721
  blocks=blocks,
696
722
  additional_kwargs={
697
- "tool_calls": tool_calls,
698
723
  "tool_call_id": tool_call_ids,
699
724
  "status": status,
700
725
  },
@@ -789,7 +814,7 @@ class BedrockConverse(FunctionCallingLLM):
789
814
  current_tool_call = join_two_dicts(
790
815
  current_tool_call, tool_use_delta
791
816
  )
792
- blocks: List[Union[TextBlock, ThinkingBlock]] = [
817
+ blocks: List[Union[TextBlock, ThinkingBlock, ToolCallBlock]] = [
793
818
  TextBlock(text=content.get("text", ""))
794
819
  ]
795
820
  if thinking != "":
@@ -803,12 +828,21 @@ class BedrockConverse(FunctionCallingLLM):
803
828
  ),
804
829
  )
805
830
 
831
+ if tool_calls:
832
+ for tool_call in tool_calls:
833
+ blocks.append(
834
+ ToolCallBlock(
835
+ tool_kwargs=tool_call.get("input", {}),
836
+ tool_name=tool_call.get("name", ""),
837
+ tool_call_id=tool_call.get("toolUseId"),
838
+ )
839
+ )
840
+
806
841
  yield ChatResponse(
807
842
  message=ChatMessage(
808
843
  role=role,
809
844
  blocks=blocks,
810
845
  additional_kwargs={
811
- "tool_calls": tool_calls,
812
846
  "tool_call_id": [
813
847
  tc.get("toolUseId", "") for tc in tool_calls
814
848
  ],
@@ -828,7 +862,7 @@ class BedrockConverse(FunctionCallingLLM):
828
862
  # Add to our list of tool calls
829
863
  tool_calls.append(current_tool_call)
830
864
 
831
- blocks: List[Union[TextBlock, ThinkingBlock]] = [
865
+ blocks: List[Union[TextBlock, ThinkingBlock, ToolCallBlock]] = [
832
866
  TextBlock(text=content.get("text", ""))
833
867
  ]
834
868
  if thinking != "":
@@ -842,12 +876,21 @@ class BedrockConverse(FunctionCallingLLM):
842
876
  ),
843
877
  )
844
878
 
879
+ if tool_calls:
880
+ for tool_call in tool_calls:
881
+ blocks.append(
882
+ ToolCallBlock(
883
+ tool_kwargs=tool_call.get("input", {}),
884
+ tool_name=tool_call.get("name", ""),
885
+ tool_call_id=tool_call.get("toolUseId"),
886
+ )
887
+ )
888
+
845
889
  yield ChatResponse(
846
890
  message=ChatMessage(
847
891
  role=role,
848
892
  blocks=blocks,
849
893
  additional_kwargs={
850
- "tool_calls": tool_calls,
851
894
  "tool_call_id": [
852
895
  tc.get("toolUseId", "") for tc in tool_calls
853
896
  ],
@@ -864,7 +907,7 @@ class BedrockConverse(FunctionCallingLLM):
864
907
  # Handle metadata event - this contains the final token usage
865
908
  if usage := metadata.get("usage"):
866
909
  # Yield a final response with correct token usage
867
- blocks: List[Union[TextBlock, ThinkingBlock]] = [
910
+ blocks: List[Union[TextBlock, ThinkingBlock, ToolCallBlock]] = [
868
911
  TextBlock(text=content.get("text", ""))
869
912
  ]
870
913
  if thinking != "":
@@ -878,12 +921,21 @@ class BedrockConverse(FunctionCallingLLM):
878
921
  ),
879
922
  )
880
923
 
924
+ if tool_calls:
925
+ for tool_call in tool_calls:
926
+ blocks.append(
927
+ ToolCallBlock(
928
+ tool_kwargs=tool_call.get("input", {}),
929
+ tool_name=tool_call.get("name", ""),
930
+ tool_call_id=tool_call.get("toolUseId"),
931
+ )
932
+ )
933
+
881
934
  yield ChatResponse(
882
935
  message=ChatMessage(
883
936
  role=role,
884
937
  blocks=blocks,
885
938
  additional_kwargs={
886
- "tool_calls": tool_calls,
887
939
  "tool_call_id": [
888
940
  tc.get("toolUseId", "") for tc in tool_calls
889
941
  ],
@@ -960,7 +1012,11 @@ class BedrockConverse(FunctionCallingLLM):
960
1012
  **kwargs: Any,
961
1013
  ) -> List[ToolSelection]:
962
1014
  """Predict and call the tool."""
963
- tool_calls = response.message.additional_kwargs.get("tool_calls", [])
1015
+ tool_calls = [
1016
+ block
1017
+ for block in response.message.blocks
1018
+ if isinstance(block, ToolCallBlock)
1019
+ ]
964
1020
 
965
1021
  if len(tool_calls) < 1:
966
1022
  if error_on_no_tool_call:
@@ -972,26 +1028,23 @@ class BedrockConverse(FunctionCallingLLM):
972
1028
 
973
1029
  tool_selections = []
974
1030
  for tool_call in tool_calls:
975
- if "toolUseId" not in tool_call or "name" not in tool_call:
976
- raise ValueError("Invalid tool call.")
977
-
978
1031
  # handle empty inputs
979
1032
  argument_dict = {}
980
- if "input" in tool_call and isinstance(tool_call["input"], str):
1033
+ if isinstance(tool_call.tool_kwargs, str):
981
1034
  # TODO parse_partial_json is not perfect
982
1035
  try:
983
- argument_dict = parse_partial_json(tool_call["input"])
1036
+ argument_dict = parse_partial_json(tool_call.tool_kwargs)
984
1037
  except ValueError:
985
1038
  argument_dict = {}
986
- elif "input" in tool_call and isinstance(tool_call["input"], dict):
987
- argument_dict = tool_call["input"]
1039
+ elif isinstance(tool_call.tool_kwargs, dict):
1040
+ argument_dict = tool_call.tool_kwargs
988
1041
  else:
989
1042
  continue
990
1043
 
991
1044
  tool_selections.append(
992
1045
  ToolSelection(
993
- tool_id=tool_call["toolUseId"],
994
- tool_name=tool_call["name"],
1046
+ tool_id=tool_call.tool_call_id or "",
1047
+ tool_name=tool_call.tool_name,
995
1048
  tool_kwargs=argument_dict,
996
1049
  )
997
1050
  )
@@ -32,6 +32,7 @@ from llama_index.core.base.llms.types import (
32
32
  DocumentBlock,
33
33
  CachePoint,
34
34
  ThinkingBlock,
35
+ ToolCallBlock,
35
36
  )
36
37
 
37
38
 
@@ -188,8 +189,8 @@ def is_reasoning(model_name: str) -> bool:
188
189
 
189
190
  def get_model_name(model_name: str) -> str:
190
191
  """Extract base model name from region-prefixed model identifier."""
191
- # Check for region prefixes (us, eu, apac, global)
192
- REGION_PREFIXES = ["us.", "eu.", "apac.", "global."]
192
+ # Check for region prefixes (us, eu, apac, jp, global)
193
+ REGION_PREFIXES = ["us.", "eu.", "apac.", "jp.", "global."]
193
194
 
194
195
  # If no region prefix, return the original model name
195
196
  if not any(prefix in model_name for prefix in REGION_PREFIXES):
@@ -303,6 +304,23 @@ def _content_block_to_bedrock_format(
303
304
  elif isinstance(block, AudioBlock):
304
305
  logger.warning("Audio blocks are not supported in Bedrock Converse API.")
305
306
  return None
307
+ elif isinstance(block, ToolCallBlock):
308
+ if isinstance(block.tool_kwargs, str):
309
+ try:
310
+ tool_input = json.loads(block.tool_kwargs or "{}")
311
+ except json.JSONDecodeError:
312
+ tool_input = {}
313
+ else:
314
+ tool_input = block.tool_kwargs
315
+
316
+ return {
317
+ "toolUse": {
318
+ "input": tool_input,
319
+ "toolUseId": block.tool_call_id or "",
320
+ "name": block.tool_name,
321
+ }
322
+ }
323
+
306
324
  else:
307
325
  logger.warning(f"Unsupported block type: {type(block)}")
308
326
  return None
@@ -345,7 +363,9 @@ def messages_to_converse_messages(
345
363
  converse_messages = []
346
364
  system_prompt = []
347
365
  current_system_prompt = ""
366
+
348
367
  for message in messages:
368
+ unique_tool_calls = []
349
369
  if message.role == MessageRole.SYSTEM:
350
370
  # we iterate over blocks, if content was used, the blocks are added anyway
351
371
  for block in message.blocks:
@@ -402,6 +422,13 @@ def messages_to_converse_messages(
402
422
  )
403
423
  if bedrock_format_block:
404
424
  content.append(bedrock_format_block)
425
+ if "toolUse" in bedrock_format_block:
426
+ unique_tool_calls.append(
427
+ (
428
+ bedrock_format_block["toolUse"]["toolUseId"],
429
+ bedrock_format_block["toolUse"]["name"],
430
+ )
431
+ )
405
432
 
406
433
  if content:
407
434
  converse_messages.append(
@@ -411,6 +438,7 @@ def messages_to_converse_messages(
411
438
  }
412
439
  )
413
440
 
441
+ # keep this code here for compatibility with older chat histories
414
442
  # convert tool calls to the AWS Bedrock Converse format
415
443
  # NOTE tool calls might show up within any message,
416
444
  # e.g. within assistant message or in consecutive tool calls,
@@ -418,25 +446,28 @@ def messages_to_converse_messages(
418
446
  tool_calls = message.additional_kwargs.get("tool_calls", [])
419
447
  content = []
420
448
  for tool_call in tool_calls:
421
- assert "toolUseId" in tool_call, f"`toolUseId` not found in {tool_call}"
422
- assert "input" in tool_call, f"`input` not found in {tool_call}"
423
- assert "name" in tool_call, f"`name` not found in {tool_call}"
424
- tool_input = tool_call["input"] if tool_call["input"] else {}
425
- if isinstance(tool_input, str):
426
- try:
427
- tool_input = json.loads(tool_input or "{}")
428
- except json.JSONDecodeError:
429
- tool_input = {}
430
-
431
- content.append(
432
- {
433
- "toolUse": {
434
- "input": tool_input,
435
- "toolUseId": tool_call["toolUseId"],
436
- "name": tool_call["name"],
437
- }
438
- }
439
- )
449
+ try:
450
+ assert "toolUseId" in tool_call
451
+ assert "input" in tool_call
452
+ assert "name" in tool_call
453
+ if (tool_call["toolUseId"], tool_call["name"]) not in unique_tool_calls:
454
+ tool_input = tool_call["input"] if tool_call["input"] else {}
455
+ if isinstance(tool_input, str):
456
+ try:
457
+ tool_input = json.loads(tool_input or "{}")
458
+ except json.JSONDecodeError:
459
+ tool_input = {}
460
+ content.append(
461
+ {
462
+ "toolUse": {
463
+ "input": tool_input,
464
+ "toolUseId": tool_call["toolUseId"],
465
+ "name": tool_call["name"],
466
+ }
467
+ }
468
+ )
469
+ except AssertionError:
470
+ continue
440
471
  if len(content) > 0:
441
472
  converse_messages.append(
442
473
  {
@@ -499,9 +530,15 @@ def tools_to_converse_tools(
499
530
 
500
531
 
501
532
  def force_single_tool_call(response: ChatResponse) -> None:
502
- tool_calls = response.message.additional_kwargs.get("tool_calls", [])
533
+ tool_calls = [
534
+ block for block in response.message.blocks if isinstance(block, ToolCallBlock)
535
+ ]
503
536
  if len(tool_calls) > 1:
504
- response.message.additional_kwargs["tool_calls"] = [tool_calls[0]]
537
+ response.message.blocks = [
538
+ block
539
+ for block in response.message.blocks
540
+ if not isinstance(block, ToolCallBlock)
541
+ ] + [tool_calls[0]]
505
542
 
506
543
 
507
544
  def _create_retry_decorator(client: Any, max_retries: int) -> Callable[[Any], Any]:
@@ -29,7 +29,7 @@ dev = [
29
29
 
30
30
  [project]
31
31
  name = "llama-index-llms-bedrock-converse"
32
- version = "0.10.7"
32
+ version = "0.11.1"
33
33
  description = "llama-index llms bedrock converse integration"
34
34
  authors = [{name = "Your Name", email = "you@example.com"}]
35
35
  requires-python = ">=3.9,<4.0"
@@ -38,7 +38,7 @@ license = "MIT"
38
38
  dependencies = [
39
39
  "boto3>=1.38.27,<2",
40
40
  "aioboto3>=15.0.0,<16",
41
- "llama-index-core>=0.14.3,<0.15",
41
+ "llama-index-core>=0.14.5,<0.15",
42
42
  ]
43
43
 
44
44
  [tool.codespell]
@@ -65,4 +65,4 @@ BedrockConverse = "AndreCNF"
65
65
  disallow_untyped_defs = true
66
66
  exclude = ["_static", "build", "examples", "notebooks", "venv"]
67
67
  ignore_missing_imports = true
68
- python_version = "3.8"
68
+ python_version = "3.10"