newrelic 13.13.0 → 13.14.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.
Files changed (35) hide show
  1. package/NEWS.md +33 -0
  2. package/lib/aggregators/base-aggregator.js +2 -0
  3. package/lib/aggregators/profiling-aggregator.js +6 -1
  4. package/lib/errors/index.js +81 -21
  5. package/lib/harvester.js +18 -3
  6. package/lib/instrumentation/@azure/functions.js +45 -1
  7. package/lib/instrumentation/aws-sdk/v3/bedrock.js +10 -11
  8. package/lib/llm-events/aws-bedrock/bedrock-command.js +1 -1
  9. package/lib/llm-events/aws-bedrock/bedrock-response.js +1 -1
  10. package/lib/llm-events/aws-bedrock/chat-completion-message.js +40 -64
  11. package/lib/llm-events/aws-bedrock/chat-completion-summary.js +37 -43
  12. package/lib/llm-events/aws-bedrock/embedding.js +31 -29
  13. package/lib/llm-events/aws-bedrock/index.js +4 -9
  14. package/lib/llm-events/error-message.js +2 -2
  15. package/lib/llm-events/google-genai/chat-completion-message.js +24 -30
  16. package/lib/llm-events/google-genai/chat-completion-summary.js +31 -11
  17. package/lib/llm-events/google-genai/embedding.js +25 -12
  18. package/lib/llm-events/google-genai/get-usage-tokens.js +26 -0
  19. package/lib/llm-events/google-genai/index.js +4 -12
  20. package/lib/llm-events/langgraph/agent.js +13 -29
  21. package/lib/profiling/index.js +2 -1
  22. package/lib/spans/span-event.js +8 -8
  23. package/lib/subscribers/ai-monitoring/embedding.js +1 -1
  24. package/lib/subscribers/google-genai/config.js +20 -2
  25. package/lib/subscribers/google-genai/embed-content.js +11 -1
  26. package/lib/subscribers/google-genai/generate-content.js +37 -9
  27. package/lib/subscribers/langgraph/graph-stream.js +3 -3
  28. package/lib/transaction/trace/partial-trace.js +9 -6
  29. package/package.json +1 -1
  30. package/lib/llm-events/aws-bedrock/event.js +0 -79
  31. package/lib/llm-events/event.js +0 -130
  32. package/lib/llm-events/google-genai/event.js +0 -36
  33. package/lib/{llm-events/aws-bedrock → instrumentation/aws-sdk/v3}/converse-stream-handler.js +0 -0
  34. package/lib/{llm-events/aws-bedrock → instrumentation/aws-sdk/v3}/stream-handler.js +0 -0
  35. package/lib/llm-events/aws-bedrock/{utils.js → stringify-message.js} +5 -5
