@mastra/langsmith 1.3.29-alpha.1 → 1.4.0-alpha.2

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
@@ -1,9 +1,10 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let crypto = require("crypto");
2
3
  let _mastra_core_observability = require("@mastra/core/observability");
3
4
  let _mastra_core_utils = require("@mastra/core/utils");
4
5
  let _mastra_observability = require("@mastra/observability");
5
6
  let langsmith = require("langsmith");
6
- //#region src/metrics.ts
7
+ // src/metrics.ts
7
8
  /**
8
9
  * Formats UsageStats to LangSmith's expected metric format.
9
10
  */
@@ -42,9 +43,37 @@ function formatUsageMetrics(usage) {
42
43
  };
43
44
  return metrics;
44
45
  }
45
- //#endregion
46
- //#region src/tracing.ts
46
+ // src/tracing.ts
47
+ /**
48
+ * LangSmith Exporter for Mastra Tracing
49
+ *
50
+ * This exporter sends observability data to LangSmith
51
+ * Root spans become top-level LangSmith RunTrees (no trace wrapper).
52
+ * Events are handled as zero-duration RunTrees with matching start/end times.
53
+ */
47
54
  const DEFAULT_RUN_ID_CACHE_MAX_ENTRIES = 1e4;
55
+ /** Waits between LangSmith lookups for a trace another process exported (ingestion takes ~5-8 s). */
56
+ const SCORE_LOOKUP_RETRY_DELAYS_MS = [
57
+ 2e3,
58
+ 3e3,
59
+ 5e3
60
+ ];
61
+ /** Mastra trace ids are hex (`isValidTraceId` in @mastra/observability); anything else cannot match a run. */
62
+ const HEX_TRACE_ID = /^[0-9a-f]{1,32}$/i;
63
+ /** UUID v5 namespace for Mastra → LangSmith run ids. Changing it orphans existing runs from future scores. */
64
+ const RUN_ID_NAMESPACE = Buffer.from("a6f3c3f06b0e4a1e9f4c7e1b2d3c4f5a", "hex");
65
+ /**
66
+ * LangSmith run id for a Mastra span id (UUID v5). Every run, root or not, is named
67
+ * after its own span, so a span-level score can be routed from any process without
68
+ * remembering the ids LangSmith would otherwise allocate.
69
+ */
70
+ function langsmithRunIdFor(spanId) {
71
+ const hash = (0, crypto.createHash)("sha1").update(RUN_ID_NAMESPACE).update(spanId).digest();
72
+ hash[6] = hash[6] & 15 | 80;
73
+ hash[8] = hash[8] & 63 | 128;
74
+ const hex = hash.subarray(0, 16).toString("hex");
75
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
76
+ }
48
77
  const DEFAULT_SPAN_TYPE = "chain";
49
78
  const SPAN_TYPE_EXCEPTIONS = {
50
79
  [_mastra_core_observability.SpanType.MODEL_GENERATION]: "llm",
@@ -64,18 +93,21 @@ var LangSmithExporter = class extends _mastra_observability.TrackingExporter {
64
93
  name = "langsmith";
65
94
  #client;
66
95
  /**
67
- * Maps Mastra `span.id` to the runId LangSmith allocated when the corresponding
68
- * RunTree was created. LangSmith's `createFeedback` requires the LangSmith runId,
69
- * not the Mastra span id, so scores submitted via `onScoreEvent` look up the
70
- * LangSmith runId here before calling the API.
96
+ * Maps a Mastra trace id to the first root run this process exported for it, and the
97
+ * project it went to. A fast path for scores: a trace-level score on a miss falls back
98
+ * to asking LangSmith for the root run tagged with that `mastra_trace_id`.
71
99
  *
72
- * Bounded LRU keyed by Mastra span id — entries are evicted oldest-first when
100
+ * Bounded LRU keyed by Mastra trace id — entries are evicted oldest-first when
73
101
  * size exceeds `#runIdCacheMaxEntries` so the cache cannot grow without bound.
74
102
  */
75
- #langsmithRunIdBySpanId = /* @__PURE__ */ new Map();
103
+ #rootRunByTraceId = /* @__PURE__ */ new Map();
76
104
  /** Manual-mode client flushes, chained so an overlapping flush waits for the earlier upload */
77
105
  #manualFlushChain = Promise.resolve();
78
106
  #runIdCacheMaxEntries;
107
+ /** Project runs go to unless `withLangsmithMetadata({ projectName })` says otherwise. */
108
+ #defaultProjectName;
109
+ /** LangSmith project (session) ids by name: `createFeedback` requires one and run queries are scoped to one. */
110
+ #projectIdByName = /* @__PURE__ */ new Map();
79
111
  constructor(config = {}) {
80
112
  const apiKey = config.apiKey ?? process.env.LANGSMITH_API_KEY;
81
113
  super({
@@ -83,28 +115,29 @@ var LangSmithExporter = class extends _mastra_observability.TrackingExporter {
83
115
  apiKey
84
116
  });
85
117
  this.#runIdCacheMaxEntries = Math.max(1, config.runIdCacheMaxEntries ?? DEFAULT_RUN_ID_CACHE_MAX_ENTRIES);
118
+ this.#defaultProjectName = config.projectName ?? process.env.LANGSMITH_PROJECT ?? process.env.LANGCHAIN_PROJECT ?? "default";
86
119
  if (!apiKey) {
87
120
  this.setDisabled(`Missing required credentials (apiKey: ${!!apiKey})`);
88
121
  return;
89
122
  }
90
123
  this.#client = config.client ?? new langsmith.Client(this.config);
91
124
  }
92
- /** Look up the LangSmith runId for a Mastra span id, refreshing its LRU position on hit. */
93
- #getLangsmithRunId(spanId) {
94
- const runId = this.#langsmithRunIdBySpanId.get(spanId);
95
- if (runId === void 0) return void 0;
96
- this.#langsmithRunIdBySpanId.delete(spanId);
97
- this.#langsmithRunIdBySpanId.set(spanId, runId);
98
- return runId;
125
+ /** Look up the root run for a Mastra trace id, refreshing its LRU position on hit. */
126
+ #getRootRun(traceId) {
127
+ const root = this.#rootRunByTraceId.get(traceId);
128
+ if (root === void 0) return void 0;
129
+ this.#rootRunByTraceId.delete(traceId);
130
+ this.#rootRunByTraceId.set(traceId, root);
131
+ return root;
99
132
  }
100
- /** Remember the LangSmith runId for a Mastra span id, evicting oldest entries when full. */
101
- #rememberLangsmithRunId(spanId, runId) {
102
- if (this.#langsmithRunIdBySpanId.has(spanId)) this.#langsmithRunIdBySpanId.delete(spanId);
103
- this.#langsmithRunIdBySpanId.set(spanId, runId);
104
- while (this.#langsmithRunIdBySpanId.size > this.#runIdCacheMaxEntries) {
105
- const oldest = this.#langsmithRunIdBySpanId.keys().next().value;
133
+ /** Remember the first root run of a Mastra trace, evicting oldest entries when full. */
134
+ #rememberRootRun(traceId, root) {
135
+ if (this.#rootRunByTraceId.has(traceId)) return;
136
+ this.#rootRunByTraceId.set(traceId, root);
137
+ while (this.#rootRunByTraceId.size > this.#runIdCacheMaxEntries) {
138
+ const oldest = this.#rootRunByTraceId.keys().next().value;
106
139
  if (oldest === void 0) break;
107
- this.#langsmithRunIdBySpanId.delete(oldest);
140
+ this.#rootRunByTraceId.delete(oldest);
108
141
  }
109
142
  }
110
143
  /**
@@ -135,19 +168,16 @@ var LangSmithExporter = class extends _mastra_observability.TrackingExporter {
135
168
  try {
136
169
  await this.#flushClient();
137
170
  } finally {
138
- this.#langsmithRunIdBySpanId.clear();
171
+ this.#rootRunByTraceId.clear();
139
172
  }
140
173
  }
141
174
  async onScoreEvent(event) {
142
175
  if (!this.#client) return;
143
176
  const { score } = event;
144
- if (!score.spanId) {
145
- this.logger.warn("LangSmith exporter: dropping score with no spanId; trace-level scoring is not yet supported", { scorerId: score.scorerId });
146
- return;
147
- }
148
- const langsmithRunId = this.#getLangsmithRunId(score.spanId);
177
+ const root = score.spanId ? score.traceId ? this.#getRootRun(score.traceId) : void 0 : score.traceId ? await this.#resolveRootRun(score.traceId) : void 0;
178
+ const langsmithRunId = score.spanId ? langsmithRunIdFor(score.spanId) : root?.runId;
149
179
  if (!langsmithRunId) {
150
- this.logger.warn("LangSmith exporter: dropping score for a span that was not previously emitted to LangSmith (span_started must be processed before submitting a score for it)", {
180
+ this.logger.warn("LangSmith exporter: dropping score; no LangSmith run found for the target (score has neither traceId nor spanId, the trace is not ingested yet, or it was exported by an older @mastra/langsmith)", {
151
181
  traceId: score.traceId,
152
182
  spanId: score.spanId,
153
183
  scorerId: score.scorerId
@@ -155,8 +185,10 @@ var LangSmithExporter = class extends _mastra_observability.TrackingExporter {
155
185
  return;
156
186
  }
157
187
  const key = score.scorerName ?? score.scorerId;
188
+ const sessionId = root?.sessionId ?? await this.#getProjectId(root?.projectName ?? this.#defaultProjectName);
158
189
  try {
159
190
  await this.#client.createFeedback(langsmithRunId, key, {
191
+ ...sessionId ? { sessionId } : {},
160
192
  score: score.score,
161
193
  ...score.reason ? { comment: score.reason } : {},
162
194
  feedbackId: score.scoreId,
@@ -176,6 +208,63 @@ var LangSmithExporter = class extends _mastra_observability.TrackingExporter {
176
208
  });
177
209
  }
178
210
  }
211
+ /**
212
+ * A trace-level score goes to the trace's first root run. This process remembers the
213
+ * roots it exported; otherwise LangSmith is asked for the earliest root run tagged with
214
+ * the Mastra trace id.
215
+ */
216
+ async #resolveRootRun(traceId) {
217
+ const known = this.#getRootRun(traceId);
218
+ if (known) return known;
219
+ if (!HEX_TRACE_ID.test(traceId)) return void 0;
220
+ for (let attempt = 0;; attempt++) {
221
+ const found = await this.#findRootRun(traceId);
222
+ if (found) {
223
+ this.#rememberRootRun(traceId, found);
224
+ return found;
225
+ }
226
+ const delay = SCORE_LOOKUP_RETRY_DELAYS_MS[attempt];
227
+ if (delay === void 0) return void 0;
228
+ await new Promise((resolve) => setTimeout(resolve, delay));
229
+ }
230
+ }
231
+ async #findRootRun(traceId) {
232
+ const projectId = await this.#getProjectId(this.#defaultProjectName);
233
+ if (!projectId) return void 0;
234
+ try {
235
+ const runs = this.#client.listRuns({
236
+ projectId,
237
+ isRoot: true,
238
+ filter: `has(metadata, '${JSON.stringify({ mastra_trace_id: traceId })}')`,
239
+ select: ["id", "session_id"],
240
+ order: "asc",
241
+ limit: 1
242
+ });
243
+ for await (const run of runs) return {
244
+ runId: run.id,
245
+ sessionId: run.session_id
246
+ };
247
+ } catch (err) {
248
+ this.logger.debug("LangSmith exporter: root run lookup failed", {
249
+ traceId,
250
+ error: err
251
+ });
252
+ }
253
+ }
254
+ #getProjectId(projectName) {
255
+ let projectId = this.#projectIdByName.get(projectName);
256
+ if (!projectId) {
257
+ projectId = Promise.resolve().then(() => this.#client.readProject({ projectName })).then((project) => project.id).catch((err) => {
258
+ this.logger.debug("LangSmith exporter: could not resolve the project id", {
259
+ projectName,
260
+ error: err
261
+ });
262
+ this.#projectIdByName.delete(projectName);
263
+ });
264
+ this.#projectIdByName.set(projectName, projectId);
265
+ }
266
+ return projectId;
267
+ }
179
268
  skipBuildRootTask = true;
