Skip to content

Commit 9f3d442

Browse files
committed
Handle dict input in _decode_tool_use for Bedrock streaming
When content_block_start contains tool_use with input as empty dict {}, _decode_tool_use fails trying to json.loads() a dict. Added isinstance check to skip decoding if input is already a dict.
1 parent 428af02 commit 9f3d442

File tree

3 files changed

+84
-0
lines changed

3 files changed

+84
-0
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
1212
## Unreleased
1313

14+
### Fixed
15+
16+
- `opentelemetry-instrumentation-botocore`: Handle dict input in _decode_tool_use for Bedrock streaming
17+
([#3875](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3875))
18+
1419
## Version 1.38.0/0.59b0 (2025-10-16)
1520

1621
### Fixed

instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/extensions/bedrock_utils.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@
3939
def _decode_tool_use(tool_use):
4040
# input get sent encoded in json
4141
if "input" in tool_use:
42+
# Skip parsing if already a dict
43+
if isinstance(tool_use["input"], dict):
44+
return tool_use
4245
try:
4346
tool_use["input"] = json.loads(tool_use["input"])
4447
except json.JSONDecodeError:

instrumentation/opentelemetry-instrumentation-botocore/tests/test_botocore_bedrock.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2975,6 +2975,82 @@ def test_invoke_model_with_response_stream_invalid_model(
29752975
assert len(logs) == 0
29762976

29772977

2978+
@pytest.mark.parametrize(
2979+
"input_value,expected_output",
2980+
[
2981+
({"location": "Seattle"}, {"location": "Seattle"}),
2982+
({}, {}),
2983+
(None, None),
2984+
],
2985+
)
2986+
def test_anthropic_claude_chunk_tool_use_input_handling(
2987+
input_value, expected_output
2988+
):
2989+
"""Test that _process_anthropic_claude_chunk handles various tool_use input formats."""
2990+
from opentelemetry.instrumentation.botocore.extensions.bedrock_utils import (
2991+
InvokeModelWithResponseStreamWrapper,
2992+
)
2993+
2994+
def stream_done_callback(response, ended):
2995+
pass
2996+
2997+
def stream_error_callback(exc, ended):
2998+
pass
2999+
3000+
wrapper = InvokeModelWithResponseStreamWrapper(
3001+
stream=mock.MagicMock(),
3002+
stream_done_callback=stream_done_callback,
3003+
stream_error_callback=stream_error_callback,
3004+
model_id="anthropic.claude-3-5-sonnet-20240620-v1:0",
3005+
)
3006+
3007+
# Simulate message_start
3008+
wrapper._process_anthropic_claude_chunk(
3009+
{
3010+
"type": "message_start",
3011+
"message": {
3012+
"role": "assistant",
3013+
"content": [],
3014+
},
3015+
}
3016+
)
3017+
3018+
# Simulate content_block_start with specified input
3019+
content_block = {
3020+
"type": "tool_use",
3021+
"id": "test_id",
3022+
"name": "test_tool",
3023+
}
3024+
if input_value is not None:
3025+
content_block["input"] = input_value
3026+
3027+
wrapper._process_anthropic_claude_chunk(
3028+
{
3029+
"type": "content_block_start",
3030+
"index": 0,
3031+
"content_block": content_block,
3032+
}
3033+
)
3034+
3035+
# Simulate content_block_stop
3036+
wrapper._process_anthropic_claude_chunk(
3037+
{"type": "content_block_stop", "index": 0}
3038+
)
3039+
3040+
# Verify the message content
3041+
assert len(wrapper._message["content"]) == 1
3042+
tool_block = wrapper._message["content"][0]
3043+
assert tool_block["type"] == "tool_use"
3044+
assert tool_block["id"] == "test_id"
3045+
assert tool_block["name"] == "test_tool"
3046+
3047+
if expected_output is not None:
3048+
assert tool_block["input"] == expected_output
3049+
assert isinstance(tool_block["input"], dict)
3050+
else:
3051+
assert "input" not in tool_block
3052+
3053+
29783054
def amazon_nova_messages():
29793055
return [
29803056
{"role": "user", "content": [{"text": "Say this is a test"}]},

0 commit comments

Comments
 (0)