newrelic 13.13.0 → 13.14.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 +33 -0
- package/lib/aggregators/base-aggregator.js +2 -0
- package/lib/aggregators/profiling-aggregator.js +6 -1
- package/lib/errors/index.js +81 -21
- package/lib/harvester.js +18 -3
- package/lib/instrumentation/@azure/functions.js +45 -1
- package/lib/instrumentation/aws-sdk/v3/bedrock.js +10 -11
- package/lib/llm-events/aws-bedrock/bedrock-command.js +1 -1
- package/lib/llm-events/aws-bedrock/bedrock-response.js +1 -1
- package/lib/llm-events/aws-bedrock/chat-completion-message.js +40 -64
- package/lib/llm-events/aws-bedrock/chat-completion-summary.js +37 -43
- package/lib/llm-events/aws-bedrock/embedding.js +31 -29
- package/lib/llm-events/aws-bedrock/index.js +4 -9
- package/lib/llm-events/error-message.js +2 -2
- package/lib/llm-events/google-genai/chat-completion-message.js +24 -30
- package/lib/llm-events/google-genai/chat-completion-summary.js +31 -11
- package/lib/llm-events/google-genai/embedding.js +25 -12
- package/lib/llm-events/google-genai/get-usage-tokens.js +26 -0
- package/lib/llm-events/google-genai/index.js +4 -12
- package/lib/llm-events/langgraph/agent.js +13 -29
- package/lib/profiling/index.js +2 -1
- package/lib/spans/span-event.js +8 -8
- package/lib/subscribers/ai-monitoring/embedding.js +1 -1
- package/lib/subscribers/google-genai/config.js +20 -2
- package/lib/subscribers/google-genai/embed-content.js +11 -1
- package/lib/subscribers/google-genai/generate-content.js +37 -9
- package/lib/subscribers/langgraph/graph-stream.js +3 -3
- package/lib/transaction/trace/partial-trace.js +9 -6
- package/package.json +1 -1
- package/lib/llm-events/aws-bedrock/event.js +0 -79
- package/lib/llm-events/event.js +0 -130
- package/lib/llm-events/google-genai/event.js +0 -36
- package/lib/{llm-events/aws-bedrock → instrumentation/aws-sdk/v3}/converse-stream-handler.js +0 -0
- package/lib/{llm-events/aws-bedrock → instrumentation/aws-sdk/v3}/stream-handler.js +0 -0
- package/lib/llm-events/aws-bedrock/{utils.js → stringify-message.js} +5 -5
package/NEWS.md
CHANGED
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
### v13.14.0 (2026-02-24)
|
|
2
|
+
|
|
3
|
+
#### Features
|
|
4
|
+
|
|
5
|
+
* Added ability to delay the start of the Profiler and run for n milliseconds before shutting down. ([#3758](https://github.com/newrelic/node-newrelic/pull/3758)) ([8503d25](https://github.com/newrelic/node-newrelic/commit/8503d252dce31fa5ba0cbb4f146511155de48f88))
|
|
6
|
+
* Added functionality to ingest logs from Azure Functions App automatically ([#3772](https://github.com/newrelic/node-newrelic/pull/3772)) ([0c70ba2](https://github.com/newrelic/node-newrelic/commit/0c70ba20d2c03af03605ebc12baadbfd00c8733d))
|
|
7
|
+
* Added support for error causes ([#3769](https://github.com/newrelic/node-newrelic/pull/3769)) ([7c5aae3](https://github.com/newrelic/node-newrelic/commit/7c5aae3ac8b14e781a0740ffc57c2b2defc4300b))
|
|
8
|
+
|
|
9
|
+
#### Bug fixes
|
|
10
|
+
|
|
11
|
+
* Updated `@google/genai` subscriber to handle the new `embedContentInternal` method in 1.42.0 ([#3771](https://github.com/newrelic/node-newrelic/pull/3771)) ([e96c974](https://github.com/newrelic/node-newrelic/commit/e96c974a1aa8c15caebd66004c939c43f61cc0e2))
|
|
12
|
+
|
|
13
|
+
#### Code refactoring
|
|
14
|
+
|
|
15
|
+
* AWS Bedrock llm events refactor ([#3759](https://github.com/newrelic/node-newrelic/pull/3759)) ([7ff953d](https://github.com/newrelic/node-newrelic/commit/7ff953dbda4529591438811b8b58cfada3767f80))
|
|
16
|
+
* Google Gen AI LLM event refactor ([#3748](https://github.com/newrelic/node-newrelic/pull/3748)) ([f1e3a0e](https://github.com/newrelic/node-newrelic/commit/f1e3a0e57ea26370ce5021010ab6fe793dd5c594))
|
|
17
|
+
* LangGraph LlmAgent event, remove old LlmEvent base ([#3761](https://github.com/newrelic/node-newrelic/pull/3761)) ([39d1d9a](https://github.com/newrelic/node-newrelic/commit/39d1d9a59ca467d71d4a7db4cf437d743a24939b))
|
|
18
|
+
* Updated the reparenting logic for compact mode of partial traces to assign parent id to entry span ([#3768](https://github.com/newrelic/node-newrelic/pull/3768)) ([5196318](https://github.com/newrelic/node-newrelic/commit/5196318713cbeba113936a902ff4f092c33109cb))
|
|
19
|
+
|
|
20
|
+
#### Documentation
|
|
21
|
+
|
|
22
|
+
* Updated compatibility report ([#3774](https://github.com/newrelic/node-newrelic/pull/3774)) ([5d04bba](https://github.com/newrelic/node-newrelic/commit/5d04bba57ddb23b2aa1861cf46e393adae2c43ca))
|
|
23
|
+
|
|
24
|
+
#### Miscellaneous chores
|
|
25
|
+
|
|
26
|
+
* Added API key to enable support for GH action ([#3760](https://github.com/newrelic/node-newrelic/pull/3760)) ([a521e80](https://github.com/newrelic/node-newrelic/commit/a521e8065c525312d1170bfa725610d708b011e3))
|
|
27
|
+
* Added call to agent metadata workflow - step 1 ([#3750](https://github.com/newrelic/node-newrelic/pull/3750)) ([90eeaec](https://github.com/newrelic/node-newrelic/commit/90eeaec0e4de28c779ccab7a8f4b03448e8448bd))
|
|
28
|
+
* Added span id to all debug/trace level logs in `span.applyPartialTraceRules` ([#3766](https://github.com/newrelic/node-newrelic/pull/3766)) ([8c57230](https://github.com/newrelic/node-newrelic/commit/8c57230f3d580ace1cb5eb1611dc78f85bbe36f1))
|
|
29
|
+
* Renamed file and fix cache output ([#3757](https://github.com/newrelic/node-newrelic/pull/3757)) ([1ac00ea](https://github.com/newrelic/node-newrelic/commit/1ac00ea4718fb6e95502961d777e11c8ae71dde9))
|
|
30
|
+
* Set up metadata action to run with releases ([#3762](https://github.com/newrelic/node-newrelic/pull/3762)) ([66efb13](https://github.com/newrelic/node-newrelic/commit/66efb13b9bde33a28fb4256cb70bf313e10774bc))
|
|
31
|
+
* Updated `@langchain/core` version for langchain-openai tests ([#3763](https://github.com/newrelic/node-newrelic/pull/3763)) ([c27213e](https://github.com/newrelic/node-newrelic/commit/c27213e6ca3048463158fcfecb7e1cfcf87d4922))
|
|
32
|
+
|
|
1
33
|
### v13.13.0 (2026-02-17)
|
|
2
34
|
|
|
3
35
|
## Feature Release
|
|
@@ -8367,4 +8399,5 @@ Special thanks to Ryan Copley (@RyanCopley) for the contribution.
|
|
|
8367
8399
|
|
|
8368
8400
|
|
|
8369
8401
|
|
|
8402
|
+
|
|
8370
8403
|
|
|
@@ -160,6 +160,8 @@ class Aggregator extends EventEmitter {
|
|
|
160
160
|
|
|
161
161
|
this.defaultPeriod = this.periodMs = opts.periodMs
|
|
162
162
|
this.defaultLimit = this.limit = opts.limit
|
|
163
|
+
this.delay = opts.delay ?? 0
|
|
164
|
+
this.duration = opts.duration ?? 0
|
|
163
165
|
this.runId = opts.runId
|
|
164
166
|
this.isAsync = opts.isAsync || false
|
|
165
167
|
// function to pass in to determine if we can start a given aggregator
|
|
@@ -43,8 +43,13 @@ class ProfilingAggregator extends BaseAggregator {
|
|
|
43
43
|
* and send the gzipped binary encoded data for each profiler
|
|
44
44
|
*/
|
|
45
45
|
start() {
|
|
46
|
-
logger.trace(`${this.method} aggregator started.`)
|
|
47
46
|
this.profilingManager.register()
|
|
47
|
+
const started = this.profilingManager.start()
|
|
48
|
+
if (!started) {
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
logger.trace(`${this.method} aggregator started.`)
|
|
48
53
|
|
|
49
54
|
if (!this.sendTimer) {
|
|
50
55
|
this.sendTimer = setInterval(this.collectData.bind(this), this.periodMs)
|
package/lib/errors/index.js
CHANGED
|
@@ -18,6 +18,19 @@ const {
|
|
|
18
18
|
const synthetics = require('../synthetics')
|
|
19
19
|
const ERROR_EXPECTED_PATH = 'error.expected'
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Wraps `Error` instances for delivery to the back end. The wrapper includes
|
|
23
|
+
* the original error along with metadata, such as custom attributes, to be
|
|
24
|
+
* delivered.
|
|
25
|
+
*
|
|
26
|
+
* @class
|
|
27
|
+
* @param {object} options Configuration options for the exception
|
|
28
|
+
* @param {Error} options.error The error object to be wrapped
|
|
29
|
+
* @param {number} [options.timestamp=0] Unix timestamp (in milliseconds) when the error occurred
|
|
30
|
+
* @param {object} [options.customAttributes={}] Custom user-defined attributes associated with the error
|
|
31
|
+
* @param {object} [options.agentAttributes={}] Agent-generated attributes associated with the error
|
|
32
|
+
* @param {boolean} [options.expected] Whether this error is expected (not a true failure)
|
|
33
|
+
*/
|
|
21
34
|
class Exception {
|
|
22
35
|
constructor({ error, timestamp, customAttributes, agentAttributes, expected }) {
|
|
23
36
|
this.error = error
|
|
@@ -45,6 +58,36 @@ class Exception {
|
|
|
45
58
|
}
|
|
46
59
|
}
|
|
47
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Given the `.cause` value from an `Error` instance, serialize the chain
|
|
63
|
+
* into an object than can be attached to the agent attributes object.
|
|
64
|
+
*
|
|
65
|
+
* @param {Error|string} input The error cause to serialize.
|
|
66
|
+
* @returns {object[]} Serialized causes object array.
|
|
67
|
+
*/
|
|
68
|
+
function serializeCauses(input) {
|
|
69
|
+
if (!input) {
|
|
70
|
+
return []
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (typeof input === 'string') {
|
|
74
|
+
return [{ message: input }]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (
|
|
78
|
+
Object.hasOwn(input, 'message') === false ||
|
|
79
|
+
Object.hasOwn(input, 'stack') === false
|
|
80
|
+
) {
|
|
81
|
+
return [{ message: input.message ?? 'cause does not look like an Error instance' }]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return [{
|
|
85
|
+
message: input.message,
|
|
86
|
+
stack: input.stack,
|
|
87
|
+
cause: input.cause ? serializeCauses(input.cause) : []
|
|
88
|
+
}]
|
|
89
|
+
}
|
|
90
|
+
|
|
48
91
|
/**
|
|
49
92
|
* Given either or both of a transaction and an exception, generate an error
|
|
50
93
|
* trace in the JSON format expected by the collector. Since this will be
|
|
@@ -110,6 +153,21 @@ function createError(transaction, exception, config) {
|
|
|
110
153
|
|
|
111
154
|
maybeAddAgentAttributes(params, exception)
|
|
112
155
|
|
|
156
|
+
// We need to detect error causes and do something with them. We don't have
|
|
157
|
+
// a spec, or ui expectation, for them. So we are opting to embed them as
|
|
158
|
+
// agent attributes. This does not account for `AggregateError` instances.
|
|
159
|
+
if (Object.hasOwn(error ?? {}, 'cause') === true) {
|
|
160
|
+
// We have to stringify the data in order for it to show up correctly
|
|
161
|
+
// in the errors inbox's list of attributes. If we rely on our aggregator
|
|
162
|
+
// to stringify it, then the backend will decode it to an array of objects
|
|
163
|
+
// and the attribute will not be presented in the UI. By doubly encoding
|
|
164
|
+
// it, the back end will decode it to a JSON _string_ and show the
|
|
165
|
+
// attribute in the UI as desired.
|
|
166
|
+
params.agentAttributes['error.cause'] = JSON.stringify(
|
|
167
|
+
serializeCauses(error.cause)
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
113
171
|
return [0, name, message, type, params, transaction?.id]
|
|
114
172
|
}
|
|
115
173
|
|
|
@@ -118,31 +176,33 @@ function isValidErrorGroupOutput(output) {
|
|
|
118
176
|
}
|
|
119
177
|
|
|
120
178
|
function maybeAddAgentAttributes(attributes, exception) {
|
|
121
|
-
if (exception.errorGroupCallback) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
customAttributes: Object.assign({}, attributes.userAttributes),
|
|
125
|
-
'request.uri': attributes.agentAttributes['request.uri'],
|
|
126
|
-
'http.statusCode': attributes.agentAttributes['http.statusCode'],
|
|
127
|
-
'http.method': attributes.agentAttributes['request.method'],
|
|
128
|
-
'error.expected': attributes.intrinsics[ERROR_EXPECTED_PATH]
|
|
129
|
-
}
|
|
179
|
+
if (typeof exception.errorGroupCallback !== 'function') {
|
|
180
|
+
return
|
|
181
|
+
}
|
|
130
182
|
|
|
131
|
-
|
|
132
|
-
|
|
183
|
+
const callbackInput = {
|
|
184
|
+
error: exception.error,
|
|
185
|
+
customAttributes: Object.assign({}, attributes.userAttributes),
|
|
186
|
+
'request.uri': attributes.agentAttributes['request.uri'],
|
|
187
|
+
'http.statusCode': attributes.agentAttributes['http.statusCode'],
|
|
188
|
+
'http.method': attributes.agentAttributes['request.method'],
|
|
189
|
+
'error.expected': attributes.intrinsics[ERROR_EXPECTED_PATH]
|
|
190
|
+
}
|
|
133
191
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return
|
|
137
|
-
}
|
|
192
|
+
try {
|
|
193
|
+
const callbackOutput = exception.errorGroupCallback(callbackInput)
|
|
138
194
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
err,
|
|
143
|
-
'Function provided via setErrorGroupCallback failed, not generating `error.group.name`'
|
|
144
|
-
)
|
|
195
|
+
if (!isValidErrorGroupOutput(callbackOutput)) {
|
|
196
|
+
logger.warn('Function provided via setErrorGroupCallback return value malformed')
|
|
197
|
+
return
|
|
145
198
|
}
|
|
199
|
+
|
|
200
|
+
attributes.agentAttributes['error.group.name'] = callbackOutput
|
|
201
|
+
} catch (err) {
|
|
202
|
+
logger.warn(
|
|
203
|
+
err,
|
|
204
|
+
'Function provided via setErrorGroupCallback failed, not generating `error.group.name`'
|
|
205
|
+
)
|
|
146
206
|
}
|
|
147
207
|
}
|
|
148
208
|
|
package/lib/harvester.js
CHANGED
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
* Copyright 2024 New Relic Corporation. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
5
|
'use strict'
|
|
6
|
+
const defaultLogger = require('./logger').child({ component: 'harvester' })
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @class
|
|
10
10
|
* @classdesc Used to keep track of all registered aggregators.
|
|
11
11
|
*/
|
|
12
12
|
module.exports = class Harvester {
|
|
13
|
-
constructor() {
|
|
13
|
+
constructor({ logger = defaultLogger } = {}) {
|
|
14
14
|
this.aggregators = []
|
|
15
|
+
this.logger = logger
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
/**
|
|
@@ -19,9 +20,23 @@ module.exports = class Harvester {
|
|
|
19
20
|
*/
|
|
20
21
|
start() {
|
|
21
22
|
for (const aggregator of this.aggregators) {
|
|
22
|
-
if (aggregator.enabled) {
|
|
23
|
+
if (aggregator.enabled && aggregator.delay > 0) {
|
|
24
|
+
this.logger.debug(`Delay start of ${aggregator.method} by ${aggregator.delay} milliseconds`)
|
|
25
|
+
const timeout = setTimeout(() => {
|
|
26
|
+
aggregator.start()
|
|
27
|
+
}, aggregator.delay)
|
|
28
|
+
timeout.unref()
|
|
29
|
+
} else if (aggregator.enabled) {
|
|
23
30
|
aggregator.start()
|
|
24
31
|
}
|
|
32
|
+
|
|
33
|
+
if (aggregator.enabled && aggregator.duration > 0) {
|
|
34
|
+
this.logger.debug(`Running ${aggregator.method} for ${aggregator.duration} milliseconds`)
|
|
35
|
+
const durationTimeout = setTimeout(() => {
|
|
36
|
+
aggregator.stop()
|
|
37
|
+
}, aggregator.delay + aggregator.duration)
|
|
38
|
+
durationTimeout.unref()
|
|
39
|
+
}
|
|
25
40
|
}
|
|
26
41
|
}
|
|
27
42
|
|
|
@@ -10,6 +10,12 @@ const { Transform } = require('node:stream')
|
|
|
10
10
|
|
|
11
11
|
const backgroundRecorder = require('../../metrics/recorders/other.js')
|
|
12
12
|
const recordWeb = require('../../metrics/recorders/http')
|
|
13
|
+
const {
|
|
14
|
+
isApplicationLoggingEnabled,
|
|
15
|
+
isLogForwardingEnabled,
|
|
16
|
+
isMetricsEnabled,
|
|
17
|
+
incrementLoggingLinesMetrics
|
|
18
|
+
} = require('../../util/application-logging.js')
|
|
13
19
|
|
|
14
20
|
const {
|
|
15
21
|
DESTINATIONS: DESTS,
|
|
@@ -25,9 +31,16 @@ const SUBSCRIPTION_ID = WEBSITE_OWNER_NAME?.split('+').shift()
|
|
|
25
31
|
const RESOURCE_GROUP_NAME = WEBSITE_RESOURCE_GROUP ?? WEBSITE_OWNER_NAME?.split('+').pop().split('-Linux').shift()
|
|
26
32
|
const AZURE_FUNCTION_APP_NAME = WEBSITE_SITE_NAME
|
|
27
33
|
|
|
34
|
+
// Azure Functions uses 'information' as the context level for context.log and context.info and uses 'warning' as the context level for context.warn.
|
|
35
|
+
// These map to the NR standard names that LOGGING.LEVELS recognizes ('info', 'warn').
|
|
36
|
+
const AZURE_TO_NR_LEVEL = {
|
|
37
|
+
information: 'info',
|
|
38
|
+
warning: 'warn'
|
|
39
|
+
}
|
|
40
|
+
|
|
28
41
|
let coldStart = true
|
|
29
42
|
|
|
30
|
-
module.exports = function initialize(
|
|
43
|
+
module.exports = function initialize(agent, azureFunctions, _moduleName, shim, { logger = defaultLogger } = {}) {
|
|
31
44
|
if (!SUBSCRIPTION_ID || !RESOURCE_GROUP_NAME || !AZURE_FUNCTION_APP_NAME) {
|
|
32
45
|
logger.warn(
|
|
33
46
|
{
|
|
@@ -59,6 +72,8 @@ module.exports = function initialize(_agent, azureFunctions, _moduleName, shim,
|
|
|
59
72
|
'webPubSub'
|
|
60
73
|
]
|
|
61
74
|
shim.wrap(azureFunctions.app, backgroundMethods, wrapAzureBackgroundMethods)
|
|
75
|
+
|
|
76
|
+
registerAzureLogs(agent, azureFunctions, logger)
|
|
62
77
|
}
|
|
63
78
|
|
|
64
79
|
function wrapAzureHttpMethods(shim, appMethod) {
|
|
@@ -174,6 +189,35 @@ function wrapAzureBackgroundMethods(shim, appMethod) {
|
|
|
174
189
|
}
|
|
175
190
|
}
|
|
176
191
|
|
|
192
|
+
function registerAzureLogs(agent, azureFunctions, logger) {
|
|
193
|
+
if (!isApplicationLoggingEnabled(agent.config)) {
|
|
194
|
+
logger.debug('Application logging not enabled. Not auto capturing logs from Azure Functions.')
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
azureFunctions.app.hook?.log((context) => {
|
|
199
|
+
const logLevel = AZURE_TO_NR_LEVEL[context.level] ?? context.level
|
|
200
|
+
|
|
201
|
+
if (isLogForwardingEnabled(agent.config, agent)) {
|
|
202
|
+
const meta = agent.getLinkingMetadata(true)
|
|
203
|
+
|
|
204
|
+
const logData = {
|
|
205
|
+
message: context.message,
|
|
206
|
+
level: logLevel,
|
|
207
|
+
timestamp: Date.now(),
|
|
208
|
+
category: context.category,
|
|
209
|
+
...meta
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
agent.logs.add(logData)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (isMetricsEnabled(agent.config)) {
|
|
216
|
+
incrementLoggingLinesMetrics(logLevel, agent.metrics)
|
|
217
|
+
}
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
|
|
177
221
|
/**
|
|
178
222
|
* Add required New Relic attributes to the transaction.
|
|
179
223
|
*
|
|
@@ -4,22 +4,22 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
'use strict'
|
|
7
|
+
const StreamHandler = require('./stream-handler')
|
|
8
|
+
const ConverseStreamHandler = require('./converse-stream-handler')
|
|
7
9
|
const {
|
|
8
10
|
LlmChatCompletionMessage,
|
|
9
11
|
LlmChatCompletionSummary,
|
|
10
12
|
LlmEmbedding,
|
|
11
|
-
LlmErrorMessage,
|
|
12
13
|
BedrockCommand,
|
|
13
|
-
BedrockResponse
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
BedrockResponse
|
|
15
|
+
} = require('#agentlib/llm-events/aws-bedrock/index.js')
|
|
16
|
+
const LlmErrorMessage = require('#agentlib/llm-events/error-message.js')
|
|
16
17
|
|
|
17
18
|
const { DESTINATIONS } = require('../../../config/attribute-filter')
|
|
18
19
|
const { AI } = require('../../../metrics/names')
|
|
19
20
|
const { RecorderSpec } = require('../../../shim/specs')
|
|
20
21
|
const InstrumentationDescriptor = require('../../../instrumentation-descriptor')
|
|
21
22
|
const { extractLlmContext } = require('../../../util/llm-utils')
|
|
22
|
-
const ConverseStreamHandler = require('../../../llm-events/aws-bedrock/converse-stream-handler')
|
|
23
23
|
|
|
24
24
|
let TRACKING_METRIC
|
|
25
25
|
|
|
@@ -73,7 +73,6 @@ function isStreamingEnabled({ commandName, config }) {
|
|
|
73
73
|
* @param {object} params.msg LLM event
|
|
74
74
|
*/
|
|
75
75
|
function recordEvent({ agent, type, msg }) {
|
|
76
|
-
msg.serialize()
|
|
77
76
|
const llmContext = extractLlmContext(agent)
|
|
78
77
|
const timestamp = msg?.timestamp ?? Date.now()
|
|
79
78
|
|
|
@@ -134,7 +133,7 @@ function recordChatCompletionMessages({
|
|
|
134
133
|
bedrockCommand,
|
|
135
134
|
transaction,
|
|
136
135
|
segment,
|
|
137
|
-
|
|
136
|
+
error: err !== null
|
|
138
137
|
})
|
|
139
138
|
|
|
140
139
|
// Record context message(s)
|
|
@@ -149,7 +148,7 @@ function recordChatCompletionMessages({
|
|
|
149
148
|
content: contextMessage.content,
|
|
150
149
|
role: contextMessage.role,
|
|
151
150
|
bedrockResponse,
|
|
152
|
-
|
|
151
|
+
sequence: i,
|
|
153
152
|
completionId: summary.id
|
|
154
153
|
})
|
|
155
154
|
recordEvent({ agent, type: 'LlmChatCompletionMessage', msg })
|
|
@@ -164,7 +163,7 @@ function recordChatCompletionMessages({
|
|
|
164
163
|
bedrockCommand,
|
|
165
164
|
bedrockResponse,
|
|
166
165
|
isResponse: true,
|
|
167
|
-
|
|
166
|
+
sequence: promptContextMessages.length + i,
|
|
168
167
|
content,
|
|
169
168
|
role: 'assistant',
|
|
170
169
|
completionId: summary.id
|
|
@@ -217,9 +216,9 @@ function recordEmbeddingMessage({
|
|
|
217
216
|
segment,
|
|
218
217
|
transaction,
|
|
219
218
|
bedrockCommand,
|
|
220
|
-
|
|
219
|
+
requestInput: prompt.content,
|
|
221
220
|
bedrockResponse,
|
|
222
|
-
|
|
221
|
+
error: err !== null
|
|
223
222
|
}))
|
|
224
223
|
|
|
225
224
|
for (const embedding of embeddings) {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
'use strict'
|
|
7
7
|
|
|
8
|
-
const { stringifyClaudeChunkedMessage, stringifyConverseChunkedMessage } = require('./
|
|
8
|
+
const { stringifyClaudeChunkedMessage, stringifyConverseChunkedMessage } = require('./stringify-message')
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Parses an AWS Bedrock command instance into a re-usable entity,
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
'use strict'
|
|
7
7
|
|
|
8
|
-
const { stringifyClaudeChunkedMessage, stringifyConverseChunkedMessage } = require('./
|
|
8
|
+
const { stringifyClaudeChunkedMessage, stringifyConverseChunkedMessage } = require('./stringify-message')
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* @typedef {object} AwsBedrockMiddlewareResponse
|
|
@@ -1,82 +1,59 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright
|
|
2
|
+
* Copyright 2026 New Relic Corporation. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
'use strict'
|
|
7
7
|
|
|
8
|
-
const
|
|
9
|
-
/**
|
|
10
|
-
* @typedef {object} LlmChatCompletionParams
|
|
11
|
-
* @augments LlmEventParams
|
|
12
|
-
* @property {string} completionId An identifier for the completion message.
|
|
13
|
-
* @property {string} content The human readable response from the LLM.
|
|
14
|
-
* @property {number} [index=0] The order of the message in the conversation.
|
|
15
|
-
* @property {boolean} [isResponse=false] Indicates if the message represents
|
|
16
|
-
* a response from the LLM.
|
|
17
|
-
* @property {object} message The message sent to the LLM.
|
|
18
|
-
* @property {OutgoingMessage} request The outgoing HTTP request used in the
|
|
19
|
-
* LLM conversation.
|
|
20
|
-
*/
|
|
21
|
-
/**
|
|
22
|
-
* @type {LlmChatCompletionParams}
|
|
23
|
-
*/
|
|
24
|
-
const defaultParams = {
|
|
25
|
-
completionId: '',
|
|
26
|
-
content: '',
|
|
27
|
-
index: 0,
|
|
28
|
-
isResponse: false,
|
|
29
|
-
message: {},
|
|
30
|
-
request: {}
|
|
31
|
-
}
|
|
8
|
+
const LlmChatCompletionMessage = require('../chat-completion-message')
|
|
32
9
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
10
|
+
module.exports = class AwsBedrockLlmChatCompletionMessage extends LlmChatCompletionMessage {
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
* @param {object} params constructor parameters
|
|
14
|
+
* @param {Agent} params.agent New Relic agent instance
|
|
15
|
+
* @param {object} params.segment Current segment
|
|
16
|
+
* @param {object} params.transaction Current and active transaction
|
|
17
|
+
* @param {object} params.bedrockCommand AWS Bedrock Command object, represents the request
|
|
18
|
+
* @param {object} params.bedrockResponse AWS Bedrock Response object
|
|
19
|
+
* @param {string} params.content Content of the message
|
|
20
|
+
* @param {string} [params.role] Role of the message creator (e.g. `user`, `assistant`, `tool`)
|
|
21
|
+
* @param {string} params.completionId ID of the `LlmChatCompletionSummary` event that
|
|
22
|
+
* this message event is connected to
|
|
23
|
+
* @param {number} params.sequence Index (beginning at 0) associated with
|
|
24
|
+
* each message including the prompt and responses
|
|
25
|
+
* @param {boolean} [params.isResponse] Indiciates if this message is the response
|
|
26
|
+
*/
|
|
27
|
+
constructor({ agent, segment, transaction, bedrockCommand, bedrockResponse, content, role, completionId, sequence = 0, isResponse }) {
|
|
28
|
+
super({ agent,
|
|
29
|
+
segment,
|
|
30
|
+
transaction,
|
|
31
|
+
vendor: 'bedrock',
|
|
32
|
+
content,
|
|
33
|
+
role,
|
|
34
|
+
sequence,
|
|
35
|
+
requestId: bedrockResponse?.requestId,
|
|
36
|
+
responseId: bedrockResponse?.id,
|
|
37
|
+
responseModel: bedrockCommand?.modelId, // we can assume requestModel==responseModel in bedrock
|
|
38
|
+
completionId,
|
|
39
|
+
isResponse })
|
|
43
40
|
|
|
44
|
-
this.
|
|
45
|
-
this.completion_id = completionId
|
|
46
|
-
this.sequence = index
|
|
47
|
-
this.content = recordContent === true ? content : undefined
|
|
48
|
-
this.role = role
|
|
49
|
-
if (this.is_response === false) {
|
|
50
|
-
// Only record for request/input messages
|
|
51
|
-
this.timestamp = segment.timer.start
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
this.#setId(index)
|
|
55
|
-
this.setTokenCount(agent)
|
|
41
|
+
this.setTokenCount(agent, bedrockCommand, bedrockResponse)
|
|
56
42
|
}
|
|
57
43
|
|
|
58
|
-
|
|
59
|
-
const cmd = this.bedrockCommand
|
|
60
|
-
if (cmd.isConverse || cmd.isTitan() === true || cmd.isClaude() === true) {
|
|
61
|
-
this.id = `${this.id}-${index}`
|
|
62
|
-
} else if (cmd.isCohere() === true) {
|
|
63
|
-
this.id = `${this.bedrockResponse.id || this.id}-${index}`
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
setTokenCount(agent) {
|
|
44
|
+
setTokenCount(agent, bedrockCommand, bedrockResponse) {
|
|
68
45
|
const tokenCB = agent?.llm?.tokenCountCallback
|
|
69
46
|
|
|
70
47
|
if (tokenCB) {
|
|
71
|
-
const promptContent =
|
|
72
|
-
const completionContent =
|
|
48
|
+
const promptContent = bedrockCommand?.prompt?.map((msg) => msg.content).join(' ')
|
|
49
|
+
const completionContent = bedrockResponse?.completions?.join(' ')
|
|
73
50
|
|
|
74
51
|
if (promptContent && completionContent) {
|
|
75
52
|
this.setTokenFromCallback(
|
|
76
53
|
{
|
|
77
54
|
tokenCB,
|
|
78
|
-
reqModel:
|
|
79
|
-
resModel:
|
|
55
|
+
reqModel: bedrockCommand.modelId,
|
|
56
|
+
resModel: bedrockCommand.modelId,
|
|
80
57
|
promptContent,
|
|
81
58
|
completionContent
|
|
82
59
|
}
|
|
@@ -85,8 +62,7 @@ class LlmChatCompletionMessage extends LlmEvent {
|
|
|
85
62
|
return
|
|
86
63
|
}
|
|
87
64
|
|
|
88
|
-
this.setTokenInCompletionMessage({ promptTokens:
|
|
65
|
+
this.setTokenInCompletionMessage({ promptTokens: bedrockResponse.inputTokenCount,
|
|
66
|
+
completionTokens: bedrockResponse.outputTokenCount })
|
|
89
67
|
}
|
|
90
68
|
}
|
|
91
|
-
|
|
92
|
-
module.exports = LlmChatCompletionMessage
|
|
@@ -1,59 +1,53 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright
|
|
2
|
+
* Copyright 2026 New Relic Corporation. All rights reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
'use strict'
|
|
7
7
|
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
this
|
|
36
|
-
this['response.choices.finish_reason'] = res.finishReason
|
|
37
|
-
this['request.temperature'] = cmd.temperature
|
|
38
|
-
this['response.number_of_messages'] = (cmd.prompt.length ?? 0) + (res.completions.length ?? 0)
|
|
39
|
-
|
|
40
|
-
this.timestamp = segment.timer.start
|
|
41
|
-
this.setTokens(agent)
|
|
8
|
+
const LlmChatCompletionSummary = require('../chat-completion-summary')
|
|
9
|
+
|
|
10
|
+
module.exports = class AwsBedrockLlmChatCompletionSummary extends LlmChatCompletionSummary {
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
* @param {object} params constructor parameters
|
|
14
|
+
* @param {Agent} params.agent New Relic agent instance
|
|
15
|
+
* @param {object} params.segment Current segment
|
|
16
|
+
* @param {object} params.transaction Current and active transaction
|
|
17
|
+
* @param {object} params.bedrockCommand AWS Bedrock Command object, represents the request
|
|
18
|
+
* @param {object} params.bedrockResponse AWS Bedrock Response object
|
|
19
|
+
* @param {boolean} [params.error] Set to `true` if an error occurred during creation call, omitted if no error occurred
|
|
20
|
+
*/
|
|
21
|
+
constructor({ agent, segment, transaction, bedrockCommand, bedrockResponse, error }) {
|
|
22
|
+
super({ agent,
|
|
23
|
+
segment,
|
|
24
|
+
transaction,
|
|
25
|
+
error,
|
|
26
|
+
vendor: 'bedrock',
|
|
27
|
+
requestModel: bedrockCommand?.modelId,
|
|
28
|
+
requestId: bedrockResponse?.requestId,
|
|
29
|
+
responseModel: bedrockCommand?.modelId, // we can assume requestModel==responseModel in bedrock
|
|
30
|
+
temperature: bedrockCommand.temperature,
|
|
31
|
+
maxTokens: bedrockCommand.maxTokens,
|
|
32
|
+
numMsgs: (bedrockCommand.prompt.length ?? 0) + (bedrockResponse.completions.length ?? 0),
|
|
33
|
+
finishReason: bedrockResponse?.finishReason })
|
|
34
|
+
|
|
35
|
+
this.setTokens(agent, bedrockCommand, bedrockResponse)
|
|
42
36
|
}
|
|
43
37
|
|
|
44
|
-
setTokens(agent) {
|
|
38
|
+
setTokens(agent, bedrockCommand, bedrockResponse) {
|
|
45
39
|
const tokenCB = agent?.llm?.tokenCountCallback
|
|
46
40
|
|
|
47
41
|
// Prefer callback for prompt and completion tokens; if unavailable, fall back to response data.
|
|
48
42
|
if (tokenCB) {
|
|
49
|
-
const promptContent =
|
|
50
|
-
const completionContent =
|
|
43
|
+
const promptContent = bedrockCommand?.prompt?.map((msg) => msg.content).join(' ')
|
|
44
|
+
const completionContent = bedrockResponse?.completions?.join(' ')
|
|
51
45
|
|
|
52
46
|
this.setTokenUsageFromCallback(
|
|
53
47
|
{
|
|
54
48
|
tokenCB,
|
|
55
|
-
reqModel:
|
|
56
|
-
resModel:
|
|
49
|
+
reqModel: bedrockCommand.modelId,
|
|
50
|
+
resModel: bedrockCommand.modelId,
|
|
57
51
|
promptContent,
|
|
58
52
|
completionContent
|
|
59
53
|
}
|
|
@@ -61,8 +55,8 @@ class LlmChatCompletionSummary extends LlmEvent {
|
|
|
61
55
|
return
|
|
62
56
|
}
|
|
63
57
|
|
|
64
|
-
this.setTokensInResponse({ promptTokens:
|
|
58
|
+
this.setTokensInResponse({ promptTokens: bedrockResponse.inputTokenCount,
|
|
59
|
+
completionTokens: bedrockResponse.outputTokenCount,
|
|
60
|
+
totalTokens: bedrockResponse.totalTokenCount })
|
|
65
61
|
}
|
|
66
62
|
}
|
|
67
|
-
|
|
68
|
-
module.exports = LlmChatCompletionSummary
|