package/NEWS.md CHANGED
@@ -1,3 +1,35 @@
1
+ ### v13.14.0 (2026-02-24)
2
+
3
+ #### Features
4
+
5
+ * Added ability to delay the start of the Profiler and run for n milliseconds before shutting down. ([#3758](https://github.com/newrelic/node-newrelic/pull/3758)) ([8503d25](https://github.com/newrelic/node-newrelic/commit/8503d252dce31fa5ba0cbb4f146511155de48f88))
6
+ * Added functionality to ingest logs from Azure Functions App automatically ([#3772](https://github.com/newrelic/node-newrelic/pull/3772)) ([0c70ba2](https://github.com/newrelic/node-newrelic/commit/0c70ba20d2c03af03605ebc12baadbfd00c8733d))
7
+ * Added support for error causes ([#3769](https://github.com/newrelic/node-newrelic/pull/3769)) ([7c5aae3](https://github.com/newrelic/node-newrelic/commit/7c5aae3ac8b14e781a0740ffc57c2b2defc4300b))
8
+
9
+ #### Bug fixes
10
+
11
+ * Updated `@google/genai` subscriber to handle the new `embedContentInternal` method in 1.42.0 ([#3771](https://github.com/newrelic/node-newrelic/pull/3771)) ([e96c974](https://github.com/newrelic/node-newrelic/commit/e96c974a1aa8c15caebd66004c939c43f61cc0e2))
12
+
13
+ #### Code refactoring
14
+
15
+ * AWS Bedrock llm events refactor ([#3759](https://github.com/newrelic/node-newrelic/pull/3759)) ([7ff953d](https://github.com/newrelic/node-newrelic/commit/7ff953dbda4529591438811b8b58cfada3767f80))
16
+ * Google Gen AI LLM event refactor ([#3748](https://github.com/newrelic/node-newrelic/pull/3748)) ([f1e3a0e](https://github.com/newrelic/node-newrelic/commit/f1e3a0e57ea26370ce5021010ab6fe793dd5c594))
17
+ * LangGraph LlmAgent event, remove old LlmEvent base ([#3761](https://github.com/newrelic/node-newrelic/pull/3761)) ([39d1d9a](https://github.com/newrelic/node-newrelic/commit/39d1d9a59ca467d71d4a7db4cf437d743a24939b))
18
+ * Updated the reparenting logic for compact mode of partial traces to assign parent id to entry span ([#3768](https://github.com/newrelic/node-newrelic/pull/3768)) ([5196318](https://github.com/newrelic/node-newrelic/commit/5196318713cbeba113936a902ff4f092c33109cb))
19
+
20
+ #### Documentation
21
+
22
+ * Updated compatibility report ([#3774](https://github.com/newrelic/node-newrelic/pull/3774)) ([5d04bba](https://github.com/newrelic/node-newrelic/commit/5d04bba57ddb23b2aa1861cf46e393adae2c43ca))
23
+
24
+ #### Miscellaneous chores
25
+
26
+ * Added API key to enable support for GH action ([#3760](https://github.com/newrelic/node-newrelic/pull/3760)) ([a521e80](https://github.com/newrelic/node-newrelic/commit/a521e8065c525312d1170bfa725610d708b011e3))
27
+ * Added call to agent metadata workflow - step 1 ([#3750](https://github.com/newrelic/node-newrelic/pull/3750)) ([90eeaec](https://github.com/newrelic/node-newrelic/commit/90eeaec0e4de28c779ccab7a8f4b03448e8448bd))
28
+ * Added span id to all debug/trace level logs in `span.applyPartialTraceRules` ([#3766](https://github.com/newrelic/node-newrelic/pull/3766)) ([8c57230](https://github.com/newrelic/node-newrelic/commit/8c57230f3d580ace1cb5eb1611dc78f85bbe36f1))
29
+ * Renamed file and fix cache output ([#3757](https://github.com/newrelic/node-newrelic/pull/3757)) ([1ac00ea](https://github.com/newrelic/node-newrelic/commit/1ac00ea4718fb6e95502961d777e11c8ae71dde9))
30
+ * Set up metadata action to run with releases ([#3762](https://github.com/newrelic/node-newrelic/pull/3762)) ([66efb13](https://github.com/newrelic/node-newrelic/commit/66efb13b9bde33a28fb4256cb70bf313e10774bc))
31
+ * Updated `@langchain/core` version for langchain-openai tests ([#3763](https://github.com/newrelic/node-newrelic/pull/3763)) ([c27213e](https://github.com/newrelic/node-newrelic/commit/c27213e6ca3048463158fcfecb7e1cfcf87d4922))
32
+
1
33
  ### v13.13.0 (2026-02-17)
2
34
 
3
35
  ## Feature Release
@@ -8367,4 +8399,5 @@ Special thanks to Ryan Copley (@RyanCopley) for the contribution.
8367
8399
 
8368
8400
 
8369
8401
 
8402
+
8370
8403
 
@@ -160,6 +160,8 @@ class Aggregator extends EventEmitter {
160
160
 
161
161
  this.defaultPeriod = this.periodMs = opts.periodMs
162
162
  this.defaultLimit = this.limit = opts.limit
163
+ this.delay = opts.delay ?? 0
164
+ this.duration = opts.duration ?? 0
163
165
  this.runId = opts.runId
164
166
  this.isAsync = opts.isAsync || false
165
167
  // function to pass in to determine if we can start a given aggregator
@@ -43,8 +43,13 @@ class ProfilingAggregator extends BaseAggregator {
43
43
  * and send the gzipped binary encoded data for each profiler
44
44
  */
45
45
  start() {
46
- logger.trace(`${this.method} aggregator started.`)
47
46
  this.profilingManager.register()
47
+ const started = this.profilingManager.start()
48
+ if (!started) {
49
+ return
50
+ }
51
+
52
+ logger.trace(`${this.method} aggregator started.`)
48
53
 
49
54
  if (!this.sendTimer) {
50
55
  this.sendTimer = setInterval(this.collectData.bind(this), this.periodMs)
@@ -18,6 +18,19 @@ const {
18
18
  const synthetics = require('../synthetics')
19
19
  const ERROR_EXPECTED_PATH = 'error.expected'
20
20
 
21
+ /**
22
+ * Wraps `Error` instances for delivery to the back end. The wrapper includes
23
+ * the original error along with metadata, such as custom attributes, to be
24
+ * delivered.
25
+ *
26
+ * @class
27
+ * @param {object} options Configuration options for the exception
28
+ * @param {Error} options.error The error object to be wrapped
29
+ * @param {number} [options.timestamp=0] Unix timestamp (in milliseconds) when the error occurred
30
+ * @param {object} [options.customAttributes={}] Custom user-defined attributes associated with the error
31
+ * @param {object} [options.agentAttributes={}] Agent-generated attributes associated with the error
32
+ * @param {boolean} [options.expected] Whether this error is expected (not a true failure)
33
+ */
21
34
  class Exception {
22
35
  constructor({ error, timestamp, customAttributes, agentAttributes, expected }) {
23
36
  this.error = error
@@ -45,6 +58,36 @@ class Exception {
45
58
  }
46
59
  }
47
60
 
61
+ /**
62
+ * Given the `.cause` value from an `Error` instance, serialize the chain
63
+ * into an object than can be attached to the agent attributes object.
64
+ *
65
+ * @param {Error|string} input The error cause to serialize.
66
+ * @returns {object[]} Serialized causes object array.
67
+ */
68
+ function serializeCauses(input) {
69
+ if (!input) {
70
+ return []
71
+ }
72
+
73
+ if (typeof input === 'string') {
74
+ return [{ message: input }]
75
+ }
76
+
77
+ if (
78
+ Object.hasOwn(input, 'message') === false ||
79
+ Object.hasOwn(input, 'stack') === false
80
+ ) {
81
+ return [{ message: input.message ?? 'cause does not look like an Error instance' }]
82
+ }
83
+
84
+ return [{
85
+ message: input.message,
86
+ stack: input.stack,
87
+ cause: input.cause ? serializeCauses(input.cause) : []
88
+ }]
89
+ }
90
+
48
91
  /**
49
92
  * Given either or both of a transaction and an exception, generate an error
50
93
  * trace in the JSON format expected by the collector. Since this will be
@@ -110,6 +153,21 @@ function createError(transaction, exception, config) {
110
153
 
111
154
  maybeAddAgentAttributes(params, exception)
112
155
 
156
+ // We need to detect error causes and do something with them. We don't have
157
+ // a spec, or ui expectation, for them. So we are opting to embed them as
158
+ // agent attributes. This does not account for `AggregateError` instances.
159
+ if (Object.hasOwn(error ?? {}, 'cause') === true) {
160
+ // We have to stringify the data in order for it to show up correctly
161
+ // in the errors inbox's list of attributes. If we rely on our aggregator
162
+ // to stringify it, then the backend will decode it to an array of objects
163
+ // and the attribute will not be presented in the UI. By doubly encoding
164
+ // it, the back end will decode it to a JSON _string_ and show the
165
+ // attribute in the UI as desired.
166
+ params.agentAttributes['error.cause'] = JSON.stringify(
167
+ serializeCauses(error.cause)
168
+ )
169
+ }
170
+
113
171
  return [0, name, message, type, params, transaction?.id]
114
172
  }
115
173
 
@@ -118,31 +176,33 @@ function isValidErrorGroupOutput(output) {
118
176
  }
119
177
 
120
178
  function maybeAddAgentAttributes(attributes, exception) {
121
- if (exception.errorGroupCallback) {
122
- const callbackInput = {
123
- error: exception.error,
124
- customAttributes: Object.assign({}, attributes.userAttributes),
125
- 'request.uri': attributes.agentAttributes['request.uri'],
126
- 'http.statusCode': attributes.agentAttributes['http.statusCode'],
127
- 'http.method': attributes.agentAttributes['request.method'],
128
- 'error.expected': attributes.intrinsics[ERROR_EXPECTED_PATH]
129
- }
179
+ if (typeof exception.errorGroupCallback !== 'function') {
180
+ return
181
+ }
130
182
 
131
- try {
132
- const callbackOutput = exception.errorGroupCallback(callbackInput)
183
+ const callbackInput = {
184
+ error: exception.error,
185
+ customAttributes: Object.assign({}, attributes.userAttributes),
186
+ 'request.uri': attributes.agentAttributes['request.uri'],
187
+ 'http.statusCode': attributes.agentAttributes['http.statusCode'],
188
+ 'http.method': attributes.agentAttributes['request.method'],
189
+ 'error.expected': attributes.intrinsics[ERROR_EXPECTED_PATH]
190
+ }
133
191
 
134
- if (!isValidErrorGroupOutput(callbackOutput)) {
135
- logger.warn('Function provided via setErrorGroupCallback return value malformed')
136
- return
137
- }
192
+ try {
193
+ const callbackOutput = exception.errorGroupCallback(callbackInput)
138
194
 
139
- attributes.agentAttributes['error.group.name'] = callbackOutput
140
- } catch (err) {
141
- logger.warn(
142
- err,
143
- 'Function provided via setErrorGroupCallback failed, not generating `error.group.name`'
144
- )
195
+ if (!isValidErrorGroupOutput(callbackOutput)) {
196
+ logger.warn('Function provided via setErrorGroupCallback return value malformed')
197
+ return
145
198
  }
199
+
200
+ attributes.agentAttributes['error.group.name'] = callbackOutput
201
+ } catch (err) {
202
+ logger.warn(
203
+ err,
204
+ 'Function provided via setErrorGroupCallback failed, not generating `error.group.name`'
205
+ )
146
206
  }
147
207
  }
148
208
 
package/lib/harvester.js CHANGED
@@ -2,16 +2,17 @@
2
2
  * Copyright 2024 New Relic Corporation. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
-
6
5
  'use strict'
6
+ const defaultLogger = require('./logger').child({ component: 'harvester' })
7
7
 
8
8
  /**
9
9
  * @class
10
10
  * @classdesc Used to keep track of all registered aggregators.
11
11
  */
12
12
  module.exports = class Harvester {
13
- constructor() {
13
+ constructor({ logger = defaultLogger } = {}) {
14
14
  this.aggregators = []
15
+ this.logger = logger
15
16
  }
16
17
 
17
18
  /**
@@ -19,9 +20,23 @@ module.exports = class Harvester {
19
20
  */
20
21
  start() {
21
22
  for (const aggregator of this.aggregators) {
22
- if (aggregator.enabled) {
23
+ if (aggregator.enabled && aggregator.delay > 0) {
24
+ this.logger.debug(`Delay start of ${aggregator.method} by ${aggregator.delay} milliseconds`)
25
+ const timeout = setTimeout(() => {
26
+ aggregator.start()
27
+ }, aggregator.delay)
28
+ timeout.unref()
29
+ } else if (aggregator.enabled) {
23
30
  aggregator.start()
24
31
  }
32
+
33
+ if (aggregator.enabled && aggregator.duration > 0) {
34
+ this.logger.debug(`Running ${aggregator.method} for ${aggregator.duration} milliseconds`)
35
+ const durationTimeout = setTimeout(() => {
36
+ aggregator.stop()
37
+ }, aggregator.delay + aggregator.duration)
38
+ durationTimeout.unref()
39
+ }
25
40
  }
26
41
  }
27
42
 
@@ -10,6 +10,12 @@ const { Transform } = require('node:stream')
10
10
 
11
11
  const backgroundRecorder = require('../../metrics/recorders/other.js')
12
12
  const recordWeb = require('../../metrics/recorders/http')
13
+ const {
14
+ isApplicationLoggingEnabled,
15
+ isLogForwardingEnabled,
16
+ isMetricsEnabled,
17
+ incrementLoggingLinesMetrics
18
+ } = require('../../util/application-logging.js')
13
19
 
14
20
  const {
15
21
  DESTINATIONS: DESTS,
@@ -25,9 +31,16 @@ const SUBSCRIPTION_ID = WEBSITE_OWNER_NAME?.split('+').shift()
25
31
  const RESOURCE_GROUP_NAME = WEBSITE_RESOURCE_GROUP ?? WEBSITE_OWNER_NAME?.split('+').pop().split('-Linux').shift()
26
32
  const AZURE_FUNCTION_APP_NAME = WEBSITE_SITE_NAME
27
33
 
34
+ // Azure Functions uses 'information' as the context level for context.log and context.info and uses 'warning' as the context level for context.warn.
35
+ // These map to the NR standard names that LOGGING.LEVELS recognizes ('info', 'warn').
36
+ const AZURE_TO_NR_LEVEL = {
37
+ information: 'info',
38
+ warning: 'warn'
39
+ }
40
+
28
41
  let coldStart = true
29
42
 
30
- module.exports = function initialize(_agent, azureFunctions, _moduleName, shim, { logger = defaultLogger } = {}) {
43
+ module.exports = function initialize(agent, azureFunctions, _moduleName, shim, { logger = defaultLogger } = {}) {
31
44
  if (!SUBSCRIPTION_ID || !RESOURCE_GROUP_NAME || !AZURE_FUNCTION_APP_NAME) {
32
45
  logger.warn(
33
46
  {
@@ -59,6 +72,8 @@ module.exports = function initialize(_agent, azureFunctions, _moduleName, shim,
59
72
  'webPubSub'
60
73
  ]
61
74
  shim.wrap(azureFunctions.app, backgroundMethods, wrapAzureBackgroundMethods)
75
+
76
+ registerAzureLogs(agent, azureFunctions, logger)
62
77
  }
63
78
 
64
79
  function wrapAzureHttpMethods(shim, appMethod) {
@@ -174,6 +189,35 @@ function wrapAzureBackgroundMethods(shim, appMethod) {
174
189
  }
175
190
  }
176
191
 
192
+ function registerAzureLogs(agent, azureFunctions, logger) {
193
+ if (!isApplicationLoggingEnabled(agent.config)) {
194
+ logger.debug('Application logging not enabled. Not auto capturing logs from Azure Functions.')
195
+ return
196
+ }
197
+
198
+ azureFunctions.app.hook?.log((context) => {
199
+ const logLevel = AZURE_TO_NR_LEVEL[context.level] ?? context.level
200
+
201
+ if (isLogForwardingEnabled(agent.config, agent)) {
202
+ const meta = agent.getLinkingMetadata(true)
203
+
204
+ const logData = {
205
+ message: context.message,
206
+ level: logLevel,
207
+ timestamp: Date.now(),
208
+ category: context.category,
209
+ ...meta
210
+ }
211
+
212
+ agent.logs.add(logData)
213
+ }
214
+
215
+ if (isMetricsEnabled(agent.config)) {
216
+ incrementLoggingLinesMetrics(logLevel, agent.metrics)
217
+ }
218
+ })
219
+ }
220
+
177
221
  /**
178
222
  * Add required New Relic attributes to the transaction.
179
223
  *
@@ -4,22 +4,22 @@
4
4
  */
5
5
 
6
6
  'use strict'
7
+ const StreamHandler = require('./stream-handler')
8
+ const ConverseStreamHandler = require('./converse-stream-handler')
7
9
  const {
8
10
  LlmChatCompletionMessage,
9
11
  LlmChatCompletionSummary,
10
12
  LlmEmbedding,
11
- LlmErrorMessage,
12
13
  BedrockCommand,
13
- BedrockResponse,
14
- StreamHandler
15
- } = require('../../../llm-events/aws-bedrock')
14
+ BedrockResponse
15
+ } = require('#agentlib/llm-events/aws-bedrock/index.js')
16
+ const LlmErrorMessage = require('#agentlib/llm-events/error-message.js')
16
17
 
17
18
  const { DESTINATIONS } = require('../../../config/attribute-filter')
18
19
  const { AI } = require('../../../metrics/names')
19
20
  const { RecorderSpec } = require('../../../shim/specs')
20
21
  const InstrumentationDescriptor = require('../../../instrumentation-descriptor')
21
22
  const { extractLlmContext } = require('../../../util/llm-utils')
22
- const ConverseStreamHandler = require('../../../llm-events/aws-bedrock/converse-stream-handler')
23
23
 
24
24
  let TRACKING_METRIC
25
25
 
@@ -73,7 +73,6 @@ function isStreamingEnabled({ commandName, config }) {
73
73
  * @param {object} params.msg LLM event
74
74
  */
75
75
  function recordEvent({ agent, type, msg }) {
76
- msg.serialize()
77
76
  const llmContext = extractLlmContext(agent)
78
77
  const timestamp = msg?.timestamp ?? Date.now()
79
78
 
@@ -134,7 +133,7 @@ function recordChatCompletionMessages({
134
133
  bedrockCommand,
135
134
  transaction,
136
135
  segment,
137
- isError: err !== null
136
+ error: err !== null
138
137
  })
139
138
 
140
139
  // Record context message(s)
@@ -149,7 +148,7 @@ function recordChatCompletionMessages({
149
148
  content: contextMessage.content,
150
149
  role: contextMessage.role,
151
150
  bedrockResponse,
152
- index: i,
151
+ sequence: i,
153
152
  completionId: summary.id
154
153
  })
155
154
  recordEvent({ agent, type: 'LlmChatCompletionMessage', msg })
@@ -164,7 +163,7 @@ function recordChatCompletionMessages({
164
163
  bedrockCommand,
165
164
  bedrockResponse,
166
165
  isResponse: true,
167
- index: promptContextMessages.length + i,
166
+ sequence: promptContextMessages.length + i,
168
167
  content,
169
168
  role: 'assistant',
170
169
  completionId: summary.id
@@ -217,9 +216,9 @@ function recordEmbeddingMessage({
217
216
  segment,
218
217
  transaction,
219
218
  bedrockCommand,
220
- input: prompt.content,
219
+ requestInput: prompt.content,
221
220
  bedrockResponse,
222
- isError: err !== null
221
+ error: err !== null
223
222
  }))
224
223
 
225
224
  for (const embedding of embeddings) {
@@ -5,7 +5,7 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- const { stringifyClaudeChunkedMessage, stringifyConverseChunkedMessage } = require('./utils')
8
+ const { stringifyClaudeChunkedMessage, stringifyConverseChunkedMessage } = require('./stringify-message')
9
9
 
10
10
  /**
11
11
  * Parses an AWS Bedrock command instance into a re-usable entity,
@@ -5,7 +5,7 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- const { stringifyClaudeChunkedMessage, stringifyConverseChunkedMessage } = require('./utils')
8
+ const { stringifyClaudeChunkedMessage, stringifyConverseChunkedMessage } = require('./stringify-message')
9
9
 
10
10
  /**
11
11
  * @typedef {object} AwsBedrockMiddlewareResponse
@@ -1,82 +1,59 @@
1
1
  /*
2
- * Copyright 2023 New Relic Corporation. All rights reserved.
2
+ * Copyright 2026 New Relic Corporation. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
 
6
6
  'use strict'
7
7
 
8
- const LlmEvent = require('./event')
9
- /**
10
- * @typedef {object} LlmChatCompletionParams
11
- * @augments LlmEventParams
12
- * @property {string} completionId An identifier for the completion message.
13
- * @property {string} content The human readable response from the LLM.
14
- * @property {number} [index=0] The order of the message in the conversation.
15
- * @property {boolean} [isResponse=false] Indicates if the message represents
16
- * a response from the LLM.
17
- * @property {object} message The message sent to the LLM.
18
- * @property {OutgoingMessage} request The outgoing HTTP request used in the
19
- * LLM conversation.
20
- */
21
- /**
22
- * @type {LlmChatCompletionParams}
23
- */
24
- const defaultParams = {
25
- completionId: '',
26
- content: '',
27
- index: 0,
28
- isResponse: false,
29
- message: {},
30
- request: {}
31
- }
8
+ const LlmChatCompletionMessage = require('../chat-completion-message')
32
9
 
33
- /**
34
- * Represents an LLM chat completion.
35
- */
36
- class LlmChatCompletionMessage extends LlmEvent {
37
- constructor(params = defaultParams) {
38
- params = Object.assign({}, defaultParams, params)
39
- super(params)
40
-
41
- const { agent, content, isResponse, index, completionId, role, segment } = params
42
- const recordContent = agent.config?.ai_monitoring?.record_content?.enabled
10
+ module.exports = class AwsBedrockLlmChatCompletionMessage extends LlmChatCompletionMessage {
11
+ /**
12
+ *
13
+ * @param {object} params constructor parameters
14
+ * @param {Agent} params.agent New Relic agent instance
15
+ * @param {object} params.segment Current segment
16
+ * @param {object} params.transaction Current and active transaction
17
+ * @param {object} params.bedrockCommand AWS Bedrock Command object, represents the request
18
+ * @param {object} params.bedrockResponse AWS Bedrock Response object
19
+ * @param {string} params.content Content of the message
20
+ * @param {string} [params.role] Role of the message creator (e.g. `user`, `assistant`, `tool`)
21
+ * @param {string} params.completionId ID of the `LlmChatCompletionSummary` event that
22
+ * this message event is connected to
23
+ * @param {number} params.sequence Index (beginning at 0) associated with
24
+ * each message including the prompt and responses
25
+ * @param {boolean} [params.isResponse] Indiciates if this message is the response
26
+ */
27
+ constructor({ agent, segment, transaction, bedrockCommand, bedrockResponse, content, role, completionId, sequence = 0, isResponse }) {
28
+ super({ agent,
29
+ segment,
30
+ transaction,
31
+ vendor: 'bedrock',
32
+ content,
33
+ role,
34
+ sequence,
35
+ requestId: bedrockResponse?.requestId,
36
+ responseId: bedrockResponse?.id,
37
+ responseModel: bedrockCommand?.modelId, // we can assume requestModel==responseModel in bedrock
38
+ completionId,
39
+ isResponse })
43
40
 
44
- this.is_response = isResponse
45
- this.completion_id = completionId
46
- this.sequence = index
47
- this.content = recordContent === true ? content : undefined
48
- this.role = role
49
- if (this.is_response === false) {
50
- // Only record for request/input messages
51
- this.timestamp = segment.timer.start
52
- }
53
-
54
- this.#setId(index)
55
- this.setTokenCount(agent)
41
+ this.setTokenCount(agent, bedrockCommand, bedrockResponse)
56
42
  }
57
43
 
58
- #setId(index) {
59
- const cmd = this.bedrockCommand
60
- if (cmd.isConverse || cmd.isTitan() === true || cmd.isClaude() === true) {
61
- this.id = `${this.id}-${index}`
62
- } else if (cmd.isCohere() === true) {
63
- this.id = `${this.bedrockResponse.id || this.id}-${index}`
64
- }
65
- }
66
-
67
- setTokenCount(agent) {
44
+ setTokenCount(agent, bedrockCommand, bedrockResponse) {
68
45
  const tokenCB = agent?.llm?.tokenCountCallback
69
46
 
70
47
  if (tokenCB) {
71
- const promptContent = this.bedrockCommand?.prompt?.map((msg) => msg.content).join(' ')
72
- const completionContent = this.bedrockResponse?.completions?.join(' ')
48
+ const promptContent = bedrockCommand?.prompt?.map((msg) => msg.content).join(' ')
49
+ const completionContent = bedrockResponse?.completions?.join(' ')
73
50
 
74
51
  if (promptContent && completionContent) {
75
52
  this.setTokenFromCallback(
76
53
  {
77
54
  tokenCB,
78
- reqModel: this.bedrockCommand.modelId,
79
- resModel: this.bedrockCommand.modelId,
55
+ reqModel: bedrockCommand.modelId,
56
+ resModel: bedrockCommand.modelId,
80
57
  promptContent,
81
58
  completionContent
82
59
  }
@@ -85,8 +62,7 @@ class LlmChatCompletionMessage extends LlmEvent {
85
62
  return
86
63
  }
87
64
 
88
- this.setTokenInCompletionMessage({ promptTokens: this.bedrockResponse.inputTokenCount, completionTokens: this.bedrockResponse.outputTokenCount })
65
+ this.setTokenInCompletionMessage({ promptTokens: bedrockResponse.inputTokenCount,
66
+ completionTokens: bedrockResponse.outputTokenCount })
89
67
  }
90
68
  }
91
-
92
- module.exports = LlmChatCompletionMessage
@@ -1,59 +1,53 @@
1
1
  /*
2
- * Copyright 2024 New Relic Corporation. All rights reserved.
2
+ * Copyright 2026 New Relic Corporation. All rights reserved.
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
 
6
6
  'use strict'
7
7
 
8
- const LlmEvent = require('./event')
9
-
10
- /**
11
- * @typedef {object} LlmChatCompletionSummaryParams
12
- * @augments LlmEventParams
13
- * @property {string} segment the segment associated with this LlmChatCompletionSummary
14
- * @property {boolean} isError whether this event represents an error
15
- */
16
- /**
17
- * @type {LlmChatCompletionSummaryParams}
18
- */
19
- const defaultParams = {}
20
-
21
- /**
22
- * Represents an LLM chat completion summary.
23
- */
24
- class LlmChatCompletionSummary extends LlmEvent {
25
- constructor(params = defaultParams) {
26
- super(params)
27
-
28
- const { segment, isError, agent } = params
29
- this.error = isError
30
- this.duration = segment.getDurationInMillis()
31
-
32
- const cmd = this.bedrockCommand
33
- const res = this.bedrockResponse
34
-
35
- this['request.max_tokens'] = cmd.maxTokens
36
- this['response.choices.finish_reason'] = res.finishReason
37
- this['request.temperature'] = cmd.temperature
38
- this['response.number_of_messages'] = (cmd.prompt.length ?? 0) + (res.completions.length ?? 0)
39
-
40
- this.timestamp = segment.timer.start
41
- this.setTokens(agent)
8
+ const LlmChatCompletionSummary = require('../chat-completion-summary')
9
+
10
+ module.exports = class AwsBedrockLlmChatCompletionSummary extends LlmChatCompletionSummary {
11
+ /**
12
+ *
13
+ * @param {object} params constructor parameters
14
+ * @param {Agent} params.agent New Relic agent instance
15
+ * @param {object} params.segment Current segment
16
+ * @param {object} params.transaction Current and active transaction
17
+ * @param {object} params.bedrockCommand AWS Bedrock Command object, represents the request
18
+ * @param {object} params.bedrockResponse AWS Bedrock Response object
19
+ * @param {boolean} [params.error] Set to `true` if an error occurred during creation call, omitted if no error occurred
20
+ */
21
+ constructor({ agent, segment, transaction, bedrockCommand, bedrockResponse, error }) {
22
+ super({ agent,
23
+ segment,
24
+ transaction,
25
+ error,
26
+ vendor: 'bedrock',
27
+ requestModel: bedrockCommand?.modelId,
28
+ requestId: bedrockResponse?.requestId,
29
+ responseModel: bedrockCommand?.modelId, // we can assume requestModel==responseModel in bedrock
30
+ temperature: bedrockCommand.temperature,
31
+ maxTokens: bedrockCommand.maxTokens,
32
+ numMsgs: (bedrockCommand.prompt.length ?? 0) + (bedrockResponse.completions.length ?? 0),
33
+ finishReason: bedrockResponse?.finishReason })
34
+
35
+ this.setTokens(agent, bedrockCommand, bedrockResponse)
42
36
  }
43
37
 
44
- setTokens(agent) {
38
+ setTokens(agent, bedrockCommand, bedrockResponse) {
45
39
  const tokenCB = agent?.llm?.tokenCountCallback
46
40
 
47
41
  // Prefer callback for prompt and completion tokens; if unavailable, fall back to response data.
48
42
  if (tokenCB) {
49
- const promptContent = this.bedrockCommand?.prompt?.map((msg) => msg.content).join(' ')
50
- const completionContent = this.bedrockResponse?.completions?.join(' ')
43
+ const promptContent = bedrockCommand?.prompt?.map((msg) => msg.content).join(' ')
44
+ const completionContent = bedrockResponse?.completions?.join(' ')
51
45
 
52
46
  this.setTokenUsageFromCallback(
53
47
  {
54
48
  tokenCB,
55
- reqModel: this.bedrockCommand.modelId,
56
- resModel: this.bedrockCommand.modelId,
49
+ reqModel: bedrockCommand.modelId,
50
+ resModel: bedrockCommand.modelId,
57
51
  promptContent,
58
52
  completionContent
59
53
  }
@@ -61,8 +55,8 @@ class LlmChatCompletionSummary extends LlmEvent {
61
55
  return
62
56
  }
63
57
 
64
- this.setTokensInResponse({ promptTokens: this.bedrockResponse.inputTokenCount, completionTokens: this.bedrockResponse.outputTokenCount, totalTokens: this.bedrockResponse.totalTokenCount })
58
+ this.setTokensInResponse({ promptTokens: bedrockResponse.inputTokenCount,
59
+ completionTokens: bedrockResponse.outputTokenCount,
60
+ totalTokens: bedrockResponse.totalTokenCount })
65
61
  }
66
62
  }
67
-
68
- module.exports = LlmChatCompletionSummary