amazon-bedrock-haystack 3.9.1__py3-none-any.whl → 3.10.0__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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amazon-bedrock-haystack
3
- Version: 3.9.1
3
+ Version: 3.10.0
4
4
  Summary: An integration of Amazon Bedrock as an AmazonBedrockGenerator component.
5
5
  Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/amazon_bedrock#readme
6
6
  Project-URL: Issues, https://github.com/deepset-ai/haystack-core-integrations/issues
@@ -12,11 +12,11 @@ haystack_integrations/components/generators/amazon_bedrock/adapters.py,sha256=yB
12
12
  haystack_integrations/components/generators/amazon_bedrock/generator.py,sha256=c_saV5zxFYQVJT0Hzo80lKty46itL0Dp31VuDueYa3M,14716
13
13
  haystack_integrations/components/generators/amazon_bedrock/chat/__init__.py,sha256=6GZ8Y3Lw0rLOsOAqi6Tu5mZC977UzQvgDxKpOWr8IQw,110
14
14
  haystack_integrations/components/generators/amazon_bedrock/chat/chat_generator.py,sha256=iIaMsOOX9eYvR1GNgpxNKxaOli91ShrCv3MuBBK1NSs,24743
15
- haystack_integrations/components/generators/amazon_bedrock/chat/utils.py,sha256=bDNaExYhrhxLHyOdu6EHC8Ixdpg43IIPJldjddzV4GE,23236
15
+ haystack_integrations/components/generators/amazon_bedrock/chat/utils.py,sha256=g2SZV8LdLobaCZpwWCreBJn1BtS1V3-wQkpisStJrcY,29015
16
16
  haystack_integrations/components/rankers/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
17
  haystack_integrations/components/rankers/amazon_bedrock/__init__.py,sha256=Zrc3BSVkEaXYpliEi6hKG9bqW4J7DNk93p50SuoyT1Q,107
18
18
  haystack_integrations/components/rankers/amazon_bedrock/ranker.py,sha256=enAjf2QyDwfpidKkFCdLz954cx-Tjh9emrOS3vINJDg,12344
19
- amazon_bedrock_haystack-3.9.1.dist-info/METADATA,sha256=wByKDtTt_NpNsmtNh9t3-8izh0dKl04569OfCt6xR3w,2287
20
- amazon_bedrock_haystack-3.9.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
21
- amazon_bedrock_haystack-3.9.1.dist-info/licenses/LICENSE.txt,sha256=B05uMshqTA74s-0ltyHKI6yoPfJ3zYgQbvcXfDVGFf8,10280
22
- amazon_bedrock_haystack-3.9.1.dist-info/RECORD,,
19
+ amazon_bedrock_haystack-3.10.0.dist-info/METADATA,sha256=DZDchQY_Nsi4GsU4fZKTVkHxFcnn4cYuXNMjZ1VxlQg,2288
20
+ amazon_bedrock_haystack-3.10.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
21
+ amazon_bedrock_haystack-3.10.0.dist-info/licenses/LICENSE.txt,sha256=B05uMshqTA74s-0ltyHKI6yoPfJ3zYgQbvcXfDVGFf8,10280
22
+ amazon_bedrock_haystack-3.10.0.dist-info/RECORD,,
@@ -55,6 +55,11 @@ def _format_tool_call_message(tool_call_message: ChatMessage) -> Dict[str, Any]:
55
55
  Dictionary representing the tool call message in Bedrock's expected format
56
56
  """
57
57
  content: List[Dict[str, Any]] = []
58
+
59
+ # tool call messages can contain reasoning content
60
+ if reasoning_contents := tool_call_message.meta.get("reasoning_contents"):
61
+ content.extend(_format_reasoning_contents(reasoning_contents=reasoning_contents))
62
+
58
63
  # Tool call message can contain text
59
64
  if tool_call_message.text:
60
65
  content.append({"text": tool_call_message.text})
@@ -157,6 +162,24 @@ def _repair_tool_result_messages(bedrock_formatted_messages: List[Dict[str, Any]
157
162
  return [msg for _, msg in repaired_bedrock_formatted_messages]
158
163
 
159
164
 
165
+ def _format_reasoning_contents(reasoning_contents: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
166
+ """
167
+ Format reasoning contents to match Bedrock's expected structure.
168
+
169
+ :param reasoning_contents: List of reasoning content dictionaries from Haystack ChatMessage metadata.
170
+ :returns: List of formatted reasoning content dictionaries for Bedrock.
171
+ """
172
+ formatted_contents = []
173
+ for reasoning_content in reasoning_contents:
174
+ formatted_content = {"reasoningContent": reasoning_content["reasoning_content"]}
175
+ if reasoning_text := formatted_content["reasoningContent"].pop("reasoning_text", None):
176
+ formatted_content["reasoningContent"]["reasoningText"] = reasoning_text
177
+ if redacted_content := formatted_content["reasoningContent"].pop("redacted_content", None):
178
+ formatted_content["reasoningContent"]["redactedContent"] = redacted_content
179
+ formatted_contents.append(formatted_content)
180
+ return formatted_contents
181
+
182
+
160
183
  def _format_text_image_message(message: ChatMessage) -> Dict[str, Any]:
161
184
  """
