newrelic 11.12.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,25 @@
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
+
1
23
  ### v11.12.0 (2024-03-04)
2
24
 
3
25
  #### 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
@@ -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
  ]
@@ -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
  }
@@ -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]() {
@@ -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,13 +23,12 @@ 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)
31
29
  const { agent } = params
32
30
 
31
+ this.search_id = params.search_id
33
32
  this.sequence = params.sequence
34
33
 
35
34
  if (agent.config.ai_monitoring.record_content.enabled === true) {
@@ -18,5 +18,16 @@ module.exports = class LlmChatCompletionMessage extends LlmEvent {
18
18
  if (agent.config.ai_monitoring.record_content.enabled === true) {
19
19
  this.content = message?.content
20
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
+ }
21
32
  }
22
33
  }
@@ -14,5 +14,9 @@ module.exports = class LlmEmbedding extends LlmEvent {
14
14
  if (agent.config.ai_monitoring.record_content.enabled === true) {
15
15
  this.input = request.input?.toString()
16
16
  }
17
+ this.token_count = agent.llm?.tokenCountCallback?.(
18
+ this['request.model'],
19
+ request.input?.toString()
20
+ )
17
21
  }
18
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.12.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",