Repository navigation
ref(server-utils): Use shared stream state and endStreamSpan for Bedrock streams - #26192
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ock streams Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit a236111. Configure here.
size-limit report 📦Compared: f206027 (PR test merge using develop at 2e4231e) Against: 2e4231e (develop) · base run #65527 ✅ Baseline matches: this diff contains only this PR's changes.
|
Optional E2E testsSome optional checks were not successful. They do not block the merge. 6 not successful, 51 successful. See the run.
Successful checks
|
…stream-state # Conflicts: # dev-packages/node-integration-tests/suites/aws-serverless/bedrock/test.ts
| @@ -291,6 +291,31 @@ describe('BedrockRuntimeServiceExtension token usage with prompt caching', () => | |||
| expect(usageAttributes(span, EXPECTED_USAGE)).toEqual(EXPECTED_USAGE); | |||
| }); | |||
|
|
|||
There was a problem hiding this comment.
l: titan, llama and nova are not tested, we could add a test for each of those to check if they work. Feel free to change the city to Vienna.
| it.each([ | |
| { | |
| family: 'Llama', | |
| modelId: 'meta.llama3-8b-instruct-v1:0', | |
| chunks: [ | |
| { generation: 'Paris', prompt_token_count: 12, generation_token_count: 1, stop_reason: null }, | |
| { generation: '.', prompt_token_count: null, generation_token_count: 2, stop_reason: 'stop' }, | |
| ], | |
| expected: { | |
| [GEN_AI_USAGE_INPUT_TOKENS]: 12, | |
| [GEN_AI_USAGE_OUTPUT_TOKENS]: 2, | |
| [GEN_AI_USAGE_TOTAL_TOKENS]: 14, | |
| [GEN_AI_RESPONSE_FINISH_REASONS]: '["stop"]', | |
| }, | |
| }, | |
| { | |
| family: 'Titan', | |
| modelId: 'amazon.titan-text-express-v1', | |
| chunks: [ | |
| { outputText: 'Paris', totalOutputTextTokenCount: null, completionReason: null, inputTextTokenCount: 5 }, | |
| { outputText: '.', totalOutputTextTokenCount: 2, completionReason: 'FINISH', inputTextTokenCount: null }, | |
| ], | |
| expected: { | |
| [GEN_AI_USAGE_INPUT_TOKENS]: 5, | |
| [GEN_AI_USAGE_OUTPUT_TOKENS]: 2, | |
| [GEN_AI_USAGE_TOTAL_TOKENS]: 7, | |
| [GEN_AI_RESPONSE_FINISH_REASONS]: '["FINISH"]', | |
| }, | |
| }, | |
| { | |
| family: 'Nova', | |
| modelId: 'amazon.nova-lite-v1:0', | |
| chunks: [ | |
| { contentBlockDelta: { delta: { text: 'Paris' }, contentBlockIndex: 0 } }, | |
| { messageStop: { stopReason: 'end_turn' } }, | |
| { metadata: { usage: { inputTokens: 7, outputTokens: 2 }, metrics: {}, trace: {} } }, | |
| ], | |
| expected: { | |
| [GEN_AI_USAGE_INPUT_TOKENS]: 7, | |
| [GEN_AI_USAGE_OUTPUT_TOKENS]: 2, | |
| [GEN_AI_USAGE_TOTAL_TOKENS]: 9, | |
| [GEN_AI_RESPONSE_FINISH_REASONS]: '["end_turn"]', | |
| }, | |
| }, | |
| ])('records token usage and finish reasons for a $family stream', async ({ modelId, chunks, expected }) => { | |
| const span = startInactiveSpan({ name: 'bedrock' }); | |
| const encoder = new TextEncoder(); | |
| const data = { | |
| body: (async function* () { | |
| for (const chunk of chunks) { | |
| yield { chunk: { bytes: encoder.encode(JSON.stringify(chunk)) } }; | |
| } | |
| })(), | |
| }; | |
| respond(span, 'InvokeModelWithResponseStream', { modelId }, data); | |
| await drain(data.body); | |
| expect(spanToJSON(span).attributes).toMatchObject(expected); | |
| }); | |
There was a problem hiding this comment.
Added, with Kawasaki as the city.

What
Bedrock ConverseStream and InvokeModelWithResponseStream spans now collect stream data into the shared stream state and end through
endStreamSpan, like the other AI integrations. Stream spans now also getgen_ai.response.streamingand a total token count for all model families.Why
The shared state is the base for recording Bedrock outputs in a later PR, and it makes Bedrock stream spans consistent with the other AI integrations.
Extracted from #26118.