180
269
  async _buildRoot(_args) {
181
270
  throw new Error("Method not implemented.");
@@ -184,12 +273,17 @@ var LangSmithExporter = class extends _mastra_observability.TrackingExporter {
184
273
  const { span, traceData } = args;
185
274
  const parent = span.isRootSpan ? void 0 : traceData.getParent(args);
186
275
  if (!span.isRootSpan && !parent) return;
276
+ const runId = langsmithRunIdFor(span.id);
187
277
  const payload = {
188
278
  name: span.name,
279
+ id: runId,
189
280
  ...this.buildRunTreePayload(span, traceData, true)
190
281
  };
191
282
  const langSmithSpan = span.isRootSpan ? new langsmith.RunTree(payload) : parent.createChild(payload);
192
- if (langSmithSpan.id) this.#rememberLangsmithRunId(span.id, langSmithSpan.id);
283
+ if (span.isRootSpan) this.#rememberRootRun(span.traceId, {
284
+ runId,
285
+ projectName: payload.project_name ?? this.#defaultProjectName
286
+ });
193
287
  await langSmithSpan.postRun();
194
288
  return langSmithSpan;
195
289
  }
@@ -278,6 +372,19 @@ var LangSmithExporter = class extends _mastra_observability.TrackingExporter {
278
372
  });
279
373
  return merged;
280
374
  }
375
+ /**
376
+ * LangSmith groups runs into a Thread only by `session_id` / `thread_id` metadata and
377
+ * needs it on every run of the trace. A child run follows the session its parent was
378
+ * built with: sub-agents and Observational Memory run on derived threads, and their
379
+ * own `threadId` would split the conversation into one-off Threads. The span's own
380
+ * `sessionId` / `threadId` applies at the root, or when no ancestor carries a session.
381
+ */
382
+ findSessionId(span, traceData) {
383
+ const inherited = span.isRootSpan ? void 0 : traceData.getParent({ span })?.metadata?.session_id;
384
+ if (typeof inherited === "string" && inherited) return inherited;
385
+ const own = span.metadata?.sessionId ?? span.metadata?.threadId;
386
+ return typeof own === "string" && own ? own : void 0;
387
+ }
281
388
  buildRunTreePayload(span, traceData, isNew = false) {
282
389
  const vendorMetadata = this.findLangsmithMetadata(traceData, span);
283
390
  const spanMetadata = span.metadata ? (0, _mastra_core_utils.omitKeys)(span.metadata, ["langsmith"]) : {};
@@ -285,6 +392,8 @@ var LangSmithExporter = class extends _mastra_observability.TrackingExporter {
285
392
  client: this.#client,
286
393
  metadata: {
287
394
  mastra_span_type: span.type,
395
+ mastra_span_id: span.id,
396
+ mastra_trace_id: span.traceId,
288
397
  ...spanMetadata
289
398
  }
290
399
  };
@@ -294,7 +403,8 @@ var LangSmithExporter = class extends _mastra_observability.TrackingExporter {
294
403
  }
295
404
  const projectName = vendorMetadata?.projectName ?? this.config.projectName;
296
405
  if (projectName) payload.project_name = projectName;
297
- if (vendorMetadata?.sessionId) payload.metadata.session_id = vendorMetadata.sessionId;
406
+ const sessionId = vendorMetadata?.sessionId ?? this.findSessionId(span, traceData);
407
+ if (sessionId) payload.metadata.session_id = sessionId;
298
408
  if (vendorMetadata?.sessionName) payload.metadata.session_name = vendorMetadata.sessionName;
299
409
  if (span.isRootSpan && span.tags?.length) payload.tags = span.tags;
300
410
  if (span.input !== void 0) payload.inputs = isKVMap(span.input) ? span.input : { input: span.input };
@@ -328,8 +438,7 @@ var LangSmithExporter = class extends _mastra_observability.TrackingExporter {
328
438
  return payload;
329
439
  }
330
440
  };