162
185
  Format a Haystack ChatMessage containing text and optional image content into Bedrock format.
@@ -168,6 +191,10 @@ def _format_text_image_message(message: ChatMessage) -> Dict[str, Any]:
168
191
  content_parts = message._content
169
192
 
170
193
  bedrock_content_blocks: List[Dict[str, Any]] = []
194
+ # Add reasoning content if available as the first content block
195
+ if message.meta.get("reasoning_contents"):
196
+ bedrock_content_blocks.extend(_format_reasoning_contents(reasoning_contents=message.meta["reasoning_contents"]))
197
+
171
198
  for part in content_parts:
172
199
  if isinstance(part, TextContent):
173
200
  bedrock_content_blocks.append({"text": part.text})
@@ -221,7 +248,6 @@ def _format_messages(messages: List[ChatMessage]) -> Tuple[List[Dict[str, Any]],
221
248
  return system_prompts, repaired_bedrock_formatted_messages
222
249
 
223
250
 
224
- # Bedrock to Haystack util method
225
251
  def _parse_completion_response(response_body: Dict[str, Any], model: str) -> List[ChatMessage]:
226
252
  """
227
253
  Parse a Bedrock API response into Haystack ChatMessage objects.
@@ -255,6 +281,7 @@ def _parse_completion_response(response_body: Dict[str, Any], model: str) -> Lis
255
281
  # Process all content blocks and combine them into a single message
256
282
  text_content = []
257
283
  tool_calls = []
284
+ reasoning_contents = []
258
285
  for content_block in content_blocks:
259
286
  if "text" in content_block:
260
287
  text_content.append(content_block["text"])
@@ -267,6 +294,17 @@ def _parse_completion_response(response_body: Dict[str, Any], model: str) -> Lis
267
294
  arguments=tool_use.get("input", {}),
268
295
  )
269
296
  tool_calls.append(tool_call)
297
+ elif "reasoningContent" in content_block:
298
+ reasoning_content = content_block["reasoningContent"]
299
+ # If reasoningText is present, replace it with reasoning_text
300
+ if "reasoningText" in reasoning_content:
301
+ reasoning_content["reasoning_text"] = reasoning_content.pop("reasoningText")
302
+ if "redactedContent" in reasoning_content:
303
+ reasoning_content["redacted_content"] = reasoning_content.pop("redactedContent")
304
+ reasoning_contents.append({"reasoning_content": reasoning_content})
305
+
306
+ # If reasoning contents were found, add them to the base meta
307
+ base_meta.update({"reasoning_contents": reasoning_contents})
270
308
 
271
309
  # Create a single ChatMessage with combined text and tool calls
272
310
  replies.append(ChatMessage.from_assistant(" ".join(text_content), tool_calls=tool_calls, meta=base_meta))
@@ -274,7 +312,6 @@ def _parse_completion_response(response_body: Dict[str, Any], model: str) -> Lis
274
312
  return replies
275
313
 
276
314
 
277
- # Bedrock streaming to Haystack util methods
278
315
  def _convert_event_to_streaming_chunk(
279
316
  event: Dict[str, Any], model: str, component_info: ComponentInfo
280
317
  ) -> StreamingChunk:
@@ -367,6 +404,22 @@ def _convert_event_to_streaming_chunk(
367
404
  "received_at": datetime.now(timezone.utc).isoformat(),
368
405
  },
369
406
  )
407
+ # This is for accumulating reasoning content deltas
408
+ elif "reasoningContent" in delta:
409
+ reasoning_content = delta["reasoningContent"]
410
+ if "redactedContent" in reasoning_content:
411
+ reasoning_content["redacted_content"] = reasoning_content.pop("redactedContent")
412
+ streaming_chunk = StreamingChunk(
413
+ content="",
414
+ meta={
415
+ "model": model,
416
+ "index": 0,
417
+ "tool_calls": None,
418
+ "finish_reason": None,
419
+ "received_at": datetime.now(timezone.utc).isoformat(),
420
+ "reasoning_contents": [{"index": block_idx, "reasoning_content": reasoning_content}],
421
+ },
422
+ )
370
423
 
371
424
  elif "messageStop" in event:
372
425
  finish_reason = event["messageStop"].get("stopReason")
@@ -406,6 +459,66 @@ def _convert_event_to_streaming_chunk(
406
459
  return streaming_chunk
407
460
 
408
461
 
462
+ def _process_reasoning_contents(chunks: List[StreamingChunk]) -> List[Dict[str, Any]]:
463
+ """
464
+ Process reasoning contents from a list of StreamingChunk objects into the Bedrock expected format.
465
+
466
+ :param chunks: List of StreamingChunk objects potentially containing reasoning contents.
467
+
468
+ :returns: List of Bedrock formatted reasoning content dictionaries
469
+ """
470
+ formatted_reasoning_contents = []
471
+ current_index = None
472
+ reasoning_text = ""
473
+ reasoning_signature = None
474
+ redacted_content = None
475
+ for chunk in chunks:
476
+ reasoning_contents = chunk.meta.get("reasoning_contents", [])
477
+
478
+ for reasoning_content in reasoning_contents:
479
+ content_block_index = reasoning_content["index"]
480
+
481
+ # Start new group when index changes
482
+ if current_index is not None and content_block_index != current_index:
483
+ # Finalize current group
484
+ if reasoning_text:
485
+ formatted_reasoning_contents.append(
486
+ {
487
+ "reasoning_content": {
488
+ "reasoning_text": {"text": reasoning_text, "signature": reasoning_signature},
489
+ }
490
+ }
491
+ )
492
+ if redacted_content:
493
+ formatted_reasoning_contents.append({"reasoning_content": {"redacted_content": redacted_content}})
494
+ reasoning_text = ""
495
+ reasoning_signature = None
496
+ redacted_content = None
497
+
498
+ # Accumulate content for current index
499
+ current_index = content_block_index
500
+ reasoning_text += reasoning_content["reasoning_content"].get("text", "")
501
+ if "redacted_content" in reasoning_content["reasoning_content"]:
502
+ redacted_content = reasoning_content["reasoning_content"]["redacted_content"]
503
+ if "signature" in reasoning_content["reasoning_content"]:
504
+ reasoning_signature = reasoning_content["reasoning_content"]["signature"]
505
+
506
+ # Finalize the last group
507
+ if current_index is not None:
508
+ if reasoning_text:
509
+ formatted_reasoning_contents.append(
510
+ {
511
+ "reasoning_content": {
512
+ "reasoning_text": {"text": reasoning_text, "signature": reasoning_signature},
513
+ }
514
+ }
515
+ )
516
+ if redacted_content:
517
+ formatted_reasoning_contents.append({"reasoning_content": {"redacted_content": redacted_content}})
518
+
519
+ return formatted_reasoning_contents
520
+
521
+
409
522
  def _convert_streaming_chunks_to_chat_message(chunks: List[StreamingChunk]) -> ChatMessage:
410
523
  """
411
524
  Converts a list of streaming chunks into a ChatMessage object.
@@ -421,8 +534,12 @@ def _convert_streaming_chunks_to_chat_message(chunks: List[StreamingChunk]) -> C
421
534
  A ChatMessage object constructed from the streaming chunks, containing the aggregated text, processed tool
422
535
  calls, and metadata.
423
536
  """
537
+ # Join all text content from the chunks
424
538
  text = "".join([chunk.content for chunk in chunks])
425
539
 
540
+ # If reasoning content is present in any chunk, accumulate it
541
+ reasoning_contents = _process_reasoning_contents(chunks=chunks)
542
+
426
543
  # Process tool calls if present in any chunk
427
544
  tool_calls = []
428
545
  tool_call_data: Dict[int, Dict[str, str]] = {} # Track tool calls by index
@@ -474,6 +591,7 @@ def _convert_streaming_chunks_to_chat_message(chunks: List[StreamingChunk]) -> C
474
591
  "finish_reason": finish_reason,
475
592
  "completion_start_time": chunks[0].meta.get("received_at"), # first chunk received
476
593
  "usage": usage,
594
+ "reasoning_contents": reasoning_contents,
477
595
  }
478
596
 
479
597
  return ChatMessage.from_assistant(text=text or None, tool_calls=tool_calls, meta=meta)