@traceloop/instrumentation-bedrock 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,11 +1,42 @@
1
1
  import { __awaiter, __asyncValues } from 'tslib';
2
2
  import { trace, context, SpanStatusCode, SpanKind } from '@opentelemetry/api';
3
3
  import { InstrumentationBase, InstrumentationNodeModuleDefinition, safeExecuteInTheMiddle } from '@opentelemetry/instrumentation';
4
- import { LLMRequestTypeValues, SpanAttributes, CONTEXT_KEY_ALLOW_TRACE_CONTENT } from '@traceloop/ai-semantic-conventions';
5
- import { ATTR_GEN_AI_RESPONSE_MODEL, ATTR_GEN_AI_REQUEST_MODEL, ATTR_GEN_AI_SYSTEM, ATTR_GEN_AI_USAGE_PROMPT_TOKENS, ATTR_GEN_AI_USAGE_COMPLETION_TOKENS, ATTR_GEN_AI_COMPLETION, ATTR_GEN_AI_REQUEST_MAX_TOKENS, ATTR_GEN_AI_REQUEST_TEMPERATURE, ATTR_GEN_AI_REQUEST_TOP_P, ATTR_GEN_AI_PROMPT } from '@opentelemetry/semantic-conventions/incubating';
4
+ import { FinishReasons, SpanAttributes, CONTEXT_KEY_ALLOW_TRACE_CONTENT } from '@traceloop/ai-semantic-conventions';
5
+ import { GEN_AI_PROVIDER_NAME_VALUE_AWS_BEDROCK, ATTR_GEN_AI_REQUEST_MODEL, ATTR_GEN_AI_PROVIDER_NAME, ATTR_GEN_AI_OPERATION_NAME, ATTR_GEN_AI_RESPONSE_MODEL, ATTR_GEN_AI_USAGE_INPUT_TOKENS, ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, ATTR_GEN_AI_RESPONSE_FINISH_REASONS, ATTR_GEN_AI_OUTPUT_MESSAGES, ATTR_GEN_AI_REQUEST_MAX_TOKENS, ATTR_GEN_AI_REQUEST_TEMPERATURE, ATTR_GEN_AI_REQUEST_TOP_P, ATTR_GEN_AI_INPUT_MESSAGES, ATTR_GEN_AI_REQUEST_TOP_K, ATTR_GEN_AI_REQUEST_FREQUENCY_PENALTY, ATTR_GEN_AI_REQUEST_PRESENCE_PENALTY, GEN_AI_OPERATION_NAME_VALUE_TEXT_COMPLETION, GEN_AI_OPERATION_NAME_VALUE_CHAT, ATTR_GEN_AI_SYSTEM_INSTRUCTIONS } from '@opentelemetry/semantic-conventions/incubating';
6
+ import { formatOutputMessage, mapBedrockContentBlock, formatInputMessagesFromPrompt, formatInputMessages, formatSystemInstructions } from '@traceloop/instrumentation-utils';
6
7
 
7
- var version = "0.23.0";
8
+ var BedrockVendor;
9
+ (function (BedrockVendor) {
10
+ BedrockVendor["AI21"] = "ai21";
11
+ BedrockVendor["AMAZON"] = "amazon";
12
+ BedrockVendor["ANTHROPIC"] = "anthropic";
13
+ BedrockVendor["COHERE"] = "cohere";
14
+ BedrockVendor["META"] = "meta";
15
+ })(BedrockVendor || (BedrockVendor = {}));
8
16
 
