dd-trace 5.98.0 → 5.99.1
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 +0 -1
- package/ext/tags.js +1 -0
- package/index.d.ts +9 -1
- package/package.json +68 -47
- package/packages/datadog-instrumentations/src/crypto.js +45 -0
- package/packages/datadog-instrumentations/src/cypress-config.js +122 -16
- package/packages/datadog-instrumentations/src/dns.js +24 -56
- package/packages/datadog-instrumentations/src/graphql.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +74 -0
- package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +4 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +10 -3
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/modelcontextprotocol-sdk.js +59 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +11 -2
- package/packages/datadog-instrumentations/src/jest.js +5 -5
- package/packages/datadog-instrumentations/src/modelcontextprotocol-sdk.js +7 -0
- package/packages/datadog-instrumentations/src/pino.js +4 -28
- package/packages/datadog-instrumentations/src/playwright-browser-scripts.js +27 -0
- package/packages/datadog-instrumentations/src/playwright.js +5 -17
- package/packages/datadog-instrumentations/src/stripe.js +38 -24
- package/packages/datadog-instrumentations/src/vitest.js +32 -4
- package/packages/datadog-instrumentations/src/zlib.js +29 -0
- package/packages/datadog-plugin-aws-sdk/src/base.js +1 -2
- package/packages/datadog-plugin-azure-event-hubs/src/producer.js +8 -15
- package/packages/datadog-plugin-azure-service-bus/src/producer.js +4 -9
- package/packages/datadog-plugin-cucumber/src/index.js +2 -2
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +5 -5
- package/packages/datadog-plugin-cypress/src/source-map-utils.js +48 -1
- package/packages/datadog-plugin-dd-trace-api/src/index.js +1 -1
- package/packages/datadog-plugin-graphql/src/utils.js +2 -2
- package/packages/datadog-plugin-http/src/server.js +11 -11
- package/packages/datadog-plugin-jest/src/index.js +2 -2
- package/packages/datadog-plugin-memcached/src/index.js +1 -1
- package/packages/datadog-plugin-mocha/src/index.js +1 -2
- package/packages/datadog-plugin-modelcontextprotocol-sdk/src/index.js +24 -0
- package/packages/datadog-plugin-modelcontextprotocol-sdk/src/tracing.js +55 -0
- package/packages/datadog-plugin-mongodb-core/src/index.js +1 -6
- package/packages/datadog-plugin-playwright/src/index.js +2 -3
- package/packages/datadog-plugin-vitest/src/index.js +14 -6
- package/packages/datadog-plugin-ws/src/close.js +2 -0
- package/packages/datadog-plugin-ws/src/producer.js +2 -0
- package/packages/datadog-plugin-ws/src/receiver.js +1 -0
- package/packages/dd-trace/src/aiguard/channels.js +8 -0
- package/packages/dd-trace/src/aiguard/index.js +7 -3
- package/packages/dd-trace/src/aiguard/sdk.js +44 -0
- package/packages/dd-trace/src/aiguard/tags.js +1 -0
- package/packages/dd-trace/src/appsec/blocking.js +18 -6
- package/packages/dd-trace/src/appsec/graphql.js +7 -7
- package/packages/dd-trace/src/appsec/index.js +9 -11
- package/packages/dd-trace/src/appsec/rasp/command_injection.js +4 -5
- package/packages/dd-trace/src/appsec/rasp/lfi.js +8 -4
- package/packages/dd-trace/src/appsec/rasp/sql_injection.js +5 -10
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +5 -6
- package/packages/dd-trace/src/appsec/recommended.json +2438 -13
- package/packages/dd-trace/src/appsec/reporter.js +6 -5
- package/packages/dd-trace/src/appsec/sdk/user_blocking.js +4 -8
- package/packages/dd-trace/src/appsec/store.js +50 -0
- package/packages/dd-trace/src/appsec/waf/index.js +3 -5
- package/packages/dd-trace/src/baggage.js +16 -13
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +2 -2
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +2 -2
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +2 -2
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +2 -2
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +3 -4
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -2
- package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +4 -5
- package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +3 -4
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +6 -6
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +2 -2
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +2 -2
- package/packages/dd-trace/src/config/config-types.d.ts +0 -4
- package/packages/dd-trace/src/config/defaults.js +10 -10
- package/packages/dd-trace/src/config/generated-config-types.d.ts +39 -38
- package/packages/dd-trace/src/config/index.js +29 -39
- package/packages/dd-trace/src/config/parsers.js +26 -9
- package/packages/dd-trace/src/config/supported-configurations.json +46 -78
- package/packages/dd-trace/src/debugger/config.js +2 -0
- package/packages/dd-trace/src/debugger/devtools_client/send.js +25 -5
- package/packages/dd-trace/src/dogstatsd.js +5 -8
- package/packages/dd-trace/src/encode/0.4.js +4 -5
- package/packages/dd-trace/src/exporter.js +1 -1
- package/packages/dd-trace/src/exporters/agent/index.js +0 -1
- package/packages/dd-trace/src/exporters/agent/writer.js +1 -2
- package/packages/dd-trace/src/exporters/agentless/writer.js +3 -3
- package/packages/dd-trace/src/exporters/common/util.js +2 -2
- package/packages/dd-trace/src/git_metadata_tagger.js +1 -1
- package/packages/dd-trace/src/id.js +2 -0
- package/packages/dd-trace/src/index.js +2 -5
- package/packages/dd-trace/src/lambda/handler.js +1 -3
- package/packages/dd-trace/src/llmobs/constants/tags.js +3 -0
- package/packages/dd-trace/src/llmobs/plugins/{anthropic.js → anthropic/index.js} +5 -63
- package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +106 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chain.js +3 -2
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +3 -2
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/embedding.js +2 -1
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +0 -49
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/vectorstore.js +2 -1
- package/packages/dd-trace/src/llmobs/plugins/langchain/messages.js +76 -0
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -26
- package/packages/dd-trace/src/llmobs/plugins/modelcontextprotocol-sdk/index.js +68 -0
- package/packages/dd-trace/src/llmobs/plugins/modelcontextprotocol-sdk/utils.js +57 -0
- package/packages/dd-trace/src/llmobs/sdk.js +23 -3
- package/packages/dd-trace/src/llmobs/span_processor.js +14 -1
- package/packages/dd-trace/src/llmobs/writers/base.js +7 -1
- package/packages/dd-trace/src/llmobs/writers/spans.js +1 -1
- package/packages/dd-trace/src/openfeature/eval-metrics-hook.js +103 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +3 -0
- package/packages/dd-trace/src/opentelemetry/logs/index.js +6 -6
- package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +3 -2
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +7 -7
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +3 -2
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +19 -66
- package/packages/dd-trace/src/opentelemetry/trace/index.js +11 -16
- package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +11 -3
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +51 -41
- package/packages/dd-trace/src/opentelemetry/tracer.js +9 -11
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +30 -23
- package/packages/dd-trace/src/opentracing/span.js +2 -2
- package/packages/dd-trace/src/opentracing/tracer.js +12 -5
- package/packages/dd-trace/src/plugin_manager.js +6 -6
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/log_plugin.js +1 -1
- package/packages/dd-trace/src/plugins/util/test.js +128 -7
- package/packages/dd-trace/src/plugins/util/url.js +2 -1
- package/packages/dd-trace/src/profiling/profilers/event_plugins/crypto.js +32 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/zlib.js +19 -0
- package/packages/dd-trace/src/profiling/profilers/events.js +35 -0
- package/packages/dd-trace/src/proxy.js +8 -14
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +2 -2
- package/packages/dd-trace/src/service-naming/schemas/v0/web.js +4 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/web.js +4 -0
- package/packages/dd-trace/src/span_processor.js +1 -2
- package/packages/dd-trace/src/tagger.js +2 -2
- package/packages/dd-trace/src/telemetry/send-data.js +5 -7
- package/packages/dd-trace/src/tracer.js +2 -2
- package/vendor/dist/ignore/LICENSE +0 -21
- package/vendor/dist/ignore/index.js +0 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { UNKNOWN_MODEL_PROVIDER } = require('
|
|
4
|
-
const LLMObsPlugin = require('
|
|
3
|
+
const { UNKNOWN_MODEL_PROVIDER } = require('../../constants/tags')
|
|
4
|
+
const LLMObsPlugin = require('../base')
|
|
5
|
+
const { appendMessage } = require('./util')
|
|
5
6
|
|
|
6
7
|
const ALLOWED_METADATA_KEYS = new Set([
|
|
7
8
|
'max_tokens',
|
|
@@ -144,51 +145,11 @@ class AnthropicLLMObsPlugin extends LLMObsPlugin {
|
|
|
144
145
|
const inputMessages = []
|
|
145
146
|
|
|
146
147
|
if (system) {
|
|
147
|
-
|
|
148
|
+
appendMessage(inputMessages, { role: 'system', content: system })
|
|
148
149
|
}
|
|
149
150
|
|
|
150
151
|
for (const message of messages) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (typeof content === 'string') {
|
|
154
|
-
inputMessages.push({ content, role })
|
|
155
|
-
continue
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
for (const block of content) {
|
|
159
|
-
if (block.type === 'text') {
|
|
160
|
-
inputMessages.push({ content: block.text, role })
|
|
161
|
-
} else if (block.type === 'image') {
|
|
162
|
-
inputMessages.push({ content: '([IMAGE DETECTED])', role })
|
|
163
|
-
} else if (block.type === 'tool_use') {
|
|
164
|
-
const { text, name, id, type } = block
|
|
165
|
-
let input = block.input
|
|
166
|
-
if (typeof input === 'string') {
|
|
167
|
-
input = JSON.parse(input)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const toolCall = {
|
|
171
|
-
name,
|
|
172
|
-
arguments: input,
|
|
173
|
-
toolId: id,
|
|
174
|
-
type,
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
inputMessages.push({ content: text ?? '', role, toolCalls: [toolCall] })
|
|
178
|
-
} else if (block.type === 'tool_result') {
|
|
179
|
-
const { content } = block
|
|
180
|
-
const formattedContent = this.#formatAnthropicToolResultContent(content)
|
|
181
|
-
const toolResult = {
|
|
182
|
-
result: formattedContent,
|
|
183
|
-
toolId: block.tool_use_id,
|
|
184
|
-
type: 'tool_result',
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
inputMessages.push({ content: '', role, toolResults: [toolResult] })
|
|
188
|
-
} else {
|
|
189
|
-
inputMessages.push({ content: JSON.stringify(block), role })
|
|
190
|
-
}
|
|
191
|
-
}
|
|
152
|
+
appendMessage(inputMessages, message)
|
|
192
153
|
}
|
|
193
154
|
|
|
194
155
|
this._tagger.tagLLMIO(span, inputMessages)
|
|
@@ -273,25 +234,6 @@ class AnthropicLLMObsPlugin extends LLMObsPlugin {
|
|
|
273
234
|
|
|
274
235
|
this._tagger.tagMetrics(span, metrics)
|
|
275
236
|
}
|
|
276
|
-
|
|
277
|
-
// maybe can make into a util file
|
|
278
|
-
#formatAnthropicToolResultContent (content) {
|
|
279
|
-
if (typeof content === 'string') {
|
|
280
|
-
return content
|
|
281
|
-
} else if (Array.isArray(content)) {
|
|
282
|
-
const formattedContent = []
|
|
283
|
-
for (const toolResultBlock of content) {
|
|
284
|
-
if (toolResultBlock.text) {
|
|
285
|
-
formattedContent.push(toolResultBlock.text)
|
|
286
|
-
} else if (toolResultBlock.type === 'image') {
|
|
287
|
-
formattedContent.push('([IMAGE DETECTED])')
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
return formattedContent.join(',')
|
|
292
|
-
}
|
|
293
|
-
return JSON.stringify(content)
|
|
294
|
-
}
|
|
295
237
|
}
|
|
296
238
|
|
|
297
239
|
module.exports = AnthropicLLMObsPlugin
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {{type: 'text', text: string}} TextBlock
|
|
5
|
+
* @typedef {{type: 'image'}} ImageBlock
|
|
6
|
+
* @typedef {{
|
|
7
|
+
* type: 'tool_use', text: string, name: string, id: string, input: string | Record<string, unknown>
|
|
8
|
+
* }} ToolUseBlock
|
|
9
|
+
* @typedef {{
|
|
10
|
+
* type: 'tool_result',
|
|
11
|
+
* tool_use_id: string,
|
|
12
|
+
* content: string | Array<{type: string, text?: string}>
|
|
13
|
+
* }} ToolResultBlock
|
|
14
|
+
*
|
|
15
|
+
* @typedef {{
|
|
16
|
+
* content: string,
|
|
17
|
+
* role: string,
|
|
18
|
+
* toolCalls?: Array<{
|
|
19
|
+
* name: string,
|
|
20
|
+
* arguments: string | Record<string, unknown>,
|
|
21
|
+
* toolId: string,
|
|
22
|
+
* type: string
|
|
23
|
+
* }>,
|
|
24
|
+
* toolResults?: Array<{
|
|
25
|
+
* result: string,
|
|
26
|
+
* toolId: string,
|
|
27
|
+
* type: 'tool_result'
|
|
28
|
+
* }>
|
|
29
|
+
* }} AnthropicLlmObsMessage
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Formats tool result into LLM Observability compatible contents
|
|
34
|
+
* @param {ToolResultBlock['content']} content
|
|
35
|
+
*/
|
|
36
|
+
function formatAnthropicToolResultContent (content) {
|
|
37
|
+
if (typeof content === 'string') {
|
|
38
|
+
return content
|
|
39
|
+
} else if (Array.isArray(content)) {
|
|
40
|
+
const formattedContent = []
|
|
41
|
+
for (const toolResultBlock of content) {
|
|
42
|
+
if (toolResultBlock.text) {
|
|
43
|
+
formattedContent.push(toolResultBlock.text)
|
|
44
|
+
} else if (toolResultBlock.type === 'image') {
|
|
45
|
+
formattedContent.push('([IMAGE DETECTED])')
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return formattedContent.join(',')
|
|
50
|
+
}
|
|
51
|
+
return JSON.stringify(content)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Normalizes and formats a message into LLM Observability compatible contents.
|
|
56
|
+
* Can be spread into a list of other messages.
|
|
57
|
+
*
|
|
58
|
+
* @param {AnthropicLlmObsMessage[]} messages
|
|
59
|
+
* @param {{ role: string, content: string | Array<TextBlock | ImageBlock | ToolUseBlock | ToolResultBlock> }} message
|
|
60
|
+
* @returns {void}
|
|
61
|
+
*/
|
|
62
|
+
function appendMessage (messages, { role, content }) {
|
|
63
|
+
if (typeof content === 'string') {
|
|
64
|
+
messages.push({ content, role })
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const block of content) {
|
|
69
|
+
if (block.type === 'text') {
|
|
70
|
+
messages.push({ content: block.text, role })
|
|
71
|
+
} else if (block.type === 'image') {
|
|
72
|
+
messages.push({ content: '([IMAGE DETECTED])', role })
|
|
73
|
+
} else if (block.type === 'tool_use') {
|
|
74
|
+
const { text, name, id, type } = block
|
|
75
|
+
let input = block.input
|
|
76
|
+
if (typeof input === 'string') {
|
|
77
|
+
input = JSON.parse(input)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const toolCall = {
|
|
81
|
+
name,
|
|
82
|
+
arguments: input,
|
|
83
|
+
toolId: id,
|
|
84
|
+
type,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
messages.push({ content: text ?? '', role, toolCalls: [toolCall] })
|
|
88
|
+
} else if (block.type === 'tool_result') {
|
|
89
|
+
const { content } = block
|
|
90
|
+
const formattedContent = formatAnthropicToolResultContent(content)
|
|
91
|
+
const toolResult = {
|
|
92
|
+
result: formattedContent,
|
|
93
|
+
toolId: block.tool_use_id,
|
|
94
|
+
type: 'tool_result',
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
messages.push({ content: '', role, toolResults: [toolResult] })
|
|
98
|
+
} else {
|
|
99
|
+
messages.push({ content: JSON.stringify(block), role })
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = {
|
|
105
|
+
appendMessage,
|
|
106
|
+
}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { spanHasError } = require('../../../util')
|
|
4
|
+
const { formatIO } = require('../messages')
|
|
4
5
|
const LangChainLLMObsHandler = require('.')
|
|
5
6
|
|
|
6
7
|
class LangChainLLMObsChainHandler extends LangChainLLMObsHandler {
|
|
7
8
|
setMetaTags ({ span, inputs, results }) {
|
|
8
9
|
let input
|
|
9
10
|
if (inputs) {
|
|
10
|
-
input =
|
|
11
|
+
input = formatIO(inputs)
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
const output = !results || spanHasError(span) ? '' :
|
|
14
|
+
const output = !results || spanHasError(span) ? '' : formatIO(results)
|
|
14
15
|
|
|
15
16
|
// chain spans will always be workflows
|
|
16
17
|
this._tagger.tagTextIO(span, input, output)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const LLMObsTagger = require('../../../tagger')
|
|
4
4
|
const { spanHasError } = require('../../../util')
|
|
5
|
+
const { getRole } = require('../messages')
|
|
5
6
|
const LangChainLLMObsHandler = require('.')
|
|
6
7
|
|
|
7
8
|
const LLM = 'llm'
|
|
@@ -22,7 +23,7 @@ class LangChainLLMObsChatModelHandler extends LangChainLLMObsHandler {
|
|
|
22
23
|
for (const messageSet of inputs) {
|
|
23
24
|
for (const message of messageSet) {
|
|
24
25
|
const content = message.content || ''
|
|
25
|
-
const role =
|
|
26
|
+
const role = getRole(message)
|
|
26
27
|
inputMessages.push({ content, role })
|
|
27
28
|
}
|
|
28
29
|
}
|
|
@@ -54,7 +55,7 @@ class LangChainLLMObsChatModelHandler extends LangChainLLMObsHandler {
|
|
|
54
55
|
for (const messageSet of results.generations) {
|
|
55
56
|
for (const chatCompletion of messageSet) {
|
|
56
57
|
const chatCompletionMessage = chatCompletion.message
|
|
57
|
-
const role =
|
|
58
|
+
const role = getRole(chatCompletionMessage)
|
|
58
59
|
const content = chatCompletionMessage.text || ''
|
|
59
60
|
const toolCalls = this.extractToolCalls(chatCompletionMessage)
|
|
60
61
|
outputMessages.push({ content, role, toolCalls })
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const LLMObsTagger = require('../../../tagger')
|
|
4
4
|
const { spanHasError } = require('../../../util')
|
|
5
|
+
const { formatIO } = require('../messages')
|
|
5
6
|
const LangChainLLMObsHandler = require('.')
|
|
6
7
|
|
|
7
8
|
class LangChainLLMObsEmbeddingHandler extends LangChainLLMObsHandler {
|
|
@@ -10,7 +11,7 @@ class LangChainLLMObsEmbeddingHandler extends LangChainLLMObsHandler {
|
|
|
10
11
|
let embeddingInput, embeddingOutput
|
|
11
12
|
|
|
12
13
|
if (isWorkflow) {
|
|
13
|
-
embeddingInput =
|
|
14
|
+
embeddingInput = formatIO(inputs)
|
|
14
15
|
} else {
|
|
15
16
|
const input = Array.isArray(inputs) ? inputs : [inputs]
|
|
16
17
|
embeddingInput = input.map(doc => ({ text: doc }))
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const ROLE_MAPPINGS = {
|
|
4
|
-
human: 'user',
|
|
5
|
-
ai: 'assistant',
|
|
6
|
-
system: 'system',
|
|
7
|
-
}
|
|
8
|
-
|
|
9
3
|
class LangChainLLMObsHandler {
|
|
10
4
|
constructor (tagger) {
|
|
11
5
|
/** @type {import('../../../tagger')} */
|
|
@@ -18,38 +12,6 @@ class LangChainLLMObsHandler {
|
|
|
18
12
|
|
|
19
13
|
setMetaTags () {}
|
|
20
14
|
|
|
21
|
-
formatIO (messages) {
|
|
22
|
-
if (messages.constructor.name === 'Object') { // plain JSON
|
|
23
|
-
const formatted = {}
|
|
24
|
-
for (const [key, value] of Object.entries(messages)) {
|
|
25
|
-
formatted[key] = this.formatIO(value)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return formatted
|
|
29
|
-
} else if (Array.isArray(messages)) {
|
|
30
|
-
return messages.map(message => this.formatIO(message))
|
|
31
|
-
} // either a BaseMesage type or a string
|
|
32
|
-
return this.getContentFromMessage(messages)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
getContentFromMessage (message) {
|
|
36
|
-
if (typeof message === 'string') {
|
|
37
|
-
return message
|
|
38
|
-
}
|
|
39
|
-
try {
|
|
40
|
-
const messageContent = {
|
|
41
|
-
content: message.content || '',
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const role = this.getRole(message)
|
|
45
|
-
if (role) messageContent.role = role
|
|
46
|
-
|
|
47
|
-
return messageContent
|
|
48
|
-
} catch {
|
|
49
|
-
return JSON.stringify(message)
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
15
|
checkTokenUsageChatOrLLMResult (results) {
|
|
54
16
|
const llmOutput = results.llmOutput
|
|
55
17
|
const tokens = {
|
|
@@ -90,17 +52,6 @@ class LangChainLLMObsHandler {
|
|
|
90
52
|
runId: runIdBase,
|
|
91
53
|
}
|
|
92
54
|
}
|
|
93
|
-
|
|
94
|
-
getRole (message) {
|
|
95
|
-
if (message.role) return ROLE_MAPPINGS[message.role] || message.role
|
|
96
|
-
|
|
97
|
-
const type = (
|
|
98
|
-
(typeof message.getType === 'function' && message.getType()) ||
|
|
99
|
-
(typeof message._getType === 'function' && message._getType())
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
return ROLE_MAPPINGS[type] || type
|
|
103
|
-
}
|
|
104
55
|
}
|
|
105
56
|
|
|
106
57
|
module.exports = LangChainLLMObsHandler
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { spanHasError } = require('../../../util')
|
|
4
|
+
const { formatIO } = require('../messages')
|
|
4
5
|
const LangChainLLMObsHandler = require('.')
|
|
5
6
|
|
|
6
7
|
class LangChainLLMObsVectorStoreHandler extends LangChainLLMObsHandler {
|
|
7
8
|
setMetaTags ({ span, inputs, results }) {
|
|
8
|
-
const input =
|
|
9
|
+
const input = formatIO(inputs)
|
|
9
10
|
if (spanHasError(span)) {
|
|
10
11
|
this._tagger.tagRetrievalIO(span, input)
|
|
11
12
|
return
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const ROLE_MAPPINGS = {
|
|
4
|
+
human: 'user',
|
|
5
|
+
ai: 'assistant',
|
|
6
|
+
system: 'system',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function getRole (message) {
|
|
10
|
+
if (message.role) return ROLE_MAPPINGS[message.role] || message.role
|
|
11
|
+
|
|
12
|
+
const type = (
|
|
13
|
+
(typeof message.getType === 'function' && message.getType()) ||
|
|
14
|
+
(typeof message._getType === 'function' && message._getType())
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
return ROLE_MAPPINGS[type] || type
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getContentFromMessage (message) {
|
|
21
|
+
if (typeof message === 'string') {
|
|
22
|
+
return message
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const messageContent = {
|
|
26
|
+
content: message.content || '',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const role = getRole(message)
|
|
30
|
+
if (role) messageContent.role = role
|
|
31
|
+
|
|
32
|
+
return messageContent
|
|
33
|
+
} catch {
|
|
34
|
+
return JSON.stringify(message)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isBaseMessage (data) {
|
|
39
|
+
return typeof data._getType === 'function' || typeof data.getType === 'function'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function formatIO (data) {
|
|
43
|
+
if (data == null) return ''
|
|
44
|
+
|
|
45
|
+
if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean') {
|
|
46
|
+
return data
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (data.constructor?.name === 'Object') {
|
|
50
|
+
const formatted = {}
|
|
51
|
+
for (const [key, value] of Object.entries(data)) {
|
|
52
|
+
formatted[key] = formatIO(value)
|
|
53
|
+
}
|
|
54
|
+
return formatted
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (Array.isArray(data)) {
|
|
58
|
+
return data.map(item => formatIO(item))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Only duck-typed BaseMessage instances collapse to { content, role }.
|
|
62
|
+
// Other class instances (e.g. LangChain Document) preserve their shape via JSON.stringify,
|
|
63
|
+
// otherwise they'd reduce to { content: '' } and lose data.
|
|
64
|
+
if (isBaseMessage(data)) return getContentFromMessage(data)
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
return JSON.stringify(data)
|
|
68
|
+
} catch {
|
|
69
|
+
return String(data)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = {
|
|
74
|
+
getRole,
|
|
75
|
+
formatIO,
|
|
76
|
+
}
|
|
@@ -1,36 +1,11 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const LLMObsPlugin = require('../base')
|
|
4
|
+
const { formatIO } = require('../langchain/messages')
|
|
4
5
|
const { spanHasError } = require('../../util')
|
|
5
6
|
|
|
6
7
|
const streamDataMap = new WeakMap()
|
|
7
8
|
|
|
8
|
-
function formatIO (data) {
|
|
9
|
-
if (data == null) return ''
|
|
10
|
-
|
|
11
|
-
if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean') {
|
|
12
|
-
return data
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
if (data.constructor?.name === 'Object') {
|
|
16
|
-
const formatted = {}
|
|
17
|
-
for (const [key, value] of Object.entries(data)) {
|
|
18
|
-
formatted[key] = formatIO(value)
|
|
19
|
-
}
|
|
20
|
-
return formatted
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (Array.isArray(data)) {
|
|
24
|
-
return data.map(item => formatIO(item))
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
return JSON.stringify(data)
|
|
29
|
-
} catch {
|
|
30
|
-
return String(data)
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
9
|
class PregelStreamLLMObsPlugin extends LLMObsPlugin {
|
|
35
10
|
static id = 'llmobs_langgraph_pregel_stream'
|
|
36
11
|
static integration = 'langgraph'
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const LLMObsPlugin = require('../base')
|
|
4
|
+
const { formatInput, formatOutput } = require('./utils')
|
|
5
|
+
|
|
6
|
+
class McpToolCallLLMObsPlugin extends LLMObsPlugin {
|
|
7
|
+
static id = 'llmobs_mcp_tool_call'
|
|
8
|
+
static integration = 'modelcontextprotocol-sdk'
|
|
9
|
+
static prefix = 'tracing:orchestrion:@modelcontextprotocol/sdk:Client_callTool'
|
|
10
|
+
|
|
11
|
+
getLLMObsSpanRegisterOptions (ctx) {
|
|
12
|
+
const params = ctx.arguments?.[0]
|
|
13
|
+
const toolName = params?.name || 'unknown_tool'
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
kind: 'tool',
|
|
17
|
+
name: `MCP Client Tool Call: ${toolName}`,
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
setLLMObsTags (ctx) {
|
|
22
|
+
const span = ctx.currentStore?.span
|
|
23
|
+
if (!span) return
|
|
24
|
+
|
|
25
|
+
const params = ctx.arguments?.[0]
|
|
26
|
+
const toolName = params?.name
|
|
27
|
+
const toolArguments = params?.arguments
|
|
28
|
+
|
|
29
|
+
const spanTags = { mcp_tool_kind: 'client' }
|
|
30
|
+
|
|
31
|
+
const serverVersion = ctx.self?.getServerVersion?.()
|
|
32
|
+
if (serverVersion) {
|
|
33
|
+
if (serverVersion.name) spanTags.mcp_server_name = serverVersion.name
|
|
34
|
+
if (serverVersion.version) spanTags.mcp_server_version = serverVersion.version
|
|
35
|
+
if (serverVersion.title) spanTags.mcp_server_title = serverVersion.title
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this._tagger.tagSpanTags(span, spanTags)
|
|
39
|
+
|
|
40
|
+
const hasError = ctx.error || ctx.result?.isError
|
|
41
|
+
const input = formatInput(toolName, toolArguments)
|
|
42
|
+
const output = hasError ? undefined : formatOutput(ctx.result)
|
|
43
|
+
|
|
44
|
+
this._tagger.tagTextIO(span, input, output)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
class McpListToolsLLMObsPlugin extends LLMObsPlugin {
|
|
49
|
+
static id = 'llmobs_mcp_list_tools'
|
|
50
|
+
static integration = 'modelcontextprotocol-sdk'
|
|
51
|
+
static prefix = 'tracing:orchestrion:@modelcontextprotocol/sdk:Client_listTools'
|
|
52
|
+
|
|
53
|
+
getLLMObsSpanRegisterOptions () {
|
|
54
|
+
return {
|
|
55
|
+
kind: 'task',
|
|
56
|
+
name: 'MCP Client List Tools',
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
setLLMObsTags (ctx) {
|
|
61
|
+
const span = ctx.currentStore?.span
|
|
62
|
+
if (!span || ctx.error) return
|
|
63
|
+
|
|
64
|
+
this._tagger.tagTextIO(span, null, JSON.stringify(ctx.result))
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = [McpToolCallLLMObsPlugin, McpListToolsLLMObsPlugin]
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Formats tool call input as a JSON string.
|
|
5
|
+
* @param {string} toolName - The name of the tool being called
|
|
6
|
+
* @param {object} toolArguments - The arguments passed to the tool
|
|
7
|
+
* @returns {string} Formatted input string
|
|
8
|
+
*/
|
|
9
|
+
function formatInput (toolName, toolArguments) {
|
|
10
|
+
if (!toolName && !toolArguments) return ''
|
|
11
|
+
|
|
12
|
+
if (toolArguments === undefined || toolArguments === null) {
|
|
13
|
+
return toolName || ''
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
return JSON.stringify({ name: toolName, arguments: toolArguments })
|
|
18
|
+
} catch {
|
|
19
|
+
return toolName || ''
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Formats MCP tool call result as a structured object matching Python's output format.
|
|
25
|
+
* MCP tool results contain a `content` array with items like:
|
|
26
|
+
* `[{ type: 'text', text: '...' }, { type: 'image', data: '...', mimeType: '...' }]`
|
|
27
|
+
* @param {object} result - The MCP CallToolResult
|
|
28
|
+
* @returns {string} JSON string of `{ content: Array<{type, text, annotations, meta}>, isError: boolean }`
|
|
29
|
+
*/
|
|
30
|
+
function formatOutput (result) {
|
|
31
|
+
if (!result) return ''
|
|
32
|
+
|
|
33
|
+
const content = result.content
|
|
34
|
+
const isError = result.isError || false
|
|
35
|
+
|
|
36
|
+
const processed = []
|
|
37
|
+
if (Array.isArray(content)) {
|
|
38
|
+
for (const item of content) {
|
|
39
|
+
if (item.type !== 'text') continue
|
|
40
|
+
const contentBlock = {
|
|
41
|
+
type: item.type,
|
|
42
|
+
text: item.text || '',
|
|
43
|
+
annotations: item.annotations || {},
|
|
44
|
+
meta: item._meta || {},
|
|
45
|
+
}
|
|
46
|
+
processed.push(contentBlock)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
return JSON.stringify({ content: processed, isError })
|
|
52
|
+
} catch {
|
|
53
|
+
return ''
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = { formatInput, formatOutput }
|
|
@@ -2,12 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
const { channel } = require('dc-polyfill')
|
|
4
4
|
|
|
5
|
-
const {
|
|
5
|
+
const { isError, isTrue } = require('../util')
|
|
6
6
|
const tracerVersion = require('../../../../package.json').version
|
|
7
7
|
const logger = require('../log')
|
|
8
8
|
const { getValueFromEnvSources } = require('../config/helper')
|
|
9
9
|
const Span = require('../opentracing/span')
|
|
10
|
-
const {
|
|
10
|
+
const {
|
|
11
|
+
SPAN_KIND,
|
|
12
|
+
OUTPUT_VALUE,
|
|
13
|
+
INPUT_VALUE,
|
|
14
|
+
LLMOBS_TRACE_ID_BRIDGE_KEY,
|
|
15
|
+
LLMOBS_PARENT_ID_BRIDGE_KEY,
|
|
16
|
+
} = require('./constants/tags')
|
|
11
17
|
const {
|
|
12
18
|
getFunctionArguments,
|
|
13
19
|
validateKind,
|
|
@@ -427,7 +433,7 @@ class LLMObs extends NoopLLMObs {
|
|
|
427
433
|
}
|
|
428
434
|
|
|
429
435
|
// When OTel tracing is enabled, add source:otel tag to allow backend to wait for OTel span conversion
|
|
430
|
-
if (
|
|
436
|
+
if (this._config.DD_TRACE_OTEL_ENABLED) {
|
|
431
437
|
evaluationTags.source = 'otel'
|
|
432
438
|
}
|
|
433
439
|
|
|
@@ -533,6 +539,20 @@ class LLMObs extends NoopLLMObs {
|
|
|
533
539
|
...options,
|
|
534
540
|
parent: parentStore?.span,
|
|
535
541
|
})
|
|
542
|
+
|
|
543
|
+
// Bridge tags read by the dd-go LLMObs trace-indexer to correlate OTel
|
|
544
|
+
// gen_ai.* spans with SDK LLMObs spans. Written once per local trace,
|
|
545
|
+
// on the first successful SDK LLMObs span registration. The shared
|
|
546
|
+
// _trace.tags bag is serialized to the first span in every flushed
|
|
547
|
+
// chunk's meta, so partial flush is covered automatically without a
|
|
548
|
+
// separate flush-time processor. Writing only after registerLLMObsSpan
|
|
549
|
+
// succeeds avoids poisoning _trace.tags with bridge tags pointing at a
|
|
550
|
+
// span that will never produce an LLMObs event.
|
|
551
|
+
const traceTags = span?.context?.()._trace?.tags
|
|
552
|
+
if (this.enabled && traceTags && !traceTags[LLMOBS_TRACE_ID_BRIDGE_KEY]) {
|
|
553
|
+
traceTags[LLMOBS_TRACE_ID_BRIDGE_KEY] = span.context().toTraceId(true)
|
|
554
|
+
traceTags[LLMOBS_PARENT_ID_BRIDGE_KEY] = span.context().toSpanId()
|
|
555
|
+
}
|
|
536
556
|
}
|
|
537
557
|
|
|
538
558
|
try {
|
|
@@ -30,6 +30,7 @@ const {
|
|
|
30
30
|
INPUT_PROMPT,
|
|
31
31
|
ROUTING_API_KEY,
|
|
32
32
|
ROUTING_SITE,
|
|
33
|
+
LLMOBS_SUBMITTED_TAG_KEY,
|
|
33
34
|
} = require('./constants/tags')
|
|
34
35
|
const { UNSERIALIZABLE_VALUE_TEXT } = require('./constants/text')
|
|
35
36
|
const telemetry = require('./telemetry')
|
|
@@ -87,7 +88,19 @@ class LLMObsSpanProcessor {
|
|
|
87
88
|
site: mlObsTags[ROUTING_SITE],
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
this.#writer.append(formattedEvent, routing)
|
|
91
|
+
const enqueued = this.#writer.append(formattedEvent, routing)
|
|
92
|
+
|
|
93
|
+
// Marker read by the dd-go LLMObs trace-indexer: when reparenting OTel
|
|
94
|
+
// gen_ai.* spans, the parent-chain walk stops at any span carrying this
|
|
95
|
+
// tag, preserving this span as the immediate LLMObs parent. Set only
|
|
96
|
+
// when the writer actually buffered the event — format may have dropped
|
|
97
|
+
// it (user processor returned null), thrown, or the writer may have
|
|
98
|
+
// dropped it silently when its buffer is full. Leaving this tag off in
|
|
99
|
+
// those cases avoids dd-go reparenting OTel children under a span that
|
|
100
|
+
// has no corresponding LLMObs event.
|
|
101
|
+
if (enqueued) {
|
|
102
|
+
span.context()._tags[LLMOBS_SUBMITTED_TAG_KEY] = '1'
|
|
103
|
+
}
|
|
91
104
|
} catch (e) {
|
|
92
105
|
// this should be a rare case
|
|
93
106
|
// we protect against unserializable properties in the format function, and in
|
|
@@ -87,19 +87,25 @@ class BaseLLMObsWriter {
|
|
|
87
87
|
return buffer
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/**
|
|
91
|
+
* @returns {boolean} `true` if the event was buffered, `false` if it was dropped
|
|
92
|
+
* (e.g. the per-routing buffer was full). Callers that depend on the event
|
|
93
|
+
* actually being submitted should check this value.
|
|
94
|
+
*/
|
|
90
95
|
append (event, routing, byteLength) {
|
|
91
96
|
const buffer = this._getBuffer(routing)
|
|
92
97
|
|
|
93
98
|
if (buffer.events.length >= buffer.limit) {
|
|
94
99
|
logger.warn(`${this.constructor.name} event buffer full (limit is ${buffer.limit}), dropping event`)
|
|
95
100
|
telemetry.recordDroppedPayload(1, this._eventType, 'buffer_full')
|
|
96
|
-
return
|
|
101
|
+
return false
|
|
97
102
|
}
|
|
98
103
|
|
|
99
104
|
const eventSize = byteLength || Buffer.byteLength(JSON.stringify(event))
|
|
100
105
|
|
|
101
106
|
buffer.size += eventSize
|
|
102
107
|
buffer.events.push(event)
|
|
108
|
+
return true
|
|
103
109
|
}
|
|
104
110
|
|
|
105
111
|
flush () {
|