Skip to content

Commit 2b7feac

Browse files
anuraagatrentm
andauthored
feat(instrumentation-aws-sdk): add bedrock-runtime extension to apply gen ai conventions (#2700)
Co-authored-by: Trent Mick <[email protected]>
1 parent 7f48564 commit 2b7feac

File tree

11 files changed

+1622
-136
lines changed

11 files changed

+1622
-136
lines changed

package-lock.json

Lines changed: 991 additions & 97 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 11 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1-
# Note: tests must set `SKIP_TEST_IF_DISABLE=true` to override usage of
2-
# `mocha --require '../../../scripts/skip-test-if.js' ...` if calling `npm test`.
3-
41
# Versions [3.363.0, 3.377.0] of all @aws-sdk/client-* were bad releases. See:
52
# - https://github.com/open-telemetry/opentelemetry-js-contrib/pull/2464#issuecomment-2403652552
63
# - https://github.com/open-telemetry/opentelemetry-js-contrib/issues/1828#issuecomment-1834276719
74

8-
# node version support in JS SDK v3:
9-
# - 14.x dropped in v3.567.0 https://github.com/aws/aws-sdk-js-v3/pull/6034
10-
# - 16.x dropped in v3.723.0 https://github.com/aws/aws-sdk-js-v3/pull/6775
5+
"@aws-sdk/client-bedrock-runtime":
6+
env:
7+
- SKIP_TEST_IF_DISABLE=true
8+
jobs:
9+
- node: ">=18"
10+
versions:
11+
include: "^3.587.0"
12+
exclude: ">=3.363.0 <=3.377.0"
13+
mode: "max-7"
14+
commands:
15+
- mocha --require '@opentelemetry/contrib-test-utils' test/bedrock-runtime.test.ts
1116

1217
"@aws-sdk/client-s3":
1318
env:
@@ -22,22 +27,6 @@
2227
commands:
2328
- mocha --require '@opentelemetry/contrib-test-utils' test/aws-sdk-v3-s3.test.ts
2429
- mocha --require '@opentelemetry/contrib-test-utils' test/s3.test.ts
25-
- node: "16"
26-
versions:
27-
include: ">=3.6.1 <3.723.0"
28-
exclude: "3.529.0 || >=3.363.0 <=3.377.0"
29-
mode: "max-7"
30-
commands:
31-
- mocha --require '@opentelemetry/contrib-test-utils' test/aws-sdk-v3-s3.test.ts
32-
- mocha --require '@opentelemetry/contrib-test-utils' test/s3.test.ts
33-
- node: "14"
34-
versions:
35-
include: ">=3.6.1 <3.567.0"
36-
exclude: "3.529.0 || >=3.363.0 <=3.377.0"
37-
mode: "max-7"
38-
commands:
39-
- mocha --require '@opentelemetry/contrib-test-utils' test/aws-sdk-v3-s3.test.ts
40-
- mocha --require '@opentelemetry/contrib-test-utils' test/s3.test.ts
4130

4231
"@aws-sdk/client-sqs":
4332
env:
@@ -50,18 +39,3 @@
5039
mode: "max-7"
5140
commands:
5241
- mocha --require '@opentelemetry/contrib-test-utils' test/aws-sdk-v3-sqs.test.ts
53-
- node: "16"
54-
versions:
55-
include: ">=3.24.0 <3.723.0"
56-
exclude: ">=3.363.0 <=3.377.0"
57-
mode: "max-7"
58-
commands:
59-
- mocha --require '@opentelemetry/contrib-test-utils' test/aws-sdk-v3-sqs.test.ts
60-
- node: "14"
61-
versions:
62-
include: ">=3.24.0 <3.567.0"
63-
exclude: ">=3.363.0 <=3.377.0"
64-
mode: "max-7"
65-
commands:
66-
- mocha --require '@opentelemetry/contrib-test-utils' test/aws-sdk-v3-sqs.test.ts
67-

plugins/node/opentelemetry-instrumentation-aws-sdk/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ Specific service logic currently implemented for:
9494
- [SNS](./doc/sns.md)
9595
- [Lambda](./doc/lambda.md)
9696
- DynamoDb
97+
- Amazon Bedrock Runtime (See the [GenAI semantic conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/).)
9798

9899
## Potential Side Effects
99100

plugins/node/opentelemetry-instrumentation-aws-sdk/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"prewatch": "npm run precompile",
3636
"prepublishOnly": "npm run compile",
3737
"tdd": "npm run test -- --watch-extensions ts --watch",
38-
"test": "SKIP_TEST_IF_NODE_OLDER_THAN=18 nyc mocha --require '../../../scripts/skip-test-if.js' --require '@opentelemetry/contrib-test-utils' 'test/**/*.test.ts'",
38+
"test": "nyc mocha --require '@opentelemetry/contrib-test-utils' 'test/**/*.test.ts'",
3939
"test-all-versions": "tav",
4040
"version:update": "node ../../../scripts/version-update.js",
4141
"watch": "tsc -w"
@@ -50,6 +50,7 @@
5050
"@opentelemetry/semantic-conventions": "^1.27.0"
5151
},
5252
"devDependencies": {
53+
"@aws-sdk/client-bedrock-runtime": "^3.587.0",
5354
"@aws-sdk/client-dynamodb": "^3.85.0",
5455
"@aws-sdk/client-kinesis": "^3.85.0",
5556
"@aws-sdk/client-lambda": "^3.85.0",

plugins/node/opentelemetry-instrumentation-aws-sdk/src/aws-sdk.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,9 @@ export class AwsInstrumentation extends InstrumentationBase<AwsSdkInstrumentatio
323323
const serviceName =
324324
clientConfig?.serviceId ??
325325
removeSuffixFromStringIfExists(
326-
awsExecutionContext.clientName,
326+
// Use 'AWS' as a fallback serviceName to match type definition.
327+
// In practice, `clientName` should always be set.
328+
awsExecutionContext.clientName || 'AWS',
327329
'Client'
328330
);
329331
const commandName =
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/*
18+
* This file contains a copy of unstable semantic convention definitions
19+
* used by this package.
20+
* @see https://github.com/open-telemetry/opentelemetry-js/tree/main/semantic-conventions#unstable-semconv
21+
*/
22+
23+
/**
24+
* The name of the operation being performed.
25+
*
26+
* @note If one of the predefined values applies, but specific system uses a different name it's **RECOMMENDED** to document it in the semantic conventions for specific GenAI system and use system-specific name in the instrumentation. If a different name is not documented, instrumentation libraries **SHOULD** use applicable predefined value.
27+
*
28+
* @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
29+
*/
30+
export const ATTR_GEN_AI_OPERATION_NAME = 'gen_ai.operation.name' as const;
31+
32+
/**
33+
* The maximum number of tokens the model generates for a request.
34+
*
35+
* @example 100
36+
*
37+
* @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
38+
*/
39+
export const ATTR_GEN_AI_REQUEST_MAX_TOKENS =
40+
'gen_ai.request.max_tokens' as const;
41+
42+
/**
43+
* The name of the GenAI model a request is being made to.
44+
*
45+
* @example "gpt-4"
46+
*
47+
* @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
48+
*/
49+
export const ATTR_GEN_AI_REQUEST_MODEL = 'gen_ai.request.model' as const;
50+
51+
/**
52+
* List of sequences that the model will use to stop generating further tokens.
53+
*
54+
* @example ["forest", "lived"]
55+
*
56+
* @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
57+
*/
58+
export const ATTR_GEN_AI_REQUEST_STOP_SEQUENCES =
59+
'gen_ai.request.stop_sequences' as const;
60+
61+
/**
62+
* The temperature setting for the GenAI request.
63+
*
64+
* @example 0.0
65+
*
66+
* @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
67+
*/
68+
export const ATTR_GEN_AI_REQUEST_TEMPERATURE =
69+
'gen_ai.request.temperature' as const;
70+
71+
/**
72+
* The top_p sampling setting for the GenAI request.
73+
*
74+
* @example 1.0
75+
*
76+
* @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
77+
*/
78+
export const ATTR_GEN_AI_REQUEST_TOP_P = 'gen_ai.request.top_p' as const;
79+
80+
/**
81+
* Array of reasons the model stopped generating tokens, corresponding to each generation received.
82+
*
83+
* @example ["stop"]
84+
* @example ["stop", "length"]
85+
*
86+
* @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
87+
*/
88+
export const ATTR_GEN_AI_RESPONSE_FINISH_REASONS =
89+
'gen_ai.response.finish_reasons' as const;
90+
91+
/**
92+
* The Generative AI product as identified by the client or server instrumentation.
93+
*
94+
* @example "openai"
95+
*
96+
* @note The `gen_ai.system` describes a family of GenAI models with specific model identified
97+
* by `gen_ai.request.model` and `gen_ai.response.model` attributes.
98+
*
99+
* The actual GenAI product may differ from the one identified by the client.
100+
* Multiple systems, including Azure OpenAI and Gemini, are accessible by OpenAI client
101+
* libraries. In such cases, the `gen_ai.system` is set to `openai` based on the
102+
* instrumentation's best knowledge, instead of the actual system. The `server.address`
103+
* attribute may help identify the actual system in use for `openai`.
104+
*
105+
* For custom model, a custom friendly name **SHOULD** be used.
106+
* If none of these options apply, the `gen_ai.system` **SHOULD** be set to `_OTHER`.
107+
*
108+
* @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
109+
*/
110+
export const ATTR_GEN_AI_SYSTEM = 'gen_ai.system' as const;
111+
112+
/**
113+
* The number of tokens used in the GenAI input (prompt).
114+
*
115+
* @example 100
116+
*
117+
* @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
118+
*/
119+
export const ATTR_GEN_AI_USAGE_INPUT_TOKENS =
120+
'gen_ai.usage.input_tokens' as const;
121+
122+
/**
123+
* The number of tokens used in the GenAI response (completion).
124+
*
125+
* @example 180
126+
*
127+
* @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
128+
*/
129+
export const ATTR_GEN_AI_USAGE_OUTPUT_TOKENS =
130+
'gen_ai.usage.output_tokens' as const;
131+
132+
/**
133+
* Enum value "chat" for attribute {@link ATTR_GEN_AI_OPERATION_NAME}.
134+
*/
135+
export const GEN_AI_OPERATION_NAME_VALUE_CHAT = 'chat' as const;
136+
137+
/**
138+
* Enum value "aws.bedrock" for attribute {@link ATTR_GEN_AI_SYSTEM}.
139+
*/
140+
export const GEN_AI_SYSTEM_VALUE_AWS_BEDROCK = 'aws.bedrock' as const;

plugins/node/opentelemetry-instrumentation-aws-sdk/src/services/ServicesExtensions.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
NormalizedRequest,
2222
NormalizedResponse,
2323
} from '../types';
24+
import { BedrockRuntimeServiceExtension } from './bedrock-runtime';
2425
import { DynamodbServiceExtension } from './dynamodb';
2526
import { SnsServiceExtension } from './sns';
2627
import { LambdaServiceExtension } from './lambda';
@@ -37,6 +38,7 @@ export class ServicesExtensions implements ServiceExtension {
3738
this.services.set('Lambda', new LambdaServiceExtension());
3839
this.services.set('S3', new S3ServiceExtension());
3940
this.services.set('Kinesis', new KinesisServiceExtension());
41+
this.services.set('BedrockRuntime', new BedrockRuntimeServiceExtension());
4042
}
4143

