Skip to content

Commit cf28145

Browse files
octavifsnirga
andauthored
fix(bedrock): handle non-text contentBlockDelta events in converse_stream (#3404)
Co-authored-by: Nir Gazit <[email protected]>
1 parent 4c13c34 commit cf28145

File tree

3 files changed

+219
-1
lines changed

3 files changed

+219
-1
lines changed

packages/opentelemetry-instrumentation-bedrock/opentelemetry/instrumentation/bedrock/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ def wrap(*args, **kwargs):
388388
span = kwargs.pop("span")
389389
event = func(*args, **kwargs)
390390
nonlocal role
391-
if "contentBlockDelta" in event:
391+
if "contentBlockDelta" in event and "text" in event["contentBlockDelta"].get("delta", {}):
392392
response_msg.append(event["contentBlockDelta"]["delta"]["text"])
393393
elif "messageStart" in event:
394394
role = event["messageStart"]["role"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
interactions:
2+
- request:
3+
body: '{"messages": [{"role": "user", "content": [{"text": "What is the weather
4+
in Barcelona?"}]}], "toolConfig": {"tools": [{"toolSpec": {"name": "get_weather",
5+
"description": "Get the current weather for a location", "inputSchema": {"json":
6+
{"type": "object", "properties": {"location": {"type": "string", "description":
7+
"The city name"}}, "required": ["location"]}}}}]}, "inferenceConfig": {"maxTokens":
8+
300, "temperature": 0.5}}'
9+
headers:
10+
Content-Length:
11+
- '425'
12+
Content-Type:
13+
- !!binary |
14+
YXBwbGljYXRpb24vanNvbg==
15+
User-Agent:
16+
- !!binary |
17+
Qm90bzMvMS4zNC4xNDUgbWQvQm90b2NvcmUjMS4zNC4xNDUgdWEvMi4wIG9zL21hY29zIzI0LjYu
18+
MCBtZC9hcmNoI2FybTY0IGxhbmcvcHl0aG9uIzMuMTEuMTIgbWQvcHlpbXBsI0NQeXRob24gY2Zn
19+
L3JldHJ5LW1vZGUjbGVnYWN5IEJvdG9jb3JlLzEuMzQuMTQ1
20+
X-Amz-Date:
21+
- !!binary |
22+
MjAyNTEwMDlUMTUzNzI5Wg==
23+
amz-sdk-invocation-id:
24+
- !!binary |
25+
MTg2NWQ3NDUtYjEzNS00NGY5LThjZjUtZTMzMjQwMTEyZWEw
26+
amz-sdk-request:
27+
- !!binary |
28+
YXR0ZW1wdD0x
29+
method: POST
30+
uri: https://bedrock-runtime.us-east-1.amazonaws.com/model/anthropic.claude-3-sonnet-20240229-v1%3A0/converse-stream
31+
response:
32+
body:
33+
string: !!binary |
34+
AAAAoAAAAFKQYHAnCzpldmVudC10eXBlBwAMbWVzc2FnZVN0YXJ0DTpjb250ZW50LXR5cGUHABBh
35+
cHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsicCI6ImFiY2RlZmdoaWprbG1u
36+
b3BxcnN0dXZ3eHl6QUJDREVGR0hJIiwicm9sZSI6ImFzc2lzdGFudCJ9CdeWBQAAALwAAABXRRr+
37+
Kws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNh
38+
dGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRl
39+
bHRhIjp7InRleHQiOiJPa2F5In0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RF
40+
In0w4o7XAAAAvgAAAFc/2q1LCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRl
41+
bnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50
42+
QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsidGV4dCI6IiwifSwicCI6ImFiY2RlZmdoaWprbG1ub3Bx
43+
cnN0dXZ3eHl6QUJDREVGR0hJSiJ9VK8L7wAAALUAAABXSAqcWgs6ZXZlbnQtdHlwZQcAEWNvbnRl
44+
bnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5
45+
cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InRleHQiOiIgbGV0In0s
46+
InAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3gifVP2VB8AAADLAAAAVw7owvQLOmV2ZW50LXR5
47+
cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06
48+
bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJ0ZXh0
49+
IjoiIG1lIn0sInAiOiJhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1BR
50+
UlNUVSJ9PuPo+AAAAKgAAABX0HrPaQs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpj
51+
b250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29u
52+
dGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InRleHQiOiIgY2hlY2sifSwicCI6ImFiY2RlZmdo
53+
aSJ9Gp4XkQAAAKAAAABX4AqEqAs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250
54+
ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVu
55+
dEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InRleHQiOiIgdGhlIn0sInAiOiJhYmMifePRLc8AAAC7
56+
AAAAV/c6IjsLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQ
57+
YXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4
58+
IjowLCJkZWx0YSI6eyJ0ZXh0IjoiIHdlYXRoZXIifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0
59+
dXZ3eHl6In0qhm0SAAAArAAAAFcl+mmpCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGEN
60+
OmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJj
61+
b250ZW50QmxvY2tJbmRleCI6MCwiZGVsdGEiOnsidGV4dCI6IiBmb3IifSwicCI6ImFiY2RlZmdo
62+
aWprbG1ubyJ99ysW0AAAAMkAAABXdCiRlAs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRh
63+
DTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsi
64+
Y29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InRleHQiOiIgQmFyY2Vsb25hIn0sInAiOiJh
65+
YmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTCJ9dQFosgAAAMEAAABXRFjaVQs6
66+
ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlv
67+
bi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRh
68+
Ijp7InRleHQiOiIgdXNpbmcifSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVG
69+
R0gifQn6Cj4AAACiAAAAV5rK18gLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29u
70+
dGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRl
71+
bnRCbG9ja0luZGV4IjowLCJkZWx0YSI6eyJ0ZXh0IjoiIHRoZSJ9LCJwIjoiYWJjZGUifR59TKYA
72+
AACwAAAAV4DqEyoLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBl
73+
BwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0lu
74+
ZGV4IjowLCJkZWx0YSI6eyJ0ZXh0IjoiIGF2YWlsYWJsZSJ9LCJwIjoiYWJjZGVmZ2hpamtsbSJ9
75+
R3HVnwAAAL0AAABXeHrXmws6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0RlbHRhDTpjb250ZW50
76+
LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsiY29udGVudEJs
77+
b2NrSW5kZXgiOjAsImRlbHRhIjp7InRleHQiOiIgdG9vbCJ9LCJwIjoiYWJjZGVmZ2hpamtsbW5v
78+
cHFyc3R1dnd4eXpBQkNERSJ9DXybgAAAALIAAABX+ipASgs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRC
79+
bG9ja0RlbHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUH
80+
AAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsImRlbHRhIjp7InRleHQiOiI6In0sInAiOiJh
81+
YmNkZWZnaGlqa2xtbm9wcXJzdHV2d3gifU8L/HgAAACcAAAAVvPc4bkLOmV2ZW50LXR5cGUHABBj
82+
b250ZW50QmxvY2tTdG9wDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdl
83+
LXR5cGUHAAVldmVudHsiY29udGVudEJsb2NrSW5kZXgiOjAsInAiOiJhYmNkZWZnaGlqa2xtbm9w
84+
cXJzdHV2d3gifWORzUUAAAEPAAAAV9khX0oLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tTdGFy
85+
dA06Y29udGVudC10eXBlBwAQYXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7
86+
ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNE
87+
RUZHSElKS0xNTk9QUVJTVFVWV1giLCJzdGFydCI6eyJ0b29sVXNlIjp7Im5hbWUiOiJnZXRfd2Vh
88+
dGhlciIsInRvb2xVc2VJZCI6InRvb2x1c2VfeXlwWURyNzFUd0N3QnlveWFvNC1WZyJ9fX2H4Ebd
89+
AAAAxgAAAFf2eAZFCzpldmVudC10eXBlBwARY29udGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlw
90+
ZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJ
91+
bmRleCI6MSwiZGVsdGEiOnsidG9vbFVzZSI6eyJpbnB1dCI6IiJ9fSwicCI6ImFiY2RlZmdoaWpr
92+
bG1ub3BxcnN0dXZ3eHl6QUJDREVGIn14/2YJAAAAxQAAAFex2HyVCzpldmVudC10eXBlBwARY29u
93+
dGVudEJsb2NrRGVsdGENOmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2Ut
94+
dHlwZQcABWV2ZW50eyJjb250ZW50QmxvY2tJbmRleCI6MSwiZGVsdGEiOnsidG9vbFVzZSI6eyJp
95+
bnB1dCI6IntcImxvY2F0aSJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXYifWRGfuMAAAC/
96+
AAAAVwK6hPsLOmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQ
97+
YXBwbGljYXRpb24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4
98+
IjoxLCJkZWx0YSI6eyJ0b29sVXNlIjp7ImlucHV0Ijoib25cIjogXCJCYXIifX0sInAiOiJhYmNk
99+
ZWZnaGlqa2xtbiJ9EYMO9gAAAMcAAABXyxgv9Qs6ZXZlbnQtdHlwZQcAEWNvbnRlbnRCbG9ja0Rl
100+
bHRhDTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVu
101+
dHsiY29udGVudEJsb2NrSW5kZXgiOjEsImRlbHRhIjp7InRvb2xVc2UiOnsiaW5wdXQiOiJjZSJ9
102+
fSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREUifSZWjxQAAADFAAAAV7HYfJUL
103+
OmV2ZW50LXR5cGUHABFjb250ZW50QmxvY2tEZWx0YQ06Y29udGVudC10eXBlBwAQYXBwbGljYXRp
104+
b24vanNvbg06bWVzc2FnZS10eXBlBwAFZXZlbnR7ImNvbnRlbnRCbG9ja0luZGV4IjoxLCJkZWx0
105+
YSI6eyJ0b29sVXNlIjp7ImlucHV0IjoibG9uYVwifSJ9fSwicCI6ImFiY2RlZmdoaWprbG1ub3Bx
106+
cnN0dXZ3eCJ9ZJRmigAAALEAAABWyo0KDAs6ZXZlbnQtdHlwZQcAEGNvbnRlbnRCbG9ja1N0b3AN
107+
OmNvbnRlbnQtdHlwZQcAEGFwcGxpY2F0aW9uL2pzb24NOm1lc3NhZ2UtdHlwZQcABWV2ZW50eyJj
108+
b250ZW50QmxvY2tJbmRleCI6MSwicCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVG
109+
R0hJSktMTU5PUFFSUyJ9QmndSQAAAK4AAABRtlmf/As6ZXZlbnQtdHlwZQcAC21lc3NhZ2VTdG9w
110+
DTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsi
111+
cCI6ImFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSUyIsInN0b3BS
112+
ZWFzb24iOiJ0b29sX3VzZSJ9MTlQawAAAQoAAABOdap4+gs6ZXZlbnQtdHlwZQcACG1ldGFkYXRh
113+
DTpjb250ZW50LXR5cGUHABBhcHBsaWNhdGlvbi9qc29uDTptZXNzYWdlLXR5cGUHAAVldmVudHsi
114+
bWV0cmljcyI6eyJsYXRlbmN5TXMiOjExNzZ9LCJwIjoiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4
115+
eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1giLCJ1c2FnZSI6eyJpbnB1dFRva2VucyI6MjM2LCJv
116+
dXRwdXRUb2tlbnMiOjU4LCJzZXJ2ZXJUb29sVXNhZ2UiOnt9LCJ0b3RhbFRva2VucyI6Mjk0fX31
117+
KYdd
118+
headers:
119+
Connection:
120+
- keep-alive
121+
Content-Type:
122+
- application/vnd.amazon.eventstream
123+
Date:
124+
- Thu, 09 Oct 2025 15:37:29 GMT
125+
Transfer-Encoding:
126+
- chunked
127+
x-amzn-RequestId:
128+
- c1136034-9602-49a2-8b55-4034878dda8a
129+
status:
130+
code: 200
131+
message: OK
132+
version: 1

