dd-trace 5.80.0 → 5.81.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 +79 -88
- package/ext/tags.d.ts +1 -0
- package/ext/tags.js +1 -0
- package/index.d.ts +35 -35
- package/loader-hook.mjs +10 -3
- package/package.json +22 -40
- package/packages/datadog-esbuild/index.js +36 -19
- package/packages/datadog-instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/anthropic.js +12 -0
- package/packages/datadog-instrumentations/src/aws-sdk.js +5 -1
- package/packages/datadog-instrumentations/src/cucumber.js +2 -2
- package/packages/datadog-instrumentations/src/find-my-way.js +6 -5
- package/packages/datadog-instrumentations/src/google-genai.js +120 -0
- package/packages/datadog-instrumentations/src/graphql.js +20 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/instrument.js +10 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +6 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +27 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +152 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +5 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langchain.js +237 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/loader.js +9 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +11 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +139 -0
- package/packages/datadog-instrumentations/src/langchain.js +3 -109
- package/packages/datadog-instrumentations/src/mocha/main.js +1 -1
- package/packages/datadog-instrumentations/src/mysql2.js +1 -1
- package/packages/datadog-instrumentations/src/playwright.js +45 -16
- package/packages/datadog-instrumentations/src/router.js +1 -1
- package/packages/datadog-instrumentations/src/selenium.js +3 -1
- package/packages/datadog-instrumentations/src/ws.js +35 -17
- package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +1 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +23 -2
- package/packages/datadog-plugin-cypress/src/plugin.js +1 -1
- package/packages/datadog-plugin-cypress/src/support.js +73 -31
- package/packages/datadog-plugin-google-genai/src/index.js +17 -0
- package/packages/datadog-plugin-google-genai/src/tracing.js +41 -0
- package/packages/datadog-plugin-graphql/src/tools/transforms.js +5 -4
- package/packages/datadog-plugin-jest/src/util.js +1 -1
- package/packages/datadog-plugin-langchain/src/tracing.js +7 -3
- package/packages/datadog-plugin-next/src/index.js +11 -3
- package/packages/dd-trace/src/aiguard/sdk.js +18 -10
- package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
- package/packages/dd-trace/src/appsec/iast/overhead-controller.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +1 -2
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +1 -1
- package/packages/dd-trace/src/appsec/reporter.js +0 -4
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +4 -8
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +4 -2
- package/packages/dd-trace/src/config.js +81 -7
- package/packages/dd-trace/src/config_defaults.js +14 -2
- package/packages/dd-trace/src/datastreams/encoding.js +23 -6
- package/packages/dd-trace/src/datastreams/pathway.js +40 -1
- package/packages/dd-trace/src/datastreams/processor.js +1 -1
- package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +15 -5
- package/packages/dd-trace/src/debugger/devtools_client/condition.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +30 -15
- package/packages/dd-trace/src/debugger/devtools_client/inspector_promises_polyfill.js +2 -0
- package/packages/dd-trace/src/debugger/devtools_client/json-buffer.js +24 -18
- package/packages/dd-trace/src/debugger/devtools_client/send.js +18 -8
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +103 -15
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/constants.js +25 -0
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +56 -25
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +64 -23
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/symbols.js +3 -1
- package/packages/dd-trace/src/debugger/devtools_client/snapshot-pruner.js +404 -0
- package/packages/dd-trace/src/debugger/devtools_client/source-maps.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/state.js +7 -2
- package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
- package/packages/dd-trace/src/debugger/index.js +1 -1
- package/packages/dd-trace/src/encode/span-stats.js +7 -1
- package/packages/dd-trace/src/histogram.js +1 -1
- package/packages/dd-trace/src/id.js +60 -0
- package/packages/dd-trace/src/lambda/runtime/ritm.js +1 -1
- package/packages/dd-trace/src/llmobs/constants/tags.js +1 -0
- package/packages/dd-trace/src/llmobs/plugins/genai/index.js +104 -0
- package/packages/dd-trace/src/llmobs/plugins/genai/util.js +486 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +2 -2
- package/packages/dd-trace/src/llmobs/plugins/{openai.js → openai/index.js} +48 -6
- package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +114 -0
- package/packages/dd-trace/src/llmobs/sdk.js +5 -0
- package/packages/dd-trace/src/llmobs/span_processor.js +6 -1
- package/packages/dd-trace/src/llmobs/tagger.js +4 -0
- package/packages/dd-trace/src/opentelemetry/logs/index.js +2 -2
- package/packages/dd-trace/src/opentelemetry/logs/logger.js +3 -2
- package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +5 -3
- package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +8 -8
- package/packages/dd-trace/src/opentelemetry/metrics/constants.js +34 -0
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +81 -0
- package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +225 -0
- package/packages/dd-trace/src/opentelemetry/metrics/meter.js +171 -0
- package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +54 -0
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +62 -0
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +251 -0
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +532 -0
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +10 -18
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +36 -22
- package/packages/dd-trace/src/opentelemetry/otlp/protobuf_loader.js +1 -1
- package/packages/dd-trace/src/opentelemetry/span.js +1 -1
- package/packages/dd-trace/src/opentelemetry/tracer.js +1 -1
- package/packages/dd-trace/src/opentelemetry/tracer_provider.js +1 -1
- package/packages/dd-trace/src/payload-tagging/index.js +2 -2
- package/packages/dd-trace/src/plugin_manager.js +4 -2
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/test.js +3 -3
- package/packages/dd-trace/src/plugins/util/url.js +119 -1
- package/packages/dd-trace/src/plugins/util/web.js +10 -41
- package/packages/dd-trace/src/process-tags/index.js +81 -0
- package/packages/dd-trace/src/profiling/config.js +1 -1
- package/packages/dd-trace/src/profiling/exporters/agent.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/events.js +10 -1
- package/packages/dd-trace/src/proxy.js +5 -0
- package/packages/dd-trace/src/rate_limiter.js +1 -1
- package/packages/dd-trace/src/remote_config/manager.js +1 -1
- package/packages/dd-trace/src/ritm.js +1 -1
- package/packages/dd-trace/src/service-naming/schemas/v0/web.js +4 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/web.js +4 -0
- package/packages/dd-trace/src/span_format.js +9 -4
- package/packages/dd-trace/src/span_processor.js +8 -3
- package/packages/dd-trace/src/span_stats.js +15 -4
- package/packages/dd-trace/src/spanleak.js +1 -1
- package/packages/dd-trace/src/supported-configurations.json +13 -0
- package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
- package/packages/dd-trace/src/telemetry/telemetry.js +11 -2
- package/vendor/dist/@datadog/sketches-js/LICENSE +39 -0
- package/vendor/dist/@datadog/sketches-js/index.js +1 -0
- package/vendor/dist/@datadog/source-map/LICENSE +28 -0
- package/vendor/dist/@datadog/source-map/index.js +1 -0
- package/vendor/dist/@isaacs/ttlcache/LICENSE +55 -0
- package/vendor/dist/@isaacs/ttlcache/index.js +1 -0
- package/vendor/dist/@opentelemetry/core/LICENSE +201 -0
- package/vendor/dist/@opentelemetry/core/index.js +1 -0
- package/vendor/dist/@opentelemetry/resources/LICENSE +201 -0
- package/vendor/dist/@opentelemetry/resources/index.js +1 -0
- package/vendor/dist/astring/LICENSE +19 -0
- package/vendor/dist/astring/index.js +1 -0
- package/vendor/dist/crypto-randomuuid/index.js +1 -0
- package/vendor/dist/escape-string-regexp/LICENSE +9 -0
- package/vendor/dist/escape-string-regexp/index.js +1 -0
- package/vendor/dist/esquery/LICENSE +24 -0
- package/vendor/dist/esquery/index.js +1 -0
- package/vendor/dist/ignore/LICENSE +21 -0
- package/vendor/dist/ignore/index.js +1 -0
- package/vendor/dist/istanbul-lib-coverage/LICENSE +24 -0
- package/vendor/dist/istanbul-lib-coverage/index.js +1 -0
- package/vendor/dist/jest-docblock/LICENSE +21 -0
- package/vendor/dist/jest-docblock/index.js +1 -0
- package/vendor/dist/jsonpath-plus/LICENSE +22 -0
- package/vendor/dist/jsonpath-plus/index.js +1 -0
- package/vendor/dist/limiter/LICENSE +19 -0
- package/vendor/dist/limiter/index.js +1 -0
- package/vendor/dist/lodash.sortby/LICENSE +47 -0
- package/vendor/dist/lodash.sortby/index.js +1 -0
- package/vendor/dist/lru-cache/LICENSE +15 -0
- package/vendor/dist/lru-cache/index.js +1 -0
- package/vendor/dist/meriyah/LICENSE +7 -0
- package/vendor/dist/meriyah/index.js +1 -0
- package/vendor/dist/module-details-from-path/LICENSE +21 -0
- package/vendor/dist/module-details-from-path/index.js +1 -0
- package/vendor/dist/mutexify/promise/LICENSE +21 -0
- package/vendor/dist/mutexify/promise/index.js +1 -0
- package/vendor/dist/opentracing/LICENSE +201 -0
- package/vendor/dist/opentracing/binary_carrier.d.ts +11 -0
- package/vendor/dist/opentracing/constants.d.ts +61 -0
- package/vendor/dist/opentracing/examples/demo/demo.d.ts +2 -0
- package/vendor/dist/opentracing/ext/tags.d.ts +90 -0
- package/vendor/dist/opentracing/functions.d.ts +20 -0
- package/vendor/dist/opentracing/global_tracer.d.ts +14 -0
- package/vendor/dist/opentracing/index.d.ts +12 -0
- package/vendor/dist/opentracing/index.js +1 -0
- package/vendor/dist/opentracing/mock_tracer/index.d.ts +5 -0
- package/vendor/dist/opentracing/mock_tracer/mock_context.d.ts +13 -0
- package/vendor/dist/opentracing/mock_tracer/mock_report.d.ts +16 -0
- package/vendor/dist/opentracing/mock_tracer/mock_span.d.ts +50 -0
- package/vendor/dist/opentracing/mock_tracer/mock_tracer.d.ts +26 -0
- package/vendor/dist/opentracing/noop.d.ts +8 -0
- package/vendor/dist/opentracing/reference.d.ts +33 -0
- package/vendor/dist/opentracing/span.d.ts +147 -0
- package/vendor/dist/opentracing/span_context.d.ts +26 -0
- package/vendor/dist/opentracing/test/api_compatibility.d.ts +16 -0
- package/vendor/dist/opentracing/test/mocktracer_implemenation.d.ts +3 -0
- package/vendor/dist/opentracing/test/noop_implementation.d.ts +4 -0
- package/vendor/dist/opentracing/test/opentracing_api.d.ts +3 -0
- package/vendor/dist/opentracing/test/unittest.d.ts +2 -0
- package/vendor/dist/opentracing/tracer.d.ts +127 -0
- package/vendor/dist/path-to-regexp/LICENSE +21 -0
- package/vendor/dist/path-to-regexp/index.js +1 -0
- package/vendor/dist/pprof-format/LICENSE +8 -0
- package/vendor/dist/pprof-format/index.js +1 -0
- package/vendor/dist/protobufjs/LICENSE +39 -0
- package/vendor/dist/protobufjs/index.js +1 -0
- package/vendor/dist/protobufjs/minimal/LICENSE +39 -0
- package/vendor/dist/protobufjs/minimal/index.js +1 -0
- package/vendor/dist/retry/LICENSE +21 -0
- package/vendor/dist/retry/index.js +1 -0
- package/vendor/dist/rfdc/LICENSE +15 -0
- package/vendor/dist/rfdc/index.js +1 -0
- package/vendor/dist/semifies/LICENSE +201 -0
- package/vendor/dist/semifies/index.js +1 -0
- package/vendor/dist/shell-quote/LICENSE +24 -0
- package/vendor/dist/shell-quote/index.js +1 -0
- package/vendor/dist/source-map/LICENSE +28 -0
- package/vendor/dist/source-map/index.js +1 -0
- package/vendor/dist/source-map/lib/util/LICENSE +28 -0
- package/vendor/dist/source-map/lib/util/index.js +1 -0
- package/vendor/dist/source-map/mappings.wasm +0 -0
- package/vendor/dist/tlhunter-sorted-set/LICENSE +21 -0
- package/vendor/dist/tlhunter-sorted-set/index.js +1 -0
- package/vendor/dist/ttl-set/LICENSE +21 -0
- package/vendor/dist/ttl-set/index.js +1 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const LLMObsPlugin = require('../base')
|
|
4
|
+
const {
|
|
5
|
+
getOperation,
|
|
6
|
+
extractMetrics,
|
|
7
|
+
extractMetadata,
|
|
8
|
+
aggregateStreamingChunks,
|
|
9
|
+
formatInputMessages,
|
|
10
|
+
formatEmbeddingInput,
|
|
11
|
+
formatOutputMessages,
|
|
12
|
+
formatEmbeddingOutput
|
|
13
|
+
} = require('./util')
|
|
14
|
+
|
|
15
|
+
class GenAiLLMObsPlugin extends LLMObsPlugin {
|
|
16
|
+
static id = 'google-genai'
|
|
17
|
+
static integration = 'google_genai'
|
|
18
|
+
static prefix = 'tracing:apm:google:genai:request'
|
|
19
|
+
|
|
20
|
+
constructor () {
|
|
21
|
+
super(...arguments)
|
|
22
|
+
|
|
23
|
+
// Subscribe to streaming chunk events
|
|
24
|
+
this.addSub('apm:google:genai:request:chunk', ({ ctx, chunk, done }) => {
|
|
25
|
+
ctx.isStreaming = true
|
|
26
|
+
ctx.chunks = ctx.chunks || []
|
|
27
|
+
|
|
28
|
+
if (chunk) ctx.chunks.push(chunk)
|
|
29
|
+
if (!done) return
|
|
30
|
+
|
|
31
|
+
// Aggregate streaming chunks into a single response
|
|
32
|
+
ctx.result = aggregateStreamingChunks(ctx.chunks)
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getLLMObsSpanRegisterOptions (ctx) {
|
|
37
|
+
const { args, methodName } = ctx
|
|
38
|
+
if (!methodName) return
|
|
39
|
+
|
|
40
|
+
const inputs = args[0]
|
|
41
|
+
const operation = getOperation(methodName)
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
modelProvider: 'google',
|
|
45
|
+
modelName: inputs.model,
|
|
46
|
+
kind: operation,
|
|
47
|
+
name: 'google_genai.request'
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
setLLMObsTags (ctx) {
|
|
52
|
+
const { args, methodName } = ctx
|
|
53
|
+
const span = ctx.currentStore?.span
|
|
54
|
+
if (!methodName) return
|
|
55
|
+
|
|
56
|
+
const inputs = args[0]
|
|
57
|
+
const response = ctx.result
|
|
58
|
+
const error = !!span.context()._tags.error
|
|
59
|
+
|
|
60
|
+
const operation = getOperation(methodName)
|
|
61
|
+
|
|
62
|
+
if (operation === 'llm') {
|
|
63
|
+
this.#tagGenerateContent(span, inputs, response, error, ctx.isStreaming)
|
|
64
|
+
} else if (operation === 'embedding') {
|
|
65
|
+
this.#tagEmbedding(span, inputs, response, error)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!error && response) {
|
|
69
|
+
const metrics = extractMetrics(response)
|
|
70
|
+
this._tagger.tagMetrics(span, metrics)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#tagGenerateContent (span, inputs, response, error, isStreaming = false) {
|
|
75
|
+
const { config = {} } = inputs
|
|
76
|
+
|
|
77
|
+
const inputMessages = formatInputMessages(inputs.contents)
|
|
78
|
+
|
|
79
|
+
const metadata = extractMetadata(config)
|
|
80
|
+
this._tagger.tagMetadata(span, metadata)
|
|
81
|
+
|
|
82
|
+
if (error) {
|
|
83
|
+
this._tagger.tagLLMIO(span, inputMessages, [{ content: '' }])
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const outputMessages = formatOutputMessages(response, isStreaming)
|
|
88
|
+
this._tagger.tagLLMIO(span, inputMessages, outputMessages)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
#tagEmbedding (span, inputs, response, error) {
|
|
92
|
+
const embeddingInput = formatEmbeddingInput(inputs.contents)
|
|
93
|
+
|
|
94
|
+
if (error) {
|
|
95
|
+
this._tagger.tagEmbeddingIO(span, embeddingInput)
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const embeddingOutput = formatEmbeddingOutput(response)
|
|
100
|
+
this._tagger.tagEmbeddingIO(span, embeddingInput, embeddingOutput)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = GenAiLLMObsPlugin
|
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Constants for role mapping
|
|
4
|
+
const ROLES = {
|
|
5
|
+
MODEL: 'model',
|
|
6
|
+
ASSISTANT: 'assistant',
|
|
7
|
+
USER: 'user',
|
|
8
|
+
REASONING: 'reasoning'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get the operation type from the method name
|
|
13
|
+
* @param {string} methodName
|
|
14
|
+
* @returns {'embedding' | 'llm'}
|
|
15
|
+
*/
|
|
16
|
+
function getOperation (methodName) {
|
|
17
|
+
return methodName.includes('embed') ? 'embedding' : 'llm'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Extract text parts from an array of parts
|
|
22
|
+
* @param {Array<{text?: string}>} parts
|
|
23
|
+
* @returns {string[]}
|
|
24
|
+
*/
|
|
25
|
+
function extractTextParts (parts) {
|
|
26
|
+
return parts
|
|
27
|
+
.filter(part => part.text)
|
|
28
|
+
.map(part => part.text)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Group parts by role (reasoning vs assistant)
|
|
33
|
+
* @param {Array<{text?: string, thought?: boolean}>} parts
|
|
34
|
+
* @returns {{reasoning: string, assistant: string}}
|
|
35
|
+
*/
|
|
36
|
+
function groupPartsByRole (parts) {
|
|
37
|
+
const grouped = {
|
|
38
|
+
reasoning: '',
|
|
39
|
+
assistant: ''
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (const part of parts) {
|
|
43
|
+
if (!part.text) continue
|
|
44
|
+
|
|
45
|
+
if (part.thought === true) {
|
|
46
|
+
grouped.reasoning += part.text
|
|
47
|
+
} else {
|
|
48
|
+
grouped.assistant += part.text
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return grouped
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check if parts contain thought/reasoning content
|
|
57
|
+
* @param {Array<{thought?: boolean}>} parts
|
|
58
|
+
* @returns {boolean}
|
|
59
|
+
*/
|
|
60
|
+
function hasThoughtParts (parts) {
|
|
61
|
+
return parts.some(part => part.thought === true)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Determine the role from a candidate and its parts
|
|
66
|
+
* @param {object} candidate
|
|
67
|
+
* @param {Array<{thought?: boolean}>} parts
|
|
68
|
+
* @returns {string}
|
|
69
|
+
*/
|
|
70
|
+
function determineRole (candidate, parts = []) {
|
|
71
|
+
// Check parts for thought indicators
|
|
72
|
+
if (hasThoughtParts(parts)) {
|
|
73
|
+
return ROLES.REASONING
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Extract role from various possible locations
|
|
77
|
+
const rawRole = candidate.role ||
|
|
78
|
+
candidate.content?.role ||
|
|
79
|
+
candidate[0]?.content?.role
|
|
80
|
+
|
|
81
|
+
return normalizeRole(rawRole)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Normalize role to standard values
|
|
86
|
+
* @param {string} role
|
|
87
|
+
* @returns {string}
|
|
88
|
+
*/
|
|
89
|
+
function normalizeRole (role) {
|
|
90
|
+
if (role === ROLES.MODEL) return ROLES.ASSISTANT
|
|
91
|
+
if (role === ROLES.ASSISTANT) return ROLES.ASSISTANT
|
|
92
|
+
if (role === ROLES.USER) return ROLES.USER
|
|
93
|
+
if (role === ROLES.REASONING) return ROLES.REASONING
|
|
94
|
+
return ROLES.USER // default
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Extract metrics from response
|
|
99
|
+
* @param {object} response
|
|
100
|
+
* @returns {object}
|
|
101
|
+
*/
|
|
102
|
+
function extractMetrics (response) {
|
|
103
|
+
const metrics = {}
|
|
104
|
+
const tokenUsage = response.usageMetadata
|
|
105
|
+
|
|
106
|
+
if (!tokenUsage) return metrics
|
|
107
|
+
|
|
108
|
+
if (tokenUsage.promptTokenCount) {
|
|
109
|
+
metrics.inputTokens = tokenUsage.promptTokenCount
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (tokenUsage.candidatesTokenCount) {
|
|
113
|
+
metrics.outputTokens = tokenUsage.candidatesTokenCount
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const totalTokens = tokenUsage.totalTokenCount ||
|
|
117
|
+
(tokenUsage.promptTokenCount || 0) + (tokenUsage.candidatesTokenCount || 0)
|
|
118
|
+
if (totalTokens) {
|
|
119
|
+
metrics.totalTokens = totalTokens
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return metrics
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Extract metadata from config
|
|
127
|
+
* @param {object} config
|
|
128
|
+
* @returns {object}
|
|
129
|
+
*/
|
|
130
|
+
function extractMetadata (config) {
|
|
131
|
+
if (!config) return {}
|
|
132
|
+
|
|
133
|
+
const fieldMap = {
|
|
134
|
+
temperature: 'temperature',
|
|
135
|
+
top_p: 'topP',
|
|
136
|
+
top_k: 'topK',
|
|
137
|
+
candidate_count: 'candidateCount',
|
|
138
|
+
max_output_tokens: 'maxOutputTokens',
|
|
139
|
+
stop_sequences: 'stopSequences',
|
|
140
|
+
response_logprobs: 'responseLogprobs',
|
|
141
|
+
logprobs: 'logprobs',
|
|
142
|
+
presence_penalty: 'presencePenalty',
|
|
143
|
+
frequency_penalty: 'frequencyPenalty',
|
|
144
|
+
seed: 'seed',
|
|
145
|
+
response_mime_type: 'responseMimeType',
|
|
146
|
+
safety_settings: 'safetySettings',
|
|
147
|
+
automatic_function_calling: 'automaticFunctionCalling'
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const metadata = {}
|
|
151
|
+
for (const [metadataKey, configKey] of Object.entries(fieldMap)) {
|
|
152
|
+
metadata[metadataKey] = config[configKey] ?? null
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return metadata
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Format function call message
|
|
160
|
+
* @param {Array} parts
|
|
161
|
+
* @param {Array} functionCalls
|
|
162
|
+
* @param {string} role
|
|
163
|
+
* @returns {object}
|
|
164
|
+
*/
|
|
165
|
+
function formatFunctionCallMessage (parts, functionCalls, role) {
|
|
166
|
+
const toolCalls = functionCalls.map(part => ({
|
|
167
|
+
name: part.functionCall.name,
|
|
168
|
+
arguments: part.functionCall.args,
|
|
169
|
+
toolId: part.functionCall.id || '',
|
|
170
|
+
type: 'function_call'
|
|
171
|
+
}))
|
|
172
|
+
|
|
173
|
+
const textParts = extractTextParts(parts)
|
|
174
|
+
const content = textParts.length > 0 ? textParts.join('\n') : undefined
|
|
175
|
+
const message = { role, toolCalls }
|
|
176
|
+
|
|
177
|
+
if (content) message.content = content
|
|
178
|
+
|
|
179
|
+
return message
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Format function response message
|
|
184
|
+
* @param {Array} functionResponses
|
|
185
|
+
* @param {string} role
|
|
186
|
+
* @returns {object}
|
|
187
|
+
*/
|
|
188
|
+
function formatFunctionResponseMessage (functionResponses, role) {
|
|
189
|
+
const toolResults = functionResponses.map(part => ({
|
|
190
|
+
name: part.functionResponse.name,
|
|
191
|
+
result: JSON.stringify(part.functionResponse.response),
|
|
192
|
+
toolId: part.functionResponse.id,
|
|
193
|
+
type: 'function_response'
|
|
194
|
+
}))
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
role,
|
|
198
|
+
toolResults
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Aggregate streaming chunks into a single response
|
|
204
|
+
* @param {Array} chunks
|
|
205
|
+
* @returns {object}
|
|
206
|
+
*/
|
|
207
|
+
function aggregateStreamingChunks (chunks) {
|
|
208
|
+
const response = { candidates: [] }
|
|
209
|
+
|
|
210
|
+
for (const chunk of chunks) {
|
|
211
|
+
if (chunk.candidates) {
|
|
212
|
+
// Flatten candidates array
|
|
213
|
+
response.candidates.push(...chunk.candidates)
|
|
214
|
+
}
|
|
215
|
+
if (chunk.usageMetadata) {
|
|
216
|
+
response.usageMetadata = chunk.usageMetadata
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return response
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Format a content object into a message
|
|
225
|
+
* @param {object} content
|
|
226
|
+
* @returns {object}
|
|
227
|
+
*/
|
|
228
|
+
function formatContentObject (content) {
|
|
229
|
+
const parts = content.parts || []
|
|
230
|
+
const role = determineRole(content, parts)
|
|
231
|
+
|
|
232
|
+
// Check if this is a thought/reasoning part
|
|
233
|
+
if (hasThoughtParts(parts)) {
|
|
234
|
+
return {
|
|
235
|
+
role: ROLES.REASONING,
|
|
236
|
+
content: extractTextParts(parts).join('\n')
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Check for function calls
|
|
241
|
+
const functionCalls = parts.filter(part => part.functionCall)
|
|
242
|
+
if (functionCalls.length > 0) {
|
|
243
|
+
return formatFunctionCallMessage(parts, functionCalls, role)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Check for function responses
|
|
247
|
+
const functionResponses = parts.filter(part => part.functionResponse)
|
|
248
|
+
if (functionResponses.length > 0) {
|
|
249
|
+
return formatFunctionResponseMessage(functionResponses, role)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Regular text content
|
|
253
|
+
return {
|
|
254
|
+
role,
|
|
255
|
+
content: extractTextParts(parts).join('\n')
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Format input messages from contents
|
|
261
|
+
* @param {*} contents
|
|
262
|
+
* @returns {Array}
|
|
263
|
+
*/
|
|
264
|
+
function formatInputMessages (contents) {
|
|
265
|
+
if (!contents) return []
|
|
266
|
+
|
|
267
|
+
const contentArray = Array.isArray(contents) ? contents : [contents]
|
|
268
|
+
const messages = []
|
|
269
|
+
|
|
270
|
+
for (const content of contentArray) {
|
|
271
|
+
if (typeof content === 'string') {
|
|
272
|
+
messages.push({ role: ROLES.USER, content })
|
|
273
|
+
} else if (content.text) {
|
|
274
|
+
messages.push({ role: ROLES.USER, content: content.text })
|
|
275
|
+
} else if (content.parts) {
|
|
276
|
+
const message = formatContentObject(content)
|
|
277
|
+
if (message) messages.push(message)
|
|
278
|
+
} else {
|
|
279
|
+
messages.push({ role: ROLES.USER, content: JSON.stringify(content) })
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return messages
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Format embedding input from contents
|
|
288
|
+
* @param {*} contents
|
|
289
|
+
* @returns {Array}
|
|
290
|
+
*/
|
|
291
|
+
function formatEmbeddingInput (contents) {
|
|
292
|
+
if (!contents) return []
|
|
293
|
+
|
|
294
|
+
const contentArray = Array.isArray(contents) ? contents : [contents]
|
|
295
|
+
const documents = []
|
|
296
|
+
|
|
297
|
+
for (const content of contentArray) {
|
|
298
|
+
if (typeof content === 'string') {
|
|
299
|
+
documents.push({ text: content })
|
|
300
|
+
} else if (content.text) {
|
|
301
|
+
documents.push({ text: content.text })
|
|
302
|
+
} else if (content.parts) {
|
|
303
|
+
for (const part of content.parts) {
|
|
304
|
+
if (typeof part === 'string') {
|
|
305
|
+
documents.push({ text: part })
|
|
306
|
+
} else if (part.text) {
|
|
307
|
+
documents.push({ text: part.text })
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return documents
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Format a non-streaming candidate into messages
|
|
318
|
+
* @param {object} candidate
|
|
319
|
+
* @returns {Array}
|
|
320
|
+
*/
|
|
321
|
+
function formatNonStreamingCandidate (candidate) {
|
|
322
|
+
const messages = []
|
|
323
|
+
const content = Array.isArray(candidate) ? candidate[0].content : candidate.content
|
|
324
|
+
|
|
325
|
+
if (!content?.parts) return messages
|
|
326
|
+
|
|
327
|
+
const { parts } = content
|
|
328
|
+
|
|
329
|
+
// Check for function calls
|
|
330
|
+
const functionCalls = parts.filter(part => part.functionCall)
|
|
331
|
+
if (functionCalls.length > 0) {
|
|
332
|
+
messages.push(formatFunctionCallMessage(parts, functionCalls, ROLES.ASSISTANT))
|
|
333
|
+
return messages
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Check for executable code
|
|
337
|
+
const executableCode = parts.find(part => part.executableCode)
|
|
338
|
+
if (executableCode) {
|
|
339
|
+
messages.push({
|
|
340
|
+
role: ROLES.ASSISTANT,
|
|
341
|
+
content: JSON.stringify({
|
|
342
|
+
language: executableCode.executableCode.language,
|
|
343
|
+
code: executableCode.executableCode.code
|
|
344
|
+
})
|
|
345
|
+
})
|
|
346
|
+
return messages
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Check for code execution result
|
|
350
|
+
const codeExecutionResult = parts.find(part => part.codeExecutionResult)
|
|
351
|
+
if (codeExecutionResult) {
|
|
352
|
+
messages.push({
|
|
353
|
+
role: ROLES.ASSISTANT,
|
|
354
|
+
content: JSON.stringify({
|
|
355
|
+
outcome: codeExecutionResult.codeExecutionResult.outcome,
|
|
356
|
+
output: codeExecutionResult.codeExecutionResult.output
|
|
357
|
+
})
|
|
358
|
+
})
|
|
359
|
+
return messages
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Regular text content - may contain both reasoning and assistant parts
|
|
363
|
+
const partsByRole = groupPartsByRole(parts)
|
|
364
|
+
|
|
365
|
+
if (partsByRole.reasoning) {
|
|
366
|
+
messages.push({
|
|
367
|
+
role: ROLES.REASONING,
|
|
368
|
+
content: partsByRole.reasoning
|
|
369
|
+
})
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (partsByRole.assistant) {
|
|
373
|
+
messages.push({
|
|
374
|
+
role: ROLES.ASSISTANT,
|
|
375
|
+
content: partsByRole.assistant
|
|
376
|
+
})
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return messages
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Format streaming output from response
|
|
384
|
+
* @param {object} response
|
|
385
|
+
* @returns {Array}
|
|
386
|
+
*/
|
|
387
|
+
function formatStreamingOutput (response) {
|
|
388
|
+
const messages = []
|
|
389
|
+
const messagesByRole = new Map()
|
|
390
|
+
|
|
391
|
+
for (const candidate of response.candidates) {
|
|
392
|
+
const content = Array.isArray(candidate) ? candidate[0].content : candidate.content
|
|
393
|
+
if (!content?.parts) continue
|
|
394
|
+
|
|
395
|
+
// Skip special cases in streaming (handle them as non-streaming)
|
|
396
|
+
if (content.parts.some(part => part.functionCall ||
|
|
397
|
+
part.executableCode ||
|
|
398
|
+
part.codeExecutionResult)) {
|
|
399
|
+
messages.push(...formatNonStreamingCandidate(candidate))
|
|
400
|
+
continue
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Accumulate text parts by role
|
|
404
|
+
const partsByRole = groupPartsByRole(content.parts)
|
|
405
|
+
|
|
406
|
+
for (const [partRole, textContent] of Object.entries(partsByRole)) {
|
|
407
|
+
if (!textContent) continue
|
|
408
|
+
|
|
409
|
+
if (messagesByRole.has(partRole)) {
|
|
410
|
+
const index = messagesByRole.get(partRole)
|
|
411
|
+
messages[index].content += textContent
|
|
412
|
+
} else {
|
|
413
|
+
const messageIndex = messages.length
|
|
414
|
+
messages.push({ role: partRole, content: textContent })
|
|
415
|
+
messagesByRole.set(partRole, messageIndex)
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return messages.length > 0 ? messages : [{ content: '' }]
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Format non-streaming output from response
|
|
425
|
+
* @param {object} response
|
|
426
|
+
* @returns {Array}
|
|
427
|
+
*/
|
|
428
|
+
function formatNonStreamingOutput (response) {
|
|
429
|
+
const messages = []
|
|
430
|
+
|
|
431
|
+
for (const candidate of response.candidates) {
|
|
432
|
+
messages.push(...formatNonStreamingCandidate(candidate))
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return messages.length > 0 ? messages : [{ content: '' }]
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Format output messages from response
|
|
440
|
+
* @param {object} response
|
|
441
|
+
* @param {boolean} isStreaming
|
|
442
|
+
* @returns {Array}
|
|
443
|
+
*/
|
|
444
|
+
function formatOutputMessages (response, isStreaming = false) {
|
|
445
|
+
if (!response?.candidates?.length) {
|
|
446
|
+
return [{ content: '' }]
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (isStreaming) {
|
|
450
|
+
return formatStreamingOutput(response)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return formatNonStreamingOutput(response)
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Format embedding output from response
|
|
458
|
+
* @param {object} response
|
|
459
|
+
* @returns {string}
|
|
460
|
+
*/
|
|
461
|
+
function formatEmbeddingOutput (response) {
|
|
462
|
+
if (!response?.embeddings?.length) {
|
|
463
|
+
return ''
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const embeddingCount = response.embeddings.length
|
|
467
|
+
const firstEmbedding = response.embeddings[0]
|
|
468
|
+
|
|
469
|
+
if (firstEmbedding.values && Array.isArray(firstEmbedding.values)) {
|
|
470
|
+
const embeddingDim = firstEmbedding.values.length
|
|
471
|
+
return `[${embeddingCount} embedding(s) returned with size ${embeddingDim}]`
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return `[${embeddingCount} embedding(s) returned]`
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
module.exports = {
|
|
478
|
+
getOperation,
|
|
479
|
+
extractMetrics,
|
|
480
|
+
extractMetadata,
|
|
481
|
+
aggregateStreamingChunks,
|
|
482
|
+
formatInputMessages,
|
|
483
|
+
formatEmbeddingInput,
|
|
484
|
+
formatOutputMessages,
|
|
485
|
+
formatEmbeddingOutput
|
|
486
|
+
}
|
|
@@ -174,13 +174,13 @@ class BaseLLMGeneratePlugin extends BaseLangChainLLMObsPlugin {
|
|
|
174
174
|
class EmbeddingsEmbedQueryPlugin extends BaseLangChainLLMObsPlugin {
|
|
175
175
|
static id = 'llmobs_langchain_embeddings_embed_query'
|
|
176
176
|
static lcType = 'embedding'
|
|
177
|
-
static prefix = 'tracing:
|
|
177
|
+
static prefix = 'tracing:orchestrion:@langchain/core:Embeddings_embedQuery'
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
class EmbeddingsEmbedDocumentsPlugin extends BaseLangChainLLMObsPlugin {
|
|
181
181
|
static id = 'llmobs_langchain_embeddings_embed_documents'
|
|
182
182
|
static lcType = 'embedding'
|
|
183
|
-
static prefix = 'tracing:
|
|
183
|
+
static prefix = 'tracing:orchestrion:@langchain/core:Embeddings_embedDocuments'
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
class ToolInvokePlugin extends BaseLangChainLLMObsPlugin {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const LLMObsPlugin = require('
|
|
3
|
+
const LLMObsPlugin = require('../base')
|
|
4
|
+
const { extractChatTemplateFromInstructions, normalizePromptVariables, extractTextFromContentItem } = require('./utils')
|
|
4
5
|
|
|
5
6
|
const allowedParamKeys = new Set([
|
|
6
7
|
'max_output_tokens',
|
|
@@ -115,6 +116,10 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
|
|
|
115
116
|
metrics.cacheReadTokens = cacheReadTokens
|
|
116
117
|
}
|
|
117
118
|
}
|
|
119
|
+
// Reasoning tokens - Responses API returns `output_tokens_details`, `completion_tokens_details`
|
|
120
|
+
const reasoningOutputObject = tokenUsage.output_tokens_details ?? tokenUsage.completion_tokens_details
|
|
121
|
+
const reasoningOutputTokens = reasoningOutputObject?.reasoning_tokens ?? 0
|
|
122
|
+
if (reasoningOutputTokens !== undefined) metrics.reasoningOutputTokens = reasoningOutputTokens
|
|
118
123
|
}
|
|
119
124
|
|
|
120
125
|
return metrics
|
|
@@ -221,7 +226,8 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
|
|
|
221
226
|
#tagResponse (span, inputs, response, error) {
|
|
222
227
|
// Tag metadata - use allowlist approach for request parameters
|
|
223
228
|
|
|
224
|
-
const {
|
|
229
|
+
const { model, ...parameters } = inputs
|
|
230
|
+
let input = inputs.input
|
|
225
231
|
|
|
226
232
|
// Create input messages
|
|
227
233
|
const inputMessages = []
|
|
@@ -231,10 +237,33 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
|
|
|
231
237
|
inputMessages.push({ role: 'system', content: inputs.instructions })
|
|
232
238
|
}
|
|
233
239
|
|
|
240
|
+
// For reusable prompts, use response.instructions if no explicit input is provided
|
|
241
|
+
if (!input && inputs.prompt && response?.instructions) {
|
|
242
|
+
input = response.instructions
|
|
243
|
+
}
|
|
244
|
+
|
|
234
245
|
// Handle input - can be string or array of mixed messages
|
|
235
246
|
if (Array.isArray(input)) {
|
|
236
247
|
for (const item of input) {
|
|
237
|
-
if (item.type === '
|
|
248
|
+
if (item.type === 'message') {
|
|
249
|
+
// Handle instruction messages (from response.instructions for reusable prompts)
|
|
250
|
+
const role = item.role
|
|
251
|
+
if (!role) continue
|
|
252
|
+
|
|
253
|
+
let content = ''
|
|
254
|
+
if (Array.isArray(item.content)) {
|
|
255
|
+
const textParts = item.content
|
|
256
|
+
.map(extractTextFromContentItem)
|
|
257
|
+
.filter(Boolean)
|
|
258
|
+
content = textParts.join('')
|
|
259
|
+
} else if (typeof item.content === 'string') {
|
|
260
|
+
content = item.content
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (content) {
|
|
264
|
+
inputMessages.push({ role, content })
|
|
265
|
+
}
|
|
266
|
+
} else if (item.type === 'function_call') {
|
|
238
267
|
// Function call: convert to message with tool_calls
|
|
239
268
|
// Parse arguments if it's a JSON string
|
|
240
269
|
let parsedArgs = item.arguments
|
|
@@ -380,6 +409,22 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
|
|
|
380
409
|
|
|
381
410
|
this._tagger.tagLLMIO(span, inputMessages, outputMessages)
|
|
382
411
|
|
|
412
|
+
// Handle prompt tracking for reusable prompts
|
|
413
|
+
if (inputs.prompt && response?.prompt) {
|
|
414
|
+
const { id, version } = response.prompt // ResponsePrompt
|
|
415
|
+
// TODO: Add proper tagger API for prompt metadata
|
|
416
|
+
if (id && version) {
|
|
417
|
+
const normalizedVariables = normalizePromptVariables(inputs.prompt.variables)
|
|
418
|
+
const chatTemplate = extractChatTemplateFromInstructions(response.instructions, normalizedVariables)
|
|
419
|
+
this._tagger._setTag(span, '_ml_obs.meta.input.prompt', {
|
|
420
|
+
id,
|
|
421
|
+
version,
|
|
422
|
+
variables: normalizedVariables,
|
|
423
|
+
chat_template: chatTemplate
|
|
424
|
+
})
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
383
428
|
const outputMetadata = {}
|
|
384
429
|
|
|
385
430
|
// Add fields from response object (convert numbers to floats)
|
|
@@ -388,9 +433,6 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
|
|
|
388
433
|
if (response.tool_choice !== undefined) outputMetadata.tool_choice = response.tool_choice
|
|
389
434
|
if (response.truncation !== undefined) outputMetadata.truncation = response.truncation
|
|
390
435
|
if (response.text !== undefined) outputMetadata.text = response.text
|
|
391
|
-
if (response.usage?.output_tokens_details?.reasoning_tokens !== undefined) {
|
|
392
|
-
outputMetadata.reasoning_tokens = response.usage.output_tokens_details.reasoning_tokens
|
|
393
|
-
}
|
|
394
436
|
|
|
395
437
|
this._tagger.tagMetadata(span, outputMetadata) // update the metadata with the output metadata
|
|
396
438
|
}
|