dd-trace 5.48.0 → 5.49.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/package.json +3 -2
- package/packages/datadog-esbuild/index.js +4 -3
- package/packages/datadog-instrumentations/src/orchestrion-config/index.js +54 -5
- package/packages/datadog-instrumentations/src/playwright.js +28 -14
- package/packages/datadog-shimmer/src/shimmer.js +3 -128
- package/packages/dd-trace/src/config.js +3 -3
- package/packages/dd-trace/src/llmobs/constants/writers.js +9 -5
- package/packages/dd-trace/src/llmobs/index.js +23 -9
- package/packages/dd-trace/src/llmobs/sdk.js +0 -7
- package/packages/dd-trace/src/llmobs/writers/base.js +66 -38
- package/packages/dd-trace/src/llmobs/writers/evaluations.js +9 -6
- package/packages/dd-trace/src/llmobs/writers/{spans/base.js → spans.js} +20 -12
- package/packages/dd-trace/src/llmobs/writers/util.js +60 -0
- package/packages/dd-trace/src/plugins/log_plugin.js +5 -0
- package/packages/dd-trace/src/profiling/profilers/wall.js +2 -2
- package/packages/datadog-instrumentations/orchestrion.yml +0 -52
- package/packages/dd-trace/src/llmobs/writers/spans/agentProxy.js +0 -23
- package/packages/dd-trace/src/llmobs/writers/spans/agentless.js +0 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-trace",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.49.0",
|
|
4
4
|
"description": "Datadog APM tracing client for JavaScript",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"test:integration:cucumber": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/cucumber/*.spec.js\"",
|
|
49
49
|
"test:integration:cypress": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/cypress/*.spec.js\"",
|
|
50
50
|
"test:integration:debugger": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/debugger/*.spec.js\"",
|
|
51
|
+
"test:integration:esbuild": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/esbuild/*.spec.js\"",
|
|
51
52
|
"test:integration:jest": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/jest/*.spec.js\"",
|
|
52
53
|
"test:integration:mocha": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/mocha/*.spec.js\"",
|
|
53
54
|
"test:integration:playwright": "mocha --timeout 60000 -r \"packages/dd-trace/test/setup/core.js\" \"integration-tests/playwright/*.spec.js\"",
|
|
@@ -90,7 +91,7 @@
|
|
|
90
91
|
"@datadog/native-metrics": "^3.1.1",
|
|
91
92
|
"@datadog/pprof": "5.7.1",
|
|
92
93
|
"@datadog/sketches-js": "^2.1.0",
|
|
93
|
-
"@datadog/wasm-js-rewriter": "4.0.
|
|
94
|
+
"@datadog/wasm-js-rewriter": "4.0.1",
|
|
94
95
|
"@isaacs/ttlcache": "^1.4.1",
|
|
95
96
|
"@opentelemetry/api": ">=1.0.0 <1.9.0",
|
|
96
97
|
"@opentelemetry/core": "^1.14.0",
|
|
@@ -47,8 +47,7 @@ const DEBUG = !!process.env.DD_TRACE_DEBUG
|
|
|
47
47
|
// Those packages will still be handled via RITM
|
|
48
48
|
// Attempting to instrument them would fail as they have no package.json file
|
|
49
49
|
for (const pkg of INSTRUMENTED) {
|
|
50
|
-
if (builtins.has(pkg)) continue
|
|
51
|
-
if (pkg.startsWith('node:')) continue
|
|
50
|
+
if (builtins.has(pkg) || pkg.startsWith('node:')) continue
|
|
52
51
|
modulesOfInterest.add(pkg)
|
|
53
52
|
}
|
|
54
53
|
|
|
@@ -71,7 +70,9 @@ module.exports.setup = function (build) {
|
|
|
71
70
|
}
|
|
72
71
|
|
|
73
72
|
// TODO: Should this also check for namespace === 'file'?
|
|
74
|
-
if (args.path
|
|
73
|
+
if (!modulesOfInterest.has(args.path) &&
|
|
74
|
+
args.path.startsWith('@') &&
|
|
75
|
+
!args.importer.includes('node_modules/')) {
|
|
75
76
|
// This is the Next.js convention for loading local files
|
|
76
77
|
if (DEBUG) console.log(`@LOCAL: ${args.path}`)
|
|
77
78
|
return
|
|
@@ -1,5 +1,54 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
module.exports = `
|
|
2
|
+
version: 1
|
|
3
|
+
dc_module: dc-polyfill
|
|
4
|
+
instrumentations:
|
|
5
|
+
- module_name: "@langchain/core"
|
|
6
|
+
version_range: ">=0.1.0"
|
|
7
|
+
file_path: dist/runnables/base.js
|
|
8
|
+
function_query:
|
|
9
|
+
name: invoke
|
|
10
|
+
type: method
|
|
11
|
+
kind: async
|
|
12
|
+
class: RunnableSequence
|
|
13
|
+
operator: tracePromise
|
|
14
|
+
channel_name: "RunnableSequence_invoke"
|
|
15
|
+
- module_name: "@langchain/core"
|
|
16
|
+
version_range: ">=0.1.0"
|
|
17
|
+
file_path: dist/runnables/base.js
|
|
18
|
+
function_query:
|
|
19
|
+
name: batch
|
|
20
|
+
type: method
|
|
21
|
+
kind: async
|
|
22
|
+
class: RunnableSequence
|
|
23
|
+
operator: tracePromise
|
|
24
|
+
channel_name: "RunnableSequence_batch"
|
|
25
|
+
- module_name: "@langchain/core"
|
|
26
|
+
version_range: ">=0.1.0"
|
|
27
|
+
file_path: dist/language_models/chat_models.js
|
|
28
|
+
function_query:
|
|
29
|
+
name: generate
|
|
30
|
+
type: method
|
|
31
|
+
kind: async
|
|
32
|
+
class: BaseChatModel
|
|
33
|
+
operator: tracePromise
|
|
34
|
+
channel_name: "BaseChatModel_generate"
|
|
35
|
+
- module_name: "@langchain/core"
|
|
36
|
+
version_range: ">=0.1.0"
|
|
37
|
+
file_path: dist/language_models/llms.js
|
|
38
|
+
function_query:
|
|
39
|
+
name: generate
|
|
40
|
+
type: method
|
|
41
|
+
kind: async
|
|
42
|
+
operator: tracePromise
|
|
43
|
+
channel_name: "BaseLLM_generate"
|
|
44
|
+
- module_name: "@langchain/core"
|
|
45
|
+
version_range: ">=0.1.0"
|
|
46
|
+
file_path: dist/embeddings.js
|
|
47
|
+
function_query:
|
|
48
|
+
name: constructor
|
|
49
|
+
type: method
|
|
50
|
+
kind: sync
|
|
51
|
+
class: Embeddings
|
|
52
|
+
operator: traceSync
|
|
53
|
+
channel_name: "Embeddings_constructor"
|
|
54
|
+
`
|
|
@@ -761,9 +761,17 @@ addHook({
|
|
|
761
761
|
return rootSuite
|
|
762
762
|
}
|
|
763
763
|
|
|
764
|
-
|
|
764
|
+
// We need to proxy the createRootSuite function because the function is not configurable
|
|
765
|
+
const proxy = new Proxy(loadUtilsPackage, {
|
|
766
|
+
get (target, prop) {
|
|
767
|
+
if (prop === 'createRootSuite') {
|
|
768
|
+
return newCreateRootSuite
|
|
769
|
+
}
|
|
770
|
+
return target[prop]
|
|
771
|
+
}
|
|
772
|
+
})
|
|
765
773
|
|
|
766
|
-
return
|
|
774
|
+
return proxy
|
|
767
775
|
})
|
|
768
776
|
|
|
769
777
|
// main process hook
|
|
@@ -805,19 +813,25 @@ addHook({
|
|
|
805
813
|
|
|
806
814
|
const page = this
|
|
807
815
|
|
|
808
|
-
|
|
809
|
-
if (
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
816
|
+
try {
|
|
817
|
+
if (page) {
|
|
818
|
+
const isRumActive = await page.evaluate(() => {
|
|
819
|
+
if (window.DD_RUM && window.DD_RUM.getInternalContext) {
|
|
820
|
+
return !!window.DD_RUM.getInternalContext()
|
|
821
|
+
} else {
|
|
822
|
+
return false
|
|
823
|
+
}
|
|
824
|
+
})
|
|
815
825
|
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
826
|
+
if (isRumActive) {
|
|
827
|
+
testPageGotoCh.publish({
|
|
828
|
+
isRumActive,
|
|
829
|
+
page
|
|
830
|
+
})
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
} catch (e) {
|
|
834
|
+
// ignore errors such as redirects, context destroyed, etc
|
|
821
835
|
}
|
|
822
836
|
|
|
823
837
|
return response
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const log = require('../../dd-trace/src/log')
|
|
4
|
-
|
|
5
3
|
function copyProperties (original, wrapped) {
|
|
6
4
|
// TODO getPrototypeOf is not fast. Should we instead do this in specific
|
|
7
5
|
// instrumentations where needed?
|
|
@@ -24,9 +22,7 @@ function copyProperties (original, wrapped) {
|
|
|
24
22
|
function wrapFunction (original, wrapper) {
|
|
25
23
|
if (typeof original === 'function') assertNotClass(original)
|
|
26
24
|
|
|
27
|
-
const wrapped =
|
|
28
|
-
? safeWrapper(original, wrapper)
|
|
29
|
-
: wrapper(original)
|
|
25
|
+
const wrapped = wrapper(original)
|
|
30
26
|
|
|
31
27
|
if (typeof original === 'function') copyProperties(original, wrapped)
|
|
32
28
|
|
|
@@ -37,36 +33,6 @@ const wrapFn = function (original, delegate) {
|
|
|
37
33
|
throw new Error('calling `wrap()` with 2 args is deprecated. Use wrapFunction instead.')
|
|
38
34
|
}
|
|
39
35
|
|
|
40
|
-
// This is only used in safe mode. It's a simple state machine to track if the
|
|
41
|
-
// original method was called and if it returned. We need this to determine if
|
|
42
|
-
// an error was thrown by the original method, or by us. We'll use one of these
|
|
43
|
-
// per call to a wrapped method.
|
|
44
|
-
class CallState {
|
|
45
|
-
constructor () {
|
|
46
|
-
this.called = false
|
|
47
|
-
this.completed = false
|
|
48
|
-
this.retVal = undefined
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
startCall () {
|
|
52
|
-
this.called = true
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
endCall (retVal) {
|
|
56
|
-
this.completed = true
|
|
57
|
-
this.retVal = retVal
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function isPromise (obj) {
|
|
62
|
-
return obj && typeof obj === 'object' && typeof obj.then === 'function'
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
let safeMode = !!process.env.DD_INEJCTION_ENABLED
|
|
66
|
-
function setSafe (value) {
|
|
67
|
-
safeMode = value
|
|
68
|
-
}
|
|
69
|
-
|
|
70
36
|
function wrapMethod (target, name, wrapper, noAssert) {
|
|
71
37
|
if (!noAssert) {
|
|
72
38
|
assertMethod(target, name)
|
|
@@ -74,9 +40,7 @@ function wrapMethod (target, name, wrapper, noAssert) {
|
|
|
74
40
|
}
|
|
75
41
|
|
|
76
42
|
const original = target[name]
|
|
77
|
-
const wrapped =
|
|
78
|
-
? safeWrapper(original, wrapper)
|
|
79
|
-
: wrapper(original)
|
|
43
|
+
const wrapped = wrapper(original)
|
|
80
44
|
|
|
81
45
|
const descriptor = Object.getOwnPropertyDescriptor(target, name)
|
|
82
46
|
|
|
@@ -110,94 +74,6 @@ function wrapMethod (target, name, wrapper, noAssert) {
|
|
|
110
74
|
return target
|
|
111
75
|
}
|
|
112
76
|
|
|
113
|
-
function safeWrapper (original, wrapper) {
|
|
114
|
-
// In this mode, we make a best-effort attempt to handle errors that are thrown
|
|
115
|
-
// by us, rather than wrapped code. With such errors, we log them, and then attempt
|
|
116
|
-
// to return the result as if no wrapping was done at all.
|
|
117
|
-
//
|
|
118
|
-
// Caveats:
|
|
119
|
-
// * If the original function is called in a later iteration of the event loop,
|
|
120
|
-
// and we throw _then_, then it won't be caught by this. In practice, we always call
|
|
121
|
-
// the original function synchronously, so this is not a problem.
|
|
122
|
-
// * While async errors are dealt with here, errors in callbacks are not. This
|
|
123
|
-
// is because we don't necessarily know _for sure_ that any function arguments
|
|
124
|
-
// are wrapped by us. We could wrap them all anyway and just make that assumption,
|
|
125
|
-
// or just assume that the last argument is always a callback set by us if it's a
|
|
126
|
-
// function, but those don't seem like things we can rely on. We could add a
|
|
127
|
-
// `shimmer.markCallbackAsWrapped()` function that's a no-op outside safe-mode,
|
|
128
|
-
// but that means modifying every instrumentation. Even then, the complexity of
|
|
129
|
-
// this code increases because then we'd need to effectively do the reverse of
|
|
130
|
-
// what we're doing for synchronous functions. This is a TODO.
|
|
131
|
-
|
|
132
|
-
// We're going to hold on to current callState in this variable in this scope,
|
|
133
|
-
// which is fine because any time we reference it, we're referencing it synchronously.
|
|
134
|
-
// We'll use it in the our wrapper (which, again, is called syncrhonously), and in the
|
|
135
|
-
// errorHandler, which will already have been bound to this callState.
|
|
136
|
-
let currentCallState
|
|
137
|
-
|
|
138
|
-
// Rather than calling the original function directly from the shim wrapper, we wrap
|
|
139
|
-
// it again so that we can track if it was called and if it returned. This is because
|
|
140
|
-
// we need to know if an error was thrown by the original function, or by us.
|
|
141
|
-
// We could do this inside the `wrapper` function defined below, which would simplify
|
|
142
|
-
// managing the callState, but then we'd be calling `wrapper` on each invocation, so
|
|
143
|
-
// instead we do it here, once.
|
|
144
|
-
const innerWrapped = wrapper(function (...args) {
|
|
145
|
-
// We need to stash the callState here because of recursion.
|
|
146
|
-
const callState = currentCallState
|
|
147
|
-
callState.startCall()
|
|
148
|
-
const retVal = original.apply(this, args)
|
|
149
|
-
if (isPromise(retVal)) {
|
|
150
|
-
retVal.then(callState.endCall.bind(callState))
|
|
151
|
-
} else {
|
|
152
|
-
callState.endCall(retVal)
|
|
153
|
-
}
|
|
154
|
-
return retVal
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
// This is the crux of what we're doing in safe mode. It handles errors
|
|
158
|
-
// that _we_ cause, by logging them, and transparently providing results
|
|
159
|
-
// as if no wrapping was done at all. That means detecting (via callState)
|
|
160
|
-
// whether the function has already run or not, and if it has, returning
|
|
161
|
-
// the result, and otherwise calling the original function unwrapped.
|
|
162
|
-
const handleError = function (args, callState, e) {
|
|
163
|
-
if (callState.completed) {
|
|
164
|
-
// error was thrown after original function returned/resolved, so
|
|
165
|
-
// it was us. log it.
|
|
166
|
-
log.error('Shimmer error was thrown after original function returned/resolved', e)
|
|
167
|
-
// original ran and returned something. return it.
|
|
168
|
-
return callState.retVal
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (!callState.called) {
|
|
172
|
-
// error was thrown before original function was called, so
|
|
173
|
-
// it was us. log it.
|
|
174
|
-
log.error('Shimmer error was thrown before original function was called', e)
|
|
175
|
-
// original never ran. call it unwrapped.
|
|
176
|
-
return original.apply(this, args)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// error was thrown during original function execution, so
|
|
180
|
-
// it was them. throw.
|
|
181
|
-
throw e
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// The wrapped function is the one that will be called by the user.
|
|
185
|
-
// It calls our version of the original function, which manages the
|
|
186
|
-
// callState. That way when we use the errorHandler, it can tell where
|
|
187
|
-
// the error originated.
|
|
188
|
-
return function (...args) {
|
|
189
|
-
currentCallState = new CallState()
|
|
190
|
-
const errorHandler = handleError.bind(this, args, currentCallState)
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
const retVal = innerWrapped.apply(this, args)
|
|
194
|
-
return isPromise(retVal) ? retVal.catch(errorHandler) : retVal
|
|
195
|
-
} catch (e) {
|
|
196
|
-
return errorHandler(e)
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
77
|
function wrap (target, name, wrapper) {
|
|
202
78
|
return typeof name === 'function'
|
|
203
79
|
? wrapFn(target, name)
|
|
@@ -256,6 +132,5 @@ function assertNotClass (target) {
|
|
|
256
132
|
module.exports = {
|
|
257
133
|
wrap,
|
|
258
134
|
wrapFunction,
|
|
259
|
-
massWrap
|
|
260
|
-
setSafe
|
|
135
|
+
massWrap
|
|
261
136
|
}
|
|
@@ -530,7 +530,7 @@ class Config {
|
|
|
530
530
|
this._setValue(defaults, 'isManualApiEnabled', false)
|
|
531
531
|
this._setValue(defaults, 'langchain.spanCharLimit', 128)
|
|
532
532
|
this._setValue(defaults, 'langchain.spanPromptCompletionSampleRate', 1.0)
|
|
533
|
-
this._setValue(defaults, 'llmobs.agentlessEnabled',
|
|
533
|
+
this._setValue(defaults, 'llmobs.agentlessEnabled', undefined)
|
|
534
534
|
this._setValue(defaults, 'llmobs.enabled', false)
|
|
535
535
|
this._setValue(defaults, 'llmobs.mlApp', undefined)
|
|
536
536
|
this._setValue(defaults, 'ciVisibilityTestSessionName', '')
|
|
@@ -825,7 +825,7 @@ class Config {
|
|
|
825
825
|
this._setValue(env, 'baggageMaxBytes', DD_TRACE_BAGGAGE_MAX_BYTES)
|
|
826
826
|
this._setValue(env, 'baggageMaxItems', DD_TRACE_BAGGAGE_MAX_ITEMS)
|
|
827
827
|
this._setBoolean(env, 'clientIpEnabled', DD_TRACE_CLIENT_IP_ENABLED)
|
|
828
|
-
this._setString(env, 'clientIpHeader', DD_TRACE_CLIENT_IP_HEADER)
|
|
828
|
+
this._setString(env, 'clientIpHeader', DD_TRACE_CLIENT_IP_HEADER?.toLowerCase())
|
|
829
829
|
this._setBoolean(env, 'crashtracking.enabled', coalesce(
|
|
830
830
|
DD_CRASHTRACKING_ENABLED,
|
|
831
831
|
!this._isInServerlessEnvironment()
|
|
@@ -1032,7 +1032,7 @@ class Config {
|
|
|
1032
1032
|
this._setValue(opts, 'appsec.wafTimeout', maybeInt(options.appsec.wafTimeout))
|
|
1033
1033
|
this._optsUnprocessed['appsec.wafTimeout'] = options.appsec.wafTimeout
|
|
1034
1034
|
this._setBoolean(opts, 'clientIpEnabled', options.clientIpEnabled)
|
|
1035
|
-
this._setString(opts, 'clientIpHeader', options.clientIpHeader)
|
|
1035
|
+
this._setString(opts, 'clientIpHeader', options.clientIpHeader?.toLowerCase())
|
|
1036
1036
|
this._setValue(opts, 'baggageMaxBytes', options.baggageMaxBytes)
|
|
1037
1037
|
this._setValue(opts, 'baggageMaxItems', options.baggageMaxItems)
|
|
1038
1038
|
this._setBoolean(opts, 'codeOriginForSpans.enabled', options.codeOriginForSpans?.enabled)
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
module.exports = {
|
|
4
|
-
EVP_PROXY_AGENT_BASE_PATH: 'evp_proxy/v2',
|
|
5
|
-
EVP_PROXY_AGENT_ENDPOINT: 'evp_proxy/v2/api/v2/llmobs',
|
|
4
|
+
EVP_PROXY_AGENT_BASE_PATH: '/evp_proxy/v2/',
|
|
6
5
|
EVP_SUBDOMAIN_HEADER_NAME: 'X-Datadog-EVP-Subdomain',
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
|
|
7
|
+
SPANS_EVENT_TYPE: 'span',
|
|
8
|
+
SPANS_INTAKE: 'llmobs-intake',
|
|
9
|
+
SPANS_ENDPOINT: '/api/v2/llmobs',
|
|
10
|
+
|
|
11
|
+
EVALUATIONS_INTAKE: 'api',
|
|
12
|
+
EVALUATIONS_EVENT_TYPE: 'evaluation_metric',
|
|
13
|
+
EVALUATIONS_ENDPOINT: '/api/intake/llm-obs/v1/eval-metric',
|
|
10
14
|
|
|
11
15
|
EVP_PAYLOAD_SIZE_LIMIT: 5 << 20, // 5MB (actual limit is 5.1MB)
|
|
12
16
|
EVP_EVENT_SIZE_LIMIT: (1 << 20) - 1024 // 999KB (actual limit is 1MB)
|
|
@@ -13,9 +13,9 @@ const evalMetricAppendCh = channel('llmobs:eval-metric:append')
|
|
|
13
13
|
const flushCh = channel('llmobs:writers:flush')
|
|
14
14
|
const injectCh = channel('dd-trace:span:inject')
|
|
15
15
|
|
|
16
|
-
const LLMObsAgentlessSpanWriter = require('./writers/spans/agentless')
|
|
17
|
-
const LLMObsAgentProxySpanWriter = require('./writers/spans/agentProxy')
|
|
18
16
|
const LLMObsEvalMetricsWriter = require('./writers/evaluations')
|
|
17
|
+
const LLMObsSpanWriter = require('./writers/spans')
|
|
18
|
+
const { setAgentStrategy } = require('./writers/util')
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Setting writers and processor globally when LLMObs is enabled
|
|
@@ -25,8 +25,14 @@ const LLMObsEvalMetricsWriter = require('./writers/evaluations')
|
|
|
25
25
|
* if the tracer is `init`ed. But, in those cases, we don't want to start writers or subscribe
|
|
26
26
|
* to channels.
|
|
27
27
|
*/
|
|
28
|
+
|
|
29
|
+
/** @type {LLMObsSpanProcessor | null} */
|
|
28
30
|
let spanProcessor
|
|
31
|
+
|
|
32
|
+
/** @type {LLMObsSpanWriter | null} */
|
|
29
33
|
let spanWriter
|
|
34
|
+
|
|
35
|
+
/** @type {LLMObsEvalMetricsWriter | null} */
|
|
30
36
|
let evalWriter
|
|
31
37
|
|
|
32
38
|
function enable (config) {
|
|
@@ -34,7 +40,7 @@ function enable (config) {
|
|
|
34
40
|
// create writers and eval writer append and flush channels
|
|
35
41
|
// span writer append is handled by the span processor
|
|
36
42
|
evalWriter = new LLMObsEvalMetricsWriter(config)
|
|
37
|
-
spanWriter =
|
|
43
|
+
spanWriter = new LLMObsSpanWriter(config)
|
|
38
44
|
|
|
39
45
|
evalMetricAppendCh.subscribe(handleEvalMetricAppend)
|
|
40
46
|
flushCh.subscribe(handleFlush)
|
|
@@ -46,7 +52,20 @@ function enable (config) {
|
|
|
46
52
|
|
|
47
53
|
// distributed tracing for llmobs
|
|
48
54
|
injectCh.subscribe(handleLLMObsParentIdInjection)
|
|
49
|
-
|
|
55
|
+
|
|
56
|
+
setAgentStrategy(config, useAgentless => {
|
|
57
|
+
if (useAgentless && !(config.apiKey && config.site)) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
'Cannot send LLM Observability data without a running agent or without both a Datadog API key and site.\n' +
|
|
60
|
+
'Ensure these configurations are set before running your application.'
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
evalWriter?.setAgentless(useAgentless)
|
|
65
|
+
spanWriter?.setAgentless(useAgentless)
|
|
66
|
+
|
|
67
|
+
telemetry.recordLLMObsEnabled(startTime, config)
|
|
68
|
+
})
|
|
50
69
|
}
|
|
51
70
|
|
|
52
71
|
function disable () {
|
|
@@ -74,11 +93,6 @@ function handleLLMObsParentIdInjection ({ carrier }) {
|
|
|
74
93
|
carrier['x-datadog-tags'] += `,${PROPAGATED_PARENT_ID_KEY}=${parentId}`
|
|
75
94
|
}
|
|
76
95
|
|
|
77
|
-
function createSpanWriter (config) {
|
|
78
|
-
const SpanWriter = config.llmobs.agentlessEnabled ? LLMObsAgentlessSpanWriter : LLMObsAgentProxySpanWriter
|
|
79
|
-
return new SpanWriter(config)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
96
|
function handleFlush () {
|
|
83
97
|
try {
|
|
84
98
|
spanWriter.flush()
|
|
@@ -287,13 +287,6 @@ class LLMObs extends NoopLLMObs {
|
|
|
287
287
|
submitEvaluation (llmobsSpanContext, options = {}) {
|
|
288
288
|
if (!this.enabled) return
|
|
289
289
|
|
|
290
|
-
if (!this._config.apiKey) {
|
|
291
|
-
throw new Error(
|
|
292
|
-
'DD_API_KEY is required for sending evaluation metrics. Evaluation metric data will not be sent.\n' +
|
|
293
|
-
'Ensure this configuration is set before running your application.'
|
|
294
|
-
)
|
|
295
|
-
}
|
|
296
|
-
|
|
297
290
|
const { traceId, spanId } = llmobsSpanContext
|
|
298
291
|
if (!traceId || !spanId) {
|
|
299
292
|
throw new Error(
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const request = require('../../exporters/common/request')
|
|
4
|
-
const { URL, format } = require('url')
|
|
4
|
+
const { URL, format } = require('node:url')
|
|
5
|
+
const path = require('node:path')
|
|
5
6
|
|
|
6
7
|
const logger = require('../../log')
|
|
7
8
|
|
|
8
9
|
const { encodeUnicode } = require('../util')
|
|
9
10
|
const telemetry = require('../telemetry')
|
|
10
11
|
const log = require('../../log')
|
|
12
|
+
const {
|
|
13
|
+
EVP_SUBDOMAIN_HEADER_NAME,
|
|
14
|
+
EVP_PROXY_AGENT_BASE_PATH
|
|
15
|
+
} = require('../constants/writers')
|
|
16
|
+
const { parseResponseAndLog } = require('./util')
|
|
11
17
|
|
|
12
18
|
class BaseLLMObsWriter {
|
|
13
|
-
constructor ({ interval, timeout,
|
|
19
|
+
constructor ({ interval, timeout, eventType, config, endpoint, intake }) {
|
|
14
20
|
this._interval = interval || 1000 // 1s
|
|
15
21
|
this._timeout = timeout || 5000 // 5s
|
|
16
22
|
this._eventType = eventType
|
|
@@ -19,28 +25,20 @@ class BaseLLMObsWriter {
|
|
|
19
25
|
this._bufferLimit = 1000
|
|
20
26
|
this._bufferSize = 0
|
|
21
27
|
|
|
22
|
-
this.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
port: port || 443,
|
|
26
|
-
pathname: endpoint
|
|
27
|
-
}))
|
|
28
|
-
|
|
29
|
-
this._headers = {
|
|
30
|
-
'Content-Type': 'application/json'
|
|
31
|
-
}
|
|
28
|
+
this._config = config
|
|
29
|
+
this._endpoint = endpoint
|
|
30
|
+
this._intake = intake
|
|
32
31
|
|
|
33
32
|
this._periodic = setInterval(() => {
|
|
34
33
|
this.flush()
|
|
35
34
|
}, this._interval).unref()
|
|
36
35
|
|
|
37
|
-
|
|
36
|
+
this._beforeExitHandler = () => {
|
|
38
37
|
this.destroy()
|
|
39
|
-
}
|
|
38
|
+
}
|
|
39
|
+
process.once('beforeExit', this._beforeExitHandler)
|
|
40
40
|
|
|
41
41
|
this._destroyed = false
|
|
42
|
-
|
|
43
|
-
logger.debug(`Started ${this.constructor.name} to ${this._url}`)
|
|
44
42
|
}
|
|
45
43
|
|
|
46
44
|
append (event, byteLength) {
|
|
@@ -55,7 +53,9 @@ class BaseLLMObsWriter {
|
|
|
55
53
|
}
|
|
56
54
|
|
|
57
55
|
flush () {
|
|
58
|
-
|
|
56
|
+
const noAgentStrategy = this._agentless == null
|
|
57
|
+
|
|
58
|
+
if (this._buffer.length === 0 || noAgentStrategy) {
|
|
59
59
|
return
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -64,29 +64,12 @@ class BaseLLMObsWriter {
|
|
|
64
64
|
this._bufferSize = 0
|
|
65
65
|
const payload = this._encode(this.makePayload(events))
|
|
66
66
|
|
|
67
|
-
const options = {
|
|
68
|
-
headers: this._headers,
|
|
69
|
-
method: 'POST',
|
|
70
|
-
url: this._url,
|
|
71
|
-
timeout: this._timeout
|
|
72
|
-
}
|
|
73
|
-
|
|
74
67
|
log.debug(`Encoded LLMObs payload: ${payload}`)
|
|
75
68
|
|
|
69
|
+
const options = this._getOptions()
|
|
70
|
+
|
|
76
71
|
request(payload, options, (err, resp, code) => {
|
|
77
|
-
|
|
78
|
-
logger.error(
|
|
79
|
-
'Error sending %d LLMObs %s events to %s: %s', events.length, this._eventType, this._url, err.message, err
|
|
80
|
-
)
|
|
81
|
-
telemetry.recordDroppedPayload(events.length, this._eventType, 'request_error')
|
|
82
|
-
} else if (code >= 300) {
|
|
83
|
-
logger.error(
|
|
84
|
-
'Error sending %d LLMObs %s events to %s: %s', events.length, this._eventType, this._url, code
|
|
85
|
-
)
|
|
86
|
-
telemetry.recordDroppedPayload(events.length, this._eventType, 'http_error')
|
|
87
|
-
} else {
|
|
88
|
-
logger.debug(`Sent ${events.length} LLMObs ${this._eventType} events to ${this._url}`)
|
|
89
|
-
}
|
|
72
|
+
parseResponseAndLog(err, code, events.length, options.url.href, this._eventType)
|
|
90
73
|
})
|
|
91
74
|
}
|
|
92
75
|
|
|
@@ -96,12 +79,57 @@ class BaseLLMObsWriter {
|
|
|
96
79
|
if (!this._destroyed) {
|
|
97
80
|
logger.debug(`Stopping ${this.constructor.name}`)
|
|
98
81
|
clearInterval(this._periodic)
|
|
99
|
-
process.removeListener('beforeExit', this.
|
|
82
|
+
process.removeListener('beforeExit', this._beforeExitHandler)
|
|
100
83
|
this.flush()
|
|
101
84
|
this._destroyed = true
|
|
102
85
|
}
|
|
103
86
|
}
|
|
104
87
|
|
|
88
|
+
setAgentless (agentless) {
|
|
89
|
+
this._agentless = agentless
|
|
90
|
+
this._url = this._getUrl()
|
|
91
|
+
logger.debug(`Configuring ${this.constructor.name} to ${this._url.href}`)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
_getUrl () {
|
|
95
|
+
if (this._agentless) {
|
|
96
|
+
return new URL(format({
|
|
97
|
+
protocol: 'https:',
|
|
98
|
+
hostname: `${this._intake}.${this._config.site}`,
|
|
99
|
+
pathname: this._endpoint
|
|
100
|
+
}))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const { hostname, port } = this._config
|
|
104
|
+
const base = this._config.url || new URL(format({
|
|
105
|
+
protocol: 'http:',
|
|
106
|
+
hostname,
|
|
107
|
+
port
|
|
108
|
+
}))
|
|
109
|
+
|
|
110
|
+
const proxyPath = path.join(EVP_PROXY_AGENT_BASE_PATH, this._endpoint)
|
|
111
|
+
return new URL(proxyPath, base)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
_getOptions () {
|
|
115
|
+
const options = {
|
|
116
|
+
headers: {
|
|
117
|
+
'Content-Type': 'application/json'
|
|
118
|
+
},
|
|
119
|
+
method: 'POST',
|
|
120
|
+
timeout: this._timeout,
|
|
121
|
+
url: this._url
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (this._agentless) {
|
|
125
|
+
options.headers['DD-API-KEY'] = this._config.apiKey || ''
|
|
126
|
+
} else {
|
|
127
|
+
options.headers[EVP_SUBDOMAIN_HEADER_NAME] = this._intake
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return options
|
|
131
|
+
}
|
|
132
|
+
|
|
105
133
|
_encode (payload) {
|
|
106
134
|
return JSON.stringify(payload, (key, value) => {
|
|
107
135
|
if (typeof value === 'string') {
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const {
|
|
4
|
+
EVALUATIONS_ENDPOINT,
|
|
5
|
+
EVALUATIONS_EVENT_TYPE,
|
|
6
|
+
EVALUATIONS_INTAKE
|
|
7
|
+
} = require('../constants/writers')
|
|
4
8
|
const BaseWriter = require('./base')
|
|
5
9
|
|
|
6
10
|
class LLMObsEvalMetricsWriter extends BaseWriter {
|
|
7
11
|
constructor (config) {
|
|
8
12
|
super({
|
|
9
|
-
|
|
10
|
-
intake:
|
|
11
|
-
eventType:
|
|
13
|
+
config,
|
|
14
|
+
intake: EVALUATIONS_INTAKE,
|
|
15
|
+
eventType: EVALUATIONS_EVENT_TYPE,
|
|
16
|
+
endpoint: EVALUATIONS_ENDPOINT
|
|
12
17
|
})
|
|
13
|
-
|
|
14
|
-
this._headers['DD-API-KEY'] = config.apiKey
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
makePayload (events) {
|
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
const {
|
|
4
|
+
EVP_EVENT_SIZE_LIMIT,
|
|
5
|
+
EVP_PAYLOAD_SIZE_LIMIT,
|
|
6
|
+
SPANS_ENDPOINT,
|
|
7
|
+
SPANS_EVENT_TYPE,
|
|
8
|
+
SPANS_INTAKE
|
|
9
|
+
} = require('../constants/writers')
|
|
10
|
+
const { DROPPED_VALUE_TEXT } = require('../constants/text')
|
|
11
|
+
const { DROPPED_IO_COLLECTION_ERROR } = require('../constants/tags')
|
|
12
|
+
const BaseWriter = require('./base')
|
|
13
|
+
const telemetry = require('../telemetry')
|
|
14
|
+
const logger = require('../../log')
|
|
9
15
|
|
|
10
|
-
const tracerVersion = require('
|
|
16
|
+
const tracerVersion = require('../../../../../package.json').version
|
|
11
17
|
|
|
12
18
|
class LLMObsSpanWriter extends BaseWriter {
|
|
13
|
-
constructor (
|
|
19
|
+
constructor (config) {
|
|
14
20
|
super({
|
|
15
|
-
|
|
16
|
-
eventType:
|
|
21
|
+
config,
|
|
22
|
+
eventType: SPANS_EVENT_TYPE,
|
|
23
|
+
intake: SPANS_INTAKE,
|
|
24
|
+
endpoint: SPANS_ENDPOINT
|
|
17
25
|
})
|
|
18
26
|
}
|
|
19
27
|
|
|
@@ -33,11 +41,11 @@ class LLMObsSpanWriter extends BaseWriter {
|
|
|
33
41
|
telemetry.recordLLMObsSpanSize(event, processedEventSizeBytes, shouldTruncate)
|
|
34
42
|
|
|
35
43
|
if (this._bufferSize + eventSizeBytes > EVP_PAYLOAD_SIZE_LIMIT) {
|
|
36
|
-
logger.debug('
|
|
44
|
+
logger.debug('Flushing queue because queuing next event will exceed EvP payload limit')
|
|
37
45
|
this.flush()
|
|
38
46
|
}
|
|
39
47
|
|
|
40
|
-
super.append(event,
|
|
48
|
+
super.append(event, processedEventSizeBytes)
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
makePayload (events) {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const logger = require('../../log')
|
|
4
|
+
const { EVP_PROXY_AGENT_BASE_PATH } = require('../constants/writers')
|
|
5
|
+
const telemetry = require('../telemetry')
|
|
6
|
+
|
|
7
|
+
const AgentInfoExporter = require('../../exporters/common/agent-info-exporter')
|
|
8
|
+
/** @type {AgentInfoExporter} */
|
|
9
|
+
let agentInfoExporter
|
|
10
|
+
|
|
11
|
+
function setAgentStrategy (config, setWritersAgentlessValue) {
|
|
12
|
+
const agentlessEnabled = config.llmobs.agentlessEnabled
|
|
13
|
+
|
|
14
|
+
if (agentlessEnabled != null) {
|
|
15
|
+
setWritersAgentlessValue(agentlessEnabled)
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!agentInfoExporter) {
|
|
20
|
+
agentInfoExporter = new AgentInfoExporter(config)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
agentInfoExporter.getAgentInfo((err, agentInfo) => {
|
|
24
|
+
if (err) {
|
|
25
|
+
setWritersAgentlessValue(true)
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const endpoints = agentInfo.endpoints
|
|
30
|
+
const hasEndpoint = Array.isArray(endpoints) && endpoints.some(endpoint => endpoint === EVP_PROXY_AGENT_BASE_PATH)
|
|
31
|
+
setWritersAgentlessValue(!hasEndpoint)
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parseResponseAndLog (err, code, eventsLength, url, eventType) {
|
|
36
|
+
if (code === 403 && err.message.includes('API key is invalid')) {
|
|
37
|
+
logger.error(
|
|
38
|
+
'[LLMObs] The provided Datadog API key is invalid (likely due to an API key and DD_SITE mismatch). ' +
|
|
39
|
+
'Please verify your API key and DD_SITE are correct.'
|
|
40
|
+
)
|
|
41
|
+
telemetry.recordDroppedPayload(eventsLength, eventType, 'request_error')
|
|
42
|
+
} else if (err) {
|
|
43
|
+
logger.error(
|
|
44
|
+
'Error sending %d LLMObs %s events to %s: %s', eventsLength, eventType, url, err.message, err
|
|
45
|
+
)
|
|
46
|
+
telemetry.recordDroppedPayload(eventsLength, eventType, 'request_error')
|
|
47
|
+
} else if (code >= 300) {
|
|
48
|
+
logger.error(
|
|
49
|
+
'Error sending %d LLMObs %s events to %s: %s', eventsLength, eventType, url, code
|
|
50
|
+
)
|
|
51
|
+
telemetry.recordDroppedPayload(eventsLength, eventType, 'http_error')
|
|
52
|
+
} else {
|
|
53
|
+
logger.debug(`Sent ${eventsLength} LLMObs ${eventType} events to ${url}`)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
setAgentStrategy,
|
|
59
|
+
parseResponseAndLog
|
|
60
|
+
}
|
|
@@ -17,6 +17,11 @@ function messageProxy (message, holder) {
|
|
|
17
17
|
return holder.dd
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
// This is a workaround for a V8 bug that surfaced in Node.js 22
|
|
21
|
+
if (p === 'stack') {
|
|
22
|
+
return target.stack
|
|
23
|
+
}
|
|
24
|
+
|
|
20
25
|
return Reflect.get(target, p, receiver)
|
|
21
26
|
},
|
|
22
27
|
ownKeys (target) {
|
|
@@ -115,8 +115,6 @@ class NativeWallProfiler {
|
|
|
115
115
|
start ({ mapper } = {}) {
|
|
116
116
|
if (this._started) return
|
|
117
117
|
|
|
118
|
-
ensureChannelsActivated()
|
|
119
|
-
|
|
120
118
|
this._mapper = mapper
|
|
121
119
|
this._pprof = require('@datadog/pprof')
|
|
122
120
|
kSampleCount = this._pprof.time.constants.kSampleCount
|
|
@@ -147,6 +145,8 @@ class NativeWallProfiler {
|
|
|
147
145
|
this._profilerState = this._pprof.time.getState()
|
|
148
146
|
this._lastSampleCount = 0
|
|
149
147
|
|
|
148
|
+
ensureChannelsActivated()
|
|
149
|
+
|
|
150
150
|
beforeCh.subscribe(this._enter)
|
|
151
151
|
enterCh.subscribe(this._enter)
|
|
152
152
|
spanFinishCh.subscribe(this._spanFinished)
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
version: 1
|
|
2
|
-
dc_module: dc-polyfill
|
|
3
|
-
instrumentations:
|
|
4
|
-
- module_name: "@langchain/core"
|
|
5
|
-
version_range: ">=0.1.0"
|
|
6
|
-
file_path: dist/runnables/base.js
|
|
7
|
-
function_query:
|
|
8
|
-
name: invoke
|
|
9
|
-
type: method
|
|
10
|
-
kind: async
|
|
11
|
-
class: RunnableSequence
|
|
12
|
-
operator: tracePromise
|
|
13
|
-
channel_name: "RunnableSequence_invoke"
|
|
14
|
-
- module_name: "@langchain/core"
|
|
15
|
-
version_range: ">=0.1.0"
|
|
16
|
-
file_path: dist/runnables/base.js
|
|
17
|
-
function_query:
|
|
18
|
-
name: batch
|
|
19
|
-
type: method
|
|
20
|
-
kind: async
|
|
21
|
-
class: RunnableSequence
|
|
22
|
-
operator: tracePromise
|
|
23
|
-
channel_name: "RunnableSequence_batch"
|
|
24
|
-
- module_name: "@langchain/core"
|
|
25
|
-
version_range: ">=0.1.0"
|
|
26
|
-
file_path: dist/language_models/chat_models.js
|
|
27
|
-
function_query:
|
|
28
|
-
name: generate
|
|
29
|
-
type: method
|
|
30
|
-
kind: async
|
|
31
|
-
class: BaseChatModel
|
|
32
|
-
operator: tracePromise
|
|
33
|
-
channel_name: "BaseChatModel_generate"
|
|
34
|
-
- module_name: "@langchain/core"
|
|
35
|
-
version_range: ">=0.1.0"
|
|
36
|
-
file_path: dist/language_models/llms.js
|
|
37
|
-
function_query:
|
|
38
|
-
name: generate
|
|
39
|
-
type: method
|
|
40
|
-
kind: async
|
|
41
|
-
operator: tracePromise
|
|
42
|
-
channel_name: "BaseLLM_generate"
|
|
43
|
-
- module_name: "@langchain/core"
|
|
44
|
-
version_range: ">=0.1.0"
|
|
45
|
-
file_path: dist/embeddings.js
|
|
46
|
-
function_query:
|
|
47
|
-
name: constructor
|
|
48
|
-
type: method
|
|
49
|
-
kind: sync
|
|
50
|
-
class: Embeddings
|
|
51
|
-
operator: traceSync
|
|
52
|
-
channel_name: "Embeddings_constructor"
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const {
|
|
4
|
-
EVP_SUBDOMAIN_HEADER_NAME,
|
|
5
|
-
EVP_SUBDOMAIN_HEADER_VALUE,
|
|
6
|
-
EVP_PROXY_AGENT_ENDPOINT
|
|
7
|
-
} = require('../../constants/writers')
|
|
8
|
-
const LLMObsBaseSpanWriter = require('./base')
|
|
9
|
-
|
|
10
|
-
class LLMObsAgentProxySpanWriter extends LLMObsBaseSpanWriter {
|
|
11
|
-
constructor (config) {
|
|
12
|
-
super({
|
|
13
|
-
intake: config.url?.hostname || config.hostname || 'localhost',
|
|
14
|
-
protocol: config.url?.protocol || 'http:',
|
|
15
|
-
endpoint: EVP_PROXY_AGENT_ENDPOINT,
|
|
16
|
-
port: config.url?.port || config.port
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
this._headers[EVP_SUBDOMAIN_HEADER_NAME] = EVP_SUBDOMAIN_HEADER_VALUE
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
module.exports = LLMObsAgentProxySpanWriter
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const { AGENTLESS_SPANS_ENDPOINT } = require('../../constants/writers')
|
|
4
|
-
const LLMObsBaseSpanWriter = require('./base')
|
|
5
|
-
|
|
6
|
-
class LLMObsAgentlessSpanWriter extends LLMObsBaseSpanWriter {
|
|
7
|
-
constructor (config) {
|
|
8
|
-
super({
|
|
9
|
-
intake: `llmobs-intake.${config.site}`,
|
|
10
|
-
endpoint: AGENTLESS_SPANS_ENDPOINT
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
this._headers['DD-API-KEY'] = config.apiKey
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
module.exports = LLMObsAgentlessSpanWriter
|