dd-trace 5.30.0 → 5.32.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 +9 -7
- package/package.json +7 -6
- package/packages/datadog-core/src/storage.js +11 -2
- package/packages/datadog-instrumentations/src/aerospike.js +1 -1
- package/packages/datadog-instrumentations/src/aws-sdk.js +2 -1
- package/packages/datadog-instrumentations/src/cucumber.js +14 -5
- package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
- package/packages/datadog-instrumentations/src/jest.js +70 -36
- package/packages/datadog-instrumentations/src/mocha/utils.js +23 -7
- package/packages/datadog-instrumentations/src/node-serialize.js +22 -0
- package/packages/datadog-instrumentations/src/openai.js +2 -0
- package/packages/datadog-instrumentations/src/vitest.js +107 -59
- package/packages/datadog-instrumentations/src/vm.js +49 -0
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime.js +295 -0
- package/packages/datadog-plugin-aws-sdk/src/services/index.js +1 -0
- package/packages/datadog-plugin-cucumber/src/index.js +30 -32
- package/packages/datadog-plugin-jest/src/index.js +34 -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 +18 -36
- package/packages/datadog-plugin-vitest/src/index.js +20 -34
- 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/untrusted-deserialization-analyzer.js +16 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +9 -8
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
- package/packages/dd-trace/src/appsec/remote_config/manager.js +11 -1
- 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/test-worker/index.js +18 -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 +39 -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 +3 -1
- package/packages/dd-trace/src/debugger/devtools_client/defaults.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +32 -14
- package/packages/dd-trace/src/debugger/devtools_client/json-buffer.js +36 -0
- package/packages/dd-trace/src/debugger/devtools_client/send.js +29 -10
- 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 +20 -11
- 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/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/sdk.js +90 -26
- 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/log/index.js +8 -9
- package/packages/dd-trace/src/noop/proxy.js +2 -2
- package/packages/dd-trace/src/noop/span.js +1 -1
- package/packages/dd-trace/src/opentelemetry/context_manager.js +43 -3
- package/packages/dd-trace/src/opentracing/span.js +11 -1
- package/packages/dd-trace/src/opentracing/span_context.js +12 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +57 -27
- package/packages/dd-trace/src/plugins/util/test.js +42 -12
- package/packages/dd-trace/src/priority_sampler.js +7 -2
- 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
- package/packages/dd-trace/src/scope.js +1 -1
- package/packages/dd-trace/src/telemetry/index.js +2 -0
|
@@ -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
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { SPAN_KIND, OUTPUT_VALUE } = require('./constants/tags')
|
|
3
|
+
const { SPAN_KIND, OUTPUT_VALUE, INPUT_VALUE } = require('./constants/tags')
|
|
4
4
|
|
|
5
5
|
const {
|
|
6
6
|
getFunctionArguments,
|
|
7
7
|
validateKind
|
|
8
8
|
} = require('./util')
|
|
9
|
-
const { isTrue } = require('../util')
|
|
9
|
+
const { isTrue, isError } = require('../util')
|
|
10
10
|
|
|
11
11
|
const { storage } = require('./storage')
|
|
12
12
|
|
|
@@ -134,29 +134,63 @@ class LLMObs extends NoopLLMObs {
|
|
|
134
134
|
|
|
135
135
|
function wrapped () {
|
|
136
136
|
const span = llmobs._tracer.scope().active()
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
const fnArgs = arguments
|
|
138
|
+
|
|
139
|
+
const lastArgId = fnArgs.length - 1
|
|
140
|
+
const cb = fnArgs[lastArgId]
|
|
141
|
+
const hasCallback = typeof cb === 'function'
|
|
142
|
+
|
|
143
|
+
if (hasCallback) {
|
|
144
|
+
const scopeBoundCb = llmobs._bind(cb)
|
|
145
|
+
fnArgs[lastArgId] = function () {
|
|
146
|
+
// it is standard practice to follow the callback signature (err, result)
|
|
147
|
+
// however, we try to parse the arguments to determine if the first argument is an error
|
|
148
|
+
// if it is not, and is not undefined, we will use that for the output value
|
|
149
|
+
const maybeError = arguments[0]
|
|
150
|
+
const maybeResult = arguments[1]
|
|
151
|
+
|
|
152
|
+
llmobs._autoAnnotate(
|
|
153
|
+
span,
|
|
154
|
+
kind,
|
|
155
|
+
getFunctionArguments(fn, fnArgs),
|
|
156
|
+
isError(maybeError) || maybeError == null ? maybeResult : maybeError
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
return scopeBoundCb.apply(this, arguments)
|
|
141
160
|
}
|
|
161
|
+
}
|
|
142
162
|
|
|
143
|
-
|
|
144
|
-
|
|
163
|
+
try {
|
|
164
|
+
const result = llmobs._activate(span, { kind, options: llmobsOptions }, () => fn.apply(this, fnArgs))
|
|
165
|
+
|
|
166
|
+
if (result && typeof result.then === 'function') {
|
|
167
|
+
return result.then(
|
|
168
|
+
value => {
|
|
169
|
+
if (!hasCallback) {
|
|
170
|
+
llmobs._autoAnnotate(span, kind, getFunctionArguments(fn, fnArgs), value)
|
|
171
|
+
}
|
|
172
|
+
return value
|
|
173
|
+
},
|
|
174
|
+
err => {
|
|
175
|
+
llmobs._autoAnnotate(span, kind, getFunctionArguments(fn, fnArgs))
|
|
176
|
+
throw err
|
|
177
|
+
}
|
|
178
|
+
)
|
|
179
|
+
}
|
|
145
180
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
}
|
|
181
|
+
// it is possible to return a value and have a callback
|
|
182
|
+
// however, since the span finishes when the callback is called, it is possible that
|
|
183
|
+
// the callback is called before the function returns (although unlikely)
|
|
184
|
+
// we do not want to throw for "annotating a finished span" in this case
|
|
185
|
+
if (!hasCallback) {
|
|
186
|
+
llmobs._autoAnnotate(span, kind, getFunctionArguments(fn, fnArgs), result)
|
|
187
|
+
}
|
|
154
188
|
|
|
155
|
-
|
|
156
|
-
|
|
189
|
+
return result
|
|
190
|
+
} catch (e) {
|
|
191
|
+
llmobs._autoAnnotate(span, kind, getFunctionArguments(fn, fnArgs))
|
|
192
|
+
throw e
|
|
157
193
|
}
|
|
158
|
-
|
|
159
|
-
return result
|
|
160
194
|
}
|
|
161
195
|
|
|
162
196
|
return this._tracer.wrap(name, spanOptions, wrapped)
|
|
@@ -333,20 +367,34 @@ class LLMObs extends NoopLLMObs {
|
|
|
333
367
|
flushCh.publish()
|
|
334
368
|
}
|
|
335
369
|
|
|
370
|
+
_autoAnnotate (span, kind, input, output) {
|
|
371
|
+
const annotations = {}
|
|
372
|
+
if (input && !['llm', 'embedding'].includes(kind) && !LLMObsTagger.tagMap.get(span)?.[INPUT_VALUE]) {
|
|
373
|
+
annotations.inputData = input
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (output && !['llm', 'retrieval'].includes(kind) && !LLMObsTagger.tagMap.get(span)?.[OUTPUT_VALUE]) {
|
|
377
|
+
annotations.outputData = output
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
this.annotate(span, annotations)
|
|
381
|
+
}
|
|
382
|
+
|
|
336
383
|
_active () {
|
|
337
384
|
const store = storage.getStore()
|
|
338
385
|
return store?.span
|
|
339
386
|
}
|
|
340
387
|
|
|
341
|
-
_activate (span,
|
|
388
|
+
_activate (span, options, fn) {
|
|
342
389
|
const parent = this._active()
|
|
343
390
|
if (this.enabled) storage.enterWith({ span })
|
|
344
391
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
392
|
+
if (options) {
|
|
393
|
+
this._tagger.registerLLMObsSpan(span, {
|
|
394
|
+
...options,
|
|
395
|
+
parent
|
|
396
|
+
})
|
|
397
|
+
}
|
|
350
398
|
|
|
351
399
|
try {
|
|
352
400
|
return fn()
|
|
@@ -355,6 +403,22 @@ class LLMObs extends NoopLLMObs {
|
|
|
355
403
|
}
|
|
356
404
|
}
|
|
357
405
|
|
|
406
|
+
// bind function to active LLMObs span
|
|
407
|
+
_bind (fn) {
|
|
408
|
+
if (typeof fn !== 'function') return fn
|
|
409
|
+
|
|
410
|
+
const llmobs = this
|
|
411
|
+
const activeSpan = llmobs._active()
|
|
412
|
+
|
|
413
|
+
const bound = function () {
|
|
414
|
+
return llmobs._activate(activeSpan, null, () => {
|
|
415
|
+
return fn.apply(this, arguments)
|
|
416
|
+
})
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return bound
|
|
420
|
+
}
|
|
421
|
+
|
|
358
422
|
_extractOptions (options) {
|
|
359
423
|
const {
|
|
360
424
|
modelName,
|
|
@@ -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
|
|
@@ -63,15 +63,14 @@ const log = {
|
|
|
63
63
|
|
|
64
64
|
Error.captureStackTrace(logRecord, this.trace)
|
|
65
65
|
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
traceChannel.publish(Log.parse(formatted))
|
|
66
|
+
const stack = logRecord.stack.split('\n')
|
|
67
|
+
const fn = stack[1].replace(/^\s+at ([^\s]+) .+/, '$1')
|
|
68
|
+
const options = { depth: 2, breakLength: Infinity, compact: true, maxArrayLength: Infinity }
|
|
69
|
+
const params = args.map(a => inspect(a, options)).join(', ')
|
|
70
|
+
|
|
71
|
+
stack[0] = `Trace: ${fn}(${params})`
|
|
72
|
+
|
|
73
|
+
traceChannel.publish(Log.parse(stack.join('\n')))
|
|
75
74
|
}
|
|
76
75
|
return this
|
|
77
76
|
},
|