langsmith 0.3.42 → 0.3.44-rc.0

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/client.cjs CHANGED
@@ -202,6 +202,7 @@ exports.AutoBatchQueue = AutoBatchQueue;
202
202
  // 20 MB
203
203
  exports.DEFAULT_BATCH_SIZE_LIMIT_BYTES = 20_971_520;
204
204
  const SERVER_INFO_REQUEST_TIMEOUT = 2500;
205
+ const DEFAULT_API_URL = "https://api.smith.langchain.com";
205
206
  class Client {
206
207
  constructor(config = {}) {
207
208
  Object.defineProperty(this, "apiKey", {
@@ -349,6 +350,12 @@ class Client {
349
350
  writable: true,
350
351
  value: void 0
351
352
  });
353
+ Object.defineProperty(this, "multipartStreamingDisabled", {
354
+ enumerable: true,
355
+ configurable: true,
356
+ writable: true,
357
+ value: false
358
+ });
352
359
  Object.defineProperty(this, "debug", {
353
360
  enumerable: true,
354
361
  configurable: true,
@@ -400,8 +407,7 @@ class Client {
400
407
  }
401
408
  static getDefaultClientConfig() {
402
409
  const apiKey = (0, env_js_1.getLangSmithEnvironmentVariable)("API_KEY");
403
- const apiUrl = (0, env_js_1.getLangSmithEnvironmentVariable)("ENDPOINT") ??
404
- "https://api.smith.langchain.com";
410
+ const apiUrl = (0, env_js_1.getLangSmithEnvironmentVariable)("ENDPOINT") ?? DEFAULT_API_URL;
405
411
  const hideInputs = (0, env_js_1.getLangSmithEnvironmentVariable)("HIDE_INPUTS") === "true";
406
412
  const hideOutputs = (0, env_js_1.getLangSmithEnvironmentVariable)("HIDE_OUTPUTS") === "true";
407
413
  return {
@@ -1117,12 +1123,12 @@ class Client {
1117
1123
  return stream;
1118
1124
  }
1119
1125
  async _sendMultipartRequest(parts, context, options) {
1120
- try {
1121
- // Create multipart form data boundary
1122
- const boundary = "----LangSmithFormBoundary" + Math.random().toString(36).slice(2);
1123
- const body = await ((0, fetch_js_1._globalFetchImplementationIsNodeFetch)()
1124
- ? this._createNodeFetchBody(parts, boundary)
1125
- : this._createMultipartStream(parts, boundary));
1126
+ // Create multipart form data boundary
1127
+ const boundary = "----LangSmithFormBoundary" + Math.random().toString(36).slice(2);
1128
+ const isNodeFetch = (0, fetch_js_1._globalFetchImplementationIsNodeFetch)();
1129
+ const buildBuffered = () => this._createNodeFetchBody(parts, boundary);
1130
+ const buildStream = () => this._createMultipartStream(parts, boundary);
1131
+ const send = async (body) => {
1126
1132
  const headers = {
1127
1133
  ...this.headers,
1128
1134
  "Content-Type": `multipart/form-data; boundary=${boundary}`,
@@ -1130,7 +1136,7 @@ class Client {
1130
1136
  if (options?.apiKey !== undefined) {
1131
1137
  headers["x-api-key"] = options.apiKey;
1132
1138
  }
1133
- const res = await this.batchIngestCaller.call((0, fetch_js_1._getFetchImplementation)(this.debug), `${options?.apiUrl ?? this.apiUrl}/runs/multipart`, {
1139
+ return this.batchIngestCaller.call((0, fetch_js_1._getFetchImplementation)(this.debug), `${options?.apiUrl ?? this.apiUrl}/runs/multipart`, {
1134
1140
  method: "POST",
1135
1141
  headers,
1136
1142
  body,
@@ -1138,6 +1144,31 @@ class Client {
1138
1144
  signal: AbortSignal.timeout(this.timeout_ms),
1139
1145
  ...this.fetchOptions,
1140
1146
  });
1147
+ };
1148
+ try {
1149
+ let res;
1150
+ let streamedAttempt = false;
1151
+ // attempt stream only if not disabled and not using node-fetch
1152
+ if (!isNodeFetch && !this.multipartStreamingDisabled) {
1153
+ streamedAttempt = true;
1154
+ res = await send(await buildStream());
1155
+ }
1156
+ else {
1157
+ res = await send(await buildBuffered());
1158
+ }
1159
+ // if stream fails, fallback to buffered body
1160
+ if ((!this.multipartStreamingDisabled || streamedAttempt) &&
1161
+ res.status === 422 &&
1162
+ (options?.apiUrl ?? this.apiUrl) !== DEFAULT_API_URL) {
1163
+ console.warn(`Streaming multipart upload to ${options?.apiUrl ?? this.apiUrl}/runs/multipart failed. ` +
1164
+ `This usually means the host does not support chunked uploads. ` +
1165
+ `Retrying with a buffered upload for operation "${context}".`);
1166
+ // Disable streaming for future requests
1167
+ this.multipartStreamingDisabled = true;
1168
+ // retry with fully-buffered body
1169
+ res = await send(await buildBuffered());
1170
+ }
1171
+ // raise if still failing
1141
1172
  await (0, error_js_1.raiseForStatus)(res, "ingest multipart runs", true);
1142
1173
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1143
1174
  }
@@ -3122,7 +3153,7 @@ class Client {
3122
3153
  const settings = await this._getSettings();
3123
3154
  if (options?.isPublic && !settings.tenant_handle) {
3124
3155
  throw new Error(`Cannot create a public prompt without first\n
3125
- creating a LangChain Hub handle.
3156
+ creating a LangChain Hub handle.
3126
3157
  You can add a handle by creating a public prompt at:\n
3127
3158
  https://smith.langchain.com/prompts`);
3128
3159
  }
package/dist/client.d.ts CHANGED
@@ -312,6 +312,7 @@ export declare class Client implements LangSmithTracingClientInterface {
312
312
  private _getServerInfoPromise?;
313
313
  private manualFlushMode;
314
314
  private langSmithToOTELTranslator?;
315
+ private multipartStreamingDisabled;
315
316
  debug: boolean;
316
317
  constructor(config?: ClientConfig);
317
318
  static getDefaultClientConfig(): {
package/dist/client.js CHANGED
@@ -164,6 +164,7 @@ export class AutoBatchQueue {
164
164
  // 20 MB
165
165
  export const DEFAULT_BATCH_SIZE_LIMIT_BYTES = 20_971_520;
166
166
  const SERVER_INFO_REQUEST_TIMEOUT = 2500;
167
+ const DEFAULT_API_URL = "https://api.smith.langchain.com";
167
168
  export class Client {
168
169
  constructor(config = {}) {
169
170
  Object.defineProperty(this, "apiKey", {
@@ -311,6 +312,12 @@ export class Client {
311
312
  writable: true,
312
313
  value: void 0
313
314
  });
315
+ Object.defineProperty(this, "multipartStreamingDisabled", {
316
+ enumerable: true,
317
+ configurable: true,
318
+ writable: true,
319
+ value: false
320
+ });
314
321
  Object.defineProperty(this, "debug", {
315
322
  enumerable: true,
316
323
  configurable: true,
@@ -362,8 +369,7 @@ export class Client {
362
369
  }
363
370
  static getDefaultClientConfig() {
364
371
  const apiKey = getLangSmithEnvironmentVariable("API_KEY");
365
- const apiUrl = getLangSmithEnvironmentVariable("ENDPOINT") ??
366
- "https://api.smith.langchain.com";
372
+ const apiUrl = getLangSmithEnvironmentVariable("ENDPOINT") ?? DEFAULT_API_URL;
367
373
  const hideInputs = getLangSmithEnvironmentVariable("HIDE_INPUTS") === "true";
368
374
  const hideOutputs = getLangSmithEnvironmentVariable("HIDE_OUTPUTS") === "true";
369
375
  return {
@@ -1079,12 +1085,12 @@ export class Client {
1079
1085
  return stream;
1080
1086
  }
1081
1087
  async _sendMultipartRequest(parts, context, options) {
1082
- try {
1083
- // Create multipart form data boundary
1084
- const boundary = "----LangSmithFormBoundary" + Math.random().toString(36).slice(2);
1085
- const body = await (_globalFetchImplementationIsNodeFetch()
1086
- ? this._createNodeFetchBody(parts, boundary)
1087
- : this._createMultipartStream(parts, boundary));
1088
+ // Create multipart form data boundary
1089
+ const boundary = "----LangSmithFormBoundary" + Math.random().toString(36).slice(2);
1090
+ const isNodeFetch = _globalFetchImplementationIsNodeFetch();
1091
+ const buildBuffered = () => this._createNodeFetchBody(parts, boundary);
1092
+ const buildStream = () => this._createMultipartStream(parts, boundary);
1093
+ const send = async (body) => {
1088
1094
  const headers = {
1089
1095
  ...this.headers,
1090
1096
  "Content-Type": `multipart/form-data; boundary=${boundary}`,
@@ -1092,7 +1098,7 @@ export class Client {
1092
1098
  if (options?.apiKey !== undefined) {
1093
1099
  headers["x-api-key"] = options.apiKey;
1094
1100
  }
1095
- const res = await this.batchIngestCaller.call(_getFetchImplementation(this.debug), `${options?.apiUrl ?? this.apiUrl}/runs/multipart`, {
1101
+ return this.batchIngestCaller.call(_getFetchImplementation(this.debug), `${options?.apiUrl ?? this.apiUrl}/runs/multipart`, {
1096
1102
  method: "POST",
1097
1103
  headers,
1098
1104
  body,
@@ -1100,6 +1106,31 @@ export class Client {
1100
1106
  signal: AbortSignal.timeout(this.timeout_ms),
1101
1107
  ...this.fetchOptions,
1102
1108
  });
1109
+ };
1110
+ try {
1111
+ let res;
1112
+ let streamedAttempt = false;
1113
+ // attempt stream only if not disabled and not using node-fetch
1114
+ if (!isNodeFetch && !this.multipartStreamingDisabled) {
1115
+ streamedAttempt = true;
1116
+ res = await send(await buildStream());
1117
+ }
1118
+ else {
1119
+ res = await send(await buildBuffered());
1120
+ }
1121
+ // if stream fails, fallback to buffered body
1122
+ if ((!this.multipartStreamingDisabled || streamedAttempt) &&
1123
+ res.status === 422 &&
1124
+ (options?.apiUrl ?? this.apiUrl) !== DEFAULT_API_URL) {
1125
+ console.warn(`Streaming multipart upload to ${options?.apiUrl ?? this.apiUrl}/runs/multipart failed. ` +
1126
+ `This usually means the host does not support chunked uploads. ` +
1127
+ `Retrying with a buffered upload for operation "${context}".`);
1128
+ // Disable streaming for future requests
1129
+ this.multipartStreamingDisabled = true;
1130
+ // retry with fully-buffered body
1131
+ res = await send(await buildBuffered());
1132
+ }
1133
+ // raise if still failing
1103
1134
  await raiseForStatus(res, "ingest multipart runs", true);
1104
1135
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1105
1136
  }
@@ -3084,7 +3115,7 @@ export class Client {
3084
3115
  const settings = await this._getSettings();
3085
3116
  if (options?.isPublic && !settings.tenant_handle) {
3086
3117
  throw new Error(`Cannot create a public prompt without first\n
3087
- creating a LangChain Hub handle.
3118
+ creating a LangChain Hub handle.
3088
3119
  You can add a handle by creating a public prompt at:\n
3089
3120
  https://smith.langchain.com/prompts`);
3090
3121
  }
@@ -692,6 +692,7 @@ async function _evaluate(target, fields) {
692
692
  // Start consuming the results.
693
693
  const results = new ExperimentResults(manager);
694
694
  await results.processData(manager);
695
+ await client.awaitPendingTraceBatches();
695
696
  return results;
696
697
  }
697
698
  async function _forward(fn, example, experimentName, metadata, client, includeAttachments) {
@@ -687,6 +687,7 @@ async function _evaluate(target, fields) {
687
687
  // Start consuming the results.
688
688
  const results = new ExperimentResults(manager);
689
689
  await results.processData(manager);
690
+ await client.awaitPendingTraceBatches();
690
691
  return results;
691
692
  }
692
693
  async function _forward(fn, example, experimentName, metadata, client, includeAttachments) {
@@ -217,5 +217,6 @@ async function evaluateComparative(experiments, options) {
217
217
  return tracedEvaluators.map((evaluator) => caller.call(evaluateAndSubmitFeedback, runs, exampleMap[exampleId], evaluator));
218
218
  });
219
219
  const results = await Promise.all(promises);
220
+ await client.awaitPendingTraceBatches();
220
221
  return { experimentName, results };
221
222
  }
@@ -211,5 +211,6 @@ export async function evaluateComparative(experiments, options) {
211
211
  return tracedEvaluators.map((evaluator) => caller.call(evaluateAndSubmitFeedback, runs, exampleMap[exampleId], evaluator));
212
212
  });
213
213
  const results = await Promise.all(promises);
214
+ await client.awaitPendingTraceBatches();
214
215
  return { experimentName, results };
215
216
  }
@@ -67,7 +67,7 @@ class DynamicRunEvaluator {
67
67
  * @returns A promise that extracts to the evaluation result.
68
68
  */
69
69
  async evaluateRun(run, example, options) {
70
- const sourceRunId = (0, uuid_1.v4)();
70
+ let sourceRunId = (0, uuid_1.v4)();
71
71
  const metadata = {
72
72
  targetRunId: run.id,
73
73
  };
@@ -80,7 +80,11 @@ class DynamicRunEvaluator {
80
80
  const wrappedTraceableFunc = (0, traceable_js_1.traceable)(this.func, {
81
81
  project_name: "evaluators",
82
82
  name: "evaluator",
83
- id: sourceRunId,
83
+ on_end: (runTree) => {
84
+ // If tracing with OTEL, setting run id manually does not work.
85
+ // Instead get it at the end of the run.
86
+ sourceRunId = runTree.id;
87
+ },
84
88
  ...options,
85
89
  });
86
90
  const result = await wrappedTraceableFunc(
@@ -63,7 +63,7 @@ export class DynamicRunEvaluator {
63
63
  * @returns A promise that extracts to the evaluation result.
64
64
  */
65
65
  async evaluateRun(run, example, options) {
66
- const sourceRunId = uuidv4();
66
+ let sourceRunId = uuidv4();
67
67
  const metadata = {
68
68
  targetRunId: run.id,
69
69
  };
@@ -76,7 +76,11 @@ export class DynamicRunEvaluator {
76
76
  const wrappedTraceableFunc = traceable(this.func, {
77
77
  project_name: "evaluators",
78
78
  name: "evaluator",
79
- id: sourceRunId,
79
+ on_end: (runTree) => {
80
+ // If tracing with OTEL, setting run id manually does not work.
81
+ // Instead get it at the end of the run.
82
+ sourceRunId = runTree.id;
83
+ },
80
84
  ...options,
81
85
  });
82
86
  const result = await wrappedTraceableFunc(
@@ -1,35 +1,23 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getOtelTraceIdFromUuid = getOtelTraceIdFromUuid;
4
- exports.getOtelSpanIdFromUuid = getOtelSpanIdFromUuid;
5
- exports.createOtelSpanContextFromRun = createOtelSpanContextFromRun;
3
+ exports.getUuidFromOtelTraceId = getUuidFromOtelTraceId;
4
+ exports.getUuidFromOtelSpanId = getUuidFromOtelSpanId;
6
5
  /**
7
- * Get OpenTelemetry trace ID as hex string from UUID.
8
- * @param uuidStr - The UUID string to convert
9
- * @returns Hex string representation of the trace ID
6
+ * Get UUID string from OpenTelemetry trace ID hex string.
7
+ * @param traceId - The hex string trace ID to convert
8
+ * @returns UUID string representation
10
9
  */
11
- function getOtelTraceIdFromUuid(uuidStr) {
12
- // Use full UUID hex (like Python's uuid_val.hex)
13
- return uuidStr.replace(/-/g, "");
10
+ function getUuidFromOtelTraceId(traceId) {
11
+ // Insert hyphens to convert back to UUID format
12
+ return `${traceId.substring(0, 8)}-${traceId.substring(8, 12)}-${traceId.substring(12, 16)}-${traceId.substring(16, 20)}-${traceId.substring(20, 32)}`;
14
13
  }
15
14
  /**
16
- * Get OpenTelemetry span ID as hex string from UUID.
17
- * @param uuidStr - The UUID string to convert
18
- * @returns Hex string representation of the span ID
15
+ * Get UUID string from OpenTelemetry span ID hex string.
16
+ * @param spanId - The hex string span ID to convert (8 bytes/16 hex chars)
17
+ * @returns UUID string representation with zero padding at the front
19
18
  */
20
- function getOtelSpanIdFromUuid(uuidStr) {
21
- // Convert UUID string to bytes equivalent (first 8 bytes for span ID)
22
- // Like Python's uuid_val.bytes[:8].hex()
23
- const cleanUuid = uuidStr.replace(/-/g, "");
24
- return cleanUuid.substring(0, 16); // First 8 bytes (16 hex chars)
25
- }
26
- function createOtelSpanContextFromRun(run) {
27
- const traceId = getOtelTraceIdFromUuid(run.trace_id ?? run.id);
28
- const spanId = getOtelSpanIdFromUuid(run.id);
29
- return {
30
- traceId,
31
- spanId,
32
- isRemote: false,
33
- traceFlags: 1, // SAMPLED
34
- };
19
+ function getUuidFromOtelSpanId(spanId) {
20
+ // Pad with zeros at the front, then format as UUID
21
+ const paddedHex = spanId.padStart(16, "0");
22
+ return `00000000-0000-0000-${paddedHex.substring(0, 4)}-${paddedHex.substring(4, 16)}`;
35
23
  }
@@ -1,17 +1,12 @@
1
- import type { OTELSpanContext } from "./types.js";
2
1
  /**
3
- * Get OpenTelemetry trace ID as hex string from UUID.
4
- * @param uuidStr - The UUID string to convert
5
- * @returns Hex string representation of the trace ID
2
+ * Get UUID string from OpenTelemetry trace ID hex string.
3
+ * @param traceId - The hex string trace ID to convert
4
+ * @returns UUID string representation
6
5
  */
7
- export declare function getOtelTraceIdFromUuid(uuidStr: string): string;
6
+ export declare function getUuidFromOtelTraceId(traceId: string): string;
8
7
  /**
9
- * Get OpenTelemetry span ID as hex string from UUID.
10
- * @param uuidStr - The UUID string to convert
11
- * @returns Hex string representation of the span ID
8
+ * Get UUID string from OpenTelemetry span ID hex string.
9
+ * @param spanId - The hex string span ID to convert (8 bytes/16 hex chars)
10
+ * @returns UUID string representation with zero padding at the front
12
11
  */
13
- export declare function getOtelSpanIdFromUuid(uuidStr: string): string;
14
- export declare function createOtelSpanContextFromRun(run: {
15
- trace_id?: string;
16
- id: string;
17
- }): OTELSpanContext;
12
+ export declare function getUuidFromOtelSpanId(spanId: string): string;
@@ -1,30 +1,19 @@
1
1
  /**
2
- * Get OpenTelemetry trace ID as hex string from UUID.
3
- * @param uuidStr - The UUID string to convert
4
- * @returns Hex string representation of the trace ID
2
+ * Get UUID string from OpenTelemetry trace ID hex string.
3
+ * @param traceId - The hex string trace ID to convert
4
+ * @returns UUID string representation
5
5
  */
6
- export function getOtelTraceIdFromUuid(uuidStr) {
7
- // Use full UUID hex (like Python's uuid_val.hex)
8
- return uuidStr.replace(/-/g, "");
6
+ export function getUuidFromOtelTraceId(traceId) {
7
+ // Insert hyphens to convert back to UUID format
8
+ return `${traceId.substring(0, 8)}-${traceId.substring(8, 12)}-${traceId.substring(12, 16)}-${traceId.substring(16, 20)}-${traceId.substring(20, 32)}`;
9
9
  }
10
10
  /**
11
- * Get OpenTelemetry span ID as hex string from UUID.
12
- * @param uuidStr - The UUID string to convert
13
- * @returns Hex string representation of the span ID
11
+ * Get UUID string from OpenTelemetry span ID hex string.
12
+ * @param spanId - The hex string span ID to convert (8 bytes/16 hex chars)
13
+ * @returns UUID string representation with zero padding at the front
14
14
  */
15
- export function getOtelSpanIdFromUuid(uuidStr) {
16
- // Convert UUID string to bytes equivalent (first 8 bytes for span ID)
17
- // Like Python's uuid_val.bytes[:8].hex()
18
- const cleanUuid = uuidStr.replace(/-/g, "");
19
- return cleanUuid.substring(0, 16); // First 8 bytes (16 hex chars)
20
- }
21
- export function createOtelSpanContextFromRun(run) {
22
- const traceId = getOtelTraceIdFromUuid(run.trace_id ?? run.id);
23
- const spanId = getOtelSpanIdFromUuid(run.id);
24
- return {
25
- traceId,
26
- spanId,
27
- isRemote: false,
28
- traceFlags: 1, // SAMPLED
29
- };
15
+ export function getUuidFromOtelSpanId(spanId) {
16
+ // Pad with zeros at the front, then format as UUID
17
+ const paddedHex = spanId.padStart(16, "0");
18
+ return `00000000-0000-0000-${paddedHex.substring(0, 4)}-${paddedHex.substring(4, 16)}`;
30
19
  }
package/dist/index.cjs CHANGED
@@ -10,4 +10,4 @@ Object.defineProperty(exports, "overrideFetchImplementation", { enumerable: true
10
10
  var project_js_1 = require("./utils/project.cjs");
11
11
  Object.defineProperty(exports, "getDefaultProjectName", { enumerable: true, get: function () { return project_js_1.getDefaultProjectName; } });
12
12
  // Update using yarn bump-version
13
- exports.__version__ = "0.3.42";
13
+ exports.__version__ = "0.3.44-rc.0";
package/dist/index.d.ts CHANGED
@@ -3,4 +3,4 @@ export type { Dataset, Example, TracerSession, Run, Feedback, RetrieverOutput, }
3
3
  export { RunTree, type RunTreeConfig } from "./run_trees.js";
4
4
  export { overrideFetchImplementation } from "./singletons/fetch.js";
5
5
  export { getDefaultProjectName } from "./utils/project.js";
6
- export declare const __version__ = "0.3.42";
6
+ export declare const __version__ = "0.3.44-rc.0";
package/dist/index.js CHANGED
@@ -3,4 +3,4 @@ export { RunTree } from "./run_trees.js";
3
3
  export { overrideFetchImplementation } from "./singletons/fetch.js";
4
4
  export { getDefaultProjectName } from "./utils/project.js";
5
5
  // Update using yarn bump-version
6
- export const __version__ = "0.3.42";
6
+ export const __version__ = "0.3.44-rc.0";
@@ -24,9 +24,8 @@ function maybeCreateOtelContext(runTree, tracer
24
24
  return;
25
25
  }
26
26
  const otel_trace = (0, otel_js_1.getOTELTrace)();
27
- const otel_context = (0, otel_js_1.getOTELContext)();
28
27
  try {
29
- const spanContext = (0, utils_js_1.createOtelSpanContextFromRun)(runTree);
28
+ const activeTraceId = otel_trace.getActiveSpan()?.spanContext()?.traceId;
30
29
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
30
  return (fn) => {
32
31
  const resolvedTracer = tracer ?? otel_trace.getTracer("langsmith", index_js_1.__version__);
@@ -35,10 +34,29 @@ function maybeCreateOtelContext(runTree, tracer
35
34
  attributes[constants_js_2.LANGSMITH_REFERENCE_EXAMPLE_ID] =
36
35
  runTree.reference_example_id;
37
36
  }
37
+ const forceOTELRoot = runTree.extra?.ls_otel_root === true;
38
38
  return resolvedTracer.startActiveSpan(runTree.name, {
39
39
  attributes,
40
+ root: forceOTELRoot,
40
41
  }, () => {
41
- otel_trace.setSpanContext(otel_context.active(), spanContext);
42
+ if (activeTraceId === undefined || forceOTELRoot) {
43
+ const otelSpanId = otel_trace
44
+ .getActiveSpan()
45
+ ?.spanContext()?.spanId;
46
+ if (otelSpanId) {
47
+ const langsmithTraceId = (0, utils_js_1.getUuidFromOtelSpanId)(otelSpanId);
48
+ // Must refetch from our primary async local storage
49
+ const currentRunTree = (0, traceable_js_1.getCurrentRunTree)();
50
+ if (currentRunTree) {
51
+ // This is only for root runs to ensure that trace id
52
+ // and the root run id are returned correctly.
53
+ // This is important for things like leaving feedback on
54
+ // target function runs during evaluation.
55
+ currentRunTree.id = langsmithTraceId;
56
+ currentRunTree.trace_id = langsmithTraceId;
57
+ }
58
+ }
59
+ }
42
60
  return fn();
43
61
  });
44
62
  };
package/dist/traceable.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import { RunTree, isRunTree, isRunnableConfigLike, } from "./run_trees.js";
3
3
  import { isTracingEnabled } from "./env.js";
4
- import { ROOT, AsyncLocalStorageProviderSingleton, } from "./singletons/traceable.js";
4
+ import { ROOT, AsyncLocalStorageProviderSingleton, getCurrentRunTree, } from "./singletons/traceable.js";
5
5
  import { _LC_CONTEXT_VARIABLES_KEY } from "./singletons/constants.js";
6
6
  import { isKVMap, isReadableStream, isAsyncIterable, isIteratorLike, isThenable, isGenerator, isPromiseMethod, } from "./utils/asserts.js";
7
7
  import { getEnvironmentVariable } from "./utils/env.js";
8
8
  import { __version__ } from "./index.js";
9
9
  import { getOTELTrace, getOTELContext } from "./singletons/otel.js";
10
- import { createOtelSpanContextFromRun } from "./experimental/otel/utils.js";
10
+ import { getUuidFromOtelSpanId } from "./experimental/otel/utils.js";
11
11
  import { LANGSMITH_REFERENCE_EXAMPLE_ID } from "./experimental/otel/constants.js";
12
12
  AsyncLocalStorageProviderSingleton.initializeGlobalInstance(new AsyncLocalStorage());
13
13
  /**
@@ -20,9 +20,8 @@ function maybeCreateOtelContext(runTree, tracer
20
20
  return;
21
21
  }
22
22
  const otel_trace = getOTELTrace();
23
- const otel_context = getOTELContext();
24
23
  try {
25
- const spanContext = createOtelSpanContextFromRun(runTree);
24
+ const activeTraceId = otel_trace.getActiveSpan()?.spanContext()?.traceId;
26
25
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
26
  return (fn) => {
28
27
  const resolvedTracer = tracer ?? otel_trace.getTracer("langsmith", __version__);
@@ -31,10 +30,29 @@ function maybeCreateOtelContext(runTree, tracer
31
30
  attributes[LANGSMITH_REFERENCE_EXAMPLE_ID] =
32
31
  runTree.reference_example_id;
33
32
  }
33
+ const forceOTELRoot = runTree.extra?.ls_otel_root === true;
34
34
  return resolvedTracer.startActiveSpan(runTree.name, {
35
35
  attributes,
36
+ root: forceOTELRoot,
36
37
  }, () => {
37
- otel_trace.setSpanContext(otel_context.active(), spanContext);
38
+ if (activeTraceId === undefined || forceOTELRoot) {
39
+ const otelSpanId = otel_trace
40
+ .getActiveSpan()
41
+ ?.spanContext()?.spanId;
42
+ if (otelSpanId) {
43
+ const langsmithTraceId = getUuidFromOtelSpanId(otelSpanId);
44
+ // Must refetch from our primary async local storage
45
+ const currentRunTree = getCurrentRunTree();
46
+ if (currentRunTree) {
47
+ // This is only for root runs to ensure that trace id
48
+ // and the root run id are returned correctly.
49
+ // This is important for things like leaving feedback on
50
+ // target function runs during evaluation.
51
+ currentRunTree.id = langsmithTraceId;
52
+ currentRunTree.trace_id = langsmithTraceId;
53
+ }
54
+ }
55
+ }
38
56
  return fn();
39
57
  });
40
58
  };
@@ -22,7 +22,7 @@ function wrapEvaluator(evaluator) {
22
22
  `See this page for more information: https://docs.smith.langchain.com/evaluation/how_to_guides/vitest_jest`,
23
23
  ].join("\n"));
24
24
  }
25
- const evalRunId = config?.runId ?? config?.id ?? (0, uuid_1.v4)();
25
+ let evalRunId = config?.runId ?? config?.id ?? (0, uuid_1.v4)();
26
26
  let evalResult;
27
27
  if ((0, globals_js_1.trackingEnabled)(context)) {
28
28
  const wrappedEvaluator = (0, traceable_js_1.traceable)(async (_runTree, params) => {
@@ -30,12 +30,21 @@ function wrapEvaluator(evaluator) {
30
30
  }, {
31
31
  id: evalRunId,
32
32
  trace_id: evalRunId,
33
+ on_end: (runTree) => {
34
+ // If tracing with OTEL, setting run id manually does not work.
35
+ // Instead get it at the end of the run.
36
+ evalRunId = runTree.id;
37
+ },
33
38
  reference_example_id: context.currentExample.id,
34
39
  client: context.client,
35
40
  tracingEnabled: true,
36
41
  name: evaluator.name ?? "<evaluator>",
37
42
  project_name: "evaluators",
38
43
  ...config,
44
+ extra: {
45
+ ...config?.extra,
46
+ ls_otel_root: true,
47
+ },
39
48
  });
40
49
  evalResult = await wrappedEvaluator(traceable_js_1.ROOT, input);
41
50
  }
@@ -18,7 +18,7 @@ export function wrapEvaluator(evaluator) {
18
18
  `See this page for more information: https://docs.smith.langchain.com/evaluation/how_to_guides/vitest_jest`,
19
19
  ].join("\n"));
20
20
  }
21
- const evalRunId = config?.runId ?? config?.id ?? v4();
21
+ let evalRunId = config?.runId ?? config?.id ?? v4();
22
22
  let evalResult;
23
23
  if (trackingEnabled(context)) {
24
24
  const wrappedEvaluator = traceable(async (_runTree, params) => {
@@ -26,12 +26,21 @@ export function wrapEvaluator(evaluator) {
26
26
  }, {
27
27
  id: evalRunId,
28
28
  trace_id: evalRunId,
29
+ on_end: (runTree) => {
30
+ // If tracing with OTEL, setting run id manually does not work.
31
+ // Instead get it at the end of the run.
32
+ evalRunId = runTree.id;
33
+ },
29
34
  reference_example_id: context.currentExample.id,
30
35
  client: context.client,
31
36
  tracingEnabled: true,
32
37
  name: evaluator.name ?? "<evaluator>",
33
38
  project_name: "evaluators",
34
39
  ...config,
40
+ extra: {
41
+ ...config?.extra,
42
+ ls_otel_root: true,
43
+ },
35
44
  });
36
45
  evalResult = await wrappedEvaluator(ROOT, input);
37
46
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "langsmith",
3
- "version": "0.3.42",
3
+ "version": "0.3.44-rc.0",
4
4
  "description": "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform.",
5
5
  "packageManager": "yarn@1.22.19",
6
6
  "files": [