UNPKG

@mastra/core

Version:
419 lines (418 loc) 14 kB
import { a as getOrCreateSpan, f as EntityType } from "./utils-DxsDNzD2.js"; import "./tracing-Bm0k4FBA.js"; //#region src/observability/types/core.ts /** * Sampling strategy types */ let SamplingStrategyType = /* @__PURE__ */ function(SamplingStrategyType) { SamplingStrategyType["ALWAYS"] = "always"; SamplingStrategyType["NEVER"] = "never"; SamplingStrategyType["RATIO"] = "ratio"; SamplingStrategyType["CUSTOM"] = "custom"; return SamplingStrategyType; }({}); //#endregion //#region src/observability/types/metrics.ts /** * Default labels to block from metrics to prevent cardinality explosion. * These are high-cardinality fields that should not be used as metric labels. */ const DEFAULT_BLOCKED_LABELS = [ "trace_id", "span_id", "run_id", "request_id", "user_id", "resource_id", "session_id", "thread_id" ]; //#endregion //#region src/observability/no-op.ts const noOpCounter = { add() {} }; const noOpGauge = { set() {} }; const noOpHistogram = { record() {} }; /** * No-op tracing context used when observability is not configured. */ const noOpTracingContext = { currentSpan: void 0 }; /** * No-op logger context that silently discards all log calls. * Used when observability is not configured. */ const noOpLoggerContext = { debug() {}, info() {}, warn() {}, error() {}, fatal() {} }; /** * No-op metrics context that silently discards all metric operations. * Used when observability is not configured. */ const noOpMetricsContext = { emit() {}, counter() { return noOpCounter; }, gauge() { return noOpGauge; }, histogram() { return noOpHistogram; } }; /** No-op observability entrypoint that silently discards all operations. */ var NoOpObservability = class { setMastraContext(_options) {} setLogger(_options) {} getSelectedInstance(_options) {} async getRecordedTrace(_args) { return null; } async addScore(_args) {} async addFeedback(_args) {} registerInstance(_name, _instance, _isDefault = false) {} getInstance(_name) {} getDefaultInstance() {} listInstances() { return /* @__PURE__ */ new Map(); } unregisterInstance(_name) { return false; } hasInstance(_name) { return false; } setConfigSelector(_selector) {} clear() {} async flush() {} async shutdown() {} }; //#endregion //#region src/observability/context-factory.ts /** * Derives a LoggerContext from the current span's ObservabilityInstance. * Falls back to no-op when there is no span or the instance doesn't support logging. */ function deriveLoggerContext(tracing) { const span = tracing.currentSpan; return span?.observabilityInstance?.getLoggerContext?.(span) ?? noOpLoggerContext; } /** * Derives a MetricsContext from the current span's ObservabilityInstance. * Falls back to no-op when there is no span or the instance doesn't support metrics. */ function deriveMetricsContext(tracing) { const span = tracing.currentSpan; return span?.observabilityInstance?.getMetricsContext?.(span) ?? noOpMetricsContext; } /** * Creates an observability context with real or no-op implementations for * tracing, logging, and metrics. * * When a TracingContext with a current span is provided, the logger and metrics * contexts are derived from the span's ObservabilityInstance so that log entries * and metric data points are automatically correlated to the active trace. * * @param tracingContext - TracingContext with current span, or undefined for no-op * @returns ObservabilityContext with all three signals (tracing, logger, metrics) */ function createObservabilityContext(tracingContext) { const tracing = tracingContext ?? noOpTracingContext; return { tracing, loggerVNext: deriveLoggerContext(tracing), metrics: deriveMetricsContext(tracing), tracingContext: tracing }; } /** * Resolves a partial observability context (from execute params) into a * complete ObservabilityContext with no-op defaults for any missing fields. * * Explicitly provided logger/metrics contexts are preserved (e.g. when set * upstream). When missing, they are derived from the tracing context's span, * following the same derivation logic as createObservabilityContext(). * * @param partial - Partial context from ExecuteFunctionParams * @returns Complete ObservabilityContext */ function resolveObservabilityContext(partial) { const tracing = partial.tracing ?? partial.tracingContext ?? noOpTracingContext; return { tracing, loggerVNext: partial.loggerVNext ?? deriveLoggerContext(tracing), metrics: partial.metrics ?? deriveMetricsContext(tracing), tracingContext: tracing }; } //#endregion //#region src/observability/context.ts const AGENT_GETTERS = ["getAgent", "getAgentById"]; const AGENT_METHODS_TO_WRAP = [ "generate", "stream", "generateLegacy", "streamLegacy" ]; const WORKFLOW_GETTERS = ["getWorkflow", "getWorkflowById"]; const WORKFLOW_METHODS_TO_WRAP = [ "execute", "createRun", "createRun" ]; /** * Helper function to detect NoOp spans to avoid unnecessary wrapping */ function isNoOpSpan(span) { return span.constructor.name === "NoOpSpan" || span.__isNoOp === true; } /** * Checks to see if a passed object is an actual instance of Mastra * (for the purposes of wrapping it for Tracing) */ function isMastra(mastra) { const hasAgentGetters = AGENT_GETTERS.every((method) => typeof mastra?.[method] === "function"); const hasWorkflowGetters = WORKFLOW_GETTERS.every((method) => typeof mastra?.[method] === "function"); return hasAgentGetters && hasWorkflowGetters; } /** * Creates a tracing-aware Mastra proxy that automatically injects * tracing context into agent and workflow method calls */ function wrapMastra(mastra, tracingContext) { if (!tracingContext.currentSpan || isNoOpSpan(tracingContext.currentSpan)) return mastra; if (!isMastra(mastra)) return mastra; try { return new Proxy(mastra, { get(target, prop) { try { if (AGENT_GETTERS.includes(prop)) return (...args) => { return wrapAgent(target[prop](...args), tracingContext); }; if (WORKFLOW_GETTERS.includes(prop)) return (...args) => { return wrapWorkflow(target[prop](...args), tracingContext); }; const value = target[prop]; return typeof value === "function" ? value.bind(target) : value; } catch (error) { console.warn("Tracing: Failed to wrap method, falling back to original", error); const value = target[prop]; return typeof value === "function" ? value.bind(target) : value; } } }); } catch (error) { console.warn("Tracing: Failed to create proxy, using original Mastra instance", error); return mastra; } } /** * Creates a tracing-aware Agent proxy that automatically injects * tracing context into generation method calls */ function wrapAgent(agent, tracingContext) { if (!tracingContext.currentSpan || isNoOpSpan(tracingContext.currentSpan)) return agent; try { return new Proxy(agent, { get(target, prop) { try { if (AGENT_METHODS_TO_WRAP.includes(prop)) return (input, options = {}) => { return target[prop](input, { ...options, ...createObservabilityContext(tracingContext) }); }; const value = target[prop]; return typeof value === "function" ? value.bind(target) : value; } catch (error) { console.warn("Tracing: Failed to wrap agent method, falling back to original", error); const value = target[prop]; return typeof value === "function" ? value.bind(target) : value; } } }); } catch (error) { console.warn("Tracing: Failed to create agent proxy, using original instance", error); return agent; } } /** * Creates a tracing-aware Workflow proxy that automatically injects * tracing context into execution method calls */ function wrapWorkflow(workflow, tracingContext) { if (!tracingContext.currentSpan || isNoOpSpan(tracingContext.currentSpan)) return workflow; try { return new Proxy(workflow, { get(target, prop) { try { if (WORKFLOW_METHODS_TO_WRAP.includes(prop)) { if (prop === "createRun" || prop === "createRun") return async (options = {}) => { const run = await target[prop](options); return run ? wrapRun(run, tracingContext) : run; }; return (input, options = {}) => { return target[prop](input, { ...options, ...createObservabilityContext(tracingContext) }); }; } const value = target[prop]; return typeof value === "function" ? value.bind(target) : value; } catch (error) { console.warn("Tracing: Failed to wrap workflow method, falling back to original", error); const value = target[prop]; return typeof value === "function" ? value.bind(target) : value; } } }); } catch (error) { console.warn("Tracing: Failed to create workflow proxy, using original instance", error); return workflow; } } /** * Creates a tracing-aware Run proxy that automatically injects * tracing context into start method calls */ function wrapRun(run, tracingContext) { if (!tracingContext.currentSpan || isNoOpSpan(tracingContext.currentSpan)) return run; try { return new Proxy(run, { get(target, prop) { try { if (prop === "start") return (startOptions = {}) => { return target.start({ ...startOptions, ...createObservabilityContext(startOptions.tracingContext ?? tracingContext) }); }; const value = target[prop]; return typeof value === "function" ? value.bind(target) : value; } catch (error) { console.warn("Tracing: Failed to wrap run method, falling back to original", error); const value = target[prop]; return typeof value === "function" ? value.bind(target) : value; } } }); } catch (error) { console.warn("Tracing: Failed to create run proxy, using original instance", error); return run; } } //#endregion //#region src/observability/rag-ingestion.ts /** * RAG ingestion helpers. * * Provides a thin wrapper around `getOrCreateSpan` for starting a * `RAG_INGESTION` root span without reaching into observability internals. * * Two surfaces: * - `startRagIngestion(...)` — manual: returns `{ span, observabilityContext }`, * caller is responsible for `.end()` / `.error()`. * - `withRagIngestion(opts, fn)` — scoped: runs `fn(observabilityContext)`, * automatically attaches the return value as the span's `output` and * routes thrown errors to `span.error(...)`. * * ## Observability data * * Mastra emits raw span data (start/end timestamps, attributes, input, * output) — exporters and downstream consumers do their own aggregation. * The shapes below are designed to make the common derivations cheap: * * - **Duration**: every span has start/end, so per-operation latency * falls out for free. * - **Embedding cost**: `RAG_EMBEDDING` spans (and only `RAG_EMBEDDING` * spans) expose `attributes.usage` using the same `UsageStats` shape as * `MODEL_GENERATION`, so any existing LLM cost-extraction pipeline that * parses `usage.inputTokens` handles embeddings uniformly. Cost * dimensions are `{model, provider, mode}` (mode is `'ingest'` or * `'query'`). Token counts are deliberately NOT duplicated on the * `RAG_INGESTION` root — aggregating at the root would double-count * when summing child spans. Mirrors how `AGENT_RUN` does not carry * aggregated `MODEL_GENERATION` usage. * - **Vector store throughput**: `RAG_VECTOR_OPERATION` spans carry * `{operation, store, indexName}` as attributes; result counts live on * `output` (e.g. `output.returned`, `output.vectorCount`). * - **Ingestion roll-ups**: `RAG_INGESTION` (root) carries * `{vectorStore, indexName, embeddingModel, embeddingProvider}` as * attributes and aggregate `usage` summed across child embed calls. */ /** * Start a `RAG_INGESTION` root span. Caller is responsible for closing it * via `result.span?.end(...)` or `result.span?.error(...)`. * * Prefer `withRagIngestion` for the common try/catch/end flow. * * @example * ```ts * const { span, observabilityContext } = startRagIngestion({ * mastra, * name: 'docs ingestion', * attributes: { vectorStore: 'pgvector', indexName: 'docs' }, * }); * try { * const chunks = await doc.chunk({ observabilityContext }); * // ... * span?.end({ output: { chunkCount: chunks.length } }); * } catch (err) { * span?.error({ error: err as Error }); * throw err; * } * ``` */ function startRagIngestion(options) { const span = getOrCreateSpan({ ...options, entityType: EntityType.RAG_INGESTION, type: "rag_ingestion" }); return { span, observabilityContext: createObservabilityContext(span ? { currentSpan: span } : void 0) }; } /** * Run an async function inside a `RAG_INGESTION` root span. * * The callback receives an `ObservabilityContext` to thread into chunk, * embed, and vector-store calls. The return value is attached to the span * as `output`. Thrown errors are recorded via `span.error(...)` and * re-thrown. * * @example * ```ts * await withRagIngestion( * { * mastra, * name: 'docs ingestion', * attributes: { vectorStore: 'pgvector', indexName: 'docs' }, * }, * async (observabilityContext) => { * const chunks = await doc.chunk({ observabilityContext }); * const { embeddings } = await embed(chunks, { observabilityContext }); * await vectorStore.upsert({ * indexName: 'docs', * vectors: embeddings, * observabilityContext, * }); * return { chunkCount: chunks.length }; * }, * ); * ``` */ async function withRagIngestion(options, fn) { const { span, observabilityContext } = startRagIngestion(options); try { const result = await fn(observabilityContext); span?.end({ output: result }); return result; } catch (err) { span?.error({ error: err, endSpan: true }); throw err; } } //#endregion export { resolveObservabilityContext as a, noOpMetricsContext as c, SamplingStrategyType as d, createObservabilityContext as i, noOpTracingContext as l, withRagIngestion as n, NoOpObservability as o, wrapMastra as r, noOpLoggerContext as s, startRagIngestion as t, DEFAULT_BLOCKED_LABELS as u }; //# sourceMappingURL=observability-Cz-X7NF_.js.map