[bot] Bedrock ConverseStream drops tool use and non-text content blocks in streaming spans
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 64/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Quiet
- Tech stack
- aws, java
- Domain
- cloud, observability
Research direction
Start with TeeingSubscriber.onNext, parseDeltaText, and buildConverseJson in braintrust-sdk/instrumentation/aws_bedrock_2_30_0/src/main/java/dev/braintrust/instrumentation/awsbedrock/v2_30_0/BraintrustBedrockInterceptor.java. Compare the streaming event handling with normalizeBedrockMessage() in braintrust-sdk/src/main/java/dev/braintrust/instrumentation/InstrumentationSemConv.java and review the existing Bedrock tests. Done means streaming tool-use content, identifiers, names, and input are represented in the span output, with coverage in BraintrustAWSBedrockTest.java.
Written by the indexing model from the issue text.
Description
Summary
The Bedrock ConverseStream instrumentation in TeeingSubscriber only accumulates delta.text from contentBlockDelta events. When the model returns tool use content blocks during streaming (a documented and supported Bedrock feature), the tool call data is silently dropped from the span output.
Non-streaming Converse calls handle tool use correctly — normalizeBedrockMessage() in InstrumentationSemConv already recognizes toolUse, toolResult, and image content block types and adds appropriate type annotations. The gap is exclusively in the streaming path.
What is missing
1. TeeingSubscriber only parses text deltas (lines 273–289)
case "contentBlockDelta" -> {
String t = parseDeltaText(payload); // only looks for delta.text
if (t != null) {
text.append(t);
// ...
}
}
parseDeltaText() (lines 322–339) searches exclusively for delta.text. Bedrock ConverseStream sends delta.toolUse for tool use blocks (containing incremental input JSON), which is silently ignored.
2. contentBlockStart events are not handled
The TeeingSubscriber switch (lines 273–289) has no case for contentBlockStart. This event carries toolUse.toolUseId and toolUse.name — metadata needed to reconstruct the tool call. Without it, even if tool use deltas were captured, the tool name and ID would be lost.
3. buildConverseJson always produces a single text block (lines 385–410)
gen.writeArrayFieldStart("content");
gen.writeStartObject();
gen.writeStringField("text", text); // always one text block
gen.writeEndObject();
gen.writeEndArray();
The synthetic response JSON always contains exactly one {"text": "..."} content block, regardless of what the model actually returned. When the model requests a tool call, the span output will show empty text instead of the tool invocation.
Impact
When a streaming Bedrock call triggers tool use (common in agentic workflows):
- The span's
output_jsoncontains[{"role":"assistant","content":[{"text":"","type":"text"}]}]— as if the model returned nothing - The
stopReasonis captured as"tool_use", contradicting the empty output - Tool call names, IDs, and argument JSON are permanently lost from the trace
- Non-streaming
Conversecalls with tool use are correctly captured
Braintrust docs status
- Bedrock is listed as a supported cloud provider at https://www.braintrust.dev/docs/integrations/ai-providers: supported
- Tool calling in Bedrock streaming is not specifically documented on Braintrust docs: not_found
Upstream sources
- Bedrock ConverseStream API reference: https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_ConverseStream.html — documents
toolConfigparameter and streaming tool use events (contentBlockStartwithtoolUse,contentBlockDeltawithtoolUseinput) - Bedrock tool use examples: https://docs.aws.amazon.com/bedrock/latest/userguide/tool-use-examples.html — shows ConverseStream with tool use
- Bedrock streaming event sequence: Events include
contentBlockStart(carriesstart.toolUsewithtoolUseIdandname),contentBlockDelta(carriesdelta.toolUsewithinputJSON fragments), andcontentBlockStop
Local files inspected
braintrust-sdk/instrumentation/aws_bedrock_2_30_0/src/main/java/dev/braintrust/instrumentation/awsbedrock/v2_30_0/BraintrustBedrockInterceptor.java— lines 273–289 (TeeingSubscriber.onNext: onlycontentBlockDelta→text,messageStop,metadatahandled; nocontentBlockStart), lines 322–339 (parseDeltaText: onlydelta.text), lines 385–410 (buildConverseJson: hardcoded single text block)braintrust-sdk/src/main/java/dev/braintrust/instrumentation/InstrumentationSemConv.java— lines 400–434 (normalizeBedrockMessage: non-streaming path correctly handlestoolUse,toolResult,imagecontent block types)braintrust-sdk/instrumentation/aws_bedrock_2_30_0/src/test/java/dev/braintrust/instrumentation/awsbedrock/v2_30_0/BraintrustAWSBedrockTest.java— no streaming test exercises tool use responses
- Dominant language
- Java
- Stars
- 21
- Forks
- 5
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 8
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from braintrustdata/braintrust-sdk-java
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
All issues in braintrustdata/braintrust-sdk-java
Similar issues
-
certification
Difficulty 1/5 Under an hour Newbie friendliness 80/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
-
[BUG] ECR GetAuthorizationToken returns a proxyEndpoint for the default region, not the request's Openbug ecr
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
-
Needs: Triage Type: Feature request
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
AntennaPod/AntennaPod#8794 ·
-
agentic-workflows
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
github/copilot-sdk#2760 ·