newrelic 13.10.0 → 13.11.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 (40) hide show
  1. package/NEWS.md +39 -0
  2. package/lib/instrumentation/aws-sdk/v3/bedrock.js +2 -1
  3. package/lib/instrumentation/when/index.js +0 -1
  4. package/lib/llm-events/aws-bedrock/chat-completion-message.js +5 -1
  5. package/lib/llm-events/google-genai/chat-completion-message.js +5 -0
  6. package/lib/llm-events/langchain/chat-completion-message.js +16 -10
  7. package/lib/llm-events/openai/chat-completion-summary.js +4 -2
  8. package/lib/metrics/names.js +3 -1
  9. package/lib/otel/traces/span-processor.js +9 -2
  10. package/lib/spans/span-event.js +2 -0
  11. package/lib/subscribers/ai-monitoring/base.js +113 -0
  12. package/lib/subscribers/ai-monitoring/chat.js +117 -0
  13. package/lib/subscribers/ai-monitoring/embedding.js +72 -0
  14. package/lib/subscribers/ai-monitoring/index.js +14 -0
  15. package/lib/subscribers/google-genai/embed-content.js +18 -44
  16. package/lib/subscribers/google-genai/generate-content-stream.js +16 -39
  17. package/lib/subscribers/google-genai/generate-content.js +40 -29
  18. package/lib/subscribers/ioredis/index.js +2 -12
  19. package/lib/subscribers/langchain/runnable-stream.js +33 -66
  20. package/lib/subscribers/langchain/runnable.js +73 -32
  21. package/lib/subscribers/langchain/tool-callback-manager.js +4 -3
  22. package/lib/subscribers/langchain/tool.js +9 -19
  23. package/lib/subscribers/langchain/vectorstore.js +6 -26
  24. package/lib/subscribers/mcp-sdk/client-request.js +6 -12
  25. package/lib/subscribers/middleware-wrapper.js +5 -1
  26. package/lib/subscribers/openai/chat.js +204 -41
  27. package/lib/subscribers/openai/client.js +10 -3
  28. package/lib/subscribers/openai/embeddings.js +22 -31
  29. package/lib/subscribers/pg/connect.js +6 -1
  30. package/lib/subscribers/pg/native-connect.js +1 -1
  31. package/lib/subscribers/pg/native-query.js +1 -1
  32. package/lib/subscribers/pg/query.js +6 -1
  33. package/lib/subscribers/utils.js +38 -0
  34. package/lib/transaction/trace/partial-trace.js +36 -4
  35. package/lib/transaction/trace/segment.js +19 -1
  36. package/package.json +1 -1
  37. package/lib/subscribers/google-genai/base.js +0 -123
  38. package/lib/subscribers/langchain/base.js +0 -164
  39. package/lib/subscribers/openai/base.js +0 -46
  40. package/lib/subscribers/openai/utils.js +0 -371
