Skip to content

Commit 952af65

Browse files
committed
fix botocore malformed response handling for converse stream operations
1 parent 47a8238 commit 952af65

File tree

2 files changed

+30
-1
lines changed

2 files changed

+30
-1
lines changed

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/patches/_botocore_patches.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,9 +334,10 @@ def patched_extract_tool_calls(
334334
tool_calls.append(tool_call)
335335
return tool_calls
336336

337-
# TODO: The following code is to patch a bedrock bug that was fixed in
337+
# TODO: The following code is to patch bedrock bugs that were fixed in
338338
# opentelemetry-instrumentation-botocore==0.60b0 in:
339339
# https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3875
340+
# https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3990
340341
# Remove this code once we've bumped opentelemetry-instrumentation-botocore dependency to 0.60b0
341342
def patched_process_anthropic_claude_chunk(self, chunk):
342343
# pylint: disable=too-many-return-statements,too-many-branches
@@ -412,12 +413,30 @@ def patched_process_anthropic_claude_chunk(self, chunk):
412413
self._stream_done_callback(self._response)
413414
return
414415

416+
def patched_from_converse(cls, response: dict[str, Any], capture_content: bool) -> bedrock_utils._Choice:
417+
# be defensive about malformed responses, refer to #3958 for more context
418+
output = response.get("output", {})
419+
orig_message = output.get("message", {})
420+
if role := orig_message.get("role"):
421+
message = {"role": role}
422+
else:
423+
# amazon.titan does not serialize the role
424+
message = {}
425+
426+
if tool_calls := bedrock_utils.extract_tool_calls(orig_message, capture_content):
427+
message["tool_calls"] = tool_calls
428+
elif capture_content:
429+
message["content"] = orig_message["content"]
430+
431+
return cls(message, response["stopReason"], index=0)
432+
415433
bedrock_utils.ConverseStreamWrapper.__init__ = patched_init
416434
bedrock_utils.ConverseStreamWrapper._process_event = patched_process_event
417435
bedrock_utils.InvokeModelWithResponseStreamWrapper._process_anthropic_claude_chunk = (
418436
patched_process_anthropic_claude_chunk
419437
)
420438
bedrock_utils.extract_tool_calls = patched_extract_tool_calls
439+
bedrock_utils._Choice.from_converse = classmethod(patched_from_converse)
421440

422441
# END The OpenTelemetry Authors code
423442

aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/patches/test_instrumentation_patch.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ def _test_patched_botocore_instrumentation(self):
250250
self._test_patched_process_anthropic_claude_chunk({"location": "Seattle"}, {"location": "Seattle"})
251251
self._test_patched_process_anthropic_claude_chunk(None, None)
252252
self._test_patched_process_anthropic_claude_chunk({}, {})
253+
self._test_patched_from_converse_with_malformed_response()
253254

254255
# Bedrock Agent Runtime
255256
self.assertTrue("bedrock-agent-runtime" in _KNOWN_EXTENSIONS)
@@ -645,6 +646,15 @@ def _test_patched_extract_tool_calls(self):
645646
result = bedrock_utils.extract_tool_calls(message_with_type_tool_use, True)
646647
self.assertEqual(len(result), 1)
647648

649+
def _test_patched_from_converse_with_malformed_response(self):
650+
"""Test patched from_converse handles malformed response missing output key"""
651+
malformed_response = {"stopReason": "end_turn"}
652+
choice = bedrock_utils._Choice.from_converse(malformed_response, capture_content=False)
653+
654+
self.assertEqual(choice.finish_reason, "end_turn")
655+
self.assertEqual(choice.message, {})
656+
self.assertEqual(choice.index, 0)
657+
648658
def _test_patched_process_anthropic_claude_chunk(
649659
self, input_value: Dict[str, str], expected_output: Dict[str, str]
650660
):

0 commit comments

Comments
 (0)