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