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.
- package/NEWS.md +39 -0
- package/lib/instrumentation/aws-sdk/v3/bedrock.js +2 -1
- package/lib/instrumentation/when/index.js +0 -1
- package/lib/llm-events/aws-bedrock/chat-completion-message.js +5 -1
- package/lib/llm-events/google-genai/chat-completion-message.js +5 -0
- package/lib/llm-events/langchain/chat-completion-message.js +16 -10
- package/lib/llm-events/openai/chat-completion-summary.js +4 -2
- package/lib/metrics/names.js +3 -1
- package/lib/otel/traces/span-processor.js +9 -2
- package/lib/spans/span-event.js +2 -0
- package/lib/subscribers/ai-monitoring/base.js +113 -0
- package/lib/subscribers/ai-monitoring/chat.js +117 -0
- package/lib/subscribers/ai-monitoring/embedding.js +72 -0
- package/lib/subscribers/ai-monitoring/index.js +14 -0
- package/lib/subscribers/google-genai/embed-content.js +18 -44
- package/lib/subscribers/google-genai/generate-content-stream.js +16 -39
- package/lib/subscribers/google-genai/generate-content.js +40 -29
- package/lib/subscribers/ioredis/index.js +2 -12
- package/lib/subscribers/langchain/runnable-stream.js +33 -66
- package/lib/subscribers/langchain/runnable.js +73 -32
- package/lib/subscribers/langchain/tool-callback-manager.js +4 -3
- package/lib/subscribers/langchain/tool.js +9 -19
- package/lib/subscribers/langchain/vectorstore.js +6 -26
- package/lib/subscribers/mcp-sdk/client-request.js +6 -12
- package/lib/subscribers/middleware-wrapper.js +5 -1
- package/lib/subscribers/openai/chat.js +204 -41
- package/lib/subscribers/openai/client.js +10 -3
- package/lib/subscribers/openai/embeddings.js +22 -31
- package/lib/subscribers/pg/connect.js +6 -1
- package/lib/subscribers/pg/native-connect.js +1 -1
- package/lib/subscribers/pg/native-query.js +1 -1
- package/lib/subscribers/pg/query.js +6 -1
- package/lib/subscribers/utils.js +38 -0
- package/lib/transaction/trace/partial-trace.js +36 -4
- package/lib/transaction/trace/segment.js +19 -1
- package/package.json +1 -1
- package/lib/subscribers/google-genai/base.js +0 -123
- package/lib/subscribers/langchain/base.js +0 -164
- package/lib/subscribers/openai/base.js +0 -46
- 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
|
|
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 (
|
|
44
|
-
this.id = `${
|
|
43
|
+
if (runId) {
|
|
44
|
+
this.id = `${runId}-${sequence}`
|
|
45
45
|
} else {
|
|
46
|
-
this.id = `${this.id}-${
|
|
46
|
+
this.id = `${this.id}-${sequence}`
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
this.sequence =
|
|
50
|
-
this.completion_id =
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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 =
|
|
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
|
|
package/lib/metrics/names.js
CHANGED
|
@@ -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
|
|
package/lib/spans/span-event.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|