dd-trace 5.31.0 → 5.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-3rdparty.csv +1 -0
- package/README.md +17 -14
- package/index.d.ts +11 -1
- package/package.json +6 -5
- package/packages/datadog-instrumentations/src/aws-sdk.js +4 -1
- package/packages/datadog-instrumentations/src/cucumber.js +31 -14
- package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
- package/packages/datadog-instrumentations/src/jest.js +105 -56
- package/packages/datadog-instrumentations/src/mocha/main.js +9 -4
- package/packages/datadog-instrumentations/src/mocha/utils.js +27 -9
- package/packages/datadog-instrumentations/src/mocha/worker.js +4 -2
- package/packages/datadog-instrumentations/src/node-serialize.js +22 -0
- package/packages/datadog-instrumentations/src/openai.js +2 -0
- package/packages/datadog-instrumentations/src/playwright.js +8 -3
- package/packages/datadog-instrumentations/src/vitest.js +134 -62
- package/packages/datadog-instrumentations/src/vm.js +49 -0
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/index.js +16 -0
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/tracing.js +63 -0
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +287 -0
- package/packages/datadog-plugin-aws-sdk/src/services/index.js +1 -0
- package/packages/datadog-plugin-cucumber/src/index.js +31 -31
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -8
- package/packages/datadog-plugin-cypress/src/support.js +6 -2
- package/packages/datadog-plugin-fetch/src/index.js +3 -3
- package/packages/datadog-plugin-http/src/client.js +5 -33
- package/packages/datadog-plugin-jest/src/index.js +37 -37
- package/packages/datadog-plugin-langchain/src/index.js +12 -80
- package/packages/datadog-plugin-langchain/src/tracing.js +89 -0
- package/packages/datadog-plugin-mocha/src/index.js +19 -35
- package/packages/datadog-plugin-playwright/src/index.js +3 -1
- package/packages/datadog-plugin-vitest/src/index.js +33 -35
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +2 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +3 -3
- package/packages/dd-trace/src/appsec/iast/analyzers/untrusted-deserialization-analyzer.js +16 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +41 -24
- package/packages/dd-trace/src/appsec/iast/iast-context.js +12 -0
- package/packages/dd-trace/src/appsec/iast/path-line.js +19 -23
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +9 -8
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +1 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +75 -24
- package/packages/dd-trace/src/appsec/rasp/utils.js +10 -5
- package/packages/dd-trace/src/appsec/stack_trace.js +38 -28
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +37 -0
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +65 -28
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +57 -17
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +5 -4
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +18 -3
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +5 -3
- package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +20 -3
- package/packages/dd-trace/src/config.js +43 -3
- package/packages/dd-trace/src/crashtracking/crashtracker.js +9 -0
- package/packages/dd-trace/src/crashtracking/noop.js +3 -0
- package/packages/dd-trace/src/datastreams/fnv.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -2
- package/packages/dd-trace/src/debugger/devtools_client/config.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/defaults.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +30 -13
- package/packages/dd-trace/src/debugger/devtools_client/send.js +4 -8
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +35 -1
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +112 -0
- package/packages/dd-trace/src/debugger/devtools_client/status.js +12 -10
- package/packages/dd-trace/src/debugger/index.js +2 -13
- package/packages/dd-trace/src/llmobs/plugins/base.js +40 -11
- package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +59 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chain.js +24 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +111 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/embedding.js +42 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +102 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/llm.js +32 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +131 -0
- package/packages/dd-trace/src/llmobs/plugins/openai.js +1 -1
- package/packages/dd-trace/src/llmobs/tagger.js +11 -3
- package/packages/dd-trace/src/llmobs/util.js +7 -1
- package/packages/dd-trace/src/llmobs/writers/spans/agentProxy.js +3 -3
- package/packages/dd-trace/src/opentelemetry/context_manager.js +43 -3
- package/packages/dd-trace/src/plugins/ci_plugin.js +58 -27
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +0 -2
- package/packages/dd-trace/src/plugins/util/test.js +44 -12
- package/packages/dd-trace/src/priority_sampler.js +4 -1
- package/packages/dd-trace/src/profiling/exporters/event_serializer.js +21 -0
- package/packages/dd-trace/src/profiling/profiler.js +11 -8
- package/packages/dd-trace/src/profiling/profilers/events.js +17 -1
- package/packages/dd-trace/src/proxy.js +6 -3
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const LangChainLLMObsHandler = require('.')
|
|
4
|
+
const LLMObsTagger = require('../../../tagger')
|
|
5
|
+
const { spanHasError } = require('../../../util')
|
|
6
|
+
|
|
7
|
+
const LLM = 'llm'
|
|
8
|
+
|
|
9
|
+
class LangChainLLMObsChatModelHandler extends LangChainLLMObsHandler {
|
|
10
|
+
setMetaTags ({ span, inputs, results, options, integrationName }) {
|
|
11
|
+
if (integrationName === 'openai' && options?.response_format) {
|
|
12
|
+
// langchain-openai will call a beta client if "response_format" is passed in on the options object
|
|
13
|
+
// we do not trace these calls, so this should be an llm span
|
|
14
|
+
this._tagger.changeKind(span, LLM)
|
|
15
|
+
}
|
|
16
|
+
const spanKind = LLMObsTagger.getSpanKind(span)
|
|
17
|
+
const isWorkflow = spanKind === 'workflow'
|
|
18
|
+
|
|
19
|
+
const inputMessages = []
|
|
20
|
+
if (!Array.isArray(inputs)) inputs = [inputs]
|
|
21
|
+
|
|
22
|
+
for (const messageSet of inputs) {
|
|
23
|
+
for (const message of messageSet) {
|
|
24
|
+
const content = message.content || ''
|
|
25
|
+
const role = this.getRole(message)
|
|
26
|
+
inputMessages.push({ content, role })
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (spanHasError(span)) {
|
|
31
|
+
if (isWorkflow) {
|
|
32
|
+
this._tagger.tagTextIO(span, inputMessages, [{ content: '' }])
|
|
33
|
+
} else {
|
|
34
|
+
this._tagger.tagLLMIO(span, inputMessages, [{ content: '' }])
|
|
35
|
+
}
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const outputMessages = []
|
|
40
|
+
let inputTokens = 0
|
|
41
|
+
let outputTokens = 0
|
|
42
|
+
let totalTokens = 0
|
|
43
|
+
let tokensSetTopLevel = false
|
|
44
|
+
const tokensPerRunId = {}
|
|
45
|
+
|
|
46
|
+
if (!isWorkflow) {
|
|
47
|
+
const tokens = this.checkTokenUsageChatOrLLMResult(results)
|
|
48
|
+
inputTokens = tokens.inputTokens
|
|
49
|
+
outputTokens = tokens.outputTokens
|
|
50
|
+
totalTokens = tokens.totalTokens
|
|
51
|
+
tokensSetTopLevel = totalTokens > 0
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (const messageSet of results.generations) {
|
|
55
|
+
for (const chatCompletion of messageSet) {
|
|
56
|
+
const chatCompletionMessage = chatCompletion.message
|
|
57
|
+
const role = this.getRole(chatCompletionMessage)
|
|
58
|
+
const content = chatCompletionMessage.text || ''
|
|
59
|
+
const toolCalls = this.extractToolCalls(chatCompletionMessage)
|
|
60
|
+
outputMessages.push({ content, role, toolCalls })
|
|
61
|
+
|
|
62
|
+
if (!isWorkflow && !tokensSetTopLevel) {
|
|
63
|
+
const { tokens, runId } = this.checkTokenUsageFromAIMessage(chatCompletionMessage)
|
|
64
|
+
if (!tokensPerRunId[runId]) {
|
|
65
|
+
tokensPerRunId[runId] = tokens
|
|
66
|
+
} else {
|
|
67
|
+
tokensPerRunId[runId].inputTokens += tokens.inputTokens
|
|
68
|
+
tokensPerRunId[runId].outputTokens += tokens.outputTokens
|
|
69
|
+
tokensPerRunId[runId].totalTokens += tokens.totalTokens
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!isWorkflow && !tokensSetTopLevel) {
|
|
76
|
+
inputTokens = Object.values(tokensPerRunId).reduce((acc, val) => acc + val.inputTokens, 0)
|
|
77
|
+
outputTokens = Object.values(tokensPerRunId).reduce((acc, val) => acc + val.outputTokens, 0)
|
|
78
|
+
totalTokens = Object.values(tokensPerRunId).reduce((acc, val) => acc + val.totalTokens, 0)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (isWorkflow) {
|
|
82
|
+
this._tagger.tagTextIO(span, inputMessages, outputMessages)
|
|
83
|
+
} else {
|
|
84
|
+
this._tagger.tagLLMIO(span, inputMessages, outputMessages)
|
|
85
|
+
this._tagger.tagMetrics(span, {
|
|
86
|
+
inputTokens,
|
|
87
|
+
outputTokens,
|
|
88
|
+
totalTokens
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
extractToolCalls (message) {
|
|
94
|
+
let toolCalls = message.tool_calls
|
|
95
|
+
if (!toolCalls) return []
|
|
96
|
+
|
|
97
|
+
const toolCallsInfo = []
|
|
98
|
+
if (!Array.isArray(toolCalls)) toolCalls = [toolCalls]
|
|
99
|
+
for (const toolCall of toolCalls) {
|
|
100
|
+
toolCallsInfo.push({
|
|
101
|
+
name: toolCall.name || '',
|
|
102
|
+
arguments: toolCall.args || {},
|
|
103
|
+
tool_id: toolCall.id || ''
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return toolCallsInfo
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = LangChainLLMObsChatModelHandler
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const LangChainLLMObsHandler = require('.')
|
|
4
|
+
const LLMObsTagger = require('../../../tagger')
|
|
5
|
+
const { spanHasError } = require('../../../util')
|
|
6
|
+
|
|
7
|
+
class LangChainLLMObsEmbeddingHandler extends LangChainLLMObsHandler {
|
|
8
|
+
setMetaTags ({ span, inputs, results }) {
|
|
9
|
+
const isWorkflow = LLMObsTagger.getSpanKind(span) === 'workflow'
|
|
10
|
+
let embeddingInput, embeddingOutput
|
|
11
|
+
|
|
12
|
+
if (isWorkflow) {
|
|
13
|
+
embeddingInput = this.formatIO(inputs)
|
|
14
|
+
} else {
|
|
15
|
+
const input = Array.isArray(inputs) ? inputs : [inputs]
|
|
16
|
+
embeddingInput = input.map(doc => ({ text: doc }))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (spanHasError(span) || !results) {
|
|
20
|
+
embeddingOutput = ''
|
|
21
|
+
} else {
|
|
22
|
+
let embeddingDimensions, embeddingsCount
|
|
23
|
+
if (typeof results[0] === 'number') {
|
|
24
|
+
embeddingsCount = 1
|
|
25
|
+
embeddingDimensions = results.length
|
|
26
|
+
} else {
|
|
27
|
+
embeddingsCount = results.length
|
|
28
|
+
embeddingDimensions = results[0].length
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
embeddingOutput = `[${embeddingsCount} embedding(s) returned with size ${embeddingDimensions}]`
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (isWorkflow) {
|
|
35
|
+
this._tagger.tagTextIO(span, embeddingInput, embeddingOutput)
|
|
36
|
+
} else {
|
|
37
|
+
this._tagger.tagEmbeddingIO(span, embeddingInput, embeddingOutput)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = LangChainLLMObsEmbeddingHandler
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const ROLE_MAPPINGS = {
|
|
4
|
+
human: 'user',
|
|
5
|
+
ai: 'assistant',
|
|
6
|
+
system: 'system'
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
class LangChainLLMObsHandler {
|
|
10
|
+
constructor (tagger) {
|
|
11
|
+
this._tagger = tagger
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
setMetaTags () {}
|
|
15
|
+
|
|
16
|
+
formatIO (messages) {
|
|
17
|
+
if (messages.constructor.name === 'Object') { // plain JSON
|
|
18
|
+
const formatted = {}
|
|
19
|
+
for (const [key, value] of Object.entries(messages)) {
|
|
20
|
+
formatted[key] = this.formatIO(value)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return formatted
|
|
24
|
+
} else if (Array.isArray(messages)) {
|
|
25
|
+
return messages.map(message => this.formatIO(message))
|
|
26
|
+
} else { // either a BaseMesage type or a string
|
|
27
|
+
return this.getContentFromMessage(messages)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
getContentFromMessage (message) {
|
|
32
|
+
if (typeof message === 'string') {
|
|
33
|
+
return message
|
|
34
|
+
} else {
|
|
35
|
+
try {
|
|
36
|
+
const messageContent = {}
|
|
37
|
+
messageContent.content = message.content || ''
|
|
38
|
+
|
|
39
|
+
const role = this.getRole(message)
|
|
40
|
+
if (role) messageContent.role = role
|
|
41
|
+
|
|
42
|
+
return messageContent
|
|
43
|
+
} catch {
|
|
44
|
+
return JSON.stringify(message)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
checkTokenUsageChatOrLLMResult (results) {
|
|
50
|
+
const llmOutput = results.llmOutput
|
|
51
|
+
const tokens = {
|
|
52
|
+
inputTokens: 0,
|
|
53
|
+
outputTokens: 0,
|
|
54
|
+
totalTokens: 0
|
|
55
|
+
}
|
|
56
|
+
if (!llmOutput) return tokens
|
|
57
|
+
const tokenUsage = llmOutput.tokenUsage || llmOutput.usageMetadata || llmOutput.usage || {}
|
|
58
|
+
if (!tokenUsage) return tokens
|
|
59
|
+
|
|
60
|
+
tokens.inputTokens = tokenUsage.promptTokens || tokenUsage.inputTokens || 0
|
|
61
|
+
tokens.outputTokens = tokenUsage.completionTokens || tokenUsage.outputTokens || 0
|
|
62
|
+
tokens.totalTokens = tokenUsage.totalTokens || tokens.inputTokens + tokens.outputTokens
|
|
63
|
+
|
|
64
|
+
return tokens
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
checkTokenUsageFromAIMessage (message) {
|
|
68
|
+
let usage = message.usage_metadata || message.additional_kwargs?.usage
|
|
69
|
+
const runId = message.run_id || message.id || ''
|
|
70
|
+
const runIdBase = runId ? runId.split('-').slice(0, -1).join('-') : ''
|
|
71
|
+
|
|
72
|
+
const responseMetadata = message.response_metadata || {}
|
|
73
|
+
usage = usage || responseMetadata.usage || responseMetadata.tokenUsage || {}
|
|
74
|
+
|
|
75
|
+
const inputTokens = usage.promptTokens || usage.inputTokens || usage.prompt_tokens || usage.input_tokens || 0
|
|
76
|
+
const outputTokens =
|
|
77
|
+
usage.completionTokens || usage.outputTokens || usage.completion_tokens || usage.output_tokens || 0
|
|
78
|
+
const totalTokens = usage.totalTokens || inputTokens + outputTokens
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
tokens: {
|
|
82
|
+
inputTokens,
|
|
83
|
+
outputTokens,
|
|
84
|
+
totalTokens
|
|
85
|
+
},
|
|
86
|
+
runId: runIdBase
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
getRole (message) {
|
|
91
|
+
if (message.role) return ROLE_MAPPINGS[message.role] || message.role
|
|
92
|
+
|
|
93
|
+
const type = (
|
|
94
|
+
(typeof message.getType === 'function' && message.getType()) ||
|
|
95
|
+
(typeof message._getType === 'function' && message._getType())
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
return ROLE_MAPPINGS[type] || type
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = LangChainLLMObsHandler
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const LangChainLLMObsHandler = require('.')
|
|
4
|
+
const LLMObsTagger = require('../../../tagger')
|
|
5
|
+
const { spanHasError } = require('../../../util')
|
|
6
|
+
|
|
7
|
+
class LangChainLLMObsLlmHandler extends LangChainLLMObsHandler {
|
|
8
|
+
setMetaTags ({ span, inputs, results }) {
|
|
9
|
+
const isWorkflow = LLMObsTagger.getSpanKind(span) === 'workflow'
|
|
10
|
+
const prompts = Array.isArray(inputs) ? inputs : [inputs]
|
|
11
|
+
|
|
12
|
+
let outputs
|
|
13
|
+
if (spanHasError(span)) {
|
|
14
|
+
outputs = [{ content: '' }]
|
|
15
|
+
} else {
|
|
16
|
+
outputs = results.generations.map(completion => ({ content: completion[0].text }))
|
|
17
|
+
|
|
18
|
+
if (!isWorkflow) {
|
|
19
|
+
const tokens = this.checkTokenUsageChatOrLLMResult(results)
|
|
20
|
+
this._tagger.tagMetrics(span, tokens)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (isWorkflow) {
|
|
25
|
+
this._tagger.tagTextIO(span, prompts, outputs)
|
|
26
|
+
} else {
|
|
27
|
+
this._tagger.tagLLMIO(span, prompts, outputs)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = LangChainLLMObsLlmHandler
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const log = require('../../../log')
|
|
4
|
+
const LLMObsPlugin = require('../base')
|
|
5
|
+
|
|
6
|
+
const pluginManager = require('../../../../../..')._pluginManager
|
|
7
|
+
|
|
8
|
+
const ANTHROPIC_PROVIDER_NAME = 'anthropic'
|
|
9
|
+
const BEDROCK_PROVIDER_NAME = 'amazon_bedrock'
|
|
10
|
+
const OPENAI_PROVIDER_NAME = 'openai'
|
|
11
|
+
|
|
12
|
+
const SUPPORTED_INTEGRATIONS = ['openai']
|
|
13
|
+
const LLM_SPAN_TYPES = ['llm', 'chat_model', 'embedding']
|
|
14
|
+
const LLM = 'llm'
|
|
15
|
+
const WORKFLOW = 'workflow'
|
|
16
|
+
const EMBEDDING = 'embedding'
|
|
17
|
+
|
|
18
|
+
const ChainHandler = require('./handlers/chain')
|
|
19
|
+
const ChatModelHandler = require('./handlers/chat_model')
|
|
20
|
+
const LlmHandler = require('./handlers/llm')
|
|
21
|
+
const EmbeddingHandler = require('./handlers/embedding')
|
|
22
|
+
|
|
23
|
+
class LangChainLLMObsPlugin extends LLMObsPlugin {
|
|
24
|
+
static get prefix () {
|
|
25
|
+
return 'tracing:apm:langchain:invoke'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
constructor () {
|
|
29
|
+
super(...arguments)
|
|
30
|
+
|
|
31
|
+
this._handlers = {
|
|
32
|
+
chain: new ChainHandler(this._tagger),
|
|
33
|
+
chat_model: new ChatModelHandler(this._tagger),
|
|
34
|
+
llm: new LlmHandler(this._tagger),
|
|
35
|
+
embedding: new EmbeddingHandler(this._tagger)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getLLMObsSpanRegisterOptions (ctx) {
|
|
40
|
+
const span = ctx.currentStore?.span
|
|
41
|
+
const tags = span?.context()._tags || {}
|
|
42
|
+
|
|
43
|
+
const modelProvider = tags['langchain.request.provider'] // could be undefined
|
|
44
|
+
const modelName = tags['langchain.request.model'] // could be undefined
|
|
45
|
+
const kind = this.getKind(ctx.type, modelProvider)
|
|
46
|
+
const name = tags['resource.name']
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
modelProvider,
|
|
50
|
+
modelName,
|
|
51
|
+
kind,
|
|
52
|
+
name
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
setLLMObsTags (ctx) {
|
|
57
|
+
const span = ctx.currentStore?.span
|
|
58
|
+
const type = ctx.type // langchain operation type (oneof chain,chat_model,llm,embedding)
|
|
59
|
+
|
|
60
|
+
if (!Object.keys(this._handlers).includes(type)) {
|
|
61
|
+
log.warn(`Unsupported LangChain operation type: ${type}`)
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const provider = span?.context()._tags['langchain.request.provider']
|
|
66
|
+
const integrationName = this.getIntegrationName(type, provider)
|
|
67
|
+
this.setMetadata(span, provider)
|
|
68
|
+
|
|
69
|
+
const inputs = ctx.args?.[0]
|
|
70
|
+
const options = ctx.args?.[1]
|
|
71
|
+
const results = ctx.result
|
|
72
|
+
|
|
73
|
+
this._handlers[type].setMetaTags({ span, inputs, results, options, integrationName })
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
setMetadata (span, provider) {
|
|
77
|
+
if (!provider) return
|
|
78
|
+
|
|
79
|
+
const metadata = {}
|
|
80
|
+
|
|
81
|
+
// these fields won't be set for non model-based operations
|
|
82
|
+
const temperature =
|
|
83
|
+
span?.context()._tags[`langchain.request.${provider}.parameters.temperature`] ||
|
|
84
|
+
span?.context()._tags[`langchain.request.${provider}.parameters.model_kwargs.temperature`]
|
|
85
|
+
|
|
86
|
+
const maxTokens =
|
|
87
|
+
span?.context()._tags[`langchain.request.${provider}.parameters.max_tokens`] ||
|
|
88
|
+
span?.context()._tags[`langchain.request.${provider}.parameters.maxTokens`] ||
|
|
89
|
+
span?.context()._tags[`langchain.request.${provider}.parameters.model_kwargs.max_tokens`]
|
|
90
|
+
|
|
91
|
+
if (temperature) {
|
|
92
|
+
metadata.temperature = parseFloat(temperature)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (maxTokens) {
|
|
96
|
+
metadata.maxTokens = parseInt(maxTokens)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this._tagger.tagMetadata(span, metadata)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
getKind (type, provider) {
|
|
103
|
+
if (LLM_SPAN_TYPES.includes(type)) {
|
|
104
|
+
const llmobsIntegration = this.getIntegrationName(type, provider)
|
|
105
|
+
|
|
106
|
+
if (!this.isLLMIntegrationEnabled(llmobsIntegration)) {
|
|
107
|
+
return type === 'embedding' ? EMBEDDING : LLM
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return WORKFLOW
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
getIntegrationName (type, provider = 'custom') {
|
|
115
|
+
if (provider.startsWith(BEDROCK_PROVIDER_NAME)) {
|
|
116
|
+
return 'bedrock'
|
|
117
|
+
} else if (provider.startsWith(OPENAI_PROVIDER_NAME)) {
|
|
118
|
+
return 'openai'
|
|
119
|
+
} else if (type === 'chat_model' && provider.startsWith(ANTHROPIC_PROVIDER_NAME)) {
|
|
120
|
+
return 'anthropic'
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return provider
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
isLLMIntegrationEnabled (integration) {
|
|
127
|
+
return SUPPORTED_INTEGRATIONS.includes(integration) && pluginManager?._pluginsByName[integration]?.llmobs?._enabled
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
module.exports = LangChainLLMObsPlugin
|
|
@@ -7,7 +7,7 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
|
|
|
7
7
|
return 'tracing:apm:openai:request'
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
getLLMObsSpanRegisterOptions (ctx) {
|
|
11
11
|
const resource = ctx.methodName
|
|
12
12
|
const methodName = gateResource(normalizeOpenAIResourceName(resource))
|
|
13
13
|
if (!methodName) return // we will not trace all openai methods for llmobs
|
|
@@ -40,6 +40,10 @@ class LLMObsTagger {
|
|
|
40
40
|
return registry
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
static getSpanKind (span) {
|
|
44
|
+
return registry.get(span)?.[SPAN_KIND]
|
|
45
|
+
}
|
|
46
|
+
|
|
43
47
|
registerLLMObsSpan (span, {
|
|
44
48
|
modelName,
|
|
45
49
|
modelProvider,
|
|
@@ -60,10 +64,10 @@ class LLMObsTagger {
|
|
|
60
64
|
if (modelName) this._setTag(span, MODEL_NAME, modelName)
|
|
61
65
|
if (modelProvider) this._setTag(span, MODEL_PROVIDER, modelProvider)
|
|
62
66
|
|
|
63
|
-
sessionId = sessionId || parent?.
|
|
67
|
+
sessionId = sessionId || registry.get(parent)?.[SESSION_ID]
|
|
64
68
|
if (sessionId) this._setTag(span, SESSION_ID, sessionId)
|
|
65
69
|
|
|
66
|
-
if (!mlApp) mlApp = parent?.
|
|
70
|
+
if (!mlApp) mlApp = registry.get(parent)?.[ML_APP] || this._config.llmobs.mlApp
|
|
67
71
|
this._setTag(span, ML_APP, mlApp)
|
|
68
72
|
|
|
69
73
|
const parentId =
|
|
@@ -136,6 +140,10 @@ class LLMObsTagger {
|
|
|
136
140
|
this._setTag(span, TAGS, tags)
|
|
137
141
|
}
|
|
138
142
|
|
|
143
|
+
changeKind (span, newKind) {
|
|
144
|
+
this._setTag(span, SPAN_KIND, newKind)
|
|
145
|
+
}
|
|
146
|
+
|
|
139
147
|
_tagText (span, data, key) {
|
|
140
148
|
if (data) {
|
|
141
149
|
if (typeof data === 'string') {
|
|
@@ -310,7 +318,7 @@ class LLMObsTagger {
|
|
|
310
318
|
_setTag (span, key, value) {
|
|
311
319
|
if (!this._config.llmobs.enabled) return
|
|
312
320
|
if (!registry.has(span)) {
|
|
313
|
-
this._handleFailure(
|
|
321
|
+
this._handleFailure(`Span "${span._name}" must be an LLMObs generated span.`)
|
|
314
322
|
return
|
|
315
323
|
}
|
|
316
324
|
|
|
@@ -169,8 +169,14 @@ function getFunctionArguments (fn, args = []) {
|
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
function spanHasError (span) {
|
|
173
|
+
const tags = span.context()._tags
|
|
174
|
+
return !!(tags.error || tags['error.type'])
|
|
175
|
+
}
|
|
176
|
+
|
|
172
177
|
module.exports = {
|
|
173
178
|
encodeUnicode,
|
|
174
179
|
validateKind,
|
|
175
|
-
getFunctionArguments
|
|
180
|
+
getFunctionArguments,
|
|
181
|
+
spanHasError
|
|
176
182
|
}
|
|
@@ -10,10 +10,10 @@ const LLMObsBaseSpanWriter = require('./base')
|
|
|
10
10
|
class LLMObsAgentProxySpanWriter extends LLMObsBaseSpanWriter {
|
|
11
11
|
constructor (config) {
|
|
12
12
|
super({
|
|
13
|
-
intake: config.hostname || 'localhost',
|
|
14
|
-
protocol: 'http:',
|
|
13
|
+
intake: config.url?.hostname || config.hostname || 'localhost',
|
|
14
|
+
protocol: config.url?.protocol || 'http:',
|
|
15
15
|
endpoint: EVP_PROXY_AGENT_ENDPOINT,
|
|
16
|
-
port: config.port
|
|
16
|
+
port: config.url?.port || config.port
|
|
17
17
|
})
|
|
18
18
|
|
|
19
19
|
this._headers[EVP_SUBDOMAIN_HEADER_NAME] = EVP_SUBDOMAIN_HEADER_VALUE
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { storage } = require('../../../datadog-core')
|
|
4
|
-
const { trace, ROOT_CONTEXT } = require('@opentelemetry/api')
|
|
4
|
+
const { trace, ROOT_CONTEXT, propagation } = require('@opentelemetry/api')
|
|
5
5
|
const DataDogSpanContext = require('../opentracing/span_context')
|
|
6
6
|
|
|
7
7
|
const SpanContext = require('./span_context')
|
|
@@ -18,17 +18,40 @@ class ContextManager {
|
|
|
18
18
|
const context = (activeSpan && activeSpan.context()) || store || ROOT_CONTEXT
|
|
19
19
|
|
|
20
20
|
if (!(context instanceof DataDogSpanContext)) {
|
|
21
|
+
const span = trace.getSpan(context)
|
|
22
|
+
// span instanceof NonRecordingSpan
|
|
23
|
+
if (span && span._spanContext && span._spanContext._ddContext && span._spanContext._ddContext._baggageItems) {
|
|
24
|
+
const baggages = span._spanContext._ddContext._baggageItems
|
|
25
|
+
const entries = {}
|
|
26
|
+
for (const [key, value] of Object.entries(baggages)) {
|
|
27
|
+
entries[key] = { value }
|
|
28
|
+
}
|
|
29
|
+
const otelBaggages = propagation.createBaggage(entries)
|
|
30
|
+
return propagation.setBaggage(context, otelBaggages)
|
|
31
|
+
}
|
|
21
32
|
return context
|
|
22
33
|
}
|
|
23
34
|
|
|
35
|
+
const baggages = JSON.parse(activeSpan.getAllBaggageItems())
|
|
36
|
+
const entries = {}
|
|
37
|
+
for (const [key, value] of Object.entries(baggages)) {
|
|
38
|
+
entries[key] = { value }
|
|
39
|
+
}
|
|
40
|
+
const otelBaggages = propagation.createBaggage(entries)
|
|
41
|
+
|
|
24
42
|
if (!context._otelSpanContext) {
|
|
25
43
|
const newSpanContext = new SpanContext(context)
|
|
26
44
|
context._otelSpanContext = newSpanContext
|
|
27
45
|
}
|
|
28
46
|
if (store && trace.getSpanContext(store) === context._otelSpanContext) {
|
|
29
|
-
return
|
|
47
|
+
return otelBaggages
|
|
48
|
+
? propagation.setBaggage(store, otelBaggages)
|
|
49
|
+
: store
|
|
30
50
|
}
|
|
31
|
-
|
|
51
|
+
const wrappedContext = trace.setSpanContext(store || ROOT_CONTEXT, context._otelSpanContext)
|
|
52
|
+
return otelBaggages
|
|
53
|
+
? propagation.setBaggage(wrappedContext, otelBaggages)
|
|
54
|
+
: wrappedContext
|
|
32
55
|
}
|
|
33
56
|
|
|
34
57
|
with (context, fn, thisArg, ...args) {
|
|
@@ -38,9 +61,26 @@ class ContextManager {
|
|
|
38
61
|
const cb = thisArg == null ? fn : fn.bind(thisArg)
|
|
39
62
|
return this._store.run(context, cb, ...args)
|
|
40
63
|
}
|
|
64
|
+
const baggages = propagation.getBaggage(context)
|
|
65
|
+
let baggageItems = []
|
|
66
|
+
if (baggages) {
|
|
67
|
+
baggageItems = baggages.getAllEntries()
|
|
68
|
+
}
|
|
41
69
|
if (span && span._ddSpan) {
|
|
70
|
+
// does otel always override datadog?
|
|
71
|
+
span._ddSpan.removeAllBaggageItems()
|
|
72
|
+
for (const baggage of baggageItems) {
|
|
73
|
+
span._ddSpan.setBaggageItem(baggage[0], baggage[1].value)
|
|
74
|
+
}
|
|
42
75
|
return ddScope.activate(span._ddSpan, run)
|
|
43
76
|
}
|
|
77
|
+
// span instanceof NonRecordingSpan
|
|
78
|
+
if (span && span._spanContext && span._spanContext._ddContext && span._spanContext._ddContext._baggageItems) {
|
|
79
|
+
span._spanContext._ddContext._baggageItems = {}
|
|
80
|
+
for (const baggage of baggageItems) {
|
|
81
|
+
span._spanContext._ddContext._baggageItems[baggage[0]] = baggage[1].value
|
|
82
|
+
}
|
|
83
|
+
}
|
|
44
84
|
return run()
|
|
45
85
|
}
|
|
46
86
|
|