331
- //#endregion
332
- //#region src/helpers.ts
441
+ // src/helpers.ts
333
442
  /**
334
443
  * Adds LangSmith metadata to the tracing options.
335
444
  *
@@ -382,8 +491,8 @@ function withLangsmithMetadata(metadata) {
382
491
  }
383
492
  });
384
493
  }
385
- //#endregion
386
494
  exports.LangSmithExporter = LangSmithExporter;
495
+ exports.langsmithRunIdFor = langsmithRunIdFor;
387
496
  exports.withLangsmithMetadata = withLangsmithMetadata;
388
497
 
389
498
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["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 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 `spanId → langsmithRunId` mappings to retain for resolving\n * `onScoreEvent` lookups. Older entries are evicted in LRU order when the cap\n * is exceeded so long-running processes do not grow unbounded.\n * Defaults to 10000.\n */\n runIdCacheMaxEntries?: number;\n}\n\nconst DEFAULT_RUN_ID_CACHE_MAX_ENTRIES = 10_000;\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 Mastra `span.id` to the runId LangSmith allocated when the corresponding\n * RunTree was created. LangSmith's `createFeedback` requires the LangSmith runId,\n * not the Mastra span id, so scores submitted via `onScoreEvent` look up the\n * LangSmith runId here before calling the API.\n *\n * Bounded LRU keyed by Mastra span id — entries are evicted oldest-first when\n * size exceeds `#runIdCacheMaxEntries` so the cache cannot grow without bound.\n */\n #langsmithRunIdBySpanId = new Map<string, string>();\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\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\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 LangSmith runId for a Mastra span id, refreshing its LRU position on hit. */\n #getLangsmithRunId(spanId: string): string | undefined {\n const runId = this.#langsmithRunIdBySpanId.get(spanId);\n if (runId === undefined) return undefined;\n // Re-insert to mark as most-recently-used (Map preserves insertion order).\n this.#langsmithRunIdBySpanId.delete(spanId);\n this.#langsmithRunIdBySpanId.set(spanId, runId);\n return runId;\n }\n\n /** Remember the LangSmith runId for a Mastra span id, evicting oldest entries when full. */\n #rememberLangsmithRunId(spanId: string, runId: string): void {\n if (this.#langsmithRunIdBySpanId.has(spanId)) {\n this.#langsmithRunIdBySpanId.delete(spanId);\n }\n this.#langsmithRunIdBySpanId.set(spanId, runId);\n while (this.#langsmithRunIdBySpanId.size > this.#runIdCacheMaxEntries) {\n const oldest = this.#langsmithRunIdBySpanId.keys().next().value;\n if (oldest === undefined) break;\n this.#langsmithRunIdBySpanId.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.#langsmithRunIdBySpanId.clear();\n }\n }\n\n async onScoreEvent(event: ScoreEvent): Promise<void> {\n if (!this.#client) return;\n\n const { score } = event;\n if (!score.spanId) {\n this.logger.warn('LangSmith exporter: dropping score with no spanId; trace-level scoring is not yet supported', {\n scorerId: score.scorerId,\n });\n return;\n }\n\n const langsmithRunId = this.#getLangsmithRunId(score.spanId);\n if (!langsmithRunId) {\n this.logger.warn(\n 'LangSmith exporter: dropping score for a span that was not previously emitted to LangSmith ' +\n '(span_started must be processed before submitting a score for it)',\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\n try {\n await this.#client.createFeedback(langsmithRunId, key, {\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 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 payload = {\n name: span.name,\n ...this.buildRunTreePayload(span, traceData, true),\n };\n\n const langSmithSpan = span.isRootSpan ? new RunTree(payload) : parent!.createChild(payload);\n\n if (langSmithSpan.id) {\n this.#rememberLangsmithRunId(span.id, langSmithSpan.id);\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 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 ...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 // Add session info to metadata if provided via vendor metadata\n if (vendorMetadata?.sessionId) {\n payload.metadata.session_id = vendorMetadata.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, ['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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"}
package/dist/index.js CHANGED
@@ -1,8 +1,9 @@
1
+ import { createHash } from "crypto";
1
2
  import { SpanType } from "@mastra/core/observability";
2
3
  import { omitKeys } from "@mastra/core/utils";
3
4
  import { TrackingExporter } from "@mastra/observability";
4
5
  import { Client, RunTree } from "langsmith";
5
- //#region src/metrics.ts
6
+ // src/metrics.ts
6
7
  /**
7
8
  * Formats UsageStats to LangSmith's expected metric format.
8
9
  */
@@ -41,9 +42,37 @@ function formatUsageMetrics(usage) {
41
42
  };
42
43
  return metrics;
43
44
  }
44
- //#endregion
45
- //#region src/tracing.ts
45
+ // src/tracing.ts
46
+ /**
47
+ * LangSmith Exporter for Mastra Tracing
48
+ *
49
+ * This exporter sends observability data to LangSmith
50
+ * Root spans become top-level LangSmith RunTrees (no trace wrapper).
51
+ * Events are handled as zero-duration RunTrees with matching start/end times.
52
+ */
46
53
  const DEFAULT_RUN_ID_CACHE_MAX_ENTRIES = 1e4;
54
+ /** Waits between LangSmith lookups for a trace another process exported (ingestion takes ~5-8 s). */
55
+ const SCORE_LOOKUP_RETRY_DELAYS_MS = [
56
+ 2e3,
57
+ 3e3,
58
+ 5e3
59
+ ];
60
+ /** Mastra trace ids are hex (`isValidTraceId` in @mastra/observability); anything else cannot match a run. */
61
+ const HEX_TRACE_ID = /^[0-9a-f]{1,32}$/i;
62
+ /** UUID v5 namespace for Mastra → LangSmith run ids. Changing it orphans existing runs from future scores. */
63
+ const RUN_ID_NAMESPACE = Buffer.from("a6f3c3f06b0e4a1e9f4c7e1b2d3c4f5a", "hex");
64
+ /**
65
+ * LangSmith run id for a Mastra span id (UUID v5). Every run, root or not, is named
66
+ * after its own span, so a span-level score can be routed from any process without
67
+ * remembering the ids LangSmith would otherwise allocate.
68
+ */
69
+ function langsmithRunIdFor(spanId) {
70
+ const hash = createHash("sha1").update(RUN_ID_NAMESPACE).update(spanId).digest();
71
+ hash[6] = hash[6] & 15 | 80;
72
+ hash[8] = hash[8] & 63 | 128;
73
+ const hex = hash.subarray(0, 16).toString("hex");
74
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
75
+ }
47
76
  const DEFAULT_SPAN_TYPE = "chain";
