newrelic 11.11.0 → 11.12.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 CHANGED
@@ -1,3 +1,17 @@
1
+ ### v11.12.0 (2024-03-04)
2
+
3
+ #### Features
4
+
5
+ * Added instrumentation for `chain.stream` for langchain js. ([#2052](https://github.com/newrelic/node-newrelic/pull/2052)) ([03abfce](https://github.com/newrelic/node-newrelic/commit/03abfce666d3c48abd0994c44817bb5cbe8655a9))
6
+
7
+ #### Miscellaneous chores
8
+
9
+ * Added @azure/openai and @langchain/community/llms/bedrock as tracking packages so we can measure usage with angler ([#2053](https://github.com/newrelic/node-newrelic/pull/2053)) ([4830ea3](https://github.com/newrelic/node-newrelic/commit/4830ea3285726f8cc8fb60820c4cf2d3790e5250))
10
+
11
+ #### Tests
12
+
13
+ * Updated aws-sdk branch to main as the necessary code was merged. ([#2051](https://github.com/newrelic/node-newrelic/pull/2051)) ([1e52a5c](https://github.com/newrelic/node-newrelic/commit/1e52a5cd7a8acc8970dbdc6b1bf774c06aa6f5fb))
14
+
1
15
  ### v11.11.0 (2024-02-27)
2
16
 
3
17
  #### Features
@@ -1359,6 +1359,19 @@ defaultConfig.definition = () => ({
1359
1359
  formatter: boolean,
1360
1360
  default: false
1361
1361
  },
1362
+
1363
+ /**
1364
+ * When enabled, the content of LLM messages will be included in the
1365
+ * recorded spans (i.e. delivered to the New Relic collector). This is
1366
+ * enabled by default.
1367
+ */
1368
+ record_content: {
1369
+ enabled: {
1370
+ formatter: boolean,
1371
+ default: true
1372
+ }
1373
+ },
1374
+
1362
1375
  /**
1363
1376
  * Toggles the capturing of Llm events when using streaming
1364
1377
  * based methods in AIM supported libraries(i.e.- openai, AWS bedrock, langchain)
@@ -1390,8 +1403,8 @@ defaultConfig.config = () => {
1390
1403
  *
1391
1404
  * @param {object} definition configuration definition for a given leaf node
1392
1405
  * @param {object} config object that is used to construct the agent config
1393
- * @param {Array} [paths=[]] an array to capture the leaf node keys to properly assign defaults for nested objects
1394
- * @param {int} [objectKeys=1] amount of keys in a given leaf node
1406
+ * @param {Array} [paths] an array to capture the leaf node keys to properly assign defaults for nested objects
1407
+ * @param {int} [objectKeys] amount of keys in a given leaf node
1395
1408
  */
1396
1409
  function buildConfig(definition, config, paths = [], objectKeys = 1) {
1397
1410
  let keysSeen = 0
@@ -1429,7 +1442,7 @@ function buildConfig(definition, config, paths = [], objectKeys = 1) {
1429
1442
  * @param {object} params.config object that is used to construct the agent config
1430
1443
  * @param {string} params.key name of the configuration option
1431
1444
  * @param {string} params.value value the configuration option
1432
- * @param {Array} [params.paths=[]] an array to capture the leaf node keys to properly assign defaults for nested objects
1445
+ * @param {Array} [params.paths] an array to capture the leaf node keys to properly assign defaults for nested objects
1433
1446
  */
1434
1447
  function assignConfigValue({ config, key, value, paths }) {
1435
1448
  if (paths.length) {
@@ -6,9 +6,8 @@
6
6
  'use strict'
7
7
 
8
8
  const common = require('./common')
9
- const {
10
- AI: { LANGCHAIN }
11
- } = require('../../metrics/names')
9
+ const { AI } = require('../../metrics/names')
10
+ const { LANGCHAIN } = AI
12
11
  const {
13
12
  LangChainCompletionMessage,
14
13
  LangChainCompletionSummary
@@ -28,10 +27,33 @@ module.exports = function initialize(shim, langchain) {
28
27
  return
29
28
  }
30
29
 
30
+ instrumentInvokeChain({ langchain, shim })
31
+
32
+ if (agent.config.ai_monitoring.streaming.enabled) {
33
+ instrumentStream({ langchain, shim })
34
+ } else {
35
+ shim.logger.warn(
36
+ '`ai_monitoring.streaming.enabled` is set to `false`, stream will not be instrumented.'
37
+ )
38
+ agent.metrics.getOrCreateMetric(AI.STREAMING_DISABLED).incrementCallCount()
39
+ agent.metrics
40
+ .getOrCreateMetric(`${LANGCHAIN.TRACKING_PREFIX}/${pkgVersion}`)
41
+ .incrementCallCount()
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Instruments and records span and relevant LLM events for `chain.invoke`
47
+ *
48
+ * @param {object} params function params
49
+ * @param {object} params.langchain `@langchain/core/runnables/base` export
50
+ * @param {Shim} params.shim instace of shim
51
+ */
52
+ function instrumentInvokeChain({ langchain, shim }) {
31
53
  shim.record(
32
54
  langchain.RunnableSequence.prototype,
33
55
  'invoke',
34
- function wrapCall(shim, invoke, fnName, args) {
56
+ function wrapCall(shim, _invoke, fnName, args) {
35
57
  const [request, params] = args
36
58
  const metadata = params?.metadata ?? {}
37
59
  const tags = params?.tags ?? []
@@ -41,53 +63,183 @@ module.exports = function initialize(shim, langchain) {
41
63
  promise: true,
42
64
  // eslint-disable-next-line max-params
43
65
  after(_shim, _fn, _name, err, output, segment) {
44
- segment.end()
45
- const completionSummary = new LangChainCompletionSummary({
46
- agent,
47
- messages: [{ output }],
48
- metadata,
49
- tags,
66
+ recordChatCompletionEvents({
50
67
  segment,
51
- error: err != null,
52
- runId: segment[langchainRunId]
53
- })
54
-
55
- common.recordEvent({
56
- agent,
57
- type: 'LlmChatCompletionSummary',
58
- pkgVersion,
59
- msg: completionSummary
60
- })
61
-
62
- // output can be BaseMessage with a content property https://js.langchain.com/docs/modules/model_io/concepts#messages
63
- // or an output parser https://js.langchain.com/docs/modules/model_io/concepts#output-parsers
64
- recordCompletions({
68
+ messages: [output],
65
69
  events: [request, output],
66
- completionSummary,
67
- agent,
68
- segment,
70
+ metadata,
71
+ tags,
72
+ err,
69
73
  shim
70
74
  })
75
+ }
76
+ })
77
+ }
78
+ )
79
+ }
80
+
81
+ /**
82
+ * Instruments and records span and relevant LLM events for `chain.stream`
83
+ *
84
+ * @param {object} params function params
85
+ * @param {object} params.langchain `@langchain/core/runnables/base` export
86
+ * @param {Shim} params.shim instace of shim
87
+ */
88
+ function instrumentStream({ langchain, shim }) {
89
+ shim.record(
90
+ langchain.RunnableSequence.prototype,
91
+ 'stream',
92
+ function wrapStream(shim, _stream, fnName, args) {
93
+ const [request, params] = args
94
+ const metadata = params?.metadata ?? {}
95
+ const tags = params?.tags ?? []
71
96
 
72
- if (err) {
73
- agent.errors.add(
74
- segment.transaction,
97
+ return new RecorderSpec({
98
+ name: `${LANGCHAIN.CHAIN}/${fnName}`,
99
+ promise: true,
100
+ // eslint-disable-next-line max-params
101
+ after(_shim, _fn, _name, err, output, segment) {
102
+ // Input error occurred which means a stream was not created.
103
+ // Skip instrumenting streaming and create Llm Events from
104
+ // the data we have
105
+ if (output?.next) {
106
+ wrapNextHandler({ shim, output, segment, request, metadata, tags })
107
+ } else {
108
+ recordChatCompletionEvents({
109
+ segment,
110
+ messages: [],
111
+ events: [request],
112
+ metadata,
113
+ tags,
75
114
  err,
76
- new LlmErrorMessage({
77
- response: {},
78
- cause: err,
79
- summary: completionSummary
80
- })
81
- )
115
+ shim
116
+ })
82
117
  }
83
-
84
- segment.transaction.trace.attributes.addAttribute(DESTINATIONS.TRANS_EVENT, 'llm', true)
85
118
  }
86
119
  })
87
120
  }
88
121
  )
89
122
  }
90
123
 
124
+ /**
125
+ * Wraps the next method on the IterableReadableStream. It will also record the Llm
126
+ * events when the stream is done processing.
127
+ *
128
+ * @param {object} params function params
129
+ * @param {Shim} params.shim shim instance
130
+ * @param {TraceSegment} params.segment active segment
131
+ * @param {function} params.output IterableReadableStream
132
+ * @param {string} params.request the prompt message
133
+ * @param {object} params.metadata metadata for the call
134
+ * @param {Array} params.tags tags for the call
135
+ */
136
+ function wrapNextHandler({ shim, output, segment, request, metadata, tags }) {
137
+ shim.wrap(output, 'next', function wrapIterator(shim, orig) {
138
+ let content = ''
139
+ return async function wrappedIterator() {
140
+ try {
141
+ const result = await orig.apply(this, arguments)
142
+ // only create Llm events when stream iteration is done
143
+ if (result?.done) {
144
+ recordChatCompletionEvents({
145
+ segment,
146
+ messages: [content],
147
+ events: [request, content],
148
+ metadata,
149
+ tags,
150
+ shim
151
+ })
152
+ } else {
153
+ content += result.value
154
+ }
155
+ return result
156
+ } catch (error) {
157
+ recordChatCompletionEvents({
158
+ segment,
159
+ messages: [content],
160
+ events: [request, content],
161
+ metadata,
162
+ tags,
163
+ err: error,
164
+ shim
165
+ })
166
+ throw error
167
+ } finally {
168
+ // update segment duration on every stream iteration to extend
169
+ // the timer
170
+ segment.touch()
171
+ }
172
+ }
173
+ })
174
+ }
175
+
176
+ /**
177
+ * Ends active segment, creates LlmChatCompletionSummary, and LlmChatCompletionMessage(s), and handles errors if they exists
178
+ *
179
+ * @param {object} params function params
180
+ * @param {TraceSegment} params.segment active segment
181
+ * @param {Array} params.messages response messages
182
+ * @param {Array} params.events prompt and response messages
183
+ * @param {object} params.metadata metadata for the call
184
+ * @param {Array} params.tags tags for the call
185
+ * @param {Error} params.err error object from call
186
+ * @param {Shim} params.shim shim instance
187
+ */
188
+ function recordChatCompletionEvents({ segment, messages, events, metadata, tags, err, shim }) {
189
+ const { pkgVersion, agent } = shim
190
+ segment.end()
191
+ const completionSummary = new LangChainCompletionSummary({
192
+ agent,
193
+ messages,
194
+ metadata,
195
+ tags,
196
+ segment,
197
+ error: err != null,
198
+ runId: segment[langchainRunId]
199
+ })
200
+
201
+ common.recordEvent({
202
+ agent,
203
+ type: 'LlmChatCompletionSummary',
204
+ pkgVersion,
205
+ msg: completionSummary
206
+ })
207
+
208
+ // output can be BaseMessage with a content property https://js.langchain.com/docs/modules/model_io/concepts#messages
209
+ // or an output parser https://js.langchain.com/docs/modules/model_io/concepts#output-parsers
210
+ recordCompletions({
211
+ events,
212
+ completionSummary,
213
+ agent,
214
+ segment,
215
+ shim
216
+ })
217
+
218
+ if (err) {
219
+ agent.errors.add(
220
+ segment.transaction,
221
+ err,
222
+ new LlmErrorMessage({
223
+ response: {},
224
+ cause: err,
225
+ summary: completionSummary
226
+ })
227
+ )
228
+ }
229
+
230
+ segment.transaction.trace.attributes.addAttribute(DESTINATIONS.TRANS_EVENT, 'llm', true)
231
+ }
232
+
233
+ /**
234
+ * Records the LlmChatCompletionMessage(s)
235
+ *
236
+ * @param {object} params function params
237
+ * @param {Array} params.events prompt and response messages
238
+ * @param {LangChainCompletionSummary} params.completionSummary LlmChatCompletionSummary event
239
+ * @param {Agent} params.agent instance of agent
240
+ * @param {TraceSegment} params.segment active segment
241
+ * @param {Shim} params.shim shim instance
242
+ */
91
243
  function recordCompletions({ events, completionSummary, agent, segment, shim }) {
92
244
  for (let i = 0; i < events.length; i += 1) {
93
245
  let msg = events[i]
@@ -55,6 +55,8 @@ module.exports = function instrumentations() {
55
55
  'loglevel': { type: MODULE_TYPE.TRACKING },
56
56
  'npmlog': { type: MODULE_TYPE.TRACKING },
57
57
  'fancy-log': { type: MODULE_TYPE.TRACKING },
58
- 'knex': { type: MODULE_TYPE.TRACKING }
58
+ 'knex': { type: MODULE_TYPE.TRACKING },
59
+ '@azure/openai': { type: MODULE_TYPE.TRACKING },
60
+ '@langchain/community/llms/bedrock': { type: MODULE_TYPE.TRACKING }
59
61
  }
60
62
  }
@@ -36,6 +36,7 @@ class LangChainCompletionMessage extends LangChainEvent {
36
36
  constructor(params = defaultParams) {
37
37
  params = Object.assign({}, defaultParams, params)
38
38
  super(params)
39
+ const { agent } = params
39
40
 
40
41
  if (params.runId) {
41
42
  this.id = `${params.runId}-${params.sequence}`
@@ -43,10 +44,13 @@ class LangChainCompletionMessage extends LangChainEvent {
43
44
  this.id = `${this.id}-${params.sequence}`
44
45
  }
45
46
 
46
- this.content = params.content
47
47
  this.sequence = params.sequence
48
48
  this.completion_id = params.completionId
49
49
  this.is_response = params.isResponse ?? false
50
+
51
+ if (agent.config.ai_monitoring.record_content.enabled === true) {
52
+ this.content = params.content
53
+ }
50
54
  }
51
55
  }
52
56
 
@@ -51,7 +51,6 @@ class LangChainEvent extends BaseEvent {
51
51
  ingest_source = 'Node'
52
52
  vendor = 'langchain'
53
53
  virtual_llm = true
54
- error = false
55
54
 
56
55
  constructor(params = defaultParams) {
57
56
  params = Object.assign({}, defaultParams, params)
@@ -66,7 +65,7 @@ class LangChainEvent extends BaseEvent {
66
65
  this.langchainMeta = params.metadata
67
66
  this.metadata = agent
68
67
  this.tags = Array.isArray(params.tags) ? params.tags.join(',') : params.tags
69
- this.error = params.error ?? false
68
+ this.error = params.error ?? null
70
69
 
71
70
  if (params.virtual !== undefined) {
72
71
  if (params.virtual !== true && params.virtual !== false) {
@@ -9,8 +9,8 @@ const LangChainEvent = require('./event')
9
9
  class LangChainTool extends LangChainEvent {
10
10
  constructor(params) {
11
11
  super(params)
12
- this.input = params.input
13
- this.output = params.output
12
+ const { agent } = params
13
+
14
14
  this.name = params.name
15
15
  this.description = params.description
16
16
  this.duration = params?.segment?.getDurationInMillis()
@@ -18,6 +18,11 @@ class LangChainTool extends LangChainEvent {
18
18
  delete this.request_id
19
19
  delete this.virtual_llm
20
20
  delete this.conversation_id
21
+
22
+ if (agent.config.ai_monitoring.record_content.enabled === true) {
23
+ this.input = params.input
24
+ this.output = params.output
25
+ }
21
26
  }
22
27
  }
23
28
 
@@ -28,9 +28,13 @@ class LangChainVectorSearchResult extends LangChainEvent {
28
28
  constructor(params) {
29
29
  params = Object.assign({}, defaultParams, params)
30
30
  super(params)
31
+ const { agent } = params
31
32
 
32
- this.page_content = params.pageContent
33
33
  this.sequence = params.sequence
34
+
35
+ if (agent.config.ai_monitoring.record_content.enabled === true) {
36
+ this.page_content = params.pageContent
37
+ }
34
38
  }
35
39
  }
36
40
 
@@ -28,12 +28,15 @@ class LangChainVectorSearch extends LangChainEvent {
28
28
  constructor(params) {
29
29
  params = Object.assign({}, defaultParams, params)
30
30
  super(params)
31
- const { segment } = params
31
+ const { agent, segment } = params
32
32
 
33
33
  this.duration = segment?.getDurationInMillis()
34
- this['request.query'] = params.query
35
34
  this['request.k'] = params.k
36
35
  this['response.number_of_documents'] = params.documents?.length
36
+
37
+ if (agent.config.ai_monitoring.record_content.enabled === true) {
38
+ this['request.query'] = params.query
39
+ }
37
40
  }
38
41
  }
39
42
 
@@ -10,10 +10,13 @@ module.exports = class LlmChatCompletionMessage extends LlmEvent {
10
10
  constructor({ agent, segment, request = {}, response = {}, index = 0, message, completionId }) {
11
11
  super({ agent, segment, request, response })
12
12
  this.id = `${response.id}-${index}`
13
- this.content = message?.content
14
13
  this.role = message?.role
15
14
  this.sequence = index
16
15
  this.completion_id = completionId
17
- this.is_response = response?.choices?.[0]?.message?.content === this.content
16
+ this.is_response = response?.choices?.[0]?.message?.content === message?.content
17
+
18
+ if (agent.config.ai_monitoring.record_content.enabled === true) {
19
+ this.content = message?.content
20
+ }
18
21
  }
19
22
  }
@@ -10,6 +10,9 @@ module.exports = class LlmEmbedding extends LlmEvent {
10
10
  constructor({ agent, segment, request = {}, response = {}, withError = false }) {
11
11
  super({ agent, segment, request, response, responseAttrs: true })
12
12
  this.error = withError
13
- this.input = request.input?.toString()
13
+
14
+ if (agent.config.ai_monitoring.record_content.enabled === true) {
15
+ this.input = request.input?.toString()
16
+ }
14
17
  }
15
18
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newrelic",
3
- "version": "11.11.0",
3
+ "version": "11.12.0",
4
4
  "author": "New Relic Node.js agent team <nodejs@newrelic.com>",
5
5
  "license": "Apache-2.0",
6
6
  "contributors": [