package/NEWS.md CHANGED
@@ -1,3 +1,42 @@
1
+ ### v13.11.0 (2026-01-29)
2
+
3
+ #### Features
4
+
5
+ * Added `timestamp` to AIM `LlmChatCompletionMessage` messages
6
+ * Added `timestamp` to AWS Bedrock `LlmChatCompletionMessage` ([#3685](https://github.com/newrelic/node-newrelic/pull/3685)) ([ea297c8](https://github.com/newrelic/node-newrelic/commit/ea297c86772c1ae377a16e636db8bbcf53ef1547))
7
+ * Added `timestamp` to Google Gen AI `LlmChatCompletionMessage` ([#3686](https://github.com/newrelic/node-newrelic/pull/3686)) ([f4dd09e](https://github.com/newrelic/node-newrelic/commit/f4dd09ec332c47396277f6aaeff2f6f15daad97a))
8
+ * Added `timestamp` to LangChain input `LlmChatCompletionMessage` ([#3658](https://github.com/newrelic/node-newrelic/pull/3658)) ([0bce235](https://github.com/newrelic/node-newrelic/commit/0bce235ce02f772bc089bf947afadf312c735c1b))
9
+ * Added timestamp to OpenAI `LlmChatCompletionSummary` ([#3689](https://github.com/newrelic/node-newrelic/pull/3689)) ([55b0310](https://github.com/newrelic/node-newrelic/commit/55b03100323eefb3578816cff69f2b919e4b7bdd))
10
+ * Additional support for partial granularity traces (Not available for production use)
11
+ * Added essential tracing mode for span links ([#3670](https://github.com/newrelic/node-newrelic/pull/3670)) ([83dfb84](https://github.com/newrelic/node-newrelic/commit/83dfb84b842e0245392425ce61e92a2789fcaf06))
12
+ * Updated `Supportability/Nodejs/PartialGranularity/NrIds/Dropped` metric to only keep track of spans that got dropped when `nr.ids` execeeded 63 spans. ([#3684](https://github.com/newrelic/node-newrelic/pull/3684)) ([9797cf3](https://github.com/newrelic/node-newrelic/commit/9797cf38aa2808b29da9623158edb221b1819548))
13
+
14
+ #### Bug fixes
15
+
16
+ * Added defensive code in `lib/subscribers/middleware-wrapper.js` to prevent crash when attempting to associate an error on an incoming http request ([#3697](https://github.com/newrelic/node-newrelic/pull/3697)) ([658e78b](https://github.com/newrelic/node-newrelic/commit/658e78ba564eae996d790bb3334bda6847c7e2bd))
17
+ * Updated `pg` instrumentation to properly capture the TraceSegment duration for promise based queries ([#3695](https://github.com/newrelic/node-newrelic/pull/3695)) ([0051697](https://github.com/newrelic/node-newrelic/commit/005169776a3f3fe9d988174857b3c96e6131aea3))
18
+
19
+ #### Code refactoring
20
+
21
+ * Added AIM abstract subscribers and updated all AIM subscribers to use them ([#3676](https://github.com/newrelic/node-newrelic/pull/3676)) ([08cd38b](https://github.com/newrelic/node-newrelic/commit/08cd38b5b34b4f1a2636ccc12d359484af6b3e83))
22
+
23
+ #### Documentation
24
+
25
+ * Updated compatibility report ([#3675](https://github.com/newrelic/node-newrelic/pull/3675)) ([cc898f4](https://github.com/newrelic/node-newrelic/commit/cc898f4d8b0f520e120f330ff9e90fe7baaebedc))
26
+
27
+ #### Miscellaneous chores
28
+
29
+ * Added TimedEvent limit of 100 ([#3683](https://github.com/newrelic/node-newrelic/pull/3683)) ([0d595d8](https://github.com/newrelic/node-newrelic/commit/0d595d8579b5a58e49908bc24321ce7b526ddff2))
30
+ * Added supportability metrics `Supportability/Nodejs/SpanEvent/<Links|Events>/Dropped` when limit of 100 is exceeded ([#3688](https://github.com/newrelic/node-newrelic/pull/3688)) ([a247d31](https://github.com/newrelic/node-newrelic/commit/a247d31fc9c24752be50a73d4ddfc7a088a68ce6))
31
+ * Removed eslint disable next line for `sonarjs/no-dead-store` ([#3692](https://github.com/newrelic/node-newrelic/pull/3692)) ([593fa15](https://github.com/newrelic/node-newrelic/commit/593fa15a033437b95fe375cf6034b8453c7d2b36))
32
+
33
+ #### Continuous Integration
34
+ * Fixed CI issue when sec-agent present ([#3677](https://github.com/newrelic/node-newrelic/pull/3677)) ([23da654](https://github.com/newrelic/node-newrelic/commit/23da6547e72f49bf798046330e8cfa74a0b4d2af))
35
+
36
+ #### Tests
37
+
38
+ * Assert that span events drop in partial traces ([#3680](https://github.com/newrelic/node-newrelic/pull/3680)) ([25e3781](https://github.com/newrelic/node-newrelic/commit/25e3781098343b9ca9e4aed0cd5c9e0f94266926))
39
+
1
40
  ### v13.10.0 (2026-01-20)
2
41
 
3
42
  #### Features
@@ -75,9 +75,10 @@ function isStreamingEnabled({ commandName, config }) {
75
75
  function recordEvent({ agent, type, msg }) {
76
76
  msg.serialize()
77
77
  const llmContext = extractLlmContext(agent)
78
+ const timestamp = msg?.timestamp ?? Date.now()
78
79
 
79
80
  agent.customEventAggregator.add([
80
- { type, timestamp: Date.now() },
81
+ { type, timestamp },
81
82
  Object.assign({}, msg, llmContext)
82
83
  ])
83
84
  }
@@ -296,7 +296,6 @@ module.exports = function initialize(shim, when) {
296
296
  ret = agent.tracer.bindFunction(fn, context, true).apply(this, arguments)
297
297
  } finally {
298
298
  if (ret && typeof ret.then === 'function') {
299
- // eslint-disable-next-line sonarjs/no-dead-store
300
299
  ret = ctx.next[symbols.context].continue(ret)
301
300
  }
302
301
  }
@@ -38,7 +38,7 @@ class LlmChatCompletionMessage extends LlmEvent {
38
38
  params = Object.assign({}, defaultParams, params)
39
39
  super(params)
40
40
 
41
- const { agent, content, isResponse, index, completionId, role } = params
41
+ const { agent, content, isResponse, index, completionId, role, segment } = params
42
42
  const recordContent = agent.config?.ai_monitoring?.record_content?.enabled
43
43
 
44
44
  this.is_response = isResponse
@@ -46,6 +46,10 @@ class LlmChatCompletionMessage extends LlmEvent {
46
46
  this.sequence = index
47
47
  this.content = recordContent === true ? content : undefined
48
48
  this.role = role
49
+ if (this.is_response === false) {
50
+ // Only record for request/input messages
51
+ this.timestamp = segment.timer.start
52
+ }
49
53
 
50
54
  this.#setId(index)
51
55
  this.setTokenCount(agent)
@@ -33,6 +33,11 @@ module.exports = class LlmChatCompletionMessage extends LlmEvent {
33
33
  this.content = this.is_response ? message?.parts?.[0]?.text : message
34
34
  }
35
35
 
36
+ // only add timestamp for request/input messages
37
+ if (this.is_response === false) {
38
+ this.timestamp = segment.timer.start
39
+ }
40
+
36
41
  this.setTokenCount(agent, request, response)
37
42
  }
38
43
 
@@ -38,19 +38,25 @@ class LangChainCompletionMessage extends LangChainEvent {
38
38
  constructor(params = defaultParams) {
39
39
  params = Object.assign({}, defaultParams, params)
40
40
  super(params)
41
- const { agent } = params
41
+ const { agent, runId, segment, sequence, role, content, completionId, isResponse } = params
42
42
 
43
- if (params.runId) {
44
- this.id = `${params.runId}-${params.sequence}`
43
+ if (runId) {
44
+ this.id = `${runId}-${sequence}`
45
45
  } else {
46
- this.id = `${this.id}-${params.sequence}`
46
+ this.id = `${this.id}-${sequence}`
47
47
  }
48
48
 
49
- this.sequence = params.sequence
50
- this.completion_id = params.completionId
51
- this.is_response = params.isResponse ?? false
52
- if (params.role) {
53
- this.role = params.role
49
+ this.sequence = sequence
50
+ this.completion_id = completionId
51
+
52
+ this.is_response = isResponse ?? false
53
+ if (this.is_response === false) {
54
+ // `timestamp` is only required for input/request messages.
55
+ this.timestamp = segment.timer.start
56
+ }
57
+
58
+ if (role) {
59
+ this.role = role
54
60
  } else {
55
61
  // As a backup, we can infer the role based on if it
56
62
  // is a response or not.
@@ -58,7 +64,7 @@ class LangChainCompletionMessage extends LangChainEvent {
58
64
  }
59
65
 
60
66
  if (agent.config.ai_monitoring.record_content.enabled === true) {
61
- this.content = params.content
67
+ this.content = content
62
68
  }
63
69
  }
64
70
  }
@@ -14,17 +14,19 @@ module.exports = class LlmChatCompletionSummary extends LlmEvent {
14
14
  this['request.temperature'] = request.temperature
15
15
 
16
16
  if (request?.input) {
17
- // `responses.create` logic
17
+ // `responses.create` api logic
18
18
  // `request.input` can be an array or a string.
19
19
  const requestLength = Array.isArray(request?.input) ? request.input.length : 1
20
20
  this['response.number_of_messages'] = requestLength + (response?.output?.length ?? 0)
21
21
  this['response.choices.finish_reason'] = response?.status
22
22
  } else {
23
- // `chat.completions.create` logic
23
+ // `chat.completions.create` api logic
24
24
  this['response.number_of_messages'] = request?.messages?.length + response?.choices?.length
25
25
  this['response.choices.finish_reason'] = response?.choices?.[0]?.finish_reason
26
26
  }
27
27
 
28
+ this.timestamp = segment.timer.start
29
+
28
30
  this.setTokens(agent, request, response)
29
31
  }
30
32
 
@@ -325,7 +325,9 @@ const SPAN_EVENTS = {
325
325
  SEEN: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'TotalEventsSeen',
326
326
  SENT: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'TotalEventsSent',
327
327
  DROPPED: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'Discarded',
328
- LIMIT: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'Limit'
328
+ LIMIT: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'Limit',
329
+ LINKS_DROPPED: SUPPORTABILITY.NODEJS + '/' + SPAN_EVENT_PREFIX + 'Links/Dropped',
330
+ EVENTS_DROPPED: SUPPORTABILITY.NODEJS + '/' + SPAN_EVENT_PREFIX + 'Events/Dropped'
329
331
  }
330
332
 
331
333
  const INFINITE_TRACING = {
@@ -23,6 +23,7 @@ const {
23
23
  transformTemplate
24
24
  } = require('./utils.js')
25
25
  const defaultLogger = require('#agentlib/logger.js').child({ component: 'span-processor' })
26
+ const { SPAN_EVENTS } = require('#agentlib/metrics/names.js')
26
27
 
27
28
  const {
28
29
  SPAN_STATUS_CODE
@@ -155,7 +156,10 @@ module.exports = class NrSpanProcessor {
155
156
  event: span.events[i],
156
157
  spanContext
157
158
  })
158
- segment.addTimedEvent(event)
159
+ const added = segment.addTimedEvent(event)
160
+ if (!added) {
161
+ this.agent.metrics.getOrCreateMetric(SPAN_EVENTS.EVENTS_DROPPED).incrementCallCount()
162
+ }
159
163
  }
160
164
  }
161
165
 
@@ -181,7 +185,10 @@ module.exports = class NrSpanProcessor {
181
185
  spanContext,
182
186
  timestamp
183
187
  })
184
- segment.addSpanLink(link)
188
+ const added = segment.addSpanLink(link)
189
+ if (!added) {
190
+ this.agent.metrics.getOrCreateMetric(SPAN_EVENTS.LINKS_DROPPED).incrementCallCount()
191
+ }
185
192
  }
186
193
  }
187
194
 
@@ -60,6 +60,8 @@ class SpanIntrinsics {
60
60
  this.duration = null
61
61
  this['nr.entryPoint'] = null
62
62
  this['nr.pg'] = null
63
+ this['nr.durations'] = null
64
+ this['nr.ids'] = null
63
65
  this['span.kind'] = null
64
66
  this.trustedParentId = null
65
67
  this.tracingVendors = null
@@ -0,0 +1,113 @@
1
+ /*
2
+ * Copyright 2026 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+ const Subscriber = require('../base')
8
+ const { DESTINATIONS } = require('#agentlib/config/attribute-filter.js')
9
+ const { extractLlmContext } = require('#agentlib/util/llm-utils.js')
10
+
11
+ class AiMonitoringSubscriber extends Subscriber {
12
+ /**
13
+ * @param {object} params constructor params object
14
+ * @param {object} params.agent A New Relic Node.js agent instance.
15
+ * @param {object} params.logger An agent logger instance.
16
+ * @param {string} params.packageName The package name being instrumented.
17
+ * This is what a developer would provide to the `require` function.
18
+ * @param {string} params.channelName A unique name for the diagnostics channel
19
+ * that will be created and monitored.
20
+ * @param {string} params.name name of segment for a given subscriber
21
+ * @param {string} params.trackingPrefix prefix for the tracking metric for a given subscriber
22
+ */
23
+ constructor({ agent, logger, packageName, channelName, name, trackingPrefix }) {
24
+ super({ agent, logger, packageName, channelName })
25
+ this.name = name
26
+ this.trackingPrefix = trackingPrefix
27
+ }
28
+
29
+ get enabled() {
30
+ return super.enabled && this.agent.config.ai_monitoring.enabled === true
31
+ }
32
+
33
+ set name(name) {
34
+ if (!name) {
35
+ throw new Error('subscriber must pass in `name`')
36
+ }
37
+ this._name = name
38
+ }
39
+
40
+ get name() {
41
+ return this._name
42
+ }
43
+
44
+ set trackingPrefix(prefix) {
45
+ if (!prefix) {
46
+ throw new Error('subscriber must pass in `trackingPrefix`')
47
+ }
48
+ this._prefix = prefix
49
+ }
50
+
51
+ get trackingPrefix() {
52
+ return this._prefix
53
+ }
54
+
55
+ /**
56
+ * Increments the tracking metric and sets the llm attribute on transactions
57
+ *
58
+ * @param {object} params input params
59
+ * @param {Transaction} params.ctx active context
60
+ * @param {string} params.version package version
61
+ */
62
+ addLlmMeta({ ctx, version }) {
63
+ if (!ctx?.transaction) {
64
+ return
65
+ }
66
+
67
+ const transaction = ctx.transaction
68
+ const trackingMetric = `${this.trackingPrefix}/${version}`
69
+ this.agent.metrics.getOrCreateMetric(trackingMetric).incrementCallCount()
70
+ transaction.trace.attributes.addAttribute(DESTINATIONS.TRANS_EVENT, 'llm', true)
71
+ }
72
+
73
+ /**
74
+ * Enqueues a LLM event to the custom event aggregator
75
+ *
76
+ * @param {object} params input params
77
+ * @param {string} params.type LLM event type
78
+ * @param {object} params.msg LLM event
79
+ */
80
+ recordEvent({ type, msg }) {
81
+ const llmContext = extractLlmContext(this.agent)
82
+ // prefer timestamp on event if present, otherwise use `Date.now`
83
+ const timestamp = msg?.timestamp ?? Date.now()
84
+ this.agent.customEventAggregator.add([
85
+ { type, timestamp },
86
+ Object.assign({}, msg, llmContext)
87
+ ])
88
+ }
89
+
90
+ /**
91
+ * Defines the common handler for all AIM calls.
92
+ * All of these calls simply create a segment with a name
93
+ * The `this.name` must be defined in the inherited subscriber
94
+ * @param {object} data event passed to handler
95
+ * @param {Context} ctx context passed to handler
96
+ * @returns {Context} either new context or existing
97
+ */
98
+ handler(data, ctx) {
99
+ if (!this.enabled) {
100
+ this.logger.debug('`ai_monitoring.enabled` is set to false, not creating segment.')
101
+ return ctx
102
+ }
103
+
104
+ this.addLlmMeta({ ctx, version: data.moduleVersion })
105
+
106
+ return this.createSegment({
107
+ name: this.name,
108
+ ctx
109
+ })
110
+ }
111
+ }
112
+
113
+ module.exports = AiMonitoringSubscriber
@@ -0,0 +1,117 @@
1
+ /*
2
+ * Copyright 2026 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+ const AiMonitoringSubscriber = require('./base')
8
+ const LlmErrorMessage = require('#agentlib/llm-events/error-message.js')
9
+
10
+ class AiMonitoringChatSubscriber extends AiMonitoringSubscriber {
11
+ /**
12
+ * @param {object} params constructor params object
13
+ * @param {object} params.agent A New Relic Node.js agent instance.
14
+ * @param {object} params.logger An agent logger instance.
15
+ * @param {string} params.packageName The package name being instrumented.
16
+ * This is what a developer would provide to the `require` function.
17
+ * @param {string} params.channelName A unique name for the diagnostics channel
18
+ * that will be created and monitored.
19
+ * @param {string} params.name name of segment for a given subscriber
20
+ * @param {string} params.trackingPrefix prefix for the tracking metric for a given subscriber
21
+ */
22
+ constructor({ agent, logger, packageName, channelName, name, trackingPrefix }) {
23
+ super({ agent, logger, packageName, channelName, name, trackingPrefix })
24
+ }
25
+
26
+ get streamingEnabled() {
27
+ return this.agent.config.ai_monitoring.streaming.enabled === true
28
+ }
29
+
30
+ /**
31
+ * Function that must be implemented by inherited subscriber to create an llm completion message.
32
+ *
33
+ * @param {object} params to function
34
+ * @param {Context} params.ctx active context
35
+ * @param {object} params.request request made to method on a given llm library
36
+ * @param {object} params.response response from method on a given llm library
37
+ * @param {object|string} params.message the message object/string used to create llm completion message
38
+ * @param {string} params.completionId the id of the llm completion summary for a given conversation
39
+ * @param {number} params.index index of message for a given conversation
40
+ * returns {object} a llm completion message instance for the given LLM
41
+ */
42
+ createCompletionMessage({ ctx, request, response, message, completionId, index }) {
43
+ throw new Error('createCompletionMessage must be implemented by your subscriber')
44
+ }
45
+
46
+ /**
47
+ * Function that must be implemented by inherited subscriber to create the llm completion summary.
48
+ *
49
+ * @param {object} params to function
50
+ * @param {Context} params.ctx active context
51
+ * @param {object} params.request request made to method on a given llm library
52
+ * @param {object} params.response response from method on a given llm library
53
+ * @param {object} params.err error if present
54
+ * @param {object} params.metadata used only for langchain events at the moment
55
+ * @param {Array} params.tags used only for langchain events at the moment
56
+ * returns {object} a llm completion summary instance for the given LLM
57
+ */
58
+ createCompletionSummary({ ctx, request, response, err, metadata, tags }) {
59
+ throw new Error('createCompletionSummary must be implemented by your subscriber')
60
+ }
61
+
62
+ /**
63
+ * Function that must be implemented by inherited subscriber to retrieve the relevant
64
+ * messages for a given chat conversation.
65
+ *
66
+ * @param {object} params to function
67
+ * @param {object} params.request request to instrumented function
68
+ * @param {object} params.response response to instrumented function
69
+ * returns {Array} an array of relevant messages used to construct llm completion messages
70
+ */
71
+ getMessages({ request, response }) {
72
+ throw new Error('getMessages must be implemented by your subscriber')
73
+ }
74
+
75
+ /**
76
+ * Function to create both llm completion summary and messages
77
+ * as well as assign errors to transaction if present
78
+ * @param {object} params to function
79
+ * @param {Context} params.ctx active context
80
+ * @param {object} params.request request made to method on a given llm library
81
+ * @param {object} params.response response from method on a given llm library
82
+ * @param {object} params.err error if present
83
+ * @param {object} params.metadata used only for langchain events at the moment
84
+ * @param {Array} params.tags used only for langchain events at the moment
85
+ */
86
+ recordChatCompletionEvents({ ctx, request, response, err, metadata = {}, tags = [] }) {
87
+ if (!this.enabled) {
88
+ this.logger.debug('config.ai_monitoring.enabled is set to false, not creating chat completion events.')
89
+ return
90
+ }
91
+
92
+ if (!(ctx?.segment || ctx?.transaction)) {
93
+ this.logger.debug('Empty context, not creating completion events.')
94
+ return
95
+ }
96
+
97
+ // Explicitly end segment to provide consistent duration
98
+ // for both LLM events and the segment
99
+ ctx.segment.end()
100
+ const completionSummary = this.createCompletionSummary({ ctx, request, response, err, metadata, tags })
101
+ this.recordEvent({ type: 'LlmChatCompletionSummary', msg: completionSummary })
102
+
103
+ const messages = this.getMessages({ request, response })
104
+ for (let i = 0; i < messages.length; i++) {
105
+ const message = messages[i]
106
+ const completionMessage = this.createCompletionMessage({ ctx, completionId: completionSummary.id, index: i, message, request, response })
107
+ this.recordEvent({ type: 'LlmChatCompletionMessage', msg: completionMessage })
108
+ }
109
+
110
+ if (err) {
111
+ const llmError = new LlmErrorMessage({ cause: err, summary: completionSummary, response })
112
+ this.agent.errors.add(ctx.transaction, err, llmError)
113
+ }
114
+ }
115
+ }
116
+
117
+ module.exports = AiMonitoringChatSubscriber
@@ -0,0 +1,72 @@
1
+ /*
2
+ * Copyright 2026 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+ const AiMonitoringSubscriber = require('./base')
8
+ const LlmErrorMessage = require('#agentlib/llm-events/error-message.js')
9
+
10
+ class AiMonitoringEmbeddingSubscriber extends AiMonitoringSubscriber {
11
+ /**
12
+ * @param {object} params constructor params object
13
+ * @param {object} params.agent A New Relic Node.js agent instance.
14
+ * @param {object} params.logger An agent logger instance.
15
+ * @param {string} params.packageName The package name being instrumented.
16
+ * This is what a developer would provide to the `require` function.
17
+ * @param {string} params.channelName A unique name for the diagnostics channel
18
+ * that will be created and monitored.
19
+ * @param {string} params.name name of segment for a given subscriber
20
+ * @param {string} params.trackingPrefix prefix for the tracking metric for a given subscriber
21
+ */
22
+ constructor({ agent, logger, packageName, channelName, name, trackingPrefix }) {
23
+ super({ agent, logger, packageName, channelName, name, trackingPrefix })
24
+ }
25
+
26
+ /**
27
+ * Function that must be implemented by inherited subscriber to create an llm embedding.
28
+ *
29
+ * @param {object} params to function
30
+ * @param {Context} params.ctx active context
31
+ * @param {object} params.request request made to method on a given llm library
32
+ * @param {object} params.response response from method on a given llm library
33
+ * @param {object} params.err error if present
34
+ * returns {object} a llm embedding instance for the given LLM
35
+ */
36
+ createEmbedding({ ctx, request, response, err }) {
37
+ throw new Error('createEmbedding must be implemented by your subscriber')
38
+ }
39
+
40
+ /**
41
+ * Function to create embedding as well as assign errors to transaction if present
42
+ * @param {object} params to function
43
+ * @param {Context} params.ctx active context
44
+ * @param {object} params.request request made to method on a given llm library
45
+ * @param {object} params.response response from method on a given llm library
46
+ * @param {object} params.err error if present
47
+ */
48
+ recordEmbedding({ ctx, request, response, err }) {
49
+ if (!this.enabled) {
50
+ this.logger.debug('config.ai_monitoring.enabled is set to false, not creating embedding event.')
51
+ return
52
+ }
53
+
54
+ if (!(ctx?.segment || ctx?.transaction)) {
55
+ this.logger.debug('Empty context, not creating embedding event.')
56
+ return
57
+ }
58
+
59
+ // Explicitly end segment to provide consistent duration
60
+ // for both LLM events and the segment
61
+ ctx.segment.end()
62
+ const embedding = this.createEmbedding({ ctx, request, response, err })
63
+ this.recordEvent({ type: 'LlmEmbedding', msg: embedding })
64
+
65
+ if (err) {
66
+ const llmError = new LlmErrorMessage({ cause: err, embedding, response })
67
+ this.agent.errors.add(ctx.transaction, err, llmError)
68
+ }
69
+ }
70
+ }
71
+
72
+ module.exports = AiMonitoringEmbeddingSubscriber
@@ -0,0 +1,14 @@
1
+ /*
2
+ * Copyright 2026 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ const AiMonitoringChatSubscriber = require('./chat')
7
+ const AiMonitoringEmbeddingSubscriber = require('./embedding')
8
+ const AiMonitoringSubscriber = require('./base')
9
+
10
+ module.exports = {
11
+ AiMonitoringSubscriber,
12
+ AiMonitoringChatSubscriber,
13
+ AiMonitoringEmbeddingSubscriber
14
+ }
@@ -3,67 +3,41 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
 
6
- const GoogleGenAISubscriber = require('./base')
6
+ const { AiMonitoringEmbeddingSubscriber } = require('../ai-monitoring')
7
7
  const { AI } = require('../../../lib/metrics/names')
8
8
  const { GEMINI } = AI
9
- const {
10
- LlmErrorMessage,
11
- LlmEmbedding
12
- } = require('../../../lib/llm-events/google-genai')
9
+ const { LlmEmbedding } = require('#agentlib/llm-events/google-genai/index.js')
13
10
 
14
- class GoogleGenAIEmbedContentSubscriber extends GoogleGenAISubscriber {
11
+ class GoogleGenAIEmbedContentSubscriber extends AiMonitoringEmbeddingSubscriber {
15
12
  constructor ({ agent, logger }) {
16
- super({ agent, logger, channelName: 'nr_embedContent' })
13
+ super({ agent, logger, packageName: '@google/genai', channelName: 'nr_embedContent', trackingPrefix: GEMINI.TRACKING_PREFIX, name: GEMINI.EMBEDDING })
14
+ this.events = ['asyncEnd']
17
15
  }
18
16
 
19
- handler(data, ctx) {
20
- if (!this.enabled) {
21
- this.logger.debug('`ai_monitoring.enabled` is set to false, not creating segment.')
22
- return ctx
23
- }
24
- const segment = this.agent.tracer.createSegment({
25
- name: GEMINI.EMBEDDING,
26
- parent: ctx.segment,
27
- transaction: ctx.transaction
17
+ createEmbedding({ ctx, request, response = {}, err }) {
18
+ const { segment, transaction } = ctx
19
+ return new LlmEmbedding({
20
+ agent: this.agent,
21
+ segment,
22
+ transaction,
23
+ request,
24
+ response,
25
+ withError: !!err
28
26
  })
29
- return ctx.enterSegment({ segment })
30
27
  }
31
28
 
32
29
  asyncEnd(data) {
33
- const agent = this.agent
34
- if (!this.enabled) {
35
- this.logger.debug('`ai_monitoring.enabled` is set to false, not recording Llm events.')
36
- return
37
- }
38
- const ctx = agent.tracer.getContext()
39
- if (!ctx?.segment || !ctx?.transaction) {
40
- return
41
- }
42
- this.addLlmMeta({ transaction: ctx.transaction, version: data.moduleVersion })
30
+ const ctx = this.agent.tracer.getContext()
43
31
  // If we get an error, it is possible that `response = null`.
44
32
  // In that case, we define it to be an empty object.
45
33
  const { result: response = {}, arguments: args, error: err } = data
46
34
  const [request] = args
47
-
48
- // Explicitly end segment to get consistent duration
49
- // for both LLM events and the segment
50
- ctx.segment.end()
51
-
52
- const embedding = new LlmEmbedding({
53
- agent,
54
- segment: ctx.segment,
55
- transaction: ctx.transaction,
35
+ this.recordEmbedding({
36
+ ctx,
56
37
  request,
57
38
  response,
58
- withError: err != null
39
+ err
59
40
  })
60
-
61
- this.recordEvent({ type: 'LlmEmbedding', msg: embedding })
62
-
63
- if (err) {
64
- const llmError = new LlmErrorMessage({ cause: err, embedding, response })
65
- agent.errors.add(ctx.transaction, err, llmError)
66
- }
67
41
  }
68
42
  }
69
43