48
77
  const SPAN_TYPE_EXCEPTIONS = {
49
78
  [SpanType.MODEL_GENERATION]: "llm",
@@ -63,18 +92,21 @@ var LangSmithExporter = class extends TrackingExporter {
63
92
  name = "langsmith";
64
93
  #client;
65
94
  /**
66
- * Maps Mastra `span.id` to the runId LangSmith allocated when the corresponding
67
- * RunTree was created. LangSmith's `createFeedback` requires the LangSmith runId,
68
- * not the Mastra span id, so scores submitted via `onScoreEvent` look up the
69
- * LangSmith runId here before calling the API.
95
+ * Maps a Mastra trace id to the first root run this process exported for it, and the
96
+ * project it went to. A fast path for scores: a trace-level score on a miss falls back
97
+ * to asking LangSmith for the root run tagged with that `mastra_trace_id`.
70
98
  *
71
- * Bounded LRU keyed by Mastra span id — entries are evicted oldest-first when
99
+ * Bounded LRU keyed by Mastra trace id — entries are evicted oldest-first when
72
100
  * size exceeds `#runIdCacheMaxEntries` so the cache cannot grow without bound.
73
101
  */
74
- #langsmithRunIdBySpanId = /* @__PURE__ */ new Map();
102
+ #rootRunByTraceId = /* @__PURE__ */ new Map();
75
103
  /** Manual-mode client flushes, chained so an overlapping flush waits for the earlier upload */
76
104
  #manualFlushChain = Promise.resolve();
77
105
  #runIdCacheMaxEntries;
106
+ /** Project runs go to unless `withLangsmithMetadata({ projectName })` says otherwise. */
107
+ #defaultProjectName;
108
+ /** LangSmith project (session) ids by name: `createFeedback` requires one and run queries are scoped to one. */
109
+ #projectIdByName = /* @__PURE__ */ new Map();
78
110
  constructor(config = {}) {
79
111
  const apiKey = config.apiKey ?? process.env.LANGSMITH_API_KEY;
80
112
  super({
@@ -82,28 +114,29 @@ var LangSmithExporter = class extends TrackingExporter {
82
114
  apiKey
83
115
  });
84
116
  this.#runIdCacheMaxEntries = Math.max(1, config.runIdCacheMaxEntries ?? DEFAULT_RUN_ID_CACHE_MAX_ENTRIES);
117
+ this.#defaultProjectName = config.projectName ?? process.env.LANGSMITH_PROJECT ?? process.env.LANGCHAIN_PROJECT ?? "default";
85
118
  if (!apiKey) {
86
119
  this.setDisabled(`Missing required credentials (apiKey: ${!!apiKey})`);
87
120
  return;
88
121
  }
89
122
  this.#client = config.client ?? new Client(this.config);
90
123
  }
91
- /** Look up the LangSmith runId for a Mastra span id, refreshing its LRU position on hit. */
92
- #getLangsmithRunId(spanId) {
93
- const runId = this.#langsmithRunIdBySpanId.get(spanId);
94
- if (runId === void 0) return void 0;
95
- this.#langsmithRunIdBySpanId.delete(spanId);
96
- this.#langsmithRunIdBySpanId.set(spanId, runId);
97
- return runId;
124
+ /** Look up the root run for a Mastra trace id, refreshing its LRU position on hit. */
125
+ #getRootRun(traceId) {
126
+ const root = this.#rootRunByTraceId.get(traceId);
127
+ if (root === void 0) return void 0;
128
+ this.#rootRunByTraceId.delete(traceId);
129
+ this.#rootRunByTraceId.set(traceId, root);
130
+ return root;
98
131
  }
99
- /** Remember the LangSmith runId for a Mastra span id, evicting oldest entries when full. */
100
- #rememberLangsmithRunId(spanId, runId) {
101
- if (this.#langsmithRunIdBySpanId.has(spanId)) this.#langsmithRunIdBySpanId.delete(spanId);
102
- this.#langsmithRunIdBySpanId.set(spanId, runId);
103
- while (this.#langsmithRunIdBySpanId.size > this.#runIdCacheMaxEntries) {
104
- const oldest = this.#langsmithRunIdBySpanId.keys().next().value;
132
+ /** Remember the first root run of a Mastra trace, evicting oldest entries when full. */
133
+ #rememberRootRun(traceId, root) {
134
+ if (this.#rootRunByTraceId.has(traceId)) return;
135
+ this.#rootRunByTraceId.set(traceId, root);
136
+ while (this.#rootRunByTraceId.size > this.#runIdCacheMaxEntries) {
137
+ const oldest = this.#rootRunByTraceId.keys().next().value;
105
138
  if (oldest === void 0) break;
106
- this.#langsmithRunIdBySpanId.delete(oldest);
139
+ this.#rootRunByTraceId.delete(oldest);
107
140
  }
108
141
  }
109
142
  /**
@@ -134,19 +167,16 @@ var LangSmithExporter = class extends TrackingExporter {
134
167
  try {
135
168
  await this.#flushClient();
136
169
  } finally {
137
- this.#langsmithRunIdBySpanId.clear();
170
+ this.#rootRunByTraceId.clear();
138
171
  }
139
172
  }
140
173
  async onScoreEvent(event) {
141
174
  if (!this.#client) return;
142
175
  const { score } = event;
143
- if (!score.spanId) {
144
- this.logger.warn("LangSmith exporter: dropping score with no spanId; trace-level scoring is not yet supported", { scorerId: score.scorerId });
145
- return;
146
- }
147
- const langsmithRunId = this.#getLangsmithRunId(score.spanId);
176
+ const root = score.spanId ? score.traceId ? this.#getRootRun(score.traceId) : void 0 : score.traceId ? await this.#resolveRootRun(score.traceId) : void 0;
177
+ const langsmithRunId = score.spanId ? langsmithRunIdFor(score.spanId) : root?.runId;
148
178
  if (!langsmithRunId) {
149
- this.logger.warn("LangSmith exporter: dropping score for a span that was not previously emitted to LangSmith (span_started must be processed before submitting a score for it)", {
179
+ this.logger.warn("LangSmith exporter: dropping score; no LangSmith run found for the target (score has neither traceId nor spanId, the trace is not ingested yet, or it was exported by an older @mastra/langsmith)", {
150
180
  traceId: score.traceId,
151
181
  spanId: score.spanId,
152
182
  scorerId: score.scorerId
@@ -154,8 +184,10 @@ var LangSmithExporter = class extends TrackingExporter {
154
184
  return;
155
185
  }
156
186
  const key = score.scorerName ?? score.scorerId;
187
+ const sessionId = root?.sessionId ?? await this.#getProjectId(root?.projectName ?? this.#defaultProjectName);
157
188
  try {
158
189
  await this.#client.createFeedback(langsmithRunId, key, {
190
+ ...sessionId ? { sessionId } : {},
159
191
  score: score.score,
160
192
  ...score.reason ? { comment: score.reason } : {},
161
193
  feedbackId: score.scoreId,
@@ -175,6 +207,63 @@ var LangSmithExporter = class extends TrackingExporter {
175
207
  });
176
208
  }
177
209
  }
210
+ /**
211
+ * A trace-level score goes to the trace's first root run. This process remembers the
212
+ * roots it exported; otherwise LangSmith is asked for the earliest root run tagged with
213
+ * the Mastra trace id.
214
+ */
215
+ async #resolveRootRun(traceId) {
216
+ const known = this.#getRootRun(traceId);
217
+ if (known) return known;
218
+ if (!HEX_TRACE_ID.test(traceId)) return void 0;
219
+ for (let attempt = 0;; attempt++) {
220
+ const found = await this.#findRootRun(traceId);
221
+ if (found) {
222
+ this.#rememberRootRun(traceId, found);
223
+ return found;
224
+ }
225
+ const delay = SCORE_LOOKUP_RETRY_DELAYS_MS[attempt];
226
+ if (delay === void 0) return void 0;
227
+ await new Promise((resolve) => setTimeout(resolve, delay));
228
+ }
229
+ }
230
+ async #findRootRun(traceId) {
231
+ const projectId = await this.#getProjectId(this.#defaultProjectName);
232
+ if (!projectId) return void 0;
233
+ try {
234
+ const runs = this.#client.listRuns({
235
+ projectId,
236
+ isRoot: true,
237
+ filter: `has(metadata, '${JSON.stringify({ mastra_trace_id: traceId })}')`,
238
+ select: ["id", "session_id"],
239
+ order: "asc",
240
+ limit: 1
241
+ });
242
+ for await (const run of runs) return {
243
+ runId: run.id,
244
+ sessionId: run.session_id
245
+ };
246
+ } catch (err) {
247
+ this.logger.debug("LangSmith exporter: root run lookup failed", {
248
+ traceId,
249
+ error: err
250
+ });
251
+ }
252
+ }
253
+ #getProjectId(projectName) {
254
+ let projectId = this.#projectIdByName.get(projectName);
255
+ if (!projectId) {
256
+ projectId = Promise.resolve().then(() => this.#client.readProject({ projectName })).then((project) => project.id).catch((err) => {
257
+ this.logger.debug("LangSmith exporter: could not resolve the project id", {
258
+ projectName,
259
+ error: err
260
+ });
261
+ this.#projectIdByName.delete(projectName);
262
+ });
263
+ this.#projectIdByName.set(projectName, projectId);
264
+ }
265
+ return projectId;
266
+ }
178
267
  skipBuildRootTask = true;
