@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 +144 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +144 -36
- package/dist/index.js.map +1 -1
- package/dist/tracing.d.ts +18 -3
- package/dist/tracing.d.ts.map +1 -1
- package/package.json +8 -8
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
|
-
|
|
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
|
-
|
|
46
|
-
|
|
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
|
|
68
|
-
*
|
|
69
|
-
*
|
|
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
|
|
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
|
-
#
|
|
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
|
|
93
|
-
#
|
|
94
|
-
const
|
|
95
|
-
if (
|
|
96
|
-
this.#
|
|
97
|
-
this.#
|
|
98
|
-
return
|
|
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
|
|
101
|
-
#
|
|
102
|
-
if (this.#
|
|
103
|
-
this.#
|
|
104
|
-
while (this.#
|
|
105
|
-
const oldest = this.#
|
|
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.#
|
|
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.#
|
|
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
|
-
|
|
145
|
-
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/dist/index.cjs.map
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
|
67
|
-
*
|
|
68
|
-
*
|
|
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
|
|
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
|
-
#
|
|
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
|
|
92
|
-
#
|
|
93
|
-
const
|
|
94
|
-
if (
|
|
95
|
-
this.#
|
|
96
|
-
this.#
|
|
97
|
-
return
|
|
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
|
|
100
|
-
#
|
|
101
|
-
if (this.#
|
|
102
|
-
this.#
|
|
103
|
-
while (this.#
|
|
104
|
-
const oldest = this.#
|
|
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.#
|
|
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.#
|
|
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
|
-
|
|
144
|
-
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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":";;;;;;;;;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;;;;;;;;;AClDA,MAAM,mCAAmC;;AAEzC,MAAM,+BAA+B;CAAC;CAAO;CAAO;AAAK;;AAEzD,MAAM,eAAe;;AAYrB,MAAM,mBAAmB,OAAO,KAAK,oCAAoC,KAAK;;;;;;AAO9E,SAAgB,kBAAkB,QAAwB;CACxD,MAAM,OAAO,WAAW,MAAM,CAAC,CAAC,OAAO,gBAAgB,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,OAAO;CAC/E,KAAK,KAAM,KAAK,KAAM,KAAQ;CAC9B,KAAK,KAAM,KAAK,KAAM,KAAQ;CAC9B,MAAM,MAAM,KAAK,SAAS,GAAG,EAAE,CAAC,CAAC,SAAS,KAAK;CAC/C,OAAO,GAAG,IAAI,MAAM,GAAG,CAAC,EAAE,GAAG,IAAI,MAAM,GAAG,EAAE,EAAE,GAAG,IAAI,MAAM,IAAI,EAAE,EAAE,GAAG,IAAI,MAAM,IAAI,EAAE,EAAE,GAAG,IAAI,MAAM,EAAE;AACzG;AASA,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;;;;;;;;;CAUA,oCAAoB,IAAI,IAAqB;;CAE7C,oBAAmC,QAAQ,QAAQ;CACnD;;CAEA;;CAEA,mCAAmB,IAAI,IAAyC;CAEhE,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;EACxG,KAAK,sBACH,OAAO,eAAe,QAAQ,IAAI,qBAAqB,QAAQ,IAAI,qBAAqB;EAE1F,IAAI,CAAC,QAAQ;GACX,KAAK,YAAY,yCAAyC,CAAC,CAAC,OAAO,EAAE;GACrE;EACF;EAEA,KAAK,UAAU,OAAO,UAAU,IAAI,OAAO,KAAK,MAAM;CACxD;;CAGA,YAAY,SAAsC;EAChD,MAAM,OAAO,KAAK,kBAAkB,IAAI,OAAO;EAC/C,IAAI,SAAS,KAAA,GAAW,OAAO,KAAA;EAE/B,KAAK,kBAAkB,OAAO,OAAO;EACrC,KAAK,kBAAkB,IAAI,SAAS,IAAI;EACxC,OAAO;CACT;;CAGA,iBAAiB,SAAiB,MAAqB;EACrD,IAAI,KAAK,kBAAkB,IAAI,OAAO,GAAG;EACzC,KAAK,kBAAkB,IAAI,SAAS,IAAI;EACxC,OAAO,KAAK,kBAAkB,OAAO,KAAK,uBAAuB;GAC/D,MAAM,SAAS,KAAK,kBAAkB,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;GACpD,IAAI,WAAW,KAAA,GAAW;GAC1B,KAAK,kBAAkB,OAAO,MAAM;EACtC;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,kBAAkB,MAAM;EAC/B;CACF;CAEA,MAAM,aAAa,OAAkC;EACnD,IAAI,CAAC,KAAK,SAAS;EAEnB,MAAM,EAAE,UAAU;EAElB,MAAM,OAAO,MAAM,SACf,MAAM,UACJ,KAAK,YAAY,MAAM,OAAO,IAC9B,KAAA,IACF,MAAM,UACJ,MAAM,KAAK,gBAAgB,MAAM,OAAO,IACxC,KAAA;EACN,MAAM,iBAAiB,MAAM,SAAS,kBAAkB,MAAM,MAAM,IAAI,MAAM;EAC9E,IAAI,CAAC,gBAAgB;GACnB,KAAK,OAAO,KACV,qMAEA;IACE,SAAS,MAAM;IACf,QAAQ,MAAM;IACd,UAAU,MAAM;GAClB,CACF;GACA;EACF;EAEA,MAAM,MAAM,MAAM,cAAc,MAAM;EACtC,MAAM,YAAY,MAAM,aAAc,MAAM,KAAK,cAAc,MAAM,eAAe,KAAK,mBAAmB;EAE5G,IAAI;GACF,MAAM,KAAK,QAAQ,eAAe,gBAAgB,KAAK;IACrD,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;IACjC,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;;;;;;CAOA,MAAM,gBAAgB,SAA+C;EACnE,MAAM,QAAQ,KAAK,YAAY,OAAO;EACtC,IAAI,OAAO,OAAO;EAClB,IAAI,CAAC,aAAa,KAAK,OAAO,GAAG,OAAO,KAAA;EAIxC,KAAK,IAAI,UAAU,IAAK,WAAW;GACjC,MAAM,QAAQ,MAAM,KAAK,aAAa,OAAO;GAC7C,IAAI,OAAO;IACT,KAAK,iBAAiB,SAAS,KAAK;IACpC,OAAO;GACT;GACA,MAAM,QAAQ,6BAA6B;GAC3C,IAAI,UAAU,KAAA,GAAW,OAAO,KAAA;GAChC,MAAM,IAAI,SAAQ,YAAW,WAAW,SAAS,KAAK,CAAC;EACzD;CACF;CAEA,MAAM,aAAa,SAA+C;EAGhE,MAAM,YAAY,MAAM,KAAK,cAAc,KAAK,mBAAmB;EACnE,IAAI,CAAC,WAAW,OAAO,KAAA;EAEvB,IAAI;GACF,MAAM,OAAO,KAAK,QAAS,SAAS;IAClC;IACA,QAAQ;IACR,QAAQ,kBAAkB,KAAK,UAAU,EAAE,iBAAiB,QAAQ,CAAC,EAAE;IACvE,QAAQ,CAAC,MAAM,YAAY;IAC3B,OAAO;IACP,OAAO;GACT,CAAC;GACD,WAAW,MAAM,OAAO,MACtB,OAAO;IAAE,OAAO,IAAI;IAAI,WAAW,IAAI;GAAW;EAEtD,SAAS,KAAK;GACZ,KAAK,OAAO,MAAM,8CAA8C;IAAE;IAAS,OAAO;GAAI,CAAC;EACzF;CAEF;CAEA,cAAc,aAAkD;EAC9D,IAAI,YAAY,KAAK,iBAAiB,IAAI,WAAW;EACrD,IAAI,CAAC,WAAW;GACd,YAAY,QAAQ,QAAQ,CAAC,CAC1B,WAAW,KAAK,QAAS,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC,CACtD,MAAK,YAAW,QAAQ,EAAE,CAAC,CAC3B,OAAM,QAAO;IACZ,KAAK,OAAO,MAAM,wDAAwD;KAAE;KAAa,OAAO;IAAI,CAAC;IACrG,KAAK,iBAAiB,OAAO,WAAW;GAE1C,CAAC;GACH,KAAK,iBAAiB,IAAI,aAAa,SAAS;EAClD;EACA,OAAO;CACT;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,QAAQ,kBAAkB,KAAK,EAAE;EACvC,MAAM,UAAU;GACd,MAAM,KAAK;GACX,IAAI;GACJ,GAAG,KAAK,oBAAoB,MAAM,WAAW,IAAI;EACnD;EAEA,MAAM,gBAAgB,KAAK,aAAa,IAAI,QAAQ,OAAO,IAAI,OAAQ,YAAY,OAAO;EAC1F,IAAI,KAAK,YACP,KAAK,iBAAiB,KAAK,SAAS;GAAE;GAAO,aAAa,QAAQ,gBAAgB,KAAK;EAAoB,CAAC;EAG9G,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;;;;;;;;CASA,cAAsB,MAAuB,WAAmD;EAC9F,MAAM,YAAY,KAAK,aAAa,KAAA,IAAY,UAAU,UAAU,EAAE,KAAK,CAAC,CAAC,EAAE,UAAU;EACzF,IAAI,OAAO,cAAc,YAAY,WAAW,OAAO;EACvD,MAAM,MAAM,KAAK,UAAU,aAAa,KAAK,UAAU;EACvD,OAAO,OAAO,QAAQ,YAAY,MAAM,MAAM,KAAA;CAChD;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,gBAAgB,KAAK;IACrB,iBAAiB,KAAK;IACtB,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;EAKzB,MAAM,YAAY,gBAAgB,aAAa,KAAK,cAAc,MAAM,SAAS;EACjF,IAAI,WACF,QAAQ,SAAS,aAAa;EAEhC,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC1hBA,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"}
|
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 `
|
|
25
|
-
*
|
|
26
|
-
*
|
|
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 {};
|
package/dist/tracing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
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
|
+
"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.
|
|
27
|
-
"@mastra/observability": "1.21.1-alpha.
|
|
26
|
+
"langsmith": "^0.8.12",
|
|
27
|
+
"@mastra/observability": "1.21.1-alpha.2"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@ai-sdk/openai": "^2.0.
|
|
31
|
-
"@types/node": "22.
|
|
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
|
-
"@
|
|
41
|
-
"@
|
|
42
|
-
"@
|
|
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"
|