dd-trace 5.104.0 → 5.106.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 +90 -102
- package/index.d.ts +82 -3
- package/package.json +15 -15
- package/packages/datadog-core/src/storage.js +1 -1
- package/packages/datadog-instrumentations/src/aerospike.js +1 -1
- package/packages/datadog-instrumentations/src/ai.js +8 -7
- package/packages/datadog-instrumentations/src/aws-sdk.js +16 -2
- package/packages/datadog-instrumentations/src/azure-cosmos.js +7 -0
- package/packages/datadog-instrumentations/src/azure-functions.js +3 -0
- package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +19 -0
- package/packages/datadog-instrumentations/src/cucumber.js +390 -157
- package/packages/datadog-instrumentations/src/dns.js +54 -18
- package/packages/datadog-instrumentations/src/fastify.js +142 -82
- package/packages/datadog-instrumentations/src/graphql.js +188 -62
- package/packages/datadog-instrumentations/src/helpers/ai-messages.js +322 -14
- package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
- package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +269 -0
- package/packages/datadog-instrumentations/src/helpers/promise-instrumentor.js +42 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -3
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-cosmos.js +50 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +4 -2
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +85 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +37 -236
- package/packages/datadog-instrumentations/src/hono.js +54 -3
- package/packages/datadog-instrumentations/src/http/server.js +9 -4
- package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +163 -0
- package/packages/datadog-instrumentations/src/jest.js +360 -150
- package/packages/datadog-instrumentations/src/kafkajs.js +120 -16
- package/packages/datadog-instrumentations/src/mocha/main.js +128 -17
- package/packages/datadog-instrumentations/src/nats.js +182 -0
- package/packages/datadog-instrumentations/src/nyc.js +38 -1
- package/packages/datadog-instrumentations/src/openai.js +33 -18
- package/packages/datadog-instrumentations/src/oracledb.js +6 -1
- package/packages/datadog-instrumentations/src/pino.js +17 -5
- package/packages/datadog-instrumentations/src/playwright.js +515 -292
- package/packages/datadog-instrumentations/src/router.js +76 -32
- package/packages/datadog-instrumentations/src/stripe.js +1 -1
- package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/tracing.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +218 -4
- package/packages/datadog-plugin-azure-cosmos/src/index.js +144 -0
- package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -1
- package/packages/datadog-plugin-azure-functions/src/index.js +5 -2
- package/packages/datadog-plugin-azure-service-bus/src/producer.js +1 -1
- package/packages/datadog-plugin-bunyan/src/index.js +28 -0
- package/packages/datadog-plugin-cucumber/src/index.js +17 -3
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +199 -28
- package/packages/datadog-plugin-cypress/src/support.js +69 -1
- package/packages/datadog-plugin-dns/src/lookup.js +8 -6
- package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +1 -1
- package/packages/datadog-plugin-graphql/src/execute.js +2 -0
- package/packages/datadog-plugin-graphql/src/resolve.js +64 -67
- package/packages/datadog-plugin-http/src/server.js +40 -15
- package/packages/datadog-plugin-jest/src/index.js +11 -3
- package/packages/datadog-plugin-jest/src/util.js +15 -8
- package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +1 -1
- package/packages/datadog-plugin-kafkajs/src/producer.js +3 -0
- package/packages/datadog-plugin-langgraph/src/stream.js +1 -1
- package/packages/datadog-plugin-mocha/src/index.js +19 -4
- package/packages/datadog-plugin-mongodb-core/src/index.js +281 -40
- package/packages/datadog-plugin-nats/src/consumer.js +43 -0
- package/packages/datadog-plugin-nats/src/index.js +20 -0
- package/packages/datadog-plugin-nats/src/producer.js +62 -0
- package/packages/datadog-plugin-nats/src/util.js +33 -0
- package/packages/datadog-plugin-next/src/index.js +5 -3
- package/packages/datadog-plugin-openai/src/tracing.js +15 -2
- package/packages/datadog-plugin-oracledb/src/index.js +13 -2
- package/packages/datadog-plugin-pino/src/index.js +42 -0
- package/packages/datadog-plugin-playwright/src/index.js +4 -4
- package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -1
- package/packages/datadog-plugin-rhea/src/producer.js +1 -1
- package/packages/datadog-plugin-router/src/index.js +33 -44
- package/packages/datadog-plugin-selenium/src/index.js +1 -1
- package/packages/datadog-plugin-vitest/src/index.js +5 -13
- package/packages/datadog-plugin-winston/src/index.js +30 -0
- package/packages/datadog-shimmer/src/shimmer.js +33 -40
- package/packages/dd-trace/src/aiguard/index.js +1 -1
- package/packages/dd-trace/src/aiguard/sdk.js +1 -1
- package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
- package/packages/dd-trace/src/appsec/index.js +1 -1
- package/packages/dd-trace/src/appsec/reporter.js +5 -6
- package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
- package/packages/dd-trace/src/appsec/sdk/utils.js +1 -1
- package/packages/dd-trace/src/appsec/user_tracking.js +5 -4
- package/packages/dd-trace/src/baggage.js +7 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +0 -1
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +25 -13
- package/packages/dd-trace/src/ci-visibility/test-optimization-cache.js +70 -6
- package/packages/dd-trace/src/config/generated-config-types.d.ts +6 -2
- package/packages/dd-trace/src/config/supported-configurations.json +27 -8
- package/packages/dd-trace/src/datastreams/writer.js +2 -4
- package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -8
- package/packages/dd-trace/src/encode/0.4.js +124 -108
- package/packages/dd-trace/src/encode/0.5.js +114 -26
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +31 -23
- package/packages/dd-trace/src/encode/agentless-json.js +4 -2
- package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -13
- package/packages/dd-trace/src/encode/span-stats.js +16 -16
- package/packages/dd-trace/src/encode/tags-processors.js +16 -0
- package/packages/dd-trace/src/id.js +15 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +92 -6
- package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +43 -21
- package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +9 -7
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +1 -1
- package/packages/dd-trace/src/llmobs/sdk.js +0 -16
- package/packages/dd-trace/src/llmobs/span_processor.js +3 -3
- package/packages/dd-trace/src/llmobs/tagger.js +9 -1
- package/packages/dd-trace/src/llmobs/telemetry.js +1 -1
- package/packages/dd-trace/src/llmobs/util.js +66 -3
- package/packages/dd-trace/src/log/index.js +1 -1
- package/packages/dd-trace/src/msgpack/chunk.js +394 -10
- package/packages/dd-trace/src/msgpack/index.js +96 -2
- package/packages/dd-trace/src/openfeature/encoding.js +70 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +20 -0
- package/packages/dd-trace/src/openfeature/span-enrichment-hook.js +143 -0
- package/packages/dd-trace/src/openfeature/span-enrichment.js +149 -0
- package/packages/dd-trace/src/opentelemetry/span-helpers.js +4 -3
- package/packages/dd-trace/src/opentelemetry/span.js +1 -1
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +22 -3
- package/packages/dd-trace/src/opentracing/propagation/log.js +18 -7
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +64 -77
- package/packages/dd-trace/src/opentracing/span.js +59 -19
- package/packages/dd-trace/src/opentracing/span_context.js +50 -3
- package/packages/dd-trace/src/plugins/ci_plugin.js +20 -20
- package/packages/dd-trace/src/plugins/database.js +7 -6
- package/packages/dd-trace/src/plugins/index.js +4 -0
- package/packages/dd-trace/src/plugins/log_injection.js +56 -0
- package/packages/dd-trace/src/plugins/log_plugin.js +3 -48
- package/packages/dd-trace/src/plugins/outbound.js +1 -1
- package/packages/dd-trace/src/plugins/plugin.js +15 -17
- package/packages/dd-trace/src/plugins/tracing.js +43 -5
- package/packages/dd-trace/src/plugins/util/test.js +236 -13
- package/packages/dd-trace/src/plugins/util/web.js +79 -65
- package/packages/dd-trace/src/priority_sampler.js +2 -2
- package/packages/dd-trace/src/profiling/config.js +10 -23
- package/packages/dd-trace/src/profiling/exporters/agent.js +11 -10
- package/packages/dd-trace/src/profiling/profiler.js +21 -11
- package/packages/dd-trace/src/profiling/profilers/wall.js +12 -7
- package/packages/dd-trace/src/sampling_rule.js +7 -7
- package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +10 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
- package/packages/dd-trace/src/service-naming/source-resolver.js +46 -0
- package/packages/dd-trace/src/span_format.js +190 -58
- package/packages/dd-trace/src/spanleak.js +1 -1
- package/packages/dd-trace/src/standalone/index.js +3 -3
- package/packages/dd-trace/src/tagger.js +0 -2
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +70 -39
- package/vendor/dist/@datadog/sketches-js/LICENSE +10 -36
- 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/packages/dd-trace/src/msgpack/encoder.js +0 -308
- package/packages/dd-trace/src/plugins/structured_log_plugin.js +0 -9
|
@@ -41,7 +41,7 @@ const VERCEL_AI_GENERATION_METADATA_PREFIX = 'ai.settings.'
|
|
|
41
41
|
*/
|
|
42
42
|
function getSpanTags (ctx) {
|
|
43
43
|
const span = ctx.currentStore?.span
|
|
44
|
-
return /** @type {SpanTags} */ (ctx.attributes ?? span?.context().
|
|
44
|
+
return /** @type {SpanTags} */ (ctx.attributes ?? span?.context().getTags() ?? {})
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/**
|
|
@@ -66,11 +66,22 @@ function getOperation (span) {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
|
-
* Get the LLM token usage from the span tags
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
69
|
+
* Get the LLM token usage from the span tags.
|
|
70
|
+
*
|
|
71
|
+
* Supports both AI SDK v4 (promptTokens/completionTokens) and v5+
|
|
72
|
+
* (inputTokens/outputTokens), and surfaces prompt-cache metrics for providers
|
|
73
|
+
* that report them. The AI SDK convention is that `inputTokens` already
|
|
74
|
+
* includes cached tokens, so cache reads are reported as a subset of input
|
|
75
|
+
* tokens rather than added on top.
|
|
76
|
+
*
|
|
77
|
+
* @param {SpanTags} tags
|
|
78
|
+
* @returns {{
|
|
79
|
+
* inputTokens?: number,
|
|
80
|
+
* outputTokens?: number,
|
|
81
|
+
* totalTokens?: number,
|
|
82
|
+
* cacheReadTokens?: number,
|
|
83
|
+
* cacheWriteTokens?: number
|
|
84
|
+
* }}
|
|
74
85
|
*/
|
|
75
86
|
function getUsage (tags) {
|
|
76
87
|
const usage = {}
|
|
@@ -87,9 +98,84 @@ function getUsage (tags) {
|
|
|
87
98
|
const totalTokens = tags['ai.usage.totalTokens'] ?? (inputTokens + outputTokens)
|
|
88
99
|
if (!Number.isNaN(totalTokens)) usage.totalTokens = totalTokens
|
|
89
100
|
|
|
101
|
+
// Prompt-cache metrics. AI SDK v6 standardizes cache READ tokens via
|
|
102
|
+
// `ai.usage.cachedInputTokens`; cache WRITE tokens (and earlier AI SDK
|
|
103
|
+
// versions / providers that don't fill `cachedInputTokens`) are only
|
|
104
|
+
// available through provider-specific `ai.response.providerMetadata`.
|
|
105
|
+
// Skip zero values: the AI SDK sets `cachedInputTokens=0` on every span
|
|
106
|
+
// regardless of provider, so emitting it would add noise to spans that
|
|
107
|
+
// don't actually use prompt caching (e.g. OpenAI).
|
|
108
|
+
const providerCache = getProviderCacheTokens(tags['ai.response.providerMetadata'])
|
|
109
|
+
|
|
110
|
+
const cacheReadTokens = tags['ai.usage.cachedInputTokens'] ?? providerCache.cacheReadTokens
|
|
111
|
+
if (cacheReadTokens) usage.cacheReadTokens = cacheReadTokens
|
|
112
|
+
|
|
113
|
+
if (providerCache.cacheWriteTokens) usage.cacheWriteTokens = providerCache.cacheWriteTokens
|
|
114
|
+
|
|
115
|
+
// Normalize `inputTokens` to the sum convention used by `bedrockruntime.js`.
|
|
116
|
+
// Some SDK combinations (e.g. `ai@5` + `@ai-sdk/amazon-bedrock@3`) pass the
|
|
117
|
+
// raw fresh count through, which makes `nonCached = input - cacheRead -
|
|
118
|
+
// cacheWrite` go negative downstream.
|
|
119
|
+
//
|
|
120
|
+
// Detection: if `inputTokens < cacheSum`, the value cannot already be a sum
|
|
121
|
+
// that includes them (non-negative arithmetic). This is provider/version
|
|
122
|
+
// agnostic and won't double-count on stacks where the SDK already
|
|
123
|
+
// normalized (`ai@6` + `bedrock@4` / `anthropic@3`, OpenAI, Google).
|
|
124
|
+
if (usage.inputTokens != null) {
|
|
125
|
+
const cacheSum = (usage.cacheReadTokens || 0) + (usage.cacheWriteTokens || 0)
|
|
126
|
+
if (usage.inputTokens < cacheSum) {
|
|
127
|
+
usage.inputTokens += cacheSum
|
|
128
|
+
if (usage.totalTokens != null) {
|
|
129
|
+
usage.totalTokens = usage.inputTokens + (usage.outputTokens || 0)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
90
134
|
return usage
|
|
91
135
|
}
|
|
92
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Extract prompt-cache token counts from the stringified
|
|
139
|
+
* `ai.response.providerMetadata` attribute.
|
|
140
|
+
*
|
|
141
|
+
* The AI SDK does not standardize cache WRITE tokens on the usage object, and
|
|
142
|
+
* earlier versions / providers may also omit `ai.usage.cachedInputTokens`, so
|
|
143
|
+
* we read the provider-specific shape directly. Only Bedrock and Anthropic
|
|
144
|
+
* are handled here as they are the providers that report cache writes today.
|
|
145
|
+
*
|
|
146
|
+
* @see https://ai-sdk.dev/providers/ai-sdk-providers/amazon-bedrock#cache-points
|
|
147
|
+
* @see https://ai-sdk.dev/providers/ai-sdk-providers/anthropic#cache-control
|
|
148
|
+
*
|
|
149
|
+
* @param {string | undefined} providerMetadataJson
|
|
150
|
+
* @returns {{ cacheReadTokens?: number, cacheWriteTokens?: number }}
|
|
151
|
+
*/
|
|
152
|
+
function getProviderCacheTokens (providerMetadataJson) {
|
|
153
|
+
if (!providerMetadataJson) return {}
|
|
154
|
+
|
|
155
|
+
const metadata = getJsonStringValue(providerMetadataJson, null)
|
|
156
|
+
if (!metadata || typeof metadata !== 'object') return {}
|
|
157
|
+
|
|
158
|
+
const result = {}
|
|
159
|
+
|
|
160
|
+
const bedrockUsage = metadata.bedrock?.usage
|
|
161
|
+
if (bedrockUsage) {
|
|
162
|
+
if (bedrockUsage.cacheReadInputTokens != null) result.cacheReadTokens = bedrockUsage.cacheReadInputTokens
|
|
163
|
+
if (bedrockUsage.cacheWriteInputTokens != null) result.cacheWriteTokens = bedrockUsage.cacheWriteInputTokens
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const anthropic = metadata.anthropic
|
|
167
|
+
if (anthropic) {
|
|
168
|
+
if (result.cacheReadTokens == null && anthropic.cacheReadInputTokens != null) {
|
|
169
|
+
result.cacheReadTokens = anthropic.cacheReadInputTokens
|
|
170
|
+
}
|
|
171
|
+
if (result.cacheWriteTokens == null && anthropic.cacheCreationInputTokens != null) {
|
|
172
|
+
result.cacheWriteTokens = anthropic.cacheCreationInputTokens
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return result
|
|
177
|
+
}
|
|
178
|
+
|
|
93
179
|
/**
|
|
94
180
|
* Safely JSON parses a string value with a default fallback
|
|
95
181
|
* @template T typeof defaultValue
|
|
@@ -7,12 +7,22 @@ const {
|
|
|
7
7
|
extractTextAndResponseReason,
|
|
8
8
|
parseModelId,
|
|
9
9
|
extractTextAndResponseReasonFromStream,
|
|
10
|
+
extractConverseToolDefinitions,
|
|
11
|
+
extractRequestParamsConverse,
|
|
12
|
+
extractTextAndResponseReasonConverse,
|
|
13
|
+
extractTextAndResponseReasonConverseFromStream,
|
|
10
14
|
} = require('../../../../datadog-plugin-aws-sdk/src/services/bedrockruntime/utils')
|
|
11
15
|
const BaseLLMObsPlugin = require('./base')
|
|
12
16
|
|
|
13
17
|
const llmobsStore = storage('llmobs')
|
|
14
18
|
|
|
15
|
-
const ENABLED_OPERATIONS = new Set([
|
|
19
|
+
const ENABLED_OPERATIONS = new Set([
|
|
20
|
+
'invokeModel',
|
|
21
|
+
'invokeModelWithResponseStream',
|
|
22
|
+
'converse',
|
|
23
|
+
'converseStream',
|
|
24
|
+
])
|
|
25
|
+
const CONVERSE_OPERATIONS = new Set(['converse', 'converseStream'])
|
|
16
26
|
|
|
17
27
|
/**
|
|
18
28
|
* @typedef {{
|
|
@@ -79,10 +89,18 @@ class BedrockRuntimeLLMObsPlugin extends BaseLLMObsPlugin {
|
|
|
79
89
|
setLLMObsTags ({ ctx, request, span, response, modelProvider, modelName, tokensFromHeaders }) {
|
|
80
90
|
const isStream = request?.operation?.toLowerCase().includes('stream')
|
|
81
91
|
telemetry.incrementLLMObsSpanStartCount({ autoinstrumented: true, integration: 'bedrock' })
|
|
92
|
+
this.#registerSpan(span, request)
|
|
82
93
|
|
|
94
|
+
if (CONVERSE_OPERATIONS.has(request?.operation)) {
|
|
95
|
+
this.#tagConverseSpan({ ctx, request, span, response, tokensFromHeaders, isStream })
|
|
96
|
+
} else {
|
|
97
|
+
this.#tagInvokeModelSpan({ ctx, request, span, response, modelProvider, modelName, tokensFromHeaders, isStream })
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
#registerSpan (span, request) {
|
|
83
102
|
const parent = llmobsStore.getStore()?.span
|
|
84
103
|
// Use full modelId and unified provider for LLMObs (required for backend cost estimation).
|
|
85
|
-
// Split modelProvider/modelName from parseModelId() are still used below for response parsing.
|
|
86
104
|
this._tagger.registerLLMObsSpan(span, {
|
|
87
105
|
parent,
|
|
88
106
|
modelName: request.params.modelId.toLowerCase(),
|
|
@@ -91,38 +109,42 @@ class BedrockRuntimeLLMObsPlugin extends BaseLLMObsPlugin {
|
|
|
91
109
|
name: 'bedrock-runtime.command',
|
|
92
110
|
integration: 'bedrock',
|
|
93
111
|
})
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
#tagConverseSpan ({ ctx, request, span, response, tokensFromHeaders, isStream }) {
|
|
115
|
+
const requestParams = extractRequestParamsConverse(request.params)
|
|
116
|
+
const textAndResponseReason = isStream
|
|
117
|
+
? extractTextAndResponseReasonConverseFromStream(ctx.chunks)
|
|
118
|
+
: extractTextAndResponseReasonConverse(response)
|
|
119
|
+
|
|
120
|
+
const toolDefinitions = extractConverseToolDefinitions(request.params)
|
|
121
|
+
if (toolDefinitions.length > 0) this._tagger.tagToolDefinitions(span, toolDefinitions)
|
|
122
|
+
if (textAndResponseReason.finishReason) {
|
|
123
|
+
this._tagger.tagMetadata(span, { stop_reason: textAndResponseReason.finishReason })
|
|
124
|
+
}
|
|
125
|
+
this.#tagCommon({ span, requestParams, textAndResponseReason, tokensFromHeaders })
|
|
126
|
+
}
|
|
94
127
|
|
|
128
|
+
#tagInvokeModelSpan ({ ctx, request, span, response, modelProvider, modelName, tokensFromHeaders, isStream }) {
|
|
95
129
|
const requestParams = extractRequestParams(request.params, modelProvider)
|
|
96
130
|
// for streamed responses, we'll use the coerced response object we formed in the stream handler
|
|
97
131
|
const textAndResponseReason = isStream
|
|
98
132
|
? extractTextAndResponseReasonFromStream(ctx.chunks, modelProvider, modelName)
|
|
99
133
|
: extractTextAndResponseReason(response, modelProvider, modelName)
|
|
100
134
|
|
|
101
|
-
|
|
135
|
+
this.#tagCommon({ span, requestParams, textAndResponseReason, tokensFromHeaders })
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
#tagCommon ({ span, requestParams, textAndResponseReason, tokensFromHeaders }) {
|
|
102
139
|
this._tagger.tagMetadata(span, {
|
|
103
140
|
temperature: Number.parseFloat(requestParams.temperature) || 0,
|
|
104
141
|
max_tokens: Number.parseInt(requestParams.maxTokens) || 0,
|
|
105
142
|
})
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
this._tagger.tagLLMIO(
|
|
109
|
-
span,
|
|
110
|
-
requestParams.prompt,
|
|
111
|
-
[{ content: textAndResponseReason.message, role: textAndResponseReason.role }]
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
// add token metrics
|
|
115
|
-
const { inputTokens, outputTokens, totalTokens, cacheReadTokens, cacheWriteTokens } = extractTokens({
|
|
143
|
+
this._tagger.tagLLMIO(span, requestParams.prompt, textAndResponseReason.messages)
|
|
144
|
+
this._tagger.tagMetrics(span, extractTokens({
|
|
116
145
|
tokensFromHeaders,
|
|
117
146
|
usage: textAndResponseReason.usage,
|
|
118
|
-
})
|
|
119
|
-
this._tagger.tagMetrics(span, {
|
|
120
|
-
inputTokens,
|
|
121
|
-
outputTokens,
|
|
122
|
-
totalTokens,
|
|
123
|
-
cacheReadTokens,
|
|
124
|
-
cacheWriteTokens,
|
|
125
|
-
})
|
|
147
|
+
}))
|
|
126
148
|
}
|
|
127
149
|
}
|
|
128
150
|
|
|
@@ -44,7 +44,8 @@ class BaseLangChainLLMObsPlugin extends LLMObsPlugin {
|
|
|
44
44
|
|
|
45
45
|
getLLMObsSpanRegisterOptions (ctx) {
|
|
46
46
|
const span = ctx.currentStore?.span
|
|
47
|
-
const
|
|
47
|
+
const spanContext = span?.context()
|
|
48
|
+
const tags = spanContext?.getTags() || {}
|
|
48
49
|
|
|
49
50
|
const modelProvider = tags['langchain.request.provider'] // could be undefined
|
|
50
51
|
const modelName = tags['langchain.request.model'] // could be undefined
|
|
@@ -76,7 +77,7 @@ class BaseLangChainLLMObsPlugin extends LLMObsPlugin {
|
|
|
76
77
|
return
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
const provider = span?.context()
|
|
80
|
+
const provider = span?.context()?.getTag('langchain.request.provider')
|
|
80
81
|
const integrationName = this.getIntegrationName(type, provider)
|
|
81
82
|
this.setMetadata(span, provider)
|
|
82
83
|
|
|
@@ -93,14 +94,15 @@ class BaseLangChainLLMObsPlugin extends LLMObsPlugin {
|
|
|
93
94
|
const metadata = {}
|
|
94
95
|
|
|
95
96
|
// these fields won't be set for non model-based operations
|
|
97
|
+
const spanContext = span?.context()
|
|
96
98
|
const temperature =
|
|
97
|
-
|
|
98
|
-
|
|
99
|
+
spanContext?.getTag(`langchain.request.${provider}.parameters.temperature`) ||
|
|
100
|
+
spanContext?.getTag(`langchain.request.${provider}.parameters.model_kwargs.temperature`)
|
|
99
101
|
|
|
100
102
|
const maxTokens =
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
spanContext?.getTag(`langchain.request.${provider}.parameters.max_tokens`) ||
|
|
104
|
+
spanContext?.getTag(`langchain.request.${provider}.parameters.maxTokens`) ||
|
|
105
|
+
spanContext?.getTag(`langchain.request.${provider}.parameters.model_kwargs.max_tokens`)
|
|
104
106
|
|
|
105
107
|
if (temperature) {
|
|
106
108
|
metadata.temperature = Number.parseFloat(temperature)
|
|
@@ -35,7 +35,7 @@ class PregelStreamLLMObsPlugin extends LLMObsPlugin {
|
|
|
35
35
|
|
|
36
36
|
class NextStreamLLMObsPlugin extends LLMObsPlugin {
|
|
37
37
|
static id = 'llmobs_langgraph_next_stream'
|
|
38
|
-
static prefix = 'tracing:orchestrion:@langchain/langgraph:
|
|
38
|
+
static prefix = 'tracing:orchestrion:@langchain/langgraph:Pregel_stream:next'
|
|
39
39
|
|
|
40
40
|
start () {} // no-op: span was already registered by PregelStreamLLMObsPlugin
|
|
41
41
|
|
|
@@ -63,7 +63,7 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
|
|
|
63
63
|
|
|
64
64
|
const inputs = ctx.args[0] // completion, chat completion, and embeddings take one argument
|
|
65
65
|
const response = ctx.result?.data // no result if error
|
|
66
|
-
const error = !!span.context().
|
|
66
|
+
const error = !!span.context().getTag('error')
|
|
67
67
|
|
|
68
68
|
const operation = getOperation(methodName)
|
|
69
69
|
|
|
@@ -11,8 +11,6 @@ const {
|
|
|
11
11
|
SPAN_KIND,
|
|
12
12
|
OUTPUT_VALUE,
|
|
13
13
|
INPUT_VALUE,
|
|
14
|
-
LLMOBS_TRACE_ID_BRIDGE_KEY,
|
|
15
|
-
LLMOBS_PARENT_ID_BRIDGE_KEY,
|
|
16
14
|
} = require('./constants/tags')
|
|
17
15
|
const {
|
|
18
16
|
getFunctionArguments,
|
|
@@ -553,20 +551,6 @@ class LLMObs extends NoopLLMObs {
|
|
|
553
551
|
...options,
|
|
554
552
|
parent: parentStore?.span,
|
|
555
553
|
})
|
|
556
|
-
|
|
557
|
-
// Bridge tags read by the dd-go LLMObs trace-indexer to correlate OTel
|
|
558
|
-
// gen_ai.* spans with SDK LLMObs spans. Written once per local trace,
|
|
559
|
-
// on the first successful SDK LLMObs span registration. The shared
|
|
560
|
-
// _trace.tags bag is serialized to the first span in every flushed
|
|
561
|
-
// chunk's meta, so partial flush is covered automatically without a
|
|
562
|
-
// separate flush-time processor. Writing only after registerLLMObsSpan
|
|
563
|
-
// succeeds avoids poisoning _trace.tags with bridge tags pointing at a
|
|
564
|
-
// span that will never produce an LLMObs event.
|
|
565
|
-
const traceTags = span?.context?.()._trace?.tags
|
|
566
|
-
if (this.enabled && traceTags && !traceTags[LLMOBS_TRACE_ID_BRIDGE_KEY]) {
|
|
567
|
-
traceTags[LLMOBS_TRACE_ID_BRIDGE_KEY] = span.context().toTraceId(true)
|
|
568
|
-
traceTags[LLMOBS_PARENT_ID_BRIDGE_KEY] = span.context().toSpanId()
|
|
569
|
-
}
|
|
570
554
|
}
|
|
571
555
|
|
|
572
556
|
try {
|
|
@@ -107,7 +107,7 @@ class LLMObsSpanProcessor {
|
|
|
107
107
|
// those cases avoids dd-go reparenting OTel children under a span that
|
|
108
108
|
// has no corresponding LLMObs event.
|
|
109
109
|
if (enqueued) {
|
|
110
|
-
span.context().
|
|
110
|
+
span.context().setTag(LLMOBS_SUBMITTED_TAG_KEY, '1')
|
|
111
111
|
}
|
|
112
112
|
} catch (e) {
|
|
113
113
|
// this should be a rare case
|
|
@@ -123,7 +123,7 @@ class LLMObsSpanProcessor {
|
|
|
123
123
|
format (span) {
|
|
124
124
|
let inputType, outputType
|
|
125
125
|
|
|
126
|
-
const spanTags = span.context().
|
|
126
|
+
const spanTags = span.context().getTags()
|
|
127
127
|
const mlObsTags = LLMObsTagger.tagMap.get(span)
|
|
128
128
|
|
|
129
129
|
const spanKind = mlObsTags[SPAN_KIND]
|
|
@@ -318,7 +318,7 @@ class LLMObsSpanProcessor {
|
|
|
318
318
|
language: 'javascript',
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
-
const errType = span.context().
|
|
321
|
+
const errType = span.context().getTag(ERROR_TYPE) || error?.name
|
|
322
322
|
if (errType) tags.error_type = errType
|
|
323
323
|
|
|
324
324
|
if (sessionId) tags.session_id = sessionId
|
|
@@ -43,7 +43,7 @@ const {
|
|
|
43
43
|
INSTRUMENTATION_METHOD_ANNOTATED,
|
|
44
44
|
} = require('./constants/tags')
|
|
45
45
|
const { storage } = require('./storage')
|
|
46
|
-
const { validateCostTags } = require('./util')
|
|
46
|
+
const { findGenAIAncestorSpanId, validateCostTags, writeBridgeTags } = require('./util')
|
|
47
47
|
|
|
48
48
|
// global registry of LLMObs spans
|
|
49
49
|
// maps LLMObs spans to their annotations
|
|
@@ -97,6 +97,13 @@ class LLMObsTagger {
|
|
|
97
97
|
|
|
98
98
|
this._register(span)
|
|
99
99
|
|
|
100
|
+
// When the registering span sits below an OTel `gen_ai.*` ancestor, use
|
|
101
|
+
// that ancestor as the parent_id fallback and suppress the bridge
|
|
102
|
+
// parent_id tag so the indexer doesn't invert the trace.
|
|
103
|
+
const genAIAncestorSpanId = findGenAIAncestorSpanId(span)
|
|
104
|
+
|
|
105
|
+
writeBridgeTags(span, { includeParentId: genAIAncestorSpanId === null })
|
|
106
|
+
|
|
100
107
|
this._setTag(span, ML_APP, spanMlApp)
|
|
101
108
|
|
|
102
109
|
if (name) this._setTag(span, NAME, name)
|
|
@@ -113,6 +120,7 @@ class LLMObsTagger {
|
|
|
113
120
|
const parentId =
|
|
114
121
|
parent?.context().toSpanId() ??
|
|
115
122
|
span.context()._trace.tags[PROPAGATED_PARENT_ID_KEY] ??
|
|
123
|
+
genAIAncestorSpanId ??
|
|
116
124
|
ROOT_PARENT_ID
|
|
117
125
|
this._setTag(span, PARENT_ID_KEY, parentId)
|
|
118
126
|
|
|
@@ -45,7 +45,7 @@ function incrementLLMObsSpanStartCount (tags, value = 1) {
|
|
|
45
45
|
|
|
46
46
|
function incrementLLMObsSpanFinishedCount (span, value = 1) {
|
|
47
47
|
const mlObsTags = LLMObsTagger.tagMap.get(span)
|
|
48
|
-
const spanTags = span.context().
|
|
48
|
+
const spanTags = span.context().getTags()
|
|
49
49
|
|
|
50
50
|
const isRootSpan = mlObsTags[PARENT_ID_KEY] === ROOT_PARENT_ID
|
|
51
51
|
const hasSessionId = mlObsTags[SESSION_ID] != null
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const log = require('../log')
|
|
4
|
-
const {
|
|
4
|
+
const {
|
|
5
|
+
LLMOBS_PARENT_ID_BRIDGE_KEY,
|
|
6
|
+
LLMOBS_TRACE_ID_BRIDGE_KEY,
|
|
7
|
+
SPAN_KINDS,
|
|
8
|
+
} = require('./constants/tags')
|
|
5
9
|
|
|
6
10
|
// LLM I/O is overwhelmingly ASCII (English prompts and code). Walk once
|
|
7
11
|
// looking for the first non-ASCII char; if there is none, hand the input
|
|
@@ -233,8 +237,8 @@ function getFunctionArguments (fn, args = []) {
|
|
|
233
237
|
}
|
|
234
238
|
|
|
235
239
|
function spanHasError (span) {
|
|
236
|
-
const
|
|
237
|
-
return !!(
|
|
240
|
+
const spanContext = span.context()
|
|
241
|
+
return !!(spanContext.getTag('error') || spanContext.getTag('error.type'))
|
|
238
242
|
}
|
|
239
243
|
|
|
240
244
|
// LLM SDKs stream tool-call argument JSON across SSE chunks; a malformed
|
|
@@ -248,11 +252,70 @@ function safeJsonParse (value, fallback) {
|
|
|
248
252
|
}
|
|
249
253
|
}
|
|
250
254
|
|
|
255
|
+
// Bridge tags read by the trace-indexer to pull OTel `gen_ai.*` spans into
|
|
256
|
+
// the same LLMObs trace. Written once per local trace (first-writer wins on
|
|
257
|
+
// `_trace.tags`). Pass `includeParentId: false` when the span sits below an
|
|
258
|
+
// OTel `gen_ai.*` ancestor — without it the indexer treats this span as the
|
|
259
|
+
// LLMObs root and hoists the gen_ai ancestors under it, inverting the trace.
|
|
260
|
+
/**
|
|
261
|
+
* @param {import('../opentracing/span')} span
|
|
262
|
+
* @param {{ includeParentId?: boolean }} [opts]
|
|
263
|
+
*/
|
|
264
|
+
function writeBridgeTags (span, { includeParentId = true } = {}) {
|
|
265
|
+
const traceTags = span?.context?.()._trace?.tags
|
|
266
|
+
if (!traceTags || traceTags[LLMOBS_TRACE_ID_BRIDGE_KEY]) return
|
|
267
|
+
traceTags[LLMOBS_TRACE_ID_BRIDGE_KEY] = span.context().toTraceId(true)
|
|
268
|
+
if (includeParentId) {
|
|
269
|
+
traceTags[LLMOBS_PARENT_ID_BRIDGE_KEY] = span.context().toSpanId()
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Walks the APM parent chain for the nearest ancestor with any `gen_ai.*`
|
|
274
|
+
// tag. Lets an auto-instrumented LLMObs span nested under a manual OTel
|
|
275
|
+
// workflow point its `parent_id` at the OTel parent so the SDK-emitted
|
|
276
|
+
// event renders under it instead of as a parallel root.
|
|
277
|
+
/**
|
|
278
|
+
* @param {import('../opentracing/span')} span
|
|
279
|
+
* @returns {string | null}
|
|
280
|
+
*/
|
|
281
|
+
function findGenAIAncestorSpanId (span) {
|
|
282
|
+
const ctx = span?.context?.()
|
|
283
|
+
let parentId = ctx?._parentId?.toString(10)
|
|
284
|
+
if (!parentId || parentId === '0') return null
|
|
285
|
+
|
|
286
|
+
const started = ctx._trace?.started
|
|
287
|
+
if (!started || started.length === 0) return null
|
|
288
|
+
|
|
289
|
+
// Linear scan per hop — parent chains are short, avoids a per-call Map.
|
|
290
|
+
while (parentId && parentId !== '0') {
|
|
291
|
+
let parent = null
|
|
292
|
+
for (const s of started) {
|
|
293
|
+
if (s.context()._spanId.toString(10) === parentId) {
|
|
294
|
+
parent = s
|
|
295
|
+
break
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (!parent) return null
|
|
299
|
+
|
|
300
|
+
const tags = parent.context().getTags()
|
|
301
|
+
if (tags) {
|
|
302
|
+
for (const key of Object.keys(tags)) {
|
|
303
|
+
if (key.startsWith('gen_ai.')) return parentId
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
parentId = parent.context()._parentId?.toString(10)
|
|
308
|
+
}
|
|
309
|
+
return null
|
|
310
|
+
}
|
|
311
|
+
|
|
251
312
|
module.exports = {
|
|
252
313
|
encodeUnicode,
|
|
314
|
+
findGenAIAncestorSpanId,
|
|
253
315
|
validateCostTags,
|
|
254
316
|
validateKind,
|
|
255
317
|
getFunctionArguments,
|
|
256
318
|
safeJsonParse,
|
|
257
319
|
spanHasError,
|
|
320
|
+
writeBridgeTags,
|
|
258
321
|
}
|
|
@@ -107,7 +107,7 @@ function publishFormatted (ch, formatter, ...args) {
|
|
|
107
107
|
|
|
108
108
|
function getErrorLog (err) {
|
|
109
109
|
if (typeof err?.delegate === 'function') {
|
|
110
|
-
const result = err.delegate()
|
|
110
|
+
const result = err.delegate(...err.args)
|
|
111
111
|
return Array.isArray(result) ? Log.parse(...result) : Log.parse(result)
|
|
112
112
|
}
|
|
113
113
|
return err
|