newrelic 12.11.0 → 12.11.2
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 +40 -0
- package/lib/feature_flags.js +1 -1
- package/lib/header-attributes.js +4 -4
- package/lib/instrumentation/aws-sdk/v3/bedrock.js +25 -14
- package/lib/instrumentation/undici.js +9 -48
- package/lib/llm-events/aws-bedrock/bedrock-command.js +55 -21
- package/lib/llm-events/aws-bedrock/bedrock-response.js +3 -1
- package/lib/llm-events/aws-bedrock/chat-completion-message.js +4 -13
- package/lib/llm-events/aws-bedrock/chat-completion-summary.js +1 -1
- package/lib/llm-events/aws-bedrock/embedding.js +6 -4
- package/lib/llm-events/aws-bedrock/utils.js +36 -0
- package/lib/shim/shim.js +2 -2
- package/package.json +1 -1
package/NEWS.md
CHANGED
|
@@ -1,3 +1,43 @@
|
|
|
1
|
+
### v12.11.2 (2025-01-23)
|
|
2
|
+
|
|
3
|
+
#### Features
|
|
4
|
+
|
|
5
|
+
* Added support for Claude 3+ Chat API in Bedrock ([#2870](https://github.com/newrelic/node-newrelic/pull/2870)) ([6a83abf](https://github.com/newrelic/node-newrelic/commit/6a83abf8b1a0cb0f993e9d9285566a09677d7d2e))
|
|
6
|
+
|
|
7
|
+
#### Bug fixes
|
|
8
|
+
|
|
9
|
+
* Added defensive code in shim to prevent crashing when checking parent segment ([#2898](https://github.com/newrelic/node-newrelic/pull/2898)) ([751eb96](https://github.com/newrelic/node-newrelic/commit/751eb9608d7401d1123a0e810eac9dc554127e4e))
|
|
10
|
+
|
|
11
|
+
#### Documentation
|
|
12
|
+
|
|
13
|
+
* Updated compatibility report ([#2888](https://github.com/newrelic/node-newrelic/pull/2888)) ([dce9deb](https://github.com/newrelic/node-newrelic/commit/dce9debeda6c5ed30c0ed6dbc00f73cf11c8f40f))
|
|
14
|
+
|
|
15
|
+
#### Miscellaneous chores
|
|
16
|
+
|
|
17
|
+
* Replaced backtracking regex with new algorithm ([#2887](https://github.com/newrelic/node-newrelic/pull/2887)) ([46462d0](https://github.com/newrelic/node-newrelic/commit/46462d00b68c4a4cedd60d46d531b2f31800df98))
|
|
18
|
+
|
|
19
|
+
#### Tests
|
|
20
|
+
|
|
21
|
+
* Improved test coverage of normalizing claude 3 messages ([#2893](https://github.com/newrelic/node-newrelic/pull/2893)) ([cc65763](https://github.com/newrelic/node-newrelic/commit/cc657633b81daf4b372e5715e696ed3c23ecb678))
|
|
22
|
+
|
|
23
|
+
### v12.11.1 (2025-01-16)
|
|
24
|
+
|
|
25
|
+
#### Bug fixes
|
|
26
|
+
|
|
27
|
+
* Updated undici instrumentation to fix crash with trying to calculate exclusive duration on a segment that no longer exists ([#2884](https://github.com/newrelic/node-newrelic/pull/2884)) ([3b7e4bf](https://github.com/newrelic/node-newrelic/commit/3b7e4bf0a40b21f16b11e7761809cbaa83c02515))
|
|
28
|
+
|
|
29
|
+
#### Documentation
|
|
30
|
+
|
|
31
|
+
* Updated compatibility report ([#2878](https://github.com/newrelic/node-newrelic/pull/2878)) ([e784f84](https://github.com/newrelic/node-newrelic/commit/e784f8427bd49852f0ed7a15d6025ea8dfc73c72))
|
|
32
|
+
|
|
33
|
+
#### Miscellaneous chores
|
|
34
|
+
|
|
35
|
+
* Fixed dns integration tests ([#2883](https://github.com/newrelic/node-newrelic/pull/2883)) ([dd30ad7](https://github.com/newrelic/node-newrelic/commit/dd30ad71f07233682da39ca41f452a55c6798a15))
|
|
36
|
+
|
|
37
|
+
#### Tests
|
|
38
|
+
|
|
39
|
+
* Restored the branch to test apollo-server-plugin to main ([#2879](https://github.com/newrelic/node-newrelic/pull/2879)) ([978faab](https://github.com/newrelic/node-newrelic/commit/978faab59f5378e26cb8f5e584a3c9e684e7bbb1))
|
|
40
|
+
|
|
1
41
|
### v12.11.0 (2025-01-14)
|
|
2
42
|
|
|
3
43
|
#### Features
|
package/lib/feature_flags.js
CHANGED
|
@@ -13,7 +13,6 @@ exports.prerelease = {
|
|
|
13
13
|
|
|
14
14
|
promise_segments: false,
|
|
15
15
|
reverse_naming_rules: false,
|
|
16
|
-
undici_async_tracking: true,
|
|
17
16
|
unresolved_promise_cleanup: true,
|
|
18
17
|
kafkajs_instrumentation: false
|
|
19
18
|
}
|
|
@@ -36,6 +35,7 @@ exports.released = [
|
|
|
36
35
|
'await_support',
|
|
37
36
|
'certificate_bundle',
|
|
38
37
|
'async_local_context',
|
|
38
|
+
'undici_async_tracking',
|
|
39
39
|
'undici_instrumentation',
|
|
40
40
|
'aws_bedrock_instrumentation',
|
|
41
41
|
'langchain_instrumentation'
|
package/lib/header-attributes.js
CHANGED
|
@@ -95,10 +95,10 @@ function _headerToCamelCase(header) {
|
|
|
95
95
|
const newHeader = header.charAt(0).toLowerCase() + header.slice(1)
|
|
96
96
|
|
|
97
97
|
// Converts headers in the form 'header-name' to be in the form 'headerName'
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return
|
|
101
|
-
})
|
|
98
|
+
return newHeader.split(/[\W_]/).map((ele, i) => {
|
|
99
|
+
if (i === 0) return ele
|
|
100
|
+
return ele.slice(0, 1).toUpperCase() + ele.slice(1)
|
|
101
|
+
}).join('')
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
function _collectHeaders(headers, nameMap, prefix, transaction) {
|
|
@@ -118,18 +118,24 @@ function recordChatCompletionMessages({
|
|
|
118
118
|
isError: err !== null
|
|
119
119
|
})
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
121
|
+
// Record context message(s)
|
|
122
|
+
const promptContextMessages = bedrockCommand.prompt
|
|
123
|
+
promptContextMessages.forEach((contextMessage, promptIndex) => {
|
|
124
|
+
const msg = new LlmChatCompletionMessage({
|
|
125
|
+
agent,
|
|
126
|
+
segment,
|
|
127
|
+
transaction,
|
|
128
|
+
bedrockCommand,
|
|
129
|
+
content: contextMessage.content,
|
|
130
|
+
role: contextMessage.role,
|
|
131
|
+
bedrockResponse,
|
|
132
|
+
index: promptIndex,
|
|
133
|
+
completionId: summary.id
|
|
134
|
+
})
|
|
135
|
+
recordEvent({ agent, type: 'LlmChatCompletionMessage', msg })
|
|
129
136
|
})
|
|
130
|
-
recordEvent({ agent, type: 'LlmChatCompletionMessage', msg })
|
|
131
137
|
|
|
132
|
-
bedrockResponse.completions.forEach((content,
|
|
138
|
+
bedrockResponse.completions.forEach((content, completionIndex) => {
|
|
133
139
|
const chatCompletionMessage = new LlmChatCompletionMessage({
|
|
134
140
|
agent,
|
|
135
141
|
segment,
|
|
@@ -137,8 +143,9 @@ function recordChatCompletionMessages({
|
|
|
137
143
|
bedrockCommand,
|
|
138
144
|
bedrockResponse,
|
|
139
145
|
isResponse: true,
|
|
140
|
-
index:
|
|
146
|
+
index: promptContextMessages.length + completionIndex,
|
|
141
147
|
content,
|
|
148
|
+
role: 'assistant',
|
|
142
149
|
completionId: summary.id
|
|
143
150
|
})
|
|
144
151
|
recordEvent({ agent, type: 'LlmChatCompletionMessage', msg: chatCompletionMessage })
|
|
@@ -179,18 +186,22 @@ function recordEmbeddingMessage({
|
|
|
179
186
|
return
|
|
180
187
|
}
|
|
181
188
|
|
|
182
|
-
const
|
|
189
|
+
const embeddings = bedrockCommand.prompt.map(prompt => new LlmEmbedding({
|
|
183
190
|
agent,
|
|
184
191
|
segment,
|
|
185
192
|
transaction,
|
|
186
193
|
bedrockCommand,
|
|
194
|
+
input: prompt.content,
|
|
187
195
|
bedrockResponse,
|
|
188
196
|
isError: err !== null
|
|
197
|
+
}))
|
|
198
|
+
|
|
199
|
+
embeddings.forEach(embedding => {
|
|
200
|
+
recordEvent({ agent, type: 'LlmEmbedding', msg: embedding })
|
|
189
201
|
})
|
|
190
202
|
|
|
191
|
-
recordEvent({ agent, type: 'LlmEmbedding', msg: embedding })
|
|
192
203
|
if (err) {
|
|
193
|
-
const llmError = new LlmError({ bedrockResponse, err, embedding })
|
|
204
|
+
const llmError = new LlmError({ bedrockResponse, err, embedding: embeddings.length === 1 ? embeddings[0] : undefined })
|
|
194
205
|
agent.errors.add(transaction, err, llmError)
|
|
195
206
|
}
|
|
196
207
|
}
|
|
@@ -11,8 +11,6 @@ const logger = require('../logger').child({ component: 'undici' })
|
|
|
11
11
|
const NAMES = require('../metrics/names')
|
|
12
12
|
const symbols = require('../symbols')
|
|
13
13
|
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
14
|
-
const { executionAsyncResource } = require('async_hooks')
|
|
15
|
-
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
16
14
|
const diagnosticsChannel = require('diagnostics_channel')
|
|
17
15
|
const synthetics = require('../synthetics')
|
|
18
16
|
const urltils = require('../util/urltils')
|
|
@@ -49,42 +47,6 @@ module.exports.unsubscribe = function unsubscribe() {
|
|
|
49
47
|
})
|
|
50
48
|
}
|
|
51
49
|
|
|
52
|
-
/**
|
|
53
|
-
* Retrieves the current segment(parent in our context) and transaction from executionAsyncResource
|
|
54
|
-
* or from context manager then adds to the executionAsyncResource for future
|
|
55
|
-
* undici requests within same async context.
|
|
56
|
-
*
|
|
57
|
-
* It was found that when running concurrent undici requests
|
|
58
|
-
* within a transaction that the parent segment would get out of sync
|
|
59
|
-
* depending on the async context of the transaction. By using
|
|
60
|
-
* `async_hooks.executionResource` it is more reliable.
|
|
61
|
-
*
|
|
62
|
-
* Note: However, if you have concurrent undici requests in a transaction
|
|
63
|
-
* and the request to the transaction is using a keep alive there is a chance the
|
|
64
|
-
* executionAsyncResource may be incorrect because of shared connections. To revert to a more
|
|
65
|
-
* naive tracking of parent set `config.feature_flag.undici_async_tracking: false` and
|
|
66
|
-
* it will just get the segment and transaction from the context manager.
|
|
67
|
-
*
|
|
68
|
-
* @param {Shim} shim instance of shim
|
|
69
|
-
* @returns {TraceSegment} parent segment
|
|
70
|
-
*/
|
|
71
|
-
function getParentContext(shim) {
|
|
72
|
-
const { config } = shim.agent
|
|
73
|
-
if (config.feature_flag.undici_async_tracking) {
|
|
74
|
-
const resource = executionAsyncResource()
|
|
75
|
-
|
|
76
|
-
if (!resource[symbols.parentSegment]) {
|
|
77
|
-
const parent = shim.getSegment()
|
|
78
|
-
resource[symbols.parentSegment] = parent
|
|
79
|
-
const tx = shim.tracer.getTransaction()
|
|
80
|
-
resource[symbols.transaction] = tx
|
|
81
|
-
}
|
|
82
|
-
return { segment: resource[symbols.parentSegment], transaction: resource[symbols.transaction] }
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return shim.tracer.getContext()
|
|
86
|
-
}
|
|
87
|
-
|
|
88
50
|
/**
|
|
89
51
|
* Injects relevant DT headers for the external request
|
|
90
52
|
*
|
|
@@ -160,7 +122,7 @@ function createExternalSegment({ shim, request, segment }) {
|
|
|
160
122
|
*/
|
|
161
123
|
function requestCreateHook(shim, { request }) {
|
|
162
124
|
const { config } = shim.agent
|
|
163
|
-
const { transaction, segment } =
|
|
125
|
+
const { transaction, segment } = shim.tracer.getContext()
|
|
164
126
|
request[symbols.parentSegment] = segment
|
|
165
127
|
request[symbols.transaction] = transaction
|
|
166
128
|
if (!(segment || transaction) || segment?.opaque) {
|
|
@@ -221,9 +183,8 @@ function requestHeadersHook(shim, { request, response }) {
|
|
|
221
183
|
}
|
|
222
184
|
|
|
223
185
|
/**
|
|
224
|
-
* Gets the active
|
|
225
|
-
* and ends
|
|
226
|
-
* it will add the error to the transaction
|
|
186
|
+
* Gets the active segment, parent segment and transaction from given ctx(request, client connector)
|
|
187
|
+
* and ends segment and sets the previous parent segment as the active segment. If an error exists it will add the error to the transaction
|
|
227
188
|
*
|
|
228
189
|
* @param {Shim} shim instance of shim
|
|
229
190
|
* @param {object} params object from undici hook
|
|
@@ -236,14 +197,14 @@ function endAndRestoreSegment(shim, { request, error }) {
|
|
|
236
197
|
const tx = request[symbols.transaction]
|
|
237
198
|
if (activeSegment) {
|
|
238
199
|
activeSegment.end()
|
|
200
|
+
}
|
|
239
201
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
202
|
+
if (error && tx) {
|
|
203
|
+
handleError(shim, tx, error)
|
|
204
|
+
}
|
|
243
205
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
206
|
+
if (parentSegment) {
|
|
207
|
+
shim.setActiveSegment(parentSegment)
|
|
247
208
|
}
|
|
248
209
|
}
|
|
249
210
|
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
'use strict'
|
|
7
7
|
|
|
8
|
+
const { stringifyClaudeChunkedMessage } = require('./utils')
|
|
9
|
+
|
|
8
10
|
/**
|
|
9
11
|
* Parses an AWS invoke command instance into a re-usable entity.
|
|
10
12
|
*/
|
|
@@ -68,37 +70,34 @@ class BedrockCommand {
|
|
|
68
70
|
/**
|
|
69
71
|
* The question posed to the LLM.
|
|
70
72
|
*
|
|
71
|
-
* @returns {
|
|
73
|
+
* @returns {object[]} The array of context messages passed to the LLM (or a single user prompt for legacy "non-chat" models)
|
|
72
74
|
*/
|
|
73
75
|
get prompt() {
|
|
74
|
-
let result
|
|
75
76
|
if (this.isTitan() === true || this.isTitanEmbed() === true) {
|
|
76
|
-
|
|
77
|
+
return [
|
|
78
|
+
{
|
|
79
|
+
role: 'user',
|
|
80
|
+
content: this.#body.inputText
|
|
81
|
+
}
|
|
82
|
+
]
|
|
77
83
|
} else if (this.isCohereEmbed() === true) {
|
|
78
|
-
|
|
84
|
+
return [
|
|
85
|
+
{
|
|
86
|
+
role: 'user',
|
|
87
|
+
content: this.#body.texts.join(' ')
|
|
88
|
+
}
|
|
89
|
+
]
|
|
79
90
|
} else if (
|
|
80
|
-
this.
|
|
91
|
+
this.isClaudeTextCompletionApi() === true ||
|
|
81
92
|
this.isAi21() === true ||
|
|
82
93
|
this.isCohere() === true ||
|
|
83
94
|
this.isLlama() === true
|
|
84
95
|
) {
|
|
85
|
-
|
|
86
|
-
} else if (this.
|
|
87
|
-
|
|
88
|
-
for (const message of this.#body?.messages) {
|
|
89
|
-
if (message?.role === 'assistant') {
|
|
90
|
-
continue
|
|
91
|
-
}
|
|
92
|
-
if (typeof message?.content === 'string') {
|
|
93
|
-
collected.push(message?.content)
|
|
94
|
-
continue
|
|
95
|
-
}
|
|
96
|
-
const mappedMsgObj = message?.content.map((msgContent) => msgContent.text)
|
|
97
|
-
collected.push(mappedMsgObj)
|
|
98
|
-
}
|
|
99
|
-
result = collected.join(' ')
|
|
96
|
+
return [{ role: 'user', content: this.#body.prompt }]
|
|
97
|
+
} else if (this.isClaudeMessagesApi() === true) {
|
|
98
|
+
return normalizeClaude3Messages(this.#body?.messages)
|
|
100
99
|
}
|
|
101
|
-
return
|
|
100
|
+
return []
|
|
102
101
|
}
|
|
103
102
|
|
|
104
103
|
/**
|
|
@@ -151,6 +150,41 @@ class BedrockCommand {
|
|
|
151
150
|
isTitanEmbed() {
|
|
152
151
|
return this.#modelId.startsWith('amazon.titan-embed')
|
|
153
152
|
}
|
|
153
|
+
|
|
154
|
+
isClaudeMessagesApi() {
|
|
155
|
+
return (this.isClaude3() === true || this.isClaude() === true) && 'messages' in this.#body
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
isClaudeTextCompletionApi() {
|
|
159
|
+
return this.isClaude() === true && 'prompt' in this.#body
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Claude v3 requests in Bedrock can have two different "chat" flavors. This function normalizes them into a consistent
|
|
165
|
+
* format per the AIM agent spec
|
|
166
|
+
*
|
|
167
|
+
* @param messages - The raw array of messages passed to the invoke API
|
|
168
|
+
* @returns {number|undefined} - The normalized messages
|
|
169
|
+
*/
|
|
170
|
+
function normalizeClaude3Messages(messages) {
|
|
171
|
+
const result = []
|
|
172
|
+
for (const message of messages ?? []) {
|
|
173
|
+
if (message == null) {
|
|
174
|
+
continue
|
|
175
|
+
}
|
|
176
|
+
if (typeof message.content === 'string') {
|
|
177
|
+
// Messages can be specified with plain string content
|
|
178
|
+
result.push({ role: message.role, content: message.content })
|
|
179
|
+
} else if (Array.isArray(message.content)) {
|
|
180
|
+
// Or in a "chunked" format for multi-modal support
|
|
181
|
+
result.push({
|
|
182
|
+
role: message.role,
|
|
183
|
+
content: stringifyClaudeChunkedMessage(message.content)
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return result
|
|
154
188
|
}
|
|
155
189
|
|
|
156
190
|
module.exports = BedrockCommand
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
'use strict'
|
|
7
7
|
|
|
8
|
+
const { stringifyClaudeChunkedMessage } = require('./utils')
|
|
9
|
+
|
|
8
10
|
/**
|
|
9
11
|
* @typedef {object} AwsBedrockMiddlewareResponse
|
|
10
12
|
* @property {object} response Has a `body` property that is an IncomingMessage,
|
|
@@ -63,7 +65,7 @@ class BedrockResponse {
|
|
|
63
65
|
// Streamed response
|
|
64
66
|
this.#completions = body.completions
|
|
65
67
|
} else {
|
|
66
|
-
this.#completions = body?.content
|
|
68
|
+
this.#completions = [stringifyClaudeChunkedMessage(body?.content)]
|
|
67
69
|
}
|
|
68
70
|
this.#id = body.id
|
|
69
71
|
} else if (cmd.isCohere() === true) {
|
|
@@ -39,7 +39,7 @@ class LlmChatCompletionMessage extends LlmEvent {
|
|
|
39
39
|
params = Object.assign({}, defaultParams, params)
|
|
40
40
|
super(params)
|
|
41
41
|
|
|
42
|
-
const { agent, content, isResponse, index, completionId } = params
|
|
42
|
+
const { agent, content, isResponse, index, completionId, role } = params
|
|
43
43
|
const recordContent = agent.config?.ai_monitoring?.record_content?.enabled
|
|
44
44
|
const tokenCB = agent?.llm?.tokenCountCallback
|
|
45
45
|
|
|
@@ -47,20 +47,11 @@ class LlmChatCompletionMessage extends LlmEvent {
|
|
|
47
47
|
this.completion_id = completionId
|
|
48
48
|
this.sequence = index
|
|
49
49
|
this.content = recordContent === true ? content : undefined
|
|
50
|
-
this.role =
|
|
50
|
+
this.role = role
|
|
51
51
|
|
|
52
52
|
this.#setId(index)
|
|
53
|
-
if (
|
|
54
|
-
this.
|
|
55
|
-
if (typeof tokenCB === 'function') {
|
|
56
|
-
this.token_count = tokenCB(this.bedrockCommand.modelId, content)
|
|
57
|
-
}
|
|
58
|
-
} else {
|
|
59
|
-
this.role = 'user'
|
|
60
|
-
this.content = recordContent === true ? this.bedrockCommand.prompt : undefined
|
|
61
|
-
if (typeof tokenCB === 'function') {
|
|
62
|
-
this.token_count = tokenCB(this.bedrockCommand.modelId, this.bedrockCommand.prompt)
|
|
63
|
-
}
|
|
53
|
+
if (typeof tokenCB === 'function') {
|
|
54
|
+
this.token_count = tokenCB(this.bedrockCommand.modelId, content)
|
|
64
55
|
}
|
|
65
56
|
}
|
|
66
57
|
|
|
@@ -36,7 +36,7 @@ class LlmChatCompletionSummary extends LlmEvent {
|
|
|
36
36
|
const cmd = this.bedrockCommand
|
|
37
37
|
this[cfr] = this.bedrockResponse.finishReason
|
|
38
38
|
this[rt] = cmd.temperature
|
|
39
|
-
this[nm] =
|
|
39
|
+
this[nm] = (this.bedrockCommand.prompt.length) + this.bedrockResponse.completions.length
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
@@ -10,7 +10,7 @@ const LlmEvent = require('./event')
|
|
|
10
10
|
/**
|
|
11
11
|
* @typedef {object} LlmEmbeddingParams
|
|
12
12
|
* @augments LlmEventParams
|
|
13
|
-
* @property
|
|
13
|
+
* @property {string} input - The input message for the embedding call
|
|
14
14
|
*/
|
|
15
15
|
/**
|
|
16
16
|
* @type {LlmEmbeddingParams}
|
|
@@ -20,16 +20,18 @@ const defaultParams = {}
|
|
|
20
20
|
class LlmEmbedding extends LlmEvent {
|
|
21
21
|
constructor(params = defaultParams) {
|
|
22
22
|
super(params)
|
|
23
|
-
const { agent } = params
|
|
23
|
+
const { agent, input } = params
|
|
24
24
|
const tokenCb = agent?.llm?.tokenCountCallback
|
|
25
25
|
|
|
26
26
|
this.input = agent.config?.ai_monitoring?.record_content?.enabled
|
|
27
|
-
?
|
|
27
|
+
? input
|
|
28
28
|
: undefined
|
|
29
29
|
this.error = params.isError
|
|
30
30
|
this.duration = params.segment.getDurationInMillis()
|
|
31
|
+
|
|
32
|
+
// Even if not recording content, we should use the local token counting callback to record token usage
|
|
31
33
|
if (typeof tokenCb === 'function') {
|
|
32
|
-
this.token_count = tokenCb(this.bedrockCommand.modelId,
|
|
34
|
+
this.token_count = tokenCb(this.bedrockCommand.modelId, input)
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
37
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 New Relic Corporation. All rights reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
*
|
|
10
|
+
* @param {object[]} chunks - The "chunks" that make up a single conceptual message. In a multi-modal scenario, a single message
|
|
11
|
+
* might have a number of different-typed chunks interspersed
|
|
12
|
+
* @returns {string} - A stringified version of the message. We make a best-effort effort attempt to represent non-text chunks. In the future
|
|
13
|
+
* we may want to extend the agent to support these non-text chunks in a richer way. Placeholders are represented in an XML-like format but
|
|
14
|
+
* are NOT intended to be parsed as valid XML
|
|
15
|
+
*/
|
|
16
|
+
function stringifyClaudeChunkedMessage(chunks) {
|
|
17
|
+
const stringifiedChunks = chunks.map((msgContent) => {
|
|
18
|
+
switch (msgContent.type) {
|
|
19
|
+
case 'text':
|
|
20
|
+
return msgContent.text
|
|
21
|
+
case 'image':
|
|
22
|
+
return '<image>'
|
|
23
|
+
case 'tool_use':
|
|
24
|
+
return `<tool_use>${msgContent.name}</tool_use>`
|
|
25
|
+
case 'tool_result':
|
|
26
|
+
return `<tool_result>${msgContent.content}</tool_result>`
|
|
27
|
+
default:
|
|
28
|
+
return '<unknown_chunk>'
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
return stringifiedChunks.join('\n\n')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = {
|
|
35
|
+
stringifyClaudeChunkedMessage
|
|
36
|
+
}
|
package/lib/shim/shim.js
CHANGED
|
@@ -677,7 +677,7 @@ function recordWrapper({ shim, fn, name, recordNamer }) {
|
|
|
677
677
|
return fnApply.call(fn, this, args)
|
|
678
678
|
}
|
|
679
679
|
|
|
680
|
-
//
|
|
680
|
+
// middleware recorders pass in parent segment
|
|
681
681
|
// we need to destructure this as it is not needed past this function
|
|
682
682
|
// and will overwhelm trace level loggers with logging the entire spec
|
|
683
683
|
const { parent: specParent, ...segDesc } = spec
|
|
@@ -686,7 +686,7 @@ function recordWrapper({ shim, fn, name, recordNamer }) {
|
|
|
686
686
|
const transaction = context.transaction
|
|
687
687
|
const parent = transaction?.isActive() && specParent ? specParent : context.segment
|
|
688
688
|
|
|
689
|
-
if (!transaction?.isActive()) {
|
|
689
|
+
if (!transaction?.isActive() || !parent) {
|
|
690
690
|
shim.logger.debug('Not recording function %s, not in a transaction.', name)
|
|
691
691
|
return fnApply.call(fn, this, arguments)
|
|
692
692
|
}
|