@mastra/langsmith 1.4.0 → 1.4.1-alpha.1

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/dist/index.cjs CHANGED
@@ -421,7 +421,8 @@ var LangSmithExporter = class extends _mastra_observability.TrackingExporter {
421
421
  "provider",
422
422
  "usage",
423
423
  "parameters",
424
- "completionStartTime"
424
+ "completionStartTime",
425
+ "timeToFirstTokenMs"
425
426
  ]);
426
427
  payload.metadata = {
427
428
  ...payload.metadata,
@@ -429,7 +430,7 @@ var LangSmithExporter = class extends _mastra_observability.TrackingExporter {
429
430
  };
430
431
  } else payload.metadata = {
431
432
  ...payload.metadata,
432
- ...attributes
433
+ ...(0, _mastra_core_utils.omitKeys)(attributes, ["timeToFirstTokenMs"])
433
434
  };
434
435
  if (span.errorInfo) {
435
436
  payload.error = span.errorInfo.message;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["createHash","SpanType","TrackingExporter","Client","RunTree","omitKeys"],"sources":["../src/metrics.ts","../src/tracing.ts","../src/helpers.ts"],"sourcesContent":["import type { UsageStats } from '@mastra/core/observability';\n\n/**\n * LangSmithUsageMetrics\n *\n * Canonical metric keys expected by LangSmith for LLM usage accounting.\n * See: https://docs.langchain.com/langsmith/log-llm-trace#provide-token-and-cost-information\n */\nexport interface LangSmithUsageMetrics {\n input_tokens?: number;\n output_tokens?: number;\n total_tokens?: number;\n input_token_details?: {\n [key: string]: number;\n };\n output_token_details?: {\n [key: string]: number;\n };\n}\n\n/**\n * Formats UsageStats to LangSmith's expected metric format.\n */\nexport function formatUsageMetrics(usage?: UsageStats): LangSmithUsageMetrics {\n const metrics: LangSmithUsageMetrics = {};\n\n if (usage?.inputTokens !== undefined) {\n metrics.input_tokens = usage.inputTokens;\n }\n\n if (usage?.outputTokens !== undefined) {\n metrics.output_tokens = usage.outputTokens;\n }\n\n // Compute total if we have both\n if (metrics.input_tokens !== undefined && metrics.output_tokens !== undefined) {\n metrics.total_tokens = metrics.input_tokens + metrics.output_tokens;\n }\n\n if (usage?.outputDetails?.reasoning !== undefined) {\n metrics.output_token_details = {\n ...(metrics.output_token_details ?? {}),\n reasoning_tokens: usage.outputDetails.reasoning,\n };\n }\n\n if (usage?.inputDetails?.cacheRead !== undefined) {\n metrics.input_token_details = {\n ...(metrics.input_token_details ?? {}),\n cache_read: usage.inputDetails.cacheRead,\n };\n }\n\n if (usage?.inputDetails?.cacheWrite !== undefined) {\n metrics.input_token_details = {\n ...(metrics.input_token_details ?? {}),\n cache_write: usage.inputDetails.cacheWrite,\n };\n }\n\n if (usage?.inputDetails?.cacheWrite5m !== undefined) {\n metrics.input_token_details = {\n ...(metrics.input_token_details ?? {}),\n cache_creation_5m: usage.inputDetails.cacheWrite5m,\n };\n }\n\n if (usage?.inputDetails?.cacheWrite1h !== undefined) {\n metrics.input_token_details = {\n ...(metrics.input_token_details ?? {}),\n cache_creation_1h: usage.inputDetails.cacheWrite1h,\n };\n }\n\n if (usage?.inputDetails?.audio !== undefined) {\n metrics.input_token_details = {\n ...(metrics.input_token_details ?? {}),\n audio: usage.inputDetails.audio,\n };\n }\n\n if (usage?.outputDetails?.audio !== undefined) {\n metrics.output_token_details = {\n ...(metrics.output_token_details ?? {}),\n audio: usage.outputDetails.audio,\n };\n }\n\n return metrics;\n}\n","/**\n * LangSmith Exporter for Mastra Tracing\n *\n * This exporter sends observability data to LangSmith\n * Root spans become top-level LangSmith RunTrees (no trace wrapper).\n * Events are handled as zero-duration RunTrees with matching start/end times.\n */\n\nimport { createHash } from 'node:crypto';\nimport type { AnyExportedSpan, ModelGenerationAttributes, ScoreEvent, SpanErrorInfo } from '@mastra/core/observability';\nimport { SpanType } from '@mastra/core/observability';\nimport { omitKeys } from '@mastra/core/utils';\nimport { TrackingExporter } from '@mastra/observability';\nimport type { TraceData, TrackingExporterConfig } from '@mastra/observability';\nimport type { ClientConfig, RunTreeConfig } from 'langsmith';\nimport { Client, RunTree } from 'langsmith';\nimport type { KVMap } from 'langsmith/schemas';\nimport type { LangSmithMetadataInput } from './helpers';\nimport { formatUsageMetrics } from './metrics';\n\nexport interface LangSmithExporterConfig extends ClientConfig, TrackingExporterConfig {\n /** LangSmith client instance */\n client?: Client;\n /**\n * The name of the LangSmith project to send traces to.\n * Overrides the LANGCHAIN_PROJECT environment variable.\n * If neither is set, traces are sent to the \"default\" project.\n */\n projectName?: string;\n /**\n * Maximum number of `traceId → root run id` mappings to keep in memory. The\n * cache lets a trace-level score skip the LangSmith lookup for traces this\n * process exported; scores for traces outside the cache are still delivered.\n * Older entries are evicted in LRU order when the cap is exceeded.\n * Defaults to 10000.\n */\n runIdCacheMaxEntries?: number;\n}\n\nconst DEFAULT_RUN_ID_CACHE_MAX_ENTRIES = 10_000;\n/** Waits between LangSmith lookups for a trace another process exported (ingestion takes ~5-8 s). */\nconst SCORE_LOOKUP_RETRY_DELAYS_MS = [2_000, 3_000, 5_000];\n/** Mastra trace ids are hex (`isValidTraceId` in @mastra/observability); anything else cannot match a run. */\nconst HEX_TRACE_ID = /^[0-9a-f]{1,32}$/i;\n\n/** The first root run of a Mastra trace and the LangSmith project it lives in. */\ninterface RootRun {\n runId: string;\n /** Project the root was posted to by this process; resolved to an id only when a score needs it. */\n projectName?: string;\n /** Project id read back from LangSmith when the root was found there. */\n sessionId?: string;\n}\n\n/** UUID v5 namespace for Mastra → LangSmith run ids. Changing it orphans existing runs from future scores. */\nconst RUN_ID_NAMESPACE = Buffer.from('a6f3c3f06b0e4a1e9f4c7e1b2d3c4f5a', 'hex');\n\n/**\n * LangSmith run id for a Mastra span id (UUID v5). Every run, root or not, is named\n * after its own span, so a span-level score can be routed from any process without\n * remembering the ids LangSmith would otherwise allocate.\n */\nexport function langsmithRunIdFor(spanId: string): string {\n const hash = createHash('sha1').update(RUN_ID_NAMESPACE).update(spanId).digest();\n hash[6] = (hash[6]! & 0x0f) | 0x50;\n hash[8] = (hash[8]! & 0x3f) | 0x80;\n const hex = hash.subarray(0, 16).toString('hex');\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\ntype LangSmithRoot = undefined;\ntype LangSmithSpan = RunTree;\ntype LangSmithEvent = RunTree;\ntype LangSmithMetadata = LangSmithMetadataInput;\ntype LangSmithTraceData = TraceData<LangSmithRoot, LangSmithSpan, LangSmithEvent, LangSmithMetadata>;\n\n// Default span type for all spans\nconst DEFAULT_SPAN_TYPE = 'chain';\n\n// Exceptions to the default mapping\nconst SPAN_TYPE_EXCEPTIONS: Partial<Record<SpanType, 'llm' | 'tool' | 'chain'>> = {\n [SpanType.MODEL_GENERATION]: 'llm',\n [SpanType.TOOL_CALL]: 'tool',\n [SpanType.MCP_TOOL_CALL]: 'tool',\n [SpanType.PROVIDER_TOOL_CALL]: 'tool',\n [SpanType.WORKFLOW_CONDITIONAL_EVAL]: 'chain',\n [SpanType.WORKFLOW_WAIT_EVENT]: 'chain',\n};\n\n// Mapping function - returns valid LangSmith span types\nfunction mapSpanType(spanType: SpanType): 'llm' | 'tool' | 'chain' {\n return SPAN_TYPE_EXCEPTIONS[spanType] ?? DEFAULT_SPAN_TYPE;\n}\n\nfunction isKVMap(value: unknown): value is KVMap {\n return value != null && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date);\n}\n\nexport class LangSmithExporter extends TrackingExporter<\n LangSmithRoot,\n LangSmithSpan,\n LangSmithEvent,\n LangSmithMetadata,\n LangSmithExporterConfig\n> {\n override name = 'langsmith';\n #client: Client | undefined;\n\n /**\n * Maps a Mastra trace id to the first root run this process exported for it, and the\n * project it went to. A fast path for scores: a trace-level score on a miss falls back\n * to asking LangSmith for the root run tagged with that `mastra_trace_id`.\n *\n * Bounded LRU keyed by Mastra trace id — entries are evicted oldest-first when\n * size exceeds `#runIdCacheMaxEntries` so the cache cannot grow without bound.\n */\n #rootRunByTraceId = new Map<string, RootRun>();\n /** Manual-mode client flushes, chained so an overlapping flush waits for the earlier upload */\n #manualFlushChain: Promise<void> = Promise.resolve();\n #runIdCacheMaxEntries: number;\n /** Project runs go to unless `withLangsmithMetadata({ projectName })` says otherwise. */\n #defaultProjectName: string;\n /** LangSmith project (session) ids by name: `createFeedback` requires one and run queries are scoped to one. */\n #projectIdByName = new Map<string, Promise<string | undefined>>();\n\n constructor(config: LangSmithExporterConfig = {}) {\n // Resolve env vars BEFORE calling super (config is readonly in base class)\n const apiKey = config.apiKey ?? process.env.LANGSMITH_API_KEY;\n\n super({\n ...config,\n apiKey,\n });\n\n this.#runIdCacheMaxEntries = Math.max(1, config.runIdCacheMaxEntries ?? DEFAULT_RUN_ID_CACHE_MAX_ENTRIES);\n this.#defaultProjectName =\n config.projectName ?? process.env.LANGSMITH_PROJECT ?? process.env.LANGCHAIN_PROJECT ?? 'default';\n\n if (!apiKey) {\n this.setDisabled(`Missing required credentials (apiKey: ${!!apiKey})`);\n return;\n }\n\n this.#client = config.client ?? new Client(this.config);\n }\n\n /** Look up the root run for a Mastra trace id, refreshing its LRU position on hit. */\n #getRootRun(traceId: string): RootRun | undefined {\n const root = this.#rootRunByTraceId.get(traceId);\n if (root === undefined) return undefined;\n // Re-insert to mark as most-recently-used (Map preserves insertion order).\n this.#rootRunByTraceId.delete(traceId);\n this.#rootRunByTraceId.set(traceId, root);\n return root;\n }\n\n /** Remember the first root run of a Mastra trace, evicting oldest entries when full. */\n #rememberRootRun(traceId: string, root: RootRun): void {\n if (this.#rootRunByTraceId.has(traceId)) return;\n this.#rootRunByTraceId.set(traceId, root);\n while (this.#rootRunByTraceId.size > this.#runIdCacheMaxEntries) {\n const oldest = this.#rootRunByTraceId.keys().next().value;\n if (oldest === undefined) break;\n this.#rootRunByTraceId.delete(oldest);\n }\n }\n\n /**\n * Send everything the LangSmith Client has queued. In manual flush mode only\n * `flush()` drains the queue (`awaitPendingTraceBatches()` just warns there);\n * otherwise `awaitPendingTraceBatches()` waits for queued and in-flight batches.\n */\n async #flushClient(): Promise<void> {\n if (!this.#client) return;\n // `manualFlushMode` is a private field in the SDK typings; fall back to our config\n const manualFlushMode =\n (this.#client as unknown as { manualFlushMode?: boolean }).manualFlushMode ?? this.config.manualFlushMode;\n if (manualFlushMode) {\n // The SDK's flush() empties its queue before uploading, so an overlapping call\n // would find nothing to wait for. Run each flush after the previous one settles.\n const client = this.#client;\n const flush = this.#manualFlushChain.then(() => client.flush());\n this.#manualFlushChain = flush.catch(() => {});\n await flush;\n } else {\n await this.#client.awaitPendingTraceBatches();\n }\n }\n\n /**\n * Flush pending trace batches to LangSmith.\n * The LangSmith Client internally batches API calls; this ensures\n * all queued runs are sent before the process exits or the flush\n * caller continues.\n */\n protected override async _flush(): Promise<void> {\n await this.#flushClient();\n }\n\n /** Spans aborted by shutdown() were only queued on the client; send them before releasing state. */\n protected override async _postShutdown(): Promise<void> {\n try {\n await this.#flushClient();\n } finally {\n this.#rootRunByTraceId.clear();\n }\n }\n\n async onScoreEvent(event: ScoreEvent): Promise<void> {\n if (!this.#client) return;\n\n const { score } = event;\n // A span-level score only needs the trace's root to know which project the run is in.\n const root = score.spanId\n ? score.traceId\n ? this.#getRootRun(score.traceId)\n : undefined\n : score.traceId\n ? await this.#resolveRootRun(score.traceId)\n : undefined;\n const langsmithRunId = score.spanId ? langsmithRunIdFor(score.spanId) : root?.runId;\n if (!langsmithRunId) {\n this.logger.warn(\n 'LangSmith exporter: dropping score; no LangSmith run found for the target ' +\n '(score has neither traceId nor spanId, the trace is not ingested yet, or it was exported by an older @mastra/langsmith)',\n {\n traceId: score.traceId,\n spanId: score.spanId,\n scorerId: score.scorerId,\n },\n );\n return;\n }\n\n const key = score.scorerName ?? score.scorerId;\n const sessionId = root?.sessionId ?? (await this.#getProjectId(root?.projectName ?? this.#defaultProjectName));\n\n try {\n await this.#client.createFeedback(langsmithRunId, key, {\n ...(sessionId ? { sessionId } : {}),\n score: score.score,\n ...(score.reason ? { comment: score.reason } : {}),\n feedbackId: score.scoreId,\n ...(score.scoreTraceId ? { sourceRunId: score.scoreTraceId } : {}),\n sourceInfo: {\n // User-supplied metadata is spread first so the authoritative reserved\n // fields below cannot be overwritten by `scorerId` / `scoreSource` keys\n // a caller may have set inside metadata.\n ...(score.metadata ?? {}),\n scorerId: score.scorerId,\n ...(score.scoreSource ? { scoreSource: score.scoreSource } : {}),\n },\n });\n } catch (err) {\n this.logger.error('LangSmith exporter: Failed to submit feedback', {\n error: err,\n traceId: score.traceId,\n spanId: score.spanId,\n scorerId: score.scorerId,\n });\n }\n }\n\n /**\n * A trace-level score goes to the trace's first root run. This process remembers the\n * roots it exported; otherwise LangSmith is asked for the earliest root run tagged with\n * the Mastra trace id.\n */\n async #resolveRootRun(traceId: string): Promise<RootRun | undefined> {\n const known = this.#getRootRun(traceId);\n if (known) return known;\n if (!HEX_TRACE_ID.test(traceId)) return undefined;\n\n // LangSmith makes posted runs queryable a few seconds later, so a score that\n // arrives right after another process exported the trace has to wait for it.\n for (let attempt = 0; ; attempt++) {\n const found = await this.#findRootRun(traceId);\n if (found) {\n this.#rememberRootRun(traceId, found);\n return found;\n }\n const delay = SCORE_LOOKUP_RETRY_DELAYS_MS[attempt];\n if (delay === undefined) return undefined;\n await new Promise(resolve => setTimeout(resolve, delay));\n }\n }\n\n async #findRootRun(traceId: string): Promise<RootRun | undefined> {\n // LangSmith only queries runs inside a project, so traces routed to another\n // project with `withLangsmithMetadata({ projectName })` are found by this process only.\n const projectId = await this.#getProjectId(this.#defaultProjectName);\n if (!projectId) return undefined;\n\n try {\n const runs = this.#client!.listRuns({\n projectId,\n isRoot: true,\n filter: `has(metadata, '${JSON.stringify({ mastra_trace_id: traceId })}')`,\n select: ['id', 'session_id'],\n order: 'asc',\n limit: 1,\n });\n for await (const run of runs) {\n return { runId: run.id, sessionId: run.session_id };\n }\n } catch (err) {\n this.logger.debug('LangSmith exporter: root run lookup failed', { traceId, error: err });\n }\n return undefined;\n }\n\n #getProjectId(projectName: string): Promise<string | undefined> {\n let projectId = this.#projectIdByName.get(projectName);\n if (!projectId) {\n projectId = Promise.resolve()\n .then(() => this.#client!.readProject({ projectName }))\n .then(project => project.id)\n .catch(err => {\n this.logger.debug('LangSmith exporter: could not resolve the project id', { projectName, error: err });\n this.#projectIdByName.delete(projectName);\n return undefined;\n });\n this.#projectIdByName.set(projectName, projectId);\n }\n return projectId;\n }\n\n protected override skipBuildRootTask = true;\n protected override async _buildRoot(_args: {\n span: AnyExportedSpan;\n traceData: LangSmithTraceData;\n }): Promise<LangSmithRoot | undefined> {\n throw new Error('Method not implemented.');\n }\n\n protected override async _buildSpan(args: {\n span: AnyExportedSpan;\n traceData: LangSmithTraceData;\n }): Promise<LangSmithSpan | undefined> {\n const { span, traceData } = args;\n\n const parent = span.isRootSpan ? undefined : traceData.getParent(args);\n\n if (!span.isRootSpan && !parent) {\n // parent doesn't exist and not creating rootSpan, return early data\n return;\n }\n\n const runId = langsmithRunIdFor(span.id);\n const payload = {\n name: span.name,\n id: runId,\n ...this.buildRunTreePayload(span, traceData, true),\n };\n\n const langSmithSpan = span.isRootSpan ? new RunTree(payload) : parent!.createChild(payload);\n if (span.isRootSpan) {\n this.#rememberRootRun(span.traceId, { runId, projectName: payload.project_name ?? this.#defaultProjectName });\n }\n\n await langSmithSpan.postRun();\n return langSmithSpan;\n }\n\n protected override async _buildEvent(args: {\n span: AnyExportedSpan;\n traceData: LangSmithTraceData;\n }): Promise<LangSmithEvent | undefined> {\n const langSmithSpan = await this._buildSpan(args);\n\n if (!langSmithSpan) {\n // parent doesn't exist and not creating rootSpan, return early data\n return;\n }\n\n // use start-time as end-time to make an event span.\n await langSmithSpan.end({ endTime: args.span.startTime.getTime() });\n await langSmithSpan.patchRun();\n return langSmithSpan;\n }\n\n protected override async _updateSpan(args: { span: AnyExportedSpan; traceData: LangSmithTraceData }): Promise<void> {\n await this.handleSpanUpdateOrEnd({ ...args, isEnd: false });\n }\n\n protected override async _finishSpan(args: { span: AnyExportedSpan; traceData: LangSmithTraceData }): Promise<void> {\n await this.handleSpanUpdateOrEnd({ ...args, isEnd: true });\n }\n\n protected override async _abortSpan(args: {\n span: LangSmithSpan;\n traceData: LangSmithTraceData;\n reason: SpanErrorInfo;\n }): Promise<void> {\n const { span, reason } = args;\n span.error = reason.message;\n span.metadata = {\n ...span.metadata,\n errorDetails: reason,\n };\n await span.end();\n await span.patchRun();\n }\n\n private async handleSpanUpdateOrEnd(args: {\n span: AnyExportedSpan;\n traceData: LangSmithTraceData;\n isEnd: boolean;\n }): Promise<void> {\n const { span, traceData, isEnd } = args;\n\n const langSmithSpan = traceData.getSpan({ spanId: span.id });\n if (!langSmithSpan) {\n //update occurred before span start, return early data\n return;\n }\n\n const updatePayload = this.buildRunTreePayload(span, traceData);\n\n langSmithSpan.metadata = {\n ...langSmithSpan.metadata,\n ...updatePayload.metadata,\n };\n if (updatePayload.inputs != null) {\n langSmithSpan.inputs = updatePayload.inputs;\n }\n if (updatePayload.outputs != null) {\n langSmithSpan.outputs = updatePayload.outputs;\n }\n if (updatePayload.error != null) {\n langSmithSpan.error = updatePayload.error;\n }\n\n // Add new_token event for TTFT tracking on MODEL_GENERATION spans\n if (span.type === SpanType.MODEL_GENERATION) {\n const modelAttr = (span.attributes ?? {}) as ModelGenerationAttributes;\n if (modelAttr.completionStartTime !== undefined) {\n langSmithSpan.addEvent({\n name: 'new_token',\n time: modelAttr.completionStartTime.toISOString(),\n });\n }\n }\n\n // LangSmith accepts one update per run, so updates only mutate the local\n // RunTree and the single patch is sent on end with the final state.\n if (isEnd) {\n // End the span with the correct endTime\n if (span.endTime) {\n await langSmithSpan.end({ endTime: span.endTime.getTime() });\n } else {\n await langSmithSpan.end();\n }\n await langSmithSpan.patchRun();\n }\n }\n\n /**\n * Find LangSmith vendor metadata by walking up the span hierarchy and merging.\n * Metadata is merged from ancestors with child values taking precedence over parent values.\n *\n * TODO(2.0): Extract shared `findVendorMetadata()` to base TrackingExporter class\n * and reuse here and in LangfuseExporter.findLangfusePrompt()\n */\n private findLangsmithMetadata(\n traceData: LangSmithTraceData,\n span: AnyExportedSpan,\n ): LangSmithMetadataInput | undefined {\n // Collect metadata from all ancestors (current span first, then parents)\n const metadataChain: LangSmithMetadataInput[] = [];\n let currentSpanId: string | undefined = span.id;\n\n while (currentSpanId) {\n const providerMetadata = traceData.getMetadata({ spanId: currentSpanId });\n if (providerMetadata) {\n metadataChain.push(providerMetadata);\n }\n currentSpanId = traceData.getParentId({ spanId: currentSpanId });\n }\n\n if (metadataChain.length === 0) {\n return undefined;\n }\n\n // Merge from ancestors to current span (parent values first, child values override)\n const merged: LangSmithMetadataInput = {};\n for (let i = metadataChain.length - 1; i >= 0; i--) {\n const meta = metadataChain[i]!;\n if (meta.projectName !== undefined) merged.projectName = meta.projectName;\n if (meta.sessionId !== undefined) merged.sessionId = meta.sessionId;\n if (meta.sessionName !== undefined) merged.sessionName = meta.sessionName;\n }\n\n this.logger.debug(`${this.name}: merged vendor metadata from hierarchy`, {\n traceId: span.traceId,\n spanId: span.id,\n metadataKeys: Object.keys(merged),\n ancestorCount: metadataChain.length,\n });\n\n return merged;\n }\n\n /**\n * LangSmith groups runs into a Thread only by `session_id` / `thread_id` metadata and\n * needs it on every run of the trace. A child run follows the session its parent was\n * built with: sub-agents and Observational Memory run on derived threads, and their\n * own `threadId` would split the conversation into one-off Threads. The span's own\n * `sessionId` / `threadId` applies at the root, or when no ancestor carries a session.\n */\n private findSessionId(span: AnyExportedSpan, traceData: LangSmithTraceData): string | undefined {\n const inherited = span.isRootSpan ? undefined : traceData.getParent({ span })?.metadata?.session_id;\n if (typeof inherited === 'string' && inherited) return inherited;\n const own = span.metadata?.sessionId ?? span.metadata?.threadId;\n return typeof own === 'string' && own ? own : undefined;\n }\n\n private buildRunTreePayload(\n span: AnyExportedSpan,\n traceData: LangSmithTraceData,\n isNew = false,\n ): Partial<RunTreeConfig> {\n // Extract vendor metadata from span hierarchy\n const vendorMetadata = this.findLangsmithMetadata(traceData, span);\n\n // Build metadata, omitting the langsmith vendor key\n const spanMetadata = span.metadata ? omitKeys(span.metadata, ['langsmith']) : {};\n\n const payload: Partial<RunTreeConfig> & { metadata: KVMap } = {\n client: this.#client,\n metadata: {\n mastra_span_type: span.type,\n mastra_span_id: span.id,\n mastra_trace_id: span.traceId,\n ...spanMetadata,\n },\n };\n\n if (isNew) {\n payload.run_type = mapSpanType(span.type);\n payload.start_time = span.startTime.getTime();\n }\n\n // Add project name - vendor metadata takes precedence over config\n const projectName = vendorMetadata?.projectName ?? this.config.projectName;\n if (projectName) {\n payload.project_name = projectName;\n }\n\n // Explicit vendor sessionId wins; otherwise the Mastra conversation thread, so turns\n // on one memory thread land in one LangSmith Thread without extra setup.\n const sessionId = vendorMetadata?.sessionId ?? this.findSessionId(span, traceData);\n if (sessionId) {\n payload.metadata.session_id = sessionId;\n }\n if (vendorMetadata?.sessionName) {\n payload.metadata.session_name = vendorMetadata.sessionName;\n }\n\n // Add tags for root spans\n if (span.isRootSpan && span.tags?.length) {\n payload.tags = span.tags;\n }\n\n // Core span data\n if (span.input !== undefined) {\n payload.inputs = isKVMap(span.input) ? span.input : { input: span.input };\n }\n\n if (span.output !== undefined) {\n payload.outputs = isKVMap(span.output) ? span.output : { output: span.output };\n }\n\n const attributes = (span.attributes ?? {}) as Record<string, any>;\n\n if (span.type === SpanType.MODEL_GENERATION) {\n const modelAttr = attributes as ModelGenerationAttributes;\n\n // See: https://docs.langchain.com/langsmith/log-llm-trace\n if (modelAttr.model !== undefined) {\n // Note - this should map to a model name recognized by LangSmith\n // eg “gpt-4o-mini”, “claude-3-opus-20240307”, etc.\n payload.metadata.ls_model_name = modelAttr.model;\n }\n\n // Provider goes to metadata (if provided by attributes)\n if (modelAttr.provider !== undefined) {\n // Note - this should map to a provider name recognized by\n // LangSmith eg “openai”, “anthropic”, etc.\n payload.metadata.ls_provider = modelAttr.provider;\n }\n\n // Usage/token info goes to metrics\n payload.metadata.usage_metadata = formatUsageMetrics(modelAttr.usage);\n\n // Model parameters go to metadata\n if (modelAttr.parameters !== undefined) {\n payload.metadata.modelParameters = modelAttr.parameters;\n }\n\n // Other LLM attributes go to metadata\n const otherAttributes = omitKeys(attributes, ['model', 'provider', 'usage', 'parameters', 'completionStartTime']);\n payload.metadata = {\n ...payload.metadata,\n ...otherAttributes,\n };\n } else {\n // For non-LLM spans, put all attributes in metadata\n payload.metadata = {\n ...payload.metadata,\n ...attributes,\n };\n }\n\n // Handle errors\n if (span.errorInfo) {\n payload.error = span.errorInfo.message;\n payload.metadata.errorDetails = span.errorInfo;\n }\n\n return payload;\n }\n}\n","/**\n * LangSmith Tracing Options Helpers\n *\n * These helpers integrate with the `buildTracingOptions` pattern from\n * `@mastra/observability` to add LangSmith-specific tracing features.\n *\n * @example\n * ```typescript\n * import { buildTracingOptions } from '@mastra/observability';\n * import { withLangsmithMetadata } from '@mastra/langsmith';\n *\n * const agent = new Agent({\n * defaultGenerateOptions: {\n * tracingOptions: buildTracingOptions(\n * withLangsmithMetadata({ projectName: 'my-project' })\n * ),\n * },\n * });\n * ```\n */\n\nimport type { TracingOptionsUpdater } from '@mastra/observability';\n\n/**\n * LangSmith vendor metadata that can be passed via span metadata.\n * These fields are extracted by the LangSmith exporter and used\n * to override default configuration on a per-span basis.\n */\nexport interface LangSmithMetadataInput {\n /**\n * Override the project name for this span and its children.\n * This allows dynamically routing traces to different LangSmith projects.\n */\n projectName?: string;\n /**\n * Session ID for grouping related traces in LangSmith.\n */\n sessionId?: string;\n /**\n * Session name for display in LangSmith.\n */\n sessionName?: string;\n}\n\n/**\n * Adds LangSmith metadata to the tracing options.\n *\n * The metadata is added under `metadata.langsmith` and allows you to:\n * - Route traces to different LangSmith projects via `projectName`\n * - Group traces by session via `sessionId` and `sessionName`\n *\n * @param metadata - The LangSmith metadata to add\n * @returns A TracingOptionsUpdater function for use with `buildTracingOptions`\n *\n * @example\n * ```typescript\n * import { buildTracingOptions } from '@mastra/observability';\n * import { withLangsmithMetadata } from '@mastra/langsmith';\n *\n * // Route traces to a specific project\n * const tracingOptions = buildTracingOptions(\n * withLangsmithMetadata({ projectName: 'customer-support' }),\n * );\n *\n * // Or set multiple fields\n * const tracingOptions = buildTracingOptions(\n * withLangsmithMetadata({\n * projectName: 'my-project',\n * sessionId: 'session-123',\n * }),\n * );\n *\n * // Use in agent config\n * const agent = new Agent({\n * name: 'support-agent',\n * model: openai('gpt-4o'),\n * defaultGenerateOptions: {\n * tracingOptions: buildTracingOptions(\n * withLangsmithMetadata({ projectName: 'support-traces' })\n * ),\n * },\n * });\n * ```\n */\nexport function withLangsmithMetadata(metadata: LangSmithMetadataInput): TracingOptionsUpdater {\n return opts => ({\n ...opts,\n metadata: {\n ...opts.metadata,\n langsmith: {\n ...(opts.metadata?.langsmith as Record<string, unknown>),\n ...metadata,\n },\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["createHash","SpanType","TrackingExporter","Client","RunTree","omitKeys"],"sources":["../src/metrics.ts","../src/tracing.ts","../src/helpers.ts"],"sourcesContent":["import type { UsageStats } from '@mastra/core/observability';\n\n/**\n * LangSmithUsageMetrics\n *\n * Canonical metric keys expected by LangSmith for LLM usage accounting.\n * See: https://docs.langchain.com/langsmith/log-llm-trace#provide-token-and-cost-information\n */\nexport interface LangSmithUsageMetrics {\n input_tokens?: number;\n output_tokens?: number;\n total_tokens?: number;\n input_token_details?: {\n [key: string]: number;\n };\n output_token_details?: {\n [key: string]: number;\n };\n}\n\n/**\n * Formats UsageStats to LangSmith's expected metric format.\n */\nexport function formatUsageMetrics(usage?: UsageStats): LangSmithUsageMetrics {\n const metrics: LangSmithUsageMetrics = {};\n\n if (usage?.inputTokens !== undefined) {\n metrics.input_tokens = usage.inputTokens;\n }\n\n if (usage?.outputTokens !== undefined) {\n metrics.output_tokens = usage.outputTokens;\n }\n\n // Compute total if we have both\n if (metrics.input_tokens !== undefined && metrics.output_tokens !== undefined) {\n metrics.total_tokens = metrics.input_tokens + metrics.output_tokens;\n }\n\n if (usage?.outputDetails?.reasoning !== undefined) {\n metrics.output_token_details = {\n ...(metrics.output_token_details ?? {}),\n reasoning_tokens: usage.outputDetails.reasoning,\n };\n }\n\n if (usage?.inputDetails?.cacheRead !== undefined) {\n metrics.input_token_details = {\n ...(metrics.input_token_details ?? {}),\n cache_read: usage.inputDetails.cacheRead,\n };\n }\n\n if (usage?.inputDetails?.cacheWrite !== undefined) {\n metrics.input_token_details = {\n ...(metrics.input_token_details ?? {}),\n cache_write: usage.inputDetails.cacheWrite,\n };\n }\n\n if (usage?.inputDetails?.cacheWrite5m !== undefined) {\n metrics.input_token_details = {\n ...(metrics.input_token_details ?? {}),\n cache_creation_5m: usage.inputDetails.cacheWrite5m,\n };\n }\n\n if (usage?.inputDetails?.cacheWrite1h !== undefined) {\n metrics.input_token_details = {\n ...(metrics.input_token_details ?? {}),\n cache_creation_1h: usage.inputDetails.cacheWrite1h,\n };\n }\n\n if (usage?.inputDetails?.audio !== undefined) {\n metrics.input_token_details = {\n ...(metrics.input_token_details ?? {}),\n audio: usage.inputDetails.audio,\n };\n }\n\n if (usage?.outputDetails?.audio !== undefined) {\n metrics.output_token_details = {\n ...(metrics.output_token_details ?? {}),\n audio: usage.outputDetails.audio,\n };\n }\n\n return metrics;\n}\n","/**\n * LangSmith Exporter for Mastra Tracing\n *\n * This exporter sends observability data to LangSmith\n * Root spans become top-level LangSmith RunTrees (no trace wrapper).\n * Events are handled as zero-duration RunTrees with matching start/end times.\n */\n\nimport { createHash } from 'node:crypto';\nimport type { AnyExportedSpan, ModelGenerationAttributes, ScoreEvent, SpanErrorInfo } from '@mastra/core/observability';\nimport { SpanType } from '@mastra/core/observability';\nimport { omitKeys } from '@mastra/core/utils';\nimport { TrackingExporter } from '@mastra/observability';\nimport type { TraceData, TrackingExporterConfig } from '@mastra/observability';\nimport type { ClientConfig, RunTreeConfig } from 'langsmith';\nimport { Client, RunTree } from 'langsmith';\nimport type { KVMap } from 'langsmith/schemas';\nimport type { LangSmithMetadataInput } from './helpers';\nimport { formatUsageMetrics } from './metrics';\n\nexport interface LangSmithExporterConfig extends ClientConfig, TrackingExporterConfig {\n /** LangSmith client instance */\n client?: Client;\n /**\n * The name of the LangSmith project to send traces to.\n * Overrides the LANGCHAIN_PROJECT environment variable.\n * If neither is set, traces are sent to the \"default\" project.\n */\n projectName?: string;\n /**\n * Maximum number of `traceId → root run id` mappings to keep in memory. The\n * cache lets a trace-level score skip the LangSmith lookup for traces this\n * process exported; scores for traces outside the cache are still delivered.\n * Older entries are evicted in LRU order when the cap is exceeded.\n * Defaults to 10000.\n */\n runIdCacheMaxEntries?: number;\n}\n\nconst DEFAULT_RUN_ID_CACHE_MAX_ENTRIES = 10_000;\n/** Waits between LangSmith lookups for a trace another process exported (ingestion takes ~5-8 s). */\nconst SCORE_LOOKUP_RETRY_DELAYS_MS = [2_000, 3_000, 5_000];\n/** Mastra trace ids are hex (`isValidTraceId` in @mastra/observability); anything else cannot match a run. */\nconst HEX_TRACE_ID = /^[0-9a-f]{1,32}$/i;\n\n/** The first root run of a Mastra trace and the LangSmith project it lives in. */\ninterface RootRun {\n runId: string;\n /** Project the root was posted to by this process; resolved to an id only when a score needs it. */\n projectName?: string;\n /** Project id read back from LangSmith when the root was found there. */\n sessionId?: string;\n}\n\n/** UUID v5 namespace for Mastra → LangSmith run ids. Changing it orphans existing runs from future scores. */\nconst RUN_ID_NAMESPACE = Buffer.from('a6f3c3f06b0e4a1e9f4c7e1b2d3c4f5a', 'hex');\n\n/**\n * LangSmith run id for a Mastra span id (UUID v5). Every run, root or not, is named\n * after its own span, so a span-level score can be routed from any process without\n * remembering the ids LangSmith would otherwise allocate.\n */\nexport function langsmithRunIdFor(spanId: string): string {\n const hash = createHash('sha1').update(RUN_ID_NAMESPACE).update(spanId).digest();\n hash[6] = (hash[6]! & 0x0f) | 0x50;\n hash[8] = (hash[8]! & 0x3f) | 0x80;\n const hex = hash.subarray(0, 16).toString('hex');\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\ntype LangSmithRoot = undefined;\ntype LangSmithSpan = RunTree;\ntype LangSmithEvent = RunTree;\ntype LangSmithMetadata = LangSmithMetadataInput;\ntype LangSmithTraceData = TraceData<LangSmithRoot, LangSmithSpan, LangSmithEvent, LangSmithMetadata>;\n\n// Default span type for all spans\nconst DEFAULT_SPAN_TYPE = 'chain';\n\n// Exceptions to the default mapping\nconst SPAN_TYPE_EXCEPTIONS: Partial<Record<SpanType, 'llm' | 'tool' | 'chain'>> = {\n [SpanType.MODEL_GENERATION]: 'llm',\n [SpanType.TOOL_CALL]: 'tool',\n [SpanType.MCP_TOOL_CALL]: 'tool',\n [SpanType.PROVIDER_TOOL_CALL]: 'tool',\n [SpanType.WORKFLOW_CONDITIONAL_EVAL]: 'chain',\n [SpanType.WORKFLOW_WAIT_EVENT]: 'chain',\n};\n\n// Mapping function - returns valid LangSmith span types\nfunction mapSpanType(spanType: SpanType): 'llm' | 'tool' | 'chain' {\n return SPAN_TYPE_EXCEPTIONS[spanType] ?? DEFAULT_SPAN_TYPE;\n}\n\nfunction isKVMap(value: unknown): value is KVMap {\n return value != null && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date);\n}\n\nexport class LangSmithExporter extends TrackingExporter<\n LangSmithRoot,\n LangSmithSpan,\n LangSmithEvent,\n LangSmithMetadata,\n LangSmithExporterConfig\n> {\n override name = 'langsmith';\n #client: Client | undefined;\n\n /**\n * Maps a Mastra trace id to the first root run this process exported for it, and the\n * project it went to. A fast path for scores: a trace-level score on a miss falls back\n * to asking LangSmith for the root run tagged with that `mastra_trace_id`.\n *\n * Bounded LRU keyed by Mastra trace id — entries are evicted oldest-first when\n * size exceeds `#runIdCacheMaxEntries` so the cache cannot grow without bound.\n */\n #rootRunByTraceId = new Map<string, RootRun>();\n /** Manual-mode client flushes, chained so an overlapping flush waits for the earlier upload */\n #manualFlushChain: Promise<void> = Promise.resolve();\n #runIdCacheMaxEntries: number;\n /** Project runs go to unless `withLangsmithMetadata({ projectName })` says otherwise. */\n #defaultProjectName: string;\n /** LangSmith project (session) ids by name: `createFeedback` requires one and run queries are scoped to one. */\n #projectIdByName = new Map<string, Promise<string | undefined>>();\n\n constructor(config: LangSmithExporterConfig = {}) {\n // Resolve env vars BEFORE calling super (config is readonly in base class)\n const apiKey = config.apiKey ?? process.env.LANGSMITH_API_KEY;\n\n super({\n ...config,\n apiKey,\n });\n\n this.#runIdCacheMaxEntries = Math.max(1, config.runIdCacheMaxEntries ?? DEFAULT_RUN_ID_CACHE_MAX_ENTRIES);\n this.#defaultProjectName =\n config.projectName ?? process.env.LANGSMITH_PROJECT ?? process.env.LANGCHAIN_PROJECT ?? 'default';\n\n if (!apiKey) {\n this.setDisabled(`Missing required credentials (apiKey: ${!!apiKey})`);\n return;\n }\n\n this.#client = config.client ?? new Client(this.config);\n }\n\n /** Look up the root run for a Mastra trace id, refreshing its LRU position on hit. */\n #getRootRun(traceId: string): RootRun | undefined {\n const root = this.#rootRunByTraceId.get(traceId);\n if (root === undefined) return undefined;\n // Re-insert to mark as most-recently-used (Map preserves insertion order).\n this.#rootRunByTraceId.delete(traceId);\n this.#rootRunByTraceId.set(traceId, root);\n return root;\n }\n\n /** Remember the first root run of a Mastra trace, evicting oldest entries when full. */\n #rememberRootRun(traceId: string, root: RootRun): void {\n if (this.#rootRunByTraceId.has(traceId)) return;\n this.#rootRunByTraceId.set(traceId, root);\n while (this.#rootRunByTraceId.size > this.#runIdCacheMaxEntries) {\n const oldest = this.#rootRunByTraceId.keys().next().value;\n if (oldest === undefined) break;\n this.#rootRunByTraceId.delete(oldest);\n }\n }\n\n /**\n * Send everything the LangSmith Client has queued. In manual flush mode only\n * `flush()` drains the queue (`awaitPendingTraceBatches()` just warns there);\n * otherwise `awaitPendingTraceBatches()` waits for queued and in-flight batches.\n */\n async #flushClient(): Promise<void> {\n if (!this.#client) return;\n // `manualFlushMode` is a private field in the SDK typings; fall back to our config\n const manualFlushMode =\n (this.#client as unknown as { manualFlushMode?: boolean }).manualFlushMode ?? this.config.manualFlushMode;\n if (manualFlushMode) {\n // The SDK's flush() empties its queue before uploading, so an overlapping call\n // would find nothing to wait for. Run each flush after the previous one settles.\n const client = this.#client;\n const flush = this.#manualFlushChain.then(() => client.flush());\n this.#manualFlushChain = flush.catch(() => {});\n await flush;\n } else {\n await this.#client.awaitPendingTraceBatches();\n }\n }\n\n /**\n * Flush pending trace batches to LangSmith.\n * The LangSmith Client internally batches API calls; this ensures\n * all queued runs are sent before the process exits or the flush\n * caller continues.\n */\n protected override async _flush(): Promise<void> {\n await this.#flushClient();\n }\n\n /** Spans aborted by shutdown() were only queued on the client; send them before releasing state. */\n protected override async _postShutdown(): Promise<void> {\n try {\n await this.#flushClient();\n } finally {\n this.#rootRunByTraceId.clear();\n }\n }\n\n async onScoreEvent(event: ScoreEvent): Promise<void> {\n if (!this.#client) return;\n\n const { score } = event;\n // A span-level score only needs the trace's root to know which project the run is in.\n const root = score.spanId\n ? score.traceId\n ? this.#getRootRun(score.traceId)\n : undefined\n : score.traceId\n ? await this.#resolveRootRun(score.traceId)\n : undefined;\n const langsmithRunId = score.spanId ? langsmithRunIdFor(score.spanId) : root?.runId;\n if (!langsmithRunId) {\n this.logger.warn(\n 'LangSmith exporter: dropping score; no LangSmith run found for the target ' +\n '(score has neither traceId nor spanId, the trace is not ingested yet, or it was exported by an older @mastra/langsmith)',\n {\n traceId: score.traceId,\n spanId: score.spanId,\n scorerId: score.scorerId,\n },\n );\n return;\n }\n\n const key = score.scorerName ?? score.scorerId;\n const sessionId = root?.sessionId ?? (await this.#getProjectId(root?.projectName ?? this.#defaultProjectName));\n\n try {\n await this.#client.createFeedback(langsmithRunId, key, {\n ...(sessionId ? { sessionId } : {}),\n score: score.score,\n ...(score.reason ? { comment: score.reason } : {}),\n feedbackId: score.scoreId,\n ...(score.scoreTraceId ? { sourceRunId: score.scoreTraceId } : {}),\n sourceInfo: {\n // User-supplied metadata is spread first so the authoritative reserved\n // fields below cannot be overwritten by `scorerId` / `scoreSource` keys\n // a caller may have set inside metadata.\n ...(score.metadata ?? {}),\n scorerId: score.scorerId,\n ...(score.scoreSource ? { scoreSource: score.scoreSource } : {}),\n },\n });\n } catch (err) {\n this.logger.error('LangSmith exporter: Failed to submit feedback', {\n error: err,\n traceId: score.traceId,\n spanId: score.spanId,\n scorerId: score.scorerId,\n });\n }\n }\n\n /**\n * A trace-level score goes to the trace's first root run. This process remembers the\n * roots it exported; otherwise LangSmith is asked for the earliest root run tagged with\n * the Mastra trace id.\n */\n async #resolveRootRun(traceId: string): Promise<RootRun | undefined> {\n const known = this.#getRootRun(traceId);\n if (known) return known;\n if (!HEX_TRACE_ID.test(traceId)) return undefined;\n\n // LangSmith makes posted runs queryable a few seconds later, so a score that\n // arrives right after another process exported the trace has to wait for it.\n for (let attempt = 0; ; attempt++) {\n const found = await this.#findRootRun(traceId);\n if (found) {\n this.#rememberRootRun(traceId, found);\n return found;\n }\n const delay = SCORE_LOOKUP_RETRY_DELAYS_MS[attempt];\n if (delay === undefined) return undefined;\n await new Promise(resolve => setTimeout(resolve, delay));\n }\n }\n\n async #findRootRun(traceId: string): Promise<RootRun | undefined> {\n // LangSmith only queries runs inside a project, so traces routed to another\n // project with `withLangsmithMetadata({ projectName })` are found by this process only.\n const projectId = await this.#getProjectId(this.#defaultProjectName);\n if (!projectId) return undefined;\n\n try {\n const runs = this.#client!.listRuns({\n projectId,\n isRoot: true,\n filter: `has(metadata, '${JSON.stringify({ mastra_trace_id: traceId })}')`,\n select: ['id', 'session_id'],\n order: 'asc',\n limit: 1,\n });\n for await (const run of runs) {\n return { runId: run.id, sessionId: run.session_id };\n }\n } catch (err) {\n this.logger.debug('LangSmith exporter: root run lookup failed', { traceId, error: err });\n }\n return undefined;\n }\n\n #getProjectId(projectName: string): Promise<string | undefined> {\n let projectId = this.#projectIdByName.get(projectName);\n if (!projectId) {\n projectId = Promise.resolve()\n .then(() => this.#client!.readProject({ projectName }))\n .then(project => project.id)\n .catch(err => {\n this.logger.debug('LangSmith exporter: could not resolve the project id', { projectName, error: err });\n this.#projectIdByName.delete(projectName);\n return undefined;\n });\n this.#projectIdByName.set(projectName, projectId);\n }\n return projectId;\n }\n\n protected override skipBuildRootTask = true;\n protected override async _buildRoot(_args: {\n span: AnyExportedSpan;\n traceData: LangSmithTraceData;\n }): Promise<LangSmithRoot | undefined> {\n throw new Error('Method not implemented.');\n }\n\n protected override async _buildSpan(args: {\n span: AnyExportedSpan;\n traceData: LangSmithTraceData;\n }): Promise<LangSmithSpan | undefined> {\n const { span, traceData } = args;\n\n const parent = span.isRootSpan ? undefined : traceData.getParent(args);\n\n if (!span.isRootSpan && !parent) {\n // parent doesn't exist and not creating rootSpan, return early data\n return;\n }\n\n const runId = langsmithRunIdFor(span.id);\n const payload = {\n name: span.name,\n id: runId,\n ...this.buildRunTreePayload(span, traceData, true),\n };\n\n const langSmithSpan = span.isRootSpan ? new RunTree(payload) : parent!.createChild(payload);\n if (span.isRootSpan) {\n this.#rememberRootRun(span.traceId, { runId, projectName: payload.project_name ?? this.#defaultProjectName });\n }\n\n await langSmithSpan.postRun();\n return langSmithSpan;\n }\n\n protected override async _buildEvent(args: {\n span: AnyExportedSpan;\n traceData: LangSmithTraceData;\n }): Promise<LangSmithEvent | undefined> {\n const langSmithSpan = await this._buildSpan(args);\n\n if (!langSmithSpan) {\n // parent doesn't exist and not creating rootSpan, return early data\n return;\n }\n\n // use start-time as end-time to make an event span.\n await langSmithSpan.end({ endTime: args.span.startTime.getTime() });\n await langSmithSpan.patchRun();\n return langSmithSpan;\n }\n\n protected override async _updateSpan(args: { span: AnyExportedSpan; traceData: LangSmithTraceData }): Promise<void> {\n await this.handleSpanUpdateOrEnd({ ...args, isEnd: false });\n }\n\n protected override async _finishSpan(args: { span: AnyExportedSpan; traceData: LangSmithTraceData }): Promise<void> {\n await this.handleSpanUpdateOrEnd({ ...args, isEnd: true });\n }\n\n protected override async _abortSpan(args: {\n span: LangSmithSpan;\n traceData: LangSmithTraceData;\n reason: SpanErrorInfo;\n }): Promise<void> {\n const { span, reason } = args;\n span.error = reason.message;\n span.metadata = {\n ...span.metadata,\n errorDetails: reason,\n };\n await span.end();\n await span.patchRun();\n }\n\n private async handleSpanUpdateOrEnd(args: {\n span: AnyExportedSpan;\n traceData: LangSmithTraceData;\n isEnd: boolean;\n }): Promise<void> {\n const { span, traceData, isEnd } = args;\n\n const langSmithSpan = traceData.getSpan({ spanId: span.id });\n if (!langSmithSpan) {\n //update occurred before span start, return early data\n return;\n }\n\n const updatePayload = this.buildRunTreePayload(span, traceData);\n\n langSmithSpan.metadata = {\n ...langSmithSpan.metadata,\n ...updatePayload.metadata,\n };\n if (updatePayload.inputs != null) {\n langSmithSpan.inputs = updatePayload.inputs;\n }\n if (updatePayload.outputs != null) {\n langSmithSpan.outputs = updatePayload.outputs;\n }\n if (updatePayload.error != null) {\n langSmithSpan.error = updatePayload.error;\n }\n\n // Add new_token event for TTFT tracking on MODEL_GENERATION spans\n if (span.type === SpanType.MODEL_GENERATION) {\n const modelAttr = (span.attributes ?? {}) as ModelGenerationAttributes;\n if (modelAttr.completionStartTime !== undefined) {\n langSmithSpan.addEvent({\n name: 'new_token',\n time: modelAttr.completionStartTime.toISOString(),\n });\n }\n }\n\n // LangSmith accepts one update per run, so updates only mutate the local\n // RunTree and the single patch is sent on end with the final state.\n if (isEnd) {\n // End the span with the correct endTime\n if (span.endTime) {\n await langSmithSpan.end({ endTime: span.endTime.getTime() });\n } else {\n await langSmithSpan.end();\n }\n await langSmithSpan.patchRun();\n }\n }\n\n /**\n * Find LangSmith vendor metadata by walking up the span hierarchy and merging.\n * Metadata is merged from ancestors with child values taking precedence over parent values.\n *\n * TODO(2.0): Extract shared `findVendorMetadata()` to base TrackingExporter class\n * and reuse here and in LangfuseExporter.findLangfusePrompt()\n */\n private findLangsmithMetadata(\n traceData: LangSmithTraceData,\n span: AnyExportedSpan,\n ): LangSmithMetadataInput | undefined {\n // Collect metadata from all ancestors (current span first, then parents)\n const metadataChain: LangSmithMetadataInput[] = [];\n let currentSpanId: string | undefined = span.id;\n\n while (currentSpanId) {\n const providerMetadata = traceData.getMetadata({ spanId: currentSpanId });\n if (providerMetadata) {\n metadataChain.push(providerMetadata);\n }\n currentSpanId = traceData.getParentId({ spanId: currentSpanId });\n }\n\n if (metadataChain.length === 0) {\n return undefined;\n }\n\n // Merge from ancestors to current span (parent values first, child values override)\n const merged: LangSmithMetadataInput = {};\n for (let i = metadataChain.length - 1; i >= 0; i--) {\n const meta = metadataChain[i]!;\n if (meta.projectName !== undefined) merged.projectName = meta.projectName;\n if (meta.sessionId !== undefined) merged.sessionId = meta.sessionId;\n if (meta.sessionName !== undefined) merged.sessionName = meta.sessionName;\n }\n\n this.logger.debug(`${this.name}: merged vendor metadata from hierarchy`, {\n traceId: span.traceId,\n spanId: span.id,\n metadataKeys: Object.keys(merged),\n ancestorCount: metadataChain.length,\n });\n\n return merged;\n }\n\n /**\n * LangSmith groups runs into a Thread only by `session_id` / `thread_id` metadata and\n * needs it on every run of the trace. A child run follows the session its parent was\n * built with: sub-agents and Observational Memory run on derived threads, and their\n * own `threadId` would split the conversation into one-off Threads. The span's own\n * `sessionId` / `threadId` applies at the root, or when no ancestor carries a session.\n */\n private findSessionId(span: AnyExportedSpan, traceData: LangSmithTraceData): string | undefined {\n const inherited = span.isRootSpan ? undefined : traceData.getParent({ span })?.metadata?.session_id;\n if (typeof inherited === 'string' && inherited) return inherited;\n const own = span.metadata?.sessionId ?? span.metadata?.threadId;\n return typeof own === 'string' && own ? own : undefined;\n }\n\n private buildRunTreePayload(\n span: AnyExportedSpan,\n traceData: LangSmithTraceData,\n isNew = false,\n ): Partial<RunTreeConfig> {\n // Extract vendor metadata from span hierarchy\n const vendorMetadata = this.findLangsmithMetadata(traceData, span);\n\n // Build metadata, omitting the langsmith vendor key\n const spanMetadata = span.metadata ? omitKeys(span.metadata, ['langsmith']) : {};\n\n const payload: Partial<RunTreeConfig> & { metadata: KVMap } = {\n client: this.#client,\n metadata: {\n mastra_span_type: span.type,\n mastra_span_id: span.id,\n mastra_trace_id: span.traceId,\n ...spanMetadata,\n },\n };\n\n if (isNew) {\n payload.run_type = mapSpanType(span.type);\n payload.start_time = span.startTime.getTime();\n }\n\n // Add project name - vendor metadata takes precedence over config\n const projectName = vendorMetadata?.projectName ?? this.config.projectName;\n if (projectName) {\n payload.project_name = projectName;\n }\n\n // Explicit vendor sessionId wins; otherwise the Mastra conversation thread, so turns\n // on one memory thread land in one LangSmith Thread without extra setup.\n const sessionId = vendorMetadata?.sessionId ?? this.findSessionId(span, traceData);\n if (sessionId) {\n payload.metadata.session_id = sessionId;\n }\n if (vendorMetadata?.sessionName) {\n payload.metadata.session_name = vendorMetadata.sessionName;\n }\n\n // Add tags for root spans\n if (span.isRootSpan && span.tags?.length) {\n payload.tags = span.tags;\n }\n\n // Core span data\n if (span.input !== undefined) {\n payload.inputs = isKVMap(span.input) ? span.input : { input: span.input };\n }\n\n if (span.output !== undefined) {\n payload.outputs = isKVMap(span.output) ? span.output : { output: span.output };\n }\n\n const attributes = (span.attributes ?? {}) as Record<string, any>;\n\n if (span.type === SpanType.MODEL_GENERATION) {\n const modelAttr = attributes as ModelGenerationAttributes;\n\n // See: https://docs.langchain.com/langsmith/log-llm-trace\n if (modelAttr.model !== undefined) {\n // Note - this should map to a model name recognized by LangSmith\n // eg “gpt-4o-mini”, “claude-3-opus-20240307”, etc.\n payload.metadata.ls_model_name = modelAttr.model;\n }\n\n // Provider goes to metadata (if provided by attributes)\n if (modelAttr.provider !== undefined) {\n // Note - this should map to a provider name recognized by\n // LangSmith eg “openai”, “anthropic”, etc.\n payload.metadata.ls_provider = modelAttr.provider;\n }\n\n // Usage/token info goes to metrics\n payload.metadata.usage_metadata = formatUsageMetrics(modelAttr.usage);\n\n // Model parameters go to metadata\n if (modelAttr.parameters !== undefined) {\n payload.metadata.modelParameters = modelAttr.parameters;\n }\n\n // Other LLM attributes go to metadata\n const otherAttributes = omitKeys(attributes, [\n 'model',\n 'provider',\n 'usage',\n 'parameters',\n 'completionStartTime',\n 'timeToFirstTokenMs',\n ]);\n payload.metadata = {\n ...payload.metadata,\n ...otherAttributes,\n };\n } else {\n // For non-LLM spans, put all attributes in metadata\n payload.metadata = {\n ...payload.metadata,\n ...omitKeys(attributes, ['timeToFirstTokenMs']),\n };\n }\n\n // Handle errors\n if (span.errorInfo) {\n payload.error = span.errorInfo.message;\n payload.metadata.errorDetails = span.errorInfo;\n }\n\n return payload;\n }\n}\n","/**\n * LangSmith Tracing Options Helpers\n *\n * These helpers integrate with the `buildTracingOptions` pattern from\n * `@mastra/observability` to add LangSmith-specific tracing features.\n *\n * @example\n * ```typescript\n * import { buildTracingOptions } from '@mastra/observability';\n * import { withLangsmithMetadata } from '@mastra/langsmith';\n *\n * const agent = new Agent({\n * defaultGenerateOptions: {\n * tracingOptions: buildTracingOptions(\n * withLangsmithMetadata({ projectName: 'my-project' })\n * ),\n * },\n * });\n * ```\n */\n\nimport type { TracingOptionsUpdater } from '@mastra/observability';\n\n/**\n * LangSmith vendor metadata that can be passed via span metadata.\n * These fields are extracted by the LangSmith exporter and used\n * to override default configuration on a per-span basis.\n */\nexport interface LangSmithMetadataInput {\n /**\n * Override the project name for this span and its children.\n * This allows dynamically routing traces to different LangSmith projects.\n */\n projectName?: string;\n /**\n * Session ID for grouping related traces in LangSmith.\n */\n sessionId?: string;\n /**\n * Session name for display in LangSmith.\n */\n sessionName?: string;\n}\n\n/**\n * Adds LangSmith metadata to the tracing options.\n *\n * The metadata is added under `metadata.langsmith` and allows you to:\n * - Route traces to different LangSmith projects via `projectName`\n * - Group traces by session via `sessionId` and `sessionName`\n *\n * @param metadata - The LangSmith metadata to add\n * @returns A TracingOptionsUpdater function for use with `buildTracingOptions`\n *\n * @example\n * ```typescript\n * import { buildTracingOptions } from '@mastra/observability';\n * import { withLangsmithMetadata } from '@mastra/langsmith';\n *\n * // Route traces to a specific project\n * const tracingOptions = buildTracingOptions(\n * withLangsmithMetadata({ projectName: 'customer-support' }),\n * );\n *\n * // Or set multiple fields\n * const tracingOptions = buildTracingOptions(\n * withLangsmithMetadata({\n * projectName: 'my-project',\n * sessionId: 'session-123',\n * }),\n * );\n *\n * // Use in agent config\n * const agent = new Agent({\n * name: 'support-agent',\n * model: openai('gpt-4o'),\n * defaultGenerateOptions: {\n * tracingOptions: buildTracingOptions(\n * withLangsmithMetadata({ projectName: 'support-traces' })\n * ),\n * },\n * });\n * ```\n */\nexport function withLangsmithMetadata(metadata: LangSmithMetadataInput): TracingOptionsUpdater {\n return opts => ({\n ...opts,\n metadata: {\n ...opts.metadata,\n langsmith: {\n ...(opts.metadata?.langsmith as Record<string, unknown>),\n ...metadata,\n },\n },\n });\n}\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -420,7 +420,8 @@ var LangSmithExporter = class extends TrackingExporter {
420
420
  "provider",
421
421
  "usage",
422
422
  "parameters",
423
- "completionStartTime"
423
+ "completionStartTime",
424
+ "timeToFirstTokenMs"
424
425
  ]);
425
426
  payload.metadata = {
426
427
  ...payload.metadata,
@@ -428,7 +429,7 @@ var LangSmithExporter = class extends TrackingExporter {
428
429
  };
429
430
  } else payload.metadata = {
430
431
  ...payload.metadata,
431
- ...attributes
432
+ ...omitKeys(attributes, ["timeToFirstTokenMs"])
432
433
  };
433
434
  if (span.errorInfo) {
434
435
  payload.error = span.errorInfo.message;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/metrics.ts","../src/tracing.ts","../src/helpers.ts"],"sourcesContent":["import type { UsageStats } from '@mastra/core/observability';\n\n/**\n * LangSmithUsageMetrics\n *\n * Canonical metric keys expected by LangSmith for LLM usage accounting.\n * See: https://docs.langchain.com/langsmith/log-llm-trace#provide-token-and-cost-information\n */\nexport interface LangSmithUsageMetrics {\n input_tokens?: number;\n output_tokens?: number;\n total_tokens?: number;\n input_token_details?: {\n [key: string]: number;\n };\n output_token_details?: {\n [key: string]: number;\n };\n}\n\n/**\n * Formats UsageStats to LangSmith's expected metric format.\n */\nexport function formatUsageMetrics(usage?: UsageStats): LangSmithUsageMetrics {\n const metrics: LangSmithUsageMetrics = {};\n\n if (usage?.inputTokens !== undefined) {\n metrics.input_tokens = usage.inputTokens;\n }\n\n if (usage?.outputTokens !== undefined) {\n metrics.output_tokens = usage.outputTokens;\n }\n\n // Compute total if we have both\n if (metrics.input_tokens !== undefined && metrics.output_tokens !== undefined) {\n metrics.total_tokens = metrics.input_tokens + metrics.output_tokens;\n }\n\n if (usage?.outputDetails?.reasoning !== undefined) {\n metrics.output_token_details = {\n ...(metrics.output_token_details ?? {}),\n reasoning_tokens: usage.outputDetails.reasoning,\n };\n }\n\n if (usage?.inputDetails?.cacheRead !== undefined) {\n metrics.input_token_details = {\n ...(metrics.input_token_details ?? {}),\n cache_read: usage.inputDetails.cacheRead,\n };\n }\n\n if (usage?.inputDetails?.cacheWrite !== undefined) {\n metrics.input_token_details = {\n ...(metrics.input_token_details ?? {}),\n cache_write: usage.inputDetails.cacheWrite,\n };\n }\n\n if (usage?.inputDetails?.cacheWrite5m !== undefined) {\n metrics.input_token_details = {\n ...(metrics.input_token_details ?? {}),\n cache_creation_5m: usage.inputDetails.cacheWrite5m,\n };\n }\n\n if (usage?.inputDetails?.cacheWrite1h !== undefined) {\n metrics.input_token_details = {\n ...(metrics.input_token_details ?? {}),\n cache_creation_1h: usage.inputDetails.cacheWrite1h,\n };\n }\n\n if (usage?.inputDetails?.audio !== undefined) {\n metrics.input_token_details = {\n ...(metrics.input_token_details ?? {}),\n audio: usage.inputDetails.audio,\n };\n }\n\n if (usage?.outputDetails?.audio !== undefined) {\n metrics.output_token_details = {\n ...(metrics.output_token_details ?? {}),\n audio: usage.outputDetails.audio,\n };\n }\n\n return metrics;\n}\n","/**\n * LangSmith Exporter for Mastra Tracing\n *\n * This exporter sends observability data to LangSmith\n * Root spans become top-level LangSmith RunTrees (no trace wrapper).\n * Events are handled as zero-duration RunTrees with matching start/end times.\n */\n\nimport { createHash } from 'node:crypto';\nimport type { AnyExportedSpan, ModelGenerationAttributes, ScoreEvent, SpanErrorInfo } from '@mastra/core/observability';\nimport { SpanType } from '@mastra/core/observability';\nimport { omitKeys } from '@mastra/core/utils';\nimport { TrackingExporter } from '@mastra/observability';\nimport type { TraceData, TrackingExporterConfig } from '@mastra/observability';\nimport type { ClientConfig, RunTreeConfig } from 'langsmith';\nimport { Client, RunTree } from 'langsmith';\nimport type { KVMap } from 'langsmith/schemas';\nimport type { LangSmithMetadataInput } from './helpers';\nimport { formatUsageMetrics } from './metrics';\n\nexport interface LangSmithExporterConfig extends ClientConfig, TrackingExporterConfig {\n /** LangSmith client instance */\n client?: Client;\n /**\n * The name of the LangSmith project to send traces to.\n * Overrides the LANGCHAIN_PROJECT environment variable.\n * If neither is set, traces are sent to the \"default\" project.\n */\n projectName?: string;\n /**\n * Maximum number of `traceId → root run id` mappings to keep in memory. The\n * cache lets a trace-level score skip the LangSmith lookup for traces this\n * process exported; scores for traces outside the cache are still delivered.\n * Older entries are evicted in LRU order when the cap is exceeded.\n * Defaults to 10000.\n */\n runIdCacheMaxEntries?: number;\n}\n\nconst DEFAULT_RUN_ID_CACHE_MAX_ENTRIES = 10_000;\n/** Waits between LangSmith lookups for a trace another process exported (ingestion takes ~5-8 s). */\nconst SCORE_LOOKUP_RETRY_DELAYS_MS = [2_000, 3_000, 5_000];\n/** Mastra trace ids are hex (`isValidTraceId` in @mastra/observability); anything else cannot match a run. */\nconst HEX_TRACE_ID = /^[0-9a-f]{1,32}$/i;\n\n/** The first root run of a Mastra trace and the LangSmith project it lives in. */\ninterface RootRun {\n runId: string;\n /** Project the root was posted to by this process; resolved to an id only when a score needs it. */\n projectName?: string;\n /** Project id read back from LangSmith when the root was found there. */\n sessionId?: string;\n}\n\n/** UUID v5 namespace for Mastra → LangSmith run ids. Changing it orphans existing runs from future scores. */\nconst RUN_ID_NAMESPACE = Buffer.from('a6f3c3f06b0e4a1e9f4c7e1b2d3c4f5a', 'hex');\n\n/**\n * LangSmith run id for a Mastra span id (UUID v5). Every run, root or not, is named\n * after its own span, so a span-level score can be routed from any process without\n * remembering the ids LangSmith would otherwise allocate.\n */\nexport function langsmithRunIdFor(spanId: string): string {\n const hash = createHash('sha1').update(RUN_ID_NAMESPACE).update(spanId).digest();\n hash[6] = (hash[6]! & 0x0f) | 0x50;\n hash[8] = (hash[8]! & 0x3f) | 0x80;\n const hex = hash.subarray(0, 16).toString('hex');\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\ntype LangSmithRoot = undefined;\ntype LangSmithSpan = RunTree;\ntype LangSmithEvent = RunTree;\ntype LangSmithMetadata = LangSmithMetadataInput;\ntype LangSmithTraceData = TraceData<LangSmithRoot, LangSmithSpan, LangSmithEvent, LangSmithMetadata>;\n\n// Default span type for all spans\nconst DEFAULT_SPAN_TYPE = 'chain';\n\n// Exceptions to the default mapping\nconst SPAN_TYPE_EXCEPTIONS: Partial<Record<SpanType, 'llm' | 'tool' | 'chain'>> = {\n [SpanType.MODEL_GENERATION]: 'llm',\n [SpanType.TOOL_CALL]: 'tool',\n [SpanType.MCP_TOOL_CALL]: 'tool',\n [SpanType.PROVIDER_TOOL_CALL]: 'tool',\n [SpanType.WORKFLOW_CONDITIONAL_EVAL]: 'chain',\n [SpanType.WORKFLOW_WAIT_EVENT]: 'chain',\n};\n\n// Mapping function - returns valid LangSmith span types\nfunction mapSpanType(spanType: SpanType): 'llm' | 'tool' | 'chain' {\n return SPAN_TYPE_EXCEPTIONS[spanType] ?? DEFAULT_SPAN_TYPE;\n}\n\nfunction isKVMap(value: unknown): value is KVMap {\n return value != null && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date);\n}\n\nexport class LangSmithExporter extends TrackingExporter<\n LangSmithRoot,\n LangSmithSpan,\n LangSmithEvent,\n LangSmithMetadata,\n LangSmithExporterConfig\n> {\n override name = 'langsmith';\n #client: Client | undefined;\n\n /**\n * Maps a Mastra trace id to the first root run this process exported for it, and the\n * project it went to. A fast path for scores: a trace-level score on a miss falls back\n * to asking LangSmith for the root run tagged with that `mastra_trace_id`.\n *\n * Bounded LRU keyed by Mastra trace id — entries are evicted oldest-first when\n * size exceeds `#runIdCacheMaxEntries` so the cache cannot grow without bound.\n */\n #rootRunByTraceId = new Map<string, RootRun>();\n /** Manual-mode client flushes, chained so an overlapping flush waits for the earlier upload */\n #manualFlushChain: Promise<void> = Promise.resolve();\n #runIdCacheMaxEntries: number;\n /** Project runs go to unless `withLangsmithMetadata({ projectName })` says otherwise. */\n #defaultProjectName: string;\n /** LangSmith project (session) ids by name: `createFeedback` requires one and run queries are scoped to one. */\n #projectIdByName = new Map<string, Promise<string | undefined>>();\n\n constructor(config: LangSmithExporterConfig = {}) {\n // Resolve env vars BEFORE calling super (config is readonly in base class)\n const apiKey = config.apiKey ?? process.env.LANGSMITH_API_KEY;\n\n super({\n ...config,\n apiKey,\n });\n\n this.#runIdCacheMaxEntries = Math.max(1, config.runIdCacheMaxEntries ?? DEFAULT_RUN_ID_CACHE_MAX_ENTRIES);\n this.#defaultProjectName =\n config.projectName ?? process.env.LANGSMITH_PROJECT ?? process.env.LANGCHAIN_PROJECT ?? 'default';\n\n if (!apiKey) {\n this.setDisabled(`Missing required credentials (apiKey: ${!!apiKey})`);\n return;\n }\n\n this.#client = config.client ?? new Client(this.config);\n }\n\n /** Look up the root run for a Mastra trace id, refreshing its LRU position on hit. */\n #getRootRun(traceId: string): RootRun | undefined {\n const root = this.#rootRunByTraceId.get(traceId);\n if (root === undefined) return undefined;\n // Re-insert to mark as most-recently-used (Map preserves insertion order).\n this.#rootRunByTraceId.delete(traceId);\n this.#rootRunByTraceId.set(traceId, root);\n return root;\n }\n\n /** Remember the first root run of a Mastra trace, evicting oldest entries when full. */\n #rememberRootRun(traceId: string, root: RootRun): void {\n if (this.#rootRunByTraceId.has(traceId)) return;\n this.#rootRunByTraceId.set(traceId, root);\n while (this.#rootRunByTraceId.size > this.#runIdCacheMaxEntries) {\n const oldest = this.#rootRunByTraceId.keys().next().value;\n if (oldest === undefined) break;\n this.#rootRunByTraceId.delete(oldest);\n }\n }\n\n /**\n * Send everything the LangSmith Client has queued. In manual flush mode only\n * `flush()` drains the queue (`awaitPendingTraceBatches()` just warns there);\n * otherwise `awaitPendingTraceBatches()` waits for queued and in-flight batches.\n */\n async #flushClient(): Promise<void> {\n if (!this.#client) return;\n // `manualFlushMode` is a private field in the SDK typings; fall back to our config\n const manualFlushMode =\n (this.#client as unknown as { manualFlushMode?: boolean }).manualFlushMode ?? this.config.manualFlushMode;\n if (manualFlushMode) {\n // The SDK's flush() empties its queue before uploading, so an overlapping call\n // would find nothing to wait for. Run each flush after the previous one settles.\n const client = this.#client;\n const flush = this.#manualFlushChain.then(() => client.flush());\n this.#manualFlushChain = flush.catch(() => {});\n await flush;\n } else {\n await this.#client.awaitPendingTraceBatches();\n }\n }\n\n /**\n * Flush pending trace batches to LangSmith.\n * The LangSmith Client internally batches API calls; this ensures\n * all queued runs are sent before the process exits or the flush\n * caller continues.\n */\n protected override async _flush(): Promise<void> {\n await this.#flushClient();\n }\n\n /** Spans aborted by shutdown() were only queued on the client; send them before releasing state. */\n protected override async _postShutdown(): Promise<void> {\n try {\n await this.#flushClient();\n } finally {\n this.#rootRunByTraceId.clear();\n }\n }\n\n async onScoreEvent(event: ScoreEvent): Promise<void> {\n if (!this.#client) return;\n\n const { score } = event;\n // A span-level score only needs the trace's root to know which project the run is in.\n const root = score.spanId\n ? score.traceId\n ? this.#getRootRun(score.traceId)\n : undefined\n : score.traceId\n ? await this.#resolveRootRun(score.traceId)\n : undefined;\n const langsmithRunId = score.spanId ? langsmithRunIdFor(score.spanId) : root?.runId;\n if (!langsmithRunId) {\n this.logger.warn(\n 'LangSmith exporter: dropping score; no LangSmith run found for the target ' +\n '(score has neither traceId nor spanId, the trace is not ingested yet, or it was exported by an older @mastra/langsmith)',\n {\n traceId: score.traceId,\n spanId: score.spanId,\n scorerId: score.scorerId,\n },\n );\n return;\n }\n\n const key = score.scorerName ?? score.scorerId;\n const sessionId = root?.sessionId ?? (await this.#getProjectId(root?.projectName ?? this.#defaultProjectName));\n\n try {\n await this.#client.createFeedback(langsmithRunId, key, {\n ...(sessionId ? { sessionId } : {}),\n score: score.score,\n ...(score.reason ? { comment: score.reason } : {}),\n feedbackId: score.scoreId,\n ...(score.scoreTraceId ? { sourceRunId: score.scoreTraceId } : {}),\n sourceInfo: {\n // User-supplied metadata is spread first so the authoritative reserved\n // fields below cannot be overwritten by `scorerId` / `scoreSource` keys\n // a caller may have set inside metadata.\n ...(score.metadata ?? {}),\n scorerId: score.scorerId,\n ...(score.scoreSource ? { scoreSource: score.scoreSource } : {}),\n },\n });\n } catch (err) {\n this.logger.error('LangSmith exporter: Failed to submit feedback', {\n error: err,\n traceId: score.traceId,\n spanId: score.spanId,\n scorerId: score.scorerId,\n });\n }\n }\n\n /**\n * A trace-level score goes to the trace's first root run. This process remembers the\n * roots it exported; otherwise LangSmith is asked for the earliest root run tagged with\n * the Mastra trace id.\n */\n async #resolveRootRun(traceId: string): Promise<RootRun | undefined> {\n const known = this.#getRootRun(traceId);\n if (known) return known;\n if (!HEX_TRACE_ID.test(traceId)) return undefined;\n\n // LangSmith makes posted runs queryable a few seconds later, so a score that\n // arrives right after another process exported the trace has to wait for it.\n for (let attempt = 0; ; attempt++) {\n const found = await this.#findRootRun(traceId);\n if (found) {\n this.#rememberRootRun(traceId, found);\n return found;\n }\n const delay = SCORE_LOOKUP_RETRY_DELAYS_MS[attempt];\n if (delay === undefined) return undefined;\n await new Promise(resolve => setTimeout(resolve, delay));\n }\n }\n\n async #findRootRun(traceId: string): Promise<RootRun | undefined> {\n // LangSmith only queries runs inside a project, so traces routed to another\n // project with `withLangsmithMetadata({ projectName })` are found by this process only.\n const projectId = await this.#getProjectId(this.#defaultProjectName);\n if (!projectId) return undefined;\n\n try {\n const runs = this.#client!.listRuns({\n projectId,\n isRoot: true,\n filter: `has(metadata, '${JSON.stringify({ mastra_trace_id: traceId })}')`,\n select: ['id', 'session_id'],\n order: 'asc',\n limit: 1,\n });\n for await (const run of runs) {\n return { runId: run.id, sessionId: run.session_id };\n }\n } catch (err) {\n this.logger.debug('LangSmith exporter: root run lookup failed', { traceId, error: err });\n }\n return undefined;\n }\n\n #getProjectId(projectName: string): Promise<string | undefined> {\n let projectId = this.#projectIdByName.get(projectName);\n if (!projectId) {\n projectId = Promise.resolve()\n .then(() => this.#client!.readProject({ projectName }))\n .then(project => project.id)\n .catch(err => {\n this.logger.debug('LangSmith exporter: could not resolve the project id', { projectName, error: err });\n this.#projectIdByName.delete(projectName);\n return undefined;\n });\n this.#projectIdByName.set(projectName, projectId);\n }\n return projectId;\n }\n\n protected override skipBuildRootTask = true;\n protected override async _buildRoot(_args: {\n span: AnyExportedSpan;\n traceData: LangSmithTraceData;\n }): Promise<LangSmithRoot | undefined> {\n throw new Error('Method not implemented.');\n }\n\n protected override async _buildSpan(args: {\n span: AnyExportedSpan;\n traceData: LangSmithTraceData;\n }): Promise<LangSmithSpan | undefined> {\n const { span, traceData } = args;\n\n const parent = span.isRootSpan ? undefined : traceData.getParent(args);\n\n if (!span.isRootSpan && !parent) {\n // parent doesn't exist and not creating rootSpan, return early data\n return;\n }\n\n const runId = langsmithRunIdFor(span.id);\n const payload = {\n name: span.name,\n id: runId,\n ...this.buildRunTreePayload(span, traceData, true),\n };\n\n const langSmithSpan = span.isRootSpan ? new RunTree(payload) : parent!.createChild(payload);\n if (span.isRootSpan) {\n this.#rememberRootRun(span.traceId, { runId, projectName: payload.project_name ?? this.#defaultProjectName });\n }\n\n await langSmithSpan.postRun();\n return langSmithSpan;\n }\n\n protected override async _buildEvent(args: {\n span: AnyExportedSpan;\n traceData: LangSmithTraceData;\n }): Promise<LangSmithEvent | undefined> {\n const langSmithSpan = await this._buildSpan(args);\n\n if (!langSmithSpan) {\n // parent doesn't exist and not creating rootSpan, return early data\n return;\n }\n\n // use start-time as end-time to make an event span.\n await langSmithSpan.end({ endTime: args.span.startTime.getTime() });\n await langSmithSpan.patchRun();\n return langSmithSpan;\n }\n\n protected override async _updateSpan(args: { span: AnyExportedSpan; traceData: LangSmithTraceData }): Promise<void> {\n await this.handleSpanUpdateOrEnd({ ...args, isEnd: false });\n }\n\n protected override async _finishSpan(args: { span: AnyExportedSpan; traceData: LangSmithTraceData }): Promise<void> {\n await this.handleSpanUpdateOrEnd({ ...args, isEnd: true });\n }\n\n protected override async _abortSpan(args: {\n span: LangSmithSpan;\n traceData: LangSmithTraceData;\n reason: SpanErrorInfo;\n }): Promise<void> {\n const { span, reason } = args;\n span.error = reason.message;\n span.metadata = {\n ...span.metadata,\n errorDetails: reason,\n };\n await span.end();\n await span.patchRun();\n }\n\n private async handleSpanUpdateOrEnd(args: {\n span: AnyExportedSpan;\n traceData: LangSmithTraceData;\n isEnd: boolean;\n }): Promise<void> {\n const { span, traceData, isEnd } = args;\n\n const langSmithSpan = traceData.getSpan({ spanId: span.id });\n if (!langSmithSpan) {\n //update occurred before span start, return early data\n return;\n }\n\n const updatePayload = this.buildRunTreePayload(span, traceData);\n\n langSmithSpan.metadata = {\n ...langSmithSpan.metadata,\n ...updatePayload.metadata,\n };\n if (updatePayload.inputs != null) {\n langSmithSpan.inputs = updatePayload.inputs;\n }\n if (updatePayload.outputs != null) {\n langSmithSpan.outputs = updatePayload.outputs;\n }\n if (updatePayload.error != null) {\n langSmithSpan.error = updatePayload.error;\n }\n\n // Add new_token event for TTFT tracking on MODEL_GENERATION spans\n if (span.type === SpanType.MODEL_GENERATION) {\n const modelAttr = (span.attributes ?? {}) as ModelGenerationAttributes;\n if (modelAttr.completionStartTime !== undefined) {\n langSmithSpan.addEvent({\n name: 'new_token',\n time: modelAttr.completionStartTime.toISOString(),\n });\n }\n }\n\n // LangSmith accepts one update per run, so updates only mutate the local\n // RunTree and the single patch is sent on end with the final state.\n if (isEnd) {\n // End the span with the correct endTime\n if (span.endTime) {\n await langSmithSpan.end({ endTime: span.endTime.getTime() });\n } else {\n await langSmithSpan.end();\n }\n await langSmithSpan.patchRun();\n }\n }\n\n /**\n * Find LangSmith vendor metadata by walking up the span hierarchy and merging.\n * Metadata is merged from ancestors with child values taking precedence over parent values.\n *\n * TODO(2.0): Extract shared `findVendorMetadata()` to base TrackingExporter class\n * and reuse here and in LangfuseExporter.findLangfusePrompt()\n */\n private findLangsmithMetadata(\n traceData: LangSmithTraceData,\n span: AnyExportedSpan,\n ): LangSmithMetadataInput | undefined {\n // Collect metadata from all ancestors (current span first, then parents)\n const metadataChain: LangSmithMetadataInput[] = [];\n let currentSpanId: string | undefined = span.id;\n\n while (currentSpanId) {\n const providerMetadata = traceData.getMetadata({ spanId: currentSpanId });\n if (providerMetadata) {\n metadataChain.push(providerMetadata);\n }\n currentSpanId = traceData.getParentId({ spanId: currentSpanId });\n }\n\n if (metadataChain.length === 0) {\n return undefined;\n }\n\n // Merge from ancestors to current span (parent values first, child values override)\n const merged: LangSmithMetadataInput = {};\n for (let i = metadataChain.length - 1; i >= 0; i--) {\n const meta = metadataChain[i]!;\n if (meta.projectName !== undefined) merged.projectName = meta.projectName;\n if (meta.sessionId !== undefined) merged.sessionId = meta.sessionId;\n if (meta.sessionName !== undefined) merged.sessionName = meta.sessionName;\n }\n\n this.logger.debug(`${this.name}: merged vendor metadata from hierarchy`, {\n traceId: span.traceId,\n spanId: span.id,\n metadataKeys: Object.keys(merged),\n ancestorCount: metadataChain.length,\n });\n\n return merged;\n }\n\n /**\n * LangSmith groups runs into a Thread only by `session_id` / `thread_id` metadata and\n * needs it on every run of the trace. A child run follows the session its parent was\n * built with: sub-agents and Observational Memory run on derived threads, and their\n * own `threadId` would split the conversation into one-off Threads. The span's own\n * `sessionId` / `threadId` applies at the root, or when no ancestor carries a session.\n */\n private findSessionId(span: AnyExportedSpan, traceData: LangSmithTraceData): string | undefined {\n const inherited = span.isRootSpan ? undefined : traceData.getParent({ span })?.metadata?.session_id;\n if (typeof inherited === 'string' && inherited) return inherited;\n const own = span.metadata?.sessionId ?? span.metadata?.threadId;\n return typeof own === 'string' && own ? own : undefined;\n }\n\n private buildRunTreePayload(\n span: AnyExportedSpan,\n traceData: LangSmithTraceData,\n isNew = false,\n ): Partial<RunTreeConfig> {\n // Extract vendor metadata from span hierarchy\n const vendorMetadata = this.findLangsmithMetadata(traceData, span);\n\n // Build metadata, omitting the langsmith vendor key\n const spanMetadata = span.metadata ? omitKeys(span.metadata, ['langsmith']) : {};\n\n const payload: Partial<RunTreeConfig> & { metadata: KVMap } = {\n client: this.#client,\n metadata: {\n mastra_span_type: span.type,\n mastra_span_id: span.id,\n mastra_trace_id: span.traceId,\n ...spanMetadata,\n },\n };\n\n if (isNew) {\n payload.run_type = mapSpanType(span.type);\n payload.start_time = span.startTime.getTime();\n }\n\n // Add project name - vendor metadata takes precedence over config\n const projectName = vendorMetadata?.projectName ?? this.config.projectName;\n if (projectName) {\n payload.project_name = projectName;\n }\n\n // Explicit vendor sessionId wins; otherwise the Mastra conversation thread, so turns\n // on one memory thread land in one LangSmith Thread without extra setup.\n const sessionId = vendorMetadata?.sessionId ?? this.findSessionId(span, traceData);\n if (sessionId) {\n payload.metadata.session_id = sessionId;\n }\n if (vendorMetadata?.sessionName) {\n payload.metadata.session_name = vendorMetadata.sessionName;\n }\n\n // Add tags for root spans\n if (span.isRootSpan && span.tags?.length) {\n payload.tags = span.tags;\n }\n\n // Core span data\n if (span.input !== undefined) {\n payload.inputs = isKVMap(span.input) ? span.input : { input: span.input };\n }\n\n if (span.output !== undefined) {\n payload.outputs = isKVMap(span.output) ? span.output : { output: span.output };\n }\n\n const attributes = (span.attributes ?? {}) as Record<string, any>;\n\n if (span.type === SpanType.MODEL_GENERATION) {\n const modelAttr = attributes as ModelGenerationAttributes;\n\n // See: https://docs.langchain.com/langsmith/log-llm-trace\n if (modelAttr.model !== undefined) {\n // Note - this should map to a model name recognized by LangSmith\n // eg “gpt-4o-mini”, “claude-3-opus-20240307”, etc.\n payload.metadata.ls_model_name = modelAttr.model;\n }\n\n // Provider goes to metadata (if provided by attributes)\n if (modelAttr.provider !== undefined) {\n // Note - this should map to a provider name recognized by\n // LangSmith eg “openai”, “anthropic”, etc.\n payload.metadata.ls_provider = modelAttr.provider;\n }\n\n // Usage/token info goes to metrics\n payload.metadata.usage_metadata = formatUsageMetrics(modelAttr.usage);\n\n // Model parameters go to metadata\n if (modelAttr.parameters !== undefined) {\n payload.metadata.modelParameters = modelAttr.parameters;\n }\n\n // Other LLM attributes go to metadata\n const otherAttributes = omitKeys(attributes, ['model', 'provider', 'usage', 'parameters', 'completionStartTime']);\n payload.metadata = {\n ...payload.metadata,\n ...otherAttributes,\n };\n } else {\n // For non-LLM spans, put all attributes in metadata\n payload.metadata = {\n ...payload.metadata,\n ...attributes,\n };\n }\n\n // Handle errors\n if (span.errorInfo) {\n payload.error = span.errorInfo.message;\n payload.metadata.errorDetails = span.errorInfo;\n }\n\n return payload;\n }\n}\n","/**\n * LangSmith Tracing Options Helpers\n *\n * These helpers integrate with the `buildTracingOptions` pattern from\n * `@mastra/observability` to add LangSmith-specific tracing features.\n *\n * @example\n * ```typescript\n * import { buildTracingOptions } from '@mastra/observability';\n * import { withLangsmithMetadata } from '@mastra/langsmith';\n *\n * const agent = new Agent({\n * defaultGenerateOptions: {\n * tracingOptions: buildTracingOptions(\n * withLangsmithMetadata({ projectName: 'my-project' })\n * ),\n * },\n * });\n * ```\n */\n\nimport type { TracingOptionsUpdater } from '@mastra/observability';\n\n/**\n * LangSmith vendor metadata that can be passed via span metadata.\n * These fields are extracted by the LangSmith exporter and used\n * to override default configuration on a per-span basis.\n */\nexport interface LangSmithMetadataInput {\n /**\n * Override the project name for this span and its children.\n * This allows dynamically routing traces to different LangSmith projects.\n */\n projectName?: string;\n /**\n * Session ID for grouping related traces in LangSmith.\n */\n sessionId?: string;\n /**\n * Session name for display in LangSmith.\n */\n sessionName?: string;\n}\n\n/**\n * Adds LangSmith metadata to the tracing options.\n *\n * The metadata is added under `metadata.langsmith` and allows you to:\n * - Route traces to different LangSmith projects via `projectName`\n * - Group traces by session via `sessionId` and `sessionName`\n *\n * @param metadata - The LangSmith metadata to add\n * @returns A TracingOptionsUpdater function for use with `buildTracingOptions`\n *\n * @example\n * ```typescript\n * import { buildTracingOptions } from '@mastra/observability';\n * import { withLangsmithMetadata } from '@mastra/langsmith';\n *\n * // Route traces to a specific project\n * const tracingOptions = buildTracingOptions(\n * withLangsmithMetadata({ projectName: 'customer-support' }),\n * );\n *\n * // Or set multiple fields\n * const tracingOptions = buildTracingOptions(\n * withLangsmithMetadata({\n * projectName: 'my-project',\n * sessionId: 'session-123',\n * }),\n * );\n *\n * // Use in agent config\n * const agent = new Agent({\n * name: 'support-agent',\n * model: openai('gpt-4o'),\n * defaultGenerateOptions: {\n * tracingOptions: buildTracingOptions(\n * withLangsmithMetadata({ projectName: 'support-traces' })\n * ),\n * },\n * });\n * ```\n */\nexport function withLangsmithMetadata(metadata: LangSmithMetadataInput): TracingOptionsUpdater {\n return opts => ({\n ...opts,\n metadata: {\n ...opts.metadata,\n langsmith: {\n ...(opts.metadata?.langsmith as Record<string, unknown>),\n ...metadata,\n },\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/metrics.ts","../src/tracing.ts","../src/helpers.ts"],"sourcesContent":["import type { UsageStats } from '@mastra/core/observability';\n\n/**\n * LangSmithUsageMetrics\n *\n * Canonical metric keys expected by LangSmith for LLM usage accounting.\n * See: https://docs.langchain.com/langsmith/log-llm-trace#provide-token-and-cost-information\n */\nexport interface LangSmithUsageMetrics {\n input_tokens?: number;\n output_tokens?: number;\n total_tokens?: number;\n input_token_details?: {\n [key: string]: number;\n };\n output_token_details?: {\n [key: string]: number;\n };\n}\n\n/**\n * Formats UsageStats to LangSmith's expected metric format.\n */\nexport function formatUsageMetrics(usage?: UsageStats): LangSmithUsageMetrics {\n const metrics: LangSmithUsageMetrics = {};\n\n if (usage?.inputTokens !== undefined) {\n metrics.input_tokens = usage.inputTokens;\n }\n\n if (usage?.outputTokens !== undefined) {\n metrics.output_tokens = usage.outputTokens;\n }\n\n // Compute total if we have both\n if (metrics.input_tokens !== undefined && metrics.output_tokens !== undefined) {\n metrics.total_tokens = metrics.input_tokens + metrics.output_tokens;\n }\n\n if (usage?.outputDetails?.reasoning !== undefined) {\n metrics.output_token_details = {\n ...(metrics.output_token_details ?? {}),\n reasoning_tokens: usage.outputDetails.reasoning,\n };\n }\n\n if (usage?.inputDetails?.cacheRead !== undefined) {\n metrics.input_token_details = {\n ...(metrics.input_token_details ?? {}),\n cache_read: usage.inputDetails.cacheRead,\n };\n }\n\n if (usage?.inputDetails?.cacheWrite !== undefined) {\n metrics.input_token_details = {\n ...(metrics.input_token_details ?? {}),\n cache_write: usage.inputDetails.cacheWrite,\n };\n }\n\n if (usage?.inputDetails?.cacheWrite5m !== undefined) {\n metrics.input_token_details = {\n ...(metrics.input_token_details ?? {}),\n cache_creation_5m: usage.inputDetails.cacheWrite5m,\n };\n }\n\n if (usage?.inputDetails?.cacheWrite1h !== undefined) {\n metrics.input_token_details = {\n ...(metrics.input_token_details ?? {}),\n cache_creation_1h: usage.inputDetails.cacheWrite1h,\n };\n }\n\n if (usage?.inputDetails?.audio !== undefined) {\n metrics.input_token_details = {\n ...(metrics.input_token_details ?? {}),\n audio: usage.inputDetails.audio,\n };\n }\n\n if (usage?.outputDetails?.audio !== undefined) {\n metrics.output_token_details = {\n ...(metrics.output_token_details ?? {}),\n audio: usage.outputDetails.audio,\n };\n }\n\n return metrics;\n}\n","/**\n * LangSmith Exporter for Mastra Tracing\n *\n * This exporter sends observability data to LangSmith\n * Root spans become top-level LangSmith RunTrees (no trace wrapper).\n * Events are handled as zero-duration RunTrees with matching start/end times.\n */\n\nimport { createHash } from 'node:crypto';\nimport type { AnyExportedSpan, ModelGenerationAttributes, ScoreEvent, SpanErrorInfo } from '@mastra/core/observability';\nimport { SpanType } from '@mastra/core/observability';\nimport { omitKeys } from '@mastra/core/utils';\nimport { TrackingExporter } from '@mastra/observability';\nimport type { TraceData, TrackingExporterConfig } from '@mastra/observability';\nimport type { ClientConfig, RunTreeConfig } from 'langsmith';\nimport { Client, RunTree } from 'langsmith';\nimport type { KVMap } from 'langsmith/schemas';\nimport type { LangSmithMetadataInput } from './helpers';\nimport { formatUsageMetrics } from './metrics';\n\nexport interface LangSmithExporterConfig extends ClientConfig, TrackingExporterConfig {\n /** LangSmith client instance */\n client?: Client;\n /**\n * The name of the LangSmith project to send traces to.\n * Overrides the LANGCHAIN_PROJECT environment variable.\n * If neither is set, traces are sent to the \"default\" project.\n */\n projectName?: string;\n /**\n * Maximum number of `traceId → root run id` mappings to keep in memory. The\n * cache lets a trace-level score skip the LangSmith lookup for traces this\n * process exported; scores for traces outside the cache are still delivered.\n * Older entries are evicted in LRU order when the cap is exceeded.\n * Defaults to 10000.\n */\n runIdCacheMaxEntries?: number;\n}\n\nconst DEFAULT_RUN_ID_CACHE_MAX_ENTRIES = 10_000;\n/** Waits between LangSmith lookups for a trace another process exported (ingestion takes ~5-8 s). */\nconst SCORE_LOOKUP_RETRY_DELAYS_MS = [2_000, 3_000, 5_000];\n/** Mastra trace ids are hex (`isValidTraceId` in @mastra/observability); anything else cannot match a run. */\nconst HEX_TRACE_ID = /^[0-9a-f]{1,32}$/i;\n\n/** The first root run of a Mastra trace and the LangSmith project it lives in. */\ninterface RootRun {\n runId: string;\n /** Project the root was posted to by this process; resolved to an id only when a score needs it. */\n projectName?: string;\n /** Project id read back from LangSmith when the root was found there. */\n sessionId?: string;\n}\n\n/** UUID v5 namespace for Mastra → LangSmith run ids. Changing it orphans existing runs from future scores. */\nconst RUN_ID_NAMESPACE = Buffer.from('a6f3c3f06b0e4a1e9f4c7e1b2d3c4f5a', 'hex');\n\n/**\n * LangSmith run id for a Mastra span id (UUID v5). Every run, root or not, is named\n * after its own span, so a span-level score can be routed from any process without\n * remembering the ids LangSmith would otherwise allocate.\n */\nexport function langsmithRunIdFor(spanId: string): string {\n const hash = createHash('sha1').update(RUN_ID_NAMESPACE).update(spanId).digest();\n hash[6] = (hash[6]! & 0x0f) | 0x50;\n hash[8] = (hash[8]! & 0x3f) | 0x80;\n const hex = hash.subarray(0, 16).toString('hex');\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\ntype LangSmithRoot = undefined;\ntype LangSmithSpan = RunTree;\ntype LangSmithEvent = RunTree;\ntype LangSmithMetadata = LangSmithMetadataInput;\ntype LangSmithTraceData = TraceData<LangSmithRoot, LangSmithSpan, LangSmithEvent, LangSmithMetadata>;\n\n// Default span type for all spans\nconst DEFAULT_SPAN_TYPE = 'chain';\n\n// Exceptions to the default mapping\nconst SPAN_TYPE_EXCEPTIONS: Partial<Record<SpanType, 'llm' | 'tool' | 'chain'>> = {\n [SpanType.MODEL_GENERATION]: 'llm',\n [SpanType.TOOL_CALL]: 'tool',\n [SpanType.MCP_TOOL_CALL]: 'tool',\n [SpanType.PROVIDER_TOOL_CALL]: 'tool',\n [SpanType.WORKFLOW_CONDITIONAL_EVAL]: 'chain',\n [SpanType.WORKFLOW_WAIT_EVENT]: 'chain',\n};\n\n// Mapping function - returns valid LangSmith span types\nfunction mapSpanType(spanType: SpanType): 'llm' | 'tool' | 'chain' {\n return SPAN_TYPE_EXCEPTIONS[spanType] ?? DEFAULT_SPAN_TYPE;\n}\n\nfunction isKVMap(value: unknown): value is KVMap {\n return value != null && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date);\n}\n\nexport class LangSmithExporter extends TrackingExporter<\n LangSmithRoot,\n LangSmithSpan,\n LangSmithEvent,\n LangSmithMetadata,\n LangSmithExporterConfig\n> {\n override name = 'langsmith';\n #client: Client | undefined;\n\n /**\n * Maps a Mastra trace id to the first root run this process exported for it, and the\n * project it went to. A fast path for scores: a trace-level score on a miss falls back\n * to asking LangSmith for the root run tagged with that `mastra_trace_id`.\n *\n * Bounded LRU keyed by Mastra trace id — entries are evicted oldest-first when\n * size exceeds `#runIdCacheMaxEntries` so the cache cannot grow without bound.\n */\n #rootRunByTraceId = new Map<string, RootRun>();\n /** Manual-mode client flushes, chained so an overlapping flush waits for the earlier upload */\n #manualFlushChain: Promise<void> = Promise.resolve();\n #runIdCacheMaxEntries: number;\n /** Project runs go to unless `withLangsmithMetadata({ projectName })` says otherwise. */\n #defaultProjectName: string;\n /** LangSmith project (session) ids by name: `createFeedback` requires one and run queries are scoped to one. */\n #projectIdByName = new Map<string, Promise<string | undefined>>();\n\n constructor(config: LangSmithExporterConfig = {}) {\n // Resolve env vars BEFORE calling super (config is readonly in base class)\n const apiKey = config.apiKey ?? process.env.LANGSMITH_API_KEY;\n\n super({\n ...config,\n apiKey,\n });\n\n this.#runIdCacheMaxEntries = Math.max(1, config.runIdCacheMaxEntries ?? DEFAULT_RUN_ID_CACHE_MAX_ENTRIES);\n this.#defaultProjectName =\n config.projectName ?? process.env.LANGSMITH_PROJECT ?? process.env.LANGCHAIN_PROJECT ?? 'default';\n\n if (!apiKey) {\n this.setDisabled(`Missing required credentials (apiKey: ${!!apiKey})`);\n return;\n }\n\n this.#client = config.client ?? new Client(this.config);\n }\n\n /** Look up the root run for a Mastra trace id, refreshing its LRU position on hit. */\n #getRootRun(traceId: string): RootRun | undefined {\n const root = this.#rootRunByTraceId.get(traceId);\n if (root === undefined) return undefined;\n // Re-insert to mark as most-recently-used (Map preserves insertion order).\n this.#rootRunByTraceId.delete(traceId);\n this.#rootRunByTraceId.set(traceId, root);\n return root;\n }\n\n /** Remember the first root run of a Mastra trace, evicting oldest entries when full. */\n #rememberRootRun(traceId: string, root: RootRun): void {\n if (this.#rootRunByTraceId.has(traceId)) return;\n this.#rootRunByTraceId.set(traceId, root);\n while (this.#rootRunByTraceId.size > this.#runIdCacheMaxEntries) {\n const oldest = this.#rootRunByTraceId.keys().next().value;\n if (oldest === undefined) break;\n this.#rootRunByTraceId.delete(oldest);\n }\n }\n\n /**\n * Send everything the LangSmith Client has queued. In manual flush mode only\n * `flush()` drains the queue (`awaitPendingTraceBatches()` just warns there);\n * otherwise `awaitPendingTraceBatches()` waits for queued and in-flight batches.\n */\n async #flushClient(): Promise<void> {\n if (!this.#client) return;\n // `manualFlushMode` is a private field in the SDK typings; fall back to our config\n const manualFlushMode =\n (this.#client as unknown as { manualFlushMode?: boolean }).manualFlushMode ?? this.config.manualFlushMode;\n if (manualFlushMode) {\n // The SDK's flush() empties its queue before uploading, so an overlapping call\n // would find nothing to wait for. Run each flush after the previous one settles.\n const client = this.#client;\n const flush = this.#manualFlushChain.then(() => client.flush());\n this.#manualFlushChain = flush.catch(() => {});\n await flush;\n } else {\n await this.#client.awaitPendingTraceBatches();\n }\n }\n\n /**\n * Flush pending trace batches to LangSmith.\n * The LangSmith Client internally batches API calls; this ensures\n * all queued runs are sent before the process exits or the flush\n * caller continues.\n */\n protected override async _flush(): Promise<void> {\n await this.#flushClient();\n }\n\n /** Spans aborted by shutdown() were only queued on the client; send them before releasing state. */\n protected override async _postShutdown(): Promise<void> {\n try {\n await this.#flushClient();\n } finally {\n this.#rootRunByTraceId.clear();\n }\n }\n\n async onScoreEvent(event: ScoreEvent): Promise<void> {\n if (!this.#client) return;\n\n const { score } = event;\n // A span-level score only needs the trace's root to know which project the run is in.\n const root = score.spanId\n ? score.traceId\n ? this.#getRootRun(score.traceId)\n : undefined\n : score.traceId\n ? await this.#resolveRootRun(score.traceId)\n : undefined;\n const langsmithRunId = score.spanId ? langsmithRunIdFor(score.spanId) : root?.runId;\n if (!langsmithRunId) {\n this.logger.warn(\n 'LangSmith exporter: dropping score; no LangSmith run found for the target ' +\n '(score has neither traceId nor spanId, the trace is not ingested yet, or it was exported by an older @mastra/langsmith)',\n {\n traceId: score.traceId,\n spanId: score.spanId,\n scorerId: score.scorerId,\n },\n );\n return;\n }\n\n const key = score.scorerName ?? score.scorerId;\n const sessionId = root?.sessionId ?? (await this.#getProjectId(root?.projectName ?? this.#defaultProjectName));\n\n try {\n await this.#client.createFeedback(langsmithRunId, key, {\n ...(sessionId ? { sessionId } : {}),\n score: score.score,\n ...(score.reason ? { comment: score.reason } : {}),\n feedbackId: score.scoreId,\n ...(score.scoreTraceId ? { sourceRunId: score.scoreTraceId } : {}),\n sourceInfo: {\n // User-supplied metadata is spread first so the authoritative reserved\n // fields below cannot be overwritten by `scorerId` / `scoreSource` keys\n // a caller may have set inside metadata.\n ...(score.metadata ?? {}),\n scorerId: score.scorerId,\n ...(score.scoreSource ? { scoreSource: score.scoreSource } : {}),\n },\n });\n } catch (err) {\n this.logger.error('LangSmith exporter: Failed to submit feedback', {\n error: err,\n traceId: score.traceId,\n spanId: score.spanId,\n scorerId: score.scorerId,\n });\n }\n }\n\n /**\n * A trace-level score goes to the trace's first root run. This process remembers the\n * roots it exported; otherwise LangSmith is asked for the earliest root run tagged with\n * the Mastra trace id.\n */\n async #resolveRootRun(traceId: string): Promise<RootRun | undefined> {\n const known = this.#getRootRun(traceId);\n if (known) return known;\n if (!HEX_TRACE_ID.test(traceId)) return undefined;\n\n // LangSmith makes posted runs queryable a few seconds later, so a score that\n // arrives right after another process exported the trace has to wait for it.\n for (let attempt = 0; ; attempt++) {\n const found = await this.#findRootRun(traceId);\n if (found) {\n this.#rememberRootRun(traceId, found);\n return found;\n }\n const delay = SCORE_LOOKUP_RETRY_DELAYS_MS[attempt];\n if (delay === undefined) return undefined;\n await new Promise(resolve => setTimeout(resolve, delay));\n }\n }\n\n async #findRootRun(traceId: string): Promise<RootRun | undefined> {\n // LangSmith only queries runs inside a project, so traces routed to another\n // project with `withLangsmithMetadata({ projectName })` are found by this process only.\n const projectId = await this.#getProjectId(this.#defaultProjectName);\n if (!projectId) return undefined;\n\n try {\n const runs = this.#client!.listRuns({\n projectId,\n isRoot: true,\n filter: `has(metadata, '${JSON.stringify({ mastra_trace_id: traceId })}')`,\n select: ['id', 'session_id'],\n order: 'asc',\n limit: 1,\n });\n for await (const run of runs) {\n return { runId: run.id, sessionId: run.session_id };\n }\n } catch (err) {\n this.logger.debug('LangSmith exporter: root run lookup failed', { traceId, error: err });\n }\n return undefined;\n }\n\n #getProjectId(projectName: string): Promise<string | undefined> {\n let projectId = this.#projectIdByName.get(projectName);\n if (!projectId) {\n projectId = Promise.resolve()\n .then(() => this.#client!.readProject({ projectName }))\n .then(project => project.id)\n .catch(err => {\n this.logger.debug('LangSmith exporter: could not resolve the project id', { projectName, error: err });\n this.#projectIdByName.delete(projectName);\n return undefined;\n });\n this.#projectIdByName.set(projectName, projectId);\n }\n return projectId;\n }\n\n protected override skipBuildRootTask = true;\n protected override async _buildRoot(_args: {\n span: AnyExportedSpan;\n traceData: LangSmithTraceData;\n }): Promise<LangSmithRoot | undefined> {\n throw new Error('Method not implemented.');\n }\n\n protected override async _buildSpan(args: {\n span: AnyExportedSpan;\n traceData: LangSmithTraceData;\n }): Promise<LangSmithSpan | undefined> {\n const { span, traceData } = args;\n\n const parent = span.isRootSpan ? undefined : traceData.getParent(args);\n\n if (!span.isRootSpan && !parent) {\n // parent doesn't exist and not creating rootSpan, return early data\n return;\n }\n\n const runId = langsmithRunIdFor(span.id);\n const payload = {\n name: span.name,\n id: runId,\n ...this.buildRunTreePayload(span, traceData, true),\n };\n\n const langSmithSpan = span.isRootSpan ? new RunTree(payload) : parent!.createChild(payload);\n if (span.isRootSpan) {\n this.#rememberRootRun(span.traceId, { runId, projectName: payload.project_name ?? this.#defaultProjectName });\n }\n\n await langSmithSpan.postRun();\n return langSmithSpan;\n }\n\n protected override async _buildEvent(args: {\n span: AnyExportedSpan;\n traceData: LangSmithTraceData;\n }): Promise<LangSmithEvent | undefined> {\n const langSmithSpan = await this._buildSpan(args);\n\n if (!langSmithSpan) {\n // parent doesn't exist and not creating rootSpan, return early data\n return;\n }\n\n // use start-time as end-time to make an event span.\n await langSmithSpan.end({ endTime: args.span.startTime.getTime() });\n await langSmithSpan.patchRun();\n return langSmithSpan;\n }\n\n protected override async _updateSpan(args: { span: AnyExportedSpan; traceData: LangSmithTraceData }): Promise<void> {\n await this.handleSpanUpdateOrEnd({ ...args, isEnd: false });\n }\n\n protected override async _finishSpan(args: { span: AnyExportedSpan; traceData: LangSmithTraceData }): Promise<void> {\n await this.handleSpanUpdateOrEnd({ ...args, isEnd: true });\n }\n\n protected override async _abortSpan(args: {\n span: LangSmithSpan;\n traceData: LangSmithTraceData;\n reason: SpanErrorInfo;\n }): Promise<void> {\n const { span, reason } = args;\n span.error = reason.message;\n span.metadata = {\n ...span.metadata,\n errorDetails: reason,\n };\n await span.end();\n await span.patchRun();\n }\n\n private async handleSpanUpdateOrEnd(args: {\n span: AnyExportedSpan;\n traceData: LangSmithTraceData;\n isEnd: boolean;\n }): Promise<void> {\n const { span, traceData, isEnd } = args;\n\n const langSmithSpan = traceData.getSpan({ spanId: span.id });\n if (!langSmithSpan) {\n //update occurred before span start, return early data\n return;\n }\n\n const updatePayload = this.buildRunTreePayload(span, traceData);\n\n langSmithSpan.metadata = {\n ...langSmithSpan.metadata,\n ...updatePayload.metadata,\n };\n if (updatePayload.inputs != null) {\n langSmithSpan.inputs = updatePayload.inputs;\n }\n if (updatePayload.outputs != null) {\n langSmithSpan.outputs = updatePayload.outputs;\n }\n if (updatePayload.error != null) {\n langSmithSpan.error = updatePayload.error;\n }\n\n // Add new_token event for TTFT tracking on MODEL_GENERATION spans\n if (span.type === SpanType.MODEL_GENERATION) {\n const modelAttr = (span.attributes ?? {}) as ModelGenerationAttributes;\n if (modelAttr.completionStartTime !== undefined) {\n langSmithSpan.addEvent({\n name: 'new_token',\n time: modelAttr.completionStartTime.toISOString(),\n });\n }\n }\n\n // LangSmith accepts one update per run, so updates only mutate the local\n // RunTree and the single patch is sent on end with the final state.\n if (isEnd) {\n // End the span with the correct endTime\n if (span.endTime) {\n await langSmithSpan.end({ endTime: span.endTime.getTime() });\n } else {\n await langSmithSpan.end();\n }\n await langSmithSpan.patchRun();\n }\n }\n\n /**\n * Find LangSmith vendor metadata by walking up the span hierarchy and merging.\n * Metadata is merged from ancestors with child values taking precedence over parent values.\n *\n * TODO(2.0): Extract shared `findVendorMetadata()` to base TrackingExporter class\n * and reuse here and in LangfuseExporter.findLangfusePrompt()\n */\n private findLangsmithMetadata(\n traceData: LangSmithTraceData,\n span: AnyExportedSpan,\n ): LangSmithMetadataInput | undefined {\n // Collect metadata from all ancestors (current span first, then parents)\n const metadataChain: LangSmithMetadataInput[] = [];\n let currentSpanId: string | undefined = span.id;\n\n while (currentSpanId) {\n const providerMetadata = traceData.getMetadata({ spanId: currentSpanId });\n if (providerMetadata) {\n metadataChain.push(providerMetadata);\n }\n currentSpanId = traceData.getParentId({ spanId: currentSpanId });\n }\n\n if (metadataChain.length === 0) {\n return undefined;\n }\n\n // Merge from ancestors to current span (parent values first, child values override)\n const merged: LangSmithMetadataInput = {};\n for (let i = metadataChain.length - 1; i >= 0; i--) {\n const meta = metadataChain[i]!;\n if (meta.projectName !== undefined) merged.projectName = meta.projectName;\n if (meta.sessionId !== undefined) merged.sessionId = meta.sessionId;\n if (meta.sessionName !== undefined) merged.sessionName = meta.sessionName;\n }\n\n this.logger.debug(`${this.name}: merged vendor metadata from hierarchy`, {\n traceId: span.traceId,\n spanId: span.id,\n metadataKeys: Object.keys(merged),\n ancestorCount: metadataChain.length,\n });\n\n return merged;\n }\n\n /**\n * LangSmith groups runs into a Thread only by `session_id` / `thread_id` metadata and\n * needs it on every run of the trace. A child run follows the session its parent was\n * built with: sub-agents and Observational Memory run on derived threads, and their\n * own `threadId` would split the conversation into one-off Threads. The span's own\n * `sessionId` / `threadId` applies at the root, or when no ancestor carries a session.\n */\n private findSessionId(span: AnyExportedSpan, traceData: LangSmithTraceData): string | undefined {\n const inherited = span.isRootSpan ? undefined : traceData.getParent({ span })?.metadata?.session_id;\n if (typeof inherited === 'string' && inherited) return inherited;\n const own = span.metadata?.sessionId ?? span.metadata?.threadId;\n return typeof own === 'string' && own ? own : undefined;\n }\n\n private buildRunTreePayload(\n span: AnyExportedSpan,\n traceData: LangSmithTraceData,\n isNew = false,\n ): Partial<RunTreeConfig> {\n // Extract vendor metadata from span hierarchy\n const vendorMetadata = this.findLangsmithMetadata(traceData, span);\n\n // Build metadata, omitting the langsmith vendor key\n const spanMetadata = span.metadata ? omitKeys(span.metadata, ['langsmith']) : {};\n\n const payload: Partial<RunTreeConfig> & { metadata: KVMap } = {\n client: this.#client,\n metadata: {\n mastra_span_type: span.type,\n mastra_span_id: span.id,\n mastra_trace_id: span.traceId,\n ...spanMetadata,\n },\n };\n\n if (isNew) {\n payload.run_type = mapSpanType(span.type);\n payload.start_time = span.startTime.getTime();\n }\n\n // Add project name - vendor metadata takes precedence over config\n const projectName = vendorMetadata?.projectName ?? this.config.projectName;\n if (projectName) {\n payload.project_name = projectName;\n }\n\n // Explicit vendor sessionId wins; otherwise the Mastra conversation thread, so turns\n // on one memory thread land in one LangSmith Thread without extra setup.\n const sessionId = vendorMetadata?.sessionId ?? this.findSessionId(span, traceData);\n if (sessionId) {\n payload.metadata.session_id = sessionId;\n }\n if (vendorMetadata?.sessionName) {\n payload.metadata.session_name = vendorMetadata.sessionName;\n }\n\n // Add tags for root spans\n if (span.isRootSpan && span.tags?.length) {\n payload.tags = span.tags;\n }\n\n // Core span data\n if (span.input !== undefined) {\n payload.inputs = isKVMap(span.input) ? span.input : { input: span.input };\n }\n\n if (span.output !== undefined) {\n payload.outputs = isKVMap(span.output) ? span.output : { output: span.output };\n }\n\n const attributes = (span.attributes ?? {}) as Record<string, any>;\n\n if (span.type === SpanType.MODEL_GENERATION) {\n const modelAttr = attributes as ModelGenerationAttributes;\n\n // See: https://docs.langchain.com/langsmith/log-llm-trace\n if (modelAttr.model !== undefined) {\n // Note - this should map to a model name recognized by LangSmith\n // eg “gpt-4o-mini”, “claude-3-opus-20240307”, etc.\n payload.metadata.ls_model_name = modelAttr.model;\n }\n\n // Provider goes to metadata (if provided by attributes)\n if (modelAttr.provider !== undefined) {\n // Note - this should map to a provider name recognized by\n // LangSmith eg “openai”, “anthropic”, etc.\n payload.metadata.ls_provider = modelAttr.provider;\n }\n\n // Usage/token info goes to metrics\n payload.metadata.usage_metadata = formatUsageMetrics(modelAttr.usage);\n\n // Model parameters go to metadata\n if (modelAttr.parameters !== undefined) {\n payload.metadata.modelParameters = modelAttr.parameters;\n }\n\n // Other LLM attributes go to metadata\n const otherAttributes = omitKeys(attributes, [\n 'model',\n 'provider',\n 'usage',\n 'parameters',\n 'completionStartTime',\n 'timeToFirstTokenMs',\n ]);\n payload.metadata = {\n ...payload.metadata,\n ...otherAttributes,\n };\n } else {\n // For non-LLM spans, put all attributes in metadata\n payload.metadata = {\n ...payload.metadata,\n ...omitKeys(attributes, ['timeToFirstTokenMs']),\n };\n }\n\n // Handle errors\n if (span.errorInfo) {\n payload.error = span.errorInfo.message;\n payload.metadata.errorDetails = span.errorInfo;\n }\n\n return payload;\n }\n}\n","/**\n * LangSmith Tracing Options Helpers\n *\n * These helpers integrate with the `buildTracingOptions` pattern from\n * `@mastra/observability` to add LangSmith-specific tracing features.\n *\n * @example\n * ```typescript\n * import { buildTracingOptions } from '@mastra/observability';\n * import { withLangsmithMetadata } from '@mastra/langsmith';\n *\n * const agent = new Agent({\n * defaultGenerateOptions: {\n * tracingOptions: buildTracingOptions(\n * withLangsmithMetadata({ projectName: 'my-project' })\n * ),\n * },\n * });\n * ```\n */\n\nimport type { TracingOptionsUpdater } from '@mastra/observability';\n\n/**\n * LangSmith vendor metadata that can be passed via span metadata.\n * These fields are extracted by the LangSmith exporter and used\n * to override default configuration on a per-span basis.\n */\nexport interface LangSmithMetadataInput {\n /**\n * Override the project name for this span and its children.\n * This allows dynamically routing traces to different LangSmith projects.\n */\n projectName?: string;\n /**\n * Session ID for grouping related traces in LangSmith.\n */\n sessionId?: string;\n /**\n * Session name for display in LangSmith.\n */\n sessionName?: string;\n}\n\n/**\n * Adds LangSmith metadata to the tracing options.\n *\n * The metadata is added under `metadata.langsmith` and allows you to:\n * - Route traces to different LangSmith projects via `projectName`\n * - Group traces by session via `sessionId` and `sessionName`\n *\n * @param metadata - The LangSmith metadata to add\n * @returns A TracingOptionsUpdater function for use with `buildTracingOptions`\n *\n * @example\n * ```typescript\n * import { buildTracingOptions } from '@mastra/observability';\n * import { withLangsmithMetadata } from '@mastra/langsmith';\n *\n * // Route traces to a specific project\n * const tracingOptions = buildTracingOptions(\n * withLangsmithMetadata({ projectName: 'customer-support' }),\n * );\n *\n * // Or set multiple fields\n * const tracingOptions = buildTracingOptions(\n * withLangsmithMetadata({\n * projectName: 'my-project',\n * sessionId: 'session-123',\n * }),\n * );\n *\n * // Use in agent config\n * const agent = new Agent({\n * name: 'support-agent',\n * model: openai('gpt-4o'),\n * defaultGenerateOptions: {\n * tracingOptions: buildTracingOptions(\n * withLangsmithMetadata({ projectName: 'support-traces' })\n * ),\n * },\n * });\n * ```\n */\nexport function withLangsmithMetadata(metadata: LangSmithMetadataInput): TracingOptionsUpdater {\n return opts => ({\n ...opts,\n metadata: {\n ...opts.metadata,\n langsmith: {\n ...(opts.metadata?.langsmith as Record<string, unknown>),\n ...metadata,\n },\n },\n });\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/langsmith",
3
- "version": "1.4.0",
3
+ "version": "1.4.1-alpha.1",
4
4
  "description": "Langsmith observability provider for Mastra - includes tracing and future observability features",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -23,8 +23,8 @@
23
23
  },
24
24
  "license": "Apache-2.0",
25
25
  "dependencies": {
26
- "langsmith": "^0.8.12",
27
- "@mastra/observability": "1.22.0"
26
+ "langsmith": "^0.11.0",
27
+ "@mastra/observability": "1.23.0-alpha.1"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@ai-sdk/openai": "^2.0.136",
@@ -38,8 +38,8 @@
38
38
  "zod": "^4.6.7",
39
39
  "@internal/lint": "0.0.146",
40
40
  "@internal/types-builder": "0.0.121",
41
- "@observability/test-utils": "0.1.1",
42
- "@mastra/core": "1.80.0"
41
+ "@mastra/core": "1.81.0-alpha.2",
42
+ "@observability/test-utils": "0.1.1"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "@mastra/core": ">=1.16.0-0 <2.0.0-0"