newrelic 11.11.0 → 11.13.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,39 @@
1
+ ### v11.13.0 (2024-03-13)
2
+
3
+ #### Features
4
+
5
+ * Added AI Monitoring(AIM) for OpenAI, Amazon Bedrock, and Langchain.
6
+ * To enable set `config.ai_monitoring.enabled` or `NEW_RELIC_AI_MONITORING_ENABLED` to `true`.
7
+ * It is recommended for a full experience to set `config.span_events.max_samples_stored` to `10000`, `config.custom_insights_events.max_samples_stored` to `100000`, and `config.distributed_tracing.enabled` should not be set to false.
8
+ * **Note**: Enabling [high-security mode](https://docs.newrelic.com/docs/apm/agents/manage-apm-agents/configuration/high-security-mode/) will disable the AIM instrumentation and events.
9
+ * Added instrumentation for VectorStore.similaritySearch for langchain.js ([#2049](https://github.com/newrelic/node-newrelic/pull/2049)) ([3c50606](https://github.com/newrelic/node-newrelic/commit/3c50606a57a8dc18c91fe36e3b180bafd15a4cbc))
10
+ * Added setLlmTokenCountCallback API endpoint to register a callback for calculating token count when none is provided ([#2065](https://github.com/newrelic/node-newrelic/pull/2065)) ([d2faf1a](https://github.com/newrelic/node-newrelic/commit/d2faf1a4942aacefd8b92e0bc2dc11bd8b687328))
11
+ * Added token_count to LlmEmbedding and LlmChatCompletionMessage for openai ([#2061](https://github.com/newrelic/node-newrelic/pull/2061)) ([47a925e](https://github.com/newrelic/node-newrelic/commit/47a925eccce8e5b5d682b46277bbef5191f8b553))
12
+ * Added tool_id to langchain error event ([#2064](https://github.com/newrelic/node-newrelic/pull/2064)) ([793abe8](https://github.com/newrelic/node-newrelic/commit/793abe8c9854b80fb1cf42f839a7db6a646cdc0b))
13
+
14
+ #### Miscellaneous chores
15
+
16
+ * Added flag for verbosity during docs publishing ([#2060](https://github.com/newrelic/node-newrelic/pull/2060)) ([fe0d41a](https://github.com/newrelic/node-newrelic/commit/fe0d41a86cba8da6173f5935e61c62b7dca75344))
17
+ * Added some delays to docs publishing ([#2062](https://github.com/newrelic/node-newrelic/pull/2062)) ([a602fd4](https://github.com/newrelic/node-newrelic/commit/a602fd48e929437e4bc933c4ce276989013c53f1))
18
+ * Changed casing of OpenAI in vendor property ([#2068](https://github.com/newrelic/node-newrelic/pull/2068)) ([64b4ca2](https://github.com/newrelic/node-newrelic/commit/64b4ca2750c9de8d9028fef515c15019e358a5d9))
19
+ * Changed token_count to only use tokenCountCallback ([#2070](https://github.com/newrelic/node-newrelic/pull/2070)) ([66f94b0](https://github.com/newrelic/node-newrelic/commit/66f94b05aa7018af6fa1db1d2d87de9013f23a5b))
20
+ * Removed langchain_instrumentation feature flag as the instrumentation is feature complete ([#2073](https://github.com/newrelic/node-newrelic/pull/2073)) ([925a463](https://github.com/newrelic/node-newrelic/commit/925a463c708b29c78567aecf78ac89ac226b7995))
21
+ * updated @newrelic/aws-sdk and @newrelic/koa to latest ([#2074](https://github.com/newrelic/node-newrelic/pull/2074)) ([0747ca4](https://github.com/newrelic/node-newrelic/commit/0747ca4f0f6615e1fbfcc734a36059960facf23e))
22
+
23
+ ### v11.12.0 (2024-03-04)
24
+
25
+ #### Features
26
+
27
+ * 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))
28
+
29
+ #### Miscellaneous chores
30
+
31
+ * 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))
32
+
33
+ #### Tests
34
+
35
+ * 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))
36
+
1
37
  ### v11.11.0 (2024-02-27)
2
38
 
3
39
  #### Features
@@ -504,7 +504,7 @@ This product includes source derived from [@grpc/proto-loader](https://github.co
504
504
 
505
505
  ### @newrelic/aws-sdk
506
506
 
507
- This product includes source derived from [@newrelic/aws-sdk](https://github.com/newrelic/node-newrelic-aws-sdk) ([v7.1.0](https://github.com/newrelic/node-newrelic-aws-sdk/tree/v7.1.0)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-newrelic-aws-sdk/blob/v7.1.0/LICENSE):
507
+ This product includes source derived from [@newrelic/aws-sdk](https://github.com/newrelic/node-newrelic-aws-sdk) ([v7.3.0](https://github.com/newrelic/node-newrelic-aws-sdk/tree/v7.3.0)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-newrelic-aws-sdk/blob/v7.3.0/LICENSE):
508
508
 
509
509
  ```
510
510
  Apache License
@@ -712,7 +712,7 @@ This product includes source derived from [@newrelic/aws-sdk](https://github.com
712
712
 
713
713
  ### @newrelic/koa
714
714
 
715
- This product includes source derived from [@newrelic/koa](https://github.com/newrelic/node-newrelic-koa) ([v8.0.1](https://github.com/newrelic/node-newrelic-koa/tree/v8.0.1)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-newrelic-koa/blob/v8.0.1/LICENSE):
715
+ This product includes source derived from [@newrelic/koa](https://github.com/newrelic/node-newrelic-koa) ([v9.0.0](https://github.com/newrelic/node-newrelic-koa/tree/v9.0.0)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-newrelic-koa/blob/v9.0.0/LICENSE):
716
716
 
717
717
  ```
718
718
  Apache License
package/api.js CHANGED
@@ -1839,7 +1839,7 @@ API.prototype.setErrorGroupCallback = function setErrorGroupCallback(callback) {
1839
1839
  )
1840
1840
  metric.incrementCallCount()
1841
1841
 
1842
- if (!this.shim.isFunction(callback) || this.shim.isPromise(callback)) {
1842
+ if (!this.shim.isFunction(callback) || this.shim.isAsyncFunction(callback)) {
1843
1843
  logger.warn(
1844
1844
  'Error Group callback must be a synchronous function, Error Group attribute will not be added'
1845
1845
  )
@@ -1849,4 +1849,37 @@ API.prototype.setErrorGroupCallback = function setErrorGroupCallback(callback) {
1849
1849
  this.agent.errors.errorGroupCallback = callback
1850
1850
  }
1851
1851
 
1852
+ /**
1853
+ * Registers a callback which will be used for calculating token counts on Llm events when they are not
1854
+ * available. This function will typically only be used if `ai_monitoring.record_content.enabled` is false
1855
+ * and you want to still capture token counts for Llm events.
1856
+ *
1857
+ * Provided callbacks must return an integer value for the token count for a given piece of content.
1858
+ *
1859
+ * @param {Function} callback - synchronous function called to calculate token count for content.
1860
+ * @example
1861
+ * // @param {string} model - name of model (i.e. gpt-3.5-turbo)
1862
+ * // @param {string} content - prompt or completion response
1863
+ * function tokenCallback(model, content) {
1864
+ * // calculate tokens based on model and content
1865
+ * // return token count
1866
+ * return 40
1867
+ * }
1868
+ */
1869
+ API.prototype.setLlmTokenCountCallback = function setLlmTokenCountCallback(callback) {
1870
+ const metric = this.agent.metrics.getOrCreateMetric(
1871
+ NAMES.SUPPORTABILITY.API + '/setLlmTokenCountCallback'
1872
+ )
1873
+ metric.incrementCallCount()
1874
+
1875
+ if (!this.shim.isFunction(callback) || this.shim.isAsyncFunction(callback)) {
1876
+ logger.warn(
1877
+ 'Llm token count callback must be a synchronous function, callback will not be registered.'
1878
+ )
1879
+ return
1880
+ }
1881
+
1882
+ this.agent.llm.tokenCountCallback = callback
1883
+ }
1884
+
1852
1885
  module.exports = API
@@ -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) {
@@ -12,8 +12,7 @@ exports.prerelease = {
12
12
  reverse_naming_rules: false,
13
13
  undici_async_tracking: true,
14
14
  unresolved_promise_cleanup: true,
15
- legacy_context_manager: false,
16
- langchain_instrumentation: false
15
+ legacy_context_manager: false
17
16
  }
18
17
 
19
18
  // flags that are no longer used for released features
@@ -35,7 +34,8 @@ exports.released = [
35
34
  'certificate_bundle',
36
35
  'async_local_context',
37
36
  'undici_instrumentation',
38
- 'aws_bedrock_instrumentation'
37
+ 'aws_bedrock_instrumentation',
38
+ 'langchain_instrumentation'
39
39
  ]
40
40
 
41
41
  // flags that are no longer used for unreleased features
@@ -59,7 +59,5 @@ common.recordEvent = function recordEvent({ agent, type, msg, pkgVersion }) {
59
59
  * @returns {boolean} flag if we should skip instrumentation
60
60
  */
61
61
  common.shouldSkipInstrumentation = function shouldSkipInstrumentation(config) {
62
- return !(
63
- config.ai_monitoring.enabled === true && config.feature_flag.langchain_instrumentation === true
64
- )
62
+ return config.ai_monitoring.enabled !== true
65
63
  }
@@ -7,6 +7,7 @@
7
7
  const toolsInstrumentation = require('./tools')
8
8
  const cbManagerInstrumentation = require('./callback-manager')
9
9
  const runnableInstrumentation = require('./runnable')
10
+ const vectorstoreInstrumentation = require('./vectorstore')
10
11
 
11
12
  module.exports = [
12
13
  {
@@ -23,5 +24,10 @@ module.exports = [
23
24
  type: 'generic',
24
25
  moduleName: '@langchain/core/dist/runnables/base',
25
26
  onRequire: runnableInstrumentation
27
+ },
28
+ {
29
+ type: 'generic',
30
+ moduleName: '@langchain/core/vectorstores',
31
+ onRequire: vectorstoreInstrumentation
26
32
  }
27
33
  ]
@@ -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]
@@ -57,7 +57,7 @@ module.exports = function initialize(shim, tools) {
57
57
  new LlmErrorMessage({
58
58
  response: {},
59
59
  cause: err,
60
- summary: {}
60
+ tool: toolEvent
61
61
  })
62
62
  )
63
63
  }
@@ -0,0 +1,108 @@
1
+ /*
2
+ * Copyright 2024 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+ const {
8
+ AI: { LANGCHAIN }
9
+ } = require('../../metrics/names')
10
+ const { LangChainVectorSearch, LangChainVectorSearchResult } = require('../../llm-events/langchain')
11
+ const { recordEvent, shouldSkipInstrumentation } = require('./common')
12
+ const { DESTINATIONS } = require('../../config/attribute-filter')
13
+ const { RecorderSpec } = require('../../shim/specs')
14
+ const LlmErrorMessage = require('../../llm-events/error-message')
15
+
16
+ /**
17
+ * Generates a LangChainVectorSearch for entire search request.
18
+ * Also iterates over documents in output and generates a
19
+ * LangChainVectorSearchResult for each document.
20
+ *
21
+ * @param {object} params input params
22
+ * @param {string} params.request vector search query
23
+ * @param {number} params.k vector search top k
24
+ * @param {object} params.output vector search documents
25
+ * @param {Agent} params.agent NR agent instance
26
+ * @param {TraceSegment} params.segment active segment from vector search
27
+ * @param {string} params.pkgVersion langchain version
28
+ * @param {err} params.err if it exists
29
+ */
30
+ function recordVectorSearch({ request, k, output, agent, segment, pkgVersion, err }) {
31
+ const vectorSearch = new LangChainVectorSearch({
32
+ agent,
33
+ segment,
34
+ query: request,
35
+ k,
36
+ documents: output,
37
+ error: err !== null
38
+ })
39
+
40
+ recordEvent({ agent, type: 'LlmVectorSearch', pkgVersion, msg: vectorSearch })
41
+
42
+ output.forEach((document, sequence) => {
43
+ const vectorSearchResult = new LangChainVectorSearchResult({
44
+ agent,
45
+ segment,
46
+ metadata: document.metadata,
47
+ pageContent: document.pageContent,
48
+ sequence,
49
+ search_id: vectorSearch.id
50
+ })
51
+
52
+ recordEvent({
53
+ agent,
54
+ type: 'LlmVectorSearchResult',
55
+ pkgVersion,
56
+ msg: vectorSearchResult
57
+ })
58
+ })
59
+
60
+ if (err) {
61
+ agent.errors.add(
62
+ segment.transaction,
63
+ err,
64
+ new LlmErrorMessage({
65
+ response: output,
66
+ cause: err,
67
+ vectorsearch: vectorSearch
68
+ })
69
+ )
70
+ }
71
+ }
72
+
73
+ module.exports = function initialize(shim, vectorstores) {
74
+ const { agent, pkgVersion } = shim
75
+
76
+ if (shouldSkipInstrumentation(agent.config)) {
77
+ shim.logger.debug(
78
+ 'langchain instrumentation is disabled. To enable set `config.ai_monitoring.enabled` to true'
79
+ )
80
+ return
81
+ }
82
+
83
+ shim.record(
84
+ vectorstores.VectorStore.prototype,
85
+ 'similaritySearch',
86
+ function wrapCall(shim, similaritySearch, fnName, args) {
87
+ const [request, k] = args
88
+
89
+ return new RecorderSpec({
90
+ name: `${LANGCHAIN.VECTORSTORE}/${fnName}`,
91
+ promise: true,
92
+ // eslint-disable-next-line max-params
93
+ after(_shim, _fn, _name, err, output, segment) {
94
+ if (!output) {
95
+ // If we get an error, it is possible that `output = null`.
96
+ // In that case, we define it to be an empty array.
97
+ output = []
98
+ }
99
+
100
+ segment.end()
101
+ recordVectorSearch({ request, k, output, agent, segment, pkgVersion, err })
102
+
103
+ segment.transaction.trace.attributes.addAttribute(DESTINATIONS.TRANS_EVENT, 'llm', true)
104
+ }
105
+ })
106
+ }
107
+ )
108
+ }
@@ -93,7 +93,7 @@ function wrapMiddleware(shim, middleware, _name, route) {
93
93
  })
94
94
 
95
95
  const wrappedMw = shim.recordMiddleware(middleware, spec)
96
- if (middleware.constructor.name === 'AsyncFunction') {
96
+ if (shim.isAsyncFunction(middleware)) {
97
97
  return async function asyncShim() {
98
98
  return wrappedMw.apply(this, arguments)
99
99
  }
@@ -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
  }
@@ -18,14 +18,19 @@ module.exports = class LlmErrorMessage {
18
18
  * conversation if it was a chat completion conversation.
19
19
  * @param {LlmEmbedding} [params.embedding] Details about the conversation
20
20
  * if it was an embedding conversation.
21
+ * @param {LlmVectorStoreSearch} [params.vectorsearch] Details about the vector
22
+ * search if it was a vector search event.
23
+ * @param {LlmTool} [params.tool] Details about the tool event if it was a tool event.
21
24
  */
22
- constructor({ response, cause, summary, embedding } = {}) {
25
+ constructor({ response, cause, summary, embedding, vectorsearch, tool } = {}) {
23
26
  this['http.statusCode'] = response?.status ?? cause?.status
24
27
  this['error.message'] = cause?.message
25
28
  this['error.code'] = response?.code ?? cause?.error?.code
26
29
  this['error.param'] = response?.param ?? cause?.error?.param
27
30
  this.completion_id = summary?.id
28
31
  this.embedding_id = embedding?.id
32
+ this.vector_store_id = vectorsearch?.id
33
+ this.tool_id = tool?.id
29
34
  }
30
35
 
31
36
  get [Symbol.toStringTag]() {
@@ -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
 
@@ -6,13 +6,13 @@
6
6
  'use strict'
7
7
 
8
8
  const LangChainEvent = require('./event')
9
- const crypto = require('crypto')
10
9
 
11
10
  /**
12
11
  * @typedef {object} LangChainVectorSearchResultParams
13
12
  * @augments LangChainEventParams
14
13
  * @property {string} pageContent The stringified contents of the pageContent attribute on each returned search result document.
15
14
  * @property {number} [sequence=0] The index of the document in the search result documents list.
15
+ * @property {string} search_id The identifier from the LangChainVectorSearch event.
16
16
  */
17
17
  /**
18
18
  * @type {LangChainVectorSearchResultParams}
@@ -23,14 +23,17 @@ const defaultParams = {
23
23
  }
24
24
 
25
25
  class LangChainVectorSearchResult extends LangChainEvent {
26
- search_id = crypto.randomUUID()
27
-
28
26
  constructor(params) {
29
27
  params = Object.assign({}, defaultParams, params)
30
28
  super(params)
29
+ const { agent } = params
31
30
 
32
- this.page_content = params.pageContent
31
+ this.search_id = params.search_id
33
32
  this.sequence = params.sequence
33
+
34
+ if (agent.config.ai_monitoring.record_content.enabled === true) {
35
+ this.page_content = params.pageContent
36
+ }
34
37
  }
35
38
  }
36
39
 
@@ -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,24 @@ 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
+ }
21
+
22
+ const tokenCB = agent.llm?.tokenCountCallback
23
+ if (typeof tokenCB !== 'function') {
24
+ return
25
+ }
26
+
27
+ if (this.is_response) {
28
+ this.token_count = tokenCB(this['response.model'], message?.content)
29
+ } else {
30
+ this.token_count = tokenCB(request.model || request.engine, message?.content)
31
+ }
18
32
  }
19
33
  }
@@ -10,6 +10,13 @@ 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
+ }
17
+ this.token_count = agent.llm?.tokenCountCallback?.(
18
+ this['request.model'],
19
+ request.input?.toString()
20
+ )
14
21
  }
15
22
  }
@@ -19,7 +19,7 @@ module.exports = class LlmEvent extends BaseEvent {
19
19
  this.span_id = segment?.id
20
20
  this.transaction_id = segment?.transaction?.id
21
21
  this['response.model'] = response.model
22
- this.vendor = 'openAI'
22
+ this.vendor = 'openai'
23
23
  this.ingest_source = 'Node'
24
24
  this.metadata = agent
25
25
 
@@ -170,7 +170,8 @@ const AI = {
170
170
  EMBEDDING: 'Llm/embedding',
171
171
  COMPLETION: 'Llm/completion',
172
172
  TOOL: 'Llm/tool',
173
- CHAIN: 'Llm/chain'
173
+ CHAIN: 'Llm/chain',
174
+ VECTORSTORE: 'Llm/vectorstore'
174
175
  }
175
176
 
176
177
  AI.OPENAI = {
@@ -184,7 +185,8 @@ AI.LANGCHAIN = {
184
185
  EMBEDDING: `${AI.EMBEDDING}/Langchain`,
185
186
  COMPLETION: `${AI.COMPLETION}/Langchain`,
186
187
  TOOL: `${AI.TOOL}/Langchain`,
187
- CHAIN: `${AI.CHAIN}/Langchain`
188
+ CHAIN: `${AI.CHAIN}/Langchain`,
189
+ VECTORSTORE: `${AI.VECTORSTORE}/Langchain`
188
190
  }
189
191
 
190
192
  const RESTIFY = {
package/lib/shim/shim.js CHANGED
@@ -125,6 +125,7 @@ Shim.prototype.getName = getName
125
125
  Shim.prototype.isObject = isObject
126
126
  Shim.prototype.isFunction = isFunction
127
127
  Shim.prototype.isPromise = isPromise
128
+ Shim.prototype.isAsyncFunction = isAsyncFunction
128
129
  Shim.prototype.isString = isString
129
130
  Shim.prototype.isNumber = isNumber
130
131
  Shim.prototype.isBoolean = isBoolean
@@ -1345,6 +1346,20 @@ function isPromise(obj) {
1345
1346
  return obj && typeof obj.then === 'function'
1346
1347
  }
1347
1348
 
1349
+ /**
1350
+ * Determines if function is an async function.
1351
+ * Note it does not test if the return value of function is a
1352
+ * promise or async function
1353
+ *
1354
+ * @memberof Shim.prototype
1355
+ * @param fn
1356
+ * @param (function) function to test if async
1357
+ * @returns {boolean} True if the function is an async function
1358
+ */
1359
+ function isAsyncFunction(fn) {
1360
+ return fn.constructor.name === 'AsyncFunction'
1361
+ }
1362
+
1348
1363
  /**
1349
1364
  * Determines if the given value is null.
1350
1365
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newrelic",
3
- "version": "11.11.0",
3
+ "version": "11.13.0",
4
4
  "author": "New Relic Node.js agent team <nodejs@newrelic.com>",
5
5
  "license": "Apache-2.0",
6
6
  "contributors": [
@@ -193,8 +193,8 @@
193
193
  "dependencies": {
194
194
  "@grpc/grpc-js": "^1.9.4",
195
195
  "@grpc/proto-loader": "^0.7.5",
196
- "@newrelic/aws-sdk": "^7.1.0",
197
- "@newrelic/koa": "^8.0.1",
196
+ "@newrelic/aws-sdk": "^7.3.0",
197
+ "@newrelic/koa": "^9.0.0",
198
198
  "@newrelic/ritm": "^7.2.0",
199
199
  "@newrelic/security-agent": "^1.0.1",
200
200
  "@newrelic/superagent": "^7.0.1",