newrelic 13.6.2 → 13.6.3
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 +29 -11
- package/README.md +6 -11
- package/lib/config/default.js +46 -4
- package/lib/config/index.js +110 -1
- package/lib/instrumentation/@google/genai.js +15 -24
- package/lib/instrumentations.js +0 -1
- package/lib/llm-events/aws-bedrock/bedrock-response.js +25 -1
- package/lib/llm-events/aws-bedrock/chat-completion-message.js +2 -10
- package/lib/llm-events/aws-bedrock/chat-completion-summary.js +11 -15
- package/lib/llm-events/aws-bedrock/converse-stream-handler.js +11 -4
- package/lib/llm-events/aws-bedrock/embedding.js +3 -20
- package/lib/llm-events/aws-bedrock/event.js +3 -20
- package/lib/llm-events/aws-bedrock/stream-handler.js +21 -28
- package/lib/llm-events/aws-bedrock/utils.js +1 -59
- package/lib/llm-events/event.js +108 -0
- package/lib/llm-events/google-genai/chat-completion-message.js +3 -10
- package/lib/llm-events/google-genai/chat-completion-summary.js +4 -5
- package/lib/llm-events/google-genai/embedding.js +0 -34
- package/lib/llm-events/google-genai/event.js +7 -0
- package/lib/llm-events/openai/chat-completion-message.js +3 -9
- package/lib/llm-events/openai/chat-completion-summary.js +4 -5
- package/lib/llm-events/openai/embedding.js +4 -15
- package/lib/llm-events/openai/event.js +7 -0
- package/lib/shim/datastore-shim.js +5 -7
- package/lib/subscriber-configs.js +2 -0
- package/lib/subscribers/mysql/config.js +73 -0
- package/lib/subscribers/mysql/connection-query.js +56 -0
- package/lib/subscribers/mysql/helper.js +102 -0
- package/lib/subscribers/mysql/pool-get-connection.js +20 -0
- package/lib/subscribers/mysql/pool-namespace-query.js +19 -0
- package/lib/subscribers/mysql/pool-query.js +41 -0
- package/lib/subscribers/mysql2/config.js +127 -0
- package/lib/subscribers/mysql2/connection-execute.js +18 -0
- package/lib/subscribers/mysql2/connection-query.js +17 -0
- package/lib/subscribers/mysql2/pool-get-connection.js +20 -0
- package/lib/subscribers/mysql2/pool-namespace-query.js +19 -0
- package/lib/subscribers/mysql2/pool-query.js +19 -0
- package/lib/subscribers/openai/utils.js +11 -2
- package/lib/subscribers/pg/query.js +1 -1
- package/package.json +1 -1
- package/lib/db/utils.js +0 -19
- package/lib/instrumentation/mysql/mysql.js +0 -333
- package/lib/instrumentation/mysql/nr-hooks.js +0 -26
- package/lib/llm-events/google-genai/utils.js +0 -32
- package/lib/llm-events/openai/utils.js +0 -34
- package/lib/llm-events/utils.js +0 -110
package/NEWS.md
CHANGED
|
@@ -1,14 +1,31 @@
|
|
|
1
|
-
### v13.6.
|
|
2
|
-
|
|
3
|
-
#### Bug fixes
|
|
4
|
-
|
|
5
|
-
* Updated
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
### v13.6.3 (2025-11-11)
|
|
2
|
+
|
|
3
|
+
#### Bug fixes
|
|
4
|
+
|
|
5
|
+
* Updated `@google/genai` to properly handle assigning token counts to LlmCompletionSummary message ([#3495](https://github.com/newrelic/node-newrelic/pull/3495)) ([f805d38](https://github.com/newrelic/node-newrelic/commit/f805d382ed0a898f784b053fb9855e9da97dea98))
|
|
6
|
+
* Updated AWS Bedrock instrumentation to properly assign token counts ([#3491](https://github.com/newrelic/node-newrelic/pull/3491)) ([b1687f6](https://github.com/newrelic/node-newrelic/commit/b1687f6dcb8782623a92dacee740a537ad791fdc))
|
|
7
|
+
* Updated openai instrumentation to properly handle streaming when `stream_options.include_usage` is set ([#3494](https://github.com/newrelic/node-newrelic/pull/3494)) ([37c43f5](https://github.com/newrelic/node-newrelic/commit/37c43f5b448f851b749b95b032c4fb52785f2ab0))
|
|
8
|
+
|
|
9
|
+
#### Code refactoring
|
|
10
|
+
|
|
11
|
+
* Update `mysql` and `mysql2` instrumentation to subscribe to events emitted ([#3423](https://github.com/newrelic/node-newrelic/pull/3423)) ([2e38914](https://github.com/newrelic/node-newrelic/commit/2e38914ffd8510dc610bd629a00b61e604dca2db))
|
|
12
|
+
|
|
13
|
+
#### Documentation
|
|
14
|
+
|
|
15
|
+
* Cleaned up README links and outdated verbiage ([#3484](https://github.com/newrelic/node-newrelic/pull/3484)) ([51e6d7a](https://github.com/newrelic/node-newrelic/commit/51e6d7ace2fb62b907113ec01f63a8d8e4be4e34))
|
|
16
|
+
* Updated compatibility report ([#3477](https://github.com/newrelic/node-newrelic/pull/3477)) ([859f27f](https://github.com/newrelic/node-newrelic/commit/859f27febe4d4afd46bf7d080a2db51ae9acc0cc))
|
|
17
|
+
|
|
18
|
+
### v13.6.2 (2025-10-29)
|
|
19
|
+
|
|
20
|
+
#### Bug fixes
|
|
21
|
+
|
|
22
|
+
* Updated express instrumentation to properly instrument an array of middleware defined on a route ([#3473](https://github.com/newrelic/node-newrelic/pull/3473)) ([e383c4e](https://github.com/newrelic/node-newrelic/commit/e383c4efc7171f02aef41d6c657455338b4b7258))
|
|
23
|
+
|
|
24
|
+
#### Documentation
|
|
25
|
+
|
|
26
|
+
* documented how to use the subscriber based instrumentation ([#3432](https://github.com/newrelic/node-newrelic/pull/3432)) ([3b3ab72](https://github.com/newrelic/node-newrelic/commit/3b3ab72eee3b4c1a12592d92eab59da2a48b1f46))
|
|
27
|
+
* Updated compatibility report ([#3471](https://github.com/newrelic/node-newrelic/pull/3471)) ([964543b](https://github.com/newrelic/node-newrelic/commit/964543bae1a7c1fd5af32f02ba40d4bf68737d11))
|
|
28
|
+
|
|
12
29
|
### v13.6.1 (2025-10-28)
|
|
13
30
|
|
|
14
31
|
#### Bug fixes
|
|
@@ -8022,3 +8039,4 @@ Special thanks to Ryan Copley (@RyanCopley) for the contribution.
|
|
|
8022
8039
|
|
|
8023
8040
|
|
|
8024
8041
|
|
|
8042
|
+
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
This package instruments your application for performance monitoring with [New Relic](https://newrelic.com).
|
|
7
7
|
|
|
8
|
-
In order to take full advantage of this package, make sure you have a [New Relic account](https://newrelic.com) before starting.
|
|
8
|
+
In order to take full advantage of this package, make sure you have a [New Relic account](https://newrelic.com) before starting.
|
|
9
9
|
|
|
10
10
|
As with any instrumentation tool, please test before using in production.
|
|
11
11
|
|
|
@@ -15,7 +15,7 @@ To use New Relic's Node.js agent entails these three steps, which are described
|
|
|
15
15
|
|
|
16
16
|
- Install [the `newrelic` package](https://www.npmjs.com/package/newrelic)
|
|
17
17
|
- Create a base configuration file
|
|
18
|
-
-
|
|
18
|
+
- Load the agent in your program
|
|
19
19
|
|
|
20
20
|
1. To install the agent for performance monitoring, use your favorite npm-based package manager and install the `newrelic` package into your application:
|
|
21
21
|
|
|
@@ -95,7 +95,7 @@ node -r newrelic your-program.js
|
|
|
95
95
|
|
|
96
96
|
If your application is written with `import` and `export` statements in javascript, you are using [ES Modules](https://nodejs.org/api/esm.html#modules-ecmascript-modules) and must bootstrap the agent in a different way.
|
|
97
97
|
|
|
98
|
-
The New Relic Node.js agent includes
|
|
98
|
+
The New Relic Node.js agent includes support for ES Modules.
|
|
99
99
|
|
|
100
100
|
### Setup
|
|
101
101
|
|
|
@@ -237,10 +237,9 @@ If the issue has been confirmed as a bug or is a feature request, please file a
|
|
|
237
237
|
**Support Channels**
|
|
238
238
|
|
|
239
239
|
* [New Relic Documentation](https://docs.newrelic.com/docs/agents/nodejs-agent/getting-started/introduction-new-relic-nodejs): Comprehensive guidance for using our platform
|
|
240
|
-
* [New Relic Community](https://
|
|
241
|
-
* [New Relic
|
|
240
|
+
* [New Relic Community](https://support.newrelic.com/): The best place to engage in troubleshooting questions
|
|
241
|
+
* [New Relic Technical Support Offerings](https://docs.newrelic.com/docs/licenses/license-information/general-usage-licenses/support-plan): Support offerings.
|
|
242
242
|
* [New Relic University](https://learn.newrelic.com/): A range of online training for New Relic users of every level
|
|
243
|
-
* [New Relic Technical Support](https://support.newrelic.com/) 24/7/365 ticketed support. Read more about our [Technical Support Offerings](https://docs.newrelic.com/docs/licenses/license-information/general-usage-licenses/support-plan).
|
|
244
243
|
|
|
245
244
|
|
|
246
245
|
## Privacy
|
|
@@ -250,10 +249,6 @@ We define “Personal Data” as any information relating to an identified or id
|
|
|
250
249
|
|
|
251
250
|
Please review [New Relic’s General Data Privacy Notice](https://newrelic.com/termsandconditions/privacy) for more information.
|
|
252
251
|
|
|
253
|
-
## Roadmap
|
|
254
|
-
|
|
255
|
-
See our [roadmap](./ROADMAP_Node.md), to learn more about our product vision, understand our plans, and provide us valuable feedback.
|
|
256
|
-
|
|
257
252
|
## Contribute
|
|
258
253
|
|
|
259
254
|
We encourage your contributions to improve the Node.js agent! Keep in mind when you submit your pull request, you'll need to sign the CLA via the click-through using CLA-Assistant. You only have to sign the CLA one time per project.
|
|
@@ -268,7 +263,7 @@ If you believe you have found a security vulnerability in this project or any of
|
|
|
268
263
|
|
|
269
264
|
If you would like to contribute to this project, review [these guidelines](./CONTRIBUTING.md).
|
|
270
265
|
|
|
271
|
-
To [all contributors](https://github.com/newrelic/node-newrelic/graphs/contributors), we thank you! Without your contribution, this project would not be what it is today.
|
|
266
|
+
To [all contributors](https://github.com/newrelic/node-newrelic/graphs/contributors), we thank you! Without your contribution, this project would not be what it is today.
|
|
272
267
|
|
|
273
268
|
## License
|
|
274
269
|
|
package/lib/config/default.js
CHANGED
|
@@ -1166,6 +1166,32 @@ defaultConfig.definition = () => {
|
|
|
1166
1166
|
formatter: int,
|
|
1167
1167
|
default: 10
|
|
1168
1168
|
},
|
|
1169
|
+
// The root sampling options are defined in an unusual way compared to the rest of our configuration but this is done to
|
|
1170
|
+
// allow compatibility with how OpenTelemetry sets their samplers and be backwards compatible as well. Root can either be
|
|
1171
|
+
// a string or an object.
|
|
1172
|
+
//
|
|
1173
|
+
// Example setting root sampler via config to a string value -
|
|
1174
|
+
// root: 'always_on'
|
|
1175
|
+
//
|
|
1176
|
+
// Example setting root sampler via config to trace id ratio based -
|
|
1177
|
+
// root: {
|
|
1178
|
+
// trace_id_ratio_based: {
|
|
1179
|
+
// ratio: 0.5
|
|
1180
|
+
// }
|
|
1181
|
+
// }
|
|
1182
|
+
//
|
|
1183
|
+
root: {
|
|
1184
|
+
formatter: allowList.bind(null, ['trace_id_ratio_based', 'adaptive', 'always_on', 'always_off', 'default']),
|
|
1185
|
+
default: 'default',
|
|
1186
|
+
// if setting root to trace_id_ratio_based via env vars, set NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_ROOT: 'trace_id_ratio_based'
|
|
1187
|
+
// and then set NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_ROOT_TRACE_ID_RATIO_BASED_RATIO to the desired ratio
|
|
1188
|
+
trace_id_ratio_based: {
|
|
1189
|
+
ratio: {
|
|
1190
|
+
formatter: float,
|
|
1191
|
+
env: 'NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_ROOT_TRACE_ID_RATIO_BASED_RATIO'
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
},
|
|
1169
1195
|
|
|
1170
1196
|
/**
|
|
1171
1197
|
* When set to `always_on`, the sampled flag in the `traceparent` header
|
|
@@ -1178,8 +1204,16 @@ defaultConfig.definition = () => {
|
|
|
1178
1204
|
* setting.
|
|
1179
1205
|
*/
|
|
1180
1206
|
remote_parent_sampled: {
|
|
1181
|
-
formatter: allowList.bind(null, ['always_on', 'always_off', 'default']),
|
|
1182
|
-
default: 'default'
|
|
1207
|
+
formatter: allowList.bind(null, ['trace_id_ratio_based', 'adaptive', 'always_on', 'always_off', 'default']),
|
|
1208
|
+
default: 'default',
|
|
1209
|
+
// if setting remote_parent_sampled to trace_id_ratio_based via env vars, set NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_REMOTE_PARENT_SAMPLED: 'trace_id_ratio_based'
|
|
1210
|
+
// and then set NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_REMOTE_PARENT_SAMPLED_TRACE_ID_RATIO_BASED_RATIO to the desired ratio
|
|
1211
|
+
trace_id_ratio_based: {
|
|
1212
|
+
ratio: {
|
|
1213
|
+
formatter: float,
|
|
1214
|
+
env: 'NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_REMOTE_PARENT_SAMPLED_TRACE_ID_RATIO_BASED_RATIO'
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1183
1217
|
},
|
|
1184
1218
|
|
|
1185
1219
|
/**
|
|
@@ -1193,8 +1227,16 @@ defaultConfig.definition = () => {
|
|
|
1193
1227
|
* is set to 0.
|
|
1194
1228
|
*/
|
|
1195
1229
|
remote_parent_not_sampled: {
|
|
1196
|
-
formatter: allowList.bind(null, ['always_on', 'always_off', 'default']),
|
|
1197
|
-
default: 'default'
|
|
1230
|
+
formatter: allowList.bind(null, ['trace_id_ratio_based', 'adaptive', 'always_on', 'always_off', 'default']),
|
|
1231
|
+
default: 'default',
|
|
1232
|
+
// if setting remote_parent_not_sampled to trace_id_ratio_based via env vars, set NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_REMOTE_PARENT_NOT_SAMPLED: 'trace_id_ratio_based'
|
|
1233
|
+
// and then set NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_REMOTE_PARENT_NOT_SAMPLED_TRACE_ID_RATIO_BASED_RATIO to the desired ratio
|
|
1234
|
+
trace_id_ratio_based: {
|
|
1235
|
+
ratio: {
|
|
1236
|
+
formatter: float,
|
|
1237
|
+
env: 'NEW_RELIC_DISTRIBUTED_TRACING_SAMPLER_REMOTE_PARENT_NOT_SAMPLED_TRACE_ID_RATIO_BASED_RATIO'
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1198
1240
|
}
|
|
1199
1241
|
}
|
|
1200
1242
|
},
|
package/lib/config/index.js
CHANGED
|
@@ -1060,10 +1060,13 @@ Config.prototype._fromPassed = function _fromPassed(external, internal, arbitrar
|
|
|
1060
1060
|
|
|
1061
1061
|
for (const key of Object.keys(external)) {
|
|
1062
1062
|
// if it's not in the defaults, it doesn't exist
|
|
1063
|
-
if (!arbitrary &&
|
|
1063
|
+
if (!arbitrary && Object.hasOwn(internal, key) === false) {
|
|
1064
1064
|
continue
|
|
1065
1065
|
}
|
|
1066
1066
|
|
|
1067
|
+
// Handle trace_id_ratio_based for distributed tracing sampler configuration
|
|
1068
|
+
this._buildTraceIdRatioSamplers(key, internal, external[key])
|
|
1069
|
+
|
|
1067
1070
|
if (key === 'ssl' && !isTruthular(external.ssl)) {
|
|
1068
1071
|
logger.warn(SSL_WARNING)
|
|
1069
1072
|
continue
|
|
@@ -1087,6 +1090,54 @@ Config.prototype._fromPassed = function _fromPassed(external, internal, arbitrar
|
|
|
1087
1090
|
}
|
|
1088
1091
|
}
|
|
1089
1092
|
|
|
1093
|
+
/**
|
|
1094
|
+
* Builds the trace_id_ratio_based for distributed tracing sampler configuration structure
|
|
1095
|
+
*
|
|
1096
|
+
* @param {string} key - The configuration key being processed
|
|
1097
|
+
* @param {object} internal - The internal configuration object being modified
|
|
1098
|
+
* @param {object} samplerConfig - Sampler configuration object from defaultsConfig
|
|
1099
|
+
*/
|
|
1100
|
+
Config.prototype._buildTraceIdRatioSamplers = function _buildTraceIdRatioSamplers(key, internal, samplerConfig) {
|
|
1101
|
+
if (key !== 'sampler') {
|
|
1102
|
+
return
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
const samplers = Object.keys(samplerConfig)
|
|
1106
|
+
|
|
1107
|
+
// user can set multiple samplers
|
|
1108
|
+
for (const sampler of samplers) {
|
|
1109
|
+
if (this._isTraceIdRatioBasedConfig(sampler, samplerConfig)) {
|
|
1110
|
+
const ratioValue = samplerConfig[sampler].trace_id_ratio_based.ratio
|
|
1111
|
+
if (ratioValue) {
|
|
1112
|
+
internal[key][sampler] = {
|
|
1113
|
+
trace_id_ratio_based: {
|
|
1114
|
+
ratio: ratioValue
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
logger.trace('Setting trace id ratio based sampler on %s', samplers)
|
|
1118
|
+
} else {
|
|
1119
|
+
logger.error('Not setting trace id ratio based sampler on %s.', samplers)
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
/**
|
|
1126
|
+
* Checks if the external config contains a trace_id_ratio_based for distributed tracing
|
|
1127
|
+
* sampler configuration
|
|
1128
|
+
*
|
|
1129
|
+
* @param {string} selectedSampler - The selected sampler key
|
|
1130
|
+
* @param {object} samplerConfig - The sampler configuration object
|
|
1131
|
+
* @returns {boolean} true if it's a trace_id_ratio_based config
|
|
1132
|
+
*/
|
|
1133
|
+
Config.prototype._isTraceIdRatioBasedConfig = function _isTraceIdRatioBasedConfig(selectedSampler, samplerConfig) {
|
|
1134
|
+
const samplers = ['root', 'remote_parent_sampled', 'remote_parent_not_sampled']
|
|
1135
|
+
|
|
1136
|
+
return samplers.includes(selectedSampler) &&
|
|
1137
|
+
typeof samplerConfig[selectedSampler] === 'object' &&
|
|
1138
|
+
'trace_id_ratio_based' in samplerConfig[selectedSampler]
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1090
1141
|
/**
|
|
1091
1142
|
* Some values should be picked up only if they're not otherwise set, like
|
|
1092
1143
|
* the Windows / Azure application name. Don't set it if there's already
|
|
@@ -1163,6 +1214,53 @@ function setFromEnv({ config, key, envVar, formatter, paths }) {
|
|
|
1163
1214
|
}
|
|
1164
1215
|
}
|
|
1165
1216
|
|
|
1217
|
+
/**
|
|
1218
|
+
* Assigns the value of the distributed tracing sampler env var as an trace_id_ratio_based
|
|
1219
|
+
* object to the sampler in the config.
|
|
1220
|
+
*
|
|
1221
|
+
* @param {object} params object passed to fn
|
|
1222
|
+
* @param {string} params.key key of the sampler
|
|
1223
|
+
* Example: 'root' or 'remote_parent_sampled'
|
|
1224
|
+
* @param {object} params.value value of the sampler
|
|
1225
|
+
* Example: { default: 'default', formatter: someFunc, trace_id_ratio_based: {ratio} }
|
|
1226
|
+
* @param {object} params.config agent config
|
|
1227
|
+
* @param {Array} params.paths list of leaf nodes leading to the sampling configuration value
|
|
1228
|
+
* Example: ['distributed_tracing', 'sampler']
|
|
1229
|
+
*/
|
|
1230
|
+
function setTraceIDRatioSampler({ key, value, config, paths }) {
|
|
1231
|
+
// trace id ratio based sampler is nested in leaf nodes under distributed_tracing > samplers > key
|
|
1232
|
+
// so don't continue if path is empty
|
|
1233
|
+
if (paths.length === 0) {
|
|
1234
|
+
return
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
const samplers = ['root', 'remote_parent_sampled', 'remote_parent_not_sampled']
|
|
1238
|
+
if (samplers.includes(key) && paths[paths.length - 1] === 'sampler') {
|
|
1239
|
+
// config.distributed_tracing.sampler.[TYPE OF SAMPLER] needs to be set to trace_id_ratio_based
|
|
1240
|
+
// in order to continue
|
|
1241
|
+
if (config.distributed_tracing.sampler[key] !== 'trace_id_ratio_based') {
|
|
1242
|
+
return
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
// set the trace id based ratio
|
|
1246
|
+
const envVar = value.trace_id_ratio_based.ratio.env
|
|
1247
|
+
const formatter = value.trace_id_ratio_based.ratio.formatter
|
|
1248
|
+
const setting = process.env[envVar]
|
|
1249
|
+
if (setting) {
|
|
1250
|
+
const formattedSetting = formatter(setting)
|
|
1251
|
+
config.distributed_tracing.sampler[key] = {
|
|
1252
|
+
trace_id_ratio_based: {
|
|
1253
|
+
ratio: formattedSetting
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
logger.trace('Setting %s environment variable', envVar)
|
|
1257
|
+
} else {
|
|
1258
|
+
logger.error('Not setting %s environment variable. Setting sampler root to default.', envVar)
|
|
1259
|
+
config.distributed_tracing.sampler.root = 'default'
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1166
1264
|
/**
|
|
1167
1265
|
* Recursively visit the nodes of the config definition and look for environment variable names, overriding any configuration values that are found.
|
|
1168
1266
|
*
|
|
@@ -1201,6 +1299,17 @@ Config.prototype._fromEnvironment = function _fromEnvironment(
|
|
|
1201
1299
|
if (Object.prototype.hasOwnProperty.call(value, 'default') === true) {
|
|
1202
1300
|
const envVar = deriveEnvVar(key, paths)
|
|
1203
1301
|
setFromEnv({ config, key, paths, envVar, formatter: value.formatter })
|
|
1302
|
+
|
|
1303
|
+
// Handle custom configuration of the sampler if it's set to trace_id_ratio_based
|
|
1304
|
+
// since sampler was set to a string but now has to be converted to an object.
|
|
1305
|
+
// This is called here because we need set the sampler to a value first in config before setting
|
|
1306
|
+
// trace id ratio sampler (if the user set that)
|
|
1307
|
+
setTraceIDRatioSampler({
|
|
1308
|
+
key,
|
|
1309
|
+
value,
|
|
1310
|
+
config,
|
|
1311
|
+
paths
|
|
1312
|
+
})
|
|
1204
1313
|
continue
|
|
1205
1314
|
}
|
|
1206
1315
|
|
|
@@ -121,23 +121,26 @@ function recordChatCompletionMessages({
|
|
|
121
121
|
|
|
122
122
|
function instrumentStream ({ shim, request, response, segment, transaction }) {
|
|
123
123
|
let err
|
|
124
|
-
let content
|
|
125
|
-
let modelVersion
|
|
126
|
-
let finishReason
|
|
127
124
|
let entireMessage = ''
|
|
125
|
+
let isDone = false
|
|
126
|
+
let cachedResult = {}
|
|
128
127
|
shim.wrap(response, 'next', function wrapNext(shim, originalNext) {
|
|
129
128
|
return async function wrappedNext(...nextArgs) {
|
|
130
129
|
let result = {}
|
|
131
130
|
try {
|
|
132
131
|
result = await originalNext.apply(response, nextArgs)
|
|
132
|
+
// When the stream is done we get {value: undefined, done: true}
|
|
133
|
+
// we need to cache the composed value and add the entire message
|
|
134
|
+
// back in later
|
|
135
|
+
if (result.done === true) {
|
|
136
|
+
isDone = true
|
|
137
|
+
} else {
|
|
138
|
+
cachedResult = result.value
|
|
139
|
+
}
|
|
140
|
+
|
|
133
141
|
if (result?.value?.text) {
|
|
134
|
-
modelVersion = result.value.modelVersion
|
|
135
|
-
content = result.value.candidates[0].content
|
|
136
142
|
entireMessage += result.value.text // readonly variable that equates to result.value.candidates[0].content.parts[0].text
|
|
137
143
|
}
|
|
138
|
-
if (result?.value?.candidates?.[0]?.finishReason) {
|
|
139
|
-
finishReason = result.value.candidates[0].finishReason
|
|
140
|
-
}
|
|
141
144
|
} catch (streamErr) {
|
|
142
145
|
err = streamErr
|
|
143
146
|
throw err
|
|
@@ -146,30 +149,18 @@ function instrumentStream ({ shim, request, response, segment, transaction }) {
|
|
|
146
149
|
// time it took to handle the stream
|
|
147
150
|
segment.touch()
|
|
148
151
|
|
|
149
|
-
// result will be {value: undefined, done: true}
|
|
150
|
-
// when the stream is done, so we need to create
|
|
151
|
-
// a mock GenerateContentResponse object with
|
|
152
|
-
// the entire message
|
|
153
|
-
//
|
|
154
152
|
// also need to enter this block if there was an
|
|
155
153
|
// error, so we can record it
|
|
156
|
-
if (
|
|
157
|
-
if (content) {
|
|
158
|
-
content.parts[0].text = entireMessage
|
|
159
|
-
result.value = {
|
|
160
|
-
candidates: [
|
|
161
|
-
{ content, finishReason }
|
|
162
|
-
],
|
|
163
|
-
modelVersion
|
|
164
|
-
}
|
|
154
|
+
if (isDone || err) {
|
|
155
|
+
if (cachedResult?.candidates?.[0]?.content?.parts) {
|
|
156
|
+
cachedResult.candidates[0].content.parts[0].text = entireMessage
|
|
165
157
|
}
|
|
166
|
-
|
|
167
158
|
recordChatCompletionMessages({
|
|
168
159
|
agent: shim.agent,
|
|
169
160
|
segment,
|
|
170
161
|
transaction,
|
|
171
162
|
request,
|
|
172
|
-
response:
|
|
163
|
+
response: cachedResult,
|
|
173
164
|
err
|
|
174
165
|
})
|
|
175
166
|
}
|
package/lib/instrumentations.js
CHANGED
|
@@ -30,7 +30,6 @@ module.exports = function instrumentations() {
|
|
|
30
30
|
langchain: { module: './instrumentation/langchain' },
|
|
31
31
|
memcached: { type: InstrumentationDescriptor.TYPE_DATASTORE },
|
|
32
32
|
mongodb: { type: InstrumentationDescriptor.TYPE_DATASTORE },
|
|
33
|
-
mysql: { module: './instrumentation/mysql' },
|
|
34
33
|
next: { module: './instrumentation/nextjs' },
|
|
35
34
|
q: { type: null },
|
|
36
35
|
redis: { type: InstrumentationDescriptor.TYPE_DATASTORE },
|
|
@@ -73,6 +73,30 @@ class BedrockResponse {
|
|
|
73
73
|
return this.#parsedBody
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
get inputTokenCount() {
|
|
77
|
+
if (this.#isConverse) {
|
|
78
|
+
return parseInt(this.#originalResponse?.output?.usage?.inputTokens || 0, 10)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return parseInt(this?.headers?.['x-amzn-bedrock-input-token-count'] || 0, 10)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
get outputTokenCount() {
|
|
85
|
+
if (this.#isConverse) {
|
|
86
|
+
return parseInt(this.#originalResponse?.output?.usage?.outputTokens || 0, 10)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return parseInt(this?.headers?.['x-amzn-bedrock-output-token-count'] || 0, 10)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
get totalTokenCount() {
|
|
93
|
+
if (this.#isConverse) {
|
|
94
|
+
return parseInt(this.#originalResponse?.output?.usage?.totalTokens || 0, 10)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return this.inputTokenCount + this.outputTokenCount
|
|
98
|
+
}
|
|
99
|
+
|
|
76
100
|
/**
|
|
77
101
|
* The prompt responses returned by the model.
|
|
78
102
|
*
|
|
@@ -164,7 +188,7 @@ class BedrockResponse {
|
|
|
164
188
|
} else if (cmd.isClaude3() === true) {
|
|
165
189
|
if (body?.type === 'message_stop') {
|
|
166
190
|
// Streamed response
|
|
167
|
-
this.#completions
|
|
191
|
+
this.#completions.push(body.completions)
|
|
168
192
|
} else {
|
|
169
193
|
this.#completions = [stringifyClaudeChunkedMessage(body?.content)]
|
|
170
194
|
}
|
|
@@ -6,9 +6,6 @@
|
|
|
6
6
|
'use strict'
|
|
7
7
|
|
|
8
8
|
const LlmEvent = require('./event')
|
|
9
|
-
const { setTokenFromCallback } = require('../utils')
|
|
10
|
-
const { tokenUsageAttributesExist, tokenUsageHeadersExist } = require('./utils')
|
|
11
|
-
|
|
12
9
|
/**
|
|
13
10
|
* @typedef {object} LlmChatCompletionParams
|
|
14
11
|
* @augments LlmEventParams
|
|
@@ -71,9 +68,8 @@ class LlmChatCompletionMessage extends LlmEvent {
|
|
|
71
68
|
const completionContent = this.bedrockResponse?.completions?.join(' ')
|
|
72
69
|
|
|
73
70
|
if (promptContent && completionContent) {
|
|
74
|
-
setTokenFromCallback(
|
|
71
|
+
this.setTokenFromCallback(
|
|
75
72
|
{
|
|
76
|
-
context: this,
|
|
77
73
|
tokenCB,
|
|
78
74
|
reqModel: this.bedrockCommand.modelId,
|
|
79
75
|
resModel: this.bedrockCommand.modelId,
|
|
@@ -85,11 +81,7 @@ class LlmChatCompletionMessage extends LlmEvent {
|
|
|
85
81
|
return
|
|
86
82
|
}
|
|
87
83
|
|
|
88
|
-
|
|
89
|
-
// or response headers for usage information and set token_count to 0 if all usage attributes are present.
|
|
90
|
-
if (tokenUsageAttributesExist(this.bedrockResponse) || tokenUsageHeadersExist(this.bedrockResponse)) {
|
|
91
|
-
this.token_count = 0
|
|
92
|
-
}
|
|
84
|
+
this.setTokenInCompletionMessage({ promptTokens: this.bedrockResponse.inputTokenCount, completionTokens: this.bedrockResponse.outputTokenCount })
|
|
93
85
|
}
|
|
94
86
|
}
|
|
95
87
|
|
|
@@ -7,8 +7,6 @@
|
|
|
7
7
|
|
|
8
8
|
const LlmEvent = require('./event')
|
|
9
9
|
|
|
10
|
-
const { setUsageTokens } = require('./utils')
|
|
11
|
-
const { setTokenUsageFromCallback } = require('../utils')
|
|
12
10
|
/**
|
|
13
11
|
* @typedef {object} LlmChatCompletionSummaryParams
|
|
14
12
|
* @augments LlmEventParams
|
|
@@ -50,21 +48,19 @@ class LlmChatCompletionSummary extends LlmEvent {
|
|
|
50
48
|
const promptContent = this.bedrockCommand?.prompt?.map((msg) => msg.content).join(' ')
|
|
51
49
|
const completionContent = this.bedrockResponse?.completions?.join(' ')
|
|
52
50
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
)
|
|
64
|
-
}
|
|
51
|
+
this.setTokenUsageFromCallback(
|
|
52
|
+
{
|
|
53
|
+
tokenCB,
|
|
54
|
+
reqModel: this.bedrockCommand.modelId,
|
|
55
|
+
resModel: this.bedrockCommand.modelId,
|
|
56
|
+
promptContent,
|
|
57
|
+
completionContent
|
|
58
|
+
}
|
|
59
|
+
)
|
|
65
60
|
return
|
|
66
61
|
}
|
|
67
|
-
|
|
62
|
+
|
|
63
|
+
this.setTokensInResponse({ promptTokens: this.bedrockResponse.inputTokenCount, completionTokens: this.bedrockResponse.outputTokenCount, totalTokens: this.bedrockResponse.totalTokenCount })
|
|
68
64
|
}
|
|
69
65
|
}
|
|
70
66
|
|
|
@@ -65,6 +65,11 @@ class ConverseStreamHandler {
|
|
|
65
65
|
message: {
|
|
66
66
|
|
|
67
67
|
}
|
|
68
|
+
},
|
|
69
|
+
usage: {
|
|
70
|
+
inputTokens: null,
|
|
71
|
+
outputTokens: null,
|
|
72
|
+
totalTokens: null
|
|
68
73
|
}
|
|
69
74
|
}
|
|
70
75
|
}
|
|
@@ -119,9 +124,7 @@ async function * handleConverse() {
|
|
|
119
124
|
for await (const event of this.stream) {
|
|
120
125
|
yield event
|
|
121
126
|
this.updateHeaders(event)
|
|
122
|
-
if (event.
|
|
123
|
-
this.role = 'assistant'
|
|
124
|
-
} else if (event.contentBlockStart?.start) {
|
|
127
|
+
if (event.contentBlockStart?.start) {
|
|
125
128
|
// Handles a Content block start event. Tool use only.
|
|
126
129
|
const blockStartData = event.contentBlockStart.start
|
|
127
130
|
if (blockStartData.toolUse) {
|
|
@@ -133,6 +136,7 @@ async function * handleConverse() {
|
|
|
133
136
|
activeChunk = null
|
|
134
137
|
}
|
|
135
138
|
} else if (event.contentBlockDelta?.delta) {
|
|
139
|
+
// There are also deltas for tool use (stringified inputs) but we don't currently record them so we just ignore for now
|
|
136
140
|
if (event.contentBlockDelta.delta.text) {
|
|
137
141
|
// It seems like the first streamed chunk does not always start with a contentBlockStart message
|
|
138
142
|
// If the stream starts with a delta, assume the current chunk is text
|
|
@@ -141,9 +145,12 @@ async function * handleConverse() {
|
|
|
141
145
|
}
|
|
142
146
|
activeChunk.text += event.contentBlockDelta.delta.text
|
|
143
147
|
}
|
|
144
|
-
|
|
148
|
+
// used in `handleResponse` to determine why the model stopped
|
|
145
149
|
} else if (event.messageStop) {
|
|
146
150
|
this.stopReason = event.messageStop?.stopReason
|
|
151
|
+
// sends token usage info at the end of the stream
|
|
152
|
+
} else if (event.metadata?.usage) {
|
|
153
|
+
this.response.output.usage = event.metadata.usage
|
|
147
154
|
}
|
|
148
155
|
}
|
|
149
156
|
} finally {
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
'use strict'
|
|
7
7
|
|
|
8
8
|
const LlmEvent = require('./event')
|
|
9
|
-
const { validCallbackTokenCount, calculateCallbackTokens } = require('../utils')
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
11
|
* @typedef {object} LlmEmbeddingParams
|
|
@@ -44,28 +43,12 @@ class LlmEmbedding extends LlmEvent {
|
|
|
44
43
|
return
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (hasValidCallbackCounts) {
|
|
51
|
-
this['response.usage.total_tokens'] = Number(totalTokenCount)
|
|
52
|
-
}
|
|
46
|
+
const totalTokens = this.calculateCallbackTokens(tokenCB, this['request.model'], content)
|
|
47
|
+
this.setTokensOnEmbeddingMessage(totalTokens)
|
|
53
48
|
return
|
|
54
49
|
}
|
|
55
50
|
|
|
56
|
-
|
|
57
|
-
if (totalTokens) {
|
|
58
|
-
this['response.usage.total_tokens'] = Number(totalTokens)
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
getTotalTokens() {
|
|
63
|
-
// We record the input token count as total tokens
|
|
64
|
-
const totalToken =
|
|
65
|
-
this.bedrockResponse?.usage?.input_tokens || this.bedrockResponse?.usage?.inputTokens ||
|
|
66
|
-
this.bedrockResponse?.headers['x-amzn-bedrock-input-token-count']
|
|
67
|
-
|
|
68
|
-
return totalToken
|
|
51
|
+
this.setTokensOnEmbeddingMessage(this.bedrockResponse.inputTokenCount)
|
|
69
52
|
}
|
|
70
53
|
}
|
|
71
54
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
'use strict'
|
|
7
7
|
|
|
8
8
|
const { randomUUID } = require('crypto')
|
|
9
|
-
const
|
|
9
|
+
const BaseEvent = require('../event')
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* @typedef {object} LlmEventParams
|
|
@@ -29,7 +29,7 @@ const defaultParams = {
|
|
|
29
29
|
/**
|
|
30
30
|
* Baseline object representing a LLM event.
|
|
31
31
|
*/
|
|
32
|
-
class LlmEvent {
|
|
32
|
+
class LlmEvent extends BaseEvent {
|
|
33
33
|
/**
|
|
34
34
|
* All parameters that were passed in to the constructor after they have
|
|
35
35
|
* been merged with the constructor's defaults.
|
|
@@ -43,6 +43,7 @@ class LlmEvent {
|
|
|
43
43
|
* @param {LlmEventParams} params Construction parameters.
|
|
44
44
|
*/
|
|
45
45
|
constructor(params = defaultParams) {
|
|
46
|
+
super()
|
|
46
47
|
params = Object.assign({}, defaultParams, params)
|
|
47
48
|
this.constructionParams = params
|
|
48
49
|
|
|
@@ -64,24 +65,6 @@ class LlmEvent {
|
|
|
64
65
|
this['request.max_tokens'] = null
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
/**
|
|
68
|
-
* Pull user set `llm.*` attributes from the current transaction and
|
|
69
|
-
* add them to the event.
|
|
70
|
-
*
|
|
71
|
-
* @param {object} agent The New Relic agent that provides access to the
|
|
72
|
-
* transaction.
|
|
73
|
-
*/
|
|
74
|
-
set metadata(agent) { // eslint-disable-line accessor-pairs
|
|
75
|
-
const tx = agent.tracer.getTransaction()
|
|
76
|
-
const attrs = tx?.trace?.custom.get(DESTINATIONS.TRANS_SCOPE) || {}
|
|
77
|
-
for (const [k, v] of Object.entries(attrs)) {
|
|
78
|
-
if (k.startsWith('llm.') === false) {
|
|
79
|
-
continue
|
|
80
|
-
}
|
|
81
|
-
this[k] = v
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
68
|
/**
|
|
86
69
|
* Removes the complex objects from the event
|
|
87
70
|
* This will be called right before the event is enqueued to the custom event aggregator
|