@dbx-tools/appkit-mastra 0.9.64 → 0.9.67
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/README.md +29 -3
- package/exports.ts +7 -0
- package/index.ts +7 -3
- package/lib/exports.d.ts +1 -0
- package/lib/exports.js +2 -0
- package/lib/index.d.ts +7 -3
- package/lib/index.js +7 -3
- package/lib/src/genie.js +3 -2
- package/lib/src/mlflow.d.ts +48 -5
- package/lib/src/mlflow.js +214 -8
- package/lib/src/model.js +4 -1
- package/lib/src/observability.d.ts +2 -1
- package/lib/src/observability.js +11 -6
- package/lib/src/plugin.js +10 -2
- package/lib/src/server.js +12 -1
- package/lib/src/telemetry.d.ts +17 -16
- package/lib/src/telemetry.js +161 -23
- package/lib/src/trace-attributes.d.ts +26 -0
- package/lib/src/trace-attributes.js +27 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +30 -16
- package/src/genie.ts +2 -1
- package/src/mlflow.ts +244 -7
- package/src/model.ts +3 -0
- package/src/observability.ts +10 -5
- package/src/plugin.ts +8 -0
- package/src/server.ts +13 -0
- package/src/telemetry.ts +221 -23
- package/src/trace-attributes.ts +38 -0
package/package.json
CHANGED
|
@@ -19,8 +19,7 @@
|
|
|
19
19
|
"projen": "projen"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
|
-
"@
|
|
23
|
-
"@opentelemetry/sdk-trace-node": "2.8.0",
|
|
22
|
+
"@mlflow/core": "0.4.0",
|
|
24
23
|
"@types/express": "^5.0.5",
|
|
25
24
|
"@types/node": "^24.6.0",
|
|
26
25
|
"@types/pg": "^8",
|
|
@@ -29,23 +28,24 @@
|
|
|
29
28
|
"vitest": "3.2.4"
|
|
30
29
|
},
|
|
31
30
|
"peerDependencies": {
|
|
31
|
+
"@mlflow/core": "0.4.0",
|
|
32
32
|
"skills": "^1"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@databricks/appkit": "0.81.0",
|
|
36
|
-
"@dbx-tools/appkit": "^0.9.
|
|
37
|
-
"@dbx-tools/core": "^0.9.
|
|
38
|
-
"@dbx-tools/databricks": "^0.9.
|
|
39
|
-
"@dbx-tools/fs": "^0.9.
|
|
40
|
-
"@dbx-tools/genie": "^0.9.
|
|
41
|
-
"@dbx-tools/model": "^0.9.
|
|
42
|
-
"@dbx-tools/path": "^0.9.
|
|
43
|
-
"@dbx-tools/postgres": "^0.9.
|
|
44
|
-
"@dbx-tools/shared-core": "^0.9.
|
|
45
|
-
"@dbx-tools/shared-fs": "^0.9.
|
|
46
|
-
"@dbx-tools/shared-genie": "^0.9.
|
|
47
|
-
"@dbx-tools/shared-mastra": "^0.9.
|
|
48
|
-
"@dbx-tools/shared-model": "^0.9.
|
|
36
|
+
"@dbx-tools/appkit": "^0.9.67",
|
|
37
|
+
"@dbx-tools/core": "^0.9.67",
|
|
38
|
+
"@dbx-tools/databricks": "^0.9.67",
|
|
39
|
+
"@dbx-tools/fs": "^0.9.67",
|
|
40
|
+
"@dbx-tools/genie": "^0.9.67",
|
|
41
|
+
"@dbx-tools/model": "^0.9.67",
|
|
42
|
+
"@dbx-tools/path": "^0.9.67",
|
|
43
|
+
"@dbx-tools/postgres": "^0.9.67",
|
|
44
|
+
"@dbx-tools/shared-core": "^0.9.67",
|
|
45
|
+
"@dbx-tools/shared-fs": "^0.9.67",
|
|
46
|
+
"@dbx-tools/shared-genie": "^0.9.67",
|
|
47
|
+
"@dbx-tools/shared-mastra": "^0.9.67",
|
|
48
|
+
"@dbx-tools/shared-model": "^0.9.67",
|
|
49
49
|
"@mastra/ai-sdk": "1.10.5",
|
|
50
50
|
"@mastra/core": "1.71.0",
|
|
51
51
|
"@mastra/express": "1.5.15",
|
|
@@ -58,6 +58,8 @@
|
|
|
58
58
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
59
59
|
"@opentelemetry/api": "^1.9.1",
|
|
60
60
|
"@opentelemetry/core": "2.11.0",
|
|
61
|
+
"@opentelemetry/sdk-trace-base": "2.8.0",
|
|
62
|
+
"@opentelemetry/sdk-trace-node": "2.8.0",
|
|
61
63
|
"@pydantic/monty": "0.0.23",
|
|
62
64
|
"express": "^5.1.0",
|
|
63
65
|
"pg": "^8.22.0",
|
|
@@ -194,6 +196,10 @@
|
|
|
194
196
|
"types": "./lib/src/telemetry.d.ts",
|
|
195
197
|
"default": "./lib/src/telemetry.js"
|
|
196
198
|
},
|
|
199
|
+
"./trace-attributes": {
|
|
200
|
+
"types": "./lib/src/trace-attributes.d.ts",
|
|
201
|
+
"default": "./lib/src/trace-attributes.js"
|
|
202
|
+
},
|
|
197
203
|
"./validation": {
|
|
198
204
|
"types": "./lib/src/validation.d.ts",
|
|
199
205
|
"default": "./lib/src/validation.js"
|
|
@@ -209,7 +215,7 @@
|
|
|
209
215
|
"./package.json": "./package.json"
|
|
210
216
|
}
|
|
211
217
|
},
|
|
212
|
-
"version": "0.9.
|
|
218
|
+
"version": "0.9.67",
|
|
213
219
|
"types": "./lib/index.d.ts",
|
|
214
220
|
"dbxToolsConfig": {
|
|
215
221
|
"tags": [
|
|
@@ -342,6 +348,10 @@
|
|
|
342
348
|
"types": "./lib/src/telemetry.d.ts",
|
|
343
349
|
"default": "./lib/src/telemetry.js"
|
|
344
350
|
},
|
|
351
|
+
"./trace-attributes": {
|
|
352
|
+
"types": "./lib/src/trace-attributes.d.ts",
|
|
353
|
+
"default": "./lib/src/trace-attributes.js"
|
|
354
|
+
},
|
|
345
355
|
"./validation": {
|
|
346
356
|
"types": "./lib/src/validation.d.ts",
|
|
347
357
|
"default": "./lib/src/validation.js"
|
|
@@ -359,11 +369,15 @@
|
|
|
359
369
|
"files": [
|
|
360
370
|
"index.ts",
|
|
361
371
|
"src",
|
|
372
|
+
"exports.ts",
|
|
362
373
|
"lib"
|
|
363
374
|
],
|
|
364
375
|
"peerDependenciesMeta": {
|
|
365
376
|
"skills": {
|
|
366
377
|
"optional": true
|
|
378
|
+
},
|
|
379
|
+
"@mlflow/core": {
|
|
380
|
+
"optional": true
|
|
367
381
|
}
|
|
368
382
|
},
|
|
369
383
|
"//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"bunx projen\"."
|
package/src/genie.ts
CHANGED
|
@@ -59,7 +59,7 @@ import { chartPlannerRequestSchema, chartToolOutputSchema, prepareChart } from "
|
|
|
59
59
|
import { MASTRA_USER_KEY, resolveUserKey } from "./config.ts";
|
|
60
60
|
import type { MastraPluginConfig, User } from "./config.ts";
|
|
61
61
|
import { fetchStatementData } from "./statement.ts";
|
|
62
|
-
import { stampGenieToolSpan } from "./telemetry.ts";
|
|
62
|
+
import { recordActiveTraceAuth, stampGenieToolSpan } from "./telemetry.ts";
|
|
63
63
|
import { safeWriteProgress } from "./writer.ts";
|
|
64
64
|
|
|
65
65
|
const logger = log.logger("mastra/genie");
|
|
@@ -128,6 +128,7 @@ function requireClient(
|
|
|
128
128
|
"Invoke the tool from an agent turn served by the mastra plugin, which stamps the AppKit user on the Mastra request context.",
|
|
129
129
|
);
|
|
130
130
|
}
|
|
131
|
+
recordActiveTraceAuth("isUserContext" in user.executionContext ? "obo" : "service-principal");
|
|
131
132
|
return { client: user.executionContext.client, requestContext };
|
|
132
133
|
}
|
|
133
134
|
|
package/src/mlflow.ts
CHANGED
|
@@ -27,13 +27,92 @@
|
|
|
27
27
|
|
|
28
28
|
import { ConfigurationError } from "@databricks/appkit";
|
|
29
29
|
import { appkit } from "@dbx-tools/appkit";
|
|
30
|
-
import { asyncUtils, errorUtils, log, object } from "@dbx-tools/shared-core";
|
|
30
|
+
import { asyncUtils, environmentUtils, errorUtils, log, object } from "@dbx-tools/shared-core";
|
|
31
31
|
import { feedback } from "@dbx-tools/shared-mastra";
|
|
32
|
+
import type { Context } from "@opentelemetry/api";
|
|
33
|
+
import type {
|
|
34
|
+
ReadableSpan,
|
|
35
|
+
Span as SdkSpan,
|
|
36
|
+
SpanProcessor,
|
|
37
|
+
} from "@opentelemetry/sdk-trace-base";
|
|
38
|
+
import type { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
|
|
32
39
|
import { z } from "zod";
|
|
33
40
|
import { databricksFetch, readResponseJson, readResponseText } from "./rest.ts";
|
|
41
|
+
import { MLFLOW_AGENT_TAG_ATTR } from "./trace-attributes.ts";
|
|
34
42
|
|
|
35
43
|
const logger = log.logger("mastra/mlflow");
|
|
36
44
|
|
|
45
|
+
type MlflowModule = typeof import("@mlflow/core");
|
|
46
|
+
type UpdateCurrentTraceOptions = Parameters<MlflowModule["updateCurrentTrace"]>[0];
|
|
47
|
+
|
|
48
|
+
let directMlflow: MlflowModule | undefined;
|
|
49
|
+
let directMlflowInitStarted = false;
|
|
50
|
+
let directMlflowProvider: NodeTracerProvider | undefined;
|
|
51
|
+
let directMlflowProcessor: SpanProcessor | undefined;
|
|
52
|
+
|
|
53
|
+
const NOISY_INSTRUMENTATION_SCOPES = new Set([
|
|
54
|
+
"@opentelemetry/instrumentation-express",
|
|
55
|
+
"@opentelemetry/instrumentation-http",
|
|
56
|
+
"cache-manager-cache-manager",
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
function isAgentTraceRoot(span: SdkSpan): boolean {
|
|
60
|
+
return span.attributes[MLFLOW_AGENT_TAG_ATTR] === "true";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isUsefulAgentDescendant(span: SdkSpan): boolean {
|
|
64
|
+
return !NOISY_INSTRUMENTATION_SCOPES.has(span.instrumentationScope.name);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Restrict the direct MLflow provider to chat roots and their descendants.
|
|
69
|
+
* AppKit and Mastra create other OTel roots for cache and plugin operations;
|
|
70
|
+
* forwarding those would recreate the one-trace-per-request noise this path is
|
|
71
|
+
* intended to avoid.
|
|
72
|
+
*/
|
|
73
|
+
export class AgentTraceSpanProcessor implements SpanProcessor {
|
|
74
|
+
private readonly traceIds = new Set<string>();
|
|
75
|
+
private readonly spanIds = new Set<string>();
|
|
76
|
+
|
|
77
|
+
constructor(private readonly delegate: SpanProcessor) {}
|
|
78
|
+
|
|
79
|
+
onStart(span: SdkSpan, parentContext: Context): void {
|
|
80
|
+
const traceId = span.spanContext().traceId;
|
|
81
|
+
const isRoot = !span.parentSpanContext?.spanId;
|
|
82
|
+
if (isRoot) {
|
|
83
|
+
if (!isAgentTraceRoot(span)) return;
|
|
84
|
+
this.traceIds.add(traceId);
|
|
85
|
+
} else if (!this.traceIds.has(traceId) || !isUsefulAgentDescendant(span)) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
this.spanIds.add(span.spanContext().spanId);
|
|
89
|
+
this.delegate.onStart(span, parentContext);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
onEnding(span: SdkSpan): void {
|
|
93
|
+
if (!this.spanIds.has(span.spanContext().spanId)) return;
|
|
94
|
+
this.delegate.onEnding?.(span);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
onEnd(span: ReadableSpan): void {
|
|
98
|
+
const traceId = span.spanContext().traceId;
|
|
99
|
+
const spanId = span.spanContext().spanId;
|
|
100
|
+
if (!this.spanIds.delete(spanId)) return;
|
|
101
|
+
this.delegate.onEnd(span);
|
|
102
|
+
if (!span.parentSpanContext?.spanId) this.traceIds.delete(traceId);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
forceFlush(): Promise<void> {
|
|
106
|
+
return this.delegate.forceFlush();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
shutdown(): Promise<void> {
|
|
110
|
+
this.traceIds.clear();
|
|
111
|
+
this.spanIds.clear();
|
|
112
|
+
return this.delegate.shutdown();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
37
116
|
/** Workspace client carried on an AppKit execution context. */
|
|
38
117
|
type WorkspaceClient = appkit.WorkspaceClientLike;
|
|
39
118
|
|
|
@@ -214,14 +293,172 @@ function configuredExperiment(): { id?: string; name?: string } {
|
|
|
214
293
|
};
|
|
215
294
|
}
|
|
216
295
|
|
|
296
|
+
/** Tracking URI for direct local MLflow tracing, with CLI-profile auth injected. */
|
|
297
|
+
export function directMlflowTrackingUri(): string | undefined {
|
|
298
|
+
if (environmentUtils.isDatabricksAppEnv()) return undefined;
|
|
299
|
+
const configured = process.env.MLFLOW_TRACKING_URI?.trim();
|
|
300
|
+
if (configured && configured !== "databricks") return configured;
|
|
301
|
+
const profile = process.env.DATABRICKS_CONFIG_PROFILE?.trim();
|
|
302
|
+
return profile ? `databricks://${profile}` : "databricks";
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** Parse the optional UC table-prefix location used by direct local tracing. */
|
|
306
|
+
export function directMlflowTraceLocation():
|
|
307
|
+
| { catalogName: string; schemaName: string; tablePrefix: string }
|
|
308
|
+
| undefined {
|
|
309
|
+
const catalogName = process.env.MLFLOW_UC_CATALOG?.trim();
|
|
310
|
+
const schemaName = process.env.MLFLOW_UC_SCHEMA?.trim();
|
|
311
|
+
const tablePrefix = process.env.MLFLOW_UC_TABLE_PREFIX?.trim();
|
|
312
|
+
if (catalogName && schemaName && tablePrefix) {
|
|
313
|
+
return { catalogName, schemaName, tablePrefix };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const prefix = normalizeUcPrefix(process.env.MLFLOW_UC_TRACE_PREFIX);
|
|
317
|
+
if (!prefix) return undefined;
|
|
318
|
+
const parts = prefix.split(".");
|
|
319
|
+
if (parts.length !== 3) return undefined;
|
|
320
|
+
const [prefixCatalog, prefixSchema, prefixTable] = parts;
|
|
321
|
+
if (!prefixCatalog || !prefixSchema || !prefixTable) return undefined;
|
|
322
|
+
return {
|
|
323
|
+
catalogName: prefixCatalog,
|
|
324
|
+
schemaName: prefixSchema,
|
|
325
|
+
tablePrefix: prefixTable,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** Whether the local direct-to-experiment MLflow SDK has enough configuration. */
|
|
330
|
+
export function directMlflowTracingConfigured(): boolean {
|
|
331
|
+
return Boolean(
|
|
332
|
+
!environmentUtils.isDatabricksAppEnv() &&
|
|
333
|
+
process.env.MLFLOW_EXPERIMENT_ID?.trim() &&
|
|
334
|
+
directMlflowTrackingUri(),
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async function detectedDirectMlflowTraceLocation(
|
|
339
|
+
mlflow: MlflowModule,
|
|
340
|
+
trackingUri: string,
|
|
341
|
+
experimentId: string,
|
|
342
|
+
host: string | undefined,
|
|
343
|
+
): Promise<{ catalogName: string; schemaName: string; tablePrefix: string } | undefined> {
|
|
344
|
+
const explicit = directMlflowTraceLocation();
|
|
345
|
+
if (explicit) return explicit;
|
|
346
|
+
try {
|
|
347
|
+
const authProvider = mlflow.createAuthProvider({
|
|
348
|
+
trackingUri,
|
|
349
|
+
...(host ? { host } : {}),
|
|
350
|
+
});
|
|
351
|
+
const client = new mlflow.MlflowClient({ trackingUri, authProvider });
|
|
352
|
+
const experiment = await client.getExperiment(experimentId);
|
|
353
|
+
if (!experiment) return undefined;
|
|
354
|
+
const { ucLocationFromExperimentTags } = await import("@mlflow/core/dist/core/destination");
|
|
355
|
+
const location = ucLocationFromExperimentTags(experiment.tags);
|
|
356
|
+
if (!location?.tablePrefix) return undefined;
|
|
357
|
+
return {
|
|
358
|
+
catalogName: location.catalogName,
|
|
359
|
+
schemaName: location.schemaName,
|
|
360
|
+
tablePrefix: location.tablePrefix,
|
|
361
|
+
};
|
|
362
|
+
} catch (err) {
|
|
363
|
+
logger.warn("direct MLflow UC trace-location detection failed", {
|
|
364
|
+
experimentId,
|
|
365
|
+
error: errorUtils.errorMessage(err),
|
|
366
|
+
});
|
|
367
|
+
return undefined;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Start the MLflow Node tracer for local development when AppKit has no OTLP
|
|
373
|
+
* exporter. This provider is never started in the deployed Apps path, avoiding
|
|
374
|
+
* a second global OTel provider racing AppKit's telemetry manager.
|
|
375
|
+
*/
|
|
376
|
+
export async function initializeDirectMlflowTracing(): Promise<boolean> {
|
|
377
|
+
if (directMlflow) return true;
|
|
378
|
+
if (directMlflowInitStarted || !directMlflowTracingConfigured()) return false;
|
|
379
|
+
directMlflowInitStarted = true;
|
|
380
|
+
|
|
381
|
+
const experimentId = process.env.MLFLOW_EXPERIMENT_ID!.trim();
|
|
382
|
+
const trackingUri = directMlflowTrackingUri()!;
|
|
383
|
+
try {
|
|
384
|
+
const mlflow = await import("@mlflow/core");
|
|
385
|
+
const rawHost = process.env.DATABRICKS_HOST?.trim();
|
|
386
|
+
const host = rawHost
|
|
387
|
+
? /^https?:\/\//i.test(rawHost)
|
|
388
|
+
? rawHost
|
|
389
|
+
: `https://${rawHost}`
|
|
390
|
+
: undefined;
|
|
391
|
+
const traceLocation = await detectedDirectMlflowTraceLocation(
|
|
392
|
+
mlflow,
|
|
393
|
+
trackingUri,
|
|
394
|
+
experimentId,
|
|
395
|
+
host,
|
|
396
|
+
);
|
|
397
|
+
if (!traceLocation) {
|
|
398
|
+
throw new ConfigurationError(
|
|
399
|
+
"Direct local MLflow tracing requires a UC-linked experiment or MLFLOW_UC_TRACE_PREFIX.",
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
const [{ DatabricksUCTableSpanExporter, DatabricksUCTableSpanProcessor }, sdk] =
|
|
403
|
+
await Promise.all([
|
|
404
|
+
import("@mlflow/core/dist/exporters/uc_table"),
|
|
405
|
+
import("@opentelemetry/sdk-trace-node"),
|
|
406
|
+
]);
|
|
407
|
+
const authProvider = mlflow.createAuthProvider({
|
|
408
|
+
trackingUri,
|
|
409
|
+
...(host ? { host } : {}),
|
|
410
|
+
});
|
|
411
|
+
const client = new mlflow.MlflowClient({ trackingUri, authProvider });
|
|
412
|
+
const exporter = new DatabricksUCTableSpanExporter(client);
|
|
413
|
+
const processor = new AgentTraceSpanProcessor(
|
|
414
|
+
new DatabricksUCTableSpanProcessor(exporter, traceLocation),
|
|
415
|
+
);
|
|
416
|
+
const provider = new sdk.NodeTracerProvider({ spanProcessors: [processor] });
|
|
417
|
+
provider.register();
|
|
418
|
+
directMlflow = mlflow;
|
|
419
|
+
directMlflowProvider = provider;
|
|
420
|
+
directMlflowProcessor = processor;
|
|
421
|
+
logger.info("direct MLflow tracing enabled", {
|
|
422
|
+
experimentId,
|
|
423
|
+
trackingUri,
|
|
424
|
+
traceLocation,
|
|
425
|
+
});
|
|
426
|
+
return true;
|
|
427
|
+
} catch (err) {
|
|
428
|
+
logger.warn("direct MLflow tracing disabled", {
|
|
429
|
+
error: errorUtils.errorMessage(err),
|
|
430
|
+
});
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/** Persist trace-level fields on the direct MLflow trace in the current OTel context. */
|
|
436
|
+
export function updateActiveMlflowTrace(options: UpdateCurrentTraceOptions): void {
|
|
437
|
+
directMlflow?.updateCurrentTrace(options);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/** Persist tags on the direct MLflow trace active in the current OTel context. */
|
|
441
|
+
export function updateActiveMlflowTraceTags(tags: Record<string, string>): void {
|
|
442
|
+
updateActiveMlflowTrace({ tags });
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/** Whether this process owns the filtered local MLflow tracer provider. */
|
|
446
|
+
export function directMlflowTracingActive(): boolean {
|
|
447
|
+
return directMlflowProcessor !== undefined;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/** Flush pending direct local trace exports during graceful shutdown. */
|
|
451
|
+
export async function flushDirectMlflowTracing(): Promise<void> {
|
|
452
|
+
await directMlflowProcessor?.forceFlush();
|
|
453
|
+
await directMlflowProvider?.forceFlush();
|
|
454
|
+
}
|
|
455
|
+
|
|
217
456
|
/**
|
|
218
457
|
* Whether MLflow feedback logging is available for this deployment.
|
|
219
458
|
*
|
|
220
|
-
* Enabled when an
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
* materialize there. Both are standard env vars, so no plugin config is
|
|
224
|
-
* required; a deployment opts in simply by wiring MLflow tracing.
|
|
459
|
+
* Enabled when an MLflow experiment is configured and traces have an export
|
|
460
|
+
* path: AppKit OTLP in a Databricks App, or the direct MLflow Node SDK outside
|
|
461
|
+
* one. Both paths are selected from standard environment values.
|
|
225
462
|
*/
|
|
226
463
|
export function mlflowEnabled(): boolean {
|
|
227
464
|
const hasExporter = Boolean(
|
|
@@ -231,7 +468,7 @@ export function mlflowEnabled(): boolean {
|
|
|
231
468
|
const hasExperiment = Boolean(
|
|
232
469
|
process.env.MLFLOW_EXPERIMENT_ID?.trim() || process.env.MLFLOW_EXPERIMENT_NAME?.trim(),
|
|
233
470
|
);
|
|
234
|
-
return
|
|
471
|
+
return hasExperiment && (hasExporter || directMlflowTracingConfigured());
|
|
235
472
|
}
|
|
236
473
|
|
|
237
474
|
/**
|
package/src/model.ts
CHANGED
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
rewriteServingResponseStream,
|
|
38
38
|
} from "./serving-sanitize.ts";
|
|
39
39
|
import { MASTRA_MODEL_OVERRIDE_KEY, resolveServingConfig } from "./serving.ts";
|
|
40
|
+
import { recordActiveTraceAuth, recordActiveTraceModel } from "./telemetry.ts";
|
|
40
41
|
|
|
41
42
|
type ModelClass = model.ModelClass;
|
|
42
43
|
const { parseModelClass } = classes;
|
|
@@ -135,6 +136,7 @@ export async function buildModel(
|
|
|
135
136
|
// (the active OBO scope, or the service principal) when it's absent.
|
|
136
137
|
const user = requestContext.get(MASTRA_USER_KEY) as User | undefined;
|
|
137
138
|
const executionContext = user?.executionContext ?? getExecutionContext();
|
|
139
|
+
recordActiveTraceAuth("isUserContext" in executionContext ? "obo" : "service-principal");
|
|
138
140
|
const clientConfig = executionContext.client.config;
|
|
139
141
|
const host = (await clientConfig.getHost()).toString();
|
|
140
142
|
const headers = new Headers();
|
|
@@ -165,6 +167,7 @@ export async function buildModel(
|
|
|
165
167
|
selectionInput(config, requested, overrides.modelClass, serving),
|
|
166
168
|
);
|
|
167
169
|
logger.debug("model selected", { modelId, source, requested });
|
|
170
|
+
recordActiveTraceModel(modelId);
|
|
168
171
|
|
|
169
172
|
return {
|
|
170
173
|
providerId: config.providerId ?? "databricks",
|
package/src/observability.ts
CHANGED
|
@@ -49,7 +49,7 @@ import { OtelBridge } from "@mastra/otel-bridge";
|
|
|
49
49
|
import { propagation } from "@opentelemetry/api";
|
|
50
50
|
|
|
51
51
|
import { TRACE_REQUEST_CONTEXT_KEYS } from "./config.ts";
|
|
52
|
-
import { mlflowEnabled } from "./mlflow.ts";
|
|
52
|
+
import { initializeDirectMlflowTracing, mlflowEnabled } from "./mlflow.ts";
|
|
53
53
|
|
|
54
54
|
const logger = log.logger("mastra/observability");
|
|
55
55
|
|
|
@@ -117,7 +117,8 @@ export function configureOtelPropagation(): boolean {
|
|
|
117
117
|
* OTel pipeline via `@mastra/otel-bridge`.
|
|
118
118
|
*
|
|
119
119
|
* Returns `undefined` when tracing is off: either the caller passed
|
|
120
|
-
* `enabled: false`, or auto mode finds
|
|
120
|
+
* `enabled: false`, or auto mode finds neither AppKit OTLP nor a local MLflow
|
|
121
|
+
* experiment.
|
|
121
122
|
* Skipping the bridge in that case avoids `[OtelBridge] No OTEL span
|
|
122
123
|
* found` log spam from Mastra spans that have no parent on the noop
|
|
123
124
|
* tracer.
|
|
@@ -128,13 +129,17 @@ export async function buildObservability(
|
|
|
128
129
|
const otelBase = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
|
|
129
130
|
const otelTracesOverride = process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT;
|
|
130
131
|
const otlpConfigured = isOtlpTracingConfigured();
|
|
132
|
+
const directMlflowConfigured =
|
|
133
|
+
options?.enabled === false || otlpConfigured ? false : await initializeDirectMlflowTracing();
|
|
134
|
+
if (directMlflowConfigured) configureOtelPropagation();
|
|
135
|
+
const tracingConfigured = otlpConfigured || directMlflowConfigured;
|
|
131
136
|
|
|
132
|
-
if (options?.enabled === false || (options?.enabled !== true && !
|
|
137
|
+
if (options?.enabled === false || (options?.enabled !== true && !tracingConfigured)) {
|
|
133
138
|
logger.info("Mastra observability off", {
|
|
134
139
|
reason:
|
|
135
140
|
options?.enabled === false
|
|
136
141
|
? "disabled in plugin config"
|
|
137
|
-
: "
|
|
142
|
+
: "no OTLP endpoint or direct MLflow experiment configured",
|
|
138
143
|
});
|
|
139
144
|
return undefined;
|
|
140
145
|
}
|
|
@@ -164,7 +169,7 @@ export async function buildObservability(
|
|
|
164
169
|
otelBase: otelBase ?? "<unset>",
|
|
165
170
|
resolvedTracesUrl: resolvedTracesUrl ?? "<unset>",
|
|
166
171
|
feedback,
|
|
167
|
-
observability: feedback ? "mlflow" : "otel",
|
|
172
|
+
observability: directMlflowConfigured ? "mlflow-direct" : feedback ? "mlflow" : "otel",
|
|
168
173
|
});
|
|
169
174
|
|
|
170
175
|
return new Observability({
|
package/src/plugin.ts
CHANGED
|
@@ -100,6 +100,7 @@ import {
|
|
|
100
100
|
import { buildMcpServer, type ResolvedMcp } from "./mcp.ts";
|
|
101
101
|
import { createMemoryBuilder, createServicePrincipalPool, needsLakebase } from "./memory.ts";
|
|
102
102
|
import {
|
|
103
|
+
flushDirectMlflowTracing,
|
|
103
104
|
logFeedback,
|
|
104
105
|
mlflowExperimentManagerUrl,
|
|
105
106
|
resolveFeedbackEnabled,
|
|
@@ -304,6 +305,13 @@ export class MastraPlugin extends Plugin<MastraPluginConfig> {
|
|
|
304
305
|
* shutdown budget.
|
|
305
306
|
*/
|
|
306
307
|
async shutdown(): Promise<void> {
|
|
308
|
+
try {
|
|
309
|
+
await flushDirectMlflowTracing();
|
|
310
|
+
} catch (err) {
|
|
311
|
+
this.logger.warn("error flushing direct MLflow traces", {
|
|
312
|
+
error: errorUtils.errorMessage(err),
|
|
313
|
+
});
|
|
314
|
+
}
|
|
307
315
|
const pool = this.servicePrincipalPool;
|
|
308
316
|
if (!pool) return;
|
|
309
317
|
this.servicePrincipalPool = null;
|
package/src/server.ts
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
} from "./config.ts";
|
|
30
30
|
import { requestUserEmail, requestUserId } from "./identity.ts";
|
|
31
31
|
import { resolveFeedbackEnabled } from "./mlflow.ts";
|
|
32
|
+
import { recordActiveTraceAuth, recordActiveTraceUser } from "./telemetry.ts";
|
|
32
33
|
|
|
33
34
|
import {
|
|
34
35
|
extractModelOverride,
|
|
@@ -222,6 +223,18 @@ export class MastraServer extends ExpressMastraServer {
|
|
|
222
223
|
userId: requestUserId(req),
|
|
223
224
|
email: requestUserEmail(req),
|
|
224
225
|
});
|
|
226
|
+
const user = requestContext.get(MASTRA_USER_KEY) as User | undefined;
|
|
227
|
+
if (user) {
|
|
228
|
+
recordActiveTraceAuth(
|
|
229
|
+
"isUserContext" in user.executionContext ? "obo" : "service-principal",
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
const traceUser = [
|
|
233
|
+
requestContext.get(MASTRA_USER_EMAIL_KEY),
|
|
234
|
+
requestContext.get(MASTRA_USER_NAME_KEY),
|
|
235
|
+
requestContext.get(MASTRA_RESOURCE_ID_KEY),
|
|
236
|
+
].find((value): value is string => typeof value === "string" && value.trim().length > 0);
|
|
237
|
+
recordActiveTraceUser(traceUser);
|
|
225
238
|
}
|
|
226
239
|
|
|
227
240
|
/**
|