179
268
  async _buildRoot(_args) {
180
269
  throw new Error("Method not implemented.");
@@ -183,12 +272,17 @@ var LangSmithExporter = class extends TrackingExporter {
183
272
  const { span, traceData } = args;
184
273
  const parent = span.isRootSpan ? void 0 : traceData.getParent(args);
185
274
  if (!span.isRootSpan && !parent) return;
275
+ const runId = langsmithRunIdFor(span.id);
186
276
  const payload = {
187
277
  name: span.name,
278
+ id: runId,
188
279
  ...this.buildRunTreePayload(span, traceData, true)
189
280
  };
190
281
  const langSmithSpan = span.isRootSpan ? new RunTree(payload) : parent.createChild(payload);
191
- if (langSmithSpan.id) this.#rememberLangsmithRunId(span.id, langSmithSpan.id);
282
+ if (span.isRootSpan) this.#rememberRootRun(span.traceId, {
283
+ runId,
284
+ projectName: payload.project_name ?? this.#defaultProjectName
285
+ });
192
286
  await langSmithSpan.postRun();
193
287
  return langSmithSpan;
194
288
  }
@@ -277,6 +371,19 @@ var LangSmithExporter = class extends TrackingExporter {
277
371
  });
278
372
  return merged;
279
373
  }
374
+ /**
375
+ * LangSmith groups runs into a Thread only by `session_id` / `thread_id` metadata and
376
+ * needs it on every run of the trace. A child run follows the session its parent was
377
+ * built with: sub-agents and Observational Memory run on derived threads, and their
378
+ * own `threadId` would split the conversation into one-off Threads. The span's own
379
+ * `sessionId` / `threadId` applies at the root, or when no ancestor carries a session.
380
+ */
381
+ findSessionId(span, traceData) {
382
+ const inherited = span.isRootSpan ? void 0 : traceData.getParent({ span })?.metadata?.session_id;
383
+ if (typeof inherited === "string" && inherited) return inherited;
384
+ const own = span.metadata?.sessionId ?? span.metadata?.threadId;
385
+ return typeof own === "string" && own ? own : void 0;
386
+ }
280
387
  buildRunTreePayload(span, traceData, isNew = false) {
281
388
  const vendorMetadata = this.findLangsmithMetadata(traceData, span);
282
389
  const spanMetadata = span.metadata ? omitKeys(span.metadata, ["langsmith"]) : {};
@@ -284,6 +391,8 @@ var LangSmithExporter = class extends TrackingExporter {
284
391
  client: this.#client,
285
392
  metadata: {
286
393
  mastra_span_type: span.type,
394
+ mastra_span_id: span.id,
395
+ mastra_trace_id: span.traceId,
287
396
  ...spanMetadata
288
397
  }
289
398
  };
@@ -293,7 +402,8 @@ var LangSmithExporter = class extends TrackingExporter {
293
402
  }
294
403
  const projectName = vendorMetadata?.projectName ?? this.config.projectName;
295
404
  if (projectName) payload.project_name = projectName;
296
- if (vendorMetadata?.sessionId) payload.metadata.session_id = vendorMetadata.sessionId;
405
+ const sessionId = vendorMetadata?.sessionId ?? this.findSessionId(span, traceData);
406
+ if (sessionId) payload.metadata.session_id = sessionId;
297
407
  if (vendorMetadata?.sessionName) payload.metadata.session_name = vendorMetadata.sessionName;
298
408
  if (span.isRootSpan && span.tags?.length) payload.tags = span.tags;
299
409
  if (span.input !== void 0) payload.inputs = isKVMap(span.input) ? span.input : { input: span.input };
@@ -327,8 +437,7 @@ var LangSmithExporter = class extends TrackingExporter {
327
437
  return payload;
328
438
  }
329
439
  };
