dd-trace 4.47.1 → 4.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/LICENSE-3rdparty.csv +1 -1
- package/ext/types.d.ts +1 -0
- package/ext/types.js +1 -0
- package/index.d.ts +361 -0
- package/package.json +18 -13
- package/packages/datadog-code-origin/index.js +38 -0
- package/packages/datadog-core/index.js +2 -2
- package/packages/datadog-core/src/utils/src/parse-tags.js +33 -0
- package/packages/datadog-esbuild/index.js +4 -2
- package/packages/datadog-instrumentations/src/amqplib.js +65 -5
- package/packages/datadog-instrumentations/src/avsc.js +37 -0
- package/packages/datadog-instrumentations/src/azure-functions.js +48 -0
- package/packages/datadog-instrumentations/src/child_process.js +144 -27
- package/packages/datadog-instrumentations/src/express.js +37 -4
- package/packages/datadog-instrumentations/src/fastify.js +12 -1
- package/packages/datadog-instrumentations/src/fs.js +27 -7
- package/packages/datadog-instrumentations/src/helpers/hooks.js +6 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +9 -0
- package/packages/datadog-instrumentations/src/jest.js +2 -1
- package/packages/datadog-instrumentations/src/kafkajs.js +123 -63
- package/packages/datadog-instrumentations/src/mocha/common.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/utils.js +2 -2
- package/packages/datadog-instrumentations/src/multer.js +37 -0
- package/packages/datadog-instrumentations/src/mysql2.js +220 -1
- package/packages/datadog-instrumentations/src/openai.js +2 -2
- package/packages/datadog-instrumentations/src/protobufjs.js +127 -0
- package/packages/datadog-instrumentations/src/url.js +84 -0
- package/packages/datadog-instrumentations/src/utils/src/extract-package-and-module-path.js +7 -4
- package/packages/datadog-instrumentations/src/winston.js +22 -0
- package/packages/datadog-plugin-amqplib/src/consumer.js +4 -4
- package/packages/datadog-plugin-avsc/src/index.js +9 -0
- package/packages/datadog-plugin-avsc/src/schema_iterator.js +169 -0
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -0
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -0
- package/packages/datadog-plugin-aws-sdk/src/services/s3.js +1 -0
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -0
- package/packages/datadog-plugin-azure-functions/src/index.js +77 -0
- package/packages/datadog-plugin-fastify/src/code_origin.js +31 -0
- package/packages/datadog-plugin-fastify/src/index.js +10 -12
- package/packages/datadog-plugin-fastify/src/tracing.js +19 -0
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +8 -1
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +8 -0
- package/packages/datadog-plugin-grpc/src/client.js +3 -0
- package/packages/datadog-plugin-grpc/src/server.js +3 -0
- package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +6 -3
- package/packages/datadog-plugin-kafkajs/src/consumer.js +8 -4
- package/packages/datadog-plugin-kafkajs/src/producer.js +10 -4
- package/packages/datadog-plugin-mocha/src/index.js +4 -1
- package/packages/datadog-plugin-openai/src/index.js +9 -1015
- package/packages/datadog-plugin-openai/src/tracing.js +1023 -0
- package/packages/datadog-plugin-protobufjs/src/index.js +14 -0
- package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +180 -0
- package/packages/dd-trace/src/appsec/addresses.js +8 -1
- package/packages/dd-trace/src/appsec/channels.js +7 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +13 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +8 -1
- package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
- package/packages/dd-trace/src/appsec/iast/index.js +3 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +1 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +55 -7
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +15 -0
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -2
- package/packages/dd-trace/src/appsec/index.js +61 -43
- package/packages/dd-trace/src/appsec/rasp/command_injection.js +49 -0
- package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +99 -0
- package/packages/dd-trace/src/appsec/rasp/index.js +27 -10
- package/packages/dd-trace/src/appsec/rasp/lfi.js +112 -0
- package/packages/dd-trace/src/appsec/rasp/sql_injection.js +24 -4
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +4 -3
- package/packages/dd-trace/src/appsec/rasp/utils.js +4 -2
- package/packages/dd-trace/src/appsec/recommended.json +3 -7
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +6 -1
- package/packages/dd-trace/src/appsec/remote_config/index.js +10 -0
- package/packages/dd-trace/src/appsec/reporter.js +17 -9
- package/packages/dd-trace/src/appsec/sdk/track_event.js +10 -3
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +1 -1
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +4 -0
- package/packages/dd-trace/src/azure_metadata.js +120 -0
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +97 -0
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +90 -0
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +2 -14
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +19 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +53 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +8 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +43 -0
- package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +53 -0
- package/packages/dd-trace/src/config.js +86 -6
- package/packages/dd-trace/src/constants.js +3 -1
- package/packages/dd-trace/src/datastreams/pathway.js +1 -0
- package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +25 -17
- package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +52 -5
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +4 -4
- package/packages/dd-trace/src/debugger/devtools_client/send.js +29 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +187 -0
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +40 -0
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +252 -0
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/symbols.js +6 -0
- package/packages/dd-trace/src/debugger/devtools_client/state.js +19 -4
- package/packages/dd-trace/src/debugger/index.js +10 -3
- package/packages/dd-trace/src/exporters/common/request.js +8 -34
- package/packages/dd-trace/src/exporters/common/url-to-http-options-polyfill.js +31 -0
- package/packages/dd-trace/src/llmobs/constants/tags.js +34 -0
- package/packages/dd-trace/src/llmobs/constants/text.js +6 -0
- package/packages/dd-trace/src/llmobs/constants/writers.js +13 -0
- package/packages/dd-trace/src/llmobs/index.js +103 -0
- package/packages/dd-trace/src/llmobs/noop.js +82 -0
- package/packages/dd-trace/src/llmobs/plugins/base.js +65 -0
- package/packages/dd-trace/src/llmobs/plugins/openai.js +205 -0
- package/packages/dd-trace/src/llmobs/sdk.js +377 -0
- package/packages/dd-trace/src/llmobs/span_processor.js +195 -0
- package/packages/dd-trace/src/llmobs/storage.js +7 -0
- package/packages/dd-trace/src/llmobs/tagger.js +322 -0
- package/packages/dd-trace/src/llmobs/util.js +176 -0
- package/packages/dd-trace/src/llmobs/writers/base.js +111 -0
- package/packages/dd-trace/src/llmobs/writers/evaluations.js +29 -0
- package/packages/dd-trace/src/llmobs/writers/spans/agentProxy.js +23 -0
- package/packages/dd-trace/src/llmobs/writers/spans/agentless.js +17 -0
- package/packages/dd-trace/src/llmobs/writers/spans/base.js +49 -0
- package/packages/dd-trace/src/noop/proxy.js +3 -0
- package/packages/dd-trace/src/noop/span.js +3 -0
- package/packages/dd-trace/src/opentelemetry/span.js +1 -1
- package/packages/dd-trace/src/opentelemetry/tracer.js +1 -0
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +73 -12
- package/packages/dd-trace/src/opentracing/span.js +12 -0
- package/packages/dd-trace/src/opentracing/tracer.js +8 -1
- package/packages/dd-trace/src/payload-tagging/config/aws.json +71 -3
- package/packages/dd-trace/src/payload-tagging/index.js +1 -1
- package/packages/dd-trace/src/payload-tagging/jsonpath-plus.js +2094 -0
- package/packages/dd-trace/src/plugin_manager.js +4 -2
- package/packages/dd-trace/src/plugins/ci_plugin.js +2 -0
- package/packages/dd-trace/src/plugins/index.js +3 -0
- package/packages/dd-trace/src/plugins/log_plugin.js +1 -1
- package/packages/dd-trace/src/plugins/outbound.js +9 -0
- package/packages/dd-trace/src/plugins/schema.js +35 -0
- package/packages/dd-trace/src/plugins/util/ci.js +23 -1
- package/packages/dd-trace/src/plugins/util/serverless.js +7 -0
- package/packages/dd-trace/src/plugins/util/stacktrace.js +94 -0
- package/packages/dd-trace/src/plugins/util/tags.js +7 -0
- package/packages/dd-trace/src/plugins/util/test.js +20 -22
- package/packages/dd-trace/src/plugins/util/web.js +6 -4
- package/packages/dd-trace/src/priority_sampler.js +16 -0
- package/packages/dd-trace/src/profiling/config.js +3 -1
- package/packages/dd-trace/src/profiling/exporters/agent.js +7 -5
- package/packages/dd-trace/src/profiling/profiler.js +24 -14
- package/packages/dd-trace/src/profiling/profilers/events.js +3 -3
- package/packages/dd-trace/src/profiling/profilers/wall.js +95 -66
- package/packages/dd-trace/src/proxy.js +20 -1
- package/packages/dd-trace/src/service-naming/schemas/v0/index.js +2 -1
- package/packages/dd-trace/src/service-naming/schemas/v0/serverless.js +12 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/index.js +2 -1
- package/packages/dd-trace/src/service-naming/schemas/v1/serverless.js +12 -0
- package/packages/dd-trace/src/span_processor.js +5 -0
- package/packages/dd-trace/src/telemetry/index.js +11 -1
- package/packages/datadog-core/src/storage/async_resource.js +0 -108
- package/packages/datadog-core/src/storage/index.js +0 -5
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
SPAN_KINDS: ['llm', 'agent', 'workflow', 'task', 'tool', 'embedding', 'retrieval'],
|
|
5
|
+
SPAN_KIND: '_ml_obs.meta.span.kind',
|
|
6
|
+
SESSION_ID: '_ml_obs.session_id',
|
|
7
|
+
METADATA: '_ml_obs.meta.metadata',
|
|
8
|
+
METRICS: '_ml_obs.metrics',
|
|
9
|
+
ML_APP: '_ml_obs.meta.ml_app',
|
|
10
|
+
PROPAGATED_PARENT_ID_KEY: '_dd.p.llmobs_parent_id',
|
|
11
|
+
PARENT_ID_KEY: '_ml_obs.llmobs_parent_id',
|
|
12
|
+
TAGS: '_ml_obs.tags',
|
|
13
|
+
NAME: '_ml_obs.name',
|
|
14
|
+
TRACE_ID: '_ml_obs.trace_id',
|
|
15
|
+
PROPAGATED_TRACE_ID_KEY: '_dd.p.llmobs_trace_id',
|
|
16
|
+
ROOT_PARENT_ID: 'undefined',
|
|
17
|
+
|
|
18
|
+
MODEL_NAME: '_ml_obs.meta.model_name',
|
|
19
|
+
MODEL_PROVIDER: '_ml_obs.meta.model_provider',
|
|
20
|
+
|
|
21
|
+
INPUT_DOCUMENTS: '_ml_obs.meta.input.documents',
|
|
22
|
+
INPUT_MESSAGES: '_ml_obs.meta.input.messages',
|
|
23
|
+
INPUT_VALUE: '_ml_obs.meta.input.value',
|
|
24
|
+
|
|
25
|
+
OUTPUT_DOCUMENTS: '_ml_obs.meta.output.documents',
|
|
26
|
+
OUTPUT_MESSAGES: '_ml_obs.meta.output.messages',
|
|
27
|
+
OUTPUT_VALUE: '_ml_obs.meta.output.value',
|
|
28
|
+
|
|
29
|
+
INPUT_TOKENS_METRIC_KEY: 'input_tokens',
|
|
30
|
+
OUTPUT_TOKENS_METRIC_KEY: 'output_tokens',
|
|
31
|
+
TOTAL_TOKENS_METRIC_KEY: 'total_tokens',
|
|
32
|
+
|
|
33
|
+
DROPPED_IO_COLLECTION_ERROR: 'dropped_io'
|
|
34
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
EVP_PROXY_AGENT_BASE_PATH: 'evp_proxy/v2',
|
|
5
|
+
EVP_PROXY_AGENT_ENDPOINT: 'evp_proxy/v2/api/v2/llmobs',
|
|
6
|
+
EVP_SUBDOMAIN_HEADER_NAME: 'X-Datadog-EVP-Subdomain',
|
|
7
|
+
EVP_SUBDOMAIN_HEADER_VALUE: 'llmobs-intake',
|
|
8
|
+
AGENTLESS_SPANS_ENDPOINT: '/api/v2/llmobs',
|
|
9
|
+
AGENTLESS_EVALULATIONS_ENDPOINT: '/api/intake/llm-obs/v1/eval-metric',
|
|
10
|
+
|
|
11
|
+
EVP_PAYLOAD_SIZE_LIMIT: 5 << 20, // 5MB (actual limit is 5.1MB)
|
|
12
|
+
EVP_EVENT_SIZE_LIMIT: (1 << 20) - 1024 // 999KB (actual limit is 1MB)
|
|
13
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const log = require('../log')
|
|
4
|
+
const { PROPAGATED_PARENT_ID_KEY } = require('./constants/tags')
|
|
5
|
+
const { storage } = require('./storage')
|
|
6
|
+
|
|
7
|
+
const LLMObsSpanProcessor = require('./span_processor')
|
|
8
|
+
|
|
9
|
+
const { channel } = require('dc-polyfill')
|
|
10
|
+
const spanProcessCh = channel('dd-trace:span:process')
|
|
11
|
+
const evalMetricAppendCh = channel('llmobs:eval-metric:append')
|
|
12
|
+
const flushCh = channel('llmobs:writers:flush')
|
|
13
|
+
const injectCh = channel('dd-trace:span:inject')
|
|
14
|
+
|
|
15
|
+
const LLMObsAgentlessSpanWriter = require('./writers/spans/agentless')
|
|
16
|
+
const LLMObsAgentProxySpanWriter = require('./writers/spans/agentProxy')
|
|
17
|
+
const LLMObsEvalMetricsWriter = require('./writers/evaluations')
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Setting writers and processor globally when LLMObs is enabled
|
|
21
|
+
* We're setting these in this module instead of on the SDK.
|
|
22
|
+
* This is to isolate any subscribers and periodic tasks to this module,
|
|
23
|
+
* and not conditionally instantiate in the SDK, since the SDK is always instantiated
|
|
24
|
+
* if the tracer is `init`ed. But, in those cases, we don't want to start writers or subscribe
|
|
25
|
+
* to channels.
|
|
26
|
+
*/
|
|
27
|
+
let spanProcessor
|
|
28
|
+
let spanWriter
|
|
29
|
+
let evalWriter
|
|
30
|
+
|
|
31
|
+
function enable (config) {
|
|
32
|
+
// create writers and eval writer append and flush channels
|
|
33
|
+
// span writer append is handled by the span processor
|
|
34
|
+
evalWriter = new LLMObsEvalMetricsWriter(config)
|
|
35
|
+
spanWriter = createSpanWriter(config)
|
|
36
|
+
|
|
37
|
+
evalMetricAppendCh.subscribe(handleEvalMetricAppend)
|
|
38
|
+
flushCh.subscribe(handleFlush)
|
|
39
|
+
|
|
40
|
+
// span processing
|
|
41
|
+
spanProcessor = new LLMObsSpanProcessor(config)
|
|
42
|
+
spanProcessor.setWriter(spanWriter)
|
|
43
|
+
spanProcessCh.subscribe(handleSpanProcess)
|
|
44
|
+
|
|
45
|
+
// distributed tracing for llmobs
|
|
46
|
+
injectCh.subscribe(handleLLMObsParentIdInjection)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function disable () {
|
|
50
|
+
if (evalMetricAppendCh.hasSubscribers) evalMetricAppendCh.unsubscribe(handleEvalMetricAppend)
|
|
51
|
+
if (flushCh.hasSubscribers) flushCh.unsubscribe(handleFlush)
|
|
52
|
+
if (spanProcessCh.hasSubscribers) spanProcessCh.unsubscribe(handleSpanProcess)
|
|
53
|
+
if (injectCh.hasSubscribers) injectCh.unsubscribe(handleLLMObsParentIdInjection)
|
|
54
|
+
|
|
55
|
+
spanWriter?.destroy()
|
|
56
|
+
evalWriter?.destroy()
|
|
57
|
+
spanProcessor?.setWriter(null)
|
|
58
|
+
|
|
59
|
+
spanWriter = null
|
|
60
|
+
evalWriter = null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// since LLMObs traces can extend between services and be the same trace,
|
|
64
|
+
// we need to propogate the parent id.
|
|
65
|
+
function handleLLMObsParentIdInjection ({ carrier }) {
|
|
66
|
+
const parent = storage.getStore()?.span
|
|
67
|
+
if (!parent) return
|
|
68
|
+
|
|
69
|
+
const parentId = parent?.context().toSpanId()
|
|
70
|
+
|
|
71
|
+
carrier['x-datadog-tags'] += `,${PROPAGATED_PARENT_ID_KEY}=${parentId}`
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function createSpanWriter (config) {
|
|
75
|
+
const SpanWriter = config.llmobs.agentlessEnabled ? LLMObsAgentlessSpanWriter : LLMObsAgentProxySpanWriter
|
|
76
|
+
return new SpanWriter(config)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function handleFlush () {
|
|
80
|
+
try {
|
|
81
|
+
spanWriter.flush()
|
|
82
|
+
evalWriter.flush()
|
|
83
|
+
} catch (e) {
|
|
84
|
+
log.warn(`Failed to flush LLMObs spans and evaluation metrics: ${e.message}`)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function handleSpanProcess (data) {
|
|
89
|
+
spanProcessor.process(data)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function handleEvalMetricAppend (payload) {
|
|
93
|
+
try {
|
|
94
|
+
evalWriter.append(payload)
|
|
95
|
+
} catch (e) {
|
|
96
|
+
log.warn(`
|
|
97
|
+
Failed to append evaluation metric to LLM Observability writer, likely due to an unserializable property.
|
|
98
|
+
Evaluation metrics won't be sent to LLM Observability: ${e.message}
|
|
99
|
+
`)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = { enable, disable }
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
class NoopLLMObs {
|
|
4
|
+
constructor (noopTracer) {
|
|
5
|
+
this._tracer = noopTracer
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
get enabled () {
|
|
9
|
+
return false
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
enable (options) {}
|
|
13
|
+
|
|
14
|
+
disable () {}
|
|
15
|
+
|
|
16
|
+
trace (options = {}, fn) {
|
|
17
|
+
if (typeof options === 'function') {
|
|
18
|
+
fn = options
|
|
19
|
+
options = {}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const name = options.name || options.kind || fn.name
|
|
23
|
+
|
|
24
|
+
return this._tracer.trace(name, options, fn)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
wrap (options = {}, fn) {
|
|
28
|
+
if (typeof options === 'function') {
|
|
29
|
+
fn = options
|
|
30
|
+
options = {}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const name = options.name || options.kind || fn.name
|
|
34
|
+
|
|
35
|
+
return this._tracer.wrap(name, options, fn)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
decorate (options = {}) {
|
|
39
|
+
const llmobs = this
|
|
40
|
+
return function (target, ctxOrPropertyKey, descriptor) {
|
|
41
|
+
if (!ctxOrPropertyKey) return target
|
|
42
|
+
if (typeof ctxOrPropertyKey === 'object') {
|
|
43
|
+
const ctx = ctxOrPropertyKey
|
|
44
|
+
if (ctx.kind !== 'method') return target
|
|
45
|
+
|
|
46
|
+
return llmobs.wrap({ name: ctx.name, ...options }, target)
|
|
47
|
+
} else {
|
|
48
|
+
const propertyKey = ctxOrPropertyKey
|
|
49
|
+
if (descriptor) {
|
|
50
|
+
if (typeof descriptor.value !== 'function') return descriptor
|
|
51
|
+
|
|
52
|
+
const original = descriptor.value
|
|
53
|
+
descriptor.value = llmobs.wrap({ name: propertyKey, ...options }, original)
|
|
54
|
+
|
|
55
|
+
return descriptor
|
|
56
|
+
} else {
|
|
57
|
+
if (typeof target[propertyKey] !== 'function') return target[propertyKey]
|
|
58
|
+
|
|
59
|
+
const original = target[propertyKey]
|
|
60
|
+
Object.defineProperty(target, propertyKey, {
|
|
61
|
+
...Object.getOwnPropertyDescriptor(target, propertyKey),
|
|
62
|
+
value: llmobs.wrap({ name: propertyKey, ...options }, original)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
return target
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
annotate (span, options) {}
|
|
72
|
+
|
|
73
|
+
exportSpan (span) {
|
|
74
|
+
return {}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
submitEvaluation (llmobsSpanContext, options) {}
|
|
78
|
+
|
|
79
|
+
flush () {}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = NoopLLMObs
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const log = require('../../log')
|
|
4
|
+
const { storage } = require('../storage')
|
|
5
|
+
|
|
6
|
+
const TracingPlugin = require('../../plugins/tracing')
|
|
7
|
+
const LLMObsTagger = require('../tagger')
|
|
8
|
+
|
|
9
|
+
// we make this a `Plugin` so we don't have to worry about `finish` being called
|
|
10
|
+
class LLMObsPlugin extends TracingPlugin {
|
|
11
|
+
constructor (...args) {
|
|
12
|
+
super(...args)
|
|
13
|
+
|
|
14
|
+
this._tagger = new LLMObsTagger(this._tracerConfig, true)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
getName () {}
|
|
18
|
+
|
|
19
|
+
setLLMObsTags (ctx) {
|
|
20
|
+
throw new Error('setLLMObsTags must be implemented by the subclass')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
getLLMObsSPanRegisterOptions (ctx) {
|
|
24
|
+
throw new Error('getLLMObsSPanRegisterOptions must be implemented by the subclass')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
start (ctx) {
|
|
28
|
+
const oldStore = storage.getStore()
|
|
29
|
+
const parent = oldStore?.span
|
|
30
|
+
const span = ctx.currentStore?.span
|
|
31
|
+
|
|
32
|
+
const registerOptions = this.getLLMObsSPanRegisterOptions(ctx)
|
|
33
|
+
|
|
34
|
+
this._tagger.registerLLMObsSpan(span, { parent, ...registerOptions })
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
asyncEnd (ctx) {
|
|
38
|
+
// even though llmobs span events won't be enqueued if llmobs is disabled
|
|
39
|
+
// we should avoid doing any computations here (these listeners aren't disabled)
|
|
40
|
+
const enabled = this._tracerConfig.llmobs.enabled
|
|
41
|
+
if (!enabled) return
|
|
42
|
+
|
|
43
|
+
const span = ctx.currentStore?.span
|
|
44
|
+
if (!span) {
|
|
45
|
+
log.debug(
|
|
46
|
+
`Tried to start an LLMObs span for ${this.constructor.name} without an active APM span.
|
|
47
|
+
Not starting LLMObs span.`
|
|
48
|
+
)
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.setLLMObsTags(ctx)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
configure (config) {
|
|
56
|
+
// we do not want to enable any LLMObs plugins if it is disabled on the tracer
|
|
57
|
+
const llmobsEnabled = this._tracerConfig.llmobs.enabled
|
|
58
|
+
if (llmobsEnabled === false) {
|
|
59
|
+
config = typeof config === 'boolean' ? false : { ...config, enabled: false } // override to false
|
|
60
|
+
}
|
|
61
|
+
super.configure(config)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = LLMObsPlugin
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const LLMObsPlugin = require('./base')
|
|
4
|
+
|
|
5
|
+
class OpenAiLLMObsPlugin extends LLMObsPlugin {
|
|
6
|
+
static get prefix () {
|
|
7
|
+
return 'tracing:apm:openai:request'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
getLLMObsSPanRegisterOptions (ctx) {
|
|
11
|
+
const resource = ctx.methodName
|
|
12
|
+
const methodName = gateResource(normalizeOpenAIResourceName(resource))
|
|
13
|
+
if (!methodName) return // we will not trace all openai methods for llmobs
|
|
14
|
+
|
|
15
|
+
const inputs = ctx.args[0] // completion, chat completion, and embeddings take one argument
|
|
16
|
+
const operation = getOperation(methodName)
|
|
17
|
+
const kind = operation === 'embedding' ? 'embedding' : 'llm'
|
|
18
|
+
const name = `openai.${methodName}`
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
modelProvider: 'openai',
|
|
22
|
+
modelName: inputs.model,
|
|
23
|
+
kind,
|
|
24
|
+
name
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
setLLMObsTags (ctx) {
|
|
29
|
+
const span = ctx.currentStore?.span
|
|
30
|
+
const resource = ctx.methodName
|
|
31
|
+
const methodName = gateResource(normalizeOpenAIResourceName(resource))
|
|
32
|
+
if (!methodName) return // we will not trace all openai methods for llmobs
|
|
33
|
+
|
|
34
|
+
const inputs = ctx.args[0] // completion, chat completion, and embeddings take one argument
|
|
35
|
+
const response = ctx.result?.data // no result if error
|
|
36
|
+
const error = !!span.context()._tags.error
|
|
37
|
+
|
|
38
|
+
const operation = getOperation(methodName)
|
|
39
|
+
|
|
40
|
+
if (operation === 'completion') {
|
|
41
|
+
this._tagCompletion(span, inputs, response, error)
|
|
42
|
+
} else if (operation === 'chat') {
|
|
43
|
+
this._tagChatCompletion(span, inputs, response, error)
|
|
44
|
+
} else if (operation === 'embedding') {
|
|
45
|
+
this._tagEmbedding(span, inputs, response, error)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!error) {
|
|
49
|
+
const metrics = this._extractMetrics(response)
|
|
50
|
+
this._tagger.tagMetrics(span, metrics)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
_extractMetrics (response) {
|
|
55
|
+
const metrics = {}
|
|
56
|
+
const tokenUsage = response.usage
|
|
57
|
+
|
|
58
|
+
if (tokenUsage) {
|
|
59
|
+
const inputTokens = tokenUsage.prompt_tokens
|
|
60
|
+
if (inputTokens) metrics.inputTokens = inputTokens
|
|
61
|
+
|
|
62
|
+
const outputTokens = tokenUsage.completion_tokens
|
|
63
|
+
if (outputTokens) metrics.outputTokens = outputTokens
|
|
64
|
+
|
|
65
|
+
const totalTokens = tokenUsage.total_toksn || (inputTokens + outputTokens)
|
|
66
|
+
if (totalTokens) metrics.totalTokens = totalTokens
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return metrics
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
_tagEmbedding (span, inputs, response, error) {
|
|
73
|
+
const { model, ...parameters } = inputs
|
|
74
|
+
|
|
75
|
+
const metadata = {
|
|
76
|
+
encoding_format: parameters.encoding_format || 'float'
|
|
77
|
+
}
|
|
78
|
+
if (inputs.dimensions) metadata.dimensions = inputs.dimensions
|
|
79
|
+
this._tagger.tagMetadata(span, metadata)
|
|
80
|
+
|
|
81
|
+
let embeddingInputs = inputs.input
|
|
82
|
+
if (!Array.isArray(embeddingInputs)) embeddingInputs = [embeddingInputs]
|
|
83
|
+
const embeddingInput = embeddingInputs.map(input => ({ text: input }))
|
|
84
|
+
|
|
85
|
+
if (error) {
|
|
86
|
+
this._tagger.tagEmbeddingIO(span, embeddingInput, undefined)
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const float = Array.isArray(response.data[0].embedding)
|
|
91
|
+
let embeddingOutput
|
|
92
|
+
if (float) {
|
|
93
|
+
const embeddingDim = response.data[0].embedding.length
|
|
94
|
+
embeddingOutput = `[${response.data.length} embedding(s) returned with size ${embeddingDim}]`
|
|
95
|
+
} else {
|
|
96
|
+
embeddingOutput = `[${response.data.length} embedding(s) returned]`
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this._tagger.tagEmbeddingIO(span, embeddingInput, embeddingOutput)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
_tagCompletion (span, inputs, response, error) {
|
|
103
|
+
let { prompt, model, ...parameters } = inputs
|
|
104
|
+
if (!Array.isArray(prompt)) prompt = [prompt]
|
|
105
|
+
|
|
106
|
+
const completionInput = prompt.map(p => ({ content: p }))
|
|
107
|
+
|
|
108
|
+
const completionOutput = error ? [{ content: '' }] : response.choices.map(choice => ({ content: choice.text }))
|
|
109
|
+
|
|
110
|
+
this._tagger.tagLLMIO(span, completionInput, completionOutput)
|
|
111
|
+
this._tagger.tagMetadata(span, parameters)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
_tagChatCompletion (span, inputs, response, error) {
|
|
115
|
+
const { messages, model, ...parameters } = inputs
|
|
116
|
+
|
|
117
|
+
if (error) {
|
|
118
|
+
this._tagger.tagLLMIO(span, messages, [{ content: '' }])
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const outputMessages = []
|
|
123
|
+
const { choices } = response
|
|
124
|
+
for (const choice of choices) {
|
|
125
|
+
const message = choice.message || choice.delta
|
|
126
|
+
const content = message.content || ''
|
|
127
|
+
const role = message.role
|
|
128
|
+
|
|
129
|
+
if (message.function_call) {
|
|
130
|
+
const functionCallInfo = {
|
|
131
|
+
name: message.function_call.name,
|
|
132
|
+
arguments: JSON.parse(message.function_call.arguments)
|
|
133
|
+
}
|
|
134
|
+
outputMessages.push({ content, role, toolCalls: [functionCallInfo] })
|
|
135
|
+
} else if (message.tool_calls) {
|
|
136
|
+
const toolCallsInfo = []
|
|
137
|
+
for (const toolCall of message.tool_calls) {
|
|
138
|
+
const toolCallInfo = {
|
|
139
|
+
arguments: JSON.parse(toolCall.function.arguments),
|
|
140
|
+
name: toolCall.function.name,
|
|
141
|
+
toolId: toolCall.id,
|
|
142
|
+
type: toolCall.type
|
|
143
|
+
}
|
|
144
|
+
toolCallsInfo.push(toolCallInfo)
|
|
145
|
+
}
|
|
146
|
+
outputMessages.push({ content, role, toolCalls: toolCallsInfo })
|
|
147
|
+
} else {
|
|
148
|
+
outputMessages.push({ content, role })
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
this._tagger.tagLLMIO(span, messages, outputMessages)
|
|
153
|
+
|
|
154
|
+
const metadata = Object.entries(parameters).reduce((obj, [key, value]) => {
|
|
155
|
+
if (!['tools', 'functions'].includes(key)) {
|
|
156
|
+
obj[key] = value
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return obj
|
|
160
|
+
}, {})
|
|
161
|
+
|
|
162
|
+
this._tagger.tagMetadata(span, metadata)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// TODO: this will be moved to the APM integration
|
|
167
|
+
function normalizeOpenAIResourceName (resource) {
|
|
168
|
+
switch (resource) {
|
|
169
|
+
// completions
|
|
170
|
+
case 'completions.create':
|
|
171
|
+
return 'createCompletion'
|
|
172
|
+
|
|
173
|
+
// chat completions
|
|
174
|
+
case 'chat.completions.create':
|
|
175
|
+
return 'createChatCompletion'
|
|
176
|
+
|
|
177
|
+
// embeddings
|
|
178
|
+
case 'embeddings.create':
|
|
179
|
+
return 'createEmbedding'
|
|
180
|
+
default:
|
|
181
|
+
return resource
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function gateResource (resource) {
|
|
186
|
+
return ['createCompletion', 'createChatCompletion', 'createEmbedding'].includes(resource)
|
|
187
|
+
? resource
|
|
188
|
+
: undefined
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function getOperation (resource) {
|
|
192
|
+
switch (resource) {
|
|
193
|
+
case 'createCompletion':
|
|
194
|
+
return 'completion'
|
|
195
|
+
case 'createChatCompletion':
|
|
196
|
+
return 'chat'
|
|
197
|
+
case 'createEmbedding':
|
|
198
|
+
return 'embedding'
|
|
199
|
+
default:
|
|
200
|
+
// should never happen
|
|
201
|
+
return 'unknown'
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
module.exports = OpenAiLLMObsPlugin
|