Skip to content

Commit 7bd0895

Browse files
authored
botocore: add dict type check before parsing tool_use from Bedrock messages (#3548)
Amazon Bedrock tool use extraction logic doesn't agree with som of the use cases (conversation construction), such as the case with pre-created assistant message: 'messages': [{"role": "user", "content": "Placeholder text."}, {"role": "assistant", "content": "{"}], This PR addresses this by adding a dict type check on the message content before attempting to get the value of `tool_use`from the content.
1 parent a912c9e commit 7bd0895

File tree

4 files changed

+138
-1
lines changed

4 files changed

+138
-1
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3737
([#3520](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3520))
3838
- `opentelemetry-instrumentation-botocore` Ensure spans end on early stream closure for Bedrock Streaming APIs
3939
([#3481](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3481))
40+
- `opentelemetry-instrumentation-botocore` Add type check when extracting tool use from Bedrock request message content
41+
([#3548](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3548))
4042

4143
### Breaking changes
4244

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,9 @@ def extract_tool_calls(
409409
tool_uses = [item["toolUse"] for item in content if "toolUse" in item]
410410
if not tool_uses:
411411
tool_uses = [
412-
item for item in content if item.get("type") == "tool_use"
412+
item
413+
for item in content
414+
if isinstance(item, dict) and item.get("type") == "tool_use"
413415
]
414416
tool_id_key = "id"
415417
else:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
interactions:
2+
- request:
3+
body: |-
4+
{
5+
"messages": [
6+
{
7+
"role": "user",
8+
"content": "say this is a test"
9+
},
10+
{
11+
"role": "assistant",
12+
"content": "{"
13+
}
14+
],
15+
"max_tokens": 10,
16+
"anthropic_version": "bedrock-2023-05-31"
17+
}
18+
headers:
19+
Content-Length:
20+
- '165'
21+
User-Agent:
22+
- Boto3/1.28.80 md/Botocore#1.31.80 ua/2.0 os/macos#24.5.0 md/arch#arm64 lang/python#3.12.0
23+
md/pyimpl#CPython cfg/retry-mode#legacy Botocore/1.31.80
24+
X-Amz-Date:
25+
- 20250528T092318Z
26+
X-Amz-Security-Token:
27+
- test_aws_security_token
28+
X-Amzn-Trace-Id:
29+
- Root=1-313b2456-f7fd3ee19dd37dd03a4be55a;Parent=1533ee3b8e477491;Sampled=1
30+
amz-sdk-invocation-id:
31+
- 0a756b13-c341-415c-b114-337ce59bdd11
32+
amz-sdk-request:
33+
- attempt=1
34+
authorization:
35+
- Bearer test_aws_authorization
36+
method: POST
37+
uri: https://bedrock-runtime.us-east-1.amazonaws.com/model/us.anthropic.claude-3-5-haiku-20241022-v1%3A0/invoke
38+
response:
39+
body:
40+
string: |-
41+
{
42+
"id": "msg_bdrk_01Rxpterf4rGAoFZVDpEqFEF",
43+
"type": "message",
44+
"role": "assistant",
45+
"model": "claude-3-5-haiku-20241022",
46+
"content": [
47+
{
48+
"type": "text",
49+
"text": "this is a test}"
50+
}
51+
],
52+
"stop_reason": "end_turn",
53+
"stop_sequence": null,
54+
"usage": {
55+
"input_tokens": 13,
56+
"output_tokens": 8
57+
}
58+
}
59+
headers:
60+
Connection:
61+
- keep-alive
62+
Content-Type:
63+
- application/json
64+
Date:
65+
- Wed, 28 May 2025 09:23:18 GMT
66+
Set-Cookie: test_set_cookie
67+
X-Amzn-Bedrock-Input-Token-Count:
68+
- '13'
69+
X-Amzn-Bedrock-Invocation-Latency:
70+
- '608'
71+
X-Amzn-Bedrock-Output-Token-Count:
72+
- '8'
73+
x-amzn-RequestId:
74+
- 4458f14e-5cde-44fb-a1ea-881ed935fb73
75+
status:
76+
code: 200
77+
message: OK
78+
version: 1

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1421,6 +1421,61 @@ def test_invoke_model_with_content_user_content_as_string(
14211421
assert_message_in_logs(logs[1], "gen_ai.choice", choice_body, span)
14221422

14231423

1424+
@pytest.mark.vcr()
1425+
def test_invoke_model_with_content_assistant_content_as_string(
1426+
span_exporter,
1427+
log_exporter,
1428+
bedrock_runtime_client,
1429+
instrument_with_content,
1430+
):
1431+
llm_model_value = "us.anthropic.claude-3-5-haiku-20241022-v1:0"
1432+
max_tokens = 10
1433+
body = json.dumps(
1434+
{
1435+
"messages": [
1436+
{"role": "user", "content": "say this is a test"},
1437+
{"role": "assistant", "content": "{"},
1438+
],
1439+
"max_tokens": max_tokens,
1440+
"anthropic_version": "bedrock-2023-05-31",
1441+
}
1442+
)
1443+
response = bedrock_runtime_client.invoke_model(
1444+
body=body,
1445+
modelId=llm_model_value,
1446+
)
1447+
1448+
(span,) = span_exporter.get_finished_spans()
1449+
assert_completion_attributes_from_streaming_body(
1450+
span,
1451+
llm_model_value,
1452+
response,
1453+
"chat",
1454+
request_max_tokens=max_tokens,
1455+
)
1456+
1457+
logs = log_exporter.get_finished_logs()
1458+
assert len(logs) == 3
1459+
user_content = {"content": "say this is a test"}
1460+
assert_message_in_logs(logs[0], "gen_ai.user.message", user_content, span)
1461+
1462+
assistant_content = {"content": "{"}
1463+
assert_message_in_logs(
1464+
logs[1], "gen_ai.assistant.message", assistant_content, span
1465+
)
1466+
1467+
assistant_response_message = {
1468+
"role": "assistant",
1469+
"content": [{"type": "text", "text": "this is a test}"}],
1470+
}
1471+
choice_body = {
1472+
"index": 0,
1473+
"finish_reason": "end_turn",
1474+
"message": assistant_response_message,
1475+
}
1476+
assert_message_in_logs(logs[2], "gen_ai.choice", choice_body, span)
1477+
1478+
14241479
@pytest.mark.parametrize(
14251480
"model_family",
14261481
["amazon.nova", "anthropic.claude"],

0 commit comments

Comments
 (0)