4244
requestPreSpanHook(
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import { Attributes, DiagLogger, Span, Tracer } from '@opentelemetry/api';
17+
import { RequestMetadata, ServiceExtension } from './ServiceExtension';
18+
import {
19+
ATTR_GEN_AI_SYSTEM,
20+
ATTR_GEN_AI_OPERATION_NAME,
21+
ATTR_GEN_AI_REQUEST_MODEL,
22+
ATTR_GEN_AI_REQUEST_MAX_TOKENS,
23+
ATTR_GEN_AI_REQUEST_TEMPERATURE,
24+
ATTR_GEN_AI_REQUEST_TOP_P,
25+
ATTR_GEN_AI_REQUEST_STOP_SEQUENCES,
26+
ATTR_GEN_AI_USAGE_INPUT_TOKENS,
27+
ATTR_GEN_AI_USAGE_OUTPUT_TOKENS,
28+
ATTR_GEN_AI_RESPONSE_FINISH_REASONS,
29+
GEN_AI_OPERATION_NAME_VALUE_CHAT,
30+
GEN_AI_SYSTEM_VALUE_AWS_BEDROCK,
31+
} from '../semconv';
32+
import {
33+
AwsSdkInstrumentationConfig,
34+
NormalizedRequest,
35+
NormalizedResponse,
36+
} from '../types';
37+
38+
export class BedrockRuntimeServiceExtension implements ServiceExtension {
39+
requestPreSpanHook(
40+
request: NormalizedRequest,
41+
config: AwsSdkInstrumentationConfig,
42+
diag: DiagLogger
43+
): RequestMetadata {
44+
switch (request.commandName) {
45+
case 'Converse':
46+
return this.requestPreSpanHookConverse(request, config, diag);
47+
}
48+
49+
return {
50+
isIncoming: false,
51+
};
52+
}
53+
54+
private requestPreSpanHookConverse(
55+
request: NormalizedRequest,
56+
config: AwsSdkInstrumentationConfig,
57+
diag: DiagLogger
58+
): RequestMetadata {
59+
let spanName = GEN_AI_OPERATION_NAME_VALUE_CHAT;
60+
const spanAttributes: Attributes = {
61+
[ATTR_GEN_AI_SYSTEM]: GEN_AI_SYSTEM_VALUE_AWS_BEDROCK,
62+
[ATTR_GEN_AI_OPERATION_NAME]: GEN_AI_OPERATION_NAME_VALUE_CHAT,
63+
};
64+
65+
const modelId = request.commandInput.modelId;
66+
if (modelId) {
67+
spanAttributes[ATTR_GEN_AI_REQUEST_MODEL] = modelId;
68+
if (spanName) {
69+
spanName += ` ${modelId}`;
70+
}
71+
}
72+
73+
const inferenceConfig = request.commandInput.inferenceConfig;
74+
if (inferenceConfig) {
75+
const { maxTokens, temperature, topP, stopSequences } = inferenceConfig;
76+
if (maxTokens !== undefined) {
77+
spanAttributes[ATTR_GEN_AI_REQUEST_MAX_TOKENS] = maxTokens;
78+
}
79+
if (temperature !== undefined) {
80+
spanAttributes[ATTR_GEN_AI_REQUEST_TEMPERATURE] = temperature;
81+
}
82+
if (topP !== undefined) {
83+
spanAttributes[ATTR_GEN_AI_REQUEST_TOP_P] = topP;
84+
}
85+
if (stopSequences !== undefined) {
86+
spanAttributes[ATTR_GEN_AI_REQUEST_STOP_SEQUENCES] = stopSequences;
87+
}
88+
}
89+
90+
return {
91+
spanName,
92+
isIncoming: false,
93+
spanAttributes,
94+
};
95+
}
96+
97+
responseHook(
98+
response: NormalizedResponse,
99+
span: Span,
100+
tracer: Tracer,
101+
config: AwsSdkInstrumentationConfig
102+
) {
103+
if (!span.isRecording()) {
104+
return;
105+
}
106+
107+
switch (response.request.commandName) {
108+
case 'Converse':
109+
return this.responseHookConverse(response, span, tracer, config);
110+
}
111+
}
112+
113+
private responseHookConverse(
114+
response: NormalizedResponse,
115+
span: Span,
116+
tracer: Tracer,
117+
config: AwsSdkInstrumentationConfig
118+
) {
119+
const { stopReason, usage } = response.data;
120+
if (usage) {
121+
const { inputTokens, outputTokens } = usage;
122+
if (inputTokens !== undefined) {
123+
span.setAttribute(ATTR_GEN_AI_USAGE_INPUT_TOKENS, inputTokens);
124+
}
125+
if (outputTokens !== undefined) {
126+
span.setAttribute(ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, outputTokens);
127+
}
128+
}
129+
130+
if (stopReason !== undefined) {
131+
span.setAttribute(ATTR_GEN_AI_RESPONSE_FINISH_REASONS, [stopReason]);
132+
}
133+
}
134+
}

0 commit comments

Comments
 (0)