dd-trace 5.100.0 → 5.102.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/index.d.ts +14 -0
- package/package.json +11 -9
- package/packages/datadog-instrumentations/src/aerospike.js +2 -2
- package/packages/datadog-instrumentations/src/ai.js +8 -8
- package/packages/datadog-instrumentations/src/amqplib.js +6 -7
- package/packages/datadog-instrumentations/src/anthropic.js +10 -10
- package/packages/datadog-instrumentations/src/apollo-server-core.js +3 -3
- package/packages/datadog-instrumentations/src/apollo-server.js +5 -5
- package/packages/datadog-instrumentations/src/avsc.js +6 -6
- package/packages/datadog-instrumentations/src/aws-sdk.js +151 -67
- package/packages/datadog-instrumentations/src/azure-durable-functions.js +8 -8
- package/packages/datadog-instrumentations/src/bluebird.js +2 -2
- package/packages/datadog-instrumentations/src/body-parser.js +2 -2
- package/packages/datadog-instrumentations/src/cassandra-driver.js +7 -7
- package/packages/datadog-instrumentations/src/child_process.js +12 -12
- package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +9 -9
- package/packages/datadog-instrumentations/src/connect.js +7 -7
- package/packages/datadog-instrumentations/src/cookie-parser.js +4 -4
- package/packages/datadog-instrumentations/src/cookie.js +2 -2
- package/packages/datadog-instrumentations/src/couchbase.js +16 -30
- package/packages/datadog-instrumentations/src/crypto.js +4 -4
- package/packages/datadog-instrumentations/src/cucumber.js +77 -16
- package/packages/datadog-instrumentations/src/cypress.js +5 -3
- package/packages/datadog-instrumentations/src/dns.js +0 -3
- package/packages/datadog-instrumentations/src/elasticsearch.js +8 -11
- package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +6 -6
- package/packages/datadog-instrumentations/src/express-session.js +4 -4
- package/packages/datadog-instrumentations/src/express.js +10 -11
- package/packages/datadog-instrumentations/src/fastify.js +2 -2
- package/packages/datadog-instrumentations/src/fs.js +14 -14
- package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +5 -7
- package/packages/datadog-instrumentations/src/google-genai.js +4 -4
- package/packages/datadog-instrumentations/src/grpc/server.js +2 -2
- package/packages/datadog-instrumentations/src/hapi.js +2 -2
- package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +8 -8
- package/packages/datadog-instrumentations/src/helpers/promise.js +2 -2
- package/packages/datadog-instrumentations/src/hono.js +2 -2
- package/packages/datadog-instrumentations/src/http/client.js +26 -9
- package/packages/datadog-instrumentations/src/http/server.js +9 -9
- package/packages/datadog-instrumentations/src/jest.js +93 -63
- package/packages/datadog-instrumentations/src/kafkajs.js +9 -9
- package/packages/datadog-instrumentations/src/knex.js +17 -17
- package/packages/datadog-instrumentations/src/koa.js +12 -12
- package/packages/datadog-instrumentations/src/ldapjs.js +5 -5
- package/packages/datadog-instrumentations/src/light-my-request.js +2 -2
- package/packages/datadog-instrumentations/src/limitd-client.js +4 -4
- package/packages/datadog-instrumentations/src/lodash.js +4 -4
- package/packages/datadog-instrumentations/src/mariadb.js +13 -13
- package/packages/datadog-instrumentations/src/memcached.js +2 -2
- package/packages/datadog-instrumentations/src/microgateway-core.js +2 -2
- package/packages/datadog-instrumentations/src/mocha/common.js +7 -4
- package/packages/datadog-instrumentations/src/mocha/main.js +37 -14
- package/packages/datadog-instrumentations/src/mocha/utils.js +133 -16
- package/packages/datadog-instrumentations/src/mocha/worker.js +12 -7
- package/packages/datadog-instrumentations/src/mongodb-core.js +9 -22
- package/packages/datadog-instrumentations/src/mongodb.js +5 -5
- package/packages/datadog-instrumentations/src/mongoose.js +21 -21
- package/packages/datadog-instrumentations/src/mquery.js +5 -5
- package/packages/datadog-instrumentations/src/multer.js +4 -4
- package/packages/datadog-instrumentations/src/mysql.js +16 -16
- package/packages/datadog-instrumentations/src/mysql2.js +4 -4
- package/packages/datadog-instrumentations/src/net.js +14 -8
- package/packages/datadog-instrumentations/src/nyc.js +5 -5
- package/packages/datadog-instrumentations/src/openai.js +19 -19
- package/packages/datadog-instrumentations/src/oracledb.js +6 -6
- package/packages/datadog-instrumentations/src/otel-sdk-trace.js +11 -6
- package/packages/datadog-instrumentations/src/passport-utils.js +5 -5
- package/packages/datadog-instrumentations/src/pg.js +15 -15
- package/packages/datadog-instrumentations/src/pino.js +6 -10
- package/packages/datadog-instrumentations/src/playwright.js +20 -15
- package/packages/datadog-instrumentations/src/protobufjs.js +16 -16
- package/packages/datadog-instrumentations/src/redis.js +1 -2
- package/packages/datadog-instrumentations/src/restify.js +2 -2
- package/packages/datadog-instrumentations/src/router.js +12 -12
- package/packages/datadog-instrumentations/src/stripe.js +12 -12
- package/packages/datadog-instrumentations/src/vitest.js +107 -26
- package/packages/datadog-instrumentations/src/winston.js +4 -4
- package/packages/datadog-instrumentations/src/ws.js +7 -7
- package/packages/datadog-plugin-aws-sdk/src/base.js +52 -4
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +19 -12
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +45 -35
- package/packages/datadog-plugin-aws-sdk/src/services/lambda.js +33 -22
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +12 -13
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +73 -54
- package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +19 -17
- package/packages/datadog-plugin-aws-sdk/src/util.js +22 -0
- package/packages/datadog-plugin-bullmq/src/consumer.js +2 -2
- package/packages/datadog-plugin-bullmq/src/producer.js +14 -20
- package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +6 -6
- package/packages/datadog-plugin-cucumber/src/index.js +4 -0
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +18 -4
- package/packages/datadog-plugin-cypress/src/plugin.js +5 -14
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +1 -5
- package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +3 -1
- package/packages/datadog-plugin-http/src/client.js +1 -5
- package/packages/datadog-plugin-jest/src/util.js +1 -2
- package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -9
- package/packages/datadog-plugin-kafkajs/src/producer.js +2 -8
- package/packages/datadog-plugin-mocha/src/index.js +4 -0
- package/packages/datadog-plugin-mongodb-core/src/index.js +2 -1
- package/packages/datadog-plugin-openai/src/tracing.js +12 -23
- package/packages/datadog-plugin-playwright/src/index.js +1 -1
- package/packages/datadog-plugin-vitest/src/index.js +8 -1
- package/packages/datadog-shimmer/src/shimmer.js +7 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-rules.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-rules.js +81 -81
- package/packages/dd-trace/src/appsec/iast/security-controls/index.js +2 -2
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +2 -2
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +2 -2
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +2 -2
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +2 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +1 -3
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +83 -48
- package/packages/dd-trace/src/appsec/index.js +21 -24
- package/packages/dd-trace/src/appsec/reporter.js +7 -2
- package/packages/dd-trace/src/appsec/rule_manager.js +4 -2
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +31 -16
- package/packages/dd-trace/src/ci-visibility/lage.js +2 -1
- package/packages/dd-trace/src/ci-visibility/requests/request.js +11 -33
- package/packages/dd-trace/src/config/config-types.d.ts +0 -2
- package/packages/dd-trace/src/config/git_properties.js +2 -2
- package/packages/dd-trace/src/config/index.js +1 -55
- package/packages/dd-trace/src/datastreams/checkpointer.js +4 -10
- package/packages/dd-trace/src/datastreams/encoding.js +39 -28
- package/packages/dd-trace/src/datastreams/index.js +2 -1
- package/packages/dd-trace/src/datastreams/pathway.js +29 -26
- package/packages/dd-trace/src/datastreams/processor.js +18 -17
- package/packages/dd-trace/src/datastreams/size.js +6 -2
- package/packages/dd-trace/src/debugger/config.js +5 -2
- package/packages/dd-trace/src/debugger/devtools_client/index.js +2 -5
- package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -1
- package/packages/dd-trace/src/debugger/devtools_client/snapshot-pruner.js +1 -0
- package/packages/dd-trace/src/dogstatsd.js +10 -7
- package/packages/dd-trace/src/encode/0.4.js +759 -234
- package/packages/dd-trace/src/encode/0.5.js +15 -9
- package/packages/dd-trace/src/encode/agentless-json.js +2 -2
- package/packages/dd-trace/src/encode/tags-processors.js +2 -27
- package/packages/dd-trace/src/exporters/common/request.js +22 -11
- package/packages/dd-trace/src/exporters/common/retry.js +104 -0
- package/packages/dd-trace/src/git_metadata.js +66 -0
- package/packages/dd-trace/src/git_metadata_tagger.js +13 -5
- package/packages/dd-trace/src/id.js +15 -26
- package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -2
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +27 -16
- package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +3 -0
- package/packages/dd-trace/src/llmobs/plugins/genai/util.js +33 -13
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +20 -50
- package/packages/dd-trace/src/llmobs/sdk.js +29 -27
- package/packages/dd-trace/src/llmobs/span_processor.js +52 -6
- package/packages/dd-trace/src/llmobs/tagger.js +42 -0
- package/packages/dd-trace/src/llmobs/telemetry.js +29 -0
- package/packages/dd-trace/src/llmobs/util.js +81 -5
- package/packages/dd-trace/src/msgpack/chunk.js +6 -3
- package/packages/dd-trace/src/openfeature/noop.js +40 -36
- package/packages/dd-trace/src/openfeature/writers/exposures.js +33 -52
- package/packages/dd-trace/src/opentelemetry/active-span-proxy.js +42 -0
- package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +106 -0
- package/packages/dd-trace/src/opentelemetry/context_manager.js +11 -2
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +1 -2
- package/packages/dd-trace/src/opentelemetry/span-helpers.js +188 -50
- package/packages/dd-trace/src/opentelemetry/span.js +42 -80
- package/packages/dd-trace/src/opentelemetry/tracer.js +0 -22
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +65 -27
- package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +2 -11
- package/packages/dd-trace/src/opentracing/propagation/tracestate.js +58 -22
- package/packages/dd-trace/src/opentracing/span.js +56 -48
- package/packages/dd-trace/src/opentracing/span_context.js +1 -0
- package/packages/dd-trace/src/plugins/util/ci.js +1 -1
- package/packages/dd-trace/src/plugins/util/git-cache.js +3 -5
- package/packages/dd-trace/src/plugins/util/test.js +19 -7
- package/packages/dd-trace/src/plugins/util/url.js +1 -3
- package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -1
- package/packages/dd-trace/src/plugins/util/web.js +5 -7
- package/packages/dd-trace/src/priority_sampler.js +6 -4
- package/packages/dd-trace/src/profiling/config.js +5 -4
- package/packages/dd-trace/src/profiling/profilers/events.js +3 -23
- package/packages/dd-trace/src/profiling/profilers/wall.js +4 -5
- package/packages/dd-trace/src/remote_config/index.js +5 -3
- package/packages/dd-trace/src/runtime_metrics/index.js +2 -2
- package/packages/dd-trace/src/scope.js +3 -10
- package/packages/dd-trace/src/serverless.js +1 -4
- package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +7 -1
- package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +4 -0
- package/packages/dd-trace/src/span_format.js +52 -5
- package/packages/dd-trace/src/span_processor.js +0 -4
- package/packages/dd-trace/src/spanleak.js +0 -1
- package/packages/dd-trace/src/tracer.js +7 -7
- package/packages/dd-trace/src/util.js +17 -0
|
@@ -7,6 +7,7 @@ const {
|
|
|
7
7
|
INSTRUMENTATION_METHOD_AUTO,
|
|
8
8
|
UNKNOWN_MODEL_PROVIDER,
|
|
9
9
|
} = require('../../constants/tags')
|
|
10
|
+
const { safeJsonParse } = require('../../util')
|
|
10
11
|
const {
|
|
11
12
|
extractChatTemplateFromInstructions,
|
|
12
13
|
normalizePromptVariables,
|
|
@@ -183,13 +184,12 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
|
|
|
183
184
|
_tagChatCompletion (span, inputs, response, error) {
|
|
184
185
|
const { messages, model, ...parameters } = inputs
|
|
185
186
|
|
|
186
|
-
const metadata =
|
|
187
|
-
|
|
188
|
-
|
|
187
|
+
const metadata = {}
|
|
188
|
+
for (const key of Object.keys(parameters)) {
|
|
189
|
+
if (key !== 'tools' && key !== 'functions') {
|
|
190
|
+
metadata[key] = parameters[key]
|
|
189
191
|
}
|
|
190
|
-
|
|
191
|
-
return obj
|
|
192
|
-
}, {})
|
|
192
|
+
}
|
|
193
193
|
|
|
194
194
|
this._tagger.tagMetadata(span, metadata)
|
|
195
195
|
|
|
@@ -213,14 +213,14 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
|
|
|
213
213
|
if (message.function_call) {
|
|
214
214
|
const functionCallInfo = {
|
|
215
215
|
name: message.function_call.name,
|
|
216
|
-
arguments:
|
|
216
|
+
arguments: safeJsonParse(message.function_call.arguments),
|
|
217
217
|
}
|
|
218
218
|
outputMessages.push({ content, role, toolCalls: [functionCallInfo] })
|
|
219
219
|
} else if (message.tool_calls) {
|
|
220
220
|
const toolCallsInfo = []
|
|
221
221
|
for (const toolCall of message.tool_calls) {
|
|
222
222
|
const toolCallInfo = {
|
|
223
|
-
arguments:
|
|
223
|
+
arguments: safeJsonParse(toolCall.function.arguments),
|
|
224
224
|
name: toolCall.function.name,
|
|
225
225
|
toolId: toolCall.id,
|
|
226
226
|
type: toolCall.type,
|
|
@@ -277,22 +277,12 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
|
|
|
277
277
|
inputMessages.push({ role, content })
|
|
278
278
|
}
|
|
279
279
|
} else if (item.type === 'function_call') {
|
|
280
|
-
// Function call: convert to message with tool_calls
|
|
281
|
-
// Parse arguments if it's a JSON string
|
|
282
|
-
let parsedArgs = item.arguments
|
|
283
|
-
if (typeof parsedArgs === 'string') {
|
|
284
|
-
try {
|
|
285
|
-
parsedArgs = JSON.parse(parsedArgs)
|
|
286
|
-
} catch {
|
|
287
|
-
parsedArgs = {}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
280
|
inputMessages.push({
|
|
291
281
|
role: 'assistant',
|
|
292
282
|
toolCalls: [{
|
|
293
283
|
toolId: item.call_id,
|
|
294
284
|
name: item.name,
|
|
295
|
-
arguments:
|
|
285
|
+
arguments: safeJsonParse(item.arguments, {}),
|
|
296
286
|
type: item.type,
|
|
297
287
|
}],
|
|
298
288
|
})
|
|
@@ -317,12 +307,12 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
|
|
|
317
307
|
inputMessages.push({ role: 'user', content: input })
|
|
318
308
|
}
|
|
319
309
|
|
|
320
|
-
const inputMetadata =
|
|
310
|
+
const inputMetadata = {}
|
|
311
|
+
for (const key of Object.keys(parameters)) {
|
|
321
312
|
if (allowedParamKeys.has(key)) {
|
|
322
|
-
|
|
313
|
+
inputMetadata[key] = parameters[key]
|
|
323
314
|
}
|
|
324
|
-
|
|
325
|
-
}, {})
|
|
315
|
+
}
|
|
326
316
|
|
|
327
317
|
this._tagger.tagMetadata(span, inputMetadata)
|
|
328
318
|
|
|
@@ -354,21 +344,12 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
|
|
|
354
344
|
})
|
|
355
345
|
} else if (item.type === 'function_call') {
|
|
356
346
|
// Handle function_call type (responses API tool calls)
|
|
357
|
-
let args = item.arguments
|
|
358
|
-
// Parse arguments if it's a JSON string
|
|
359
|
-
if (typeof args === 'string') {
|
|
360
|
-
try {
|
|
361
|
-
args = JSON.parse(args)
|
|
362
|
-
} catch {
|
|
363
|
-
args = {}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
347
|
outputMessages.push({
|
|
367
348
|
role: 'assistant',
|
|
368
349
|
toolCalls: [{
|
|
369
350
|
toolId: item.call_id,
|
|
370
351
|
name: item.name,
|
|
371
|
-
arguments:
|
|
352
|
+
arguments: safeJsonParse(item.arguments, {}),
|
|
372
353
|
type: item.type,
|
|
373
354
|
}],
|
|
374
355
|
})
|
|
@@ -390,23 +371,12 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
|
|
|
390
371
|
|
|
391
372
|
// Extract tool calls if present in message.tool_calls
|
|
392
373
|
if (Array.isArray(item.tool_calls)) {
|
|
393
|
-
outputMsg.toolCalls = item.tool_calls.map(tc => {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
} catch {
|
|
400
|
-
args = {}
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
return {
|
|
404
|
-
toolId: tc.id,
|
|
405
|
-
name: tc.function?.name || tc.name,
|
|
406
|
-
arguments: args,
|
|
407
|
-
type: tc.type || 'function_call',
|
|
408
|
-
}
|
|
409
|
-
})
|
|
374
|
+
outputMsg.toolCalls = item.tool_calls.map(tc => ({
|
|
375
|
+
toolId: tc.id,
|
|
376
|
+
name: tc.function?.name || tc.name,
|
|
377
|
+
arguments: safeJsonParse(tc.function?.arguments || tc.arguments, {}),
|
|
378
|
+
type: tc.type || 'function_call',
|
|
379
|
+
}))
|
|
410
380
|
}
|
|
411
381
|
|
|
412
382
|
outputMessages.push(outputMsg)
|
|
@@ -112,16 +112,16 @@ class LLMObs extends NoopLLMObs {
|
|
|
112
112
|
const {
|
|
113
113
|
spanOptions,
|
|
114
114
|
...llmobsOptions
|
|
115
|
-
} = this
|
|
115
|
+
} = this.#extractOptions(options)
|
|
116
116
|
|
|
117
117
|
if (fn.length > 1) {
|
|
118
118
|
return this._tracer.trace(name, spanOptions, (span, cb) =>
|
|
119
|
-
this
|
|
119
|
+
this.#activate(span, { kind, ...llmobsOptions }, () => fn(span, cb))
|
|
120
120
|
)
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
return this._tracer.trace(name, spanOptions, span =>
|
|
124
|
-
this
|
|
124
|
+
this.#activate(span, { kind, ...llmobsOptions }, () => fn(span))
|
|
125
125
|
)
|
|
126
126
|
}
|
|
127
127
|
|
|
@@ -142,53 +142,53 @@ class LLMObs extends NoopLLMObs {
|
|
|
142
142
|
const {
|
|
143
143
|
spanOptions,
|
|
144
144
|
...llmobsOptions
|
|
145
|
-
} = this
|
|
145
|
+
} = this.#extractOptions(options)
|
|
146
146
|
|
|
147
147
|
const llmobs = this
|
|
148
148
|
|
|
149
|
-
function wrapped () {
|
|
149
|
+
function wrapped (...args) {
|
|
150
150
|
telemetry.incrementLLMObsSpanStartCount({ autoinstrumented: false, kind })
|
|
151
151
|
|
|
152
152
|
const span = llmobs._tracer.scope().active()
|
|
153
|
-
const fnArgs =
|
|
153
|
+
const fnArgs = args
|
|
154
154
|
|
|
155
155
|
const lastArgId = fnArgs.length - 1
|
|
156
156
|
const cb = fnArgs[lastArgId]
|
|
157
157
|
const hasCallback = typeof cb === 'function'
|
|
158
158
|
|
|
159
159
|
if (hasCallback) {
|
|
160
|
-
const scopeBoundCb = llmobs
|
|
161
|
-
fnArgs[lastArgId] = function () {
|
|
160
|
+
const scopeBoundCb = llmobs.#bind(cb)
|
|
161
|
+
fnArgs[lastArgId] = function (...args) {
|
|
162
162
|
// it is standard practice to follow the callback signature (err, result)
|
|
163
163
|
// however, we try to parse the arguments to determine if the first argument is an error
|
|
164
164
|
// if it is not, and is not undefined, we will use that for the output value
|
|
165
|
-
const maybeError =
|
|
166
|
-
const maybeResult =
|
|
165
|
+
const maybeError = args[0]
|
|
166
|
+
const maybeResult = args[1]
|
|
167
167
|
|
|
168
|
-
llmobs
|
|
168
|
+
llmobs.#autoAnnotate(
|
|
169
169
|
span,
|
|
170
170
|
kind,
|
|
171
171
|
getFunctionArguments(fn, fnArgs),
|
|
172
172
|
isError(maybeError) || maybeError == null ? maybeResult : maybeError
|
|
173
173
|
)
|
|
174
174
|
|
|
175
|
-
return scopeBoundCb.apply(this,
|
|
175
|
+
return scopeBoundCb.apply(this, args)
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
try {
|
|
180
|
-
const result = llmobs
|
|
180
|
+
const result = llmobs.#activate(span, { kind, ...llmobsOptions }, () => fn.apply(this, fnArgs))
|
|
181
181
|
|
|
182
182
|
if (result && typeof result.then === 'function') {
|
|
183
183
|
return result.then(
|
|
184
184
|
value => {
|
|
185
185
|
if (!hasCallback) {
|
|
186
|
-
llmobs
|
|
186
|
+
llmobs.#autoAnnotate(span, kind, getFunctionArguments(fn, fnArgs), value)
|
|
187
187
|
}
|
|
188
188
|
return value
|
|
189
189
|
},
|
|
190
190
|
err => {
|
|
191
|
-
llmobs
|
|
191
|
+
llmobs.#autoAnnotate(span, kind, getFunctionArguments(fn, fnArgs))
|
|
192
192
|
throw err
|
|
193
193
|
}
|
|
194
194
|
)
|
|
@@ -199,12 +199,12 @@ class LLMObs extends NoopLLMObs {
|
|
|
199
199
|
// the callback is called before the function returns (although unlikely)
|
|
200
200
|
// we do not want to throw for "annotating a finished span" in this case
|
|
201
201
|
if (!hasCallback) {
|
|
202
|
-
llmobs
|
|
202
|
+
llmobs.#autoAnnotate(span, kind, getFunctionArguments(fn, fnArgs), result)
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
return result
|
|
206
206
|
} catch (e) {
|
|
207
|
-
llmobs
|
|
207
|
+
llmobs.#autoAnnotate(span, kind, getFunctionArguments(fn, fnArgs))
|
|
208
208
|
throw e
|
|
209
209
|
}
|
|
210
210
|
}
|
|
@@ -251,7 +251,7 @@ class LLMObs extends NoopLLMObs {
|
|
|
251
251
|
throw new Error('LLMObs span must have a span kind specified')
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
-
const { inputData, outputData, metadata, metrics, tags, prompt } = options
|
|
254
|
+
const { inputData, outputData, metadata, metrics, tags, prompt, costTags } = options
|
|
255
255
|
|
|
256
256
|
if (inputData || outputData) {
|
|
257
257
|
if (spanKind === 'llm') {
|
|
@@ -271,9 +271,13 @@ class LLMObs extends NoopLLMObs {
|
|
|
271
271
|
if (metrics) {
|
|
272
272
|
this._tagger.tagMetrics(span, metrics)
|
|
273
273
|
}
|
|
274
|
+
// Apply tags before costTags so costTags can reference tags from the same annotation.
|
|
274
275
|
if (tags) {
|
|
275
276
|
this._tagger.tagSpanTags(span, tags)
|
|
276
277
|
}
|
|
278
|
+
if (costTags != null) {
|
|
279
|
+
this._tagger.tagCostTags(span, costTags, 'annotate')
|
|
280
|
+
}
|
|
277
281
|
if (prompt) {
|
|
278
282
|
this._tagger.tagPrompt(span, prompt)
|
|
279
283
|
}
|
|
@@ -512,7 +516,7 @@ class LLMObs extends NoopLLMObs {
|
|
|
512
516
|
flushCh.publish()
|
|
513
517
|
}
|
|
514
518
|
|
|
515
|
-
|
|
519
|
+
#autoAnnotate (span, kind, input, output) {
|
|
516
520
|
const annotations = {}
|
|
517
521
|
if (input && !['llm', 'embedding'].includes(kind) && !LLMObsTagger.tagMap.get(span)?.[INPUT_VALUE]) {
|
|
518
522
|
annotations.inputData = input
|
|
@@ -530,7 +534,7 @@ class LLMObs extends NoopLLMObs {
|
|
|
530
534
|
return store?.span
|
|
531
535
|
}
|
|
532
536
|
|
|
533
|
-
|
|
537
|
+
#activate (span, options, fn) {
|
|
534
538
|
const parentStore = storage.getStore()
|
|
535
539
|
if (this.enabled) storage.enterWith({ ...parentStore, span })
|
|
536
540
|
|
|
@@ -563,22 +567,20 @@ class LLMObs extends NoopLLMObs {
|
|
|
563
567
|
}
|
|
564
568
|
|
|
565
569
|
// bind function to active LLMObs span
|
|
566
|
-
|
|
570
|
+
#bind (fn) {
|
|
567
571
|
if (typeof fn !== 'function') return fn
|
|
568
572
|
|
|
569
573
|
const llmobs = this
|
|
570
574
|
const activeSpan = llmobs._active()
|
|
571
575
|
|
|
572
|
-
|
|
573
|
-
return llmobs
|
|
574
|
-
return fn.apply(this,
|
|
576
|
+
return function (...args) {
|
|
577
|
+
return llmobs.#activate(activeSpan, null, () => {
|
|
578
|
+
return fn.apply(this, args)
|
|
575
579
|
})
|
|
576
580
|
}
|
|
577
|
-
|
|
578
|
-
return bound
|
|
579
581
|
}
|
|
580
582
|
|
|
581
|
-
|
|
583
|
+
#extractOptions (options) {
|
|
582
584
|
const {
|
|
583
585
|
modelName,
|
|
584
586
|
modelProvider,
|
|
@@ -14,6 +14,8 @@ const {
|
|
|
14
14
|
MODEL_NAME,
|
|
15
15
|
MODEL_PROVIDER,
|
|
16
16
|
METADATA,
|
|
17
|
+
COST_TAGS,
|
|
18
|
+
TOOL_DEFINITIONS,
|
|
17
19
|
INPUT_MESSAGES,
|
|
18
20
|
INPUT_VALUE,
|
|
19
21
|
INTEGRATION,
|
|
@@ -37,10 +39,16 @@ const telemetry = require('./telemetry')
|
|
|
37
39
|
const LLMObsTagger = require('./tagger')
|
|
38
40
|
|
|
39
41
|
class LLMObservabilitySpan {
|
|
40
|
-
|
|
42
|
+
/**
|
|
43
|
+
* @param {string} kind span kind
|
|
44
|
+
*/
|
|
45
|
+
constructor (kind) {
|
|
41
46
|
this.input = []
|
|
42
47
|
this.output = []
|
|
43
48
|
|
|
49
|
+
/** @type {string} */
|
|
50
|
+
this.kind = kind
|
|
51
|
+
|
|
44
52
|
this._tags = {}
|
|
45
53
|
}
|
|
46
54
|
|
|
@@ -113,7 +121,6 @@ class LLMObsSpanProcessor {
|
|
|
113
121
|
}
|
|
114
122
|
|
|
115
123
|
format (span) {
|
|
116
|
-
const llmObsSpan = new LLMObservabilitySpan()
|
|
117
124
|
let inputType, outputType
|
|
118
125
|
|
|
119
126
|
const spanTags = span.context()._tags
|
|
@@ -130,15 +137,31 @@ class LLMObsSpanProcessor {
|
|
|
130
137
|
meta.model_provider = (mlObsTags[MODEL_PROVIDER] || 'custom').toLowerCase()
|
|
131
138
|
}
|
|
132
139
|
|
|
133
|
-
if (mlObsTags[METADATA]) {
|
|
134
|
-
|
|
140
|
+
if (mlObsTags[METADATA] || mlObsTags[COST_TAGS]) {
|
|
141
|
+
const metadata = {}
|
|
142
|
+
if (mlObsTags[METADATA]) this.#addObject(mlObsTags[METADATA], metadata)
|
|
143
|
+
// Only seed `metadata._dd` when there's something to put in it (currently cost_tags). Mirrors
|
|
144
|
+
// dd-trace-py and the cross-language wire format enforced by system-tests — metadata-only
|
|
145
|
+
// spans must not carry an empty `_dd: {}` block.
|
|
146
|
+
if (mlObsTags[COST_TAGS]) {
|
|
147
|
+
this.#getDdMetadata(metadata).cost_tags = mlObsTags[COST_TAGS]
|
|
148
|
+
}
|
|
149
|
+
meta.metadata = metadata
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (mlObsTags[TOOL_DEFINITIONS]) {
|
|
153
|
+
meta.tool_definitions = []
|
|
154
|
+
this.#addObject(mlObsTags[TOOL_DEFINITIONS], meta.tool_definitions)
|
|
135
155
|
}
|
|
136
156
|
|
|
157
|
+
const llmObsSpan = new LLMObservabilitySpan(spanKind)
|
|
158
|
+
|
|
137
159
|
if (spanKind === 'llm' && mlObsTags[INPUT_MESSAGES]) {
|
|
138
160
|
llmObsSpan.input = mlObsTags[INPUT_MESSAGES]
|
|
139
161
|
inputType = 'messages'
|
|
140
162
|
} else if (spanKind === 'embedding' && mlObsTags[INPUT_DOCUMENTS]) {
|
|
141
|
-
input
|
|
163
|
+
llmObsSpan.input = mlObsTags[INPUT_DOCUMENTS].map(doc => ({ content: doc.text, role: '' }))
|
|
164
|
+
inputType = 'documents'
|
|
142
165
|
} else if (mlObsTags[INPUT_VALUE]) {
|
|
143
166
|
llmObsSpan.input = [{ role: '', content: mlObsTags[INPUT_VALUE] }]
|
|
144
167
|
inputType = 'value'
|
|
@@ -148,7 +171,8 @@ class LLMObsSpanProcessor {
|
|
|
148
171
|
llmObsSpan.output = mlObsTags[OUTPUT_MESSAGES]
|
|
149
172
|
outputType = 'messages'
|
|
150
173
|
} else if (spanKind === 'retrieval' && mlObsTags[OUTPUT_DOCUMENTS]) {
|
|
151
|
-
output
|
|
174
|
+
llmObsSpan.output = mlObsTags[OUTPUT_DOCUMENTS].map(doc => ({ content: doc.text, role: '' }))
|
|
175
|
+
outputType = 'documents'
|
|
152
176
|
} else if (mlObsTags[OUTPUT_VALUE]) {
|
|
153
177
|
llmObsSpan.output = [{ role: '', content: mlObsTags[OUTPUT_VALUE] }]
|
|
154
178
|
outputType = 'value'
|
|
@@ -180,6 +204,11 @@ class LLMObsSpanProcessor {
|
|
|
180
204
|
input.messages = processedSpan.input
|
|
181
205
|
} else if (inputType === 'value') {
|
|
182
206
|
input.value = processedSpan.input[0].content
|
|
207
|
+
} else if (inputType === 'documents') {
|
|
208
|
+
input.documents = processedSpan.input.map((processedDocument, processedDocumentIdx) => ({
|
|
209
|
+
...mlObsTags[INPUT_DOCUMENTS][processedDocumentIdx],
|
|
210
|
+
text: processedDocument.content,
|
|
211
|
+
}))
|
|
183
212
|
}
|
|
184
213
|
}
|
|
185
214
|
|
|
@@ -188,6 +217,11 @@ class LLMObsSpanProcessor {
|
|
|
188
217
|
output.messages = processedSpan.output
|
|
189
218
|
} else if (outputType === 'value') {
|
|
190
219
|
output.value = processedSpan.output[0].content
|
|
220
|
+
} else if (outputType === 'documents') {
|
|
221
|
+
output.documents = processedSpan.output.map((processedDocument, processedDocumentIdx) => ({
|
|
222
|
+
...mlObsTags[OUTPUT_DOCUMENTS][processedDocumentIdx],
|
|
223
|
+
text: processedDocument.content,
|
|
224
|
+
}))
|
|
191
225
|
}
|
|
192
226
|
}
|
|
193
227
|
|
|
@@ -259,6 +293,18 @@ class LLMObsSpanProcessor {
|
|
|
259
293
|
add(obj, carrier)
|
|
260
294
|
}
|
|
261
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Returns `metadata._dd`, normalizing it to a fresh object if missing or invalid.
|
|
298
|
+
* @param {Record<string, unknown>} metadata
|
|
299
|
+
* @returns {Record<string, unknown>}
|
|
300
|
+
*/
|
|
301
|
+
#getDdMetadata (metadata) {
|
|
302
|
+
if (!metadata._dd || typeof metadata._dd !== 'object' || Array.isArray(metadata._dd)) {
|
|
303
|
+
metadata._dd = {}
|
|
304
|
+
}
|
|
305
|
+
return metadata._dd
|
|
306
|
+
}
|
|
307
|
+
|
|
262
308
|
#getTags (span, mlApp, sessionId, error) {
|
|
263
309
|
let tags = {
|
|
264
310
|
...this.#config.parsedDdTags,
|
|
@@ -12,7 +12,9 @@ const {
|
|
|
12
12
|
INPUT_DOCUMENTS,
|
|
13
13
|
OUTPUT_VALUE,
|
|
14
14
|
METADATA,
|
|
15
|
+
COST_TAGS,
|
|
15
16
|
METRICS,
|
|
17
|
+
TOOL_DEFINITIONS,
|
|
16
18
|
PARENT_ID_KEY,
|
|
17
19
|
INPUT_MESSAGES,
|
|
18
20
|
OUTPUT_MESSAGES,
|
|
@@ -41,6 +43,7 @@ const {
|
|
|
41
43
|
INSTRUMENTATION_METHOD_ANNOTATED,
|
|
42
44
|
} = require('./constants/tags')
|
|
43
45
|
const { storage } = require('./storage')
|
|
46
|
+
const { validateCostTags } = require('./util')
|
|
44
47
|
|
|
45
48
|
// global registry of LLMObs spans
|
|
46
49
|
// maps LLMObs spans to their annotations
|
|
@@ -120,6 +123,11 @@ class LLMObsTagger {
|
|
|
120
123
|
const tags = annotationContext?.tags
|
|
121
124
|
if (tags) this.tagSpanTags(span, tags)
|
|
122
125
|
|
|
126
|
+
// apply after tags so only keys present at span start are accepted.
|
|
127
|
+
if (annotationContext?.costTags != null) {
|
|
128
|
+
this.tagCostTags(span, annotationContext.costTags, 'annotation_context')
|
|
129
|
+
}
|
|
130
|
+
|
|
123
131
|
// apply annotation context name
|
|
124
132
|
const annotationContextName = annotationContext?.name
|
|
125
133
|
if (annotationContextName) this._setTag(span, NAME, annotationContextName)
|
|
@@ -159,6 +167,14 @@ class LLMObsTagger {
|
|
|
159
167
|
this.#tagText(span, outputData, OUTPUT_VALUE)
|
|
160
168
|
}
|
|
161
169
|
|
|
170
|
+
tagToolDefinitions (span, toolDefinitions) {
|
|
171
|
+
if (Array.isArray(toolDefinitions) && toolDefinitions.length > 0) {
|
|
172
|
+
this._setTag(span, TOOL_DEFINITIONS, toolDefinitions)
|
|
173
|
+
} else {
|
|
174
|
+
this.#handleFailure('Tool definitions must be a non-empty array.', 'invalid_tool_definitions')
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
162
178
|
tagMetadata (span, metadata) {
|
|
163
179
|
const existingMetadata = registry.get(span)?.[METADATA]
|
|
164
180
|
if (existingMetadata) {
|
|
@@ -225,6 +241,32 @@ class LLMObsTagger {
|
|
|
225
241
|
}
|
|
226
242
|
}
|
|
227
243
|
|
|
244
|
+
/**
|
|
245
|
+
* Validates and tags cost tag keys on an LLMObs span. Cost tag references are validated against
|
|
246
|
+
* the span's already-applied tags, which are read from the registry.
|
|
247
|
+
* @param {import('../opentracing/span')} span
|
|
248
|
+
* @param {unknown} costTags Raw user-provided cost tags; validated here.
|
|
249
|
+
* @param {'annotate' | 'annotation_context'} source
|
|
250
|
+
*/
|
|
251
|
+
tagCostTags (span, costTags, source) {
|
|
252
|
+
const spanTags = registry.get(span)?.[TAGS] || {}
|
|
253
|
+
const validatedCostTags = validateCostTags(span, costTags, source, spanTags)
|
|
254
|
+
if (!validatedCostTags.length) return
|
|
255
|
+
|
|
256
|
+
// Might consider switching to a `Set` if per-span cost tag cardinality grows large enough that
|
|
257
|
+
// this `.includes`/`.push` merge becomes a hot spot
|
|
258
|
+
const currentCostTags = registry.get(span)?.[COST_TAGS]
|
|
259
|
+
if (currentCostTags) {
|
|
260
|
+
for (const costTag of validatedCostTags) {
|
|
261
|
+
if (!currentCostTags.includes(costTag)) {
|
|
262
|
+
currentCostTags.push(costTag)
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
} else {
|
|
266
|
+
this._setTag(span, COST_TAGS, validatedCostTags)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
228
270
|
/**
|
|
229
271
|
* Tags a prompt on an LLMObs span.
|
|
230
272
|
* @param {import('../opentracing/span')} span
|
|
@@ -6,6 +6,7 @@ const telemetryMetrics = require('../telemetry/metrics')
|
|
|
6
6
|
const {
|
|
7
7
|
SPAN_KIND,
|
|
8
8
|
MODEL_PROVIDER,
|
|
9
|
+
ML_APP,
|
|
9
10
|
PARENT_ID_KEY,
|
|
10
11
|
SESSION_ID,
|
|
11
12
|
ROOT_PARENT_ID,
|
|
@@ -123,6 +124,32 @@ function recordLLMObsAnnotate (span, err, value = 1) {
|
|
|
123
124
|
llmobsMetrics.count('annotations', tags).inc(value)
|
|
124
125
|
}
|
|
125
126
|
|
|
127
|
+
function recordCostTagsAnnotated (span, source, value = 1) {
|
|
128
|
+
const mlObsTags = LLMObsTagger.tagMap.get(span) || {}
|
|
129
|
+
const tags = {
|
|
130
|
+
span_kind: mlObsTags[SPAN_KIND] || 'N/A',
|
|
131
|
+
source,
|
|
132
|
+
ml_app: mlObsTags[ML_APP] || 'N/A',
|
|
133
|
+
model_provider: mlObsTags[MODEL_PROVIDER] || 'N/A',
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
llmobsMetrics.count('cost_tags.annotated', tags).inc(value)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function recordCostTagsSubmitted (span, count, source, state, reason = 'none') {
|
|
140
|
+
const mlObsTags = LLMObsTagger.tagMap.get(span) || {}
|
|
141
|
+
const tags = {
|
|
142
|
+
span_kind: mlObsTags[SPAN_KIND] || 'N/A',
|
|
143
|
+
source,
|
|
144
|
+
ml_app: mlObsTags[ML_APP] || 'N/A',
|
|
145
|
+
model_provider: mlObsTags[MODEL_PROVIDER] || 'N/A',
|
|
146
|
+
state,
|
|
147
|
+
reason,
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
llmobsMetrics.count('cost_tags.submitted', tags).inc(count)
|
|
151
|
+
}
|
|
152
|
+
|
|
126
153
|
function recordUserFlush (err, value = 1) {
|
|
127
154
|
const tags = { error: Number(!!err) }
|
|
128
155
|
if (err) tags.error_type = err
|
|
@@ -167,6 +194,8 @@ module.exports = {
|
|
|
167
194
|
recordLLMObsSpanSize,
|
|
168
195
|
recordDroppedPayload,
|
|
169
196
|
recordLLMObsAnnotate,
|
|
197
|
+
recordCostTagsAnnotated,
|
|
198
|
+
recordCostTagsSubmitted,
|
|
170
199
|
recordUserFlush,
|
|
171
200
|
recordExportSpan,
|
|
172
201
|
recordSubmitEvaluation,
|
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const log = require('../log')
|
|
3
4
|
const { SPAN_KINDS } = require('./constants/tags')
|
|
4
5
|
|
|
6
|
+
// LLM I/O is overwhelmingly ASCII (English prompts and code). Walk once
|
|
7
|
+
// looking for the first non-ASCII char; if there is none, hand the input
|
|
8
|
+
// straight back. Otherwise pick up the slow path from the byte that needed
|
|
9
|
+
// escaping. ~5x faster on typical prompt strings than the per-char `+=`
|
|
10
|
+
// loop the function used to do unconditionally.
|
|
5
11
|
function encodeUnicode (str = '') {
|
|
6
|
-
let
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
for (let index = 0; index < str.length; index++) {
|
|
13
|
+
if (str.charCodeAt(index) > 127) {
|
|
14
|
+
let result = str.slice(0, index)
|
|
15
|
+
// eslint-disable-next-line sonarjs/updated-loop-counter -- inner loop continues from outer position
|
|
16
|
+
for (; index < str.length; index++) {
|
|
17
|
+
const code = str.charCodeAt(index)
|
|
18
|
+
result += code > 127 ? String.raw`\u${code.toString(16).padStart(4, '0')}` : str[index]
|
|
19
|
+
}
|
|
20
|
+
return result
|
|
21
|
+
}
|
|
10
22
|
}
|
|
11
|
-
return
|
|
23
|
+
return str
|
|
12
24
|
}
|
|
13
25
|
|
|
14
26
|
function validateKind (kind) {
|
|
@@ -22,6 +34,57 @@ function validateKind (kind) {
|
|
|
22
34
|
return kind
|
|
23
35
|
}
|
|
24
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Validates cost tag keys and records telemetry for the annotation source.
|
|
39
|
+
* @param {import('../opentracing/span')} span
|
|
40
|
+
* @param {unknown} costTags
|
|
41
|
+
* @param {string} source
|
|
42
|
+
* @param {Record<string, unknown>} spanTags
|
|
43
|
+
* @returns {string[]}
|
|
44
|
+
*/
|
|
45
|
+
function validateCostTags (span, costTags, source, spanTags) {
|
|
46
|
+
// Lazy-required to avoid the `index.js -> telemetry -> tagger -> util` module cycle.
|
|
47
|
+
const telemetry = require('./telemetry')
|
|
48
|
+
|
|
49
|
+
telemetry.recordCostTagsAnnotated(span, source)
|
|
50
|
+
|
|
51
|
+
if (!Array.isArray(costTags)) {
|
|
52
|
+
log.warn('costTags must be an array of strings. Ignoring value.')
|
|
53
|
+
telemetry.recordCostTagsSubmitted(span, 1, source, 'error', 'non_list')
|
|
54
|
+
return []
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const validatedCostTags = new Set()
|
|
58
|
+
let nonStringEntries = 0
|
|
59
|
+
let missingSpanTags = 0
|
|
60
|
+
|
|
61
|
+
for (const costTag of costTags) {
|
|
62
|
+
if (typeof costTag !== 'string') {
|
|
63
|
+
log.warn('costTags entries must be strings. Skipping entry %s.', costTag)
|
|
64
|
+
nonStringEntries++
|
|
65
|
+
continue
|
|
66
|
+
}
|
|
67
|
+
if (!Object.hasOwn(spanTags, costTag)) {
|
|
68
|
+
log.warn('costTags entry "%s" must reference a key present in span tags. Skipping entry.', costTag)
|
|
69
|
+
missingSpanTags++
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
validatedCostTags.add(costTag)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (nonStringEntries) {
|
|
76
|
+
telemetry.recordCostTagsSubmitted(span, nonStringEntries, source, 'error', 'non_string_entry')
|
|
77
|
+
}
|
|
78
|
+
if (missingSpanTags) {
|
|
79
|
+
telemetry.recordCostTagsSubmitted(span, missingSpanTags, source, 'error', 'missing_span_tag')
|
|
80
|
+
}
|
|
81
|
+
if (validatedCostTags.size) {
|
|
82
|
+
telemetry.recordCostTagsSubmitted(span, validatedCostTags.size, source, 'success')
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return [...validatedCostTags]
|
|
86
|
+
}
|
|
87
|
+
|
|
25
88
|
// extracts the argument names from a function string
|
|
26
89
|
function parseArgumentNames (str) {
|
|
27
90
|
const result = []
|
|
@@ -174,9 +237,22 @@ function spanHasError (span) {
|
|
|
174
237
|
return !!(tags.error || tags['error.type'])
|
|
175
238
|
}
|
|
176
239
|
|
|
240
|
+
// LLM SDKs stream tool-call argument JSON across SSE chunks; a malformed
|
|
241
|
+
// accumulation would otherwise throw straight into the chunk subscriber.
|
|
242
|
+
function safeJsonParse (value, fallback) {
|
|
243
|
+
if (typeof value !== 'string') return value
|
|
244
|
+
try {
|
|
245
|
+
return JSON.parse(value)
|
|
246
|
+
} catch {
|
|
247
|
+
return fallback === undefined ? value : fallback
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
177
251
|
module.exports = {
|
|
178
252
|
encodeUnicode,
|
|
253
|
+
validateCostTags,
|
|
179
254
|
validateKind,
|
|
180
255
|
getFunctionArguments,
|
|
256
|
+
safeJsonParse,
|
|
181
257
|
spanHasError,
|
|
182
258
|
}
|