dd-trace 6.6.0 → 6.8.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/ci/diagnose.js +162 -21
- package/ci/init.js +0 -1
- package/ci/runbook.md +187 -180
- package/ci/test-optimization-validation/approval-artifacts.js +3 -1
- package/ci/test-optimization-validation/approval.js +52 -15
- package/ci/test-optimization-validation/ci-command-candidate.js +26 -32
- package/ci/test-optimization-validation/ci-discovery.js +1 -1
- package/ci/test-optimization-validation/ci-package-scripts.js +141 -0
- package/ci/test-optimization-validation/ci-remediation.js +112 -39
- package/ci/test-optimization-validation/cli.js +516 -681
- package/ci/test-optimization-validation/command-blocker.js +188 -33
- package/ci/test-optimization-validation/command-output-policy.js +8 -31
- package/ci/test-optimization-validation/command-runner.js +189 -245
- package/ci/test-optimization-validation/environment.js +90 -0
- package/ci/test-optimization-validation/executable.js +159 -388
- package/ci/test-optimization-validation/framework-adapters/cucumber.js +119 -0
- package/ci/test-optimization-validation/framework-adapters/cypress.js +107 -0
- package/ci/test-optimization-validation/framework-adapters/playwright.js +181 -0
- package/ci/test-optimization-validation/generated-files.js +84 -22
- package/ci/test-optimization-validation/generated-test-contract.js +281 -0
- package/ci/test-optimization-validation/generated-verifier.js +49 -16
- package/ci/test-optimization-validation/literal-environment.js +57 -0
- package/ci/test-optimization-validation/manifest-loader.js +2 -22
- package/ci/test-optimization-validation/manifest-scaffold.js +815 -515
- package/ci/test-optimization-validation/manifest-schema.js +494 -702
- package/ci/test-optimization-validation/offline-fixtures.js +4 -1
- package/ci/test-optimization-validation/offline-output.js +6 -6
- package/ci/test-optimization-validation/plan-writer.js +243 -984
- package/ci/test-optimization-validation/preflight-runner.js +49 -55
- package/ci/test-optimization-validation/redaction.js +2 -1
- package/ci/test-optimization-validation/report-writer.js +391 -1357
- package/ci/test-optimization-validation/result-semantics.js +86 -0
- package/ci/test-optimization-validation/runner-command.js +249 -0
- package/ci/test-optimization-validation/runner-contract.js +664 -0
- package/ci/test-optimization-validation/safe-files.js +4 -4
- package/ci/test-optimization-validation/scenarios/auto-test-retries.js +29 -3
- package/ci/test-optimization-validation/scenarios/basic-reporting.js +240 -543
- package/ci/test-optimization-validation/scenarios/ci-wiring.js +450 -670
- package/ci/test-optimization-validation/scenarios/early-flake-detection.js +4 -3
- package/ci/test-optimization-validation/scenarios/helpers.js +67 -9
- package/ci/test-optimization-validation/scenarios/test-management.js +6 -5
- package/ci/test-optimization-validation/source-text.js +87 -0
- package/ci/test-optimization-validation/test-output.js +12 -22
- package/ci/vitest-no-worker-init-setup.mjs +348 -51
- package/ext/tags.d.ts +1 -0
- package/index.d.ts +84 -13
- package/package.json +10 -4
- package/packages/datadog-core/src/utils/src/kebabcase.js +8 -2
- package/packages/datadog-esbuild/index.js +4 -2
- package/packages/datadog-esbuild/src/utils.js +1 -1
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +4 -4
- package/packages/datadog-instrumentations/src/cucumber.js +9 -6
- package/packages/datadog-instrumentations/src/helpers/extract-package-and-module-path.js +10 -0
- package/packages/datadog-instrumentations/src/helpers/hook.js +19 -2
- package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +9 -3
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/openai-agents.js +31 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +57 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +50 -18
- package/packages/datadog-instrumentations/src/helpers/router-helper.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/shared-utils.js +39 -0
- package/packages/datadog-instrumentations/src/http/client.js +1 -3
- package/packages/datadog-instrumentations/src/jest.js +8 -14
- package/packages/datadog-instrumentations/src/mocha/common.js +1 -3
- package/packages/datadog-instrumentations/src/mocha/main.js +2 -42
- package/packages/datadog-instrumentations/src/mocha/utils.js +68 -2
- package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +19 -0
- package/packages/datadog-instrumentations/src/mocha/worker.js +446 -42
- package/packages/datadog-instrumentations/src/moleculer/client.js +1 -1
- package/packages/datadog-instrumentations/src/openai-agents.js +159 -0
- package/packages/datadog-instrumentations/src/playwright.js +3 -2
- package/packages/datadog-instrumentations/src/rhea.js +1 -1
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +525 -56
- package/packages/datadog-instrumentations/src/vitest-main.js +90 -10
- package/packages/datadog-instrumentations/src/webdriverio.js +914 -0
- package/packages/datadog-plugin-aerospike/src/index.js +1 -3
- package/packages/datadog-plugin-amqplib/src/client.js +1 -1
- package/packages/datadog-plugin-amqplib/src/producer.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +2 -2
- package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +2 -2
- package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -3
- package/packages/datadog-plugin-azure-service-bus/src/producer.js +5 -3
- package/packages/datadog-plugin-bullmq/src/producer.js +3 -3
- package/packages/datadog-plugin-child_process/src/index.js +3 -3
- package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +5 -8
- package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -1
- package/packages/datadog-plugin-fastify/src/tracing.js +1 -1
- package/packages/datadog-plugin-fs/src/index.js +0 -4
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +14 -7
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +19 -10
- package/packages/datadog-plugin-graphql/src/execute.js +1 -0
- package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -1
- package/packages/datadog-plugin-http/src/client.js +2 -1
- package/packages/datadog-plugin-jest/src/index.js +2 -3
- package/packages/datadog-plugin-jest/src/util.js +2 -1
- package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
- package/packages/datadog-plugin-mocha/src/index.js +25 -6
- package/packages/datadog-plugin-openai-agents/src/index.js +74 -0
- package/packages/datadog-plugin-openai-agents/src/integration.js +503 -0
- package/packages/datadog-plugin-openai-agents/src/processor.js +108 -0
- package/packages/datadog-plugin-openai-agents/src/util.js +60 -0
- package/packages/datadog-plugin-prisma/src/index.js +5 -1
- package/packages/datadog-plugin-rhea/src/consumer.js +16 -13
- package/packages/datadog-plugin-rhea/src/producer.js +1 -1
- package/packages/datadog-plugin-undici/src/index.js +2 -1
- package/packages/datadog-plugin-vitest/src/index.js +84 -11
- package/packages/datadog-webpack/index.js +2 -2
- package/packages/dd-trace/src/appsec/downstream_requests.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -3
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +2 -2
- package/packages/dd-trace/src/appsec/sdk/track_event.js +1 -1
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +7 -6
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +50 -51
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -1
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +4 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/webdriverio.js +26 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -1
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +161 -33
- package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -3
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +7 -5
- package/packages/dd-trace/src/config/defaults.js +14 -6
- package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -2
- package/packages/dd-trace/src/config/index.js +15 -0
- package/packages/dd-trace/src/config/parsers.js +19 -1
- package/packages/dd-trace/src/config/supported-configurations.json +12 -6
- package/packages/dd-trace/src/datastreams/encoding.js +3 -4
- package/packages/dd-trace/src/debugger/devtools_client/log.js +2 -1
- package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +1 -4
- package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +2 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +16 -2
- package/packages/dd-trace/src/encode/span-stats.js +7 -1
- package/packages/dd-trace/src/encode/tags-processors.js +2 -2
- package/packages/dd-trace/src/exporter.js +16 -9
- package/packages/dd-trace/src/exporters/common/url.js +3 -5
- package/packages/dd-trace/src/exporters/electron/index.js +4 -1
- package/packages/dd-trace/src/external-logger/src/index.js +1 -2
- package/packages/dd-trace/src/flare/index.js +2 -2
- package/packages/dd-trace/src/id.js +1 -1
- package/packages/dd-trace/src/llmobs/constants/tags.js +1 -1
- package/packages/dd-trace/src/llmobs/experiments/client.js +4 -2
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +66 -14
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +289 -95
- package/packages/dd-trace/src/llmobs/experiments/index.js +51 -12
- package/packages/dd-trace/src/llmobs/experiments/noop.js +95 -10
- package/packages/dd-trace/src/llmobs/experiments/result.js +13 -2
- package/packages/dd-trace/src/llmobs/experiments/util.js +218 -0
- package/packages/dd-trace/src/llmobs/index.js +9 -1
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +104 -22
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +5 -9
- package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +20 -0
- package/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js +321 -0
- package/packages/dd-trace/src/llmobs/sdk.js +6 -12
- package/packages/dd-trace/src/llmobs/span_processor.js +14 -7
- package/packages/dd-trace/src/llmobs/tagger.js +24 -5
- package/packages/dd-trace/src/llmobs/util.js +56 -3
- package/packages/dd-trace/src/llmobs/writers/spans.js +12 -6
- package/packages/dd-trace/src/noop/proxy.js +2 -2
- package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +3 -6
- package/packages/dd-trace/src/openfeature/configuration_source.js +5 -8
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +5 -1
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +2 -1
- package/packages/dd-trace/src/opentracing/span.js +1 -1
- package/packages/dd-trace/src/opentracing/span_context.js +1 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +114 -23
- package/packages/dd-trace/src/plugins/index.js +2 -0
- package/packages/dd-trace/src/plugins/outbound.js +4 -1
- package/packages/dd-trace/src/plugins/plugin.js +1 -1
- package/packages/dd-trace/src/plugins/tracing.js +1 -0
- package/packages/dd-trace/src/plugins/util/ci.js +7 -6
- package/packages/dd-trace/src/plugins/util/git.js +1 -1
- package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +1 -1
- package/packages/dd-trace/src/plugins/util/llm.js +2 -2
- package/packages/dd-trace/src/plugins/util/tags.js +2 -0
- package/packages/dd-trace/src/plugins/util/test.js +38 -12
- package/packages/dd-trace/src/plugins/util/url.js +5 -5
- package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -1
- package/packages/dd-trace/src/plugins/util/web.js +1 -1
- package/packages/dd-trace/src/priority_sampler.js +6 -3
- package/packages/dd-trace/src/profiling/profilers/poisson.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/shared.js +2 -2
- package/packages/dd-trace/src/ritm.js +5 -20
- package/packages/dd-trace/src/span_processor.js +1 -1
- package/packages/dd-trace/src/span_stats.js +22 -4
- package/packages/dd-trace/src/startup-log.js +3 -0
- package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -1
- package/packages/dd-trace/src/util.js +16 -0
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +12 -12
- package/vendor/dist/pprof-format/index.js +1 -1
- package/vendor/dist/source-map/index.js +1 -1
- package/ci/test-optimization-validation/command-suitability.js +0 -471
- package/ci/test-optimization-validation/init-probe-preload.js +0 -163
- package/ci/test-optimization-validation/init-probe.js +0 -246
- package/ci/test-optimization-validation/late-initialization.js +0 -63
- package/ci/test-optimization-validation/local-command.js +0 -163
- package/ci/test-optimization-validation/setup-runner.js +0 -97
- package/ci/test-optimization-validation-manifest.schema.json +0 -1
|
@@ -1,8 +1,90 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const log = require('../../log')
|
|
4
|
+
|
|
5
|
+
class NoopDataset {
|
|
6
|
+
#name
|
|
7
|
+
#records
|
|
8
|
+
|
|
9
|
+
constructor (name = '', options = {}) {
|
|
10
|
+
this.#name = name
|
|
11
|
+
this.#records = (typeof options === 'string' ? [] : (options.records ?? [])).map(record => ({
|
|
12
|
+
id: record.id ?? null,
|
|
13
|
+
input: record.inputData,
|
|
14
|
+
expectedOutput: record.expectedOutput ?? null,
|
|
15
|
+
metadata: record.metadata ?? {},
|
|
16
|
+
}))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
addRecord (input, expectedOutput, metadata) {
|
|
20
|
+
this.#records.push({ id: null, input, expectedOutput: expectedOutput ?? null, metadata: metadata ?? {} })
|
|
21
|
+
return this
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
push () {
|
|
25
|
+
return Promise.resolve({ pushedCount: 0, totalCount: 0 })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
name () {
|
|
29
|
+
return this.#name
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
id () {
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
projectId () {
|
|
37
|
+
return null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
version () {
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
latestVersion () {
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
records () {
|
|
49
|
+
return [...this.#records]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
recordIds () {
|
|
53
|
+
return []
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
url () {
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
class NoopExperiment {
|
|
62
|
+
#name
|
|
63
|
+
|
|
64
|
+
constructor (name = '') {
|
|
65
|
+
this.#name = name
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
name () {
|
|
69
|
+
return this.#name
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
experimentId () {
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
url () {
|
|
77
|
+
return null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
run () {
|
|
81
|
+
return Promise.resolve({ experimentId: null, rows: [], url: null })
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
3
85
|
// No-op Experiments used when LLM Observability is disabled or the API/APP keys
|
|
4
|
-
// are not configured.
|
|
5
|
-
//
|
|
86
|
+
// are not configured. Operations warn and return inert objects rather than
|
|
87
|
+
// throwing, so intentionally disabled experiments remain graceful.
|
|
6
88
|
class NoopExperiments {
|
|
7
89
|
#reason
|
|
8
90
|
|
|
@@ -10,20 +92,23 @@ class NoopExperiments {
|
|
|
10
92
|
this.#reason = reason || 'LLMObs experiments are not available'
|
|
11
93
|
}
|
|
12
94
|
|
|
13
|
-
#
|
|
14
|
-
|
|
95
|
+
#warn () {
|
|
96
|
+
log.warn('LLMObs experiments unavailable: %s', this.#reason)
|
|
15
97
|
}
|
|
16
98
|
|
|
17
|
-
createDataset () {
|
|
18
|
-
|
|
99
|
+
createDataset (name, options = {}) {
|
|
100
|
+
this.#warn()
|
|
101
|
+
return new NoopDataset(name, options)
|
|
19
102
|
}
|
|
20
103
|
|
|
21
|
-
pullDataset () {
|
|
22
|
-
|
|
104
|
+
pullDataset (name) {
|
|
105
|
+
this.#warn()
|
|
106
|
+
return Promise.resolve(new NoopDataset(name))
|
|
23
107
|
}
|
|
24
108
|
|
|
25
|
-
experiment () {
|
|
26
|
-
|
|
109
|
+
experiment (options = {}) {
|
|
110
|
+
this.#warn()
|
|
111
|
+
return new NoopExperiment(options.name)
|
|
27
112
|
}
|
|
28
113
|
}
|
|
29
114
|
|
|
@@ -22,13 +22,24 @@ class Row {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
class ExperimentRun {
|
|
26
|
+
constructor (fields) {
|
|
27
|
+
this.runId = fields.runId
|
|
28
|
+
this.runIteration = fields.runIteration
|
|
29
|
+
this.rows = fields.rows
|
|
30
|
+
this.summaryEvaluations = fields.summaryEvaluations
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
25
34
|
// Returned by Experiment.run().
|
|
26
35
|
class ExperimentResult {
|
|
27
|
-
constructor (experimentId, rows, url) {
|
|
36
|
+
constructor (experimentId, rows, url, runs = [], summaryEvaluations = {}) {
|
|
28
37
|
this.experimentId = experimentId
|
|
29
38
|
this.rows = rows
|
|
30
39
|
this.url = url
|
|
40
|
+
this.runs = runs
|
|
41
|
+
this.summaryEvaluations = summaryEvaluations
|
|
31
42
|
}
|
|
32
43
|
}
|
|
33
44
|
|
|
34
|
-
module.exports = { Row, ExperimentResult }
|
|
45
|
+
module.exports = { Row, ExperimentRun, ExperimentResult }
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const log = require('../../log')
|
|
4
|
+
|
|
5
|
+
// Matches the backend and dd-trace-py evaluator metric label contract.
|
|
6
|
+
const EVALUATOR_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {null | string | number | boolean | NormalizedJsonValue[] | { [key: string]: NormalizedJsonValue }}
|
|
10
|
+
* NormalizedJsonValue
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {object | null | undefined} value
|
|
15
|
+
* @returns {boolean}
|
|
16
|
+
*/
|
|
17
|
+
function hasEntries (value) {
|
|
18
|
+
if (!value) return false
|
|
19
|
+
for (const key of Object.keys(value)) {
|
|
20
|
+
if (Object.hasOwn(value, key)) return true
|
|
21
|
+
}
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {string} name
|
|
27
|
+
*/
|
|
28
|
+
function validateEvaluatorName (name) {
|
|
29
|
+
if (typeof name !== 'string') throw new TypeError('Evaluator name must be a string')
|
|
30
|
+
if (name.length === 0) throw new Error('Evaluator name cannot be empty')
|
|
31
|
+
if (!EVALUATOR_NAME_PATTERN.test(name)) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Evaluator name '${name}' is invalid. Name must contain only alphanumeric characters, underscores, and hyphens.`
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {(...args: unknown[]) => unknown} fn
|
|
40
|
+
* @param {string} fallback
|
|
41
|
+
* @returns {string}
|
|
42
|
+
*/
|
|
43
|
+
function functionName (fn, fallback) {
|
|
44
|
+
return typeof fn.name === 'string' && fn.name.length > 0 ? fn.name : fallback
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {unknown} evaluators
|
|
49
|
+
* @param {string} kind
|
|
50
|
+
* @returns {Array<[string, (...args: unknown[]) => unknown]>}
|
|
51
|
+
*/
|
|
52
|
+
function normalizeEvaluators (evaluators, kind) {
|
|
53
|
+
if (evaluators == null) return []
|
|
54
|
+
|
|
55
|
+
const normalized = []
|
|
56
|
+
if (Array.isArray(evaluators)) {
|
|
57
|
+
const indexesByName = new Map()
|
|
58
|
+
for (let i = 0; i < evaluators.length; i++) {
|
|
59
|
+
const evaluator = evaluators[i]
|
|
60
|
+
if (typeof evaluator !== 'function') throw new TypeError(`${kind} evaluator must be a function`)
|
|
61
|
+
const name = functionName(evaluator, `${kind}_evaluator_${i}`)
|
|
62
|
+
validateEvaluatorName(name)
|
|
63
|
+
if (indexesByName.has(name)) {
|
|
64
|
+
log.warn('Duplicate %s evaluator name %s; previous evaluator will be overwritten', kind, name)
|
|
65
|
+
normalized[indexesByName.get(name)] = [name, evaluator]
|
|
66
|
+
} else {
|
|
67
|
+
indexesByName.set(name, normalized.length)
|
|
68
|
+
normalized.push([name, evaluator])
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return normalized
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (typeof evaluators !== 'object') {
|
|
75
|
+
throw new TypeError(`${kind} evaluators must be an array of functions or an object keyed by evaluator name`)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (const [name, evaluator] of Object.entries(evaluators)) {
|
|
79
|
+
validateEvaluatorName(name)
|
|
80
|
+
if (typeof evaluator !== 'function') throw new TypeError(`${kind} evaluator '${name}' must be a function`)
|
|
81
|
+
normalized.push([name, evaluator])
|
|
82
|
+
}
|
|
83
|
+
return normalized
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @param {unknown} value
|
|
88
|
+
* @returns {string}
|
|
89
|
+
*/
|
|
90
|
+
function inferMetricType (value) {
|
|
91
|
+
if (typeof value === 'boolean') return 'boolean'
|
|
92
|
+
if (typeof value === 'number' && Number.isFinite(value)) return 'score'
|
|
93
|
+
if (typeof value === 'string') return 'categorical'
|
|
94
|
+
return 'json'
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @param {unknown} value
|
|
99
|
+
* @param {WeakSet<object>} seen
|
|
100
|
+
* @returns {NormalizedJsonValue}
|
|
101
|
+
*/
|
|
102
|
+
function normalizeJsonValue (value, seen) {
|
|
103
|
+
if (value === null) return null
|
|
104
|
+
|
|
105
|
+
const valueType = typeof value
|
|
106
|
+
if (valueType === 'string' || valueType === 'boolean') return value
|
|
107
|
+
if (valueType === 'number') return Number.isFinite(value) ? value : String(value)
|
|
108
|
+
if (valueType === 'bigint') return value.toString()
|
|
109
|
+
if (valueType === 'undefined') return null
|
|
110
|
+
if (valueType === 'symbol') return value.toString()
|
|
111
|
+
if (valueType === 'function') return { type: 'function', name: value.name || '<anonymous>' }
|
|
112
|
+
|
|
113
|
+
if (value instanceof Date) return value.toISOString()
|
|
114
|
+
if (value instanceof Error) {
|
|
115
|
+
return { type: value.name, message: value.message, stack: value.stack ?? '' }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (seen.has(value)) return '[Circular]'
|
|
119
|
+
seen.add(value)
|
|
120
|
+
|
|
121
|
+
if (Array.isArray(value)) {
|
|
122
|
+
const normalized = new Array(value.length)
|
|
123
|
+
for (let i = 0; i < value.length; i++) normalized[i] = normalizeJsonValue(value[i], seen)
|
|
124
|
+
seen.delete(value)
|
|
125
|
+
return normalized
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const normalized = {}
|
|
129
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
130
|
+
normalized[key] = normalizeJsonValue(nestedValue, seen)
|
|
131
|
+
}
|
|
132
|
+
seen.delete(value)
|
|
133
|
+
return normalized
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @param {unknown} value
|
|
138
|
+
* @returns {{ [key: string]: NormalizedJsonValue }}
|
|
139
|
+
*/
|
|
140
|
+
function normalizeJsonMetricValue (value) {
|
|
141
|
+
const normalized = normalizeJsonValue(value, new WeakSet())
|
|
142
|
+
if (normalized !== null && typeof normalized === 'object' && !Array.isArray(normalized)) return normalized
|
|
143
|
+
return { value: normalized }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @param {unknown} value
|
|
148
|
+
* @returns {string}
|
|
149
|
+
*/
|
|
150
|
+
function stringify (value) {
|
|
151
|
+
if (value == null) return ''
|
|
152
|
+
if (typeof value === 'string') return value.toLowerCase()
|
|
153
|
+
if (typeof value === 'object') {
|
|
154
|
+
try {
|
|
155
|
+
const stringified = JSON.stringify(value)
|
|
156
|
+
if (stringified !== undefined) return stringified.toLowerCase()
|
|
157
|
+
} catch {}
|
|
158
|
+
}
|
|
159
|
+
return String(value).toLowerCase()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* @param {Record<string, unknown> | undefined} userTags
|
|
164
|
+
* @param {Record<string, unknown>} autoTags
|
|
165
|
+
* @returns {string[]}
|
|
166
|
+
*/
|
|
167
|
+
function buildTags (userTags, autoTags) {
|
|
168
|
+
const tags = new Map()
|
|
169
|
+
for (const [key, value] of Object.entries(userTags ?? {})) {
|
|
170
|
+
tags.set(key, `${key}:${value}`)
|
|
171
|
+
}
|
|
172
|
+
for (const [key, value] of Object.entries(autoTags)) {
|
|
173
|
+
if (value !== undefined && value !== null && value !== '') tags.set(key, `${key}:${value}`)
|
|
174
|
+
}
|
|
175
|
+
return [...tags.values()]
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* @param {Record<string, unknown> | undefined} userTags
|
|
180
|
+
* @param {Record<string, unknown>} autoTags
|
|
181
|
+
* @returns {Record<string, unknown>}
|
|
182
|
+
*/
|
|
183
|
+
function buildExperimentTagObject (userTags, autoTags) {
|
|
184
|
+
return userTags ? { ...userTags, ...autoTags } : { ...autoTags }
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @param {number} ms
|
|
189
|
+
* @returns {Promise<void>}
|
|
190
|
+
*/
|
|
191
|
+
function sleep (ms) {
|
|
192
|
+
if (ms <= 0) return Promise.resolve()
|
|
193
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* @param {Record<string, unknown> | undefined} recordMetadata
|
|
198
|
+
* @param {Record<string, unknown>} config
|
|
199
|
+
* @returns {Record<string, unknown>}
|
|
200
|
+
*/
|
|
201
|
+
function buildSpanMetadata (recordMetadata, config) {
|
|
202
|
+
return recordMetadata
|
|
203
|
+
? { ...recordMetadata, experiment_config: config }
|
|
204
|
+
: { experiment_config: config }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
module.exports = {
|
|
208
|
+
buildExperimentTagObject,
|
|
209
|
+
buildSpanMetadata,
|
|
210
|
+
buildTags,
|
|
211
|
+
hasEntries,
|
|
212
|
+
inferMetricType,
|
|
213
|
+
normalizeEvaluators,
|
|
214
|
+
normalizeJsonMetricValue,
|
|
215
|
+
sleep,
|
|
216
|
+
stringify,
|
|
217
|
+
validateEvaluatorName,
|
|
218
|
+
}
|
|
@@ -16,6 +16,8 @@ const {
|
|
|
16
16
|
SAMPLING_DECISION,
|
|
17
17
|
PROPAGATED_SAMPLE_RATE_KEY,
|
|
18
18
|
PROPAGATED_SAMPLING_DECISION_KEY,
|
|
19
|
+
TRACE_ID,
|
|
20
|
+
PROPAGATED_TRACE_ID_KEY,
|
|
19
21
|
} = require('./constants/tags')
|
|
20
22
|
const { storage } = require('./storage')
|
|
21
23
|
const telemetry = require('./telemetry')
|
|
@@ -25,6 +27,7 @@ const LLMObsTagger = require('./tagger')
|
|
|
25
27
|
const LLMObsSpanWriter = require('./writers/spans')
|
|
26
28
|
const { setAgentStrategy } = require('./writers/util')
|
|
27
29
|
const { INCOMPATIBLE_INITIALIZATION } = require('./constants/text')
|
|
30
|
+
const { llmObsTraceIdToWire } = require('./util')
|
|
28
31
|
|
|
29
32
|
const spanFinishCh = channel('dd-trace:span:finish')
|
|
30
33
|
const evalMetricAppendCh = channel('llmobs:eval-metric:append')
|
|
@@ -137,8 +140,12 @@ function handleLLMObsInjection ({ carrier }) {
|
|
|
137
140
|
mlObsSpanTags?.[SESSION_ID] ??
|
|
138
141
|
parentContext?._trace?.tags?.[SESSION_ID_TRACE_DEFAULT_KEY] ??
|
|
139
142
|
parentContext?._trace?.tags?.[PROPAGATED_SESSION_ID_KEY]
|
|
143
|
+
const llmobsTraceId = mlObsSpanTags?.[TRACE_ID]
|
|
144
|
+
const propagatedTraceId = llmobsTraceId === undefined
|
|
145
|
+
? parentContext?._trace?.tags?.[PROPAGATED_TRACE_ID_KEY]
|
|
146
|
+
: llmObsTraceIdToWire(llmobsTraceId)
|
|
140
147
|
|
|
141
|
-
if (!parentId && !mlApp && samplingDecision == null && !sessionId) return
|
|
148
|
+
if (!parentId && !mlApp && samplingDecision == null && !sessionId && !propagatedTraceId) return
|
|
142
149
|
|
|
143
150
|
// `_injectTags` only writes `x-datadog-tags` when the trace has `_dd.p.*`
|
|
144
151
|
// tags, so it may be undefined here — coalesce before appending.
|
|
@@ -149,6 +156,7 @@ function handleLLMObsInjection ({ carrier }) {
|
|
|
149
156
|
if (sessionId) tags += `${tags ? ',' : ''}${PROPAGATED_SESSION_ID_KEY}=${sessionId}`
|
|
150
157
|
if (sampleRate != null) tags += `${tags ? ',' : ''}${PROPAGATED_SAMPLE_RATE_KEY}=${sampleRate}`
|
|
151
158
|
if (samplingDecision != null) tags += `${tags ? ',' : ''}${PROPAGATED_SAMPLING_DECISION_KEY}=${samplingDecision}`
|
|
159
|
+
if (propagatedTraceId != null) tags += `${tags ? ',' : ''}${PROPAGATED_TRACE_ID_KEY}=${propagatedTraceId}`
|
|
152
160
|
if (tags !== existing) carrier['x-datadog-tags'] = tags
|
|
153
161
|
}
|
|
154
162
|
|
|
@@ -13,6 +13,8 @@ const MODEL_METADATA_KEYS = new Set([
|
|
|
13
13
|
const VERCEL_AI_TELEMETRY_METADATA_PREFIX = 'ai.telemetry.metadata.'
|
|
14
14
|
const VERCEL_AI_MODEL_METADATA_PREFIX = 'gen_ai.request.'
|
|
15
15
|
const VERCEL_AI_GENERATION_METADATA_PREFIX = 'ai.settings.'
|
|
16
|
+
const UNPARSABLE_TOOL_RESULT = '[Unparsable Tool Result]'
|
|
17
|
+
const UNSUPPORTED_TOOL_RESULT = '[Unsupported Tool Result]'
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
20
|
* @typedef {import('../../../opentracing/span')} Span
|
|
@@ -26,8 +28,31 @@ const VERCEL_AI_GENERATION_METADATA_PREFIX = 'ai.settings.'
|
|
|
26
28
|
|
|
27
29
|
/**
|
|
28
30
|
* @typedef {{
|
|
29
|
-
* type:
|
|
30
|
-
*
|
|
31
|
+
* type: 'text',
|
|
32
|
+
* text: string
|
|
33
|
+
* } | {
|
|
34
|
+
* type: 'media' | 'file',
|
|
35
|
+
* mediaType: string
|
|
36
|
+
* } | {
|
|
37
|
+
* type: 'file-data' | 'file-url' | 'file-id' | 'file-reference'
|
|
38
|
+
* } | {
|
|
39
|
+
* type: 'image-data' | 'image-url' | 'image-file-id' | 'image-file-reference'
|
|
40
|
+
* } | {
|
|
41
|
+
* type: 'custom'
|
|
42
|
+
* }} ToolCallContentPart
|
|
43
|
+
*
|
|
44
|
+
* @typedef {{
|
|
45
|
+
* type: 'text' | 'error-text',
|
|
46
|
+
* value: string
|
|
47
|
+
* } | {
|
|
48
|
+
* type: 'json' | 'error-json',
|
|
49
|
+
* value: unknown
|
|
50
|
+
* } | {
|
|
51
|
+
* type: 'content',
|
|
52
|
+
* value: ToolCallContentPart[]
|
|
53
|
+
* } | {
|
|
54
|
+
* type: 'execution-denied',
|
|
55
|
+
* reason?: string
|
|
31
56
|
* }} ToolCallOutput
|
|
32
57
|
*
|
|
33
58
|
* @typedef {{ output?: ToolCallOutput, result?: unknown } & Record<string, unknown>} ToolCallResultContent
|
|
@@ -304,32 +329,89 @@ function getToolNameFromTags (tags) {
|
|
|
304
329
|
}
|
|
305
330
|
|
|
306
331
|
/**
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
|
|
332
|
+
* @param {unknown} value
|
|
333
|
+
* @returns {string}
|
|
334
|
+
*/
|
|
335
|
+
function stringifyToolCallResult (value) {
|
|
336
|
+
return JSON.stringify(value) ?? UNPARSABLE_TOOL_RESULT
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* @param {ToolCallContentPart[]} value
|
|
341
|
+
* @returns {string}
|
|
342
|
+
*/
|
|
343
|
+
function formatToolCallContent (value) {
|
|
344
|
+
if (!Array.isArray(value)) return UNPARSABLE_TOOL_RESULT
|
|
345
|
+
|
|
346
|
+
let result = ''
|
|
347
|
+
for (const part of value) {
|
|
348
|
+
if (typeof part !== 'object' || part === null) return UNPARSABLE_TOOL_RESULT
|
|
349
|
+
|
|
350
|
+
const { type } = part
|
|
351
|
+
if (type === 'text') {
|
|
352
|
+
if (typeof part.text !== 'string') return UNPARSABLE_TOOL_RESULT
|
|
353
|
+
result += part.text
|
|
354
|
+
} else if (type === 'media' || type === 'file') {
|
|
355
|
+
const { mediaType } = part
|
|
356
|
+
if (typeof mediaType !== 'string') return UNPARSABLE_TOOL_RESULT
|
|
357
|
+
result += mediaType === 'image' || mediaType.startsWith('image/') ? '[Image]' : '[File]'
|
|
358
|
+
} else if (
|
|
359
|
+
type === 'file-data' ||
|
|
360
|
+
type === 'file-url' ||
|
|
361
|
+
type === 'file-id' ||
|
|
362
|
+
type === 'file-reference'
|
|
363
|
+
) {
|
|
364
|
+
result += '[File]'
|
|
365
|
+
} else if (
|
|
366
|
+
type === 'image-data' ||
|
|
367
|
+
type === 'image-url' ||
|
|
368
|
+
type === 'image-file-id' ||
|
|
369
|
+
type === 'image-file-reference'
|
|
370
|
+
) {
|
|
371
|
+
result += '[Image]'
|
|
372
|
+
} else if (type === 'custom') {
|
|
373
|
+
result += '[Custom Content]'
|
|
374
|
+
} else {
|
|
375
|
+
return UNPARSABLE_TOOL_RESULT
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return result
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* @param {ToolCallResultContent | null | undefined} content
|
|
310
384
|
* @returns {string}
|
|
311
385
|
*/
|
|
312
386
|
function getToolCallResultContent (content) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
387
|
+
try {
|
|
388
|
+
if (typeof content !== 'object' || content === null) return UNPARSABLE_TOOL_RESULT
|
|
389
|
+
|
|
390
|
+
const { output, result } = content
|
|
391
|
+
if (output !== undefined) {
|
|
392
|
+
if (typeof output !== 'object' || output === null) return UNPARSABLE_TOOL_RESULT
|
|
393
|
+
|
|
394
|
+
const { type, value } = output
|
|
395
|
+
if (type === 'text' || type === 'error-text') {
|
|
396
|
+
return typeof value === 'string' ? value : UNPARSABLE_TOOL_RESULT
|
|
397
|
+
} else if (type === 'json' || type === 'error-json') {
|
|
398
|
+
return stringifyToolCallResult(value)
|
|
399
|
+
} else if (type === 'content') {
|
|
400
|
+
return formatToolCallContent(value)
|
|
401
|
+
} else if (type === 'execution-denied') {
|
|
402
|
+
const { reason } = output
|
|
403
|
+
if (reason === undefined) return '[Tool Execution Denied]'
|
|
404
|
+
return typeof reason === 'string' ? reason : UNPARSABLE_TOOL_RESULT
|
|
405
|
+
}
|
|
406
|
+
return UNPARSABLE_TOOL_RESULT
|
|
319
407
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
if (typeof result === 'string') {
|
|
323
|
-
return result
|
|
408
|
+
if (result !== undefined) {
|
|
409
|
+
return typeof result === 'string' ? result : stringifyToolCallResult(result)
|
|
324
410
|
}
|
|
325
411
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
return '[Unparsable Tool Result]'
|
|
330
|
-
}
|
|
331
|
-
} else {
|
|
332
|
-
return '[Unsupported Tool Result]'
|
|
412
|
+
return UNSUPPORTED_TOOL_RESULT
|
|
413
|
+
} catch {
|
|
414
|
+
return UNPARSABLE_TOOL_RESULT
|
|
333
415
|
}
|
|
334
416
|
}
|
|
335
417
|
|
|
@@ -93,7 +93,7 @@ class AnthropicLLMObsPlugin extends LLMObsPlugin {
|
|
|
93
93
|
|
|
94
94
|
const { usage } = chunk
|
|
95
95
|
if (usage) {
|
|
96
|
-
const responseUsage =
|
|
96
|
+
const responseUsage = (response.usage ??= { input_tokens: 0, output_tokens: 0 })
|
|
97
97
|
responseUsage.output_tokens = usage.output_tokens
|
|
98
98
|
|
|
99
99
|
const cacheCreationTokens = usage.cache_creation_input_tokens
|
|
@@ -8,7 +8,7 @@ const { splitModel } = require('../../../../../datadog-plugin-claude-agent-sdk/s
|
|
|
8
8
|
const subagentToolIds = new Set()
|
|
9
9
|
|
|
10
10
|
function normalizeToolOutputString (raw) {
|
|
11
|
-
const footerIndex = raw.search(
|
|
11
|
+
const footerIndex = raw.search(/agentId: \S+ \(use SendMessage\b/)
|
|
12
12
|
if (footerIndex === -1) return raw
|
|
13
13
|
|
|
14
14
|
return raw.slice(0, footerIndex).trimEnd()
|
|
@@ -24,7 +24,7 @@ class GenAiLLMObsPlugin extends LLMObsPlugin {
|
|
|
24
24
|
// Subscribe to streaming chunk events
|
|
25
25
|
this.addSub('apm:google:genai:request:chunk', ({ ctx, chunk, done }) => {
|
|
26
26
|
ctx.isStreaming = true
|
|
27
|
-
ctx.chunks
|
|
27
|
+
ctx.chunks ||= []
|
|
28
28
|
|
|
29
29
|
if (chunk) ctx.chunks.push(chunk)
|
|
30
30
|
if (!done) return
|
|
@@ -36,7 +36,7 @@ class LangChainLLMObsHandler {
|
|
|
36
36
|
const runIdBase = runId ? runId.split('-').slice(0, -1).join('-') : ''
|
|
37
37
|
|
|
38
38
|
const responseMetadata = message.response_metadata || {}
|
|
39
|
-
usage
|
|
39
|
+
usage ||= responseMetadata.usage || responseMetadata.tokenUsage || {}
|
|
40
40
|
|
|
41
41
|
const inputTokens = usage.promptTokens || usage.inputTokens || usage.prompt_tokens || usage.input_tokens || 0
|
|
42
42
|
const outputTokens =
|
|
@@ -5,7 +5,6 @@ const {
|
|
|
5
5
|
PROMPT_TRACKING_INSTRUMENTATION_METHOD,
|
|
6
6
|
PROMPT_MULTIMODAL,
|
|
7
7
|
INSTRUMENTATION_METHOD_AUTO,
|
|
8
|
-
UNKNOWN_MODEL_PROVIDER,
|
|
9
8
|
} = require('../../constants/tags')
|
|
10
9
|
const { audioMimeTypeFromFormat, formatAudioPart, safeJsonParse } = require('../../util')
|
|
11
10
|
const { AUDIO_MIME_TYPES } = require('./constants')
|
|
@@ -15,6 +14,7 @@ const {
|
|
|
15
14
|
extractTextFromContentItem,
|
|
16
15
|
extractContentParts,
|
|
17
16
|
hasMultimodalInputs,
|
|
17
|
+
getOpenAIModelProvider,
|
|
18
18
|
} = require('./utils')
|
|
19
19
|
|
|
20
20
|
const allowedParamKeys = new Set([
|
|
@@ -108,14 +108,10 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
_getModelProviderAndClient (baseUrl = '') {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
} else if (baseUrl.includes('deepseek')) {
|
|
116
|
-
return { modelProvider: 'deepseek', client: 'DeepSeek' }
|
|
117
|
-
}
|
|
118
|
-
return { modelProvider: UNKNOWN_MODEL_PROVIDER, client: 'OpenAI' }
|
|
111
|
+
const modelProvider = getOpenAIModelProvider(baseUrl)
|
|
112
|
+
if (modelProvider === 'azure_openai') return { modelProvider, client: 'AzureOpenAI' }
|
|
113
|
+
if (modelProvider === 'deepseek') return { modelProvider, client: 'DeepSeek' }
|
|
114
|
+
return { modelProvider, client: 'OpenAI' }
|
|
119
115
|
}
|
|
120
116
|
|
|
121
117
|
_extractMetrics (response) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { UNKNOWN_MODEL_PROVIDER } = require('../../constants/tags')
|
|
3
4
|
const { audioMimeTypeFromFormat, formatAudioPart } = require('../../util')
|
|
4
5
|
const {
|
|
5
6
|
INPUT_TYPE_IMAGE,
|
|
@@ -160,10 +161,29 @@ function extractContentParts (parts) {
|
|
|
160
161
|
return { content: extracted.join('\n'), audioParts }
|
|
161
162
|
}
|
|
162
163
|
|
|
164
|
+
/**
|
|
165
|
+
* Maps an OpenAI-compatible base URL to a model provider string. Covers
|
|
166
|
+
* OpenAI, Azure OpenAI, and DeepSeek; falls back to UNKNOWN_MODEL_PROVIDER
|
|
167
|
+
* for unrecognised hosts (e.g. local proxies or custom deployments).
|
|
168
|
+
*
|
|
169
|
+
* Shared with the openai-agents integration since both consume the same
|
|
170
|
+
* client baseURL convention.
|
|
171
|
+
*
|
|
172
|
+
* @param {string} baseUrl
|
|
173
|
+
* @returns {string}
|
|
174
|
+
*/
|
|
175
|
+
function getOpenAIModelProvider (baseUrl = '') {
|
|
176
|
+
if (baseUrl.includes('azure')) return 'azure_openai'
|
|
177
|
+
if (baseUrl.includes('deepseek')) return 'deepseek'
|
|
178
|
+
if (baseUrl.includes('openai')) return 'openai'
|
|
179
|
+
return UNKNOWN_MODEL_PROVIDER
|
|
180
|
+
}
|
|
181
|
+
|
|
163
182
|
module.exports = {
|
|
164
183
|
extractChatTemplateFromInstructions,
|
|
165
184
|
normalizePromptVariables,
|
|
166
185
|
extractTextFromContentItem,
|
|
167
186
|
extractContentParts,
|
|
168
187
|
hasMultimodalInputs,
|
|
188
|
+
getOpenAIModelProvider,
|
|
169
189
|
}
|