dd-trace 5.132.0 → 5.134.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/ci/diagnose.js +9 -14
- package/ci/init.js +1 -2
- package/ci/test-optimization-validation/ci-discovery.js +9 -4
- package/ci/test-optimization-validation/executable.js +1 -5
- package/ci/test-optimization-validation/framework-adapters/cypress.js +1 -4
- package/ci/test-optimization-validation/framework-adapters/vitest.js +1 -1
- package/ci/test-optimization-validation/generated-files.js +6 -4
- package/ci/test-optimization-validation/generated-test-contract.js +7 -3
- package/ci/test-optimization-validation/manifest-scaffold.js +7 -2
- package/ci/test-optimization-validation/manifest-schema.js +6 -7
- package/ci/test-optimization-validation/report-writer.js +6 -2
- package/ci/test-optimization-validation/runner-command.js +2 -4
- package/ci/test-optimization-validation/runner-contract.js +1 -2
- package/ci/test-optimization-validation/scenarios/auto-test-retries.js +1 -15
- package/ci/vitest-no-worker-init-runner-setup.mjs +27 -0
- package/ci/vitest-no-worker-init-setup.mjs +10 -7
- package/index.d.ts +41 -19
- package/initialize.mjs +1 -3
- package/openfeature/rules-based.d.ts +11 -0
- package/openfeature/rules-based.js +7 -0
- package/package.json +25 -22
- package/packages/datadog-core/src/storage.js +16 -0
- package/packages/datadog-esbuild/index.js +18 -12
- package/packages/datadog-instrumentations/index.js +2 -1
- package/packages/datadog-instrumentations/src/aerospike.js +3 -3
- package/packages/datadog-instrumentations/src/ai.js +3 -3
- package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +0 -6
- package/packages/datadog-instrumentations/src/aws-sdk.js +21 -13
- package/packages/datadog-instrumentations/src/cassandra-driver.js +25 -20
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +9 -23
- package/packages/datadog-instrumentations/src/cucumber.js +65 -41
- package/packages/datadog-instrumentations/src/elasticsearch.js +1 -5
- package/packages/datadog-instrumentations/src/electron.js +6 -6
- package/packages/datadog-instrumentations/src/fastify.js +13 -4
- package/packages/datadog-instrumentations/src/fs.js +2 -2
- package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +9 -5
- package/packages/datadog-instrumentations/src/grpc/client.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/bundler-register.js +28 -9
- package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +1 -2
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/register.js +45 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +89 -6
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +5 -6
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/jest.js +12 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/postgres.js +48 -31
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/vitest.js +8 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +7 -7
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +5 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms/postgres.js +42 -4
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +7 -10
- package/packages/datadog-instrumentations/src/ioredis.js +7 -5
- package/packages/datadog-instrumentations/src/iovalkey.js +7 -5
- package/packages/datadog-instrumentations/src/jest.js +72 -67
- package/packages/datadog-instrumentations/src/knex.js +9 -5
- package/packages/datadog-instrumentations/src/mariadb-bundle.js +3 -3
- package/packages/datadog-instrumentations/src/mocha/common.js +20 -8
- package/packages/datadog-instrumentations/src/mocha/main.js +140 -43
- package/packages/datadog-instrumentations/src/mocha/utils.js +38 -4
- package/packages/datadog-instrumentations/src/mocha/worker.js +1 -2
- package/packages/datadog-instrumentations/src/mongodb-core.js +3 -3
- package/packages/datadog-instrumentations/src/mongoose.js +3 -3
- package/packages/datadog-instrumentations/src/nova-sonic/audio.js +173 -0
- package/packages/datadog-instrumentations/src/nova-sonic/index.js +209 -0
- package/packages/datadog-instrumentations/src/nova-sonic/session.js +492 -0
- package/packages/datadog-instrumentations/src/openai-realtime/index.js +18 -6
- package/packages/datadog-instrumentations/src/openai-realtime/session.js +1 -1
- package/packages/datadog-instrumentations/src/openai.js +7 -0
- package/packages/datadog-instrumentations/src/pino.js +5 -6
- package/packages/datadog-instrumentations/src/playwright.js +2 -0
- package/packages/datadog-instrumentations/src/redis.js +27 -13
- package/packages/datadog-instrumentations/src/router.js +30 -0
- package/packages/datadog-instrumentations/src/rum-browser-scripts.js +2 -6
- package/packages/datadog-instrumentations/src/stripe.js +23 -12
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +8 -5
- package/packages/datadog-instrumentations/src/vitest-main.js +16 -18
- package/packages/datadog-instrumentations/src/vitest-util.js +7 -4
- package/packages/datadog-instrumentations/src/vitest-worker.js +1 -2
- package/packages/datadog-instrumentations/src/vitest.js +9 -1
- package/packages/datadog-instrumentations/src/webdriverio.js +5 -8
- package/packages/datadog-instrumentations/src/ws.js +1 -4
- package/packages/datadog-plugin-amqplib/src/client.js +1 -2
- package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -5
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -2
- package/packages/datadog-plugin-aws-sdk/src/base.js +5 -8
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/index.js +18 -0
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/sonic.js +45 -0
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +9 -3
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +2 -5
- package/packages/datadog-plugin-azure-durable-functions/src/index.js +5 -4
- package/packages/datadog-plugin-azure-functions/src/index.js +5 -3
- package/packages/datadog-plugin-claude-agent-sdk/src/index.js +0 -2
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +4 -5
- package/packages/datadog-plugin-cypress/src/index.js +6 -6
- package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -2
- package/packages/datadog-plugin-cypress/src/support.js +3 -4
- package/packages/datadog-plugin-express/src/code_origin.js +1 -2
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -2
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +1 -2
- package/packages/datadog-plugin-graphql/src/execute.js +5 -5
- package/packages/datadog-plugin-grpc/src/util.js +1 -2
- package/packages/datadog-plugin-kafkajs/src/producer.js +1 -2
- package/packages/datadog-plugin-mocha/src/index.js +7 -10
- package/packages/datadog-plugin-mocha/src/webdriverio-video.js +8 -4
- package/packages/datadog-plugin-mongodb-core/src/query.js +8 -5
- package/packages/datadog-plugin-next/src/index.js +1 -2
- package/packages/datadog-plugin-openai/src/tracing.js +5 -2
- package/packages/datadog-plugin-openai-agents/src/integration.js +6 -2
- package/packages/datadog-plugin-openai-agents/src/processor.js +2 -4
- package/packages/datadog-plugin-postgres/src/index.js +24 -0
- package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -5
- package/packages/datadog-plugin-rhea/src/consumer.js +1 -4
- package/packages/datadog-plugin-vitest/src/index.js +5 -2
- package/packages/datadog-shimmer/src/shimmer.js +5 -4
- package/packages/datadog-webpack/index.js +55 -43
- package/packages/datadog-webpack/src/loader.js +56 -8
- package/packages/dd-trace/src/aiguard/evaluation.js +143 -1
- package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -4
- package/packages/dd-trace/src/aiguard/messages/utils.js +1 -2
- package/packages/dd-trace/src/aiguard/redaction.js +8 -2
- package/packages/dd-trace/src/appsec/api_security/normalized-route.js +3 -2
- package/packages/dd-trace/src/appsec/api_security/sampler.js +2 -6
- package/packages/dd-trace/src/appsec/downstream_requests.js +1 -9
- package/packages/dd-trace/src/appsec/handlers/http-request.js +3 -9
- package/packages/dd-trace/src/appsec/handlers/http-response.js +7 -2
- package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +4 -3
- package/packages/dd-trace/src/appsec/iast/analyzers/set-cookies-header-interceptor.js +1 -4
- package/packages/dd-trace/src/appsec/iast/path-line.js +2 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +7 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +1 -3
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +1 -3
- package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +1 -6
- package/packages/dd-trace/src/appsec/lambda.js +1 -3
- package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +37 -27
- package/packages/dd-trace/src/appsec/rasp/lfi.js +9 -5
- package/packages/dd-trace/src/appsec/reporter.js +21 -1
- package/packages/dd-trace/src/appsec/stack_trace.js +4 -4
- package/packages/dd-trace/src/ci-visibility/dynamic-atr-retries.js +3 -6
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +3 -2
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +13 -7
- package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +1 -2
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +2 -8
- package/packages/dd-trace/src/ci-visibility/resolve-runfile-path.js +54 -0
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +5 -8
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +2 -53
- package/packages/dd-trace/src/config/config-types.d.ts +2 -2
- package/packages/dd-trace/src/config/defaults.js +5 -5
- package/packages/dd-trace/src/config/generated-config-types.d.ts +10 -0
- package/packages/dd-trace/src/config/helper.js +34 -3
- package/packages/dd-trace/src/config/index.js +55 -9
- package/packages/dd-trace/src/config/normalize-service.js +1 -1
- package/packages/dd-trace/src/config/parsers.js +2 -2
- package/packages/dd-trace/src/config/supported-configurations.json +44 -0
- package/packages/dd-trace/src/datastreams/processor.js +3 -2
- package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +1 -4
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +6 -5
- package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -5
- package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +4 -6
- package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -0
- package/packages/dd-trace/src/debugger/guardrail-metrics.js +2 -2
- package/packages/dd-trace/src/debugger/unsupported-runtime.js +16 -0
- package/packages/dd-trace/src/dogstatsd.js +1 -1
- package/packages/dd-trace/src/encode/tags-processors.js +2 -2
- package/packages/dd-trace/src/exporters/common/request.js +1 -5
- package/packages/dd-trace/src/id.js +14 -1
- package/packages/dd-trace/src/lambda/handler.js +179 -19
- package/packages/dd-trace/src/lambda/index.js +15 -1
- package/packages/dd-trace/src/lambda/runtime/patch.js +34 -7
- package/packages/dd-trace/src/llmobs/agent-manifest.js +19 -2
- package/packages/dd-trace/src/llmobs/audio-utils.js +3 -2
- package/packages/dd-trace/src/llmobs/constants/tags.js +15 -0
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +15 -0
- package/packages/dd-trace/src/llmobs/plugins/genai/util.js +1 -2
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +1 -3
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/nova-sonic.js +92 -0
- package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +5 -0
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +64 -2
- package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +38 -0
- package/packages/dd-trace/src/llmobs/prompts/manager.js +5 -7
- package/packages/dd-trace/src/llmobs/prompts/prompt.js +1 -1
- package/packages/dd-trace/src/llmobs/sdk.js +15 -4
- package/packages/dd-trace/src/llmobs/span_processor.js +13 -2
- package/packages/dd-trace/src/llmobs/tagger.js +20 -10
- package/packages/dd-trace/src/llmobs/util.js +11 -6
- package/packages/dd-trace/src/llmobs/writers/base.js +4 -5
- package/packages/dd-trace/src/log/log.js +1 -5
- package/packages/dd-trace/src/openfeature/evaluation-telemetry-opt-out-hook.js +51 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +48 -21
- package/packages/dd-trace/src/openfeature/index.js +6 -0
- package/packages/dd-trace/src/openfeature/writers/util.js +4 -4
- package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -2
- package/packages/dd-trace/src/opentelemetry/otlp/common.proto +1 -1
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +24 -24
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +53 -13
- package/packages/dd-trace/src/otel-sampling.js +4 -5
- package/packages/dd-trace/src/otel-thread-ctx.js +79 -13
- package/packages/dd-trace/src/plugin_manager.js +1 -2
- package/packages/dd-trace/src/plugins/database.js +7 -16
- package/packages/dd-trace/src/plugins/tracing.js +0 -1
- package/packages/dd-trace/src/plugins/util/git.js +99 -59
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -5
- package/packages/dd-trace/src/plugins/util/stacktrace.js +2 -1
- package/packages/dd-trace/src/plugins/util/test.js +50 -4
- package/packages/dd-trace/src/plugins/util/web.js +90 -20
- package/packages/dd-trace/src/priority_sampler.js +1 -2
- package/packages/dd-trace/src/profiling/profiler.js +4 -6
- package/packages/dd-trace/src/proxy.js +1 -2
- package/packages/dd-trace/src/ritm.js +4 -2
- package/packages/dd-trace/src/serverless.js +5 -0
- package/packages/dd-trace/src/span_processor.js +13 -17
- package/packages/dd-trace/src/span_stats.js +4 -1
- package/packages/dd-trace/src/telemetry/endpoints.js +1 -3
- package/packages/dd-trace/src/telemetry/logs/log-collector.js +1 -1
- package/packages/dd-trace/src/util.js +37 -4
- package/packages/dd-trace/src/web-tags-cache.js +6 -2
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +8 -8
- package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
- package/vendor/dist/@datadog/openfeature-node-server/rules-based/LICENSE +201 -0
- package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.d.ts +925 -0
- package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.js +1 -0
- 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
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { pcm16ToWav } = require('../audio-codec')
|
|
4
|
+
const { fitsInlineAudioBudget, formatAudioPartWithGuard } = require('../audio-utils')
|
|
5
|
+
const { AUDIO_FALLBACK, LLMOBS_AUDIO_INLINE_MAX_BYTES } = require('../constants/audio')
|
|
6
|
+
const { storage } = require('../storage')
|
|
7
|
+
const LLMObsPlugin = require('./base')
|
|
8
|
+
|
|
9
|
+
class SonicLLMObsPlugin extends LLMObsPlugin {
|
|
10
|
+
static id = 'bedrockruntime_sonic_llmobs'
|
|
11
|
+
static integration = 'bedrock'
|
|
12
|
+
static system = 'aws.bedrock'
|
|
13
|
+
static prefix = 'tracing:apm:aws:bedrockruntime:sonic:span'
|
|
14
|
+
static emitsGenAiApmTags = false
|
|
15
|
+
|
|
16
|
+
constructor (...args) {
|
|
17
|
+
super(...args)
|
|
18
|
+
this.addSub('dd-trace:aws:bedrockruntime:sonic:capture-context', context => {
|
|
19
|
+
if (!this._llmobsEnabled) return
|
|
20
|
+
context.enabled = true
|
|
21
|
+
// Capture once at invocation, even when absent or already finished. Scoped replay restores
|
|
22
|
+
// the consumer's context on every exit, including exceptions, without repairing IDs later.
|
|
23
|
+
const store = storage.getStore()
|
|
24
|
+
const previous = context.runInContext ?? (fn => fn())
|
|
25
|
+
context.runInContext = fn => previous(() => storage.run(store, fn))
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** @param {object} ctx */
|
|
30
|
+
getLLMObsSpanRegisterOptions (ctx) {
|
|
31
|
+
const options = {
|
|
32
|
+
kind: ctx.kind,
|
|
33
|
+
name: ctx.name,
|
|
34
|
+
sessionId: ctx.descriptor.sessionId,
|
|
35
|
+
}
|
|
36
|
+
if (ctx.kind === 'llm') {
|
|
37
|
+
options.modelName = ctx.model
|
|
38
|
+
options.modelProvider = 'amazon'
|
|
39
|
+
}
|
|
40
|
+
return options
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** @param {object} ctx */
|
|
44
|
+
start (ctx) {
|
|
45
|
+
super.start(ctx)
|
|
46
|
+
// Replay already has the complete descriptor. Annotate before any end subscriber can finish
|
|
47
|
+
// the span, including when disabling/re-enabling LLMObs changes subscriber registration order.
|
|
48
|
+
if (this._llmobsEnabledFor(ctx)) this.setLLMObsTags(ctx)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** @param {object} ctx */
|
|
52
|
+
setLLMObsTags (ctx) {
|
|
53
|
+
const span = ctx.currentStore?.span
|
|
54
|
+
if (!span) return
|
|
55
|
+
const { turn, history, metadata } = ctx.descriptor
|
|
56
|
+
const text = turn.finalText || turn.speculativeText
|
|
57
|
+
if (ctx.kind !== 'llm') {
|
|
58
|
+
if (ctx.name === 'nova sonic audio turn') this._tagger.tagTextIO(span, turn.userText, text)
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const input = { role: 'user', content: turn.userText || (turn.inputAudioObserved ? AUDIO_FALLBACK : '') }
|
|
63
|
+
const output = { role: 'assistant', content: text || (turn.outputAudioObserved ? AUDIO_FALLBACK : '') }
|
|
64
|
+
if (turn.tools.length) output.toolCalls = turn.tools
|
|
65
|
+
const inputs = [...history, input, ...turn.toolResults]
|
|
66
|
+
const outputs = [output]
|
|
67
|
+
// Reserve the serialized text/tools and metadata before spending one budget across both WAVs.
|
|
68
|
+
let budget = Math.max(0, LLMOBS_AUDIO_INLINE_MAX_BYTES -
|
|
69
|
+
Buffer.byteLength(JSON.stringify([inputs, outputs, metadata])) - 256)
|
|
70
|
+
for (const [message, pcm, rate] of [
|
|
71
|
+
[input, turn.inputPcm, turn.inputRate],
|
|
72
|
+
[output, turn.output.pcm(), turn.output.rate],
|
|
73
|
+
]) {
|
|
74
|
+
if (!pcm?.length || !rate) continue
|
|
75
|
+
const part = fitsInlineAudioBudget(pcm.length + 44, budget)
|
|
76
|
+
? formatAudioPartWithGuard(pcm16ToWav(pcm, rate), 'audio/wav', budget)
|
|
77
|
+
: undefined
|
|
78
|
+
if (part) {
|
|
79
|
+
message.audioParts = [part]
|
|
80
|
+
budget -= part.content.length
|
|
81
|
+
} else if (message === output) {
|
|
82
|
+
metadata.output_audio_omitted_reason = 'payload_limit'
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (turn.output.omittedReason) metadata.output_audio_omitted_reason = turn.output.omittedReason
|
|
86
|
+
this._tagger.tagLLMIO(span, inputs, outputs)
|
|
87
|
+
this._tagger.tagMetadata(span, metadata)
|
|
88
|
+
this._tagger.tagMetrics(span, turn.metrics)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = SonicLLMObsPlugin
|
|
@@ -12,6 +12,9 @@ const AUDIO_MIME_TYPES = {
|
|
|
12
12
|
mp3: 'audio/mpeg',
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
const DECISION_PROVIDER_TAG = 'decision_provider'
|
|
16
|
+
const DECISION_PROVIDER = 'openai'
|
|
17
|
+
|
|
15
18
|
module.exports = {
|
|
16
19
|
INPUT_TYPE_IMAGE,
|
|
17
20
|
INPUT_TYPE_FILE,
|
|
@@ -19,4 +22,6 @@ module.exports = {
|
|
|
19
22
|
IMAGE_FALLBACK,
|
|
20
23
|
FILE_FALLBACK,
|
|
21
24
|
AUDIO_MIME_TYPES,
|
|
25
|
+
DECISION_PROVIDER_TAG,
|
|
26
|
+
DECISION_PROVIDER,
|
|
22
27
|
}
|
|
@@ -8,7 +8,13 @@ const {
|
|
|
8
8
|
} = require('../../constants/tags')
|
|
9
9
|
const { audioMimeTypeFromFormat, formatAudioPart } = require('../../audio-utils')
|
|
10
10
|
const { safeJsonParse } = require('../../util')
|
|
11
|
-
const {
|
|
11
|
+
const {
|
|
12
|
+
AUDIO_MIME_TYPES,
|
|
13
|
+
DECISION_PROVIDER,
|
|
14
|
+
DECISION_PROVIDER_TAG,
|
|
15
|
+
IMAGE_FALLBACK,
|
|
16
|
+
INPUT_TYPE_IMAGE,
|
|
17
|
+
} = require('./constants')
|
|
12
18
|
const {
|
|
13
19
|
extractChatTemplateFromInstructions,
|
|
14
20
|
normalizePromptVariables,
|
|
@@ -16,6 +22,7 @@ const {
|
|
|
16
22
|
extractContentParts,
|
|
17
23
|
hasMultimodalInputs,
|
|
18
24
|
getModelProviderAndClient,
|
|
25
|
+
getServerToolUsageMetrics,
|
|
19
26
|
} = require('./utils')
|
|
20
27
|
|
|
21
28
|
const allowedParamKeys = new Set([
|
|
@@ -107,6 +114,8 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
|
|
|
107
114
|
this._tagEmbedding(span, inputs, response, error)
|
|
108
115
|
} else if (operation === 'response') {
|
|
109
116
|
this.#tagResponse(span, inputs, response, error)
|
|
117
|
+
} else if (operation === 'decision') {
|
|
118
|
+
this.#tagDecision(span, inputs, response, error)
|
|
110
119
|
}
|
|
111
120
|
|
|
112
121
|
if (!error) {
|
|
@@ -148,12 +157,20 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
|
|
|
148
157
|
// Responses API - always include
|
|
149
158
|
const cacheReadTokens = tokenUsage.input_tokens_details.cached_tokens
|
|
150
159
|
if (cacheReadTokens !== undefined) metrics.cacheReadTokens = cacheReadTokens
|
|
160
|
+
const cacheWriteTokens = tokenUsage.input_tokens_details.cache_write_tokens
|
|
161
|
+
if (cacheWriteTokens != null) metrics.cacheWriteTokens = cacheWriteTokens
|
|
162
|
+
// Cache writes are billed at a different rate per TTL
|
|
163
|
+
const cacheWriteDetails = tokenUsage.input_tokens_details.cache_write_tokens_details
|
|
164
|
+
if (cacheWriteDetails?.ttl_30m_tokens != null) metrics.cacheWrite30mTokens = cacheWriteDetails.ttl_30m_tokens
|
|
165
|
+
if (cacheWriteDetails?.ttl_12h_tokens != null) metrics.cacheWrite12hTokens = cacheWriteDetails.ttl_12h_tokens
|
|
151
166
|
} else if (tokenUsage.prompt_tokens_details) {
|
|
152
167
|
// Chat/Completions API - only include if > 0
|
|
153
168
|
const cacheReadTokens = tokenUsage.prompt_tokens_details.cached_tokens
|
|
154
169
|
if (cacheReadTokens != null) {
|
|
155
170
|
metrics.cacheReadTokens = cacheReadTokens
|
|
156
171
|
}
|
|
172
|
+
const cacheWriteTokens = tokenUsage.prompt_tokens_details.cache_write_tokens
|
|
173
|
+
if (cacheWriteTokens != null) metrics.cacheWriteTokens = cacheWriteTokens
|
|
157
174
|
}
|
|
158
175
|
// Reasoning tokens - Responses API returns `output_tokens_details`, `completion_tokens_details`
|
|
159
176
|
const reasoningOutputObject = tokenUsage.output_tokens_details ?? tokenUsage.completion_tokens_details
|
|
@@ -161,6 +178,8 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
|
|
|
161
178
|
if (reasoningOutputTokens !== undefined) metrics.reasoningOutputTokens = reasoningOutputTokens
|
|
162
179
|
}
|
|
163
180
|
|
|
181
|
+
Object.assign(metrics, getServerToolUsageMetrics(response))
|
|
182
|
+
|
|
164
183
|
return metrics
|
|
165
184
|
}
|
|
166
185
|
|
|
@@ -464,6 +483,32 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
|
|
|
464
483
|
|
|
465
484
|
this._tagger.tagMetadata(span, outputMetadata) // update the metadata with the output metadata
|
|
466
485
|
}
|
|
486
|
+
|
|
487
|
+
#tagDecision (span, inputs, response, error) {
|
|
488
|
+
this._tagger.tagSpanTags(span, { [DECISION_PROVIDER_TAG]: DECISION_PROVIDER })
|
|
489
|
+
|
|
490
|
+
const { input, questions } = inputs
|
|
491
|
+
|
|
492
|
+
const inputMessages = typeof input === 'string'
|
|
493
|
+
? [{ role: 'user', content: input }]
|
|
494
|
+
: (Array.isArray(input) ? input : []).map(message => ({
|
|
495
|
+
role: message.role ?? 'user',
|
|
496
|
+
content: decisionMessageContent(message.content),
|
|
497
|
+
}))
|
|
498
|
+
|
|
499
|
+
if (Array.isArray(questions)) {
|
|
500
|
+
this._tagger.tagMetadata(span, { questions })
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (error) {
|
|
504
|
+
this._tagger.tagLLMIO(span, inputMessages, [{ content: '' }])
|
|
505
|
+
return
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
this._tagger.tagLLMIO(span, inputMessages, [
|
|
509
|
+
{ role: 'assistant', content: JSON.stringify(response.answers ?? []) },
|
|
510
|
+
])
|
|
511
|
+
}
|
|
467
512
|
}
|
|
468
513
|
|
|
469
514
|
// TODO: this will be moved to the APM integration
|
|
@@ -485,13 +530,28 @@ function normalizeOpenAIResourceName (resource) {
|
|
|
485
530
|
case 'responses.create':
|
|
486
531
|
return 'createResponse'
|
|
487
532
|
|
|
533
|
+
// decisions
|
|
534
|
+
case 'decisions.create':
|
|
535
|
+
return 'createDecision'
|
|
536
|
+
|
|
488
537
|
default:
|
|
489
538
|
return resource
|
|
490
539
|
}
|
|
491
540
|
}
|
|
492
541
|
|
|
542
|
+
// Decision images must be inline data URLs, so they are replaced with a marker instead of copied onto the span
|
|
543
|
+
function decisionMessageContent (content) {
|
|
544
|
+
if (!Array.isArray(content)) return content ?? ''
|
|
545
|
+
|
|
546
|
+
return content
|
|
547
|
+
.map(part => (part?.type === INPUT_TYPE_IMAGE ? IMAGE_FALLBACK : part?.text ?? ''))
|
|
548
|
+
.filter(Boolean)
|
|
549
|
+
.join('\n')
|
|
550
|
+
}
|
|
551
|
+
|
|
493
552
|
function gateResource (resource) {
|
|
494
|
-
return ['createCompletion', 'createChatCompletion', 'createEmbedding', 'createResponse']
|
|
553
|
+
return ['createCompletion', 'createChatCompletion', 'createEmbedding', 'createResponse', 'createDecision']
|
|
554
|
+
.includes(resource)
|
|
495
555
|
? resource
|
|
496
556
|
: undefined
|
|
497
557
|
}
|
|
@@ -506,6 +566,8 @@ function getOperation (resource) {
|
|
|
506
566
|
return 'embedding'
|
|
507
567
|
case 'createResponse':
|
|
508
568
|
return 'response'
|
|
569
|
+
case 'createDecision':
|
|
570
|
+
return 'decision'
|
|
509
571
|
default:
|
|
510
572
|
// should never happen
|
|
511
573
|
return 'unknown'
|
|
@@ -198,6 +198,43 @@ function getModelProviderAndClient (baseUrl = '') {
|
|
|
198
198
|
return { modelProvider, client: 'OpenAI' }
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Count the billable server-side tool calls in an OpenAI Responses response.
|
|
203
|
+
*
|
|
204
|
+
* Web searches are read from the undocumented `tool_usage.web_search.num_requests` when present, otherwise
|
|
205
|
+
* counted from completed `web_search_call` items whose action is `search` (`open_page` / `find_in_page` steps are
|
|
206
|
+
* not billed). File searches have no usage counter, so each completed `file_search_call` item counts once.
|
|
207
|
+
* Zero counts are omitted.
|
|
208
|
+
*
|
|
209
|
+
* @param {Record<string, unknown>} [response] - Any OpenAI response; only Responses API fields are read
|
|
210
|
+
* @returns {{ webSearchCount?: number, storageSearchCount?: number }}
|
|
211
|
+
*/
|
|
212
|
+
function getServerToolUsageMetrics (response) {
|
|
213
|
+
const metrics = {}
|
|
214
|
+
const reportedWebSearches = response?.tool_usage?.web_search?.num_requests
|
|
215
|
+
const hasReportedWebSearches = typeof reportedWebSearches === 'number'
|
|
216
|
+
if (hasReportedWebSearches && reportedWebSearches > 0) metrics.webSearchCount = reportedWebSearches
|
|
217
|
+
|
|
218
|
+
const output = response?.output
|
|
219
|
+
if (!Array.isArray(output)) return metrics
|
|
220
|
+
|
|
221
|
+
for (const item of output) {
|
|
222
|
+
if (item?.status !== 'completed') continue
|
|
223
|
+
|
|
224
|
+
if (item.type === 'web_search_call') {
|
|
225
|
+
if (hasReportedWebSearches) continue
|
|
226
|
+
// Items without an action (older payloads) are assumed to be searches.
|
|
227
|
+
const actionType = item.action?.type
|
|
228
|
+
if (actionType !== undefined && actionType !== 'search') continue
|
|
229
|
+
metrics.webSearchCount = (metrics.webSearchCount ?? 0) + 1
|
|
230
|
+
} else if (item.type === 'file_search_call') {
|
|
231
|
+
metrics.storageSearchCount = (metrics.storageSearchCount ?? 0) + 1
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return metrics
|
|
236
|
+
}
|
|
237
|
+
|
|
201
238
|
module.exports = {
|
|
202
239
|
extractChatTemplateFromInstructions,
|
|
203
240
|
normalizePromptVariables,
|
|
@@ -206,4 +243,5 @@ module.exports = {
|
|
|
206
243
|
hasMultimodalInputs,
|
|
207
244
|
getModelProviderAndClient,
|
|
208
245
|
getOpenAIModelProvider,
|
|
246
|
+
getServerToolUsageMetrics,
|
|
209
247
|
}
|
|
@@ -84,13 +84,11 @@ function promptFromData (data, source) {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
|
-
* Copy a prompt with a different source.
|
|
88
87
|
* @param {ManagedPrompt} prompt
|
|
89
|
-
* @param {'registry'|'cache'|'fallback'|'ff'|'resolve'} source
|
|
90
88
|
*/
|
|
91
|
-
function
|
|
92
|
-
if (
|
|
93
|
-
return new ManagedPrompt({ ...prompt, source })
|
|
89
|
+
function withCacheSource (prompt) {
|
|
90
|
+
if (SOURCE_CACHE === prompt.source) return prompt
|
|
91
|
+
return new ManagedPrompt({ ...prompt, source: /** @type {ManagedPrompt['source']} */ (SOURCE_CACHE) })
|
|
94
92
|
}
|
|
95
93
|
|
|
96
94
|
function detailFromBody (body) {
|
|
@@ -310,7 +308,7 @@ class PromptManager {
|
|
|
310
308
|
const cacheable = generationAtStart === this.cacheGeneration && latest
|
|
311
309
|
if (result.prompt) {
|
|
312
310
|
if (cacheable) {
|
|
313
|
-
const cached =
|
|
311
|
+
const cached = withCacheSource(result.prompt)
|
|
314
312
|
if (hot) this.hotCache.set(request.key, cached)
|
|
315
313
|
if (!request.resolve) this.warmCache.set(request.key, cached)
|
|
316
314
|
}
|
|
@@ -335,7 +333,7 @@ class PromptManager {
|
|
|
335
333
|
async #backgroundFetch (request, signal) {
|
|
336
334
|
const result = await this.#fetchAndCache(request, { hot: false, signal })
|
|
337
335
|
if (!result.cacheable) return
|
|
338
|
-
if (result.prompt) return
|
|
336
|
+
if (result.prompt) return withCacheSource(result.prompt)
|
|
339
337
|
if (result.notFound) return
|
|
340
338
|
throw new Error(result.reason)
|
|
341
339
|
}
|
|
@@ -93,7 +93,7 @@ class ManagedPrompt {
|
|
|
93
93
|
format (variables = {}) {
|
|
94
94
|
if (typeof this.template === 'string') return render(this.template, variables)
|
|
95
95
|
return this.template.flatMap(item => {
|
|
96
|
-
if (!isPlaceholder(item)) return
|
|
96
|
+
if (!isPlaceholder(item)) return { ...item, content: render(item.content, variables) }
|
|
97
97
|
if (!Object.hasOwn(variables, item.name)) {
|
|
98
98
|
throw new TypeError(`Missing message placeholder variable '${item.name}'`)
|
|
99
99
|
}
|
|
@@ -29,7 +29,7 @@ const {
|
|
|
29
29
|
getFunctionArguments,
|
|
30
30
|
validateKind,
|
|
31
31
|
} = require('./util')
|
|
32
|
-
const { buildAgentDeclaration } = require('./agent-manifest')
|
|
32
|
+
const { buildAgentDeclaration, toAgentVersion } = require('./agent-manifest')
|
|
33
33
|
/** @typedef {import('./agent-manifest').AgentDeclaration} AgentDeclaration */
|
|
34
34
|
const { storage } = require('./storage')
|
|
35
35
|
const telemetry = require('./telemetry')
|
|
@@ -163,7 +163,7 @@ class LLMObs extends NoopLLMObs {
|
|
|
163
163
|
const {
|
|
164
164
|
spanOptions,
|
|
165
165
|
...llmobsOptions
|
|
166
|
-
} = this.#extractOptions(options)
|
|
166
|
+
} = this.#extractOptions(options, kind)
|
|
167
167
|
|
|
168
168
|
if (fn.length > 1) {
|
|
169
169
|
return this._tracer.trace(name, spanOptions, (span, cb) =>
|
|
@@ -193,7 +193,7 @@ class LLMObs extends NoopLLMObs {
|
|
|
193
193
|
const {
|
|
194
194
|
spanOptions,
|
|
195
195
|
...llmobsOptions
|
|
196
|
-
} = this.#extractOptions(options)
|
|
196
|
+
} = this.#extractOptions(options, kind)
|
|
197
197
|
|
|
198
198
|
const llmobs = this
|
|
199
199
|
|
|
@@ -719,21 +719,32 @@ class LLMObs extends NoopLLMObs {
|
|
|
719
719
|
}
|
|
720
720
|
}
|
|
721
721
|
|
|
722
|
-
#extractOptions (options) {
|
|
722
|
+
#extractOptions (options, kind) {
|
|
723
723
|
const {
|
|
724
724
|
modelName,
|
|
725
725
|
modelProvider,
|
|
726
726
|
sessionId,
|
|
727
727
|
mlApp,
|
|
728
|
+
version,
|
|
728
729
|
_decorator,
|
|
729
730
|
...spanOptions
|
|
730
731
|
} = options
|
|
731
732
|
|
|
733
|
+
let agentVersion
|
|
734
|
+
if (version !== undefined) {
|
|
735
|
+
if (kind === 'agent') {
|
|
736
|
+
agentVersion = toAgentVersion(version)
|
|
737
|
+
} else {
|
|
738
|
+
logger.warn('Dropping the version option on a %s span, it is only supported for agent spans.', kind)
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
732
742
|
return {
|
|
733
743
|
mlApp,
|
|
734
744
|
modelName,
|
|
735
745
|
modelProvider,
|
|
736
746
|
sessionId,
|
|
747
|
+
agentVersion,
|
|
737
748
|
_decorator,
|
|
738
749
|
spanOptions,
|
|
739
750
|
}
|
|
@@ -16,6 +16,8 @@ const {
|
|
|
16
16
|
METADATA,
|
|
17
17
|
COST_TAGS,
|
|
18
18
|
AGENT_MANIFEST,
|
|
19
|
+
AGENT_VERSION,
|
|
20
|
+
AGENT_VERSION_TAG_KEY,
|
|
19
21
|
TOOL_DEFINITIONS,
|
|
20
22
|
EXPERIMENT_INPUT,
|
|
21
23
|
EXPERIMENT_OUTPUT,
|
|
@@ -32,6 +34,7 @@ const {
|
|
|
32
34
|
PARENT_ID_KEY,
|
|
33
35
|
PARENT_AGENT_NAME,
|
|
34
36
|
PARENT_AGENT_SPAN_ID,
|
|
37
|
+
PARENT_AGENT_VERSION,
|
|
35
38
|
SESSION_ID,
|
|
36
39
|
NAME,
|
|
37
40
|
INPUT_PROMPT,
|
|
@@ -92,9 +95,8 @@ class LLMObsSpanProcessor {
|
|
|
92
95
|
|
|
93
96
|
// TODO: instead of relying on the tagger's weakmap registry, can we use some namespaced storage correlation?
|
|
94
97
|
process (span) {
|
|
95
|
-
if (!this.#config.llmobs.DD_LLMOBS_ENABLED) return
|
|
96
98
|
// if the span is not in our private tagger map, it is not an llmobs span
|
|
97
|
-
if (!LLMObsTagger.tagMap.has(span)) return
|
|
99
|
+
if (!this.#config.llmobs.DD_LLMOBS_ENABLED || !LLMObsTagger.tagMap.has(span)) return
|
|
98
100
|
|
|
99
101
|
try {
|
|
100
102
|
this.#setGenAiApmTags(span)
|
|
@@ -407,6 +409,15 @@ class LLMObsSpanProcessor {
|
|
|
407
409
|
const existingTags = LLMObsTagger.tagMap.get(span)?.[TAGS] || {}
|
|
408
410
|
if (existingTags) tags = { ...tags, ...existingTags }
|
|
409
411
|
|
|
412
|
+
const mlObsTags = LLMObsTagger.tagMap.get(span)
|
|
413
|
+
// Resolved here because a span can become an agent after registration. An agent without its own version, and
|
|
414
|
+
// any other span, reports the version inherited at registration. Either wins over a user
|
|
415
|
+
// tag of the same name.
|
|
416
|
+
const agentVersion = mlObsTags?.[SPAN_KIND] === 'agent'
|
|
417
|
+
? mlObsTags[AGENT_VERSION] || mlObsTags[PARENT_AGENT_VERSION]
|
|
418
|
+
: mlObsTags?.[PARENT_AGENT_VERSION]
|
|
419
|
+
if (agentVersion) tags[AGENT_VERSION_TAG_KEY] = agentVersion
|
|
420
|
+
|
|
410
421
|
return tags
|
|
411
422
|
}
|
|
412
423
|
|
|
@@ -19,6 +19,7 @@ const {
|
|
|
19
19
|
METADATA,
|
|
20
20
|
COST_TAGS,
|
|
21
21
|
AGENT_MANIFEST,
|
|
22
|
+
AGENT_VERSION,
|
|
22
23
|
METRICS,
|
|
23
24
|
TOOL_DEFINITIONS,
|
|
24
25
|
PARENT_ID_KEY,
|
|
@@ -28,6 +29,7 @@ const {
|
|
|
28
29
|
NAME,
|
|
29
30
|
PARENT_AGENT_NAME,
|
|
30
31
|
PARENT_AGENT_SPAN_ID,
|
|
32
|
+
PARENT_AGENT_VERSION,
|
|
31
33
|
PROPAGATED_PARENT_ID_KEY,
|
|
32
34
|
PROPAGATED_PARENT_AGENT_ID_KEY,
|
|
33
35
|
PROPAGATED_PARENT_AGENT_NAME_KEY,
|
|
@@ -116,10 +118,11 @@ class LLMObsTagger {
|
|
|
116
118
|
kind,
|
|
117
119
|
name,
|
|
118
120
|
integration,
|
|
121
|
+
agentVersion,
|
|
119
122
|
_decorator,
|
|
120
123
|
} = {}) {
|
|
121
|
-
if
|
|
122
|
-
if (!kind) return
|
|
124
|
+
// Do not register it in the map if it does not have an LLMObs span kind.
|
|
125
|
+
if (!this.#config.llmobs.DD_LLMOBS_ENABLED || !kind) return
|
|
123
126
|
|
|
124
127
|
const spanMlApp =
|
|
125
128
|
mlApp ||
|
|
@@ -193,6 +196,8 @@ class LLMObsTagger {
|
|
|
193
196
|
storage.getStore()?.agentDeclarations
|
|
194
197
|
)
|
|
195
198
|
if (agentDeclarations) this.#applyAgentDeclarations(span, agentDeclarations)
|
|
199
|
+
// The span's own version wins over the enclosing contexts.
|
|
200
|
+
if (agentVersion) this._setTag(span, AGENT_VERSION, agentVersion)
|
|
196
201
|
|
|
197
202
|
// apply annotation context name
|
|
198
203
|
const annotationContextName = annotationContext?.name
|
|
@@ -213,15 +218,16 @@ class LLMObsTagger {
|
|
|
213
218
|
|
|
214
219
|
/**
|
|
215
220
|
* Applies the agents declared by the enclosing annotation contexts, outermost first, to every span in the block.
|
|
216
|
-
*
|
|
217
|
-
*
|
|
221
|
+
* Both are emitted only if the span is an agent when it finishes, since some integrations promote a span to an
|
|
222
|
+
* agent after registration.
|
|
218
223
|
*
|
|
219
224
|
* @param {import('../opentracing/span')} span
|
|
220
225
|
* @param {import('./agent-manifest').AgentDeclaration[]} declarations
|
|
221
226
|
*/
|
|
222
227
|
#applyAgentDeclarations (span, declarations) {
|
|
223
228
|
for (const declaration of declarations) {
|
|
224
|
-
this
|
|
229
|
+
if (declaration.version) this._setTag(span, AGENT_VERSION, declaration.version)
|
|
230
|
+
if (declaration.manifest) this.#tagAgentManifestFields(span, declaration.manifest)
|
|
225
231
|
}
|
|
226
232
|
}
|
|
227
233
|
|
|
@@ -233,7 +239,9 @@ class LLMObsTagger {
|
|
|
233
239
|
*/
|
|
234
240
|
tagAgent (span, agent) {
|
|
235
241
|
const declaration = buildAgentDeclaration(agent)
|
|
236
|
-
if (declaration)
|
|
242
|
+
if (!declaration) return
|
|
243
|
+
if (declaration.version) this._setTag(span, AGENT_VERSION, declaration.version)
|
|
244
|
+
if (declaration.manifest) this.#tagAgentManifestFields(span, declaration.manifest)
|
|
237
245
|
}
|
|
238
246
|
|
|
239
247
|
/**
|
|
@@ -276,8 +284,9 @@ class LLMObsTagger {
|
|
|
276
284
|
|
|
277
285
|
/**
|
|
278
286
|
* Store the nearest agent ancestor on the span so it can be surfaced as
|
|
279
|
-
* `meta.agent_attribution` at finish
|
|
280
|
-
*
|
|
287
|
+
* `meta.agent_attribution` at finish, along with that agent's version, which the agent panel reads
|
|
288
|
+
* off each span to attribute its spend. Resolved once here, at registration, so downstream children
|
|
289
|
+
* inherit it with a single lookup rather than walking the ancestor chain.
|
|
281
290
|
*
|
|
282
291
|
* @param {import('../opentracing/span')} span
|
|
283
292
|
* @param {import('../opentracing/span')} [parent] the LLMObs parent span, if any
|
|
@@ -285,10 +294,10 @@ class LLMObsTagger {
|
|
|
285
294
|
// TODO: spans whose kind changes after registration (e.g. claude-agent-sdk tools promoted to
|
|
286
295
|
// sub-agents) will not retroactively update already-finished children's attribution. Follow up.
|
|
287
296
|
#tagAgentAttribution (span, parent) {
|
|
288
|
-
let name, spanId
|
|
297
|
+
let name, spanId, version
|
|
289
298
|
if (registry.has(parent)) {
|
|
290
299
|
// Local LLMObs parent: attribute to it if it is an agent, else inherit its resolution.
|
|
291
|
-
({ name, spanId } = resolveAgentAttribution(registry.get(parent), parent))
|
|
300
|
+
({ name, spanId, version } = resolveAgentAttribution(registry.get(parent), parent))
|
|
292
301
|
} else if (span.context()._trace.tags[PROPAGATED_PARENT_ID_KEY]) {
|
|
293
302
|
// Distributed LLMObs parent: inherit the nearest agent propagated from upstream. The
|
|
294
303
|
// name may be absent when the upstream hop ran an older SDK or its name was not
|
|
@@ -300,6 +309,7 @@ class LLMObsTagger {
|
|
|
300
309
|
|
|
301
310
|
if (name != null) this._setTag(span, PARENT_AGENT_NAME, name)
|
|
302
311
|
if (spanId != null) this._setTag(span, PARENT_AGENT_SPAN_ID, spanId)
|
|
312
|
+
if (version) this._setTag(span, PARENT_AGENT_VERSION, version)
|
|
303
313
|
}
|
|
304
314
|
|
|
305
315
|
// TODO: similarly for the following `tag` methods,
|
|
@@ -8,8 +8,10 @@ const {
|
|
|
8
8
|
SPAN_KINDS,
|
|
9
9
|
SPAN_KIND,
|
|
10
10
|
NAME,
|
|
11
|
+
AGENT_VERSION,
|
|
11
12
|
PARENT_AGENT_NAME,
|
|
12
13
|
PARENT_AGENT_SPAN_ID,
|
|
14
|
+
PARENT_AGENT_VERSION,
|
|
13
15
|
} = require('./constants/tags')
|
|
14
16
|
|
|
15
17
|
const DECIMAL_TRACE_ID_REGEX = /^\d+$/
|
|
@@ -260,8 +262,7 @@ function findArgumentsBounds (str) {
|
|
|
260
262
|
|
|
261
263
|
const memo = new WeakMap()
|
|
262
264
|
function getFunctionArguments (fn, args = []) {
|
|
263
|
-
if (!fn) return
|
|
264
|
-
if (!args.length) return
|
|
265
|
+
if (!fn || !args.length) return
|
|
265
266
|
if (args.length === 1) return args[0]
|
|
266
267
|
|
|
267
268
|
try {
|
|
@@ -326,14 +327,18 @@ function agentNameWireSafe (name) {
|
|
|
326
327
|
*
|
|
327
328
|
* @param {Record<string, unknown> | undefined} tags - Registry entry for the parent span.
|
|
328
329
|
* @param {import('../opentracing/span')} [span] - The parent span itself (needed for span id / name).
|
|
329
|
-
* @returns {{ name: string | undefined, spanId: string | undefined }}
|
|
330
|
+
* @returns {{ name: string | undefined, spanId: string | undefined, version: string | undefined }}
|
|
330
331
|
*/
|
|
331
332
|
function resolveAgentAttribution (tags, span) {
|
|
332
|
-
if (!tags) return { name: undefined, spanId: undefined }
|
|
333
|
+
if (!tags) return { name: undefined, spanId: undefined, version: undefined }
|
|
333
334
|
if (tags[SPAN_KIND] === 'agent') {
|
|
334
|
-
return {
|
|
335
|
+
return {
|
|
336
|
+
name: tags[NAME] || span._name,
|
|
337
|
+
spanId: span.context().toSpanId(),
|
|
338
|
+
version: tags[AGENT_VERSION] || tags[PARENT_AGENT_VERSION],
|
|
339
|
+
}
|
|
335
340
|
}
|
|
336
|
-
return { name: tags[PARENT_AGENT_NAME], spanId: tags[PARENT_AGENT_SPAN_ID] }
|
|
341
|
+
return { name: tags[PARENT_AGENT_NAME], spanId: tags[PARENT_AGENT_SPAN_ID], version: tags[PARENT_AGENT_VERSION] }
|
|
337
342
|
}
|
|
338
343
|
|
|
339
344
|
function spanHasError (span) {
|
|
@@ -23,13 +23,12 @@ class LLMObsBuffer {
|
|
|
23
23
|
/**
|
|
24
24
|
* @param {{
|
|
25
25
|
* events: Record<string, unknown>[],
|
|
26
|
-
* size: number,
|
|
27
26
|
* routing?: { apiKey?: string, site?: string }
|
|
28
27
|
* }} options
|
|
29
28
|
*/
|
|
30
|
-
constructor ({ events,
|
|
29
|
+
constructor ({ events, routing = {} }) {
|
|
31
30
|
this.events = events
|
|
32
|
-
this.size =
|
|
31
|
+
this.size = 0
|
|
33
32
|
this.routing = routing
|
|
34
33
|
}
|
|
35
34
|
|
|
@@ -53,7 +52,7 @@ class BaseLLMObsWriter {
|
|
|
53
52
|
this._eventType = eventType
|
|
54
53
|
|
|
55
54
|
/** @type {LLMObsBuffer} */
|
|
56
|
-
this._buffer = new LLMObsBuffer({ events: []
|
|
55
|
+
this._buffer = new LLMObsBuffer({ events: [] })
|
|
57
56
|
|
|
58
57
|
/** @type {import('../../config/config-base')} */
|
|
59
58
|
this._config = config
|
|
@@ -92,7 +91,7 @@ class BaseLLMObsWriter {
|
|
|
92
91
|
const apiKey = routing.apiKey
|
|
93
92
|
let buffer = this.#multiTenantBuffers.get(apiKey)
|
|
94
93
|
if (!buffer) {
|
|
95
|
-
buffer = new LLMObsBuffer({ events: [],
|
|
94
|
+
buffer = new LLMObsBuffer({ events: [], routing })
|
|
96
95
|
this.#multiTenantBuffers.set(apiKey, buffer)
|
|
97
96
|
}
|
|
98
97
|
return buffer
|
|
@@ -17,11 +17,7 @@ class Log {
|
|
|
17
17
|
get formatted () {
|
|
18
18
|
const { message, args } = this
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
if (message && args && args.length) {
|
|
22
|
-
formatted = format(message, ...args)
|
|
23
|
-
}
|
|
24
|
-
return formatted
|
|
20
|
+
return message && args && args.length ? format(message, ...args) : message
|
|
25
21
|
}
|
|
26
22
|
|
|
27
23
|
static parse (...args) {
|