17
+ var version = "0.24.0";
18
+
19
+ const bedrockFinishReasonMap = {
20
+ // AI21
21
+ endoftext: FinishReasons.STOP,
22
+ // Amazon Titan / Nova
23
+ FINISH: FinishReasons.STOP,
24
+ LENGTH: FinishReasons.LENGTH,
25
+ CONTENT_FILTERED: FinishReasons.CONTENT_FILTER,
26
+ // Anthropic
27
+ end_turn: FinishReasons.STOP,
28
+ max_tokens: FinishReasons.LENGTH,
29
+ stop_sequence: FinishReasons.STOP,
30
+ tool_use: FinishReasons.TOOL_CALL,
31
+ // Cohere
32
+ COMPLETE: FinishReasons.STOP,
33
+ MAX_TOKENS: FinishReasons.LENGTH,
34
+ ERROR: FinishReasons.ERROR,
35
+ ERROR_TOXIC: FinishReasons.CONTENT_FILTER,
36
+ // Meta
37
+ stop: FinishReasons.STOP,
38
+ length: FinishReasons.LENGTH,
39
+ };
9
40
  class BedrockInstrumentation extends InstrumentationBase {
10
41
  constructor(config = {}) {
11
42
  super("@traceloop/instrumentation-bedrock", version, config);
@@ -36,9 +67,11 @@ class BedrockInstrumentation extends InstrumentationBase {
36
67
  // eslint-disable-next-line
37
68
  return (original) => {
38
69
  return function method(...args) {
70
+ var _a, _b, _c;
39
71
  const span = plugin._startSpan({
40
72
  params: args[0],
41
73
  });
74
+ const modelId = (_c = (_b = (_a = args[0]) === null || _a === void 0 ? void 0 : _a.input) === null || _b === void 0 ? void 0 : _b.modelId) !== null && _c !== void 0 ? _c : "";
42
75
  const execContext = trace.setSpan(context.active(), span);
43
76
  const execPromise = safeExecuteInTheMiddle(() => {
44
77
  return context.with(execContext, () => {
@@ -49,17 +82,18 @@ class BedrockInstrumentation extends InstrumentationBase {
49
82
  plugin._diag.error(`Error in bedrock instrumentation`, e);
50
83
  }
51
84
  });
52
- const wrappedPromise = plugin._wrapPromise(span, execPromise);
85
+ const wrappedPromise = plugin._wrapPromise(span, execPromise, modelId);
53
86
  return context.bind(execContext, wrappedPromise);
54
87
  };
55
88
  };
56
89
  }
57
- _wrapPromise(span, promise) {
90
+ _wrapPromise(span, promise, modelId) {
58
91
  return promise
59
92
  .then((result) => __awaiter(this, void 0, void 0, function* () {
60
93
  yield this._endSpan({
61
94
  span,
62
95
  result: result,
96
+ modelId,
63
97
  });
64
98
  return new Promise((resolve) => resolve(result));
65
99
  }))
@@ -78,17 +112,21 @@ class BedrockInstrumentation extends InstrumentationBase {
78
112
  _startSpan({ params, }) {
79
113
  var _a, _b;
80
114
  let attributes = {};
115
+ let spanName = "bedrock.completion";
81
116
  try {
82
117
  const input = params.input;
83
118
  const { modelVendor, model } = this._extractVendorAndModel(input.modelId || "");
84
119
  attributes = {
85
- [ATTR_GEN_AI_SYSTEM]: "AWS",
120
+ [ATTR_GEN_AI_PROVIDER_NAME]: GEN_AI_PROVIDER_NAME_VALUE_AWS_BEDROCK,
86
121
  [ATTR_GEN_AI_REQUEST_MODEL]: model,
87
- [ATTR_GEN_AI_RESPONSE_MODEL]: input.modelId,
88
- [SpanAttributes.LLM_REQUEST_TYPE]: LLMRequestTypeValues.COMPLETION,
89
122
  };
90
123
  if (typeof input.body === "string") {
91
124
  const requestBody = JSON.parse(input.body);
125
+ const operationType = this._getOperationType(modelVendor, requestBody);
126
+ spanName = `${operationType} ${model}`;
127
+ // Set operation name before _setRequestAttributes so it is always
128
+ // present even if _setRequestAttributes throws for an unexpected body.
129
+ attributes = Object.assign(Object.assign({}, attributes), { [ATTR_GEN_AI_OPERATION_NAME]: operationType });
92
130
  attributes = Object.assign(Object.assign({}, attributes), this._setRequestAttributes(modelVendor, requestBody));
93
131
  }
94
132
  }
@@ -96,50 +134,57 @@ class BedrockInstrumentation extends InstrumentationBase {
96
134
  this._diag.debug(e);
97
135
  (_b = (_a = this._config).exceptionLogger) === null || _b === void 0 ? void 0 : _b.call(_a, e);
98
136
  }
99
- return this.tracer.startSpan(`bedrock.completion`, {
137
+ return this.tracer.startSpan(spanName, {
100
138
  kind: SpanKind.CLIENT,
101
139
  attributes,
102
140
  });
103
141
  }
104
142
  _endSpan(_a) {
105
- return __awaiter(this, arguments, void 0, function* ({ span, result, }) {
143
+ return __awaiter(this, arguments, void 0, function* ({ span, result, modelId, }) {
106
144
  var _b, e_1, _c, _d;
107
- var _e, _f, _g;
145
+ var _e, _f, _g, _h;
108
146
  try {
109
147
  if ("body" in result) {
110
148
  const attributes = "attributes" in span
111
149
  ? span["attributes"]
112
150
  : {};
113
- if (ATTR_GEN_AI_SYSTEM in attributes) {
114
- const modelId = attributes[ATTR_GEN_AI_RESPONSE_MODEL];
151
+ if (ATTR_GEN_AI_PROVIDER_NAME in attributes) {
115
152
  const { modelVendor, model } = this._extractVendorAndModel(modelId);
116
153
  span.setAttribute(ATTR_GEN_AI_RESPONSE_MODEL, model);
117
154
  if (!(result.body instanceof Object.getPrototypeOf(Uint8Array))) {
118
155
  const rawRes = result.body;
119
156
  let streamedContent = "";
120
157
  try {
121
- for (var _h = true, rawRes_1 = __asyncValues(rawRes), rawRes_1_1; rawRes_1_1 = yield rawRes_1.next(), _b = rawRes_1_1.done, !_b; _h = true) {
158
+ for (var _j = true, rawRes_1 = __asyncValues(rawRes), rawRes_1_1; rawRes_1_1 = yield rawRes_1.next(), _b = rawRes_1_1.done, !_b; _j = true) {
122
159
  _d = rawRes_1_1.value;
123
- _h = false;
160
+ _j = false;
124
161
  const value = _d;
125
162
  // Convert it to a JSON String
126
163
  const jsonString = new TextDecoder().decode((_e = value.chunk) === null || _e === void 0 ? void 0 : _e.bytes);
127
164
  // Parse the JSON string
128
165
  const parsedResponse = JSON.parse(jsonString);
129
166
  if ("amazon-bedrock-invocationMetrics" in parsedResponse) {
130
- span.setAttribute(ATTR_GEN_AI_USAGE_PROMPT_TOKENS, parsedResponse["amazon-bedrock-invocationMetrics"]["inputTokenCount"]);
131
- span.setAttribute(ATTR_GEN_AI_USAGE_COMPLETION_TOKENS, parsedResponse["amazon-bedrock-invocationMetrics"]["outputTokenCount"]);
132
- span.setAttribute(SpanAttributes.LLM_USAGE_TOTAL_TOKENS, parsedResponse["amazon-bedrock-invocationMetrics"]["inputTokenCount"] +
133
- parsedResponse["amazon-bedrock-invocationMetrics"]["outputTokenCount"]);
167
+ span.setAttribute(ATTR_GEN_AI_USAGE_INPUT_TOKENS, parsedResponse["amazon-bedrock-invocationMetrics"]["inputTokenCount"]);
168
+ span.setAttribute(ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, parsedResponse["amazon-bedrock-invocationMetrics"]["outputTokenCount"]);
169
+ const totalTokens = parsedResponse["amazon-bedrock-invocationMetrics"]["inputTokenCount"] +
170
+ parsedResponse["amazon-bedrock-invocationMetrics"]["outputTokenCount"];
171
+ span.setAttribute(SpanAttributes.GEN_AI_USAGE_TOTAL_TOKENS, totalTokens);
134
172
  }
135
- let responseAttributes = this._setResponseAttributes(modelVendor, parsedResponse, true);
173
+ this._handleNovaStreamingMetadata(span, parsedResponse);
174
+ const responseAttributes = this._setResponseAttributes(modelVendor, parsedResponse, true);
136
175
  // ! NOTE: This make sure the content always have all streamed chunks
137
176
  if (this._shouldSendPrompts()) {
138
- // Update local value with attribute value that was set by _setResponseAttributes
139
- streamedContent +=
140
- responseAttributes[`${ATTR_GEN_AI_COMPLETION}.0.content`];
141
- // re-assign the new value to responseAttributes
142
- responseAttributes = Object.assign(Object.assign({}, responseAttributes), { [`${ATTR_GEN_AI_COMPLETION}.0.content`]: streamedContent });
177
+ const chunkContent = this._getStreamChunkContent(modelVendor, parsedResponse);
178
+ if (chunkContent !== undefined) {
179
+ streamedContent += chunkContent;
180
+ }
181
+ // When finish reason is available (final chunk), set OTel 1.40 output message
182
+ if (responseAttributes[ATTR_GEN_AI_RESPONSE_FINISH_REASONS]) {
183
+ const finishReasons = responseAttributes[ATTR_GEN_AI_RESPONSE_FINISH_REASONS];
184
+ responseAttributes[ATTR_GEN_AI_OUTPUT_MESSAGES] =
185
+ formatOutputMessage(streamedContent, (_f = finishReasons[0]) !== null && _f !== void 0 ? _f : null, {}, // already mapped by _setResponseAttributes
186
+ attributes[ATTR_GEN_AI_OPERATION_NAME], mapBedrockContentBlock);
187
+ }
143
188
  }
144
189
  span.setAttributes(responseAttributes);
145
190
  }
@@ -147,7 +192,7 @@ class BedrockInstrumentation extends InstrumentationBase {
147
192
  catch (e_1_1) { e_1 = { error: e_1_1 }; }
148
193
  finally {
149
194
  try {
150
- if (!_h && !_b && (_c = rawRes_1.return)) yield _c.call(rawRes_1);
195
+ if (!_j && !_b && (_c = rawRes_1.return)) yield _c.call(rawRes_1);
151
196
  }
152
197
  finally { if (e_1) throw e_1.error; }
153
198
  }
@@ -165,34 +210,51 @@ class BedrockInstrumentation extends InstrumentationBase {
165
210
  }
166
211
  catch (e) {
167
212
  this._diag.debug(e);
168
- (_g = (_f = this._config).exceptionLogger) === null || _g === void 0 ? void 0 : _g.call(_f, e);
213
+ (_h = (_g = this._config).exceptionLogger) === null || _h === void 0 ? void 0 : _h.call(_g, e);
169
214
  }
170
215
  span.setStatus({ code: SpanStatusCode.OK });
171
216
  span.end();
172
217
  });
173
218
  }
174
219
  _setRequestAttributes(vendor, requestBody) {
220
+ var _a, _b, _c, _d, _e, _f, _g, _h;
175
221
  switch (vendor) {
176
- case "ai21": {
177
- return Object.assign({ [ATTR_GEN_AI_REQUEST_TOP_P]: requestBody["topP"], [ATTR_GEN_AI_REQUEST_TEMPERATURE]: requestBody["temperature"], [ATTR_GEN_AI_REQUEST_MAX_TOKENS]: requestBody["maxTokens"], [SpanAttributes.LLM_PRESENCE_PENALTY]: requestBody["presencePenalty"]["scale"], [SpanAttributes.LLM_FREQUENCY_PENALTY]: requestBody["frequencyPenalty"]["scale"] }, (this._shouldSendPrompts()
222
+ case BedrockVendor.AI21: {
223
+ // Jamba format: messages array + max_tokens + top_p
224
+ if (requestBody["messages"]) {
225
+ return Object.assign({ [ATTR_GEN_AI_REQUEST_TOP_P]: requestBody["top_p"], [ATTR_GEN_AI_REQUEST_TEMPERATURE]: requestBody["temperature"], [ATTR_GEN_AI_REQUEST_MAX_TOKENS]: requestBody["max_tokens"] }, (this._shouldSendPrompts()
226
+ ? {
227
+ [ATTR_GEN_AI_INPUT_MESSAGES]: formatInputMessages(requestBody["messages"], mapBedrockContentBlock),
228
+ }
229
+ : {}));
230
+ }
231
+ // Legacy Jurassic format: prompt + topP + maxTokens
232
+ return Object.assign({ [ATTR_GEN_AI_REQUEST_TOP_P]: requestBody["topP"], [ATTR_GEN_AI_REQUEST_TEMPERATURE]: requestBody["temperature"], [ATTR_GEN_AI_REQUEST_MAX_TOKENS]: requestBody["maxTokens"], [ATTR_GEN_AI_REQUEST_PRESENCE_PENALTY]: (_a = requestBody["presencePenalty"]) === null || _a === void 0 ? void 0 : _a["scale"], [ATTR_GEN_AI_REQUEST_FREQUENCY_PENALTY]: (_b = requestBody["frequencyPenalty"]) === null || _b === void 0 ? void 0 : _b["scale"] }, (this._shouldSendPrompts()
178
233
  ? {
179
- [`${ATTR_GEN_AI_PROMPT}.0.role`]: "user",
180
- [`${ATTR_GEN_AI_PROMPT}.0.content`]: requestBody["prompt"],
234
+ [ATTR_GEN_AI_INPUT_MESSAGES]: formatInputMessagesFromPrompt(requestBody["prompt"]),
181
235
  }
182
236
  : {}));
183
237
  }
184
- case "amazon": {
185
- return Object.assign({ [ATTR_GEN_AI_REQUEST_TOP_P]: requestBody["textGenerationConfig"]["topP"], [ATTR_GEN_AI_REQUEST_TEMPERATURE]: requestBody["textGenerationConfig"]["temperature"], [ATTR_GEN_AI_REQUEST_MAX_TOKENS]: requestBody["textGenerationConfig"]["maxTokenCount"] }, (this._shouldSendPrompts()
238
+ case BedrockVendor.AMAZON: {
239
+ // Amazon Nova format: messages array + inferenceConfig
240
+ if (requestBody["messages"]) {
241
+ return Object.assign({ [ATTR_GEN_AI_REQUEST_TOP_P]: (_c = requestBody["inferenceConfig"]) === null || _c === void 0 ? void 0 : _c["topP"], [ATTR_GEN_AI_REQUEST_TEMPERATURE]: (_d = requestBody["inferenceConfig"]) === null || _d === void 0 ? void 0 : _d["temperature"], [ATTR_GEN_AI_REQUEST_MAX_TOKENS]: (_e = requestBody["inferenceConfig"]) === null || _e === void 0 ? void 0 : _e["maxTokens"] }, (this._shouldSendPrompts()
242
+ ? {
243
+ [ATTR_GEN_AI_INPUT_MESSAGES]: formatInputMessages(requestBody["messages"], mapBedrockContentBlock),
244
+ }
245
+ : {}));
246
+ }
247
+ // Amazon Titan format: inputText + textGenerationConfig
248
+ return Object.assign({ [ATTR_GEN_AI_REQUEST_TOP_P]: (_f = requestBody["textGenerationConfig"]) === null || _f === void 0 ? void 0 : _f["topP"], [ATTR_GEN_AI_REQUEST_TEMPERATURE]: (_g = requestBody["textGenerationConfig"]) === null || _g === void 0 ? void 0 : _g["temperature"], [ATTR_GEN_AI_REQUEST_MAX_TOKENS]: (_h = requestBody["textGenerationConfig"]) === null || _h === void 0 ? void 0 : _h["maxTokenCount"] }, (this._shouldSendPrompts()
186
249
  ? {
187
- [`${ATTR_GEN_AI_PROMPT}.0.role`]: "user",
188
- [`${ATTR_GEN_AI_PROMPT}.0.content`]: requestBody["inputText"],
250
+ [ATTR_GEN_AI_INPUT_MESSAGES]: formatInputMessagesFromPrompt(requestBody["inputText"]),
189
251
  }
190
252
  : {}));
191
253
  }
192
- case "anthropic": {
254
+ case BedrockVendor.ANTHROPIC: {
193
255
  const baseAttributes = {
194
256
  [ATTR_GEN_AI_REQUEST_TOP_P]: requestBody["top_p"],
195
- [SpanAttributes.LLM_TOP_K]: requestBody["top_k"],
257
+ [ATTR_GEN_AI_REQUEST_TOP_K]: requestBody["top_k"],
196
258
  [ATTR_GEN_AI_REQUEST_TEMPERATURE]: requestBody["temperature"],
197
259
  [ATTR_GEN_AI_REQUEST_MAX_TOKENS]: requestBody["max_tokens_to_sample"] || requestBody["max_tokens"],
198
260
  };
@@ -201,39 +263,35 @@ class BedrockInstrumentation extends InstrumentationBase {
201
263
  }
202
264
  // Handle new messages API format (used by langchain)
203
265
  if (requestBody["messages"]) {
204
- const promptAttributes = {};
205
- requestBody["messages"].forEach((message, index) => {
206
- promptAttributes[`${ATTR_GEN_AI_PROMPT}.${index}.role`] =
207
- message.role;
208
- promptAttributes[`${ATTR_GEN_AI_PROMPT}.${index}.content`] =
209
- typeof message.content === "string"
210
- ? message.content
211
- : JSON.stringify(message.content);
212
- });
266
+ const promptAttributes = {
267
+ [ATTR_GEN_AI_INPUT_MESSAGES]: formatInputMessages(requestBody["messages"], mapBedrockContentBlock),
268
+ };
269
+ if (requestBody["system"] !== undefined) {
270
+ promptAttributes[ATTR_GEN_AI_SYSTEM_INSTRUCTIONS] =
271
+ formatSystemInstructions(requestBody["system"]);
272
+ }
213
273
  return Object.assign(Object.assign({}, baseAttributes), promptAttributes);
214
274
  }
215
275
  // Handle legacy prompt format
216
276
  if (requestBody["prompt"]) {
217
- return Object.assign(Object.assign({}, baseAttributes), { [`${ATTR_GEN_AI_PROMPT}.0.role`]: "user", [`${ATTR_GEN_AI_PROMPT}.0.content`]: requestBody["prompt"]
218
- // The format is removing when we are setting span attribute
277
+ return Object.assign(Object.assign({}, baseAttributes), { [ATTR_GEN_AI_INPUT_MESSAGES]: formatInputMessagesFromPrompt(requestBody["prompt"]
219
278
  .replace("\n\nHuman:", "")
220
- .replace("\n\nAssistant:", "") });
279
+ .replace("\n\nAssistant:", "")) });
221
280
  }
222
281
  return baseAttributes;
223
282
  }
224
- case "cohere": {
225
- return Object.assign({ [ATTR_GEN_AI_REQUEST_TOP_P]: requestBody["p"], [SpanAttributes.LLM_TOP_K]: requestBody["k"], [ATTR_GEN_AI_REQUEST_TEMPERATURE]: requestBody["temperature"], [ATTR_GEN_AI_REQUEST_MAX_TOKENS]: requestBody["max_tokens"] }, (this._shouldSendPrompts()
226
- ? {
227
- [`${ATTR_GEN_AI_PROMPT}.0.role`]: "user",
228
- [`${ATTR_GEN_AI_PROMPT}.0.content`]: requestBody["message"] || requestBody["prompt"],
229
- }
230
- : {}));
283
+ case BedrockVendor.COHERE: {
284
+ return {
285
+ [ATTR_GEN_AI_REQUEST_TOP_P]: requestBody["p"],
286
+ [ATTR_GEN_AI_REQUEST_TOP_K]: requestBody["k"],
287
+ [ATTR_GEN_AI_REQUEST_TEMPERATURE]: requestBody["temperature"],
288
+ [ATTR_GEN_AI_REQUEST_MAX_TOKENS]: requestBody["max_tokens"],
289
+ };
231
290
  }
232
- case "meta": {
291
+ case BedrockVendor.META: {
233
292
  return Object.assign({ [ATTR_GEN_AI_REQUEST_TOP_P]: requestBody["top_p"], [ATTR_GEN_AI_REQUEST_TEMPERATURE]: requestBody["temperature"], [ATTR_GEN_AI_REQUEST_MAX_TOKENS]: requestBody["max_gen_len"] }, (this._shouldSendPrompts()
234
293
  ? {
235
- [`${ATTR_GEN_AI_PROMPT}.0.role`]: "user",
236
- [`${ATTR_GEN_AI_PROMPT}.0.content`]: requestBody["prompt"],
294
+ [ATTR_GEN_AI_INPUT_MESSAGES]: formatInputMessagesFromPrompt(requestBody["prompt"]),
237
295
  }
238
296
  : {}));
239
297
  }
@@ -242,71 +300,182 @@ class BedrockInstrumentation extends InstrumentationBase {
242
300
  }
243
301
  }
244
302
  _setResponseAttributes(vendor, response, isStream = false) {
245
- var _a, _b, _c, _d;
303
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8;
246
304
  switch (vendor) {
247
- case "ai21": {
248
- return Object.assign({ [`${ATTR_GEN_AI_COMPLETION}.0.finish_reason`]: response["completions"][0]["finishReason"]["reason"], [`${ATTR_GEN_AI_COMPLETION}.0.role`]: "assistant" }, (this._shouldSendPrompts()
305
+ case BedrockVendor.AI21: {
306
+ // Jamba format: choices[0].message + choices[0].finish_reason
307
+ if (response["choices"]) {
308
+ const usage = response["usage"];
309
+ const finishReason = (_a = response["choices"][0]) === null || _a === void 0 ? void 0 : _a["finish_reason"];
310
+ const content = isStream
311
+ ? (_c = (_b = response["choices"][0]) === null || _b === void 0 ? void 0 : _b["delta"]) === null || _c === void 0 ? void 0 : _c["content"]
312
+ : (_e = (_d = response["choices"][0]) === null || _d === void 0 ? void 0 : _d["message"]) === null || _e === void 0 ? void 0 : _e["content"];
313
+ return Object.assign(Object.assign(Object.assign({}, (finishReason != null
314
+ ? {
315
+ [ATTR_GEN_AI_RESPONSE_FINISH_REASONS]: [
316
+ (_f = bedrockFinishReasonMap[finishReason]) !== null && _f !== void 0 ? _f : finishReason,
317
+ ],
318
+ }
319
+ : {})), (usage
320
+ ? {
321
+ [ATTR_GEN_AI_USAGE_INPUT_TOKENS]: usage["prompt_tokens"],
322
+ [ATTR_GEN_AI_USAGE_OUTPUT_TOKENS]: usage["completion_tokens"],
323
+ [SpanAttributes.GEN_AI_USAGE_TOTAL_TOKENS]: usage["total_tokens"],
324
+ }
325
+ : {})), (this._shouldSendPrompts()
326
+ ? {
327
+ [ATTR_GEN_AI_OUTPUT_MESSAGES]: formatOutputMessage(content !== null && content !== void 0 ? content : "", finishReason, bedrockFinishReasonMap, GEN_AI_OPERATION_NAME_VALUE_TEXT_COMPLETION, mapBedrockContentBlock),
328
+ }
329
+ : {}));
330
+ }
331
+ // Legacy Jurassic format: completions[0].data.text
332
+ const jurassicFinishReason = response["completions"][0]["finishReason"]["reason"];
333
+ const jurassicContent = response["completions"][0]["data"]["text"];
334
+ return Object.assign({ [ATTR_GEN_AI_RESPONSE_FINISH_REASONS]: [
335
+ (_g = bedrockFinishReasonMap[jurassicFinishReason]) !== null && _g !== void 0 ? _g : jurassicFinishReason,
336
+ ] }, (this._shouldSendPrompts()
249
337
  ? {
250
- [`${ATTR_GEN_AI_COMPLETION}.0.content`]: response["completions"][0]["data"]["text"],
338
+ [ATTR_GEN_AI_OUTPUT_MESSAGES]: formatOutputMessage(jurassicContent, jurassicFinishReason, bedrockFinishReasonMap, GEN_AI_OPERATION_NAME_VALUE_TEXT_COMPLETION, mapBedrockContentBlock),
251
339
  }
252
340
  : {}));
253
341
  }
254
- case "amazon": {
255
- return Object.assign({ [`${ATTR_GEN_AI_COMPLETION}.0.finish_reason`]: isStream
256
- ? response["completionReason"]
257
- : response["results"][0]["completionReason"], [`${ATTR_GEN_AI_COMPLETION}.0.role`]: "assistant", [ATTR_GEN_AI_USAGE_PROMPT_TOKENS]: response["inputTextTokenCount"], [ATTR_GEN_AI_USAGE_COMPLETION_TOKENS]: isStream
258
- ? response["totalOutputTextTokenCount"]
259
- : response["results"][0]["tokenCount"], [SpanAttributes.LLM_USAGE_TOTAL_TOKENS]: isStream
260
- ? response["inputTextTokenCount"] +
261
- response["totalOutputTextTokenCount"]
262
- : response["inputTextTokenCount"] +
263
- response["results"][0]["tokenCount"] }, (this._shouldSendPrompts()
342
+ case BedrockVendor.AMAZON: {
343
+ if (isStream) {
344
+ // Amazon Nova format: contentBlockDelta has text, messageStop has stopReason
345
+ const novaFinishReason = (_h = response["messageStop"]) === null || _h === void 0 ? void 0 : _h["stopReason"];
346
+ // Amazon Titan format: outputText, completionReason
347
+ const titanFinishReason = response["completionReason"];
348
+ const finishReason = novaFinishReason !== null && novaFinishReason !== void 0 ? novaFinishReason : titanFinishReason;
349
+ return Object.assign(Object.assign({}, (finishReason != null
350
+ ? {
351
+ [ATTR_GEN_AI_RESPONSE_FINISH_REASONS]: [
352
+ (_j = bedrockFinishReasonMap[finishReason]) !== null && _j !== void 0 ? _j : finishReason,
353
+ ],
354
+ }
355
+ : {})), (response["inputTextTokenCount"] != null
356
+ ? {
357
+ [ATTR_GEN_AI_USAGE_INPUT_TOKENS]: response["inputTextTokenCount"],
358
+ [ATTR_GEN_AI_USAGE_OUTPUT_TOKENS]: response["totalOutputTextTokenCount"],
359
+ [SpanAttributes.GEN_AI_USAGE_TOTAL_TOKENS]: response["inputTextTokenCount"] +
360
+ response["totalOutputTextTokenCount"],
361
+ }
362
+ : {}));
363
+ }
364
+ // Amazon Titan token fields
365
+ const titanInputTokens = response["inputTextTokenCount"];
366
+ const titanOutputTokens = (_l = (_k = response["results"]) === null || _k === void 0 ? void 0 : _k[0]) === null || _l === void 0 ? void 0 : _l["tokenCount"];
367
+ // Amazon Nova token fields
368
+ const novaUsage = response["usage"];
369
+ const amazonFinishReason = (_p = (_o = (_m = response["results"]) === null || _m === void 0 ? void 0 : _m[0]) === null || _o === void 0 ? void 0 : _o["completionReason"]) !== null && _p !== void 0 ? _p : response["stopReason"];
370
+ const novaRawContent = (_r = (_q = response["output"]) === null || _q === void 0 ? void 0 : _q["message"]) === null || _r === void 0 ? void 0 : _r["content"];
371
+ const titanContent = (_t = (_s = response["results"]) === null || _s === void 0 ? void 0 : _s[0]) === null || _t === void 0 ? void 0 : _t["outputText"];
372
+ const outputContent = (_u = novaRawContent !== null && novaRawContent !== void 0 ? novaRawContent : titanContent) !== null && _u !== void 0 ? _u : "";
373
+ const operationType = novaRawContent
374
+ ? GEN_AI_OPERATION_NAME_VALUE_CHAT
375
+ : GEN_AI_OPERATION_NAME_VALUE_TEXT_COMPLETION;
376
+ return Object.assign(Object.assign(Object.assign({}, (amazonFinishReason != null
264
377
  ? {
265
- [`${ATTR_GEN_AI_COMPLETION}.0.content`]: isStream
266
- ? response["outputText"]
267
- : response["results"][0]["outputText"],
378
+ [ATTR_GEN_AI_RESPONSE_FINISH_REASONS]: [
379
+ (_v = bedrockFinishReasonMap[amazonFinishReason]) !== null && _v !== void 0 ? _v : amazonFinishReason,
380
+ ],
381
+ }
382
+ : {})), (titanInputTokens != null
383
+ ? {
384
+ [ATTR_GEN_AI_USAGE_INPUT_TOKENS]: titanInputTokens,
385
+ [ATTR_GEN_AI_USAGE_OUTPUT_TOKENS]: titanOutputTokens,
386
+ [SpanAttributes.GEN_AI_USAGE_TOTAL_TOKENS]: titanInputTokens + titanOutputTokens,
387
+ }
388
+ : novaUsage != null
389
+ ? {
390
+ [ATTR_GEN_AI_USAGE_INPUT_TOKENS]: novaUsage["inputTokens"],
391
+ [ATTR_GEN_AI_USAGE_OUTPUT_TOKENS]: novaUsage["outputTokens"],
392
+ [SpanAttributes.GEN_AI_USAGE_TOTAL_TOKENS]: novaUsage["totalTokens"],
393
+ }
394
+ : {})), (this._shouldSendPrompts()
395
+ ? {
396
+ [ATTR_GEN_AI_OUTPUT_MESSAGES]: formatOutputMessage(outputContent, amazonFinishReason, bedrockFinishReasonMap, operationType, mapBedrockContentBlock),
268
397
  }
269
398
  : {}));
270
399
  }
271
- case "anthropic": {
272
- const baseAttributes = {
273
- [`${ATTR_GEN_AI_COMPLETION}.0.finish_reason`]: response["stop_reason"],
274
- [`${ATTR_GEN_AI_COMPLETION}.0.role`]: "assistant",
275
- };
400
+ case BedrockVendor.ANTHROPIC: {
401
+ if (isStream) {
402
+ // New messages API streaming: content_block_delta has delta.text,
403
+ // message_delta has delta.stop_reason
404
+ const stopReason = (_x = (_w = response["delta"]) === null || _w === void 0 ? void 0 : _w["stop_reason"]) !== null && _x !== void 0 ? _x : response["stop_reason"];
405
+ const finishReason = stopReason !== null && stopReason !== void 0 ? stopReason : undefined;
406
+ return Object.assign({}, (finishReason != null
407
+ ? {
408
+ [ATTR_GEN_AI_RESPONSE_FINISH_REASONS]: [
409
+ (_y = bedrockFinishReasonMap[finishReason]) !== null && _y !== void 0 ? _y : finishReason,
410
+ ],
411
+ }
412
+ : {}));
413
+ }
414
+ const stopReason = response["stop_reason"];
415
+ const usage = response["usage"];
416
+ const baseAttributes = Object.assign(Object.assign({}, (stopReason != null
417
+ ? {
418
+ [ATTR_GEN_AI_RESPONSE_FINISH_REASONS]: [
419
+ (_z = bedrockFinishReasonMap[stopReason]) !== null && _z !== void 0 ? _z : stopReason,
420
+ ],
421
+ }
422
+ : {})), (usage
423
+ ? {
424
+ [ATTR_GEN_AI_USAGE_INPUT_TOKENS]: usage["input_tokens"],
425
+ [ATTR_GEN_AI_USAGE_OUTPUT_TOKENS]: usage["output_tokens"],
426
+ [SpanAttributes.GEN_AI_USAGE_TOTAL_TOKENS]: (usage["input_tokens"] || 0) + (usage["output_tokens"] || 0),
427
+ }
428
+ : {}));
276
429
  if (!this._shouldSendPrompts()) {
277
430
  return baseAttributes;
278
431
  }
279
432
  // Handle new messages API format response
280
433
  if (response["content"]) {
281
434
  const content = Array.isArray(response["content"])
282
- ? response["content"].map((c) => c.text || c).join("")
435
+ ? response["content"]
283
436
  : response["content"];
284
- return Object.assign(Object.assign({}, baseAttributes), { [`${ATTR_GEN_AI_COMPLETION}.0.content`]: content });
437
+ return Object.assign(Object.assign({}, baseAttributes), { [ATTR_GEN_AI_OUTPUT_MESSAGES]: formatOutputMessage(content, stopReason, bedrockFinishReasonMap, GEN_AI_OPERATION_NAME_VALUE_CHAT, mapBedrockContentBlock) });
285
438
  }
286
439
  // Handle legacy completion format
287
440
  if (response["completion"]) {
288
- return Object.assign(Object.assign({}, baseAttributes), { [`${ATTR_GEN_AI_COMPLETION}.0.content`]: response["completion"] });
441
+ return Object.assign(Object.assign({}, baseAttributes), { [ATTR_GEN_AI_OUTPUT_MESSAGES]: formatOutputMessage(response["completion"], stopReason, bedrockFinishReasonMap, GEN_AI_OPERATION_NAME_VALUE_TEXT_COMPLETION, mapBedrockContentBlock) });
289
442
  }
290
443
  return baseAttributes;
291
444
  }
292
- case "cohere": {
293
- const baseAttributes = Object.assign({ [`${ATTR_GEN_AI_COMPLETION}.0.finish_reason`]: (_b = (_a = response["generations"]) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b["finish_reason"], [`${ATTR_GEN_AI_COMPLETION}.0.role`]: "assistant" }, (this._shouldSendPrompts()
445
+ case BedrockVendor.COHERE: {
446
+ const cohereFinishReason = (_2 = (_1 = (_0 = response["generations"]) === null || _0 === void 0 ? void 0 : _0[0]) === null || _1 === void 0 ? void 0 : _1["finish_reason"]) !== null && _2 !== void 0 ? _2 : response["finish_reason"];
447
+ const cohereText = (_5 = (_4 = (_3 = response["generations"]) === null || _3 === void 0 ? void 0 : _3[0]) === null || _4 === void 0 ? void 0 : _4["text"]) !== null && _5 !== void 0 ? _5 : response["text"];
448
+ return Object.assign(Object.assign(Object.assign({}, (cohereFinishReason != null
294
449
  ? {
295
- [`${ATTR_GEN_AI_COMPLETION}.0.content`]: (_d = (_c = response["generations"]) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d["text"],
450
+ [ATTR_GEN_AI_RESPONSE_FINISH_REASONS]: [
451
+ (_6 = bedrockFinishReasonMap[cohereFinishReason]) !== null && _6 !== void 0 ? _6 : cohereFinishReason,
452
+ ],
453
+ }
454
+ : {})), (((_7 = response["meta"]) === null || _7 === void 0 ? void 0 : _7["billed_units"])
455
+ ? {
456
+ [ATTR_GEN_AI_USAGE_INPUT_TOKENS]: response["meta"]["billed_units"]["input_tokens"],
457
+ [ATTR_GEN_AI_USAGE_OUTPUT_TOKENS]: response["meta"]["billed_units"]["output_tokens"],
458
+ [SpanAttributes.GEN_AI_USAGE_TOTAL_TOKENS]: (response["meta"]["billed_units"]["input_tokens"] || 0) +
459
+ (response["meta"]["billed_units"]["output_tokens"] || 0),
460
+ }
461
+ : {})), (this._shouldSendPrompts() && cohereText != null
462
+ ? {
463
+ [ATTR_GEN_AI_OUTPUT_MESSAGES]: formatOutputMessage(cohereText, cohereFinishReason, bedrockFinishReasonMap, GEN_AI_OPERATION_NAME_VALUE_TEXT_COMPLETION, mapBedrockContentBlock),
296
464
  }
297
465
  : {}));
298
- // Add token usage if available
299
- if (response["meta"] && response["meta"]["billed_units"]) {
300
- const billedUnits = response["meta"]["billed_units"];
301
- return Object.assign(Object.assign({}, baseAttributes), { [ATTR_GEN_AI_USAGE_PROMPT_TOKENS]: billedUnits["input_tokens"], [ATTR_GEN_AI_USAGE_COMPLETION_TOKENS]: billedUnits["output_tokens"], [SpanAttributes.LLM_USAGE_TOTAL_TOKENS]: (billedUnits["input_tokens"] || 0) +
302
- (billedUnits["output_tokens"] || 0) });
303
- }
304
- return baseAttributes;
305
466
  }
306
- case "meta": {
307
- return Object.assign({ [`${ATTR_GEN_AI_COMPLETION}.0.finish_reason`]: response["stop_reason"], [`${ATTR_GEN_AI_COMPLETION}.0.role`]: "assistant", [ATTR_GEN_AI_USAGE_PROMPT_TOKENS]: response["prompt_token_count"], [ATTR_GEN_AI_USAGE_COMPLETION_TOKENS]: response["generation_token_count"], [SpanAttributes.LLM_USAGE_TOTAL_TOKENS]: response["prompt_token_count"] + response["generation_token_count"] }, (this._shouldSendPrompts()
467
+ case BedrockVendor.META: {
468
+ const metaFinishReason = response["stop_reason"];
469
+ const metaContent = response["generation"];
470
+ return Object.assign(Object.assign(Object.assign({}, (metaFinishReason != null
471
+ ? {
472
+ [ATTR_GEN_AI_RESPONSE_FINISH_REASONS]: [
473
+ (_8 = bedrockFinishReasonMap[metaFinishReason]) !== null && _8 !== void 0 ? _8 : metaFinishReason,
474
+ ],
475
+ }
476
+ : {})), { [ATTR_GEN_AI_USAGE_INPUT_TOKENS]: response["prompt_token_count"], [ATTR_GEN_AI_USAGE_OUTPUT_TOKENS]: response["generation_token_count"], [SpanAttributes.GEN_AI_USAGE_TOTAL_TOKENS]: response["prompt_token_count"] + response["generation_token_count"] }), (this._shouldSendPrompts()
308
477
  ? {
309
- [`${ATTR_GEN_AI_COMPLETION}.0.content`]: response["generation"],
478
+ [ATTR_GEN_AI_OUTPUT_MESSAGES]: formatOutputMessage(metaContent !== null && metaContent !== void 0 ? metaContent : "", metaFinishReason, bedrockFinishReasonMap, GEN_AI_OPERATION_NAME_VALUE_TEXT_COMPLETION, mapBedrockContentBlock),
310
479
  }
311
480
  : {}));
312
481
  }
@@ -314,6 +483,15 @@ class BedrockInstrumentation extends InstrumentationBase {
314
483
  return {};
315
484
  }
316
485
  }
486
+ _handleNovaStreamingMetadata(span, parsedResponse) {
487
+ var _a;
488
+ if ("metadata" in parsedResponse && ((_a = parsedResponse["metadata"]) === null || _a === void 0 ? void 0 : _a["usage"])) {
489
+ const usage = parsedResponse["metadata"]["usage"];
490
+ span.setAttribute(ATTR_GEN_AI_USAGE_INPUT_TOKENS, usage["inputTokens"]);
491
+ span.setAttribute(ATTR_GEN_AI_USAGE_OUTPUT_TOKENS, usage["outputTokens"]);
492
+ span.setAttribute(SpanAttributes.GEN_AI_USAGE_TOTAL_TOKENS, usage["totalTokens"]);
493
+ }
494
+ }
317
495
  _shouldSendPrompts() {
318
496
  const contextShouldSendPrompts = context
319
497
  .active()
@@ -325,17 +503,62 @@ class BedrockInstrumentation extends InstrumentationBase {
325
503
  ? this._config.traceContent
326
504
  : true;
327
505
  }
506
+ _getOperationType(vendor, requestBody) {
507
+ switch (vendor) {
508
+ case BedrockVendor.AI21:
509
+ case BedrockVendor.AMAZON:
510
+ case BedrockVendor.ANTHROPIC:
511
+ return requestBody["messages"]
512
+ ? GEN_AI_OPERATION_NAME_VALUE_CHAT
513
+ : GEN_AI_OPERATION_NAME_VALUE_TEXT_COMPLETION;
514
+ case BedrockVendor.COHERE:
515
+ case BedrockVendor.META:
516
+ return GEN_AI_OPERATION_NAME_VALUE_TEXT_COMPLETION;
517
+ default:
518
+ return GEN_AI_OPERATION_NAME_VALUE_TEXT_COMPLETION;
519
+ }
520
+ }
521
+ _getStreamChunkContent(vendor, response) {
522
+ var _a, _b, _c, _d, _e, _f, _g, _h;
523
+ switch (vendor) {
524
+ case BedrockVendor.AMAZON:
525
+ return ((_c = (_b = (_a = response["contentBlockDelta"]) === null || _a === void 0 ? void 0 : _a["delta"]) === null || _b === void 0 ? void 0 : _b["text"]) !== null && _c !== void 0 ? _c : response["outputText"]);
526
+ case BedrockVendor.ANTHROPIC:
527
+ return (_e = (_d = response["delta"]) === null || _d === void 0 ? void 0 : _d["text"]) !== null && _e !== void 0 ? _e : response["completion"];
528
+ case BedrockVendor.AI21:
529
+ return (_h = (_g = (_f = response["choices"]) === null || _f === void 0 ? void 0 : _f[0]) === null || _g === void 0 ? void 0 : _g["delta"]) === null || _h === void 0 ? void 0 : _h["content"];
530
+ case BedrockVendor.META:
531
+ return response["generation"];
532
+ default:
533
+ return undefined;
534
+ }
535
+ }
328
536
  _extractVendorAndModel(modelId) {
329
537
  if (!modelId) {
330
538
  return { modelVendor: "", model: "" };
331
539
  }
332
- const parts = modelId.split(".");
540
+ // Handle cross-region inference profile IDs like "us.anthropic.claude-3-5-sonnet-20241022-v2:0"
541
+ // Mirrors Python's _cross_region_check logic.
542
+ const prefixes = ["us", "us-gov", "eu", "apac"];
543
+ const hasCrossRegionPrefix = prefixes.some((prefix) => modelId.startsWith(prefix + "."));
544
+ if (hasCrossRegionPrefix) {
545
+ const parts = modelId.split(".");
546
+ if (parts.length > 2) {
547
+ parts.shift(); // remove region prefix ("us", "eu", etc.)
548
+ }
549
+ return { modelVendor: parts[0] || "", model: parts[1] || "" };
550
+ }
551
+ // Standard format: "vendor.model-name"
552
+ const dotIndex = modelId.indexOf(".");
553
+ if (dotIndex === -1) {
554
+ return { modelVendor: modelId, model: "" };
555
+ }
333
556
  return {
334
- modelVendor: parts[0] || "",
335
- model: parts[1] || "",
557
+ modelVendor: modelId.slice(0, dotIndex),
558
+ model: modelId.slice(dotIndex + 1),
336
559
  };
337
560
  }
338
561
  }
339
562
 
340
- export { BedrockInstrumentation };
563
+ export { BedrockInstrumentation, BedrockVendor, bedrockFinishReasonMap };
341
564
  //# sourceMappingURL=index.mjs.map