dd-trace 6.17.0 → 6.19.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/LICENSE-3rdparty.csv +1 -0
- package/ci/vitest-no-worker-init-setup.mjs +22 -12
- package/index.d.ts +55 -11
- package/package.json +6 -6
- package/packages/datadog-instrumentations/src/anthropic.js +111 -9
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +5 -1
- package/packages/datadog-instrumentations/src/cucumber.js +50 -9
- package/packages/datadog-instrumentations/src/cypress-config.js +27 -15
- package/packages/datadog-instrumentations/src/helpers/bundler-register.js +6 -0
- package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +0 -6
- package/packages/datadog-instrumentations/src/helpers/register.js +20 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +41 -4
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +15 -13
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +51 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +13 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +28 -3
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +6 -0
- package/packages/datadog-instrumentations/src/jest/session-error.js +102 -0
- package/packages/datadog-instrumentations/src/jest.js +30 -13
- package/packages/datadog-instrumentations/src/mocha/main.js +7 -5
- package/packages/datadog-instrumentations/src/mocha/utils.js +8 -8
- package/packages/datadog-instrumentations/src/playwright-reporter.js +3 -4
- package/packages/datadog-instrumentations/src/playwright.js +73 -6
- package/packages/datadog-instrumentations/src/undici.js +7 -3
- package/packages/datadog-instrumentations/src/vitest-main.js +95 -21
- package/packages/datadog-instrumentations/src/vitest-worker.js +34 -3
- package/packages/datadog-instrumentations/src/webdriverio.js +28 -12
- package/packages/datadog-instrumentations/src/ws.js +41 -1
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +83 -29
- package/packages/datadog-plugin-cucumber/src/index.js +22 -8
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +51 -14
- package/packages/datadog-plugin-cypress/src/index.js +3 -1
- package/packages/datadog-plugin-fetch/src/index.js +3 -0
- package/packages/datadog-plugin-jest/src/index.js +3 -4
- package/packages/datadog-plugin-mocha/src/index.js +3 -4
- package/packages/datadog-plugin-oracledb/src/connection-parser.js +3 -1
- package/packages/datadog-plugin-playwright/src/index.js +11 -6
- package/packages/datadog-plugin-undici/src/index.js +5 -0
- package/packages/datadog-plugin-vitest/src/index.js +3 -4
- package/packages/datadog-plugin-ws/src/producer.js +1 -1
- package/packages/datadog-shimmer/src/shimmer.js +14 -10
- package/packages/datadog-turbopack/index.js +4 -1
- package/packages/datadog-turbopack/src/loader.js +6 -2
- package/packages/dd-trace/src/agent/info.js +5 -4
- package/packages/dd-trace/src/aiguard/client.js +38 -19
- package/packages/dd-trace/src/aiguard/integrations/anthropic.js +29 -4
- package/packages/dd-trace/src/aiguard/integrations/index.js +1 -1
- package/packages/dd-trace/src/aiguard/integrations/openai.js +20 -56
- package/packages/dd-trace/src/aiguard/integrations/stream.js +60 -0
- package/packages/dd-trace/src/aiguard/messages/anthropic.js +64 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +19 -19
- package/packages/dd-trace/src/config/index.js +7 -6
- package/packages/dd-trace/src/config/supported-configurations.json +16 -5
- package/packages/dd-trace/src/constants.js +2 -0
- package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -1
- package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +5 -3
- package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
- package/packages/dd-trace/src/debugger/index.js +1 -1
- package/packages/dd-trace/src/evp_proxy/constants.js +2 -0
- package/packages/dd-trace/src/evp_proxy/direct.js +17 -13
- package/packages/dd-trace/src/evp_proxy/discovery.js +14 -10
- package/packages/dd-trace/src/evp_proxy/path.js +21 -7
- package/packages/dd-trace/src/exporters/common/url.js +33 -2
- package/packages/dd-trace/src/llmobs/constants/tags.js +31 -8
- package/packages/dd-trace/src/llmobs/gen-ai-tags.js +127 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +18 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +33 -8
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +77 -41
- package/packages/dd-trace/src/llmobs/plugins/base.js +154 -15
- package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +162 -10
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +65 -16
- package/packages/dd-trace/src/llmobs/plugins/genai/index.js +14 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +34 -35
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +10 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +25 -2
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +8 -7
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +13 -0
- package/packages/dd-trace/src/llmobs/plugins/openai/realtime.js +5 -0
- package/packages/dd-trace/src/llmobs/plugins/vertexai.js +7 -0
- package/packages/dd-trace/src/llmobs/prompts/manager.js +10 -2
- package/packages/dd-trace/src/llmobs/prompts/prompt.js +79 -12
- package/packages/dd-trace/src/llmobs/span_processor.js +10 -65
- package/packages/dd-trace/src/llmobs/tagger.js +11 -40
- package/packages/dd-trace/src/openfeature/configuration_source.js +4 -1
- package/packages/dd-trace/src/openfeature/flagging_provider.js +3 -3
- package/packages/dd-trace/src/openfeature/index.js +6 -2
- package/packages/dd-trace/src/openfeature/writers/base.js +90 -17
- package/packages/dd-trace/src/openfeature/writers/exposures.js +4 -0
- package/packages/dd-trace/src/openfeature/writers/util.js +104 -33
- package/packages/dd-trace/src/opentelemetry/trace/index.js +5 -0
- package/packages/dd-trace/src/opentracing/tracer.js +3 -3
- package/packages/dd-trace/src/plugin_manager.js +27 -8
- package/packages/dd-trace/src/plugins/ci_plugin.js +1 -1
- package/packages/dd-trace/src/plugins/util/test.js +13 -4
- package/packages/dd-trace/src/profiler.js +51 -5
- package/packages/dd-trace/src/profiling/profiler.js +24 -20
- package/packages/dd-trace/src/proxy.js +2 -2
- package/packages/dd-trace/src/ritm.js +12 -9
- package/packages/dd-trace/src/span_processor.js +6 -1
- package/packages/dd-trace/src/telemetry/send-data.js +2 -1
- package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
- package/packages/dd-trace/src/tracer.js +5 -4
- package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
- package/packages/datadog-instrumentations/src/azure-cosmos.js +0 -7
- package/packages/datadog-instrumentations/src/bullmq.js +0 -11
- package/packages/datadog-instrumentations/src/langchain.js +0 -7
- package/packages/datadog-instrumentations/src/langgraph.js +0 -7
- package/packages/datadog-instrumentations/src/mercurius.js +0 -11
|
@@ -77,6 +77,7 @@ function promptFromData (data, source) {
|
|
|
77
77
|
version: String(version),
|
|
78
78
|
source,
|
|
79
79
|
template: template || data.chat_template || [],
|
|
80
|
+
config: data.config,
|
|
80
81
|
promptUuid: data.prompt_uuid,
|
|
81
82
|
promptVersionUuid: data.prompt_version_uuid || data.id || data.ID,
|
|
82
83
|
})
|
|
@@ -488,6 +489,9 @@ class PromptManager {
|
|
|
488
489
|
if (requireAppKey && !this.config.DD_APP_KEY) {
|
|
489
490
|
throw new PromptAPIError(0, 'DD_APP_KEY is required for prompt write operations', ERROR_NAMES.AUTH)
|
|
490
491
|
}
|
|
492
|
+
if (body?.config !== undefined && !isPlainObject(body.config)) {
|
|
493
|
+
throw new PromptAPIError(0, 'config must be a JSON object', ERROR_NAMES.VALIDATION)
|
|
494
|
+
}
|
|
491
495
|
|
|
492
496
|
const headers = {
|
|
493
497
|
'Content-Type': 'application/json',
|
|
@@ -523,7 +527,8 @@ class PromptManager {
|
|
|
523
527
|
* Create a text or chat prompt and its first version.
|
|
524
528
|
* @param {string} promptId
|
|
525
529
|
* @param {string | Array<{role: string, content: string}>} template
|
|
526
|
-
* @param {{title?: string, description?: string, userVersion?: string, envIds?: string[]
|
|
530
|
+
* @param {{title?: string, description?: string, userVersion?: string, envIds?: string[],
|
|
531
|
+
* config?: Record<string, unknown>}} [options]
|
|
527
532
|
* @returns {Promise<object | object[]>}
|
|
528
533
|
*/
|
|
529
534
|
async createPrompt (promptId, template, options = {}) {
|
|
@@ -532,6 +537,7 @@ class PromptManager {
|
|
|
532
537
|
if (options.description) body.description = options.description
|
|
533
538
|
if (options.userVersion) body.user_version = options.userVersion
|
|
534
539
|
if (options.envIds !== undefined) body.env_ids = options.envIds
|
|
540
|
+
if (options.config !== undefined) body.config = options.config
|
|
535
541
|
const response = await this.#request('POST', PROMPTS_PATH, body, true)
|
|
536
542
|
this.#evictPrompt(promptId)
|
|
537
543
|
return response
|
|
@@ -541,7 +547,8 @@ class PromptManager {
|
|
|
541
547
|
* Add a text or chat version to an existing prompt.
|
|
542
548
|
* @param {string} promptId
|
|
543
549
|
* @param {string | Array<{role: string, content: string}>} template
|
|
544
|
-
* @param {{description?: string, userVersion?: string, envIds?: string[]
|
|
550
|
+
* @param {{description?: string, userVersion?: string, envIds?: string[],
|
|
551
|
+
* config?: Record<string, unknown>}} [options]
|
|
545
552
|
* @returns {Promise<object | object[]>}
|
|
546
553
|
*/
|
|
547
554
|
async createPromptVersion (promptId, template, options = {}) {
|
|
@@ -549,6 +556,7 @@ class PromptManager {
|
|
|
549
556
|
if (options.description) body.description = options.description
|
|
550
557
|
if (options.userVersion) body.user_version = options.userVersion
|
|
551
558
|
if (options.envIds !== undefined) body.env_ids = options.envIds
|
|
559
|
+
if (options.config !== undefined) body.config = options.config
|
|
552
560
|
const path = `${PROMPTS_PATH}/${encodeURIComponent(promptId)}/versions`
|
|
553
561
|
const response = await this.#request('POST', path, body, true)
|
|
554
562
|
this.#evictPrompt(promptId)
|
|
@@ -1,6 +1,43 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/** @typedef {import('../../../../../index').llmobs.FormattedPromptMessage} FormattedPromptMessage */
|
|
4
|
+
|
|
5
|
+
// Match double braces first; preserve surrounding braces such as the closing object in {"age": {age}}.
|
|
6
|
+
const VARIABLE_PATTERN = /\{\{\s*(\w+)\s*\}\}|\{\s*(\w+)\s*\}/g
|
|
7
|
+
|
|
8
|
+
function isMessage (value) {
|
|
9
|
+
return typeof value?.role === 'string' &&
|
|
10
|
+
value?.type !== 'placeholder' &&
|
|
11
|
+
(value?.content == null || typeof value.content === 'string') &&
|
|
12
|
+
(typeof value?.content === 'string' ||
|
|
13
|
+
(Array.isArray(value?.tool_calls) && value.tool_calls.length > 0) ||
|
|
14
|
+
(Array.isArray(value?.tool_results) && value.tool_results.length > 0))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isPlaceholder (value) {
|
|
18
|
+
return value?.type === 'placeholder' && typeof value?.name === 'string'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isObject (value) {
|
|
22
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isTool (value) {
|
|
26
|
+
return isObject(value) &&
|
|
27
|
+
(value.id == null || typeof value.id === 'string') &&
|
|
28
|
+
(value.type == null || typeof value.type === 'string') &&
|
|
29
|
+
(value.tool_id == null || typeof value.tool_id === 'string') &&
|
|
30
|
+
(value.name == null || typeof value.name === 'string')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function hasValidTools (message) {
|
|
34
|
+
return (message.tool_call_id == null || typeof message.tool_call_id === 'string') &&
|
|
35
|
+
(message.tool_calls == null || (Array.isArray(message.tool_calls) && message.tool_calls.every(call =>
|
|
36
|
+
isTool(call) && (call.function == null ||
|
|
37
|
+
(isObject(call.function) &&
|
|
38
|
+
typeof call.function.name === 'string' && typeof call.function.arguments === 'string'))))) &&
|
|
39
|
+
(message.tool_results == null || (Array.isArray(message.tool_results) && message.tool_results.every(isTool)))
|
|
40
|
+
}
|
|
4
41
|
|
|
5
42
|
function render (template, variables) {
|
|
6
43
|
return template.replaceAll(VARIABLE_PATTERN, (match, doubleName, singleName) => {
|
|
@@ -9,23 +46,40 @@ function render (template, variables) {
|
|
|
9
46
|
})
|
|
10
47
|
}
|
|
11
48
|
|
|
49
|
+
function freezeConfig (config) {
|
|
50
|
+
if (config === undefined) return Object.freeze({})
|
|
51
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
52
|
+
throw new TypeError('Invalid prompt config: expected a JSON object')
|
|
53
|
+
}
|
|
54
|
+
const copy = structuredClone(config)
|
|
55
|
+
const freeze = value => {
|
|
56
|
+
if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value
|
|
57
|
+
for (const nested of Object.values(value)) freeze(nested)
|
|
58
|
+
return Object.freeze(value)
|
|
59
|
+
}
|
|
60
|
+
return freeze(copy)
|
|
61
|
+
}
|
|
12
62
|
class ManagedPrompt {
|
|
13
63
|
/**
|
|
14
64
|
* @param {object} data
|
|
15
65
|
* @param {string} data.id
|
|
16
66
|
* @param {string} data.version
|
|
17
67
|
* @param {'registry'|'cache'|'fallback'|'ff'|'resolve'} data.source
|
|
18
|
-
* @param {string | Array<{role: string, content: string}>} data.template
|
|
68
|
+
* @param {string | Array<{role: string, content: string} | {type: 'placeholder', name: string}>} data.template
|
|
69
|
+
* @param {Record<string, unknown>} [data.config]
|
|
19
70
|
* @param {string} [data.promptUuid]
|
|
20
71
|
* @param {string} [data.promptVersionUuid]
|
|
21
72
|
*/
|
|
22
|
-
constructor ({ id, version, source, template, promptUuid, promptVersionUuid }) {
|
|
73
|
+
constructor ({ id, version, source, template, config, promptUuid, promptVersionUuid }) {
|
|
23
74
|
this.id = id
|
|
24
75
|
this.version = version
|
|
25
76
|
this.source = source
|
|
26
77
|
this.template = Array.isArray(template)
|
|
27
|
-
? Object.freeze(template.map(
|
|
78
|
+
? Object.freeze(template.map(item => Object.freeze(isPlaceholder(item)
|
|
79
|
+
? { type: 'placeholder', name: item.name }
|
|
80
|
+
: { role: item.role, content: item.content })))
|
|
28
81
|
: template
|
|
82
|
+
this.config = freezeConfig(config)
|
|
29
83
|
this.promptUuid = promptUuid
|
|
30
84
|
this.promptVersionUuid = promptVersionUuid
|
|
31
85
|
Object.freeze(this)
|
|
@@ -34,11 +88,21 @@ class ManagedPrompt {
|
|
|
34
88
|
/**
|
|
35
89
|
* Render the prompt without changing its stored template.
|
|
36
90
|
* @param {Record<string, unknown>} [variables]
|
|
37
|
-
* @returns {string |
|
|
91
|
+
* @returns {string | FormattedPromptMessage[]}
|
|
38
92
|
*/
|
|
39
93
|
format (variables = {}) {
|
|
40
94
|
if (typeof this.template === 'string') return render(this.template, variables)
|
|
41
|
-
return this.template.
|
|
95
|
+
return this.template.flatMap(item => {
|
|
96
|
+
if (!isPlaceholder(item)) return [{ ...item, content: render(item.content, variables) }]
|
|
97
|
+
if (!Object.hasOwn(variables, item.name)) {
|
|
98
|
+
throw new TypeError(`Missing message placeholder variable '${item.name}'`)
|
|
99
|
+
}
|
|
100
|
+
const messages = variables[item.name]
|
|
101
|
+
if (!Array.isArray(messages) || !messages.every(message => isMessage(message) && hasValidTools(message))) {
|
|
102
|
+
throw new TypeError(`Invalid message placeholder variable '${item.name}': expected an array of messages`)
|
|
103
|
+
}
|
|
104
|
+
return structuredClone(messages)
|
|
105
|
+
})
|
|
42
106
|
}
|
|
43
107
|
|
|
44
108
|
/**
|
|
@@ -52,7 +116,10 @@ class ManagedPrompt {
|
|
|
52
116
|
version: this.version,
|
|
53
117
|
template: typeof this.template === 'string' ? this.template : this.template.map(message => ({ ...message })),
|
|
54
118
|
}
|
|
55
|
-
const
|
|
119
|
+
const placeholderNames = new Set(Array.isArray(this.template)
|
|
120
|
+
? this.template.filter(isPlaceholder).map(item => item.name)
|
|
121
|
+
: [])
|
|
122
|
+
const entries = Object.entries(variables ?? {}).filter(([name]) => !placeholderNames.has(name))
|
|
56
123
|
if (entries.length) {
|
|
57
124
|
annotation.variables = Object.fromEntries(entries.map(([name, value]) => [name, String(value)]))
|
|
58
125
|
}
|
|
@@ -64,8 +131,8 @@ class ManagedPrompt {
|
|
|
64
131
|
/**
|
|
65
132
|
* Convert a caller fallback to a managed prompt.
|
|
66
133
|
* @param {string} promptId
|
|
67
|
-
* @param {string | object | Array<{role: string, content: string}> |
|
|
68
|
-
* (() => string | object | Array<{role: string, content: string}>)} fallback
|
|
134
|
+
* @param {string | object | Array<{role: string, content: string} | {type: 'placeholder', name: string}> |
|
|
135
|
+
* (() => string | object | Array<{role: string, content: string} | {type: 'placeholder', name: string}>)} fallback
|
|
69
136
|
* @returns {ManagedPrompt}
|
|
70
137
|
*/
|
|
71
138
|
static fromFallback (promptId, fallback) {
|
|
@@ -73,9 +140,8 @@ class ManagedPrompt {
|
|
|
73
140
|
const promptLike = value && !Array.isArray(value) && typeof value === 'object'
|
|
74
141
|
const template = promptLike ? value.template : value
|
|
75
142
|
const validTemplate = typeof template === 'string' || (
|
|
76
|
-
Array.isArray(template) && template.every(
|
|
77
|
-
|
|
78
|
-
})
|
|
143
|
+
Array.isArray(template) && template.every(item =>
|
|
144
|
+
isPlaceholder(item) || (isMessage(item) && typeof item.content === 'string'))
|
|
79
145
|
)
|
|
80
146
|
if (!validTemplate) {
|
|
81
147
|
throw new TypeError('Invalid prompt fallback: expected a string, chat message array, or object with a template')
|
|
@@ -85,6 +151,7 @@ class ManagedPrompt {
|
|
|
85
151
|
version: String(promptLike && value.version ? value.version : 'fallback'),
|
|
86
152
|
source: 'fallback',
|
|
87
153
|
template,
|
|
154
|
+
config: promptLike ? value.config : undefined,
|
|
88
155
|
})
|
|
89
156
|
}
|
|
90
157
|
}
|
|
@@ -38,41 +38,13 @@ const {
|
|
|
38
38
|
SAMPLE_RATE,
|
|
39
39
|
SAMPLING_DECISION,
|
|
40
40
|
TRACE_ID,
|
|
41
|
-
|
|
42
|
-
OUTPUT_TOKENS_METRIC_KEY,
|
|
43
|
-
TOTAL_TOKENS_METRIC_KEY,
|
|
44
|
-
CACHE_READ_INPUT_TOKENS_METRIC_KEY,
|
|
45
|
-
CACHE_WRITE_INPUT_TOKENS_METRIC_KEY,
|
|
46
|
-
REASONING_OUTPUT_TOKENS_METRIC_KEY,
|
|
47
|
-
GEN_AI_OPERATION_NAME,
|
|
48
|
-
GEN_AI_REQUEST_MODEL,
|
|
49
|
-
GEN_AI_PROVIDER_NAME,
|
|
50
|
-
GEN_AI_APPLICATION_NAME,
|
|
51
|
-
GEN_AI_CONVERSATION_ID,
|
|
52
|
-
GEN_AI_USAGE_INPUT_TOKENS_METRIC_KEY,
|
|
53
|
-
GEN_AI_USAGE_OUTPUT_TOKENS_METRIC_KEY,
|
|
54
|
-
GEN_AI_USAGE_TOTAL_TOKENS_METRIC_KEY,
|
|
55
|
-
GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS_METRIC_KEY,
|
|
56
|
-
GEN_AI_USAGE_CACHE_WRITE_INPUT_TOKENS_METRIC_KEY,
|
|
57
|
-
GEN_AI_USAGE_REASONING_OUTPUT_TOKENS_METRIC_KEY,
|
|
58
|
-
ARTIFICIAL_GEN_AI_TAGS,
|
|
41
|
+
DEFAULT_MODEL,
|
|
59
42
|
} = require('./constants/tags')
|
|
60
43
|
const { UNSERIALIZABLE_VALUE_TEXT } = require('./constants/text')
|
|
44
|
+
const { setGenAiApmTags } = require('./gen-ai-tags')
|
|
61
45
|
const telemetry = require('./telemetry')
|
|
62
46
|
const LLMObsTagger = require('./tagger')
|
|
63
47
|
|
|
64
|
-
const DEFAULT_MODEL = 'custom'
|
|
65
|
-
const MODEL_BACKED_SPAN_KINDS = new Set(['llm', 'embedding'])
|
|
66
|
-
|
|
67
|
-
const GEN_AI_TOKEN_METRIC_KEYS = [
|
|
68
|
-
[INPUT_TOKENS_METRIC_KEY, GEN_AI_USAGE_INPUT_TOKENS_METRIC_KEY],
|
|
69
|
-
[OUTPUT_TOKENS_METRIC_KEY, GEN_AI_USAGE_OUTPUT_TOKENS_METRIC_KEY],
|
|
70
|
-
[TOTAL_TOKENS_METRIC_KEY, GEN_AI_USAGE_TOTAL_TOKENS_METRIC_KEY],
|
|
71
|
-
[CACHE_READ_INPUT_TOKENS_METRIC_KEY, GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS_METRIC_KEY],
|
|
72
|
-
[CACHE_WRITE_INPUT_TOKENS_METRIC_KEY, GEN_AI_USAGE_CACHE_WRITE_INPUT_TOKENS_METRIC_KEY],
|
|
73
|
-
[REASONING_OUTPUT_TOKENS_METRIC_KEY, GEN_AI_USAGE_REASONING_OUTPUT_TOKENS_METRIC_KEY],
|
|
74
|
-
]
|
|
75
|
-
|
|
76
48
|
class LLMObservabilitySpan {
|
|
77
49
|
/**
|
|
78
50
|
* @param {string} kind span kind
|
|
@@ -320,46 +292,19 @@ class LLMObsSpanProcessor {
|
|
|
320
292
|
}
|
|
321
293
|
|
|
322
294
|
/**
|
|
323
|
-
* Writes the scalar `gen_ai.*` attributes onto the APM span, so model, provider, application,
|
|
324
|
-
* conversation and token usage are searchable in APM. Message bodies stay off the APM span.
|
|
325
|
-
*
|
|
326
295
|
* @param {import('../opentracing/span')} span
|
|
327
296
|
*/
|
|
328
297
|
#setGenAiApmTags (span) {
|
|
329
298
|
const mlObsTags = LLMObsTagger.tagMap.get(span)
|
|
330
|
-
const spanContext = span.context()
|
|
331
|
-
const spanKind = mlObsTags[SPAN_KIND]
|
|
332
|
-
|
|
333
|
-
if (spanKind) spanContext.setTag(GEN_AI_OPERATION_NAME, spanKind)
|
|
334
|
-
|
|
335
|
-
const modelName = mlObsTags[MODEL_NAME]
|
|
336
|
-
const modelProvider = mlObsTags[MODEL_PROVIDER]
|
|
337
|
-
const modelBacked = MODEL_BACKED_SPAN_KINDS.has(spanKind)
|
|
338
|
-
if (modelBacked) {
|
|
339
|
-
spanContext.setTag(GEN_AI_REQUEST_MODEL, modelName || DEFAULT_MODEL)
|
|
340
|
-
spanContext.setTag(GEN_AI_PROVIDER_NAME, (modelProvider || DEFAULT_MODEL).toLowerCase())
|
|
341
|
-
} else {
|
|
342
|
-
if (modelName) spanContext.setTag(GEN_AI_REQUEST_MODEL, modelName)
|
|
343
|
-
if (modelProvider) spanContext.setTag(GEN_AI_PROVIDER_NAME, modelProvider.toLowerCase())
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
const mlApp = mlObsTags[ML_APP]
|
|
347
|
-
if (mlApp) spanContext.setTag(GEN_AI_APPLICATION_NAME, mlApp)
|
|
348
|
-
|
|
349
|
-
const sessionId = mlObsTags[SESSION_ID]
|
|
350
|
-
if (sessionId) spanContext.setTag(GEN_AI_CONVERSATION_ID, sessionId)
|
|
351
|
-
|
|
352
|
-
const metrics = mlObsTags[METRICS]
|
|
353
|
-
// Other kinds carry unrelated metrics that would be misleading under a `gen_ai.usage.*` key.
|
|
354
|
-
if (modelBacked && metrics) {
|
|
355
|
-
for (const [metricKey, genAiKey] of GEN_AI_TOKEN_METRIC_KEYS) {
|
|
356
|
-
const value = metrics[metricKey]
|
|
357
|
-
if (value != null) spanContext.setTag(genAiKey, value)
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
299
|
|
|
361
|
-
|
|
362
|
-
|
|
300
|
+
setGenAiApmTags(span, {
|
|
301
|
+
spanKind: mlObsTags[SPAN_KIND],
|
|
302
|
+
modelName: mlObsTags[MODEL_NAME],
|
|
303
|
+
modelProvider: mlObsTags[MODEL_PROVIDER],
|
|
304
|
+
mlApp: mlObsTags[ML_APP],
|
|
305
|
+
sessionId: mlObsTags[SESSION_ID],
|
|
306
|
+
metrics: mlObsTags[METRICS],
|
|
307
|
+
})
|
|
363
308
|
}
|
|
364
309
|
|
|
365
310
|
// For now, this only applies to metadata, as we let users annotate this field with any object
|
|
@@ -29,14 +29,7 @@ const {
|
|
|
29
29
|
PROPAGATED_PARENT_AGENT_ID_KEY,
|
|
30
30
|
PROPAGATED_PARENT_AGENT_NAME_KEY,
|
|
31
31
|
ROOT_PARENT_ID,
|
|
32
|
-
|
|
33
|
-
CACHE_WRITE_INPUT_TOKENS_METRIC_KEY,
|
|
34
|
-
CACHE_WRITE_5M_INPUT_TOKENS_METRIC_KEY,
|
|
35
|
-
CACHE_WRITE_1H_INPUT_TOKENS_METRIC_KEY,
|
|
36
|
-
INPUT_TOKENS_METRIC_KEY,
|
|
37
|
-
OUTPUT_TOKENS_METRIC_KEY,
|
|
38
|
-
TOTAL_TOKENS_METRIC_KEY,
|
|
39
|
-
REASONING_OUTPUT_TOKENS_METRIC_KEY,
|
|
32
|
+
METRIC_KEY_ALIASES,
|
|
40
33
|
INTEGRATION,
|
|
41
34
|
DECORATOR,
|
|
42
35
|
PROPAGATED_ML_APP_KEY,
|
|
@@ -307,35 +300,8 @@ class LLMObsTagger {
|
|
|
307
300
|
tagMetrics (span, metrics) {
|
|
308
301
|
const filterdMetrics = {}
|
|
309
302
|
for (const [key, value] of Object.entries(metrics)) {
|
|
310
|
-
let processedKey = key
|
|
311
|
-
|
|
312
303
|
// processing these specifically for our metrics ingestion
|
|
313
|
-
|
|
314
|
-
case 'inputTokens':
|
|
315
|
-
processedKey = INPUT_TOKENS_METRIC_KEY
|
|
316
|
-
break
|
|
317
|
-
case 'outputTokens':
|
|
318
|
-
processedKey = OUTPUT_TOKENS_METRIC_KEY
|
|
319
|
-
break
|
|
320
|
-
case 'totalTokens':
|
|
321
|
-
processedKey = TOTAL_TOKENS_METRIC_KEY
|
|
322
|
-
break
|
|
323
|
-
case 'cacheReadTokens':
|
|
324
|
-
processedKey = CACHE_READ_INPUT_TOKENS_METRIC_KEY
|
|
325
|
-
break
|
|
326
|
-
case 'cacheWriteTokens':
|
|
327
|
-
processedKey = CACHE_WRITE_INPUT_TOKENS_METRIC_KEY
|
|
328
|
-
break
|
|
329
|
-
case 'cacheWrite5mTokens':
|
|
330
|
-
processedKey = CACHE_WRITE_5M_INPUT_TOKENS_METRIC_KEY
|
|
331
|
-
break
|
|
332
|
-
case 'cacheWrite1hTokens':
|
|
333
|
-
processedKey = CACHE_WRITE_1H_INPUT_TOKENS_METRIC_KEY
|
|
334
|
-
break
|
|
335
|
-
case 'reasoningOutputTokens':
|
|
336
|
-
processedKey = REASONING_OUTPUT_TOKENS_METRIC_KEY
|
|
337
|
-
break
|
|
338
|
-
}
|
|
304
|
+
const processedKey = METRIC_KEY_ALIASES[key] ?? key
|
|
339
305
|
|
|
340
306
|
if (typeof value === 'number') {
|
|
341
307
|
filterdMetrics[processedKey] = value
|
|
@@ -505,10 +471,13 @@ class LLMObsTagger {
|
|
|
505
471
|
}
|
|
506
472
|
|
|
507
473
|
if (Array.isArray(template)) {
|
|
508
|
-
for (const
|
|
509
|
-
|
|
474
|
+
for (const item of template) {
|
|
475
|
+
const valid = item?.type === 'placeholder'
|
|
476
|
+
? typeof item.name === 'string'
|
|
477
|
+
: typeof item?.role === 'string' && typeof item?.content === 'string'
|
|
478
|
+
if (!valid) {
|
|
510
479
|
this.#handleFailure(
|
|
511
|
-
'Prompt chat template must
|
|
480
|
+
'Prompt chat template must contain messages or message placeholders.', 'invalid_prompt'
|
|
512
481
|
)
|
|
513
482
|
return
|
|
514
483
|
}
|
|
@@ -532,7 +501,9 @@ class LLMObsTagger {
|
|
|
532
501
|
if (typeof template === 'string') {
|
|
533
502
|
finalTemplate = template
|
|
534
503
|
} else if (Array.isArray(template)) {
|
|
535
|
-
finalChatTemplate = template.map(
|
|
504
|
+
finalChatTemplate = template.map(item => item.type === 'placeholder'
|
|
505
|
+
? { type: 'placeholder', name: item.name }
|
|
506
|
+
: { role: item.role, content: item.content })
|
|
536
507
|
}
|
|
537
508
|
|
|
538
509
|
const validatedPrompt = {}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { createSiteUrl } = require('../exporters/common/url')
|
|
3
4
|
const log = require('../log')
|
|
4
5
|
|
|
5
6
|
const DEFAULT_AGENTLESS_PATH = '/api/v2/feature-flagging/config/rules-based/server'
|
|
@@ -60,7 +61,9 @@ function endpoint (config, configuredBaseUrl) {
|
|
|
60
61
|
const configured = configuredBaseUrl?.trim()
|
|
61
62
|
|
|
62
63
|
if (!configured) {
|
|
63
|
-
const url =
|
|
64
|
+
const url = createSiteUrl(config.site, 'ufc-server.ff-cdn')
|
|
65
|
+
if (url === undefined) throw new Error('Invalid DD_SITE for Feature Flagging agentless configuration')
|
|
66
|
+
url.pathname = DEFAULT_AGENTLESS_PATH
|
|
64
67
|
if (config.env) url.searchParams.set('dd_env', config.env)
|
|
65
68
|
return url
|
|
66
69
|
}
|
|
@@ -27,12 +27,12 @@ class FlaggingProvider extends DatadogNodeServerProvider {
|
|
|
27
27
|
constructor (tracer, config) {
|
|
28
28
|
super({
|
|
29
29
|
exposureChannel: channel(EXPOSURE_CHANNEL),
|
|
30
|
-
initializationTimeoutMs: config.
|
|
30
|
+
initializationTimeoutMs: config.featureFlags.DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS,
|
|
31
31
|
})
|
|
32
32
|
|
|
33
33
|
this.hooks.push(new EvalMetricsHook(config))
|
|
34
34
|
|
|
35
|
-
if (config.
|
|
35
|
+
if (config.featureFlags.DD_EXPERIMENTAL_FLAGGING_PROVIDER_SPAN_ENRICHMENT_ENABLED) {
|
|
36
36
|
this.#spanEnrichmentHook = new SpanEnrichmentHook(tracer)
|
|
37
37
|
// @ts-expect-error The upstream constructor always initializes its optional hooks property.
|
|
38
38
|
this.hooks.push(this.#spanEnrichmentHook)
|
|
@@ -42,7 +42,7 @@ class FlaggingProvider extends DatadogNodeServerProvider {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
log.debug('%s created with timeout: %dms', this.constructor.name,
|
|
45
|
-
config.
|
|
45
|
+
config.featureFlags.DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS)
|
|
46
46
|
|
|
47
47
|
this.#configurationSource = configurationSource.create(config, this.setConfiguration.bind(this))
|
|
48
48
|
this.#configurationSource?.start()
|
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
const { channel } = require('dc-polyfill')
|
|
4
4
|
const log = require('../log')
|
|
5
5
|
const ExposuresWriter = require('./writers/exposures')
|
|
6
|
-
const {
|
|
6
|
+
const { setEventDeliveryStrategy } = require('./writers/util')
|
|
7
7
|
|
|
8
8
|
const exposureSubmitCh = channel('ffe:exposure:submit')
|
|
9
9
|
const flushCh = channel('ffe:writers:flush')
|
|
10
10
|
|
|
11
11
|
let exposuresWriter = null
|
|
12
|
+
let stopEventDeliveryStrategy
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* @private
|
|
@@ -42,7 +43,7 @@ function enable (config) {
|
|
|
42
43
|
exposureSubmitCh.subscribe(_handleExposureSubmit)
|
|
43
44
|
flushCh.subscribe(_handleFlush)
|
|
44
45
|
|
|
45
|
-
|
|
46
|
+
stopEventDeliveryStrategy = setEventDeliveryStrategy(config, (enabled, route) => {
|
|
46
47
|
if (exposuresWriter !== writer) return
|
|
47
48
|
|
|
48
49
|
writer.setEnabled(enabled, route)
|
|
@@ -55,6 +56,9 @@ function enable (config) {
|
|
|
55
56
|
function disable () {
|
|
56
57
|
if (!exposuresWriter) return
|
|
57
58
|
|
|
59
|
+
stopEventDeliveryStrategy?.()
|
|
60
|
+
stopEventDeliveryStrategy = undefined
|
|
61
|
+
|
|
58
62
|
if (exposureSubmitCh.hasSubscribers) {
|
|
59
63
|
exposureSubmitCh.unsubscribe(_handleExposureSubmit)
|
|
60
64
|
}
|