dd-trace 6.3.0 → 6.5.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/README.electron.md +7 -0
- package/README.md +17 -0
- package/ci/diagnose.js +2100 -0
- package/ci/init.js +23 -2
- package/ci/runbook.md +198 -0
- package/ci/test-optimization-validation/approval-artifacts.js +143 -0
- package/ci/test-optimization-validation/approval.js +299 -0
- package/ci/test-optimization-validation/artifact-id.js +20 -0
- package/ci/test-optimization-validation/bounded-json.js +121 -0
- package/ci/test-optimization-validation/ci-command-candidate.js +83 -0
- package/ci/test-optimization-validation/ci-discovery.js +181 -0
- package/ci/test-optimization-validation/ci-remediation.js +210 -0
- package/ci/test-optimization-validation/cli.js +903 -0
- package/ci/test-optimization-validation/command-blocker.js +81 -0
- package/ci/test-optimization-validation/command-output-policy.js +206 -0
- package/ci/test-optimization-validation/command-runner.js +707 -0
- package/ci/test-optimization-validation/command-suitability.js +471 -0
- package/ci/test-optimization-validation/executable-approval.js +48 -0
- package/ci/test-optimization-validation/executable.js +480 -0
- package/ci/test-optimization-validation/generated-file-policy.js +63 -0
- package/ci/test-optimization-validation/generated-files.js +351 -0
- package/ci/test-optimization-validation/generated-verifier.js +196 -0
- package/ci/test-optimization-validation/init-probe-preload.js +163 -0
- package/ci/test-optimization-validation/init-probe.js +246 -0
- package/ci/test-optimization-validation/late-initialization.js +63 -0
- package/ci/test-optimization-validation/local-command.js +163 -0
- package/ci/test-optimization-validation/manifest-loader.js +133 -0
- package/ci/test-optimization-validation/manifest-scaffold.js +738 -0
- package/ci/test-optimization-validation/manifest-schema.js +862 -0
- package/ci/test-optimization-validation/offline-fixtures.js +327 -0
- package/ci/test-optimization-validation/offline-output.js +406 -0
- package/ci/test-optimization-validation/payload-normalizer.js +72 -0
- package/ci/test-optimization-validation/plan-writer.js +1120 -0
- package/ci/test-optimization-validation/preflight-runner.js +114 -0
- package/ci/test-optimization-validation/redaction.js +331 -0
- package/ci/test-optimization-validation/report-writer.js +1508 -0
- package/ci/test-optimization-validation/safe-files.js +203 -0
- package/ci/test-optimization-validation/scenarios/auto-test-retries.js +159 -0
- package/ci/test-optimization-validation/scenarios/basic-reporting.js +657 -0
- package/ci/test-optimization-validation/scenarios/ci-wiring.js +780 -0
- package/ci/test-optimization-validation/scenarios/early-flake-detection.js +141 -0
- package/ci/test-optimization-validation/scenarios/helpers.js +539 -0
- package/ci/test-optimization-validation/scenarios/test-management.js +218 -0
- package/ci/test-optimization-validation/setup-runner.js +97 -0
- package/ci/test-optimization-validation/static-diagnosis.js +159 -0
- package/ci/test-optimization-validation/test-output.js +129 -0
- package/ci/test-optimization-validation-manifest.schema.json +1 -0
- package/ci/validate-test-optimization.js +3 -0
- package/ext/exporters.js +1 -0
- package/index.d.ts +121 -8
- package/index.electron.js +3 -0
- package/init.js +18 -0
- package/initialize.mjs +1 -1
- package/loader-hook.mjs +28 -18
- package/openfeature.d.ts +1 -0
- package/openfeature.js +4 -0
- package/package.json +15 -8
- package/packages/datadog-instrumentations/src/ai.js +37 -26
- package/packages/datadog-instrumentations/src/child_process.js +1 -1
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
- package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +9 -3
- package/packages/datadog-instrumentations/src/cucumber.js +24 -10
- package/packages/datadog-instrumentations/src/cypress-config.js +398 -52
- package/packages/datadog-instrumentations/src/express.js +20 -2
- package/packages/datadog-instrumentations/src/fastify.js +7 -11
- package/packages/datadog-instrumentations/src/grpc/server.js +18 -0
- package/packages/datadog-instrumentations/src/helpers/hook.js +27 -6
- package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +8 -8
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +28 -2
- package/packages/datadog-instrumentations/src/helpers/router-helper.js +8 -8
- package/packages/datadog-instrumentations/src/http2/server.js +143 -17
- package/packages/datadog-instrumentations/src/jest.js +36 -4
- package/packages/datadog-instrumentations/src/mariadb.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +0 -2
- package/packages/datadog-instrumentations/src/mocha/utils.js +0 -4
- package/packages/datadog-instrumentations/src/mongodb.js +3 -3
- package/packages/datadog-instrumentations/src/mongoose.js +3 -3
- package/packages/datadog-instrumentations/src/mquery.js +2 -2
- package/packages/datadog-instrumentations/src/mysql.js +1 -1
- package/packages/datadog-instrumentations/src/next.js +24 -0
- package/packages/datadog-instrumentations/src/nyc.js +0 -2
- package/packages/datadog-instrumentations/src/oracledb.js +2 -2
- package/packages/datadog-instrumentations/src/pg.js +2 -2
- package/packages/datadog-instrumentations/src/playwright.js +159 -22
- package/packages/datadog-instrumentations/src/process.js +3 -0
- package/packages/datadog-instrumentations/src/router.js +191 -106
- package/packages/datadog-instrumentations/src/selenium.js +52 -26
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +2 -2
- package/packages/datadog-instrumentations/src/vitest-main.js +0 -2
- package/packages/datadog-instrumentations/src/ws.js +2 -1
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +2 -2
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +2 -1
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +41 -16
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +21 -21
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +14 -8
- package/packages/datadog-plugin-aws-sdk/src/util.js +2 -24
- package/packages/datadog-plugin-cucumber/src/index.js +1 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +24 -62
- package/packages/datadog-plugin-cypress/src/index.js +6 -1
- package/packages/datadog-plugin-cypress/src/support.js +92 -26
- package/packages/datadog-plugin-electron/src/ipc.js +1 -1
- package/packages/datadog-plugin-graphql/src/execute.js +177 -59
- package/packages/datadog-plugin-graphql/src/parse.js +22 -1
- package/packages/datadog-plugin-graphql/src/request.js +32 -1
- package/packages/datadog-plugin-graphql/src/utils.js +42 -0
- package/packages/datadog-plugin-graphql/src/validate.js +15 -3
- package/packages/datadog-plugin-http2/src/client.js +1 -1
- package/packages/datadog-plugin-http2/src/server.js +38 -6
- package/packages/datadog-plugin-jest/src/util.js +21 -6
- package/packages/datadog-plugin-langchain/src/handlers/embedding.js +2 -1
- package/packages/datadog-plugin-langchain/src/handlers/language_models.js +2 -1
- package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
- package/packages/datadog-plugin-playwright/src/index.js +177 -64
- package/packages/datadog-plugin-router/src/index.js +11 -2
- package/packages/datadog-plugin-selenium/src/index.js +5 -36
- package/packages/datadog-plugin-vitest/src/index.js +4 -2
- package/packages/datadog-plugin-ws/src/close.js +2 -1
- package/packages/datadog-plugin-ws/src/producer.js +2 -1
- package/packages/datadog-plugin-ws/src/receiver.js +2 -1
- package/packages/datadog-plugin-ws/src/server.js +2 -1
- package/packages/dd-trace/index.electron.js +3 -0
- package/packages/dd-trace/index.js +2 -37
- package/packages/dd-trace/src/aiguard/sdk.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +2 -2
- package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +3 -1
- package/packages/dd-trace/src/azure_metadata.js +2 -1
- package/packages/dd-trace/src/bootstrap.js +39 -0
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +93 -28
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/index.js +160 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +288 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/payload-projection.js +84 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +369 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +60 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +55 -5
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +22 -6
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +5 -0
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +62 -7
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -2
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +6 -2
- package/packages/dd-trace/src/ci-visibility/rum.js +7 -0
- package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +137 -9
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +173 -9
- package/packages/dd-trace/src/ci-visibility/test-screenshot.js +90 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +20 -0
- package/packages/dd-trace/src/config/helper.js +1 -0
- package/packages/dd-trace/src/config/index.js +10 -0
- package/packages/dd-trace/src/config/supported-configurations.json +83 -0
- package/packages/dd-trace/src/constants.js +7 -0
- package/packages/dd-trace/src/datastreams/pathway.js +6 -4
- package/packages/dd-trace/src/exporter.js +2 -0
- package/packages/dd-trace/src/exporters/common/client-library-headers.js +21 -0
- package/packages/dd-trace/src/exporters/common/request.js +59 -30
- package/packages/dd-trace/src/exporters/common/url.js +15 -1
- package/packages/dd-trace/src/feature-registry.js +29 -0
- package/packages/dd-trace/src/llmobs/constants/tags.js +1 -0
- package/packages/dd-trace/src/llmobs/experiments/client.js +113 -0
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +154 -0
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +283 -0
- package/packages/dd-trace/src/llmobs/experiments/index.js +152 -0
- package/packages/dd-trace/src/llmobs/experiments/noop.js +30 -0
- package/packages/dd-trace/src/llmobs/experiments/result.js +34 -0
- package/packages/dd-trace/src/llmobs/index.js +10 -2
- package/packages/dd-trace/src/llmobs/noop.js +6 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +20 -3
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +2 -2
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +8 -0
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +48 -14
- package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +43 -0
- package/packages/dd-trace/src/llmobs/sdk.js +17 -0
- package/packages/dd-trace/src/llmobs/tagger.js +79 -7
- package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
- package/packages/dd-trace/src/llmobs/util.js +32 -0
- package/packages/dd-trace/src/noop/proxy.js +5 -4
- package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +322 -0
- package/packages/dd-trace/src/openfeature/configuration_source.js +90 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -23
- package/packages/dd-trace/src/openfeature/index.js +0 -2
- package/packages/dd-trace/src/openfeature/noop.js +1 -28
- package/packages/dd-trace/src/openfeature/register.js +35 -0
- package/packages/dd-trace/src/openfeature/remote_config.js +15 -18
- package/packages/dd-trace/src/openfeature/require-provider.js +18 -0
- package/packages/dd-trace/src/opentelemetry/span-ending-hook.js +10 -0
- package/packages/dd-trace/src/opentelemetry/span.js +5 -0
- package/packages/dd-trace/src/opentracing/propagation/log.js +11 -2
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +80 -14
- package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +10 -1
- package/packages/dd-trace/src/opentracing/tracer.js +7 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +20 -6
- package/packages/dd-trace/src/plugins/util/git.js +3 -2
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +29 -5
- package/packages/dd-trace/src/plugins/util/test.js +65 -8
- package/packages/dd-trace/src/plugins/util/web.js +12 -0
- package/packages/dd-trace/src/proxy.js +99 -10
- package/packages/dd-trace/src/span_processor.js +5 -0
- package/packages/dd-trace/src/standalone/index.js +0 -36
- package/packages/dd-trace/src/telemetry/send-data.js +2 -2
- package/packages/dd-trace/src/util.js +26 -0
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/vendor/dist/shell-quote/index.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +0 -17
|
@@ -7,11 +7,13 @@ const {
|
|
|
7
7
|
INSTRUMENTATION_METHOD_AUTO,
|
|
8
8
|
UNKNOWN_MODEL_PROVIDER,
|
|
9
9
|
} = require('../../constants/tags')
|
|
10
|
-
const { safeJsonParse } = require('../../util')
|
|
10
|
+
const { audioMimeTypeFromFormat, formatAudioPart, safeJsonParse } = require('../../util')
|
|
11
|
+
const { AUDIO_MIME_TYPES } = require('./constants')
|
|
11
12
|
const {
|
|
12
13
|
extractChatTemplateFromInstructions,
|
|
13
14
|
normalizePromptVariables,
|
|
14
15
|
extractTextFromContentItem,
|
|
16
|
+
extractContentParts,
|
|
15
17
|
hasMultimodalInputs,
|
|
16
18
|
} = require('./utils')
|
|
17
19
|
|
|
@@ -29,6 +31,22 @@ function isIterable (obj) {
|
|
|
29
31
|
return typeof obj[Symbol.iterator] === 'function'
|
|
30
32
|
}
|
|
31
33
|
|
|
34
|
+
// Flattens multimodal chat input messages (array `content`) into readable text plus structured
|
|
35
|
+
// `audioParts`, leaving plain-string messages untouched. Model-agnostic: keys off message
|
|
36
|
+
// structure, so it works for any audio-capable chat model (gpt-audio*, gpt-4o-audio-preview, ...).
|
|
37
|
+
function flattenChatInputMessages (messages) {
|
|
38
|
+
if (!Array.isArray(messages)) return messages
|
|
39
|
+
|
|
40
|
+
return messages.map(message => {
|
|
41
|
+
if (!Array.isArray(message?.content)) return message
|
|
42
|
+
|
|
43
|
+
const { content, audioParts } = extractContentParts(message.content)
|
|
44
|
+
const flattenedMessage = { ...message, content }
|
|
45
|
+
if (audioParts.length) flattenedMessage.audioParts = audioParts
|
|
46
|
+
return flattenedMessage
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
32
50
|
class OpenAiLLMObsPlugin extends LLMObsPlugin {
|
|
33
51
|
static id = 'openai'
|
|
34
52
|
static integration = 'openai'
|
|
@@ -193,47 +211,63 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
|
|
|
193
211
|
|
|
194
212
|
this._tagger.tagMetadata(span, metadata)
|
|
195
213
|
|
|
214
|
+
const inputMessages = flattenChatInputMessages(messages)
|
|
215
|
+
|
|
196
216
|
if (error) {
|
|
197
|
-
this._tagger.tagLLMIO(span,
|
|
217
|
+
this._tagger.tagLLMIO(span, inputMessages, [{ content: '' }])
|
|
198
218
|
return
|
|
199
219
|
}
|
|
200
220
|
|
|
201
221
|
const outputMessages = []
|
|
202
222
|
const { choices } = response
|
|
203
223
|
if (!isIterable(choices)) {
|
|
204
|
-
this._tagger.tagLLMIO(span,
|
|
224
|
+
this._tagger.tagLLMIO(span, inputMessages, [{ content: '' }])
|
|
205
225
|
return
|
|
206
226
|
}
|
|
207
227
|
|
|
228
|
+
// Output audio (non-streamed) is returned in the requested format; chat-completions
|
|
229
|
+
const outputAudioFormat = inputs.audio?.format
|
|
230
|
+
|
|
208
231
|
for (const choice of choices) {
|
|
209
232
|
const message = choice.message || choice.delta
|
|
210
|
-
|
|
233
|
+
let content = message.content || ''
|
|
211
234
|
const role = message.role
|
|
212
235
|
|
|
236
|
+
const audio = message.audio
|
|
237
|
+
let audioParts
|
|
238
|
+
if (audio) {
|
|
239
|
+
if (audio.data) {
|
|
240
|
+
audioParts = [formatAudioPart(audio.data, audioMimeTypeFromFormat(outputAudioFormat, AUDIO_MIME_TYPES))]
|
|
241
|
+
}
|
|
242
|
+
// gpt-audio* / gpt-4o-audio-preview return null content; surface the transcript as text.
|
|
243
|
+
if (!content) content = audio.transcript || ''
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const outputMessage = { content, role }
|
|
247
|
+
if (audioParts) outputMessage.audioParts = audioParts
|
|
248
|
+
|
|
213
249
|
if (message.function_call) {
|
|
214
|
-
|
|
250
|
+
outputMessage.toolCalls = [{
|
|
215
251
|
name: message.function_call.name,
|
|
216
252
|
arguments: safeJsonParse(message.function_call.arguments),
|
|
217
|
-
}
|
|
218
|
-
outputMessages.push({ content, role, toolCalls: [functionCallInfo] })
|
|
253
|
+
}]
|
|
219
254
|
} else if (message.tool_calls) {
|
|
220
255
|
const toolCallsInfo = []
|
|
221
256
|
for (const toolCall of message.tool_calls) {
|
|
222
|
-
|
|
257
|
+
toolCallsInfo.push({
|
|
223
258
|
arguments: safeJsonParse(toolCall.function.arguments),
|
|
224
259
|
name: toolCall.function.name,
|
|
225
260
|
toolId: toolCall.id,
|
|
226
261
|
type: toolCall.type,
|
|
227
|
-
}
|
|
228
|
-
toolCallsInfo.push(toolCallInfo)
|
|
262
|
+
})
|
|
229
263
|
}
|
|
230
|
-
|
|
231
|
-
} else {
|
|
232
|
-
outputMessages.push({ content, role })
|
|
264
|
+
outputMessage.toolCalls = toolCallsInfo
|
|
233
265
|
}
|
|
266
|
+
|
|
267
|
+
outputMessages.push(outputMessage)
|
|
234
268
|
}
|
|
235
269
|
|
|
236
|
-
this._tagger.tagLLMIO(span,
|
|
270
|
+
this._tagger.tagLLMIO(span, inputMessages, outputMessages)
|
|
237
271
|
}
|
|
238
272
|
|
|
239
273
|
#tagResponse (span, inputs, response, error) {
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { audioMimeTypeFromFormat, formatAudioPart } = require('../../util')
|
|
3
4
|
const {
|
|
4
5
|
INPUT_TYPE_IMAGE,
|
|
5
6
|
INPUT_TYPE_FILE,
|
|
6
7
|
IMAGE_FALLBACK,
|
|
7
8
|
FILE_FALLBACK,
|
|
9
|
+
AUDIO_FALLBACK,
|
|
10
|
+
AUDIO_MIME_TYPES,
|
|
8
11
|
} = require('./constants')
|
|
9
12
|
|
|
10
13
|
const REGEX_SPECIAL_CHARS = /[.*+?^${}()|[\]\\]/g
|
|
@@ -118,9 +121,49 @@ function hasMultimodalInputs (variables) {
|
|
|
118
121
|
)
|
|
119
122
|
}
|
|
120
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Flattens an array of OpenAI chat message content parts into readable text plus structured audio.
|
|
126
|
+
*
|
|
127
|
+
* Text parts are concatenated (newline-joined),
|
|
128
|
+
* images collapse to an `[image]` marker, and `input_audio` parts with data are captured as audio
|
|
129
|
+
* parts (rendered as a player). The `[audio]` marker is only emitted as a fallback when an audio
|
|
130
|
+
* part carries no data.
|
|
131
|
+
*
|
|
132
|
+
* @param {Array<object>} parts - Array of content parts from a chat message `content`
|
|
133
|
+
* @returns {{ content: string, audioParts: Array<{ mimeType: string, content: string }> }}
|
|
134
|
+
*/
|
|
135
|
+
function extractContentParts (parts) {
|
|
136
|
+
const extracted = []
|
|
137
|
+
const audioParts = []
|
|
138
|
+
|
|
139
|
+
for (const part of parts) {
|
|
140
|
+
const partType = part?.type ?? ''
|
|
141
|
+
if (partType === 'text') {
|
|
142
|
+
extracted.push(part.text ?? '')
|
|
143
|
+
} else if (partType === 'image_url') {
|
|
144
|
+
extracted.push(IMAGE_FALLBACK)
|
|
145
|
+
} else if (partType === 'input_audio') {
|
|
146
|
+
const inputAudio = part.input_audio ?? {}
|
|
147
|
+
const data = inputAudio.data
|
|
148
|
+
if (data) {
|
|
149
|
+
// Audio is captured as a structured audio part (rendered as a player), so no text marker
|
|
150
|
+
// is needed. Only fall back to "[audio]" when there's no audio to capture.
|
|
151
|
+
audioParts.push(formatAudioPart(data, audioMimeTypeFromFormat(inputAudio.format, AUDIO_MIME_TYPES)))
|
|
152
|
+
} else {
|
|
153
|
+
extracted.push(AUDIO_FALLBACK)
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
extracted.push(`[${partType}]`)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return { content: extracted.join('\n'), audioParts }
|
|
161
|
+
}
|
|
162
|
+
|
|
121
163
|
module.exports = {
|
|
122
164
|
extractChatTemplateFromInstructions,
|
|
123
165
|
normalizePromptVariables,
|
|
124
166
|
extractTextFromContentItem,
|
|
167
|
+
extractContentParts,
|
|
125
168
|
hasMultimodalInputs,
|
|
126
169
|
}
|
|
@@ -19,6 +19,7 @@ const {
|
|
|
19
19
|
const { storage } = require('./storage')
|
|
20
20
|
const telemetry = require('./telemetry')
|
|
21
21
|
const LLMObsTagger = require('./tagger')
|
|
22
|
+
const { createExperiments } = require('./experiments')
|
|
22
23
|
|
|
23
24
|
// communicating with writer
|
|
24
25
|
const evalMetricAppendCh = channel('llmobs:eval-metric:append')
|
|
@@ -33,6 +34,12 @@ class LLMObs extends NoopLLMObs {
|
|
|
33
34
|
*/
|
|
34
35
|
#hasUserSpanProcessor = false
|
|
35
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Lazily-created experiments facade (see ./experiments).
|
|
39
|
+
* @type {import('./experiments').Experiments | undefined}
|
|
40
|
+
*/
|
|
41
|
+
#experiments
|
|
42
|
+
|
|
36
43
|
/**
|
|
37
44
|
* @param {import('../tracer')} tracer - Tracer instance
|
|
38
45
|
* @param {import('./index')} llmobsModule - LLMObs module instance
|
|
@@ -52,6 +59,16 @@ class LLMObs extends NoopLLMObs {
|
|
|
52
59
|
return this._config.llmobs.DD_LLMOBS_ENABLED ?? false
|
|
53
60
|
}
|
|
54
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Datasets & Experiments API. Requires LLM Observability to be enabled and
|
|
64
|
+
* DD_API_KEY / DD_APP_KEY to be set; otherwise the returned facade throws with
|
|
65
|
+
* a clear message on use.
|
|
66
|
+
*/
|
|
67
|
+
get experiments () {
|
|
68
|
+
this.#experiments ??= createExperiments(this._config)
|
|
69
|
+
return this.#experiments
|
|
70
|
+
}
|
|
71
|
+
|
|
55
72
|
enable (options = {}) {
|
|
56
73
|
logger.warn(
|
|
57
74
|
'Enabling LLM Observability via `llmobs.enable()` is deprecated and will be removed in dd-trace@7.0.0. ' +
|
|
@@ -7,6 +7,7 @@ const {
|
|
|
7
7
|
MODEL_NAME,
|
|
8
8
|
MODEL_PROVIDER,
|
|
9
9
|
SESSION_ID,
|
|
10
|
+
SESSION_ID_TRACE_DEFAULT_KEY,
|
|
10
11
|
ML_APP,
|
|
11
12
|
SPAN_KIND,
|
|
12
13
|
INPUT_VALUE,
|
|
@@ -141,7 +142,10 @@ class LLMObsTagger {
|
|
|
141
142
|
if (modelProvider) this._setTag(span, MODEL_PROVIDER, modelProvider)
|
|
142
143
|
|
|
143
144
|
const traceTags = span.context()._trace.tags
|
|
144
|
-
sessionId = sessionId ||
|
|
145
|
+
sessionId = sessionId ||
|
|
146
|
+
registry.get(parent)?.[SESSION_ID] ||
|
|
147
|
+
traceTags[SESSION_ID_TRACE_DEFAULT_KEY] ||
|
|
148
|
+
traceTags[PROPAGATED_SESSION_ID_KEY]
|
|
145
149
|
if (sessionId) this._setTag(span, SESSION_ID, sessionId)
|
|
146
150
|
if (integration) this._setTag(span, INTEGRATION, integration)
|
|
147
151
|
if (_decorator) this._setTag(span, DECORATOR, _decorator)
|
|
@@ -614,6 +618,65 @@ class LLMObsTagger {
|
|
|
614
618
|
return filteredToolResults
|
|
615
619
|
}
|
|
616
620
|
|
|
621
|
+
// Validates audio segments on a message and emits the snake_case wire shape
|
|
622
|
+
// `{ mime_type, content | attachment_key }`. Mirrors dd-trace-py's
|
|
623
|
+
// `_extract_audio_part`: a part requires `mimeType` and exactly one of
|
|
624
|
+
// `content` (inline base64) or `attachmentKey` (backend-offloaded).
|
|
625
|
+
#filterAudioParts (audioParts) {
|
|
626
|
+
if (!Array.isArray(audioParts)) {
|
|
627
|
+
audioParts = [audioParts]
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const filteredAudioParts = []
|
|
631
|
+
for (const audioPart of audioParts) {
|
|
632
|
+
if (audioPart == null || typeof audioPart !== 'object') {
|
|
633
|
+
this.#handleFailure('Audio part must be an object.', 'invalid_io_messages')
|
|
634
|
+
continue
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const { mimeType, content, attachmentKey } = audioPart
|
|
638
|
+
|
|
639
|
+
if (typeof mimeType !== 'string' || !mimeType) {
|
|
640
|
+
this.#handleFailure('Audio part mimeType must be a non-empty string.', 'invalid_io_messages')
|
|
641
|
+
continue
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (content == null && attachmentKey == null) {
|
|
645
|
+
this.#handleFailure("Audio part must have either 'content' or 'attachmentKey'.", 'invalid_io_messages')
|
|
646
|
+
continue
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
if (content != null && attachmentKey != null) {
|
|
650
|
+
this.#handleFailure(
|
|
651
|
+
"Audio part must have only one of 'content' or 'attachmentKey', not both.", 'invalid_io_messages'
|
|
652
|
+
)
|
|
653
|
+
continue
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const audioPartObj = { mime_type: mimeType }
|
|
657
|
+
|
|
658
|
+
// Exactly one of content / attachmentKey is set here (guarded above). Validate its type
|
|
659
|
+
// explicitly so the failure carries the `invalid_io_messages` tag for telemetry, instead
|
|
660
|
+
// of routing through `#tagConditionalString` which omits it.
|
|
661
|
+
if (content == null) {
|
|
662
|
+
if (typeof attachmentKey !== 'string') {
|
|
663
|
+
this.#handleFailure('Audio part attachmentKey must be a string.', 'invalid_io_messages')
|
|
664
|
+
continue
|
|
665
|
+
}
|
|
666
|
+
audioPartObj.attachment_key = attachmentKey
|
|
667
|
+
} else {
|
|
668
|
+
if (typeof content !== 'string') {
|
|
669
|
+
this.#handleFailure('Audio part content must be a base64-encoded string.', 'invalid_io_messages')
|
|
670
|
+
continue
|
|
671
|
+
}
|
|
672
|
+
audioPartObj.content = content
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
filteredAudioParts.push(audioPartObj)
|
|
676
|
+
}
|
|
677
|
+
return filteredAudioParts
|
|
678
|
+
}
|
|
679
|
+
|
|
617
680
|
#tagMessages (span, data, key) {
|
|
618
681
|
if (!data) {
|
|
619
682
|
return
|
|
@@ -640,6 +703,7 @@ class LLMObsTagger {
|
|
|
640
703
|
toolCalls,
|
|
641
704
|
toolResults,
|
|
642
705
|
toolId,
|
|
706
|
+
audioParts,
|
|
643
707
|
} = message
|
|
644
708
|
const messageObj = {}
|
|
645
709
|
|
|
@@ -673,6 +737,14 @@ class LLMObsTagger {
|
|
|
673
737
|
}
|
|
674
738
|
}
|
|
675
739
|
|
|
740
|
+
if (audioParts != null) {
|
|
741
|
+
const filteredAudioParts = this.#filterAudioParts(audioParts)
|
|
742
|
+
|
|
743
|
+
if (filteredAudioParts.length) {
|
|
744
|
+
messageObj.audio_parts = filteredAudioParts
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
676
748
|
if (toolId) {
|
|
677
749
|
if (role === 'tool') {
|
|
678
750
|
condition = this.#tagConditionalString(toolId, 'Tool ID', messageObj, 'tool_id') && condition
|
|
@@ -756,14 +828,14 @@ class LLMObsTagger {
|
|
|
756
828
|
tagsCarrier[key] = value
|
|
757
829
|
|
|
758
830
|
// The first session set in a trace becomes the trace-level default, stored on the trace-shared
|
|
759
|
-
//
|
|
760
|
-
//
|
|
761
|
-
//
|
|
762
|
-
//
|
|
831
|
+
// tags so later spans (incl. those under a session-less parent) inherit it in-process.
|
|
832
|
+
// Established here, the single choke point for session writes, so sessions post-populated by
|
|
833
|
+
// integrations after span start also seed it. First-writer wins, so an explicit session still
|
|
834
|
+
// overrides locally. Cross-service injection is handled centrally in `handleLLMObsInjection`.
|
|
763
835
|
if (key === SESSION_ID && value) {
|
|
764
836
|
const traceTags = span.context()._trace.tags
|
|
765
|
-
if (traceTags[
|
|
766
|
-
traceTags[
|
|
837
|
+
if (traceTags[SESSION_ID_TRACE_DEFAULT_KEY] === undefined) {
|
|
838
|
+
traceTags[SESSION_ID_TRACE_DEFAULT_KEY] = value
|
|
767
839
|
}
|
|
768
840
|
}
|
|
769
841
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const ERROR_TYPE = require('../constants')
|
|
4
4
|
|
|
5
5
|
const telemetryMetrics = require('../telemetry/metrics')
|
|
6
|
+
const { getSegment } = require('../util')
|
|
6
7
|
const {
|
|
7
8
|
SPAN_KIND,
|
|
8
9
|
MODEL_PROVIDER,
|
|
@@ -22,7 +23,7 @@ function extractIntegrationFromTags (tags) {
|
|
|
22
23
|
if (!Array.isArray(tags)) return null
|
|
23
24
|
const integrationTag = tags.find(tag => tag.startsWith('integration:'))
|
|
24
25
|
if (!integrationTag) return null
|
|
25
|
-
return integrationTag
|
|
26
|
+
return getSegment(integrationTag, ':', 1) || null
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
function extractTagsFromSpanEvent (event) {
|
|
@@ -362,9 +362,41 @@ function findGenAIAncestorSpanId (span) {
|
|
|
362
362
|
return null
|
|
363
363
|
}
|
|
364
364
|
|
|
365
|
+
// Maps an audio `format` (e.g. "wav", "mp3") to a MIME type. Defaults to `audio/wav` when the
|
|
366
|
+
// format is missing. Provider-specific overrides (e.g. OpenAI's mp3 -> audio/mpeg) are passed in
|
|
367
|
+
// via `mimeTypeLookup` so this stays provider-agnostic. A non-string `format` is treated as missing
|
|
368
|
+
// so a malformed auto-instrumented payload can't throw and disable the plugin.
|
|
369
|
+
/**
|
|
370
|
+
* @param {string} fmt
|
|
371
|
+
* @param {Record<string, string>} [mimeTypeLookup]
|
|
372
|
+
* @returns {string}
|
|
373
|
+
*/
|
|
374
|
+
function audioMimeTypeFromFormat (fmt, mimeTypeLookup = {}) {
|
|
375
|
+
fmt = typeof fmt === 'string' ? fmt.trim().toLowerCase() : ''
|
|
376
|
+
if (!fmt) return 'audio/wav'
|
|
377
|
+
return mimeTypeLookup[fmt] ?? `audio/${fmt}`
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Builds an audio part from raw audio bytes (base64-encoded) or an existing base64 string. Only
|
|
381
|
+
// Buffer/Uint8Array inputs are base64-encoded; any other shape is passed through so a malformed
|
|
382
|
+
// auto-instrumented payload can't throw (the tagger soft-skips a non-string `content`).
|
|
383
|
+
/**
|
|
384
|
+
* @param {Buffer | Uint8Array | string} data
|
|
385
|
+
* @param {string} mimeType
|
|
386
|
+
* @returns {{ mimeType: string, content: string }}
|
|
387
|
+
*/
|
|
388
|
+
function formatAudioPart (data, mimeType) {
|
|
389
|
+
const content = Buffer.isBuffer(data) || ArrayBuffer.isView(data)
|
|
390
|
+
? Buffer.from(data).toString('base64')
|
|
391
|
+
: data
|
|
392
|
+
return { mimeType, content }
|
|
393
|
+
}
|
|
394
|
+
|
|
365
395
|
module.exports = {
|
|
396
|
+
audioMimeTypeFromFormat,
|
|
366
397
|
encodeUnicode,
|
|
367
398
|
findGenAIAncestorSpanId,
|
|
399
|
+
formatAudioPart,
|
|
368
400
|
validateCostTags,
|
|
369
401
|
validateKind,
|
|
370
402
|
getFunctionArguments,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { features } = require('../feature-registry')
|
|
3
4
|
const NoopAppsecSdk = require('../appsec/sdk/noop')
|
|
4
5
|
const NoopLLMObsSDK = require('../llmobs/noop')
|
|
5
|
-
const NoopFlaggingProvider = require('../openfeature/noop')
|
|
6
6
|
const NoopAIGuardSDK = require('../aiguard/noop')
|
|
7
7
|
const NoopDogStatsDClient = require('./dogstatsd')
|
|
8
8
|
const NoopTracer = require('./tracer')
|
|
@@ -11,7 +11,6 @@ const noop = new NoopTracer()
|
|
|
11
11
|
const noopAppsec = new NoopAppsecSdk()
|
|
12
12
|
const noopDogStatsDClient = new NoopDogStatsDClient()
|
|
13
13
|
const noopLLMObs = new NoopLLMObsSDK(noop)
|
|
14
|
-
const noopOpenFeatureProvider = new NoopFlaggingProvider()
|
|
15
14
|
const noopAIGuard = new NoopAIGuardSDK()
|
|
16
15
|
const noopProfiling = {
|
|
17
16
|
setCustomLabelKeys () {},
|
|
@@ -25,8 +24,10 @@ class NoopProxy {
|
|
|
25
24
|
this.appsec = noopAppsec
|
|
26
25
|
this.dogstatsd = noopDogStatsDClient
|
|
27
26
|
this.llmobs = noopLLMObs
|
|
28
|
-
this.openfeature = noopOpenFeatureProvider
|
|
29
27
|
this.aiguard = noopAIGuard
|
|
28
|
+
for (const { name, noop } of Object.values(features)) {
|
|
29
|
+
this[name] = noop
|
|
30
|
+
}
|
|
30
31
|
this.setBaggageItem = (key, value) => {}
|
|
31
32
|
this.getBaggageItem = (key) => {}
|
|
32
33
|
this.getAllBaggageItems = () => {}
|
|
@@ -82,7 +83,7 @@ class NoopProxy {
|
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
inject () {
|
|
85
|
-
|
|
86
|
+
this._tracer.inject.apply(this._tracer, arguments)
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
extract () {
|