packages/opentelemetry-instrumentation-bedrock/tests/traces/test_anthropic.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,92 @@ def prompt_caching_call(brt):
977977
assert spans[1].attributes.get(CacheSpanAttrs.CACHED) == "read"
978978

979979

980+
@pytest.mark.vcr
981+
def test_anthropic_converse_stream_with_tool_use(
982+
instrument_legacy, brt, span_exporter, log_exporter
983+
):
984+
"""Test converse_stream with tool use to validate handling of non-text contentBlockDelta events."""
985+
messages = [
986+
{
987+
"role": "user",
988+
"content": [{"text": "What is the weather in Barcelona?"}],
989+
}
990+
]
991+
992+
tools = [
993+
{
994+
"toolSpec": {
995+
"name": "get_weather",
996+
"description": "Get the current weather for a location",
997+
"inputSchema": {
998+
"json": {
999+
"type": "object",
1000+
"properties": {
1001+
"location": {
1002+
"type": "string",
1003+
"description": "The city name",
1004+
}
1005+
},
1006+
"required": ["location"],
1007+
}
1008+
},
1009+
}
1010+
}
1011+
]
1012+
1013+
tool_config = {"tools": tools}
1014+
inf_params = {"maxTokens": 300, "temperature": 0.5}
1015+
1016+
response = brt.converse_stream(
1017+
modelId="anthropic.claude-3-sonnet-20240229-v1:0",
1018+
messages=messages,
1019+
toolConfig=tool_config,
1020+
inferenceConfig=inf_params,
1021+
)
1022+
1023+
stream = response.get("stream")
1024+
content = ""
1025+
tool_use_found = False
1026+
1027+
for event in stream:
1028+
# Test should handle contentBlockDelta events without text field
1029+
if "contentBlockDelta" in event:
1030+
delta = event["contentBlockDelta"].get("delta", {})
1031+
# Only append if text exists (validates the fix)
1032+
if "text" in delta:
1033+
content += delta["text"]
1034+
# Check if we got a toolUse delta
1035+
if "toolUse" in delta:
1036+
tool_use_found = True
1037+
1038+
spans = span_exporter.get_finished_spans()
1039+
assert len(spans) == 1
1040+
assert spans[0].name == "bedrock.converse"
1041+
1042+
bedrock_span = spans[0]
1043+
1044+
# Assert on model name
1045+
assert (
1046+
bedrock_span.attributes.get("gen_ai.request.model")
1047+
== "claude-3-sonnet-20240229-v1:0"
1048+
)
1049+
1050+
# Assert on vendor
1051+
assert bedrock_span.attributes.get("gen_ai.system") == "AWS"
1052+
1053+
# Assert on request type
1054+
assert bedrock_span.attributes.get("llm.request.type") == "chat"
1055+
1056+
# tool use should have been triggered
1057+
# (This test validates that non-text deltas don't crash the instrumentation)
1058+
assert tool_use_found is True
1059+
1060+
logs = log_exporter.get_finished_logs()
1061+
assert (
1062+
len(logs) == 0
1063+
), "Assert that it doesn't emit logs when use_legacy_attributes is True"
1064+
1065+
9801066
def assert_message_in_logs(log: LogData, event_name: str, expected_content: dict):
9811067
assert log.log_record.attributes.get(EventAttributes.EVENT_NAME) == event_name
9821068
assert (

0 commit comments

Comments
 (0)