newrelic 13.10.0 → 13.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 +58 -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/aws-bedrock/chat-completion-summary.js +1 -0
- package/lib/llm-events/error-message.js +8 -2
- package/lib/llm-events/google-genai/chat-completion-message.js +5 -0
- package/lib/llm-events/google-genai/chat-completion-summary.js +1 -0
- package/lib/llm-events/langchain/chat-completion-message.js +16 -10
- package/lib/llm-events/langchain/chat-completion-summary.js +2 -1
- package/lib/llm-events/langgraph/agent.js +41 -0
- package/lib/llm-events/openai/chat-completion-summary.js +4 -2
- package/lib/metrics/names.js +10 -2
- package/lib/otel/traces/span-processor.js +9 -2
- package/lib/spans/span-event.js +10 -0
- package/lib/spans/span-link.js +5 -0
- package/lib/subscriber-configs.js +1 -0
- package/lib/subscribers/ai-monitoring/base.js +113 -0
- package/lib/subscribers/ai-monitoring/chat.js +117 -0
- package/lib/subscribers/ai-monitoring/embedding.js +72 -0
- package/lib/subscribers/ai-monitoring/index.js +14 -0
- package/lib/subscribers/google-genai/embed-content.js +18 -44
- package/lib/subscribers/google-genai/generate-content-stream.js +16 -39
- package/lib/subscribers/google-genai/generate-content.js +40 -29
- package/lib/subscribers/ioredis/index.js +2 -12
- package/lib/subscribers/langchain/runnable-stream.js +94 -99
- package/lib/subscribers/langchain/runnable.js +119 -33
- package/lib/subscribers/langchain/tool-callback-manager.js +4 -3
- package/lib/subscribers/langchain/tool.js +24 -22
- package/lib/subscribers/langchain/vectorstore.js +6 -26
- package/lib/subscribers/langgraph/config.js +39 -0
- package/lib/subscribers/langgraph/graph-stream.js +131 -0
- package/lib/subscribers/mcp-sdk/client-request.js +6 -12
- package/lib/subscribers/middleware-wrapper.js +5 -1
- package/lib/subscribers/openai/chat.js +204 -41
- package/lib/subscribers/openai/client.js +10 -3
- package/lib/subscribers/openai/embeddings.js +22 -31
- package/lib/subscribers/pg/connect.js +6 -1
- package/lib/subscribers/pg/native-connect.js +1 -1
- package/lib/subscribers/pg/native-query.js +1 -1
- package/lib/subscribers/pg/query.js +6 -1
- package/lib/subscribers/utils.js +38 -0
- package/lib/transaction/trace/partial-trace.js +49 -20
- package/lib/transaction/trace/segment.js +19 -1
- package/package.json +1 -1
- package/lib/subscribers/google-genai/base.js +0 -123
- package/lib/subscribers/langchain/base.js +0 -164
- package/lib/subscribers/openai/base.js +0 -46
- package/lib/subscribers/openai/utils.js +0 -371
package/NEWS.md
CHANGED
|
@@ -1,3 +1,61 @@
|
|
|
1
|
+
### v13.12.0 (2026-02-02)
|
|
2
|
+
|
|
3
|
+
#### Features
|
|
4
|
+
|
|
5
|
+
* Added instrumentation support for `@langchain/langgraph` ([#3645](https://github.com/newrelic/node-newrelic/pull/3645)) ([f339675](https://github.com/newrelic/node-newrelic/commit/f3396754fb182a3f0af09488a8abb1981b6f3e3f))
|
|
6
|
+
* Added `timestamp` to `LlmChatCompletionSummary` messages
|
|
7
|
+
* Added `timestamp` to AWS Bedrock `LlmChatCompletionSummary` ([#3702](https://github.com/newrelic/node-newrelic/pull/3702)) ([430d1dd](https://github.com/newrelic/node-newrelic/commit/430d1dd5ef847203045486a054142df3a02c7c1c))
|
|
8
|
+
* Added `timestamp` to Google Gen AI `LlmChatCompletionSummary` ([#3690](https://github.com/newrelic/node-newrelic/pull/3690)) ([7748e26](https://github.com/newrelic/node-newrelic/commit/7748e26aceb7b050b13dc686c852513026f043ac))
|
|
9
|
+
* Added `timestamp` to LangChain `LlmChatCompletionSummary` ([#3701](https://github.com/newrelic/node-newrelic/pull/3701)) ([7472118](https://github.com/newrelic/node-newrelic/commit/747211814396e4bf52dc63d249c0ec045b35457a))
|
|
10
|
+
* Added compact mode for span links ([#3681](https://github.com/newrelic/node-newrelic/pull/3681)) ([6ff6961](https://github.com/newrelic/node-newrelic/commit/6ff6961e6b46feb14638da97a95d0891a0d88645))
|
|
11
|
+
|
|
12
|
+
#### Bug fixes
|
|
13
|
+
|
|
14
|
+
* Updated langchain tool instrumentation to properly redefine the segment name on every call ([#3691](https://github.com/newrelic/node-newrelic/pull/3691)) ([4df6068](https://github.com/newrelic/node-newrelic/commit/4df6068d23b5da276486bd3d3f0c3af4b748c4a8))
|
|
15
|
+
|
|
16
|
+
#### Documentation
|
|
17
|
+
|
|
18
|
+
* Updated compatibility report ([#3699](https://github.com/newrelic/node-newrelic/pull/3699)) ([40b6b81](https://github.com/newrelic/node-newrelic/commit/40b6b813e7b6490c2e866a33cb1a7492b35d2a36))
|
|
19
|
+
|
|
20
|
+
### v13.11.0 (2026-01-29)
|
|
21
|
+
|
|
22
|
+
#### Features
|
|
23
|
+
|
|
24
|
+
* Added `timestamp` to AIM `LlmChatCompletionMessage` messages
|
|
25
|
+
* Added `timestamp` to AWS Bedrock `LlmChatCompletionMessage` ([#3685](https://github.com/newrelic/node-newrelic/pull/3685)) ([ea297c8](https://github.com/newrelic/node-newrelic/commit/ea297c86772c1ae377a16e636db8bbcf53ef1547))
|
|
26
|
+
* Added `timestamp` to Google Gen AI `LlmChatCompletionMessage` ([#3686](https://github.com/newrelic/node-newrelic/pull/3686)) ([f4dd09e](https://github.com/newrelic/node-newrelic/commit/f4dd09ec332c47396277f6aaeff2f6f15daad97a))
|
|
27
|
+
* Added `timestamp` to LangChain input `LlmChatCompletionMessage` ([#3658](https://github.com/newrelic/node-newrelic/pull/3658)) ([0bce235](https://github.com/newrelic/node-newrelic/commit/0bce235ce02f772bc089bf947afadf312c735c1b))
|
|
28
|
+
* Added timestamp to OpenAI `LlmChatCompletionSummary` ([#3689](https://github.com/newrelic/node-newrelic/pull/3689)) ([55b0310](https://github.com/newrelic/node-newrelic/commit/55b03100323eefb3578816cff69f2b919e4b7bdd))
|
|
29
|
+
* Additional support for partial granularity traces (Not available for production use)
|
|
30
|
+
* Added essential tracing mode for span links ([#3670](https://github.com/newrelic/node-newrelic/pull/3670)) ([83dfb84](https://github.com/newrelic/node-newrelic/commit/83dfb84b842e0245392425ce61e92a2789fcaf06))
|
|
31
|
+
* Updated `Supportability/Nodejs/PartialGranularity/NrIds/Dropped` metric to only keep track of spans that got dropped when `nr.ids` execeeded 63 spans. ([#3684](https://github.com/newrelic/node-newrelic/pull/3684)) ([9797cf3](https://github.com/newrelic/node-newrelic/commit/9797cf38aa2808b29da9623158edb221b1819548))
|
|
32
|
+
|
|
33
|
+
#### Bug fixes
|
|
34
|
+
|
|
35
|
+
* Added defensive code in `lib/subscribers/middleware-wrapper.js` to prevent crash when attempting to associate an error on an incoming http request ([#3697](https://github.com/newrelic/node-newrelic/pull/3697)) ([658e78b](https://github.com/newrelic/node-newrelic/commit/658e78ba564eae996d790bb3334bda6847c7e2bd))
|
|
36
|
+
* Updated `pg` instrumentation to properly capture the TraceSegment duration for promise based queries ([#3695](https://github.com/newrelic/node-newrelic/pull/3695)) ([0051697](https://github.com/newrelic/node-newrelic/commit/005169776a3f3fe9d988174857b3c96e6131aea3))
|
|
37
|
+
|
|
38
|
+
#### Code refactoring
|
|
39
|
+
|
|
40
|
+
* Added AIM abstract subscribers and updated all AIM subscribers to use them ([#3676](https://github.com/newrelic/node-newrelic/pull/3676)) ([08cd38b](https://github.com/newrelic/node-newrelic/commit/08cd38b5b34b4f1a2636ccc12d359484af6b3e83))
|
|
41
|
+
|
|
42
|
+
#### Documentation
|
|
43
|
+
|
|
44
|
+
* Updated compatibility report ([#3675](https://github.com/newrelic/node-newrelic/pull/3675)) ([cc898f4](https://github.com/newrelic/node-newrelic/commit/cc898f4d8b0f520e120f330ff9e90fe7baaebedc))
|
|
45
|
+
|
|
46
|
+
#### Miscellaneous chores
|
|
47
|
+
|
|
48
|
+
* Added TimedEvent limit of 100 ([#3683](https://github.com/newrelic/node-newrelic/pull/3683)) ([0d595d8](https://github.com/newrelic/node-newrelic/commit/0d595d8579b5a58e49908bc24321ce7b526ddff2))
|
|
49
|
+
* Added supportability metrics `Supportability/Nodejs/SpanEvent/<Links|Events>/Dropped` when limit of 100 is exceeded ([#3688](https://github.com/newrelic/node-newrelic/pull/3688)) ([a247d31](https://github.com/newrelic/node-newrelic/commit/a247d31fc9c24752be50a73d4ddfc7a088a68ce6))
|
|
50
|
+
* Removed eslint disable next line for `sonarjs/no-dead-store` ([#3692](https://github.com/newrelic/node-newrelic/pull/3692)) ([593fa15](https://github.com/newrelic/node-newrelic/commit/593fa15a033437b95fe375cf6034b8453c7d2b36))
|
|
51
|
+
|
|
52
|
+
#### Continuous Integration
|
|
53
|
+
* Fixed CI issue when sec-agent present ([#3677](https://github.com/newrelic/node-newrelic/pull/3677)) ([23da654](https://github.com/newrelic/node-newrelic/commit/23da6547e72f49bf798046330e8cfa74a0b4d2af))
|
|
54
|
+
|
|
55
|
+
#### Tests
|
|
56
|
+
|
|
57
|
+
* Assert that span events drop in partial traces ([#3680](https://github.com/newrelic/node-newrelic/pull/3680)) ([25e3781](https://github.com/newrelic/node-newrelic/commit/25e3781098343b9ca9e4aed0cd5c9e0f94266926))
|
|
58
|
+
|
|
1
59
|
### v13.10.0 (2026-01-20)
|
|
2
60
|
|
|
3
61
|
#### Features
|
|
@@ -75,9 +75,10 @@ function isStreamingEnabled({ commandName, config }) {
|
|
|
75
75
|
function recordEvent({ agent, type, msg }) {
|
|
76
76
|
msg.serialize()
|
|
77
77
|
const llmContext = extractLlmContext(agent)
|
|
78
|
+
const timestamp = msg?.timestamp ?? Date.now()
|
|
78
79
|
|
|
79
80
|
agent.customEventAggregator.add([
|
|
80
|
-
{ type, timestamp
|
|
81
|
+
{ type, timestamp },
|
|
81
82
|
Object.assign({}, msg, llmContext)
|
|
82
83
|
])
|
|
83
84
|
}
|
|
@@ -296,7 +296,6 @@ module.exports = function initialize(shim, when) {
|
|
|
296
296
|
ret = agent.tracer.bindFunction(fn, context, true).apply(this, arguments)
|
|
297
297
|
} finally {
|
|
298
298
|
if (ret && typeof ret.then === 'function') {
|
|
299
|
-
// eslint-disable-next-line sonarjs/no-dead-store
|
|
300
299
|
ret = ctx.next[symbols.context].continue(ret)
|
|
301
300
|
}
|
|
302
301
|
}
|
|
@@ -38,7 +38,7 @@ class LlmChatCompletionMessage extends LlmEvent {
|
|
|
38
38
|
params = Object.assign({}, defaultParams, params)
|
|
39
39
|
super(params)
|
|
40
40
|
|
|
41
|
-
const { agent, content, isResponse, index, completionId, role } = params
|
|
41
|
+
const { agent, content, isResponse, index, completionId, role, segment } = params
|
|
42
42
|
const recordContent = agent.config?.ai_monitoring?.record_content?.enabled
|
|
43
43
|
|
|
44
44
|
this.is_response = isResponse
|
|
@@ -46,6 +46,10 @@ class LlmChatCompletionMessage extends LlmEvent {
|
|
|
46
46
|
this.sequence = index
|
|
47
47
|
this.content = recordContent === true ? content : undefined
|
|
48
48
|
this.role = role
|
|
49
|
+
if (this.is_response === false) {
|
|
50
|
+
// Only record for request/input messages
|
|
51
|
+
this.timestamp = segment.timer.start
|
|
52
|
+
}
|
|
49
53
|
|
|
50
54
|
this.#setId(index)
|
|
51
55
|
this.setTokenCount(agent)
|
|
@@ -37,6 +37,7 @@ class LlmChatCompletionSummary extends LlmEvent {
|
|
|
37
37
|
this['request.temperature'] = cmd.temperature
|
|
38
38
|
this['response.number_of_messages'] = (cmd.prompt.length ?? 0) + (res.completions.length ?? 0)
|
|
39
39
|
|
|
40
|
+
this.timestamp = segment.timer.start
|
|
40
41
|
this.setTokens(agent)
|
|
41
42
|
}
|
|
42
43
|
|
|
@@ -21,20 +21,26 @@ module.exports = class LlmErrorMessage {
|
|
|
21
21
|
* @param {LlmVectorStoreSearch} [params.vectorsearch] Details about the vector
|
|
22
22
|
* search if it was a vector search event.
|
|
23
23
|
* @param {LlmTool} [params.tool] Details about the tool event if it was a tool event.
|
|
24
|
+
* @param {object} [params.aiAgent] Details about the AI agent event if it was an AI agent event.
|
|
24
25
|
* @param {boolean} [params.useNameAsCode] defaults to false, only Bedrock sets it to true so far
|
|
25
26
|
*/
|
|
26
|
-
constructor({ response, cause, summary = {}, embedding = {}, vectorsearch = {}, tool = {}, useNameAsCode = false } = {}) {
|
|
27
|
+
constructor({ response, cause, summary = {}, embedding = {}, vectorsearch = {}, tool = {}, aiAgent = {}, useNameAsCode = false } = {}) {
|
|
27
28
|
this['http.statusCode'] = response?.statusCode ?? response?.status ?? cause?.status
|
|
28
29
|
this['error.message'] = cause?.message
|
|
29
|
-
this['error.code'] = response?.code ?? cause?.error?.code
|
|
30
|
+
this['error.code'] = response?.code ?? cause?.error?.code ?? cause?.code
|
|
30
31
|
if (useNameAsCode) {
|
|
31
32
|
this['error.code'] = cause?.name
|
|
32
33
|
}
|
|
34
|
+
if (cause?.['lc_error_code']) {
|
|
35
|
+
// this is where langchain error codes live
|
|
36
|
+
this['error.code'] = cause['lc_error_code']
|
|
37
|
+
}
|
|
33
38
|
this['error.param'] = response?.param ?? cause?.error?.param
|
|
34
39
|
this.completion_id = summary?.id
|
|
35
40
|
this.embedding_id = embedding?.id
|
|
36
41
|
this.vector_store_id = vectorsearch?.id
|
|
37
42
|
this.tool_id = tool?.id
|
|
43
|
+
this.agent_id = aiAgent?.id
|
|
38
44
|
|
|
39
45
|
if (embedding?.vendor === 'gemini' || summary?.vendor === 'gemini') {
|
|
40
46
|
this._handleGemini(cause)
|
|
@@ -33,6 +33,11 @@ module.exports = class LlmChatCompletionMessage extends LlmEvent {
|
|
|
33
33
|
this.content = this.is_response ? message?.parts?.[0]?.text : message
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
// only add timestamp for request/input messages
|
|
37
|
+
if (this.is_response === false) {
|
|
38
|
+
this.timestamp = segment.timer.start
|
|
39
|
+
}
|
|
40
|
+
|
|
36
41
|
this.setTokenCount(agent, request, response)
|
|
37
42
|
}
|
|
38
43
|
|
|
@@ -21,6 +21,7 @@ module.exports = class LlmChatCompletionSummary extends LlmEvent {
|
|
|
21
21
|
this['request.max_tokens'] = request.config?.maxOutputTokens
|
|
22
22
|
this['request.temperature'] = request.config?.temperature
|
|
23
23
|
|
|
24
|
+
this.timestamp = segment.timer.start
|
|
24
25
|
this.setTokens(agent, request, response)
|
|
25
26
|
}
|
|
26
27
|
|
|
@@ -38,19 +38,25 @@ class LangChainCompletionMessage extends LangChainEvent {
|
|
|
38
38
|
constructor(params = defaultParams) {
|
|
39
39
|
params = Object.assign({}, defaultParams, params)
|
|
40
40
|
super(params)
|
|
41
|
-
const { agent } = params
|
|
41
|
+
const { agent, runId, segment, sequence, role, content, completionId, isResponse } = params
|
|
42
42
|
|
|
43
|
-
if (
|
|
44
|
-
this.id = `${
|
|
43
|
+
if (runId) {
|
|
44
|
+
this.id = `${runId}-${sequence}`
|
|
45
45
|
} else {
|
|
46
|
-
this.id = `${this.id}-${
|
|
46
|
+
this.id = `${this.id}-${sequence}`
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
this.sequence =
|
|
50
|
-
this.completion_id =
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
this.sequence = sequence
|
|
50
|
+
this.completion_id = completionId
|
|
51
|
+
|
|
52
|
+
this.is_response = isResponse ?? false
|
|
53
|
+
if (this.is_response === false) {
|
|
54
|
+
// `timestamp` is only required for input/request messages.
|
|
55
|
+
this.timestamp = segment.timer.start
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (role) {
|
|
59
|
+
this.role = role
|
|
54
60
|
} else {
|
|
55
61
|
// As a backup, we can infer the role based on if it
|
|
56
62
|
// is a response or not.
|
|
@@ -58,7 +64,7 @@ class LangChainCompletionMessage extends LangChainEvent {
|
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
if (agent.config.ai_monitoring.record_content.enabled === true) {
|
|
61
|
-
this.content =
|
|
67
|
+
this.content = content
|
|
62
68
|
}
|
|
63
69
|
}
|
|
64
70
|
}
|
|
@@ -33,8 +33,9 @@ class LangChainCompletionSummary extends LangChainEvent {
|
|
|
33
33
|
super(params)
|
|
34
34
|
const { segment } = params
|
|
35
35
|
|
|
36
|
-
this.duration = segment
|
|
36
|
+
this.duration = segment.getDurationInMillis()
|
|
37
37
|
this['response.number_of_messages'] = params.messages?.length
|
|
38
|
+
this.timestamp = segment.timer.start
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const BaseLlmEvent = require('../event')
|
|
7
|
+
const { makeId } = require('../../util/hashes')
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {object} LangGraphAgentEventParams
|
|
11
|
+
* @property {Agent} agent The New Relic agent instance.
|
|
12
|
+
* @property {string} name The name of the LangGraph agent, defaults to 'agent'.
|
|
13
|
+
* @property {object} segment The associated NR segment.
|
|
14
|
+
* @property {object} transaction The associated NR transaction.
|
|
15
|
+
* @property {boolean} error A boolean flag to indicate if an error occurred.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
module.exports = class LangGraphAgentEvent extends BaseLlmEvent {
|
|
19
|
+
id = makeId(36)
|
|
20
|
+
span_id
|
|
21
|
+
trace_id
|
|
22
|
+
ingest_source = 'Node'
|
|
23
|
+
vendor = 'langgraph'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {LangGraphAgentEventParams} params should contain all necessary and optional LangGraph data
|
|
27
|
+
*/
|
|
28
|
+
constructor(params) {
|
|
29
|
+
super(params)
|
|
30
|
+
const { agent, segment, transaction, error = false, name = 'agent' } = params
|
|
31
|
+
|
|
32
|
+
this.name = name
|
|
33
|
+
this.span_id = segment.id
|
|
34
|
+
this.trace_id = transaction.traceId
|
|
35
|
+
this.error = error
|
|
36
|
+
|
|
37
|
+
// Setting `metadata` as the NR agent instance will allow `BaseLlmEvent`
|
|
38
|
+
// to extract the relevant `llm.<user_defined_metadata>`.
|
|
39
|
+
this.metadata = agent
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -14,17 +14,19 @@ module.exports = class LlmChatCompletionSummary extends LlmEvent {
|
|
|
14
14
|
this['request.temperature'] = request.temperature
|
|
15
15
|
|
|
16
16
|
if (request?.input) {
|
|
17
|
-
// `responses.create` logic
|
|
17
|
+
// `responses.create` api logic
|
|
18
18
|
// `request.input` can be an array or a string.
|
|
19
19
|
const requestLength = Array.isArray(request?.input) ? request.input.length : 1
|
|
20
20
|
this['response.number_of_messages'] = requestLength + (response?.output?.length ?? 0)
|
|
21
21
|
this['response.choices.finish_reason'] = response?.status
|
|
22
22
|
} else {
|
|
23
|
-
// `chat.completions.create` logic
|
|
23
|
+
// `chat.completions.create` api logic
|
|
24
24
|
this['response.number_of_messages'] = request?.messages?.length + response?.choices?.length
|
|
25
25
|
this['response.choices.finish_reason'] = response?.choices?.[0]?.finish_reason
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
this.timestamp = segment.timer.start
|
|
29
|
+
|
|
28
30
|
this.setTokens(agent, request, response)
|
|
29
31
|
}
|
|
30
32
|
|
package/lib/metrics/names.js
CHANGED
|
@@ -171,7 +171,8 @@ const AI = {
|
|
|
171
171
|
COMPLETION: 'Llm/completion',
|
|
172
172
|
TOOL: 'Llm/tool',
|
|
173
173
|
CHAIN: 'Llm/chain',
|
|
174
|
-
VECTORSTORE: 'Llm/vectorstore'
|
|
174
|
+
VECTORSTORE: 'Llm/vectorstore',
|
|
175
|
+
AGENT: 'Llm/agent'
|
|
175
176
|
}
|
|
176
177
|
|
|
177
178
|
AI.GEMINI = {
|
|
@@ -199,6 +200,11 @@ AI.LANGCHAIN = {
|
|
|
199
200
|
VECTORSTORE: `${AI.VECTORSTORE}/LangChain`
|
|
200
201
|
}
|
|
201
202
|
|
|
203
|
+
AI.LANGGRAPH = {
|
|
204
|
+
TRACKING_PREFIX: `${AI.TRACKING_PREFIX}/LangGraph`,
|
|
205
|
+
AGENT: `${AI.AGENT}/LangGraph`
|
|
206
|
+
}
|
|
207
|
+
|
|
202
208
|
const MCP = {
|
|
203
209
|
TRACKING_PREFIX: `${AI.TRACKING_PREFIX}/MCP`,
|
|
204
210
|
TOOL: `${AI.TOOL}/MCP`,
|
|
@@ -325,7 +331,9 @@ const SPAN_EVENTS = {
|
|
|
325
331
|
SEEN: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'TotalEventsSeen',
|
|
326
332
|
SENT: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'TotalEventsSent',
|
|
327
333
|
DROPPED: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'Discarded',
|
|
328
|
-
LIMIT: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'Limit'
|
|
334
|
+
LIMIT: SUPPORTABILITY.PREFIX + SPAN_EVENT_PREFIX + 'Limit',
|
|
335
|
+
LINKS_DROPPED: SUPPORTABILITY.NODEJS + '/' + SPAN_EVENT_PREFIX + 'Links/Dropped',
|
|
336
|
+
EVENTS_DROPPED: SUPPORTABILITY.NODEJS + '/' + SPAN_EVENT_PREFIX + 'Events/Dropped'
|
|
329
337
|
}
|
|
330
338
|
|
|
331
339
|
const INFINITE_TRACING = {
|
|
@@ -23,6 +23,7 @@ const {
|
|
|
23
23
|
transformTemplate
|
|
24
24
|
} = require('./utils.js')
|
|
25
25
|
const defaultLogger = require('#agentlib/logger.js').child({ component: 'span-processor' })
|
|
26
|
+
const { SPAN_EVENTS } = require('#agentlib/metrics/names.js')
|
|
26
27
|
|
|
27
28
|
const {
|
|
28
29
|
SPAN_STATUS_CODE
|
|
@@ -155,7 +156,10 @@ module.exports = class NrSpanProcessor {
|
|
|
155
156
|
event: span.events[i],
|
|
156
157
|
spanContext
|
|
157
158
|
})
|
|
158
|
-
segment.addTimedEvent(event)
|
|
159
|
+
const added = segment.addTimedEvent(event)
|
|
160
|
+
if (!added) {
|
|
161
|
+
this.agent.metrics.getOrCreateMetric(SPAN_EVENTS.EVENTS_DROPPED).incrementCallCount()
|
|
162
|
+
}
|
|
159
163
|
}
|
|
160
164
|
}
|
|
161
165
|
|
|
@@ -181,7 +185,10 @@ module.exports = class NrSpanProcessor {
|
|
|
181
185
|
spanContext,
|
|
182
186
|
timestamp
|
|
183
187
|
})
|
|
184
|
-
segment.addSpanLink(link)
|
|
188
|
+
const added = segment.addSpanLink(link)
|
|
189
|
+
if (!added) {
|
|
190
|
+
this.agent.metrics.getOrCreateMetric(SPAN_EVENTS.LINKS_DROPPED).incrementCallCount()
|
|
191
|
+
}
|
|
185
192
|
}
|
|
186
193
|
}
|
|
187
194
|
|
package/lib/spans/span-event.js
CHANGED
|
@@ -60,6 +60,8 @@ class SpanIntrinsics {
|
|
|
60
60
|
this.duration = null
|
|
61
61
|
this['nr.entryPoint'] = null
|
|
62
62
|
this['nr.pg'] = null
|
|
63
|
+
this['nr.durations'] = null
|
|
64
|
+
this['nr.ids'] = null
|
|
63
65
|
this['span.kind'] = null
|
|
64
66
|
this.trustedParentId = null
|
|
65
67
|
this.tracingVendors = null
|
|
@@ -316,6 +318,14 @@ class SpanEvent {
|
|
|
316
318
|
* @returns {SpanEvent|null} the span after applying the rules, or null if dropped
|
|
317
319
|
*/
|
|
318
320
|
applyPartialTraceRules({ isEntry, partialTrace }) {
|
|
321
|
+
// in essential and compact mode - we need to remove the non intrinsic attributes from span links early in this process because there can be an entry span,
|
|
322
|
+
// llm span exit span or span with no entity relationship attributes can have non intrinsics on it.
|
|
323
|
+
if (partialTrace.type !== PARTIAL_TYPES.REDUCED) {
|
|
324
|
+
for (const link of this.spanLinks) {
|
|
325
|
+
link.removeNonIntrinsicsAttrs()
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
319
329
|
if (isEntry) {
|
|
320
330
|
this.addIntrinsicAttribute('nr.pg', true)
|
|
321
331
|
logger.trace('Span %s is an entry point, keeping span unchanged.', this.intrinsics.name)
|
package/lib/spans/span-link.js
CHANGED
|
@@ -86,6 +86,11 @@ class SpanLink {
|
|
|
86
86
|
filterNulls(this.agentAttributes.get(DESTINATIONS.TRANS_SEGMENT))
|
|
87
87
|
]
|
|
88
88
|
}
|
|
89
|
+
|
|
90
|
+
removeNonIntrinsicsAttrs() {
|
|
91
|
+
this.userAttributes = Object.create(null)
|
|
92
|
+
this.agentAttributes = Object.create(null)
|
|
93
|
+
}
|
|
89
94
|
}
|
|
90
95
|
|
|
91
96
|
function filterNulls(inputObj) {
|
|
@@ -18,6 +18,7 @@ const subscribers = {
|
|
|
18
18
|
...require('./subscribers/ioredis/config'),
|
|
19
19
|
...require('./subscribers/iovalkey/config'),
|
|
20
20
|
...require('./subscribers/langchain/config'),
|
|
21
|
+
...require('./subscribers/langgraph/config'),
|
|
21
22
|
...require('./subscribers/mcp-sdk/config'),
|
|
22
23
|
...require('./subscribers/mysql/config'),
|
|
23
24
|
...require('./subscribers/mysql2/config'),
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
const Subscriber = require('../base')
|
|
8
|
+
const { DESTINATIONS } = require('#agentlib/config/attribute-filter.js')
|
|
9
|
+
const { extractLlmContext } = require('#agentlib/util/llm-utils.js')
|
|
10
|
+
|
|
11
|
+
class AiMonitoringSubscriber extends Subscriber {
|
|
12
|
+
/**
|
|
13
|
+
* @param {object} params constructor params object
|
|
14
|
+
* @param {object} params.agent A New Relic Node.js agent instance.
|
|
15
|
+
* @param {object} params.logger An agent logger instance.
|
|
16
|
+
* @param {string} params.packageName The package name being instrumented.
|
|
17
|
+
* This is what a developer would provide to the `require` function.
|
|
18
|
+
* @param {string} params.channelName A unique name for the diagnostics channel
|
|
19
|
+
* that will be created and monitored.
|
|
20
|
+
* @param {string} params.name name of segment for a given subscriber
|
|
21
|
+
* @param {string} params.trackingPrefix prefix for the tracking metric for a given subscriber
|
|
22
|
+
*/
|
|
23
|
+
constructor({ agent, logger, packageName, channelName, name, trackingPrefix }) {
|
|
24
|
+
super({ agent, logger, packageName, channelName })
|
|
25
|
+
this.name = name
|
|
26
|
+
this.trackingPrefix = trackingPrefix
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get enabled() {
|
|
30
|
+
return super.enabled && this.agent.config.ai_monitoring.enabled === true
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
set name(name) {
|
|
34
|
+
if (!name) {
|
|
35
|
+
throw new Error('subscriber must pass in `name`')
|
|
36
|
+
}
|
|
37
|
+
this._name = name
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get name() {
|
|
41
|
+
return this._name
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
set trackingPrefix(prefix) {
|
|
45
|
+
if (!prefix) {
|
|
46
|
+
throw new Error('subscriber must pass in `trackingPrefix`')
|
|
47
|
+
}
|
|
48
|
+
this._prefix = prefix
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get trackingPrefix() {
|
|
52
|
+
return this._prefix
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Increments the tracking metric and sets the llm attribute on transactions
|
|
57
|
+
*
|
|
58
|
+
* @param {object} params input params
|
|
59
|
+
* @param {Transaction} params.ctx active context
|
|
60
|
+
* @param {string} params.version package version
|
|
61
|
+
*/
|
|
62
|
+
addLlmMeta({ ctx, version }) {
|
|
63
|
+
if (!ctx?.transaction) {
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const transaction = ctx.transaction
|
|
68
|
+
const trackingMetric = `${this.trackingPrefix}/${version}`
|
|
69
|
+
this.agent.metrics.getOrCreateMetric(trackingMetric).incrementCallCount()
|
|
70
|
+
transaction.trace.attributes.addAttribute(DESTINATIONS.TRANS_EVENT, 'llm', true)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Enqueues a LLM event to the custom event aggregator
|
|
75
|
+
*
|
|
76
|
+
* @param {object} params input params
|
|
77
|
+
* @param {string} params.type LLM event type
|
|
78
|
+
* @param {object} params.msg LLM event
|
|
79
|
+
*/
|
|
80
|
+
recordEvent({ type, msg }) {
|
|
81
|
+
const llmContext = extractLlmContext(this.agent)
|
|
82
|
+
// prefer timestamp on event if present, otherwise use `Date.now`
|
|
83
|
+
const timestamp = msg?.timestamp ?? Date.now()
|
|
84
|
+
this.agent.customEventAggregator.add([
|
|
85
|
+
{ type, timestamp },
|
|
86
|
+
Object.assign({}, msg, llmContext)
|
|
87
|
+
])
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Defines the common handler for all AIM calls.
|
|
92
|
+
* All of these calls simply create a segment with a name
|
|
93
|
+
* The `this.name` must be defined in the inherited subscriber
|
|
94
|
+
* @param {object} data event passed to handler
|
|
95
|
+
* @param {Context} ctx context passed to handler
|
|
96
|
+
* @returns {Context} either new context or existing
|
|
97
|
+
*/
|
|
98
|
+
handler(data, ctx) {
|
|
99
|
+
if (!this.enabled) {
|
|
100
|
+
this.logger.debug('`ai_monitoring.enabled` is set to false, not creating segment.')
|
|
101
|
+
return ctx
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this.addLlmMeta({ ctx, version: data.moduleVersion })
|
|
105
|
+
|
|
106
|
+
return this.createSegment({
|
|
107
|
+
name: this.name,
|
|
108
|
+
ctx
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = AiMonitoringSubscriber
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
const AiMonitoringSubscriber = require('./base')
|
|
8
|
+
const LlmErrorMessage = require('#agentlib/llm-events/error-message.js')
|
|
9
|
+
|
|
10
|
+
class AiMonitoringChatSubscriber extends AiMonitoringSubscriber {
|
|
11
|
+
/**
|
|
12
|
+
* @param {object} params constructor params object
|
|
13
|
+
* @param {object} params.agent A New Relic Node.js agent instance.
|
|
14
|
+
* @param {object} params.logger An agent logger instance.
|
|
15
|
+
* @param {string} params.packageName The package name being instrumented.
|
|
16
|
+
* This is what a developer would provide to the `require` function.
|
|
17
|
+
* @param {string} params.channelName A unique name for the diagnostics channel
|
|
18
|
+
* that will be created and monitored.
|
|
19
|
+
* @param {string} params.name name of segment for a given subscriber
|
|
20
|
+
* @param {string} params.trackingPrefix prefix for the tracking metric for a given subscriber
|
|
21
|
+
*/
|
|
22
|
+
constructor({ agent, logger, packageName, channelName, name, trackingPrefix }) {
|
|
23
|
+
super({ agent, logger, packageName, channelName, name, trackingPrefix })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get streamingEnabled() {
|
|
27
|
+
return this.agent.config.ai_monitoring.streaming.enabled === true
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Function that must be implemented by inherited subscriber to create an llm completion message.
|
|
32
|
+
*
|
|
33
|
+
* @param {object} params to function
|
|
34
|
+
* @param {Context} params.ctx active context
|
|
35
|
+
* @param {object} params.request request made to method on a given llm library
|
|
36
|
+
* @param {object} params.response response from method on a given llm library
|
|
37
|
+
* @param {object|string} params.message the message object/string used to create llm completion message
|
|
38
|
+
* @param {string} params.completionId the id of the llm completion summary for a given conversation
|
|
39
|
+
* @param {number} params.index index of message for a given conversation
|
|
40
|
+
* returns {object} a llm completion message instance for the given LLM
|
|
41
|
+
*/
|
|
42
|
+
createCompletionMessage({ ctx, request, response, message, completionId, index }) {
|
|
43
|
+
throw new Error('createCompletionMessage must be implemented by your subscriber')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Function that must be implemented by inherited subscriber to create the llm completion summary.
|
|
48
|
+
*
|
|
49
|
+
* @param {object} params to function
|
|
50
|
+
* @param {Context} params.ctx active context
|
|
51
|
+
* @param {object} params.request request made to method on a given llm library
|
|
52
|
+
* @param {object} params.response response from method on a given llm library
|
|
53
|
+
* @param {object} params.err error if present
|
|
54
|
+
* @param {object} params.metadata used only for langchain events at the moment
|
|
55
|
+
* @param {Array} params.tags used only for langchain events at the moment
|
|
56
|
+
* returns {object} a llm completion summary instance for the given LLM
|
|
57
|
+
*/
|
|
58
|
+
createCompletionSummary({ ctx, request, response, err, metadata, tags }) {
|
|
59
|
+
throw new Error('createCompletionSummary must be implemented by your subscriber')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Function that must be implemented by inherited subscriber to retrieve the relevant
|
|
64
|
+
* messages for a given chat conversation.
|
|
65
|
+
*
|
|
66
|
+
* @param {object} params to function
|
|
67
|
+
* @param {object} params.request request to instrumented function
|
|
68
|
+
* @param {object} params.response response to instrumented function
|
|
69
|
+
* returns {Array} an array of relevant messages used to construct llm completion messages
|
|
70
|
+
*/
|
|
71
|
+
getMessages({ request, response }) {
|
|
72
|
+
throw new Error('getMessages must be implemented by your subscriber')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Function to create both llm completion summary and messages
|
|
77
|
+
* as well as assign errors to transaction if present
|
|
78
|
+
* @param {object} params to function
|
|
79
|
+
* @param {Context} params.ctx active context
|
|
80
|
+
* @param {object} params.request request made to method on a given llm library
|
|
81
|
+
* @param {object} params.response response from method on a given llm library
|
|
82
|
+
* @param {object} params.err error if present
|
|
83
|
+
* @param {object} params.metadata used only for langchain events at the moment
|
|
84
|
+
* @param {Array} params.tags used only for langchain events at the moment
|
|
85
|
+
*/
|
|
86
|
+
recordChatCompletionEvents({ ctx, request, response, err, metadata = {}, tags = [] }) {
|
|
87
|
+
if (!this.enabled) {
|
|
88
|
+
this.logger.debug('config.ai_monitoring.enabled is set to false, not creating chat completion events.')
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!(ctx?.segment || ctx?.transaction)) {
|
|
93
|
+
this.logger.debug('Empty context, not creating completion events.')
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Explicitly end segment to provide consistent duration
|
|
98
|
+
// for both LLM events and the segment
|
|
99
|
+
ctx.segment.end()
|
|
100
|
+
const completionSummary = this.createCompletionSummary({ ctx, request, response, err, metadata, tags })
|
|
101
|
+
this.recordEvent({ type: 'LlmChatCompletionSummary', msg: completionSummary })
|
|
102
|
+
|
|
103
|
+
const messages = this.getMessages({ request, response })
|
|
104
|
+
for (let i = 0; i < messages.length; i++) {
|
|
105
|
+
const message = messages[i]
|
|
106
|
+
const completionMessage = this.createCompletionMessage({ ctx, completionId: completionSummary.id, index: i, message, request, response })
|
|
107
|
+
this.recordEvent({ type: 'LlmChatCompletionMessage', msg: completionMessage })
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (err) {
|
|
111
|
+
const llmError = new LlmErrorMessage({ cause: err, summary: completionSummary, response })
|
|
112
|
+
this.agent.errors.add(ctx.transaction, err, llmError)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = AiMonitoringChatSubscriber
|