newrelic 13.9.2 → 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 +66 -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-message.js +6 -1
- package/lib/llm-events/openai/chat-completion-summary.js +4 -2
- package/lib/metrics/names.js +5 -2
- package/lib/otel/setup.js +14 -10
- package/lib/otel/traces/span-processor.js +9 -2
- package/lib/shimmer.js +1 -1
- 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/base.js +32 -14
- package/lib/subscribers/dc-base.js +47 -6
- package/lib/subscribers/fastify/index.js +5 -2
- 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 -44
- package/lib/subscribers/openai/client.js +20 -7
- package/lib/subscribers/openai/embeddings.js +22 -34
- 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/record-supportability-metric.js +39 -0
- package/lib/subscribers/resolve-package-version.js +84 -0
- package/lib/subscribers/utils.js +38 -0
- package/lib/tracking-packages.js +1 -9
- package/lib/transaction/trace/partial-trace.js +62 -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 -358
|
@@ -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
|
+
}
|
package/lib/subscribers/base.js
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
8
8
|
const { tracingChannel } = require('node:diagnostics_channel')
|
|
9
9
|
const cat = require('#agentlib/util/cat.js')
|
|
10
|
+
const recordSupportabilityMetric = require('./record-supportability-metric.js')
|
|
11
|
+
|
|
10
12
|
// Used for the `traceCallback` work.
|
|
11
13
|
// This can be removed when we add true support into orchestrion
|
|
12
14
|
const makeCall = (fn) => (...args) => fn.call(...args)
|
|
@@ -28,38 +30,45 @@ const ArrayPrototypeSplice = makeCall(Array.prototype.splice)
|
|
|
28
30
|
|
|
29
31
|
/**
|
|
30
32
|
* @property {object} agent A New Relic Node.js agent instance.
|
|
31
|
-
* @property {
|
|
33
|
+
* @property {TracingChannel} channel The tracing channel instance this subscriber will be monitoring.
|
|
34
|
+
* @property {string} channelName A unique name for the diagnostics channel
|
|
35
|
+
* that will be registered.
|
|
32
36
|
* @property {object} config The agent configuration object.
|
|
37
|
+
* @property {string} id A unique identifier for the subscriber, combining the prefix, package
|
|
38
|
+
* name, and channel name.
|
|
39
|
+
* @property {object} logger An agent logger instance.
|
|
33
40
|
* @property {string} packageName The name of the module being instrumented.
|
|
34
41
|
* This is the same string one would pass to the `require` function.
|
|
35
|
-
* @property {
|
|
36
|
-
*
|
|
42
|
+
* @property {AsyncLocalStorage} store The async local storage instance used for context management.
|
|
43
|
+
* @property {number} [callback=null] Position of callback if it needs to be wrapped for instrumentation.
|
|
44
|
+
* -1 means last argument.
|
|
37
45
|
* @property {string[]} [events=[]] Set of tracing channel event names to
|
|
38
46
|
* register handlers for. For any name in the set, a corresponding method
|
|
39
47
|
* must exist on the subscriber instance. The method will be passed the
|
|
40
48
|
* event object. Possible event names are `start`, `end`, `asyncStart`,
|
|
41
49
|
* `asyncEnd`, and `error`.
|
|
42
|
-
*
|
|
43
|
-
* See {@link https://nodejs.org/api/diagnostics_channel.html#class-tracingchannel}
|
|
44
|
-
* @property {boolean} [opaque=false] If true, any children segments will not be created.
|
|
50
|
+
* See {@link https://nodejs.org/api/diagnostics_channel.html#class-tracingchannel}
|
|
45
51
|
* @property {boolean} [internal=false] If true, any children segments from the same library
|
|
46
52
|
* will not be created.
|
|
53
|
+
* @property {boolean} [opaque=false] If true, any children segments will not be created.
|
|
47
54
|
* @property {string} [prefix='orchestrion:'] String to prepend to diagnostics
|
|
48
55
|
* channel event names. This provides a namespace for the events we are
|
|
49
56
|
* injecting into a module.
|
|
50
|
-
* @property {boolean} [requireActiveTx=true] If true, the subscriber will only handle events
|
|
51
|
-
* when there is an active transaction.
|
|
52
57
|
* @property {boolean} [propagateContext=false] If true, it will bind `asyncStart` to the store
|
|
53
58
|
* and re-propagate the active context. It will also attach the `transaction` to the event in
|
|
54
59
|
* `start.bindStore`. This is used for functions that queue async code and context is lost.
|
|
55
|
-
* @property {
|
|
56
|
-
*
|
|
57
|
-
* @property {
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
60
|
+
* @property {boolean} [requireActiveTx=true] If true, the subscriber will only handle events
|
|
61
|
+
* when there is an active transaction.
|
|
62
|
+
* @property {object} [targetModuleMeta] Defines the target module's name and
|
|
63
|
+
* version string, i.e. is an object `{ name, version }`. This is only necessary
|
|
64
|
+
* when target instrumentation can surface an unexpected name for the
|
|
65
|
+
* `packageName` property. For example, `express` uses multiple modules to
|
|
66
|
+
* compose its core functionality. We want to track things under the `express`
|
|
67
|
+
* name, but `packageName` will be set to `router` is most cases.
|
|
61
68
|
*/
|
|
62
69
|
class Subscriber {
|
|
70
|
+
#usageMetricRecorded = false
|
|
71
|
+
|
|
63
72
|
/**
|
|
64
73
|
* @param {SubscriberParams} params the subscriber constructor params
|
|
65
74
|
*/
|
|
@@ -234,6 +243,15 @@ class Subscriber {
|
|
|
234
243
|
* @returns {Context} The context after processing the event
|
|
235
244
|
*/
|
|
236
245
|
const handler = (data) => {
|
|
246
|
+
if (this.#usageMetricRecorded === false) {
|
|
247
|
+
recordSupportabilityMetric({
|
|
248
|
+
agent: this.agent,
|
|
249
|
+
moduleName: this.packageName,
|
|
250
|
+
moduleVersion: data.moduleVersion
|
|
251
|
+
})
|
|
252
|
+
this.#usageMetricRecorded = true
|
|
253
|
+
}
|
|
254
|
+
|
|
237
255
|
// only wrap the callback if a subscriber has a callback property defined
|
|
238
256
|
if (this.callback !== null) {
|
|
239
257
|
this.traceCallback(this.callback, data)
|
|
@@ -4,8 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
'use strict'
|
|
7
|
+
|
|
7
8
|
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
8
9
|
const dc = require('node:diagnostics_channel')
|
|
10
|
+
const recordSupportabilityMetric = require('./record-supportability-metric.js')
|
|
11
|
+
const resolvePackageVersion = require('./resolve-package-version.js')
|
|
9
12
|
|
|
10
13
|
/**
|
|
11
14
|
* The baseline parameters available to all subscribers.
|
|
@@ -15,6 +18,13 @@ const dc = require('node:diagnostics_channel')
|
|
|
15
18
|
* @property {object} logger An agent logger instance.
|
|
16
19
|
* @property {string} packageName The package name being instrumented.
|
|
17
20
|
* This is what a developer would provide to the `require` function.
|
|
21
|
+
* @property {boolean} [skipUsageMetricRecording=false] When set to `true`, the
|
|
22
|
+
* instrumentation will not attempt to record the usage metric. This is useful
|
|
23
|
+
* when the module being instrumented is also being instrumented via the
|
|
24
|
+
* Orchestrion based subscriber system. It is much cheaper to record the metric
|
|
25
|
+
* via Orchestrion based subscribers than through this direct diagnostics
|
|
26
|
+
* channel method (Orchestrion provides the module version, whereas we have
|
|
27
|
+
* to perform expensive operations here to get the same information).
|
|
18
28
|
*/
|
|
19
29
|
|
|
20
30
|
/**
|
|
@@ -34,19 +44,25 @@ const dc = require('node:diagnostics_channel')
|
|
|
34
44
|
* This is the same string one would pass to the `require` function.
|
|
35
45
|
*/
|
|
36
46
|
class Subscriber {
|
|
47
|
+
#usageMetricRecorded = false
|
|
48
|
+
|
|
37
49
|
/**
|
|
38
50
|
* @param {SubscriberParams} params to function
|
|
39
51
|
*/
|
|
40
|
-
constructor({ agent, logger, packageName }) {
|
|
52
|
+
constructor({ agent, logger, packageName, skipUsageMetricRecording = false }) {
|
|
41
53
|
this.agent = agent
|
|
42
54
|
this.logger = logger.child({ component: `${packageName}-subscriber` })
|
|
43
55
|
this.config = agent.config
|
|
44
56
|
this.id = packageName
|
|
57
|
+
|
|
58
|
+
if (skipUsageMetricRecording === true) {
|
|
59
|
+
this.#usageMetricRecorded = true
|
|
60
|
+
}
|
|
45
61
|
}
|
|
46
62
|
|
|
47
63
|
set channels(channels) {
|
|
48
64
|
if (!Array.isArray(channels)) {
|
|
49
|
-
throw new Error('channels must be a collection of with
|
|
65
|
+
throw new Error('channels must be a collection of objects with properties channel and hook')
|
|
50
66
|
}
|
|
51
67
|
this._channels = channels
|
|
52
68
|
}
|
|
@@ -73,18 +89,43 @@ class Subscriber {
|
|
|
73
89
|
|
|
74
90
|
subscribe() {
|
|
75
91
|
for (let index = 0; index < this.channels.length; index++) {
|
|
92
|
+
const chan = this.channels[index]
|
|
76
93
|
const { hook, channel } = this.channels[index]
|
|
77
94
|
const boundHook = hook.bind(this)
|
|
78
|
-
|
|
79
|
-
|
|
95
|
+
chan.boundHook = boundHook
|
|
96
|
+
chan.eventHandler = (message, name) => {
|
|
97
|
+
this.#supportability()
|
|
98
|
+
boundHook(message, name)
|
|
99
|
+
}
|
|
100
|
+
dc.subscribe(channel, chan.eventHandler)
|
|
80
101
|
}
|
|
81
102
|
}
|
|
82
103
|
|
|
83
104
|
unsubscribe() {
|
|
84
105
|
for (let index = 0; index < this.channels.length; index++) {
|
|
85
|
-
const { channel,
|
|
86
|
-
dc.unsubscribe(channel,
|
|
106
|
+
const { channel, eventHandler } = this.channels[index]
|
|
107
|
+
dc.unsubscribe(channel, eventHandler)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Since this class subscribes to diagnostics channels natively published by
|
|
113
|
+
* target modules, we do not get the package metadata that Orchestrion
|
|
114
|
+
* provides in its channel events. So we have to try and find the package
|
|
115
|
+
* manifest and get the version out of it in order to record our
|
|
116
|
+
* supportability metric.
|
|
117
|
+
*/
|
|
118
|
+
#supportability() {
|
|
119
|
+
if (this.#usageMetricRecorded === true) {
|
|
120
|
+
return
|
|
87
121
|
}
|
|
122
|
+
const version = resolvePackageVersion(this.id)
|
|
123
|
+
recordSupportabilityMetric({
|
|
124
|
+
agent: this.agent,
|
|
125
|
+
moduleName: this.id,
|
|
126
|
+
moduleVersion: version
|
|
127
|
+
})
|
|
128
|
+
this.#usageMetricRecorded = true
|
|
88
129
|
}
|
|
89
130
|
}
|
|
90
131
|
|
|
@@ -10,7 +10,7 @@ const MiddlewareWrapper = require('../middleware-wrapper')
|
|
|
10
10
|
|
|
11
11
|
class FastifyInitialization extends DcBase {
|
|
12
12
|
constructor({ agent, logger }) {
|
|
13
|
-
super({ agent, logger, packageName: 'fastify' })
|
|
13
|
+
super({ agent, logger, packageName: 'fastify', skipUsageMetricRecording: true })
|
|
14
14
|
this.channels = [
|
|
15
15
|
{ channel: initChannel, hook: this.handler }
|
|
16
16
|
]
|
|
@@ -24,7 +24,10 @@ class FastifyInitialization extends DcBase {
|
|
|
24
24
|
return
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
routeOptions.handler = self.wrapper.wrap({
|
|
27
|
+
routeOptions.handler = self.wrapper.wrap({
|
|
28
|
+
handler: routeOptions.handler,
|
|
29
|
+
route: routeOptions.path
|
|
30
|
+
})
|
|
28
31
|
})
|
|
29
32
|
}
|
|
30
33
|
}
|
|
@@ -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
|
|
|
@@ -11,32 +11,19 @@ class GoogleGenAIGenerateContentStreamSubscriber extends GoogleGenAIGenerateCont
|
|
|
11
11
|
super({ agent, logger, channelName: 'nr_generateContentStreamInternal' })
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
get enabled() {
|
|
15
|
-
return super.enabled && this.streamingEnabled
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
get streamingEnabled() {
|
|
19
|
-
return this.agent.config.ai_monitoring.streaming.enabled
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
handler(data, ctx) {
|
|
23
|
-
if (!this.enabled) {
|
|
24
|
-
this.logger.debug('`ai_monitoring.enabled` or `ai_monitoring.streaming.enabled` is set to false, not creating segment.')
|
|
25
|
-
return ctx
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return super.handler(data, ctx)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
14
|
/**
|
|
32
15
|
* Instruments the streaming response by wrapping the next function.
|
|
33
16
|
* @param {object} params function params
|
|
34
17
|
* @param {object} params.request the original request object
|
|
35
18
|
* @param {object} params.response the original response object
|
|
36
|
-
* @param {
|
|
37
|
-
* @param {Transaction} params.transaction the active transaction
|
|
19
|
+
* @param {object} params.ctx active context
|
|
38
20
|
*/
|
|
39
|
-
instrumentStream({ request, response,
|
|
21
|
+
instrumentStream({ request, response, ctx }) {
|
|
22
|
+
if (!(ctx?.segment || ctx?.transaction)) {
|
|
23
|
+
this.logger.debug('Empty context, not instrumenting stream')
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
40
27
|
const self = this
|
|
41
28
|
const originalNext = response.next
|
|
42
29
|
let isDone = false
|
|
@@ -65,7 +52,7 @@ class GoogleGenAIGenerateContentStreamSubscriber extends GoogleGenAIGenerateCont
|
|
|
65
52
|
} finally {
|
|
66
53
|
// Update segment duration since we want to extend the
|
|
67
54
|
// time it took to handle the stream
|
|
68
|
-
segment.touch()
|
|
55
|
+
ctx.segment.touch()
|
|
69
56
|
|
|
70
57
|
// also need to enter this block if there was an
|
|
71
58
|
// error, so we can record it
|
|
@@ -73,9 +60,8 @@ class GoogleGenAIGenerateContentStreamSubscriber extends GoogleGenAIGenerateCont
|
|
|
73
60
|
if (cachedResult?.candidates?.[0]?.content?.parts) {
|
|
74
61
|
cachedResult.candidates[0].content.parts[0].text = entireMessage
|
|
75
62
|
}
|
|
76
|
-
self.
|
|
77
|
-
|
|
78
|
-
transaction,
|
|
63
|
+
self.recordChatCompletionEvents({
|
|
64
|
+
ctx,
|
|
79
65
|
request,
|
|
80
66
|
response: cachedResult,
|
|
81
67
|
err
|
|
@@ -87,7 +73,11 @@ class GoogleGenAIGenerateContentStreamSubscriber extends GoogleGenAIGenerateCont
|
|
|
87
73
|
}
|
|
88
74
|
|
|
89
75
|
asyncEnd(data) {
|
|
90
|
-
// Check config
|
|
76
|
+
// Check config to see if ai_monitoring is still enabled
|
|
77
|
+
if (!this.enabled) {
|
|
78
|
+
this.logger.debug('`ai_monitoring.enabled` is set to false, stream will not be instrumented.')
|
|
79
|
+
return
|
|
80
|
+
}
|
|
91
81
|
if (!this.streamingEnabled) {
|
|
92
82
|
this.logger.warn(
|
|
93
83
|
'`ai_monitoring.streaming.enabled` is set to `false`, stream will not be instrumented.'
|
|
@@ -95,29 +85,16 @@ class GoogleGenAIGenerateContentStreamSubscriber extends GoogleGenAIGenerateCont
|
|
|
95
85
|
this.agent.metrics.getOrCreateMetric(AI.STREAMING_DISABLED).incrementCallCount()
|
|
96
86
|
return
|
|
97
87
|
}
|
|
98
|
-
if (!this.enabled) {
|
|
99
|
-
this.logger.debug('`ai_monitoring.enabled` is set to false, not recording Llm events.')
|
|
100
|
-
return
|
|
101
|
-
}
|
|
102
88
|
|
|
103
89
|
// Instrument the stream
|
|
104
90
|
const ctx = this.agent.tracer.getContext()
|
|
105
|
-
if (!ctx?.segment || !ctx?.transaction) {
|
|
106
|
-
return
|
|
107
|
-
}
|
|
108
91
|
const { result: response, arguments: args } = data
|
|
109
92
|
const [request] = args
|
|
110
93
|
|
|
111
94
|
this.instrumentStream({
|
|
95
|
+
ctx,
|
|
112
96
|
request,
|
|
113
97
|
response,
|
|
114
|
-
segment: ctx.segment,
|
|
115
|
-
transaction: ctx.transaction,
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
this.addLlmMeta({
|
|
119
|
-
transaction: ctx.transaction,
|
|
120
|
-
version: data.moduleVersion,
|
|
121
98
|
})
|
|
122
99
|
}
|
|
123
100
|
}
|
|
@@ -3,51 +3,62 @@
|
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const { AiMonitoringChatSubscriber } = require('../ai-monitoring')
|
|
7
7
|
const { AI } = require('../../metrics/names')
|
|
8
8
|
const { GEMINI } = AI
|
|
9
|
+
const { LlmChatCompletionSummary, LlmChatCompletionMessage } = require('#agentlib/llm-events/google-genai/index.js')
|
|
9
10
|
|
|
10
|
-
class GoogleGenAIGenerateContentSubscriber extends
|
|
11
|
+
class GoogleGenAIGenerateContentSubscriber extends AiMonitoringChatSubscriber {
|
|
11
12
|
constructor({ agent, logger, channelName = 'nr_generateContentInternal' }) {
|
|
12
|
-
super({ agent, logger, channelName })
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
handler(data, ctx) {
|
|
16
|
-
if (!this.enabled) {
|
|
17
|
-
this.logger.debug('`ai_monitoring.enabled` is set to false, not creating segment.')
|
|
18
|
-
return ctx
|
|
19
|
-
}
|
|
20
|
-
const segment = this.agent.tracer.createSegment({
|
|
21
|
-
name: GEMINI.COMPLETION,
|
|
22
|
-
parent: ctx.segment,
|
|
23
|
-
transaction: ctx.transaction
|
|
24
|
-
})
|
|
25
|
-
return ctx.enterSegment({ segment })
|
|
13
|
+
super({ agent, logger, channelName, packageName: '@google/genai', name: GEMINI.COMPLETION, trackingPrefix: GEMINI.TRACKING_PREFIX })
|
|
14
|
+
this.events = ['asyncEnd']
|
|
26
15
|
}
|
|
27
16
|
|
|
28
17
|
asyncEnd(data) {
|
|
29
|
-
if (!this.enabled) {
|
|
30
|
-
this.logger.debug('`ai_monitoring.enabled` is set to false, not recording Llm events.')
|
|
31
|
-
return
|
|
32
|
-
}
|
|
33
18
|
const ctx = this.agent.tracer.getContext()
|
|
34
|
-
if (!ctx?.segment || !ctx?.transaction) {
|
|
35
|
-
return
|
|
36
|
-
}
|
|
37
19
|
const { result: response, arguments: args, error: err } = data
|
|
38
20
|
const [request] = args
|
|
39
21
|
|
|
40
|
-
this.
|
|
41
|
-
|
|
42
|
-
transaction: ctx.transaction,
|
|
22
|
+
this.recordChatCompletionEvents({
|
|
23
|
+
ctx,
|
|
43
24
|
request,
|
|
44
25
|
response,
|
|
45
26
|
err
|
|
46
27
|
})
|
|
28
|
+
}
|
|
47
29
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
30
|
+
getMessages({ request, response }) {
|
|
31
|
+
// Only take the first response message and append to input messages
|
|
32
|
+
// request.contents can be a string or an array of strings
|
|
33
|
+
// response.candidates is an array of candidates (choices); we only take the first one
|
|
34
|
+
const inputMessages = Array.isArray(request.contents) ? request.contents : [request.contents]
|
|
35
|
+
const responseMessage = response?.candidates?.[0]?.content
|
|
36
|
+
return responseMessage !== undefined ? [...inputMessages, responseMessage] : inputMessages
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
createCompletionSummary({ ctx, request, response = {}, err }) {
|
|
40
|
+
const { transaction, segment } = ctx
|
|
41
|
+
return new LlmChatCompletionSummary({
|
|
42
|
+
agent: this.agent,
|
|
43
|
+
segment,
|
|
44
|
+
transaction,
|
|
45
|
+
request,
|
|
46
|
+
response,
|
|
47
|
+
withError: !!err
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
createCompletionMessage({ ctx, request, response, index, completionId, message }) {
|
|
52
|
+
const { segment, transaction } = ctx
|
|
53
|
+
return new LlmChatCompletionMessage({
|
|
54
|
+
agent: this.agent,
|
|
55
|
+
segment,
|
|
56
|
+
transaction,
|
|
57
|
+
request,
|
|
58
|
+
response,
|
|
59
|
+
index,
|
|
60
|
+
completionId,
|
|
61
|
+
message
|
|
51
62
|
})
|
|
52
63
|
}
|
|
53
64
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
'use strict'
|
|
7
7
|
const DbOperationSubscriber = require('../db-operation')
|
|
8
8
|
const stringify = require('json-stringify-safe')
|
|
9
|
+
const { wrapPromise } = require('../utils')
|
|
9
10
|
|
|
10
11
|
class IoRedisSubscriber extends DbOperationSubscriber {
|
|
11
12
|
constructor({ agent, logger, packageName = 'ioredis', system = 'Redis' }) {
|
|
@@ -30,18 +31,7 @@ class IoRedisSubscriber extends DbOperationSubscriber {
|
|
|
30
31
|
* @param {object} data the data associated with the `end` event
|
|
31
32
|
*/
|
|
32
33
|
end(data) {
|
|
33
|
-
|
|
34
|
-
if (typeof promise.then !== 'function') {
|
|
35
|
-
return promise
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
promise.then((result) => {
|
|
39
|
-
data.result = result
|
|
40
|
-
this.channel.asyncEnd.publish(data)
|
|
41
|
-
}, (err) => {
|
|
42
|
-
data.error = err
|
|
43
|
-
this.channel.asyncEnd.publish(data)
|
|
44
|
-
})
|
|
34
|
+
wrapPromise.call(this, data)
|
|
45
35
|
}
|
|
46
36
|
|
|
47
37
|
setParameters(self, command) {
|