You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The Anthropic Messages streaming tracer updates mt.metadata with stop_reason during stream processing but never writes the updated metadata back to the span. Additionally, the resolved model name and message id from the message_start event are not captured at all. The non-streaming path correctly updates and flushes metadata with all response fields.
This means Anthropic streaming spans have metadata frozen at request time — missing stop_reason (which indicates why the model stopped: end_turn, max_tokens, tool_use, etc.) and the resolved model version (e.g., "claude-haiku-4-5-20251001" instead of the alias sent in the request).
What is missing
1. Streaming path never writes updated metadata to span
In trace/contrib/anthropic/messages.go, parseStreamingResponse (lines 117–199) sets braintrust.output_json and braintrust.metrics but never calls SetJSONAttr(span, "braintrust.metadata", ...):
func (mt*messagesTracer) parseStreamingResponse(span trace.Span, body io.Reader) error {
// ... scan events ...output:=mt.postprocessStreamingResults(allResults)
iflen(output) >0 {
internal.SetJSONAttr(span, "braintrust.output_json", output) // ✅ set
}
// ... metrics ...internal.SetJSONAttr(span, "braintrust.metrics", metrics) // ✅ setreturnscanner.Err()
// ❌ braintrust.metadata is NEVER updated after StartSpan
}
Meanwhile, postprocessStreamingResults (line 315) updates mt.metadata["stop_reason"] but this change is orphaned — never written to the span.
2. Non-streaming path flushes metadata correctly
handleMessageResponse (lines 360–409) updates metadata and writes it:
The current message_start handler (lines 153–162) only extracts usage:
case"message_start":
ifmessage, ok:=chunk["message"].(map[string]any); ok {
ifcurUsage, ok:=message["usage"].(map[string]any); ok {
// only usage is captured
}
}
The resolved model name (which may differ from the request alias) and the message id are ignored.
4. Impact
Field
Non-streaming
Streaming
stop_reason
✅ In metadata
❌ Computed but never written to span
Resolved model
✅ Updated from response
❌ Only request-time alias
stop_sequence
✅ In metadata
❌ Not captured
Message id
❌ Not captured
❌ Not captured
stop_reason is critical for understanding model behavior — it distinguishes end_turn (natural completion), max_tokens (truncated), and tool_use (model wants to call a tool). Without it, users cannot differentiate truncated responses from complete ones in streaming traces.
5. Comparable integrations
OpenAI Responses API streaming: correctly flushes metadata after processing response.completed event (handleResponseCompletedMessage calls SetJSONAttr(span, "braintrust.metadata", ...))
Bedrock ConverseStream: correctly sets metadata in finalize() via setJSONAttr(o.log, o.span, "braintrust.metadata", o.metadata)
Braintrust docs status
The Braintrust advanced tracing docs specify that model and similar settings belong in metadata. The Anthropic integration docs describe automatic tracing of "all API calls." The streaming metadata gap is not documented. Status: unclear.
Upstream sources
Anthropic streaming events: https://docs.anthropic.com/en/api/messages-streaming — documents message_start (contains model, id, initial usage) and message_delta (contains stop_reason, stop_sequence, final usage)
Summary
The Anthropic Messages streaming tracer updates
mt.metadatawithstop_reasonduring stream processing but never writes the updated metadata back to the span. Additionally, the resolvedmodelname and messageidfrom themessage_startevent are not captured at all. The non-streaming path correctly updates and flushes metadata with all response fields.This means Anthropic streaming spans have metadata frozen at request time — missing
stop_reason(which indicates why the model stopped:end_turn,max_tokens,tool_use, etc.) and the resolved model version (e.g.,"claude-haiku-4-5-20251001"instead of the alias sent in the request).What is missing
1. Streaming path never writes updated metadata to span
In
trace/contrib/anthropic/messages.go,parseStreamingResponse(lines 117–199) setsbraintrust.output_jsonandbraintrust.metricsbut never callsSetJSONAttr(span, "braintrust.metadata", ...):Meanwhile,
postprocessStreamingResults(line 315) updatesmt.metadata["stop_reason"]but this change is orphaned — never written to the span.2. Non-streaming path flushes metadata correctly
handleMessageResponse(lines 360–409) updates metadata and writes it:3.
message_startevent carriesmodelandidbut neither is capturedVCR cassettes confirm
message_startincludes the resolved model:{"type":"message_start","message":{"model":"claude-haiku-4-5-20251001","id":"msg_01XaNC...",...}}The current
message_starthandler (lines 153–162) only extractsusage:The resolved
modelname (which may differ from the request alias) and the messageidare ignored.4. Impact
stop_reasonmodelstop_sequenceidstop_reasonis critical for understanding model behavior — it distinguishesend_turn(natural completion),max_tokens(truncated), andtool_use(model wants to call a tool). Without it, users cannot differentiate truncated responses from complete ones in streaming traces.5. Comparable integrations
response.completedevent (handleResponseCompletedMessagecallsSetJSONAttr(span, "braintrust.metadata", ...))finalize()viasetJSONAttr(o.log, o.span, "braintrust.metadata", o.metadata)Braintrust docs status
The Braintrust advanced tracing docs specify that model and similar settings belong in
metadata. The Anthropic integration docs describe automatic tracing of "all API calls." The streaming metadata gap is not documented. Status: unclear.Upstream sources
message_start(containsmodel,id, initialusage) andmessage_delta(containsstop_reason,stop_sequence, finalusage)model,id,stop_reasonas response fieldsBraintrust docs sources
Local repo files inspected
trace/contrib/anthropic/messages.go—parseStreamingResponse()lines 117–199: sets output_json and metrics but never flushes metadata;postprocessStreamingResults()line 315: updatesmt.metadata["stop_reason"]but it's orphaned;handleMessageResponse()lines 360–409: non-streaming path correctly flushes metadatatrace/contrib/anthropic/messages.go—message_starthandler lines 153–162: only extracts usage, ignoresmodelandidtrace/contrib/anthropic/testdata/cassettes/TestStreamingWithCitations.yamlline 52: confirmsmessage_startcontains resolvedmodeltrace/contrib/openai/responses.go—handleResponseCompletedMessage(): reference showing correct metadata flush in streamingtrace/contrib/bedrockruntime/stream.go—finalize(): reference showing correct metadata flush in streaming