330
- //#endregion
331
- //#region src/helpers.ts
440
+ // src/helpers.ts
332
441
  /**
333
442
  * Adds LangSmith metadata to the tracing options.
334
443
  *
@@ -381,7 +490,6 @@ function withLangsmithMetadata(metadata) {
381
490
  }
382
491
  });
383
492
  }
384
- //#endregion
385
- export { LangSmithExporter, withLangsmithMetadata };
493
+ export { LangSmithExporter, langsmithRunIdFor, withLangsmithMetadata };
386
494
 
387
495
  //# sourceMappingURL=index.js.map
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 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 `spanId → langsmithRunId` mappings to retain for resolving\n * `onScoreEvent` lookups. Older entries are evicted in LRU order when the cap\n * is exceeded so long-running processes do not grow unbounded.\n * Defaults to 10000.\n */\n runIdCacheMaxEntries?: number;\n}\n\nconst DEFAULT_RUN_ID_CACHE_MAX_ENTRIES = 10_000;\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 Mastra `span.id` to the runId LangSmith allocated when the corresponding\n * RunTree was created. LangSmith's `createFeedback` requires the LangSmith runId,\n * not the Mastra span id, so scores submitted via `onScoreEvent` look up the\n * LangSmith runId here before calling the API.\n *\n * Bounded LRU keyed by Mastra span id — entries are evicted oldest-first when\n * size exceeds `#runIdCacheMaxEntries` so the cache cannot grow without bound.\n */\n #langsmithRunIdBySpanId = new Map<string, string>();\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\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\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 LangSmith runId for a Mastra span id, refreshing its LRU position on hit. */\n #getLangsmithRunId(spanId: string): string | undefined {\n const runId = this.#langsmithRunIdBySpanId.get(spanId);\n if (runId === undefined) return undefined;\n // Re-insert to mark as most-recently-used (Map preserves insertion order).\n this.#langsmithRunIdBySpanId.delete(spanId);\n this.#langsmithRunIdBySpanId.set(spanId, runId);\n return runId;\n }\n\n /** Remember the LangSmith runId for a Mastra span id, evicting oldest entries when full. */\n #rememberLangsmithRunId(spanId: string, runId: string): void {\n if (this.#langsmithRunIdBySpanId.has(spanId)) {\n this.#langsmithRunIdBySpanId.delete(spanId);\n }\n this.#langsmithRunIdBySpanId.set(spanId, runId);\n while (this.#langsmithRunIdBySpanId.size > this.#runIdCacheMaxEntries) {\n const oldest = this.#langsmithRunIdBySpanId.keys().next().value;\n if (oldest === undefined) break;\n this.#langsmithRunIdBySpanId.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.#langsmithRunIdBySpanId.clear();\n }\n }\n\n async onScoreEvent(event: ScoreEvent): Promise<void> {\n if (!this.#client) return;\n\n const { score } = event;\n if (!score.spanId) {\n this.logger.warn('LangSmith exporter: dropping score with no spanId; trace-level scoring is not yet supported', {\n scorerId: score.scorerId,\n });\n return;\n }\n\n const langsmithRunId = this.#getLangsmithRunId(score.spanId);\n if (!langsmithRunId) {\n this.logger.warn(\n 'LangSmith exporter: dropping score for a span that was not previously emitted to LangSmith ' +\n '(span_started must be processed before submitting a score for it)',\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\n try {\n await this.#client.createFeedback(langsmithRunId, key, {\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 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 payload = {\n name: span.name,\n ...this.buildRunTreePayload(span, traceData, true),\n };\n\n const langSmithSpan = span.isRootSpan ? new RunTree(payload) : parent!.createChild(payload);\n\n if (langSmithSpan.id) {\n this.#rememberLangsmithRunId(span.id, langSmithSpan.id);\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 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 ...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 // Add session info to metadata if provided via vendor metadata\n if (vendorMetadata?.sessionId) {\n payload.metadata.session_id = vendorMetadata.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":";;;;;;;;AAuBA,SAAgB,mBAAmB,OAA2C;CAC5E,MAAM,UAAiC,CAAC;CAExC,IAAI,OAAO,gBAAgB,KAAA,GACzB,QAAQ,eAAe,MAAM;CAG/B,IAAI,OAAO,iBAAiB,KAAA,GAC1B,QAAQ,gBAAgB,MAAM;CAIhC,IAAI,QAAQ,iBAAiB,KAAA,KAAa,QAAQ,kBAAkB,KAAA,GAClE,QAAQ,eAAe,QAAQ,eAAe,QAAQ;CAGxD,IAAI,OAAO,eAAe,cAAc,KAAA,GACtC,QAAQ,uBAAuB;EAC7B,GAAI,QAAQ,wBAAwB,CAAC;EACrC,kBAAkB,MAAM,cAAc;CACxC;CAGF,IAAI,OAAO,cAAc,cAAc,KAAA,GACrC,QAAQ,sBAAsB;EAC5B,GAAI,QAAQ,uBAAuB,CAAC;EACpC,YAAY,MAAM,aAAa;CACjC;CAGF,IAAI,OAAO,cAAc,eAAe,KAAA,GACtC,QAAQ,sBAAsB;EAC5B,GAAI,QAAQ,uBAAuB,CAAC;EACpC,aAAa,MAAM,aAAa;CAClC;CAGF,IAAI,OAAO,cAAc,iBAAiB,KAAA,GACxC,QAAQ,sBAAsB;EAC5B,GAAI,QAAQ,uBAAuB,CAAC;EACpC,mBAAmB,MAAM,aAAa;CACxC;CAGF,IAAI,OAAO,cAAc,iBAAiB,KAAA,GACxC,QAAQ,sBAAsB;EAC5B,GAAI,QAAQ,uBAAuB,CAAC;EACpC,mBAAmB,MAAM,aAAa;CACxC;CAGF,IAAI,OAAO,cAAc,UAAU,KAAA,GACjC,QAAQ,sBAAsB;EAC5B,GAAI,QAAQ,uBAAuB,CAAC;EACpC,OAAO,MAAM,aAAa;CAC5B;CAGF,IAAI,OAAO,eAAe,UAAU,KAAA,GAClC,QAAQ,uBAAuB;EAC7B,GAAI,QAAQ,wBAAwB,CAAC;EACrC,OAAO,MAAM,cAAc;CAC7B;CAGF,OAAO;AACT;;;ACpDA,MAAM,mCAAmC;AASzC,MAAM,oBAAoB;AAG1B,MAAM,uBAA4E;EAC/E,SAAS,mBAAmB;EAC5B,SAAS,YAAY;EACrB,SAAS,gBAAgB;EACzB,SAAS,qBAAqB;EAC9B,SAAS,4BAA4B;EACrC,SAAS,sBAAsB;AAClC;AAGA,SAAS,YAAY,UAA8C;CACjE,OAAO,qBAAqB,aAAa;AAC3C;AAEA,SAAS,QAAQ,OAAgC;CAC/C,OAAO,SAAS,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,KAAK,EAAE,iBAAiB;AACnG;AAEA,IAAa,oBAAb,cAAuC,iBAMrC;CACA,OAAgB;CAChB;;;;;;;;;;CAWA,0CAA0B,IAAI,IAAoB;;CAElD,oBAAmC,QAAQ,QAAQ;CACnD;CAEA,YAAY,SAAkC,CAAC,GAAG;EAEhD,MAAM,SAAS,OAAO,UAAU,QAAQ,IAAI;EAE5C,MAAM;GACJ,GAAG;GACH;EACF,CAAC;EAED,KAAK,wBAAwB,KAAK,IAAI,GAAG,OAAO,wBAAwB,gCAAgC;EAExG,IAAI,CAAC,QAAQ;GACX,KAAK,YAAY,yCAAyC,CAAC,CAAC,OAAO,EAAE;GACrE;EACF;EAEA,KAAK,UAAU,OAAO,UAAU,IAAI,OAAO,KAAK,MAAM;CACxD;;CAGA,mBAAmB,QAAoC;EACrD,MAAM,QAAQ,KAAK,wBAAwB,IAAI,MAAM;EACrD,IAAI,UAAU,KAAA,GAAW,OAAO,KAAA;EAEhC,KAAK,wBAAwB,OAAO,MAAM;EAC1C,KAAK,wBAAwB,IAAI,QAAQ,KAAK;EAC9C,OAAO;CACT;;CAGA,wBAAwB,QAAgB,OAAqB;EAC3D,IAAI,KAAK,wBAAwB,IAAI,MAAM,GACzC,KAAK,wBAAwB,OAAO,MAAM;EAE5C,KAAK,wBAAwB,IAAI,QAAQ,KAAK;EAC9C,OAAO,KAAK,wBAAwB,OAAO,KAAK,uBAAuB;GACrE,MAAM,SAAS,KAAK,wBAAwB,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;GAC1D,IAAI,WAAW,KAAA,GAAW;GAC1B,KAAK,wBAAwB,OAAO,MAAM;EAC5C;CACF;;;;;;CAOA,MAAM,eAA8B;EAClC,IAAI,CAAC,KAAK,SAAS;EAInB,IADG,KAAK,QAAqD,mBAAmB,KAAK,OAAO,iBACvE;GAGnB,MAAM,SAAS,KAAK;GACpB,MAAM,QAAQ,KAAK,kBAAkB,WAAW,OAAO,MAAM,CAAC;GAC9D,KAAK,oBAAoB,MAAM,YAAY,CAAC,CAAC;GAC7C,MAAM;EACR,OACE,MAAM,KAAK,QAAQ,yBAAyB;CAEhD;;;;;;;CAQA,MAAyB,SAAwB;EAC/C,MAAM,KAAK,aAAa;CAC1B;;CAGA,MAAyB,gBAA+B;EACtD,IAAI;GACF,MAAM,KAAK,aAAa;EAC1B,UAAU;GACR,KAAK,wBAAwB,MAAM;EACrC;CACF;CAEA,MAAM,aAAa,OAAkC;EACnD,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,EAAE,UAAU;EAClB,IAAI,CAAC,MAAM,QAAQ;GACjB,KAAK,OAAO,KAAK,+FAA+F,EAC9G,UAAU,MAAM,SAClB,CAAC;GACD;EACF;EAEA,MAAM,iBAAiB,KAAK,mBAAmB,MAAM,MAAM;EAC3D,IAAI,CAAC,gBAAgB;GACnB,KAAK,OAAO,KACV,gKAEA;IACE,SAAS,MAAM;IACf,QAAQ,MAAM;IACd,UAAU,MAAM;GAClB,CACF;GACA;EACF;EAEA,MAAM,MAAM,MAAM,cAAc,MAAM;EAEtC,IAAI;GACF,MAAM,KAAK,QAAQ,eAAe,gBAAgB,KAAK;IACrD,OAAO,MAAM;IACb,GAAI,MAAM,SAAS,EAAE,SAAS,MAAM,OAAO,IAAI,CAAC;IAChD,YAAY,MAAM;IAClB,GAAI,MAAM,eAAe,EAAE,aAAa,MAAM,aAAa,IAAI,CAAC;IAChE,YAAY;KAIV,GAAI,MAAM,YAAY,CAAC;KACvB,UAAU,MAAM;KAChB,GAAI,MAAM,cAAc,EAAE,aAAa,MAAM,YAAY,IAAI,CAAC;IAChE;GACF,CAAC;EACH,SAAS,KAAK;GACZ,KAAK,OAAO,MAAM,iDAAiD;IACjE,OAAO;IACP,SAAS,MAAM;IACf,QAAQ,MAAM;IACd,UAAU,MAAM;GAClB,CAAC;EACH;CACF;CAEA,oBAAuC;CACvC,MAAyB,WAAW,OAGG;EACrC,MAAM,IAAI,MAAM,yBAAyB;CAC3C;CAEA,MAAyB,WAAW,MAGG;EACrC,MAAM,EAAE,MAAM,cAAc;EAE5B,MAAM,SAAS,KAAK,aAAa,KAAA,IAAY,UAAU,UAAU,IAAI;EAErE,IAAI,CAAC,KAAK,cAAc,CAAC,QAEvB;EAGF,MAAM,UAAU;GACd,MAAM,KAAK;GACX,GAAG,KAAK,oBAAoB,MAAM,WAAW,IAAI;EACnD;EAEA,MAAM,gBAAgB,KAAK,aAAa,IAAI,QAAQ,OAAO,IAAI,OAAQ,YAAY,OAAO;EAE1F,IAAI,cAAc,IAChB,KAAK,wBAAwB,KAAK,IAAI,cAAc,EAAE;EAGxD,MAAM,cAAc,QAAQ;EAC5B,OAAO;CACT;CAEA,MAAyB,YAAY,MAGG;EACtC,MAAM,gBAAgB,MAAM,KAAK,WAAW,IAAI;EAEhD,IAAI,CAAC,eAEH;EAIF,MAAM,cAAc,IAAI,EAAE,SAAS,KAAK,KAAK,UAAU,QAAQ,EAAE,CAAC;EAClE,MAAM,cAAc,SAAS;EAC7B,OAAO;CACT;CAEA,MAAyB,YAAY,MAA+E;EAClH,MAAM,KAAK,sBAAsB;GAAE,GAAG;GAAM,OAAO;EAAM,CAAC;CAC5D;CAEA,MAAyB,YAAY,MAA+E;EAClH,MAAM,KAAK,sBAAsB;GAAE,GAAG;GAAM,OAAO;EAAK,CAAC;CAC3D;CAEA,MAAyB,WAAW,MAIlB;EAChB,MAAM,EAAE,MAAM,WAAW;EACzB,KAAK,QAAQ,OAAO;EACpB,KAAK,WAAW;GACd,GAAG,KAAK;GACR,cAAc;EAChB;EACA,MAAM,KAAK,IAAI;EACf,MAAM,KAAK,SAAS;CACtB;CAEA,MAAc,sBAAsB,MAIlB;EAChB,MAAM,EAAE,MAAM,WAAW,UAAU;EAEnC,MAAM,gBAAgB,UAAU,QAAQ,EAAE,QAAQ,KAAK,GAAG,CAAC;EAC3D,IAAI,CAAC,eAEH;EAGF,MAAM,gBAAgB,KAAK,oBAAoB,MAAM,SAAS;EAE9D,cAAc,WAAW;GACvB,GAAG,cAAc;GACjB,GAAG,cAAc;EACnB;EACA,IAAI,cAAc,UAAU,MAC1B,cAAc,SAAS,cAAc;EAEvC,IAAI,cAAc,WAAW,MAC3B,cAAc,UAAU,cAAc;EAExC,IAAI,cAAc,SAAS,MACzB,cAAc,QAAQ,cAAc;EAItC,IAAI,KAAK,SAAS,SAAS,kBAAkB;GAC3C,MAAM,YAAa,KAAK,cAAc,CAAC;GACvC,IAAI,UAAU,wBAAwB,KAAA,GACpC,cAAc,SAAS;IACrB,MAAM;IACN,MAAM,UAAU,oBAAoB,YAAY;GAClD,CAAC;EAEL;EAIA,IAAI,OAAO;GAET,IAAI,KAAK,SACP,MAAM,cAAc,IAAI,EAAE,SAAS,KAAK,QAAQ,QAAQ,EAAE,CAAC;QAE3D,MAAM,cAAc,IAAI;GAE1B,MAAM,cAAc,SAAS;EAC/B;CACF;;;;;;;;CASA,sBACE,WACA,MACoC;EAEpC,MAAM,gBAA0C,CAAC;EACjD,IAAI,gBAAoC,KAAK;EAE7C,OAAO,eAAe;GACpB,MAAM,mBAAmB,UAAU,YAAY,EAAE,QAAQ,cAAc,CAAC;GACxE,IAAI,kBACF,cAAc,KAAK,gBAAgB;GAErC,gBAAgB,UAAU,YAAY,EAAE,QAAQ,cAAc,CAAC;EACjE;EAEA,IAAI,cAAc,WAAW,GAC3B;EAIF,MAAM,SAAiC,CAAC;EACxC,KAAK,IAAI,IAAI,cAAc,SAAS,GAAG,KAAK,GAAG,KAAK;GAClD,MAAM,OAAO,cAAc;GAC3B,IAAI,KAAK,gBAAgB,KAAA,GAAW,OAAO,cAAc,KAAK;GAC9D,IAAI,KAAK,cAAc,KAAA,GAAW,OAAO,YAAY,KAAK;GAC1D,IAAI,KAAK,gBAAgB,KAAA,GAAW,OAAO,cAAc,KAAK;EAChE;EAEA,KAAK,OAAO,MAAM,GAAG,KAAK,KAAK,0CAA0C;GACvE,SAAS,KAAK;GACd,QAAQ,KAAK;GACb,cAAc,OAAO,KAAK,MAAM;GAChC,eAAe,cAAc;EAC/B,CAAC;EAED,OAAO;CACT;CAEA,oBACE,MACA,WACA,QAAQ,OACgB;EAExB,MAAM,iBAAiB,KAAK,sBAAsB,WAAW,IAAI;EAGjE,MAAM,eAAe,KAAK,WAAW,SAAS,KAAK,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC;EAE/E,MAAM,UAAwD;GAC5D,QAAQ,KAAK;GACb,UAAU;IACR,kBAAkB,KAAK;IACvB,GAAG;GACL;EACF;EAEA,IAAI,OAAO;GACT,QAAQ,WAAW,YAAY,KAAK,IAAI;GACxC,QAAQ,aAAa,KAAK,UAAU,QAAQ;EAC9C;EAGA,MAAM,cAAc,gBAAgB,eAAe,KAAK,OAAO;EAC/D,IAAI,aACF,QAAQ,eAAe;EAIzB,IAAI,gBAAgB,WAClB,QAAQ,SAAS,aAAa,eAAe;EAE/C,IAAI,gBAAgB,aAClB,QAAQ,SAAS,eAAe,eAAe;EAIjD,IAAI,KAAK,cAAc,KAAK,MAAM,QAChC,QAAQ,OAAO,KAAK;EAItB,IAAI,KAAK,UAAU,KAAA,GACjB,QAAQ,SAAS,QAAQ,KAAK,KAAK,IAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,MAAM;EAG1E,IAAI,KAAK,WAAW,KAAA,GAClB,QAAQ,UAAU,QAAQ,KAAK,MAAM,IAAI,KAAK,SAAS,EAAE,QAAQ,KAAK,OAAO;EAG/E,MAAM,aAAc,KAAK,cAAc,CAAC;EAExC,IAAI,KAAK,SAAS,SAAS,kBAAkB;GAC3C,MAAM,YAAY;GAGlB,IAAI,UAAU,UAAU,KAAA,GAGtB,QAAQ,SAAS,gBAAgB,UAAU;GAI7C,IAAI,UAAU,aAAa,KAAA,GAGzB,QAAQ,SAAS,cAAc,UAAU;GAI3C,QAAQ,SAAS,iBAAiB,mBAAmB,UAAU,KAAK;GAGpE,IAAI,UAAU,eAAe,KAAA,GAC3B,QAAQ,SAAS,kBAAkB,UAAU;GAI/C,MAAM,kBAAkB,SAAS,YAAY;IAAC;IAAS;IAAY;IAAS;IAAc;GAAqB,CAAC;GAChH,QAAQ,WAAW;IACjB,GAAG,QAAQ;IACX,GAAG;GACL;EACF,OAEE,QAAQ,WAAW;GACjB,GAAG,QAAQ;GACX,GAAG;EACL;EAIF,IAAI,KAAK,WAAW;GAClB,QAAQ,QAAQ,KAAK,UAAU;GAC/B,QAAQ,SAAS,eAAe,KAAK;EACvC;EAEA,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AClaA,SAAgB,sBAAsB,UAAyD;CAC7F,QAAO,UAAS;EACd,GAAG;EACH,UAAU;GACR,GAAG,KAAK;GACR,WAAW;IACT,GAAI,KAAK,UAAU;IACnB,GAAG;GACL;EACF;CACF;AACF"}
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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"}
package/dist/tracing.d.ts CHANGED
@@ -21,13 +21,20 @@ export interface LangSmithExporterConfig extends ClientConfig, TrackingExporterC
21
21
  */
