From e9ae8b876bbd9f309db8d1aa43bead052953f723 Mon Sep 17 00:00:00 2001 From: Yuliia Fryshko Date: Tue, 13 May 2025 06:23:30 -0700 Subject: [PATCH 1/5] feat: add Bedrock InvokeModelWithResponseStream instrumentation --- .../src/services/bedrock-runtime.ts | 224 ++++++++++++++++++ .../test/bedrock-runtime.test.ts | 182 ++++++++++++++ ...-amazon-nova-model-attributes-to-span.json | 88 +++++++ ...amazon-titan-model-attributes-to-span.json | 70 ++++++ ...-adds-claude-model-attributes-to-span.json | 90 +++++++ .../test/current.feature | 6 + 6 files changed, 660 insertions(+) create mode 100644 packages/instrumentation-aws-sdk/test/mock-responses/bedrock-invokemodelwithstreams-adds-amazon-nova-model-attributes-to-span.json create mode 100644 packages/instrumentation-aws-sdk/test/mock-responses/bedrock-invokemodelwithstreams-adds-amazon-titan-model-attributes-to-span.json create mode 100644 packages/instrumentation-aws-sdk/test/mock-responses/bedrock-invokemodelwithstreams-adds-claude-model-attributes-to-span.json create mode 100644 plugins/node/instrumentation-cucumber/test/current.feature diff --git a/packages/instrumentation-aws-sdk/src/services/bedrock-runtime.ts b/packages/instrumentation-aws-sdk/src/services/bedrock-runtime.ts index f15a91ad39..6519237c55 100644 --- a/packages/instrumentation-aws-sdk/src/services/bedrock-runtime.ts +++ b/packages/instrumentation-aws-sdk/src/services/bedrock-runtime.ts @@ -102,6 +102,13 @@ export class BedrockRuntimeServiceExtension implements ServiceExtension { return this.requestPreSpanHookConverse(request, config, diag, true); case 'InvokeModel': return this.requestPreSpanHookInvokeModel(request, config, diag); + case 'InvokeModelWithResponseStream': + return this.requestPreSpanHookInvokeModelWithResponseStream( + request, + config, + diag, + true + ); } return { @@ -316,6 +323,86 @@ export class BedrockRuntimeServiceExtension implements ServiceExtension { }; } + private requestPreSpanHookInvokeModelWithResponseStream( + request: NormalizedRequest, + config: AwsSdkInstrumentationConfig, + diag: DiagLogger, + isStream: boolean + ): RequestMetadata { + let spanName: string | undefined; + const spanAttributes: Attributes = { + [ATTR_GEN_AI_SYSTEM]: GEN_AI_SYSTEM_VALUE_AWS_BEDROCK, + // add operation name for InvokeModel API + }; + + const modelId = request.commandInput?.modelId; + if (modelId) { + spanAttributes[ATTR_GEN_AI_REQUEST_MODEL] = modelId; + } + + if (request.commandInput?.body) { + const requestBody = JSON.parse(request.commandInput.body); + if (modelId.includes('amazon.titan')) { + if (requestBody.textGenerationConfig?.temperature !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_TEMPERATURE] = + requestBody.textGenerationConfig.temperature; + } + if (requestBody.textGenerationConfig?.topP !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_TOP_P] = + requestBody.textGenerationConfig.topP; + } + if (requestBody.textGenerationConfig?.maxTokenCount !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_MAX_TOKENS] = + requestBody.textGenerationConfig.maxTokenCount; + } + if (requestBody.textGenerationConfig?.stopSequences !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_STOP_SEQUENCES] = + requestBody.textGenerationConfig.stopSequences; + } + } else if (modelId.includes('anthropic.claude')) { + if (requestBody.max_tokens !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_MAX_TOKENS] = + requestBody.max_tokens; + } + if (requestBody.temperature !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_TEMPERATURE] = + requestBody.temperature; + } + if (requestBody.top_p !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_TOP_P] = requestBody.top_p; + } + if (requestBody.stop_sequences !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_STOP_SEQUENCES] = + requestBody.stop_sequences; + } + } else if (modelId.includes('amazon.nova')) { + if (requestBody.inferenceConfig?.temperature !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_TEMPERATURE] = + requestBody.inferenceConfig.temperature; + } + if (requestBody.inferenceConfig?.top_p !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_TOP_P] = + requestBody.inferenceConfig.top_p; + } + if (requestBody.inferenceConfig?.max_new_tokens !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_MAX_TOKENS] = + requestBody.inferenceConfig.max_new_tokens; + } + if (requestBody.inferenceConfig?.stopSequences !== undefined) { + spanAttributes[ATTR_GEN_AI_REQUEST_STOP_SEQUENCES] = + requestBody.inferenceConfig.stopSequences; + } + } + } + + return { + spanName, + isIncoming: false, + spanAttributes, + isStream, + }; + } + responseHook( response: NormalizedResponse, span: Span, @@ -346,6 +433,13 @@ export class BedrockRuntimeServiceExtension implements ServiceExtension { ); case 'InvokeModel': return this.responseHookInvokeModel(response, span, tracer, config); + case 'InvokeModelWithResponseStream': + return this.responseHookInvokeModelWithResponseStream( + response, + span, + tracer, + config + ); } } @@ -579,4 +673,134 @@ export class BedrockRuntimeServiceExtension implements ServiceExtension { } } } + + private async responseHookInvokeModelWithResponseStream( + response: NormalizedResponse, + span: Span, + tracer: Tracer, + config: AwsSdkInstrumentationConfig + ): Promise { + const stream = response.data?.body; + const modelId = response.request.commandInput?.modelId; + if (!stream || !span.isRecording()) return; + + const wrappedStream = instrumentAsyncIterable( + stream, + async (chunk: { chunk?: { bytes?: Uint8Array } }) => { + const parsedChunk = parseChunk(chunk?.chunk?.bytes); + + if (!parsedChunk) return; + + if (modelId.includes('amazon.titan')) { + recordTitanAttributes(parsedChunk); + } else if (modelId.includes('anthropic.claude')) { + recordClaudeAttributes(parsedChunk); + } else if (modelId.includes('amazon.nova')) { + recordNovaAttributes(parsedChunk); + } + } + ); + // Replace the original response body with our instrumented stream. + // - Defers span.end() until the entire stream is consumed + // This ensures downstream consumers still receive the full stream correctly, + // while OpenTelemetry can record span attributes from streamed data. + response.data.body = (async function* () { + try { + for await (const item of wrappedStream) { + yield item; + } + } finally { + span.end(); + } + })(); + return response.data; + + // Tap into the stream at the chunk level without modifying the chunk itself. + function instrumentAsyncIterable( + stream: AsyncIterable, + onChunk: (chunk: T) => void + ): AsyncIterable { + return { + [Symbol.asyncIterator]: async function* () { + for await (const chunk of stream) { + onChunk(chunk); + yield chunk; + } + }, + }; + } + + function parseChunk(bytes?: Uint8Array): any { + if (!bytes || !(bytes instanceof Uint8Array)) return null; + try { + const str = Buffer.from(bytes).toString('utf-8'); + return JSON.parse(str); + } catch (err) { + console.warn('Failed to parse streamed chunk', err); + return null; + } + } + + function recordNovaAttributes(parsedChunk: any) { + if (parsedChunk.metadata?.usage !== undefined) { + if (parsedChunk.metadata?.usage.inputTokens !== undefined) { + span.setAttribute( + ATTR_GEN_AI_USAGE_INPUT_TOKENS, + parsedChunk.metadata.usage.inputTokens + ); + } + if (parsedChunk.metadata?.usage.outputTokens !== undefined) { + span.setAttribute( + ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, + parsedChunk.metadata.usage.outputTokens + ); + } + } + if (parsedChunk.messageStop?.stopReason !== undefined) { + span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [ + parsedChunk.messageStop.stopReason, + ]); + } + } + + function recordClaudeAttributes(parsedChunk: any) { + if (parsedChunk.message?.usage?.input_tokens !== undefined) { + span.setAttribute( + ATTR_GEN_AI_USAGE_INPUT_TOKENS, + parsedChunk.message.usage.input_tokens + ); + } + if (parsedChunk.message?.usage?.output_tokens !== undefined) { + span.setAttribute( + ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, + parsedChunk.message.usage.output_tokens + ); + } + if (parsedChunk.delta?.stop_reason !== undefined) { + span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [ + parsedChunk.delta.stop_reason, + ]); + } + } + + function recordTitanAttributes(parsedChunk: any) { + if (parsedChunk.inputTextTokenCount !== undefined) { + span.setAttribute( + ATTR_GEN_AI_USAGE_INPUT_TOKENS, + parsedChunk.inputTextTokenCount + ); + } + if (parsedChunk.totalOutputTextTokenCount !== undefined) { + span.setAttribute( + ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, + parsedChunk.totalOutputTextTokenCount + ); + } + if (parsedChunk.completionReason !== undefined) { + span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [ + parsedChunk.completionReason, + ]); + } + } + } } diff --git a/packages/instrumentation-aws-sdk/test/bedrock-runtime.test.ts b/packages/instrumentation-aws-sdk/test/bedrock-runtime.test.ts index b01ed09010..068507e4dd 100644 --- a/packages/instrumentation-aws-sdk/test/bedrock-runtime.test.ts +++ b/packages/instrumentation-aws-sdk/test/bedrock-runtime.test.ts @@ -36,6 +36,7 @@ import { ConverseStreamCommand, ConversationRole, InvokeModelCommand, + InvokeModelWithResponseStreamCommand, } from '@aws-sdk/client-bedrock-runtime'; import { AwsCredentialIdentity } from '@aws-sdk/types'; import * as path from 'path'; @@ -79,6 +80,7 @@ const sanitizeRecordings = (scopes: Definition[]) => { describe('Bedrock', () => { nockBack.fixtures = path.join(__dirname, 'mock-responses'); let credentials: AwsCredentialIdentity | undefined; + if (nockBack.currentMode === 'dryrun') { credentials = { accessKeyId: 'testing', @@ -642,4 +644,184 @@ describe('Bedrock', () => { }); }); }); + + describe('InvokeModelWithStreams', () => { + it('adds amazon titan model attributes to span', async () => { + const modelId = 'amazon.titan-text-lite-v1'; + const prompt = '\n\nHuman: Hello, How are you today? \n\nAssistant:'; + + const body = { + inputText: prompt, + textGenerationConfig: { + maxTokenCount: 10, + temperature: 0.8, + topP: 1, + stopSequences: ['|'], + }, + }; + const command = new InvokeModelWithResponseStreamCommand({ + modelId, + body: JSON.stringify(body), + contentType: 'application/json', + accept: 'application/json', + }); + + const response = await client.send(command); + console.log('response', response); + + let collectedText = ''; + if (!response.body) return; + for await (const chunk of response.body) { + if (chunk?.chunk?.bytes instanceof Uint8Array) { + const parsed = JSON.parse(decodeChunk(chunk)); + collectedText += parsed.outputText; + } + } + expect(collectedText).toBe(" Hello there! I'm doing well. Thank you"); + + const invokeModelSpans: ReadableSpan[] = + getInvokeModelWithResponseStreamSpans(); + + expect(invokeModelSpans.length).toBe(1); + expect(invokeModelSpans[0].attributes).toMatchObject({ + [ATTR_GEN_AI_SYSTEM]: GEN_AI_SYSTEM_VALUE_AWS_BEDROCK, + [ATTR_GEN_AI_REQUEST_MODEL]: modelId, + [ATTR_GEN_AI_REQUEST_MAX_TOKENS]: 10, + [ATTR_GEN_AI_REQUEST_TEMPERATURE]: 0.8, + [ATTR_GEN_AI_REQUEST_TOP_P]: 1, + [ATTR_GEN_AI_REQUEST_STOP_SEQUENCES]: ['|'], + [ATTR_GEN_AI_USAGE_INPUT_TOKENS]: 13, + [ATTR_GEN_AI_USAGE_OUTPUT_TOKENS]: 10, + [ATTR_GEN_AI_RESPONSE_FINISH_REASONS]: ['LENGTH'], + }); + }); + it('adds claude model attributes to span', async () => { + const modelId = 'anthropic.claude-3-5-sonnet-20240620-v1:0'; + const prompt = '\n\nHuman: Hello, How are you today? \n\nAssistant:'; + + const body = { + anthropic_version: 'bedrock-2023-05-31', + max_tokens: 12, + top_k: 250, + stop_sequences: ['|'], + temperature: 0.8, + top_p: 1, + messages: [ + { + role: 'user', + content: [ + { + type: 'text', + text: prompt, + }, + ], + }, + ], + }; + + const command = new InvokeModelWithResponseStreamCommand({ + modelId, + body: JSON.stringify(body), + contentType: 'application/json', + accept: 'application/json', + }); + + const response = await client.send(command); + + let collectedText = ''; + if (!response.body) return; + for await (const chunk of response.body) { + if (chunk?.chunk?.bytes instanceof Uint8Array) { + const parsed = JSON.parse(decodeChunk(chunk)); + if ( + parsed.type === 'content_block_delta' && + parsed.delta?.type === 'text_delta' + ) { + collectedText += parsed.delta.text; + } + } + } + + expect(collectedText).toBe( + "Hello! I'm doing well, thank you for asking." + ); + + const invokeModelSpans: ReadableSpan[] = + getInvokeModelWithResponseStreamSpans(); + + expect(invokeModelSpans.length).toBe(1); + expect(invokeModelSpans[0].attributes).toMatchObject({ + [ATTR_GEN_AI_SYSTEM]: GEN_AI_SYSTEM_VALUE_AWS_BEDROCK, + [ATTR_GEN_AI_REQUEST_MODEL]: modelId, + [ATTR_GEN_AI_REQUEST_MAX_TOKENS]: 12, + [ATTR_GEN_AI_REQUEST_TEMPERATURE]: 0.8, + [ATTR_GEN_AI_REQUEST_TOP_P]: 1, + [ATTR_GEN_AI_REQUEST_STOP_SEQUENCES]: ['|'], + [ATTR_GEN_AI_USAGE_INPUT_TOKENS]: 22, + [ATTR_GEN_AI_USAGE_OUTPUT_TOKENS]: 1, + [ATTR_GEN_AI_RESPONSE_FINISH_REASONS]: ['max_tokens'], + }); + }); + + it('adds amazon nova model attributes to span', async () => { + const modelId = 'amazon.nova-pro-v1:0'; + const prompt = 'Say this is a test'; + const nativeRequest: any = { + messages: [{ role: 'user', content: [{ text: prompt }] }], + inferenceConfig: { + max_new_tokens: 10, + temperature: 0.8, + top_p: 1, + stopSequences: ['|'], + }, + }; + const command = new InvokeModelWithResponseStreamCommand({ + modelId, + body: JSON.stringify(nativeRequest), + }); + + const response = await client.send(command); + + let collectedText = ''; + if (!response.body) return; + for await (const chunk of response.body) { + if (chunk?.chunk?.bytes instanceof Uint8Array) { + const parsed = JSON.parse(decodeChunk(chunk)); + if (parsed.contentBlockDelta?.delta) { + collectedText += parsed.contentBlockDelta?.delta.text; + } + } + } + + expect(collectedText).toBe( + "Certainly! If you're indicating that this interaction" + ); + + const invokeModelSpans: ReadableSpan[] = + getInvokeModelWithResponseStreamSpans(); + + expect(invokeModelSpans.length).toBe(1); + expect(invokeModelSpans[0].attributes).toMatchObject({ + [ATTR_GEN_AI_SYSTEM]: GEN_AI_SYSTEM_VALUE_AWS_BEDROCK, + [ATTR_GEN_AI_REQUEST_MODEL]: modelId, + [ATTR_GEN_AI_REQUEST_MAX_TOKENS]: 10, + [ATTR_GEN_AI_REQUEST_TEMPERATURE]: 0.8, + [ATTR_GEN_AI_REQUEST_TOP_P]: 1, + [ATTR_GEN_AI_REQUEST_STOP_SEQUENCES]: ['|'], + [ATTR_GEN_AI_USAGE_INPUT_TOKENS]: 5, + [ATTR_GEN_AI_USAGE_OUTPUT_TOKENS]: 10, + [ATTR_GEN_AI_RESPONSE_FINISH_REASONS]: ['max_tokens'], + }); + }); + }); + + function getInvokeModelWithResponseStreamSpans(): ReadableSpan[] { + return getTestSpans().filter((s: ReadableSpan) => { + return s.name === 'BedrockRuntime.InvokeModelWithResponseStream'; + }); + } + + function decodeChunk(chunk: any) { + return Buffer.from(chunk.chunk.bytes).toString('utf-8'); + } }); diff --git a/packages/instrumentation-aws-sdk/test/mock-responses/bedrock-invokemodelwithstreams-adds-amazon-nova-model-attributes-to-span.json b/packages/instrumentation-aws-sdk/test/mock-responses/bedrock-invokemodelwithstreams-adds-amazon-nova-model-attributes-to-span.json new file mode 100644 index 0000000000..9bf62b446c --- /dev/null +++ b/packages/instrumentation-aws-sdk/test/mock-responses/bedrock-invokemodelwithstreams-adds-amazon-nova-model-attributes-to-span.json @@ -0,0 +1,88 @@ +[ + { + "scope": "https://bedrock-runtime.us-east-1.amazonaws.com:443", + "method": "POST", + "path": "/model/amazon.nova-pro-v1%3A0/invoke-with-response-stream", + "body": { + "messages": [ + { + "role": "user", + "content": [ + { + "text": "Say this is a test" + } + ] + } + ], + "inferenceConfig": { + "max_new_tokens": 10, + "temperature": 0.8, + "top_p": 1, + "stopSequences": [ + "|" + ] + } + }, + "status": 403, + "response": { + "message": "Signature expired: 20250521T030814Z is now earlier than 20250521T074804Z (20250521T075304Z - 5 min.)" + }, + "rawHeaders": [ + "Date", + "Wed, 21 May 2025 07:53:04 GMT", + "Content-Type", + "application/json", + "Content-Length", + "114", + "Connection", + "keep-alive", + "x-amzn-RequestId", + "46813d5b-e0f5-4df2-b02f-a1ddccb37d52", + "x-amzn-ErrorType", + "InvalidSignatureException:http://internal.amazon.com/coral/com.amazon.coral.service/" + ], + "responseIsBinary": false + }, + { + "scope": "https://bedrock-runtime.us-east-1.amazonaws.com:443", + "method": "POST", + "path": "/model/amazon.nova-pro-v1%3A0/invoke-with-response-stream", + "body": { + "messages": [ + { + "role": "user", + "content": [ + { + "text": "Say this is a test" + } + ] + } + ], + "inferenceConfig": { + "max_new_tokens": 10, + "temperature": 0.8, + "top_p": 1, + "stopSequences": [ + "|" + ] + } + }, + "status": 200, + "response": "000000b60000004b1babbac50b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a745a584e7a5957646c55335268636e51694f6e7369636d39735a534936496d467a63326c7a6447467564434a3966513d3d222c2270223a226162636465666768696a6b6c6d6e6f7071727374227dd3a6b277000000d70000004bbff9e4380b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a6a623235305a573530516d7876593274455a5778305953493665794a6b5a5778305953493665794a305a586830496a6f695132567964474670626d7835496e3073496d4e76626e526c626e52436247396a61306c755a475634496a6f776658303d222c2270223a226162636465227d2b96c4fe000000c60000004be2795a0a0b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a6a623235305a573530516d78765932745464473977496a7037496d4e76626e526c626e52436247396a61306c755a475634496a6f776658303d222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a4142227d2f56b818000000db0000004b7a0909390b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a6a623235305a573530516d7876593274455a5778305953493665794a6b5a5778305953493665794a305a586830496a6f694953424a5a694a394c434a6a623235305a573530516d78765932744a626d526c654349364d583139222c2270223a226162636465666768696a6b6c6d6e6f7071227dbe57f5b7000000cf0000004bef69387b0b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a6a623235305a573530516d78765932745464473977496a7037496d4e76626e526c626e52436247396a61306c755a475634496a6f786658303d222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a4142434445464748494a4b227d8c02cc2f000000f70000004b7e38cb3c0b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a6a623235305a573530516d7876593274455a5778305953493665794a6b5a5778305953493665794a305a586830496a6f6949486c76645364795a534270626d5270593246306157356e496e3073496d4e76626e526c626e52436247396a61306c755a475634496a6f796658303d222c2270223a226162636465666768696a6b6c6d6e6f70717273747576777879227d46a8e5d2000000b80000004ba49b04a40b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a6a623235305a573530516d78765932745464473977496a7037496d4e76626e526c626e52436247396a61306c755a475634496a6f796658303d222c2270223a226162636465666768696a6b6c6d6e227dac11916d000001030000004b08d0ee040b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a6a623235305a573530516d7876593274455a5778305953493665794a6b5a5778305953493665794a305a586830496a6f694948526f595851676447687063794270626e526c636d466a64476c7662694a394c434a6a623235305a573530516d78765932744a626d526c654349364d333139222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a41424344454647227dcbb9c378000000b70000004b26cb93750b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a6a623235305a573530516d78765932745464473977496a7037496d4e76626e526c626e52436247396a61306c755a475634496a6f7a6658303d222c2270223a226162636465666768696a6b6c6d227dc91669d4000000d30000004b4a7942f80b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a745a584e7a5957646c553352766343493665794a7a64473977556d566863323975496a6f6962574634583352766132567563794a3966513d3d222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a4142434445464748494a4b4c4d4e4f227d38032f050000024c0000004b54e753320b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a745a5852685a4746305953493665794a316332466e5a53493665794a70626e42316446527661325675637949364e5377696233563063485630564739725a57357a496a6f784d4377695932466a614756535a57466b53573577645852556232746c626b4e7664573530496a6f774c434a6a59574e6f5a5664796158526c53573577645852556232746c626b4e7664573530496a6f776653776962575630636d6c6a6379493665333073496e527959574e6c496a703766583073496d467459587076626931695a57527962324e724c576c75646d396a595852706232354e5a58527961574e7a496a7037496d6c7563485630564739725a57354462335675644349364e5377696233563063485630564739725a57354462335675644349364d544173496d6c75646d396a595852706232354d5958526c626d4e35496a6f794d545573496d5a70636e4e30516e6c305a5578686447567559336b694f6a67784c434a6a59574e6f5a564a6c5957524a626e4231644652766132567551323931626e51694f6a4173496d4e685932686c56334a706447564a626e4231644652766132567551323931626e51694f6a423966513d3d222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a4142434445464748494a4b4c4d4e4f50227d07773c15", + "rawHeaders": [ + "Date", + "Wed, 21 May 2025 07:53:05 GMT", + "Content-Type", + "application/vnd.amazon.eventstream", + "Transfer-Encoding", + "chunked", + "Connection", + "keep-alive", + "x-amzn-RequestId", + "8e38e2e5-0910-4420-b028-60bbde456c2c", + "X-Amzn-Bedrock-Content-Type", + "application/json" + ], + "responseIsBinary": true + } +] \ No newline at end of file diff --git a/packages/instrumentation-aws-sdk/test/mock-responses/bedrock-invokemodelwithstreams-adds-amazon-titan-model-attributes-to-span.json b/packages/instrumentation-aws-sdk/test/mock-responses/bedrock-invokemodelwithstreams-adds-amazon-titan-model-attributes-to-span.json new file mode 100644 index 0000000000..8df07df607 --- /dev/null +++ b/packages/instrumentation-aws-sdk/test/mock-responses/bedrock-invokemodelwithstreams-adds-amazon-titan-model-attributes-to-span.json @@ -0,0 +1,70 @@ +[ + { + "scope": "https://bedrock-runtime.us-east-1.amazonaws.com:443", + "method": "POST", + "path": "/model/amazon.titan-text-lite-v1/invoke-with-response-stream", + "body": { + "inputText": "\n\nHuman: Hello, How are you today? \n\nAssistant:", + "textGenerationConfig": { + "maxTokenCount": 10, + "temperature": 0.8, + "topP": 1, + "stopSequences": [ + "|" + ] + } + }, + "status": 403, + "response": { + "message": "Signature expired: 20250325T235114Z is now earlier than 20250521T072228Z (20250521T072728Z - 5 min.)" + }, + "rawHeaders": [ + "Date", + "Wed, 21 May 2025 07:27:28 GMT", + "Content-Type", + "application/json", + "Content-Length", + "114", + "Connection", + "keep-alive", + "x-amzn-RequestId", + "f23a7276-5fad-4471-9faa-18da8cf88f0a", + "x-amzn-ErrorType", + "InvalidSignatureException:http://internal.amazon.com/coral/com.amazon.coral.service/" + ], + "responseIsBinary": false + }, + { + "scope": "https://bedrock-runtime.us-east-1.amazonaws.com:443", + "method": "POST", + "path": "/model/amazon.titan-text-lite-v1/invoke-with-response-stream", + "body": { + "inputText": "\n\nHuman: Hello, How are you today? \n\nAssistant:", + "textGenerationConfig": { + "maxTokenCount": 10, + "temperature": 0.8, + "topP": 1, + "stopSequences": [ + "|" + ] + } + }, + "status": 200, + "response": "0000020f0000004b4bb471ab0b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a7664585277645852555a586830496a6f694945686c624778764948526f5a584a6c4953424a4a3230675a473970626d6367643256736243346756476868626d736765573931496977696157356b5a5867694f6a4173496e527664474673543356306348563056475634644652766132567551323931626e51694f6a45774c434a6a623231776247563061573975556d566863323975496a6f695445564f523152494969776961573577645852555a586830564739725a57354462335675644349364d544d73496d467459587076626931695a57527962324e724c576c75646d396a595852706232354e5a58527961574e7a496a7037496d6c7563485630564739725a57354462335675644349364d544d73496d393164484231644652766132567551323931626e51694f6a45774c434a70626e5a765932463061573975544746305a57356a655349364e6a63344c434a6d61584a7a64454a356447564d5958526c626d4e35496a6f324e7a683966513d3d222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a4142434445464748494a4b4c4d4e4f50515253227d0b0a144b", + "rawHeaders": [ + "Date", + "Wed, 21 May 2025 07:27:29 GMT", + "Content-Type", + "application/vnd.amazon.eventstream", + "Transfer-Encoding", + "chunked", + "Connection", + "keep-alive", + "x-amzn-RequestId", + "fd539077-2d10-428c-8dba-f3b397e3f67b", + "X-Amzn-Bedrock-Content-Type", + "application/json" + ], + "responseIsBinary": true + } +] \ No newline at end of file diff --git a/packages/instrumentation-aws-sdk/test/mock-responses/bedrock-invokemodelwithstreams-adds-claude-model-attributes-to-span.json b/packages/instrumentation-aws-sdk/test/mock-responses/bedrock-invokemodelwithstreams-adds-claude-model-attributes-to-span.json new file mode 100644 index 0000000000..be5038b90a --- /dev/null +++ b/packages/instrumentation-aws-sdk/test/mock-responses/bedrock-invokemodelwithstreams-adds-claude-model-attributes-to-span.json @@ -0,0 +1,90 @@ +[ + { + "scope": "https://bedrock-runtime.us-east-1.amazonaws.com:443", + "method": "POST", + "path": "/model/anthropic.claude-3-5-sonnet-20240620-v1%3A0/invoke-with-response-stream", + "body": { + "anthropic_version": "bedrock-2023-05-31", + "max_tokens": 12, + "top_k": 250, + "stop_sequences": [ + "|" + ], + "temperature": 0.8, + "top_p": 1, + "messages": [ + { + "role": "user", + "content": [ + { + "type": "text", + "text": "\n\nHuman: Hello, How are you today? \n\nAssistant:" + } + ] + } + ] + }, + "status": 403, + "response": { + "message": "Signature expired: 20250325T235114Z is now earlier than 20250521T030314Z (20250521T030814Z - 5 min.)" + }, + "rawHeaders": [ + "Date", + "Wed, 21 May 2025 03:08:14 GMT", + "Content-Type", + "application/json", + "Content-Length", + "114", + "Connection", + "keep-alive", + "x-amzn-RequestId", + "9ea60ed1-a29e-41e6-9fcc-c9f343eda11c", + "x-amzn-ErrorType", + "InvalidSignatureException:http://internal.amazon.com/coral/com.amazon.coral.service/" + ], + "responseIsBinary": false + }, + { + "scope": "https://bedrock-runtime.us-east-1.amazonaws.com:443", + "method": "POST", + "path": "/model/anthropic.claude-3-5-sonnet-20240620-v1%3A0/invoke-with-response-stream", + "body": { + "anthropic_version": "bedrock-2023-05-31", + "max_tokens": 12, + "top_k": 250, + "stop_sequences": [ + "|" + ], + "temperature": 0.8, + "top_p": 1, + "messages": [ + { + "role": "user", + "content": [ + { + "type": "text", + "text": "\n\nHuman: Hello, How are you today? \n\nAssistant:" + } + ] + } + ] + }, + "status": 200, + "response": "000001bf0000004bdde70b110b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a306558426c496a6f696257567a6332466e5a56397a6447467964434973496d316c63334e685a3255694f6e7369615751694f694a7463326466596d5279613138774d5668554d6c68465557354f596d52364f5446695956525256473569634445694c434a306558426c496a6f696257567a6332466e5a534973496e4a76624755694f694a6863334e7063335268626e51694c434a746232526c62434936496d4e735958566b5a53307a4c54557463323975626d56304c5449774d6a51774e6a49774969776959323975644756756443493657313073496e4e3062334266636d566863323975496a7075645778734c434a7a6447397758334e6c6358566c626d4e6c496a7075645778734c434a316332466e5a53493665794a70626e4231644639306232746c626e4d694f6a49794c434a766458527764585266644739725a57357a496a6f7866583139222c2270223a226162636465227d4a884b470000010f0000004bcd2003050b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a306558426c496a6f695932397564475675644639696247396a6131397a6447467964434973496d6c755a475634496a6f774c434a6a623235305a57353058324a7362324e72496a7037496e5235634755694f694a305a58683049697769644756346443493649694a3966513d3d222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a4142434445464748494a4b4c4d4e4f5051525354555657227d2152ea6b0000011e0000004b90a0bd370b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a306558426c496a6f695932397564475675644639696247396a6131396b5a57783059534973496d6c755a475634496a6f774c434a6b5a5778305953493665794a306558426c496a6f69644756346446396b5a57783059534973496e526c654851694f694a495a57787362794a3966513d3d222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a4142434445464748494a4b4c4d4e4f505152535455565758595a3031323334353637227d58245a65000001310000004bd33105e20b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a306558426c496a6f695932397564475675644639696247396a6131396b5a57783059534973496d6c755a475634496a6f774c434a6b5a5778305953493665794a306558426c496a6f69644756346446396b5a57783059534973496e526c654851694f69496849456b6e6253426b62326c755a7942335a5778734c4342306147467561794235623355696658303d222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a4142434445464748494a4b4c4d4e4f50515253545556575859227d7a6e8b52000000ff0000004b4e4880fd0b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a306558426c496a6f695932397564475675644639696247396a6131396b5a57783059534973496d6c755a475634496a6f774c434a6b5a5778305953493665794a306558426c496a6f69644756346446396b5a57783059534973496e526c654851694f6949675a6d39794947467a61326c755a7934696658303d222c2270223a226162636465666768696a6b6c6d6e6f707172737475227d59a1d9a9000000da0000004b476920890b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a306558426c496a6f695932397564475675644639696247396a6131397a64473977496977696157356b5a5867694f6a4239222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a4142434445464748494a4b4c4d4e4f505152535455565758595a30313233227dfb6c29f4000001060000004bc03061740b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a306558426c496a6f696257567a6332466e5a56396b5a57783059534973496d526c62485268496a7037496e4e3062334266636d566863323975496a6f6962574634583352766132567563794973496e4e30623342666332567864575675593255694f6d3531624778394c434a316332466e5a53493665794a766458527764585266644739725a57357a496a6f784d6e3139222c2270223a2261626364227db7b0a8070000013e0000004b516192330b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a306558426c496a6f696257567a6332466e5a56397a644739774969776959573168656d39754c574a6c5a484a76593273746157353262324e6864476c76626b316c64484a7059334d694f6e736961573577645852556232746c626b4e7664573530496a6f794d6977696233563063485630564739725a57354462335675644349364d544973496d6c75646d396a595852706232354d5958526c626d4e35496a6f314d544973496d5a70636e4e30516e6c305a5578686447567559336b694f6a4d344f583139222c2270223a226162636465666768227df53a2a65", + "rawHeaders": [ + "Date", + "Wed, 21 May 2025 03:08:14 GMT", + "Content-Type", + "application/vnd.amazon.eventstream", + "Transfer-Encoding", + "chunked", + "Connection", + "keep-alive", + "x-amzn-RequestId", + "4fa4bb62-6e9e-4e14-a559-62adc1bdfe22", + "X-Amzn-Bedrock-Content-Type", + "application/json" + ], + "responseIsBinary": true + } +] \ No newline at end of file diff --git a/plugins/node/instrumentation-cucumber/test/current.feature b/plugins/node/instrumentation-cucumber/test/current.feature new file mode 100644 index 0000000000..14449ca700 --- /dev/null +++ b/plugins/node/instrumentation-cucumber/test/current.feature @@ -0,0 +1,6 @@ + + Feature: a feature + Scenario: a scenario + When I do anything at all + Then no spans are recorded + \ No newline at end of file From da2cbfac1fb8ca898055cf800ef48ee38550b529 Mon Sep 17 00:00:00 2001 From: Yuliia Fryshko Date: Mon, 2 Jun 2025 16:01:39 +0200 Subject: [PATCH 2/5] refactor: unify InvokeModel pre-span hooks into a single function with isStream flag --- .../src/services/bedrock-runtime.ts | 93 +------------------ 1 file changed, 5 insertions(+), 88 deletions(-) diff --git a/packages/instrumentation-aws-sdk/src/services/bedrock-runtime.ts b/packages/instrumentation-aws-sdk/src/services/bedrock-runtime.ts index 6519237c55..793d865f48 100644 --- a/packages/instrumentation-aws-sdk/src/services/bedrock-runtime.ts +++ b/packages/instrumentation-aws-sdk/src/services/bedrock-runtime.ts @@ -101,14 +101,9 @@ export class BedrockRuntimeServiceExtension implements ServiceExtension { case 'ConverseStream': return this.requestPreSpanHookConverse(request, config, diag, true); case 'InvokeModel': - return this.requestPreSpanHookInvokeModel(request, config, diag); + return this.requestPreSpanHookInvokeModel(request, config, diag, false); case 'InvokeModelWithResponseStream': - return this.requestPreSpanHookInvokeModelWithResponseStream( - request, - config, - diag, - true - ); + return this.requestPreSpanHookInvokeModel(request, config, diag, true); } return { @@ -164,7 +159,8 @@ export class BedrockRuntimeServiceExtension implements ServiceExtension { private requestPreSpanHookInvokeModel( request: NormalizedRequest, config: AwsSdkInstrumentationConfig, - diag: DiagLogger + diag: DiagLogger, + isStream: boolean ): RequestMetadata { let spanName: string | undefined; const spanAttributes: Attributes = { @@ -319,87 +315,8 @@ export class BedrockRuntimeServiceExtension implements ServiceExtension { return { spanName, isIncoming: false, - spanAttributes, - }; - } - - private requestPreSpanHookInvokeModelWithResponseStream( - request: NormalizedRequest, - config: AwsSdkInstrumentationConfig, - diag: DiagLogger, - isStream: boolean - ): RequestMetadata { - let spanName: string | undefined; - const spanAttributes: Attributes = { - [ATTR_GEN_AI_SYSTEM]: GEN_AI_SYSTEM_VALUE_AWS_BEDROCK, - // add operation name for InvokeModel API - }; - - const modelId = request.commandInput?.modelId; - if (modelId) { - spanAttributes[ATTR_GEN_AI_REQUEST_MODEL] = modelId; - } - - if (request.commandInput?.body) { - const requestBody = JSON.parse(request.commandInput.body); - if (modelId.includes('amazon.titan')) { - if (requestBody.textGenerationConfig?.temperature !== undefined) { - spanAttributes[ATTR_GEN_AI_REQUEST_TEMPERATURE] = - requestBody.textGenerationConfig.temperature; - } - if (requestBody.textGenerationConfig?.topP !== undefined) { - spanAttributes[ATTR_GEN_AI_REQUEST_TOP_P] = - requestBody.textGenerationConfig.topP; - } - if (requestBody.textGenerationConfig?.maxTokenCount !== undefined) { - spanAttributes[ATTR_GEN_AI_REQUEST_MAX_TOKENS] = - requestBody.textGenerationConfig.maxTokenCount; - } - if (requestBody.textGenerationConfig?.stopSequences !== undefined) { - spanAttributes[ATTR_GEN_AI_REQUEST_STOP_SEQUENCES] = - requestBody.textGenerationConfig.stopSequences; - } - } else if (modelId.includes('anthropic.claude')) { - if (requestBody.max_tokens !== undefined) { - spanAttributes[ATTR_GEN_AI_REQUEST_MAX_TOKENS] = - requestBody.max_tokens; - } - if (requestBody.temperature !== undefined) { - spanAttributes[ATTR_GEN_AI_REQUEST_TEMPERATURE] = - requestBody.temperature; - } - if (requestBody.top_p !== undefined) { - spanAttributes[ATTR_GEN_AI_REQUEST_TOP_P] = requestBody.top_p; - } - if (requestBody.stop_sequences !== undefined) { - spanAttributes[ATTR_GEN_AI_REQUEST_STOP_SEQUENCES] = - requestBody.stop_sequences; - } - } else if (modelId.includes('amazon.nova')) { - if (requestBody.inferenceConfig?.temperature !== undefined) { - spanAttributes[ATTR_GEN_AI_REQUEST_TEMPERATURE] = - requestBody.inferenceConfig.temperature; - } - if (requestBody.inferenceConfig?.top_p !== undefined) { - spanAttributes[ATTR_GEN_AI_REQUEST_TOP_P] = - requestBody.inferenceConfig.top_p; - } - if (requestBody.inferenceConfig?.max_new_tokens !== undefined) { - spanAttributes[ATTR_GEN_AI_REQUEST_MAX_TOKENS] = - requestBody.inferenceConfig.max_new_tokens; - } - if (requestBody.inferenceConfig?.stopSequences !== undefined) { - spanAttributes[ATTR_GEN_AI_REQUEST_STOP_SEQUENCES] = - requestBody.inferenceConfig.stopSequences; - } - } - } - - return { - spanName, - isIncoming: false, - spanAttributes, isStream, + spanAttributes, }; } From 0a80410034c9fd8ed416880b864e8de517370a9b Mon Sep 17 00:00:00 2001 From: Yuliia Fryshko Date: Fri, 27 Jun 2025 20:54:24 +0200 Subject: [PATCH 3/5] Moved static functions out of the method --- .../src/services/bedrock-runtime.ts | 189 +++++++++--------- 1 file changed, 100 insertions(+), 89 deletions(-) diff --git a/packages/instrumentation-aws-sdk/src/services/bedrock-runtime.ts b/packages/instrumentation-aws-sdk/src/services/bedrock-runtime.ts index 793d865f48..0c2434ca5b 100644 --- a/packages/instrumentation-aws-sdk/src/services/bedrock-runtime.ts +++ b/packages/instrumentation-aws-sdk/src/services/bedrock-runtime.ts @@ -599,24 +599,36 @@ export class BedrockRuntimeServiceExtension implements ServiceExtension { ): Promise { const stream = response.data?.body; const modelId = response.request.commandInput?.modelId; - if (!stream || !span.isRecording()) return; - - const wrappedStream = instrumentAsyncIterable( - stream, - async (chunk: { chunk?: { bytes?: Uint8Array } }) => { - const parsedChunk = parseChunk(chunk?.chunk?.bytes); + if (!stream) return; + + const wrappedStream = + BedrockRuntimeServiceExtension.instrumentAsyncIterable( + stream, + async (chunk: { chunk?: { bytes?: Uint8Array } }) => { + const parsedChunk = BedrockRuntimeServiceExtension.parseChunk( + chunk?.chunk?.bytes + ); - if (!parsedChunk) return; + if (!parsedChunk) return; - if (modelId.includes('amazon.titan')) { - recordTitanAttributes(parsedChunk); - } else if (modelId.includes('anthropic.claude')) { - recordClaudeAttributes(parsedChunk); - } else if (modelId.includes('amazon.nova')) { - recordNovaAttributes(parsedChunk); + if (modelId.includes('amazon.titan')) { + BedrockRuntimeServiceExtension.recordTitanAttributes( + parsedChunk, + span + ); + } else if (modelId.includes('anthropic.claude')) { + BedrockRuntimeServiceExtension.recordClaudeAttributes( + parsedChunk, + span + ); + } else if (modelId.includes('amazon.nova')) { + BedrockRuntimeServiceExtension.recordNovaAttributes( + parsedChunk, + span + ); + } } - } - ); + ); // Replace the original response body with our instrumented stream. // - Defers span.end() until the entire stream is consumed // This ensures downstream consumers still receive the full stream correctly, @@ -631,93 +643,92 @@ export class BedrockRuntimeServiceExtension implements ServiceExtension { } })(); return response.data; - - // Tap into the stream at the chunk level without modifying the chunk itself. - function instrumentAsyncIterable( - stream: AsyncIterable, - onChunk: (chunk: T) => void - ): AsyncIterable { - return { - [Symbol.asyncIterator]: async function* () { - for await (const chunk of stream) { - onChunk(chunk); - yield chunk; - } - }, - }; - } - - function parseChunk(bytes?: Uint8Array): any { - if (!bytes || !(bytes instanceof Uint8Array)) return null; - try { - const str = Buffer.from(bytes).toString('utf-8'); - return JSON.parse(str); - } catch (err) { - console.warn('Failed to parse streamed chunk', err); - return null; - } - } - - function recordNovaAttributes(parsedChunk: any) { - if (parsedChunk.metadata?.usage !== undefined) { - if (parsedChunk.metadata?.usage.inputTokens !== undefined) { - span.setAttribute( - ATTR_GEN_AI_USAGE_INPUT_TOKENS, - parsedChunk.metadata.usage.inputTokens - ); - } - if (parsedChunk.metadata?.usage.outputTokens !== undefined) { - span.setAttribute( - ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, - parsedChunk.metadata.usage.outputTokens - ); + } + // Tap into the stream at the chunk level without modifying the chunk itself. + private static instrumentAsyncIterable( + stream: AsyncIterable, + onChunk: (chunk: T) => void + ): AsyncIterable { + return { + [Symbol.asyncIterator]: async function* () { + for await (const chunk of stream) { + onChunk(chunk); + yield chunk; } - } - if (parsedChunk.messageStop?.stopReason !== undefined) { - span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [ - parsedChunk.messageStop.stopReason, - ]); - } + }, + }; + } + + private static parseChunk(bytes?: Uint8Array): any { + if (!bytes || !(bytes instanceof Uint8Array)) return null; + try { + const str = Buffer.from(bytes).toString('utf-8'); + return JSON.parse(str); + } catch (err) { + console.warn('Failed to parse streamed chunk', err); + return null; } + } - function recordClaudeAttributes(parsedChunk: any) { - if (parsedChunk.message?.usage?.input_tokens !== undefined) { + private static recordNovaAttributes(parsedChunk: any, span: Span) { + if (parsedChunk.metadata?.usage !== undefined) { + if (parsedChunk.metadata?.usage.inputTokens !== undefined) { span.setAttribute( ATTR_GEN_AI_USAGE_INPUT_TOKENS, - parsedChunk.message.usage.input_tokens + parsedChunk.metadata.usage.inputTokens ); } - if (parsedChunk.message?.usage?.output_tokens !== undefined) { + if (parsedChunk.metadata?.usage.outputTokens !== undefined) { span.setAttribute( ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, - parsedChunk.message.usage.output_tokens + parsedChunk.metadata.usage.outputTokens ); } - if (parsedChunk.delta?.stop_reason !== undefined) { - span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [ - parsedChunk.delta.stop_reason, - ]); - } } + if (parsedChunk.messageStop?.stopReason !== undefined) { + span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [ + parsedChunk.messageStop.stopReason, + ]); + } + } - function recordTitanAttributes(parsedChunk: any) { - if (parsedChunk.inputTextTokenCount !== undefined) { - span.setAttribute( - ATTR_GEN_AI_USAGE_INPUT_TOKENS, - parsedChunk.inputTextTokenCount - ); - } - if (parsedChunk.totalOutputTextTokenCount !== undefined) { - span.setAttribute( - ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, - parsedChunk.totalOutputTextTokenCount - ); - } - if (parsedChunk.completionReason !== undefined) { - span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [ - parsedChunk.completionReason, - ]); - } + private static recordClaudeAttributes(parsedChunk: any, span: Span) { + if (parsedChunk.message?.usage?.input_tokens !== undefined) { + span.setAttribute( + ATTR_GEN_AI_USAGE_INPUT_TOKENS, + parsedChunk.message.usage.input_tokens + ); + } + if (parsedChunk.message?.usage?.output_tokens !== undefined) { + span.setAttribute( + ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, + parsedChunk.message.usage.output_tokens + ); + } + if (parsedChunk.delta?.stop_reason !== undefined) { + span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [ + parsedChunk.delta.stop_reason, + ]); + } + } + + private static recordTitanAttributes(parsedChunk: any, span: Span) { + if (parsedChunk.inputTextTokenCount !== undefined) { + span.setAttribute( + ATTR_GEN_AI_USAGE_INPUT_TOKENS, + parsedChunk.inputTextTokenCount + ); + } + if (parsedChunk.totalOutputTextTokenCount !== undefined) { + span.setAttribute( + ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, + parsedChunk.totalOutputTextTokenCount + ); + } + if (parsedChunk.completionReason !== undefined) { + span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [ + parsedChunk.completionReason, + ]); } } } From ff59f7258ceea00b99d5ea8a0587b5447f3f1fbc Mon Sep 17 00:00:00 2001 From: Yuliia Fryshko Date: Thu, 7 Aug 2025 18:04:50 +0200 Subject: [PATCH 4/5] added instrumentation for Bedrock Response Stream of llama, cohere and mistral --- .../src/services/bedrock-runtime.ts | 145 +++++++++++++----- 1 file changed, 108 insertions(+), 37 deletions(-) diff --git a/packages/instrumentation-aws-sdk/src/services/bedrock-runtime.ts b/packages/instrumentation-aws-sdk/src/services/bedrock-runtime.ts index 0c2434ca5b..3caa13ffc7 100644 --- a/packages/instrumentation-aws-sdk/src/services/bedrock-runtime.ts +++ b/packages/instrumentation-aws-sdk/src/services/bedrock-runtime.ts @@ -16,6 +16,7 @@ import { Attributes, DiagLogger, + diag, Histogram, HrTime, Meter, @@ -59,6 +60,7 @@ import { export class BedrockRuntimeServiceExtension implements ServiceExtension { private tokenUsage!: Histogram; private operationDuration!: Histogram; + private _diag: DiagLogger = diag; updateMetricInstruments(meter: Meter) { // https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-metrics/#metric-gen_aiclienttokenusage @@ -599,18 +601,20 @@ export class BedrockRuntimeServiceExtension implements ServiceExtension { ): Promise { const stream = response.data?.body; const modelId = response.request.commandInput?.modelId; - if (!stream) return; - - const wrappedStream = - BedrockRuntimeServiceExtension.instrumentAsyncIterable( - stream, - async (chunk: { chunk?: { bytes?: Uint8Array } }) => { - const parsedChunk = BedrockRuntimeServiceExtension.parseChunk( - chunk?.chunk?.bytes - ); + if (!stream || !modelId) return; - if (!parsedChunk) return; + // Replace the original response body with our instrumented stream. + // - Defers span.end() until the entire stream is consumed + // This ensures downstream consumers still receive the full stream correctly, + // while OpenTelemetry can record span attributes from streamed data. + response.data.body = async function* ( + this: BedrockRuntimeServiceExtension + ) { + try { + for await (const chunk of stream) { + const parsedChunk = this.parseChunk(chunk?.chunk?.bytes); + if (!parsedChunk) return; if (modelId.includes('amazon.titan')) { BedrockRuntimeServiceExtension.recordTitanAttributes( parsedChunk, @@ -626,46 +630,43 @@ export class BedrockRuntimeServiceExtension implements ServiceExtension { parsedChunk, span ); + } else if (modelId.includes('meta.llama')) { + BedrockRuntimeServiceExtension.recordLlamaAttributes( + parsedChunk, + span + ); + } else if (modelId.includes('cohere.command-r')) { + BedrockRuntimeServiceExtension.recordCohereRAttributes( + parsedChunk, + span + ); + } else if (modelId.includes('cohere.command')) { + BedrockRuntimeServiceExtension.recordCohereAttributes( + parsedChunk, + span + ); + } else if (modelId.includes('mistral')) { + BedrockRuntimeServiceExtension.recordMistralAttributes( + parsedChunk, + span + ); } - } - ); - // Replace the original response body with our instrumented stream. - // - Defers span.end() until the entire stream is consumed - // This ensures downstream consumers still receive the full stream correctly, - // while OpenTelemetry can record span attributes from streamed data. - response.data.body = (async function* () { - try { - for await (const item of wrappedStream) { - yield item; + yield chunk; } } finally { span.end(); } - })(); + }.bind(this)(); return response.data; } - // Tap into the stream at the chunk level without modifying the chunk itself. - private static instrumentAsyncIterable( - stream: AsyncIterable, - onChunk: (chunk: T) => void - ): AsyncIterable { - return { - [Symbol.asyncIterator]: async function* () { - for await (const chunk of stream) { - onChunk(chunk); - yield chunk; - } - }, - }; - } - private static parseChunk(bytes?: Uint8Array): any { + private parseChunk(bytes?: Uint8Array): any { if (!bytes || !(bytes instanceof Uint8Array)) return null; try { const str = Buffer.from(bytes).toString('utf-8'); return JSON.parse(str); } catch (err) { - console.warn('Failed to parse streamed chunk', err); + this._diag.warn('Failed to parse streamed chunk', err); return null; } } @@ -731,4 +732,74 @@ export class BedrockRuntimeServiceExtension implements ServiceExtension { ]); } } + private static recordLlamaAttributes(parsedChunk: any, span: Span) { + if (parsedChunk.prompt_token_count !== undefined) { + span.setAttribute( + ATTR_GEN_AI_USAGE_INPUT_TOKENS, + parsedChunk.prompt_token_count + ); + } + if (parsedChunk.generation_token_count !== undefined) { + span.setAttribute( + ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, + parsedChunk.generation_token_count + ); + } + if (parsedChunk.stop_reason !== undefined) { + span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [ + parsedChunk.stop_reason, + ]); + } + } + + private static recordMistralAttributes(parsedChunk: any, span: Span) { + if (parsedChunk.outputs?.[0]?.text !== undefined) { + span.setAttribute( + ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, + // NOTE: We approximate the token count since this value is not directly available in the body + // According to Bedrock docs they use (total_chars / 6) to approximate token count for pricing. + // https://docs.aws.amazon.com/bedrock/latest/userguide/model-customization-prepare.html + Math.ceil(parsedChunk.outputs[0].text.length / 6) + ); + } + if (parsedChunk.outputs?.[0]?.stop_reason !== undefined) { + span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [ + parsedChunk.outputs[0].stop_reason, + ]); + } + } + + private static recordCohereAttributes(parsedChunk: any, span: Span) { + if (parsedChunk.generations?.[0]?.text !== undefined) { + span.setAttribute( + ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, + // NOTE: We approximate the token count since this value is not directly available in the body + // According to Bedrock docs they use (total_chars / 6) to approximate token count for pricing. + // https://docs.aws.amazon.com/bedrock/latest/userguide/model-customization-prepare.html + Math.ceil(parsedChunk.generations[0].text.length / 6) + ); + } + if (parsedChunk.generations?.[0]?.finish_reason !== undefined) { + span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [ + parsedChunk.generations[0].finish_reason, + ]); + } + } + + private static recordCohereRAttributes(parsedChunk: any, span: Span) { + if (parsedChunk.text !== undefined) { + // NOTE: We approximate the token count since this value is not directly available in the body + // According to Bedrock docs they use (total_chars / 6) to approximate token count for pricing. + // https://docs.aws.amazon.com/bedrock/latest/userguide/model-customization-prepare.html + span.setAttribute( + ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, + Math.ceil(parsedChunk.text.length / 6) + ); + } + if (parsedChunk.finish_reason !== undefined) { + span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [ + parsedChunk.finish_reason, + ]); + } + } } From f7bbec1541b94e488f41045aaec78c9803070c09 Mon Sep 17 00:00:00 2001 From: Yuliia Fryshko Date: Fri, 8 Aug 2025 15:23:29 +0200 Subject: [PATCH 5/5] added tests for cohere, cohere-r and mistral --- .../test/bedrock-runtime.test.ts | 148 +++++++++++++++++- ...here-command-model-attributes-to-span.json | 66 ++++++++ ...re-command-r-model-attributes-to-span.json | 66 ++++++++ ...s-mistral-ai-model-attributes-to-span.json | 66 ++++++++ 4 files changed, 344 insertions(+), 2 deletions(-) create mode 100644 packages/instrumentation-aws-sdk/test/mock-responses/bedrock-invokemodelwithstreams-adds-cohere-command-model-attributes-to-span.json create mode 100644 packages/instrumentation-aws-sdk/test/mock-responses/bedrock-invokemodelwithstreams-adds-cohere-command-r-model-attributes-to-span.json create mode 100644 packages/instrumentation-aws-sdk/test/mock-responses/bedrock-invokemodelwithstreams-adds-mistral-ai-model-attributes-to-span.json diff --git a/packages/instrumentation-aws-sdk/test/bedrock-runtime.test.ts b/packages/instrumentation-aws-sdk/test/bedrock-runtime.test.ts index 068507e4dd..02305ac7c7 100644 --- a/packages/instrumentation-aws-sdk/test/bedrock-runtime.test.ts +++ b/packages/instrumentation-aws-sdk/test/bedrock-runtime.test.ts @@ -667,7 +667,6 @@ describe('Bedrock', () => { }); const response = await client.send(command); - console.log('response', response); let collectedText = ''; if (!response.body) return; @@ -741,7 +740,6 @@ describe('Bedrock', () => { } } } - expect(collectedText).toBe( "Hello! I'm doing well, thank you for asking." ); @@ -813,6 +811,152 @@ describe('Bedrock', () => { [ATTR_GEN_AI_RESPONSE_FINISH_REASONS]: ['max_tokens'], }); }); + + it('adds mistral ai model attributes to span', async () => { + const modelId = 'mistral.mistral-small-2402-v1:0'; + + const prompt = '\n\nHuman: Hello, How are you today? \n\nAssistant:'; + const nativeRequest: any = { + prompt: prompt, + max_tokens: 20, + temperature: 0.8, + top_p: 1, + stop: ['|'], + }; + const command = new InvokeModelWithResponseStreamCommand({ + modelId, + body: JSON.stringify(nativeRequest), + }); + + const response = await client.send(command); + + let collectedText = ''; + if (!response.body) return; + for await (const chunk of response.body) { + if (chunk?.chunk?.bytes instanceof Uint8Array) { + const parsed = JSON.parse(decodeChunk(chunk)); + + if (parsed.outputs[0].text) { + collectedText += parsed.outputs[0].text; + } + } + } + expect(collectedText).toBe( + " I'm an AI, so I don't have feelings, but I'm functioning well" + ); + + const invokeModelSpans: ReadableSpan[] = + getInvokeModelWithResponseStreamSpans(); + + expect(invokeModelSpans.length).toBe(1); + expect(invokeModelSpans[0].attributes).toMatchObject({ + [ATTR_GEN_AI_SYSTEM]: GEN_AI_SYSTEM_VALUE_AWS_BEDROCK, + [ATTR_GEN_AI_REQUEST_MODEL]: modelId, + [ATTR_GEN_AI_REQUEST_MAX_TOKENS]: 20, + [ATTR_GEN_AI_REQUEST_TEMPERATURE]: 0.8, + [ATTR_GEN_AI_REQUEST_TOP_P]: 1, + [ATTR_GEN_AI_REQUEST_STOP_SEQUENCES]: ['|'], + [ATTR_GEN_AI_USAGE_INPUT_TOKENS]: 8, + [ATTR_GEN_AI_USAGE_OUTPUT_TOKENS]: 1, + [ATTR_GEN_AI_RESPONSE_FINISH_REASONS]: ['length'], + }); + }); + + it('adds cohere command r model attributes to span', async () => { + const modelId = 'cohere.command-r-v1:0'; + const prompt = 'Say this is a test'; + const nativeRequest: any = { + message: prompt, + max_tokens: 10, + temperature: 0.8, + p: 0.99, + stop_sequences: ['|'], + }; + + const command = new InvokeModelWithResponseStreamCommand({ + modelId, + body: JSON.stringify(nativeRequest), + }); + + const response = await client.send(command); + + let collectedText = ''; + if (!response.body) return; + for await (const chunk of response.body) { + if (chunk?.chunk?.bytes instanceof Uint8Array) { + const parsed = JSON.parse(decodeChunk(chunk)); + if (parsed.text) { + collectedText += parsed.text; + } + } + } + expect(collectedText).toBe("This is indeed a test. Hopefully, it's"); + + const invokeModelSpans: ReadableSpan[] = + getInvokeModelWithResponseStreamSpans(); + + expect(invokeModelSpans.length).toBe(1); + expect(invokeModelSpans[0].attributes).toMatchObject({ + [ATTR_GEN_AI_SYSTEM]: GEN_AI_SYSTEM_VALUE_AWS_BEDROCK, + [ATTR_GEN_AI_REQUEST_MODEL]: modelId, + [ATTR_GEN_AI_REQUEST_MAX_TOKENS]: 10, + [ATTR_GEN_AI_REQUEST_TEMPERATURE]: 0.8, + [ATTR_GEN_AI_REQUEST_TOP_P]: 0.99, + [ATTR_GEN_AI_REQUEST_STOP_SEQUENCES]: ['|'], + [ATTR_GEN_AI_USAGE_INPUT_TOKENS]: 3, + [ATTR_GEN_AI_USAGE_OUTPUT_TOKENS]: 1, + [ATTR_GEN_AI_RESPONSE_FINISH_REASONS]: ['MAX_TOKENS'], + }); + }); + + it('adds cohere command model attributes to span', async () => { + const modelId = 'cohere.command-light-text-v14'; + const prompt = 'Say this is a test'; + const nativeRequest: any = { + prompt: prompt, + max_tokens: 10, + temperature: 0.8, + p: 1, + stop_sequences: ['|'], + }; + + const command = new InvokeModelWithResponseStreamCommand({ + modelId, + body: JSON.stringify(nativeRequest), + }); + + const response = await client.send(command); + + let collectedText = ''; + if (!response.body) return; + for await (const chunk of response.body) { + if (chunk?.chunk?.bytes instanceof Uint8Array) { + const parsed = JSON.parse(decodeChunk(chunk)); + if (parsed.generations[0].text) { + collectedText += parsed.generations[0].text; + } + } + } + expect(collectedText).toBe( + ' Okay, I will follow your instructions and this will' + ); + + const invokeModelSpans: ReadableSpan[] = + getInvokeModelWithResponseStreamSpans(); + + expect(invokeModelSpans.length).toBe(1); + expect(invokeModelSpans[0].attributes).toMatchObject({ + [ATTR_GEN_AI_SYSTEM]: GEN_AI_SYSTEM_VALUE_AWS_BEDROCK, + [ATTR_GEN_AI_REQUEST_MODEL]: modelId, + [ATTR_GEN_AI_REQUEST_MAX_TOKENS]: 10, + [ATTR_GEN_AI_REQUEST_TEMPERATURE]: 0.8, + [ATTR_GEN_AI_REQUEST_TOP_P]: 1, + [ATTR_GEN_AI_REQUEST_STOP_SEQUENCES]: ['|'], + [ATTR_GEN_AI_USAGE_INPUT_TOKENS]: 3, + [ATTR_GEN_AI_USAGE_OUTPUT_TOKENS]: 9, + [ATTR_GEN_AI_RESPONSE_FINISH_REASONS]: ['MAX_TOKENS'], + }); + }); }); function getInvokeModelWithResponseStreamSpans(): ReadableSpan[] { diff --git a/packages/instrumentation-aws-sdk/test/mock-responses/bedrock-invokemodelwithstreams-adds-cohere-command-model-attributes-to-span.json b/packages/instrumentation-aws-sdk/test/mock-responses/bedrock-invokemodelwithstreams-adds-cohere-command-model-attributes-to-span.json new file mode 100644 index 0000000000..24d0671d6c --- /dev/null +++ b/packages/instrumentation-aws-sdk/test/mock-responses/bedrock-invokemodelwithstreams-adds-cohere-command-model-attributes-to-span.json @@ -0,0 +1,66 @@ +[ + { + "scope": "https://bedrock-runtime.us-east-1.amazonaws.com:443", + "method": "POST", + "path": "/model/cohere.command-light-text-v14/invoke-with-response-stream", + "body": { + "prompt": "Say this is a test", + "max_tokens": 10, + "temperature": 0.8, + "p": 1, + "stop_sequences": [ + "|" + ] + }, + "status": 403, + "response": { + "message": "Signature expired: 20250808T105739Z is now earlier than 20250808T130125Z (20250808T130625Z - 5 min.)" + }, + "rawHeaders": [ + "Date", + "Fri, 08 Aug 2025 13:06:25 GMT", + "Content-Type", + "application/json", + "Content-Length", + "114", + "Connection", + "keep-alive", + "x-amzn-RequestId", + "c69d0f37-e52d-4a04-97e3-ddc49f98d665", + "x-amzn-ErrorType", + "InvalidSignatureException:http://internal.amazon.com/coral/com.amazon.coral.service/" + ], + "responseIsBinary": false + }, + { + "scope": "https://bedrock-runtime.us-east-1.amazonaws.com:443", + "method": "POST", + "path": "/model/cohere.command-light-text-v14/invoke-with-response-stream", + "body": { + "prompt": "Say this is a test", + "max_tokens": 10, + "temperature": 0.8, + "p": 1, + "stop_sequences": [ + "|" + ] + }, + "status": 200, + "response": "000002630000004b1776ebe70b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a705a434936496d4e6b5a6a51304e7a686b4c54457a595755744e4467325a4331694d5752694c5467324d4455314d6a4d77596a56684d534973496d646c626d5679595852706232357a496a706265794a705a434936496a526c5a444d305a6d557a4c544d334e4755744e446b7a4d6930354d4755334c5751354d6a646c4d6a63784d7a6c6a4e694973496e526c654851694f6949675432746865537767535342336157787349475a7662477876647942356233567949476c756333527964574e306157397563794268626d5167644768706379423361577873496977695a6d6c7561584e6f58334a6c59584e7662694936496b31425746395554307446546c4d6966563073496e42796232317764434936496c4e686553423061476c7a49476c7a494745676447567a64434973496d467459587076626931695a57527962324e724c576c75646d396a595852706232354e5a58527961574e7a496a7037496d6c7563485630564739725a57354462335675644349364e5377696233563063485630564739725a57354462335675644349364d544173496d6c75646d396a595852706232354d5958526c626d4e35496a6f794e7a5973496d5a70636e4e30516e6c305a5578686447567559336b694f6a49334e6e3139222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a414243227d60d70fc7", + "rawHeaders": [ + "Date", + "Fri, 08 Aug 2025 13:06:25 GMT", + "Content-Type", + "application/vnd.amazon.eventstream", + "Transfer-Encoding", + "chunked", + "Connection", + "keep-alive", + "x-amzn-RequestId", + "cdf4478d-13ae-486d-b1db-86055230b5a1", + "X-Amzn-Bedrock-Content-Type", + "application/json" + ], + "responseIsBinary": true + } +] \ No newline at end of file diff --git a/packages/instrumentation-aws-sdk/test/mock-responses/bedrock-invokemodelwithstreams-adds-cohere-command-r-model-attributes-to-span.json b/packages/instrumentation-aws-sdk/test/mock-responses/bedrock-invokemodelwithstreams-adds-cohere-command-r-model-attributes-to-span.json new file mode 100644 index 0000000000..eac3ece011 --- /dev/null +++ b/packages/instrumentation-aws-sdk/test/mock-responses/bedrock-invokemodelwithstreams-adds-cohere-command-r-model-attributes-to-span.json @@ -0,0 +1,66 @@ +[ + { + "scope": "https://bedrock-runtime.us-east-1.amazonaws.com:443", + "method": "POST", + "path": "/model/cohere.command-r-v1%3A0/invoke-with-response-stream", + "body": { + "message": "Say this is a test", + "max_tokens": 10, + "temperature": 0.8, + "p": 0.99, + "stop_sequences": [ + "|" + ] + }, + "status": 403, + "response": { + "message": "Signature expired: 20250808T084915Z is now earlier than 20250808T105239Z (20250808T105739Z - 5 min.)" + }, + "rawHeaders": [ + "Date", + "Fri, 08 Aug 2025 10:57:39 GMT", + "Content-Type", + "application/json", + "Content-Length", + "114", + "Connection", + "keep-alive", + "x-amzn-RequestId", + "dcd1bc90-580f-46f5-ac27-a6628c4f70c5", + "x-amzn-ErrorType", + "InvalidSignatureException:http://internal.amazon.com/coral/com.amazon.coral.service/" + ], + "responseIsBinary": false + }, + { + "scope": "https://bedrock-runtime.us-east-1.amazonaws.com:443", + "method": "POST", + "path": "/model/cohere.command-r-v1%3A0/invoke-with-response-stream", + "body": { + "message": "Say this is a test", + "max_tokens": 10, + "temperature": 0.8, + "p": 0.99, + "stop_sequences": [ + "|" + ] + }, + "status": 200, + "response": "000000fc0000004b09e8fa2d0b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a706331396d615735706332686c5a4349365a6d467363325573496d56325a57353058335235634755694f694a305a5868304c57646c626d567959585270623234694c434a305a586830496a6f695647687063794a39222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a4142434445464748494a4b4c4d4e4f505152535455565758595a3031227dfe7356b8000000e70000004b1ed85cbe0b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a706331396d615735706332686c5a4349365a6d467363325573496d56325a57353058335235634755694f694a305a5868304c57646c626d567959585270623234694c434a305a586830496a6f6949476c7a496e303d222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a41424344454647227d2aa812da000000cc0000004ba8c942ab0b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a706331396d615735706332686c5a4349365a6d467363325573496d56325a57353058335235634755694f694a305a5868304c57646c626d567959585270623234694c434a305a586830496a6f6949476c755a47566c5a434a39222c2270223a226162227de5dc40e9000000d60000004b8299cd880b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a706331396d615735706332686c5a4349365a6d467363325573496d56325a57353058335235634755694f694a305a5868304c57646c626d567959585270623234694c434a305a586830496a6f694947456966513d3d222c2270223a226162636465666768696a6b6c6d6e6f70227d3e962bdf000001010000004b7210bd640b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a706331396d615735706332686c5a4349365a6d467363325573496d56325a57353058335235634755694f694a305a5868304c57646c626d567959585270623234694c434a305a586830496a6f694948526c6333516966513d3d222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a4142434445464748494a4b4c4d4e4f505152535455565758595a303132227da52a4efc000000d10000004b30b911980b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a706331396d615735706332686c5a4349365a6d467363325573496d56325a57353058335235634755694f694a305a5868304c57646c626d567959585270623234694c434a305a586830496a6f694c694a39222c2270223a226162636465666768696a6b6c6d6e6f227dcacaffe7000000d00000004b0dd938280b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a706331396d615735706332686c5a4349365a6d467363325573496d56325a57353058335235634755694f694a305a5868304c57646c626d567959585270623234694c434a305a586830496a6f69494568766347566d6457787365534a39222c2270223a226162227dd9e4e3e7000000ee0000004b13c83ecf0b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a706331396d615735706332686c5a4349365a6d467363325573496d56325a57353058335235634755694f694a305a5868304c57646c626d567959585270623234694c434a305a586830496a6f694c434a39222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a4142434445464748494a4b4c4d4e4f505152227da9d92024000000d50000004bc539b7580b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a706331396d615735706332686c5a4349365a6d467363325573496d56325a57353058335235634755694f694a305a5868304c57646c626d567959585270623234694c434a305a586830496a6f6949476c30496e303d222c2270223a226162636465666768696a6b6c6d6e6f227d291846b2000000fb0000004bbbc8263d0b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a706331396d615735706332686c5a4349365a6d467363325573496d56325a57353058335235634755694f694a305a5868304c57646c626d567959585270623234694c434a305a586830496a6f694a334d6966513d3d222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a4142434445464748494a4b4c4d4e4f505152535455565758595a30227db50235630000041c0000004bbaad7fe40b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a706331396d615735706332686c5a43493664484a315a5377695a585a6c626e526664486c775a534936496e4e30636d56686253316c626d51694c434a795a584e776232357a5a53493665794a795a584e776232357a5a5639705a434936496a4d334f57566b4d4445344c7a4a6d4f475978597a64694c5449344d5759744e475a695a5331684e4459334c5452694d4755304e474e6b4d47517a4d534973496e526c654851694f694a5561476c7a49476c7a49476c755a47566c5a4342684948526c63335175494568766347566d64577873655377676158516e63794973496d646c626d56795958527062323566615751694f6949784d4459785a47457959693077597a49314c5451355a474974596d4a694f5330775a475134596d457a597a68685a5451694c434a6a614746305832687063335276636e6b694f6c7437496e4a76624755694f694a5655305653496977696257567a6332466e5a534936496c4e686553423061476c7a49476c7a494745676447567a64434a394c487369636d39735a534936496b4e4951565243543151694c434a745a584e7a5957646c496a6f69564768706379427063794270626d526c5a575167595342305a584e304c6942496233426c5a6e567362486b7349476c304a334d6966563073496d5a70626d6c7a614639795a57467a623234694f694a4e515668665645394c5255355449697769625756305953493665794a6863476c66646d567963326c766269493665794a325a584a7a61573975496a6f694d534a394c434a69615778735a5752666457357064484d694f6e73696157357764585266644739725a57357a496a6f314c434a766458527764585266644739725a57357a496a6f784d483073496e5276613256756379493665794a70626e4231644639306232746c626e4d694f6a63784c434a766458527764585266644739725a57357a496a6f784d483139665377695a6d6c7561584e6f58334a6c59584e7662694936496b31425746395554307446546c4d694c434a686257463662323474596d566b636d396a61793170626e5a76593246306157397554575630636d6c6a6379493665794a70626e4231644652766132567551323931626e51694f6a5573496d393164484231644652766132567551323931626e51694f6a45774c434a70626e5a765932463061573975544746305a57356a655349364d6a41774c434a6d61584a7a64454a356447564d5958526c626d4e35496a6f354d333139222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a4142434445464748227dffffe971", + "rawHeaders": [ + "Date", + "Fri, 08 Aug 2025 10:57:39 GMT", + "Content-Type", + "application/vnd.amazon.eventstream", + "Transfer-Encoding", + "chunked", + "Connection", + "keep-alive", + "x-amzn-RequestId", + "22fb7d78-9282-4b2d-a946-46d339e711f9", + "X-Amzn-Bedrock-Content-Type", + "application/json" + ], + "responseIsBinary": true + } +] \ No newline at end of file diff --git a/packages/instrumentation-aws-sdk/test/mock-responses/bedrock-invokemodelwithstreams-adds-mistral-ai-model-attributes-to-span.json b/packages/instrumentation-aws-sdk/test/mock-responses/bedrock-invokemodelwithstreams-adds-mistral-ai-model-attributes-to-span.json new file mode 100644 index 0000000000..50bb8df41e --- /dev/null +++ b/packages/instrumentation-aws-sdk/test/mock-responses/bedrock-invokemodelwithstreams-adds-mistral-ai-model-attributes-to-span.json @@ -0,0 +1,66 @@ +[ + { + "scope": "https://bedrock-runtime.us-east-1.amazonaws.com:443", + "method": "POST", + "path": "/model/mistral.mistral-small-2402-v1%3A0/invoke-with-response-stream", + "body": { + "prompt": "\n\nHuman: Hello, How are you today? \n\nAssistant:", + "max_tokens": 20, + "temperature": 0.8, + "top_p": 1, + "stop": [ + "|" + ] + }, + "status": 403, + "response": { + "message": "Signature expired: 20250521T075304Z is now earlier than 20250808T084415Z (20250808T084915Z - 5 min.)" + }, + "rawHeaders": [ + "Date", + "Fri, 08 Aug 2025 08:49:15 GMT", + "Content-Type", + "application/json", + "Content-Length", + "114", + "Connection", + "keep-alive", + "x-amzn-RequestId", + "0977e422-c56f-45d4-93b2-db898bb31bb3", + "x-amzn-ErrorType", + "InvalidSignatureException:http://internal.amazon.com/coral/com.amazon.coral.service/" + ], + "responseIsBinary": false + }, + { + "scope": "https://bedrock-runtime.us-east-1.amazonaws.com:443", + "method": "POST", + "path": "/model/mistral.mistral-small-2402-v1%3A0/invoke-with-response-stream", + "body": { + "prompt": "\n\nHuman: Hello, How are you today? \n\nAssistant:", + "max_tokens": 20, + "temperature": 0.8, + "top_p": 1, + "stop": [ + "|" + ] + }, + "status": 200, + "response": "000000e90000004ba1e8e2df0b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a76645852776458527a496a706265794a305a586830496a6f6949456b694c434a7a6447397758334a6c59584e7662694936626e56736248316466513d3d222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a4142434445464748494a4b4c4d4e4f505152535455565758595a30313233343536227d8b7c1dec000000c50000004ba5d920da0b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a76645852776458527a496a706265794a305a586830496a6f694a794973496e4e3062334266636d566863323975496a70756457787366563139222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a41227dcf8f562b000000b90000004b99fb2d140b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a76645852776458527a496a706265794a305a586830496a6f6962534973496e4e3062334266636d566863323975496a70756457787366563139222c2270223a226162636465666768696a6b6c6d6e6f227d2a509e97000000bc0000004b511ba2640b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a76645852776458527a496a706265794a305a586830496a6f69494746754969776963335276634639795a57467a623234694f6d3531624778395858303d222c2270223a226162636465666768696a6b6c6d6e227d77a26a9b000000d50000004bc539b7580b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a76645852776458527a496a706265794a305a586830496a6f694945464a4969776963335276634639795a57467a623234694f6d3531624778395858303d222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a4142434445464748494a4b4c4d227db1f2cf63000000e00000004bacf880ae0b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a76645852776458527a496a706265794a305a586830496a6f694c434973496e4e3062334266636d566863323975496a70756457787366563139222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a4142434445464748494a4b4c4d4e4f505152535455565758595a3031227d4dc7ae7f000000be0000004b2bdbf1040b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a76645852776458527a496a706265794a305a586830496a6f6949484e764969776963335276634639795a57467a623234694f6d3531624778395858303d222c2270223a226162636465666768696a6b6c6d6e6f70227db4ad8a81000000e50000004b64180fde0b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a76645852776458527a496a706265794a305a586830496a6f6949456b694c434a7a6447397758334a6c59584e7662694936626e56736248316466513d3d222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a4142434445464748494a4b4c4d4e4f505152535455565758595a303132227de1c989a3000000e30000004beb58fa7e0b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a76645852776458527a496a706265794a305a586830496a6f694947527662694973496e4e3062334266636d566863323975496a70756457787366563139222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a4142434445464748494a4b4c4d4e4f505152535455565758595a30227d3b79d4fd000000cf0000004bef69387b0b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a76645852776458527a496a706265794a305a586830496a6f694a794973496e4e3062334266636d566863323975496a70756457787366563139222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a4142434445464748494a4b227daa75eb99000000d80000004b3da973e90b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a76645852776458527a496a706265794a305a586830496a6f6964434973496e4e3062334266636d566863323975496a70756457787366563139222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a4142434445464748494a4b4c4d4e4f5051525354227d7ac0aa03000000c40000004b98b9096a0b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a76645852776458527a496a706265794a305a586830496a6f6949476868646d55694c434a7a6447397758334a6c59584e7662694936626e56736248316466513d3d222c2270223a226162636465666768696a6b6c6d6e6f707172227dbca362ec000000d70000004bbff9e4380b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a76645852776458527a496a706265794a305a586830496a6f6949475a6c5a577870626d647a4969776963335276634639795a57467a623234694f6d3531624778395858303d222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a41424344454647227d65e56e96000000da0000004b476920890b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a76645852776458527a496a706265794a305a586830496a6f694c434973496e4e3062334266636d566863323975496a70756457787366563139222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a4142434445464748494a4b4c4d4e4f50515253545556227ddd32803e000000b50000004b5c0bc0150b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a76645852776458527a496a706265794a305a586830496a6f6949474a3164434973496e4e3062334266636d566863323975496a70756457787366563139222c2270223a2261626364656667227d1c56ddb3000000b80000004ba49b04a40b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a76645852776458527a496a706265794a305a586830496a6f6949456b694c434a7a6447397758334a6c59584e7662694936626e56736248316466513d3d222c2270223a226162636465666768696a227d65cae4ae000000c60000004be2795a0a0b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a76645852776458527a496a706265794a305a586830496a6f694a794973496e4e3062334266636d566863323975496a70756457787366563139222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a4142227dd10fb4d1000000bf0000004b16bbd8b40b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a76645852776458527a496a706265794a305a586830496a6f6962534973496e4e3062334266636d566863323975496a70756457787366563139222c2270223a226162636465666768696a6b6c6d6e6f707172737475227d3e3a599f000000cc0000004ba8c942ab0b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a76645852776458527a496a706265794a305a586830496a6f6949475a31626d4e30615739756157356e4969776963335276634639795a57467a623234694f6d3531624778395858303d222c2270223a226162636465666768696a6b6c6d6e6f707172227d987a33f4000001870000004b4cb6f8560b3a6576656e742d747970650700056368756e6b0d3a636f6e74656e742d747970650700106170706c69636174696f6e2f6a736f6e0d3a6d6573736167652d747970650700056576656e747b226279746573223a2265794a76645852776458527a496a706265794a305a586830496a6f694948646c624777694c434a7a6447397758334a6c59584e7662694936496d786c626d643061434a395853776959573168656d39754c574a6c5a484a76593273746157353262324e6864476c76626b316c64484a7059334d694f6e736961573577645852556232746c626b4e7664573530496a6f784f5377696233563063485630564739725a57354462335675644349364d6a4173496d6c75646d396a595852706232354d5958526c626d4e35496a6f304d446773496d5a70636e4e30516e6c305a5578686447567559336b694f6a457a4e483139222c2270223a226162636465666768696a6b6c6d6e6f707172737475767778797a4142434445464748494a4b4c4d4e4f227d6aae213a", + "rawHeaders": [ + "Date", + "Fri, 08 Aug 2025 08:49:15 GMT", + "Content-Type", + "application/vnd.amazon.eventstream", + "Transfer-Encoding", + "chunked", + "Connection", + "keep-alive", + "x-amzn-RequestId", + "dc17fd73-04ea-4487-947a-6f290dbb20f6", + "X-Amzn-Bedrock-Content-Type", + "application/json" + ], + "responseIsBinary": true + } +] \ No newline at end of file