Skip to content

Commit a600740

Browse files
Add Bedrock, Bedrock Agent, Bedrock Runtime, and Bedrock Agent Runtime support (#74)
1 parent d896c8f commit a600740

File tree

10 files changed

+1055
-38
lines changed

10 files changed

+1055
-38
lines changed

aws-distro-opentelemetry-node-autoinstrumentation/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@
3838
"url": "https://github.com/aws-observability/aws-otel-js-instrumentation/issues"
3939
},
4040
"devDependencies": {
41+
"@aws-sdk/client-bedrock": "3.632.0",
42+
"@aws-sdk/client-bedrock-agent": "3.632.0",
43+
"@aws-sdk/client-bedrock-agent-runtime": "3.632.0",
44+
"@aws-sdk/client-bedrock-runtime": "3.632.0",
4145
"@aws-sdk/client-kinesis": "3.632.0",
4246
"@aws-sdk/client-s3": "3.632.0",
4347
"@aws-sdk/client-sqs": "3.632.0",

aws-distro-opentelemetry-node-autoinstrumentation/src/aws-attribute-keys.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,8 @@ export const AWS_ATTRIBUTE_KEYS: { [key: string]: string } = {
3131
AWS_SQS_QUEUE_NAME: 'aws.sqs.queue.name',
3232
AWS_KINESIS_STREAM_NAME: 'aws.kinesis.stream.name',
3333
AWS_DYNAMODB_TABLE_NAMES: SEMATTRS_AWS_DYNAMODB_TABLE_NAMES,
34+
AWS_BEDROCK_DATA_SOURCE_ID: 'aws.bedrock.data_source.id',
35+
AWS_BEDROCK_KNOWLEDGE_BASE_ID: 'aws.bedrock.knowledge_base.id',
36+
AWS_BEDROCK_AGENT_ID: 'aws.bedrock.agent.id',
37+
AWS_BEDROCK_GUARDRAIL_ID: 'aws.bedrock.guardrail.id',
3438
};

aws-distro-opentelemetry-node-autoinstrumentation/src/aws-metric-attribute-generator.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ const NORMALIZED_DYNAMO_DB_SERVICE_NAME: string = 'AWS::DynamoDB';
5555
const NORMALIZED_KINESIS_SERVICE_NAME: string = 'AWS::Kinesis';
5656
const NORMALIZED_S3_SERVICE_NAME: string = 'AWS::S3';
5757
const NORMALIZED_SQS_SERVICE_NAME: string = 'AWS::SQS';
58+
const NORMALIZED_BEDROCK_SERVICE_NAME: string = 'AWS::Bedrock';
59+
const NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME: string = 'AWS::BedrockRuntime';
5860

5961
const DB_CONNECTION_RESOURCE_TYPE: string = 'DB::Connection';
6062
// As per https://opentelemetry.io/docs/specs/semconv/resource/#service, if service name is not specified, SDK defaults
@@ -317,10 +319,19 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
317319
* Cloud Control resource format</a> as much as possible, with special attention to services we
318320
* can detect remote resource information for. Long term, we would like to normalize service name
319321
* in the upstream.
322+
*
323+
* For Bedrock, Bedrock Agent, and Bedrock Agent Runtime, we can align with AWS Cloud Control and use
324+
* AWS::Bedrock for RemoteService. For BedrockRuntime, we are using AWS::BedrockRuntime
325+
* as the associated remote resource (Model) is not listed in Cloud Control.
320326
*/
321327
private static normalizeRemoteServiceName(span: ReadableSpan, serviceName: string): string {
322328
if (AwsSpanProcessingUtil.isAwsSDKSpan(span)) {
323-
return 'AWS::' + serviceName;
329+
const awsSdkServiceMapping: { [key: string]: string } = {
330+
BedrockAgent: NORMALIZED_BEDROCK_SERVICE_NAME,
331+
BedrockAgentRuntime: NORMALIZED_BEDROCK_SERVICE_NAME,
332+
BedrockRuntime: NORMALIZED_BEDROCK_RUNTIME_SERVICE_NAME,
333+
};
334+
return awsSdkServiceMapping[serviceName] || 'AWS::' + serviceName;
324335
}
325336
return serviceName;
326337
}
@@ -369,6 +380,31 @@ export class AwsMetricAttributeGenerator implements MetricAttributeGenerator {
369380
remoteResourceIdentifier = SqsUrlParser.getQueueName(
370381
AwsMetricAttributeGenerator.escapeDelimiters(span.attributes[AWS_ATTRIBUTE_KEYS.AWS_SQS_QUEUE_URL])
371382
);
383+
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID)) {
384+
remoteResourceType = NORMALIZED_BEDROCK_SERVICE_NAME + '::Agent';
385+
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
386+
span.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID]
387+
);
388+
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID)) {
389+
remoteResourceType = NORMALIZED_BEDROCK_SERVICE_NAME + '::DataSource';
390+
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
391+
span.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID]
392+
);
393+
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID)) {
394+
remoteResourceType = NORMALIZED_BEDROCK_SERVICE_NAME + '::Guardrail';
395+
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
396+
span.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID]
397+
);
398+
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID)) {
399+
remoteResourceType = NORMALIZED_BEDROCK_SERVICE_NAME + '::KnowledgeBase';
400+
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
401+
span.attributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]
402+
);
403+
} else if (AwsSpanProcessingUtil.isKeyPresent(span, AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL)) {
404+
remoteResourceType = NORMALIZED_BEDROCK_SERVICE_NAME + '::Model';
405+
remoteResourceIdentifier = AwsMetricAttributeGenerator.escapeDelimiters(
406+
span.attributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL]
407+
);
372408
}
373409
} else if (AwsSpanProcessingUtil.isDBSpan(span)) {
374410
remoteResourceType = DB_CONNECTION_RESOURCE_TYPE;

aws-distro-opentelemetry-node-autoinstrumentation/src/aws-span-processing-util.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ export class AwsSpanProcessingUtil {
4040
static MAX_KEYWORD_LENGTH: number = 27;
4141
static SQL_DIALECT_PATTERN: string = '^(?:' + AwsSpanProcessingUtil.getDialectKeywords().join('|') + ')\\b';
4242

43+
// TODO: Use Semantic Conventions once upgraded
44+
static GEN_AI_REQUEST_MODEL: string = 'gen_ai.request.model';
45+
static GEN_AI_SYSTEM: string = 'gen_ai.system';
46+
4347
static getDialectKeywords(): string[] {
4448
return SQL_DIALECT_KEYWORDS_JSON.keywords;
4549
}
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { Attributes, DiagLogger, Span, SpanKind, Tracer } from '@opentelemetry/api';
5+
import {
6+
AwsSdkInstrumentationConfig,
7+
NormalizedRequest,
8+
NormalizedResponse,
9+
} from '@opentelemetry/instrumentation-aws-sdk';
10+
import { AWS_ATTRIBUTE_KEYS } from '../../../aws-attribute-keys';
11+
import { RequestMetadata, ServiceExtension } from '../../../third-party/otel/aws/services/ServiceExtension';
12+
import { AwsSpanProcessingUtil } from '../../../aws-span-processing-util';
13+
14+
const AGENT_ID: string = 'agentId';
15+
const KNOWLEDGE_BASE_ID: string = 'knowledgeBaseId';
16+
const DATA_SOURCE_ID: string = 'dataSourceId';
17+
const GUARDRAIL_ID: string = 'guardrailId';
18+
const MODEL_ID: string = 'modelId';
19+
const AWS_BEDROCK_SYSTEM: string = 'aws_bedrock';
20+
21+
const AGENT_OPERATIONS = [
22+
'CreateAgentActionGroup',
23+
'CreateAgentAlias',
24+
'DeleteAgentActionGroup',
25+
'DeleteAgentAlias',
26+
'DeleteAgent',
27+
'DeleteAgentVersion',
28+
'GetAgentActionGroup',
29+
'GetAgentAlias',
30+
'GetAgent',
31+
'GetAgentVersion',
32+
'ListAgentActionGroups',
33+
'ListAgentAliases',
34+
'ListAgentKnowledgeBases',
35+
'ListAgentVersions',
36+
'PrepareAgent',
37+
'UpdateAgentActionGroup',
38+
'UpdateAgentAlias',
39+
'UpdateAgent',
40+
];
41+
42+
const KNOWLEDGE_BASE_OPERATIONS = [
43+
'AssociateAgentKnowledgeBase',
44+
'CreateDataSource',
45+
'DeleteKnowledgeBase',
46+
'DisassociateAgentKnowledgeBase',
47+
'GetAgentKnowledgeBase',
48+
'GetKnowledgeBase',
49+
'ListDataSources',
50+
'UpdateAgentKnowledgeBase',
51+
];
52+
53+
const DATA_SOURCE_OPERATIONS = ['DeleteDataSource', 'GetDataSource', 'UpdateDataSource'];
54+
55+
// The following constants map the way we present the data in telemetry to how they appear in request/responses
56+
// e.g. we put `aws.bedrock.knowledge_base.id` into trace data by finding `knowledgeBaseId`
57+
const agentOperationAttributeKeyMapping = { [AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID]: AGENT_ID };
58+
const knowledgeBaseOperationAttributeKeyMapping = {
59+
[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID]: KNOWLEDGE_BASE_ID,
60+
};
61+
const dataSourceOperationAttributeKeyMapping = {
62+
[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_DATA_SOURCE_ID]: DATA_SOURCE_ID,
63+
};
64+
65+
// This map allows us to get all relevant attribute key mappings for a given operation
66+
const operationToBedrockAgentAttributesMap: { [key: string]: { [key: string]: string } } = {};
67+
for (const operation of AGENT_OPERATIONS) {
68+
operationToBedrockAgentAttributesMap[operation] = agentOperationAttributeKeyMapping;
69+
}
70+
for (const operation of KNOWLEDGE_BASE_OPERATIONS) {
71+
operationToBedrockAgentAttributesMap[operation] = knowledgeBaseOperationAttributeKeyMapping;
72+
}
73+
for (const operation of DATA_SOURCE_OPERATIONS) {
74+
operationToBedrockAgentAttributesMap[operation] = dataSourceOperationAttributeKeyMapping;
75+
}
76+
77+
// This class is an extension for <a
78+
// href="https://docs.aws.amazon.com/bedrock/latest/APIReference/API_Operations_Agents_for_Amazon_Bedrock.html">
79+
// Agents for Amazon Bedrock</a>.
80+
// This class primarily identify three types of resource based operations: AGENT_OPERATIONS,
81+
// KNOWLEDGE_BASE_OPERATIONS, and DATA_SOURCE_OPERATIONS. We only support operations that are related to
82+
// the resource and where the context contains the resource ID.
83+
export class BedrockAgentServiceExtension implements ServiceExtension {
84+
requestPreSpanHook(
85+
request: NormalizedRequest,
86+
config: AwsSdkInstrumentationConfig,
87+
diag: DiagLogger
88+
): RequestMetadata {
89+
const spanAttributes: Attributes = {};
90+
const isIncoming = false;
91+
const spanKind: SpanKind = SpanKind.CLIENT;
92+
let spanName: string | undefined;
93+
94+
const operation: string = request.commandName;
95+
if (operation && operationToBedrockAgentAttributesMap[operation]) {
96+
const bedrockAgentServiceInfo = operationToBedrockAgentAttributesMap[operation];
97+
for (const serviceInfo of Object.entries(bedrockAgentServiceInfo)) {
98+
const [attributeKey, requestParamKey] = serviceInfo;
99+
const requestParamValue = request.commandInput?.[requestParamKey];
100+
101+
if (requestParamValue) {
102+
spanAttributes[attributeKey] = requestParamValue;
103+
}
104+
}
105+
}
106+
107+
return {
108+
isIncoming,
109+
spanAttributes,
110+
spanKind,
111+
spanName,
112+
};
113+
}
114+
115+
responseHook(response: NormalizedResponse, span: Span, tracer: Tracer, config: AwsSdkInstrumentationConfig): void {
116+
const operation: string = response.request.commandName;
117+
if (operation && operationToBedrockAgentAttributesMap[operation]) {
118+
const bedrockAgentServiceInfo = operationToBedrockAgentAttributesMap[operation];
119+
for (const serviceInfo of Object.entries(bedrockAgentServiceInfo)) {
120+
const [attributeKey, responseParamKey] = serviceInfo;
121+
const responseParamValue = response.data[responseParamKey];
122+
123+
if (responseParamValue) {
124+
span.setAttribute(attributeKey, responseParamValue);
125+
}
126+
}
127+
}
128+
}
129+
}
130+
131+
// This class is an extension for <a
132+
// href="https://docs.aws.amazon.com/bedrock/latest/APIReference/API_Operations_Agents_for_Amazon_Bedrock_Runtime.html">
133+
// Agents for Amazon Bedrock Runtime</a>.
134+
export class BedrockAgentRuntimeServiceExtension implements ServiceExtension {
135+
requestPreSpanHook(
136+
request: NormalizedRequest,
137+
config: AwsSdkInstrumentationConfig,
138+
diag: DiagLogger
139+
): RequestMetadata {
140+
const spanAttributes: Attributes = {};
141+
const isIncoming = false;
142+
const spanKind: SpanKind = SpanKind.CLIENT;
143+
let spanName: string | undefined;
144+
145+
const agentId = request.commandInput?.[AGENT_ID];
146+
const knowledgeBaseId = request.commandInput?.[KNOWLEDGE_BASE_ID];
147+
148+
if (agentId) {
149+
spanAttributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_AGENT_ID] = agentId;
150+
}
151+
if (knowledgeBaseId) {
152+
spanAttributes[AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_KNOWLEDGE_BASE_ID] = knowledgeBaseId;
153+
}
154+
155+
return {
156+
isIncoming,
157+
spanAttributes,
158+
spanKind,
159+
spanName,
160+
};
161+
}
162+
}
163+
164+
// This class is an extension for <a
165+
// href="https://docs.aws.amazon.com/bedrock/latest/APIReference/API_Operations_Amazon_Bedrock.html">Bedrock</a>.
166+
export class BedrockServiceExtension implements ServiceExtension {
167+
// Must be implemented, returning empty metadata
168+
requestPreSpanHook(
169+
request: NormalizedRequest,
170+
config: AwsSdkInstrumentationConfig,
171+
diag: DiagLogger
172+
): RequestMetadata {
173+
const spanAttributes: Attributes = {};
174+
const isIncoming = false;
175+
const spanKind: SpanKind = SpanKind.CLIENT;
176+
let spanName: string | undefined;
177+
return {
178+
isIncoming,
179+
spanAttributes,
180+
spanKind,
181+
spanName,
182+
};
183+
}
184+
responseHook(response: NormalizedResponse, span: Span, tracer: Tracer, config: AwsSdkInstrumentationConfig): void {
185+
const guardrail_id = response.data[GUARDRAIL_ID];
186+
187+
if (guardrail_id) {
188+
span.setAttribute(AWS_ATTRIBUTE_KEYS.AWS_BEDROCK_GUARDRAIL_ID, guardrail_id);
189+
}
190+
}
191+
}
192+
193+
// This class is an extension for <a
194+
// href="https://docs.aws.amazon.com/bedrock/latest/APIReference/API_Operations_Amazon_Bedrock_Runtime.html">
195+
// Amazon Bedrock Runtime</a>.
196+
export class BedrockRuntimeServiceExtension implements ServiceExtension {
197+
requestPreSpanHook(
198+
request: NormalizedRequest,
199+
config: AwsSdkInstrumentationConfig,
200+
diag: DiagLogger
201+
): RequestMetadata {
202+
const spanAttributes: Attributes = {};
203+
const isIncoming = false;
204+
const spanKind: SpanKind = SpanKind.CLIENT;
205+
let spanName: string | undefined;
206+
207+
const modelId = request.commandInput?.[MODEL_ID];
208+
209+
spanAttributes[AwsSpanProcessingUtil.GEN_AI_SYSTEM] = AWS_BEDROCK_SYSTEM;
210+
if (modelId) {
211+
spanAttributes[AwsSpanProcessingUtil.GEN_AI_REQUEST_MODEL] = modelId;
212+
}
213+
214+
return {
215+
isIncoming,
216+
spanAttributes,
217+
spanKind,
218+
spanName,
219+
};
220+
}
221+
}

aws-distro-opentelemetry-node-autoinstrumentation/src/patches/instrumentation-patch.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ import { AWSXRAY_TRACE_ID_HEADER, AWSXRayPropagator } from '@opentelemetry/propa
1818
import { APIGatewayProxyEventHeaders, Context } from 'aws-lambda';
1919
import { AWS_ATTRIBUTE_KEYS } from '../aws-attribute-keys';
2020
import { RequestMetadata } from '../third-party/otel/aws/services/ServiceExtension';
21+
import {
22+
BedrockAgentRuntimeServiceExtension,
23+
BedrockAgentServiceExtension,
24+
BedrockRuntimeServiceExtension,
25+
BedrockServiceExtension,
26+
} from './aws/services/bedrock';
2127
import { KinesisServiceExtension } from './aws/services/kinesis';
2228
import { S3ServiceExtension } from './aws/services/s3';
2329

@@ -51,6 +57,10 @@ export function applyInstrumentationPatches(instrumentations: Instrumentation[])
5157
if (services) {
5258
services.set('S3', new S3ServiceExtension());
5359
services.set('Kinesis', new KinesisServiceExtension());
60+
services.set('Bedrock', new BedrockServiceExtension());
61+
services.set('BedrockAgent', new BedrockAgentServiceExtension());
62+
services.set('BedrockAgentRuntime', new BedrockAgentRuntimeServiceExtension());
63+
services.set('BedrockRuntime', new BedrockRuntimeServiceExtension());
5464
patchSqsServiceExtension(services.get('SQS'));
5565
}
5666
} else if (instrumentation.instrumentationName === '@opentelemetry/instrumentation-aws-lambda') {

0 commit comments

Comments
 (0)