22
22
  projectName?: string;
23
23
  /**
24
- * Maximum number of `spanId → langsmithRunId` mappings to retain for resolving
25
- * `onScoreEvent` lookups. Older entries are evicted in LRU order when the cap
26
- * is exceeded so long-running processes do not grow unbounded.
24
+ * Maximum number of `traceId → root run id` mappings to keep in memory. The
25
+ * cache lets a trace-level score skip the LangSmith lookup for traces this
26
+ * process exported; scores for traces outside the cache are still delivered.
27
+ * Older entries are evicted in LRU order when the cap is exceeded.
27
28
  * Defaults to 10000.
28
29
  */
29
30
  runIdCacheMaxEntries?: number;
30
31
  }
32
+ /**
33
+ * LangSmith run id for a Mastra span id (UUID v5). Every run, root or not, is named
34
+ * after its own span, so a span-level score can be routed from any process without
35
+ * remembering the ids LangSmith would otherwise allocate.
36
+ */
37
+ export declare function langsmithRunIdFor(spanId: string): string;
31
38
  type LangSmithRoot = undefined;
32
39
  type LangSmithSpan = RunTree;
33
40
  type LangSmithEvent = RunTree;
@@ -82,6 +89,14 @@ export declare class LangSmithExporter extends TrackingExporter<LangSmithRoot, L
82
89
  * and reuse here and in LangfuseExporter.findLangfusePrompt()
