From 36d5e9511a96dbb0bb9f75f3a12c30df80dab993 Mon Sep 17 00:00:00 2001 From: Hussainbeam <88007126+SYED-M-HUSSAIN@users.noreply.github.com> Date: Mon, 26 May 2025 12:20:09 +0500 Subject: [PATCH 1/2] fix(bedrock): return string content in ChatBedrockConverse streaming --- .../chat_models/bedrock_converse.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/libs/aws/langchain_aws/chat_models/bedrock_converse.py b/libs/aws/langchain_aws/chat_models/bedrock_converse.py index 9695e26f..31a8ba1b 100644 --- a/libs/aws/langchain_aws/chat_models/bedrock_converse.py +++ b/libs/aws/langchain_aws/chat_models/bedrock_converse.py @@ -1022,9 +1022,9 @@ def _parse_stream_event(event: Dict[str, Any]) -> Optional[BaseMessageChunk]: if "messageStart" in event: # TODO: needed? return ( - AIMessageChunk(content=[]) + AIMessageChunk(content="") if event["messageStart"]["role"] == "assistant" - else HumanMessageChunk(content=[]) + else HumanMessageChunk(content="") ) elif "contentBlockStart" in event: block = { @@ -1041,7 +1041,9 @@ def _parse_stream_event(event: Dict[str, Any]) -> Optional[BaseMessageChunk]: index=event["contentBlockStart"]["contentBlockIndex"], ) ) - return AIMessageChunk(content=[block], tool_call_chunks=tool_call_chunks) + # Apply the same content formatting as _parse_response + content = _str_if_single_text_block([block]) + return AIMessageChunk(content=content, tool_call_chunks=tool_call_chunks) elif "contentBlockDelta" in event: block = { **_bedrock_to_lc([event["contentBlockDelta"]["delta"]])[0], @@ -1057,19 +1059,19 @@ def _parse_stream_event(event: Dict[str, Any]) -> Optional[BaseMessageChunk]: index=event["contentBlockDelta"]["contentBlockIndex"], ) ) - return AIMessageChunk(content=[block], tool_call_chunks=tool_call_chunks) + # Apply the same content formatting as _parse_response + content = _str_if_single_text_block([block]) + return AIMessageChunk(content=content, tool_call_chunks=tool_call_chunks) elif "contentBlockStop" in event: # TODO: needed? - return AIMessageChunk( - content=[{"index": event["contentBlockStop"]["contentBlockIndex"]}] - ) + return AIMessageChunk(content="") elif "messageStop" in event: # TODO: snake case response metadata? - return AIMessageChunk(content=[], response_metadata=event["messageStop"]) + return AIMessageChunk(content="", response_metadata=event["messageStop"]) elif "metadata" in event: usage = _extract_usage_metadata(event["metadata"]) return AIMessageChunk( - content=[], response_metadata=event["metadata"], usage_metadata=usage + content="", response_metadata=event["metadata"], usage_metadata=usage ) elif "Exception" in list(event.keys())[0]: name, info = list(event.items())[0] @@ -1079,7 +1081,6 @@ def _parse_stream_event(event: Dict[str, Any]) -> Optional[BaseMessageChunk]: else: raise ValueError(f"Received unsupported stream event:\n\n{event}") - def _format_data_content_block(block: dict) -> dict: """Format standard data content block to format expected by Converse API.""" if block["type"] == "image": From ebec1d510a5cd1e8da9fc1d7a0b34969ee78934f Mon Sep 17 00:00:00 2001 From: Hussainbeam <88007126+SYED-M-HUSSAIN@users.noreply.github.com> Date: Tue, 27 May 2025 15:27:56 +0500 Subject: [PATCH 2/2] This approach addresses @blanketspy99's concern while still fixing the original consistency issue. It's a more robust solution that considers both current and future use cases. --- .../chat_models/bedrock_converse.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/libs/aws/langchain_aws/chat_models/bedrock_converse.py b/libs/aws/langchain_aws/chat_models/bedrock_converse.py index 31a8ba1b..e7309539 100644 --- a/libs/aws/langchain_aws/chat_models/bedrock_converse.py +++ b/libs/aws/langchain_aws/chat_models/bedrock_converse.py @@ -1041,9 +1041,14 @@ def _parse_stream_event(event: Dict[str, Any]) -> Optional[BaseMessageChunk]: index=event["contentBlockStart"]["contentBlockIndex"], ) ) - # Apply the same content formatting as _parse_response - content = _str_if_single_text_block([block]) + # Keep tool calls as list to preserve type information + content = [block] + else: + # Convert text content to string for consistency with other LangChain models + content = _str_if_single_text_block([block]) + return AIMessageChunk(content=content, tool_call_chunks=tool_call_chunks) + elif "contentBlockDelta" in event: block = { **_bedrock_to_lc([event["contentBlockDelta"]["delta"]])[0], @@ -1059,9 +1064,14 @@ def _parse_stream_event(event: Dict[str, Any]) -> Optional[BaseMessageChunk]: index=event["contentBlockDelta"]["contentBlockIndex"], ) ) - # Apply the same content formatting as _parse_response - content = _str_if_single_text_block([block]) + # Keep tool calls as list to preserve type information + content = [block] + else: + # Convert text content to string for consistency with other LangChain models + content = _str_if_single_text_block([block]) + return AIMessageChunk(content=content, tool_call_chunks=tool_call_chunks) + elif "contentBlockStop" in event: # TODO: needed? return AIMessageChunk(content="")