83
90
  */
84
91
  private findLangsmithMetadata;
92
+ /**
93
+ * LangSmith groups runs into a Thread only by `session_id` / `thread_id` metadata and
94
+ * needs it on every run of the trace. A child run follows the session its parent was
95
+ * built with: sub-agents and Observational Memory run on derived threads, and their
96
+ * own `threadId` would split the conversation into one-off Threads. The span's own
97
+ * `sessionId` / `threadId` applies at the root, or when no ancestor carries a session.
98
+ */
99
+ private findSessionId;
85
100
  private buildRunTreePayload;
86
101
  }
87
102
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,eAAe,EAA6B,UAAU,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAGxH,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,KAAK,EAAE,YAAY,EAAiB,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE5C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAGxD,MAAM,WAAW,uBAAwB,SAAQ,YAAY,EAAE,sBAAsB;IACnF,gCAAgC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAID,KAAK,aAAa,GAAG,SAAS,CAAC;AAC/B,KAAK,aAAa,GAAG,OAAO,CAAC;AAC7B,KAAK,cAAc,GAAG,OAAO,CAAC;AAC9B,KAAK,iBAAiB,GAAG,sBAAsB,CAAC;AAChD,KAAK,kBAAkB,GAAG,SAAS,CAAC,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,iBAAiB,CAAC,CAAC;AAwBrG,qBAAa,iBAAkB,SAAQ,gBAAgB,CACrD,aAAa,EACb,aAAa,EACb,cAAc,EACd,iBAAiB,EACjB,uBAAuB,CACxB;;IACU,IAAI,SAAe;IAiB5B,YAAY,MAAM,GAAE,uBAA4B,EAiB/C;IA+CD;;;;;OAKG;IACH,UAAyB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAE/C;IAED,oGAAoG;IACpG,UAAyB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAMtD;IAEK,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAkDnD;IAED,UAAmB,iBAAiB,UAAQ;IAC5C,UAAyB,UAAU,CAAC,KAAK,EAAE;QACzC,IAAI,EAAE,eAAe,CAAC;QACtB,SAAS,EAAE,kBAAkB,CAAC;KAC/B,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAErC;IAED,UAAyB,UAAU,CAAC,IAAI,EAAE;QACxC,IAAI,EAAE,eAAe,CAAC;QACtB,SAAS,EAAE,kBAAkB,CAAC;KAC/B,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAuBrC;IAED,UAAyB,WAAW,CAAC,IAAI,EAAE;QACzC,IAAI,EAAE,eAAe,CAAC;QACtB,SAAS,EAAE,kBAAkB,CAAC;KAC/B,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAYtC;IAED,UAAyB,WAAW,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,eAAe,CAAC;QAAC,SAAS,EAAE,kBAAkB,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAElH;IAED,UAAyB,WAAW,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,eAAe,CAAC;QAAC,SAAS,EAAE,kBAAkB,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAElH;IAED,UAAyB,UAAU,CAAC,IAAI,EAAE;QACxC,IAAI,EAAE,aAAa,CAAC;QACpB,SAAS,EAAE,kBAAkB,CAAC;QAC9B,MAAM,EAAE,aAAa,CAAC;KACvB,GAAG,OAAO,CAAC,IAAI,CAAC,CAShB;YAEa,qBAAqB;IAqDnC;;;;;;OAMG;IACH,OAAO,CAAC,qBAAqB;IAuC7B,OAAO,CAAC,mBAAmB;CAqG5B"}
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.3.29-alpha.1",
3
+ "version": "1.4.0-alpha.2",
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,12 +23,12 @@
23
23
  },
24
24
  "license": "Apache-2.0",
25
25
  "dependencies": {
26
- "langsmith": "^0.8.7",
27
- "@mastra/observability": "1.21.1-alpha.1"
26
+ "langsmith": "^0.8.12",
27
+ "@mastra/observability": "1.21.1-alpha.2"
28
28
  },
29
29
  "devDependencies": {
30
- "@ai-sdk/openai": "^2.0.135",
31
- "@types/node": "22.20.5",
30
+ "@ai-sdk/openai": "^2.0.136",
31
+ "@types/node": "22.19.15",
32
32
  "@vitest/coverage-v8": "4.1.12",
33
33
  "@vitest/ui": "4.1.12",
34
34
  "dotenv": "^17.4.2",
@@ -37,9 +37,9 @@
37
37
  "vitest": "4.1.12",
38
38
  "zod": "^4.6.7",
39
39
  "@internal/types-builder": "0.0.120",
40
- "@observability/test-utils": "0.1.1",
41
- "@mastra/core": "1.80.0-alpha.1",
42
- "@internal/lint": "0.0.145"
40
+ "@mastra/core": "1.80.0-alpha.4",
41
+ "@internal/lint": "0.0.145",
42
+ "@observability/test-utils": "0.1.1"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "@mastra/core": ">=1.16.0-0 <2.0.0-0"