langsmith 0.2.4 → 0.2.6

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
@@ -259,17 +259,11 @@ class Client {
259
259
  writable: true,
260
260
  value: void 0
261
261
  });
262
- Object.defineProperty(this, "autoBatchInitialDelayMs", {
263
- enumerable: true,
264
- configurable: true,
265
- writable: true,
266
- value: 250
267
- });
268
262
  Object.defineProperty(this, "autoBatchAggregationDelayMs", {
269
263
  enumerable: true,
270
264
  configurable: true,
271
265
  writable: true,
272
- value: 50
266
+ value: 250
273
267
  });
274
268
  Object.defineProperty(this, "batchSizeBytesLimit", {
275
269
  enumerable: true,
@@ -576,7 +570,6 @@ class Client {
576
570
  }
577
571
  }
578
572
  async processRunOperation(item) {
579
- const oldTimeout = this.autoBatchTimeout;
580
573
  clearTimeout(this.autoBatchTimeout);
581
574
  this.autoBatchTimeout = undefined;
582
575
  if (item.action === "create") {
@@ -591,9 +584,7 @@ class Client {
591
584
  this.autoBatchTimeout = setTimeout(() => {
592
585
  this.autoBatchTimeout = undefined;
593
586
  this.drainAutoBatchQueue(sizeLimitBytes);
594
- }, oldTimeout
595
- ? this.autoBatchAggregationDelayMs
596
- : this.autoBatchInitialDelayMs);
587
+ }, this.autoBatchAggregationDelayMs);
597
588
  }
598
589
  return itemPromise;
599
590
  }
@@ -615,7 +606,7 @@ class Client {
615
606
  this._serverInfo = await this._getServerInfo();
616
607
  }
617
608
  catch (e) {
618
- console.warn(`[WARNING]: LangSmith failed to fetch info on supported operations. Falling back to single calls and default limits.`);
609
+ console.warn(`[WARNING]: LangSmith failed to fetch info on supported operations. Falling back to batch operations and default limits.`);
619
610
  }
620
611
  }
621
612
  return this._serverInfo ?? {};
@@ -705,19 +696,6 @@ class Client {
705
696
  if (!rawBatch.post.length && !rawBatch.patch.length) {
706
697
  return;
707
698
  }
708
- const serverInfo = await this._ensureServerInfo();
709
- if (serverInfo.version === undefined) {
710
- this.autoBatchTracing = false;
711
- for (const preparedCreateParam of rawBatch.post) {
712
- await this.createRun(preparedCreateParam);
713
- }
714
- for (const preparedUpdateParam of rawBatch.patch) {
715
- if (preparedUpdateParam.id !== undefined) {
716
- await this.updateRun(preparedUpdateParam.id, preparedUpdateParam);
717
- }
718
- }
719
- return;
720
- }
721
699
  const batchChunks = {
722
700
  post: [],
723
701
  patch: [],
@@ -824,7 +802,7 @@ class Client {
824
802
  ]) {
825
803
  for (const originalPayload of payloads) {
826
804
  // collect fields to be sent as separate parts
827
- const { inputs, outputs, events, ...payload } = originalPayload;
805
+ const { inputs, outputs, events, attachments, ...payload } = originalPayload;
828
806
  const fields = { inputs, outputs, events };
829
807
  // encode the main run payload
830
808
  const stringifiedPayload = (0, index_js_2.stringify)(payload);
@@ -853,10 +831,16 @@ class Client {
853
831
  if (attachments) {
854
832
  delete allAttachments[payload.id];
855
833
  for (const [name, [contentType, content]] of Object.entries(attachments)) {
834
+ // Validate that the attachment name doesn't contain a '.'
835
+ if (name.includes(".")) {
836
+ console.warn(`Skipping attachment '${name}' for run ${payload.id}: Invalid attachment name. ` +
837
+ `Attachment names must not contain periods ('.'). Please rename the attachment and try again.`);
838
+ continue;
839
+ }
856
840
  accumulatedParts.push({
857
841
  name: `attachment.${payload.id}.${name}`,
858
842
  payload: new Blob([content], {
859
- type: `${contentType}; length=${content.length}`,
843
+ type: `${contentType}; length=${content.byteLength}`,
860
844
  }),
861
845
  });
862
846
  }
@@ -870,30 +854,40 @@ class Client {
870
854
  }
871
855
  async _sendMultipartRequest(parts, context) {
872
856
  try {
873
- const formData = new FormData();
857
+ // Create multipart form data manually using Blobs
858
+ const boundary = "----LangSmithFormBoundary" + Math.random().toString(36).slice(2);
859
+ const chunks = [];
874
860
  for (const part of parts) {
875
- formData.append(part.name, part.payload);
876
- }
877
- await this.batchIngestCaller.call((0, fetch_js_1._getFetchImplementation)(), `${this.apiUrl}/runs/multipart`, {
861
+ // Add field boundary
862
+ chunks.push(new Blob([`--${boundary}\r\n`]));
863
+ chunks.push(new Blob([
864
+ `Content-Disposition: form-data; name="${part.name}"\r\n`,
865
+ `Content-Type: ${part.payload.type}\r\n\r\n`,
866
+ ]));
867
+ chunks.push(part.payload);
868
+ chunks.push(new Blob(["\r\n"]));
869
+ }
870
+ // Add final boundary
871
+ chunks.push(new Blob([`--${boundary}--\r\n`]));
872
+ // Combine all chunks into a single Blob
873
+ const body = new Blob(chunks);
874
+ // Convert Blob to ArrayBuffer for compatibility
875
+ const arrayBuffer = await body.arrayBuffer();
876
+ const res = await this.batchIngestCaller.call((0, fetch_js_1._getFetchImplementation)(), `${this.apiUrl}/runs/multipart`, {
878
877
  method: "POST",
879
878
  headers: {
880
879
  ...this.headers,
880
+ "Content-Type": `multipart/form-data; boundary=${boundary}`,
881
881
  },
882
- body: formData,
882
+ body: arrayBuffer,
883
883
  signal: AbortSignal.timeout(this.timeout_ms),
884
884
  ...this.fetchOptions,
885
885
  });
886
+ await (0, error_js_1.raiseForStatus)(res, "ingest multipart runs", true);
887
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
886
888
  }
887
889
  catch (e) {
888
- let errorMessage = "Failed to multipart ingest runs";
889
- // eslint-disable-next-line no-instanceof/no-instanceof
890
- if (e instanceof Error) {
891
- errorMessage += `: ${e.stack || e.message}`;
892
- }
893
- else {
894
- errorMessage += `: ${String(e)}`;
895
- }
896
- console.warn(`${errorMessage.trim()}\n\nContext: ${context}`);
890
+ console.warn(`${e.message.trim()}\n\nContext: ${context}`);
897
891
  }
898
892
  }
899
893
  async updateRun(runId, run) {
package/dist/client.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { AsyncCallerParams } from "./utils/async_caller.js";
2
- import { ComparativeExperiment, DataType, Dataset, DatasetDiffInfo, DatasetShareSchema, Example, ExampleUpdate, ExampleUpdateWithId, Feedback, FeedbackConfig, FeedbackIngestToken, KVMap, LangChainBaseMessage, LangSmithSettings, LikePromptResponse, Prompt, PromptCommit, PromptSortField, Run, RunCreate, RunUpdate, ScoreType, ExampleSearch, TimeDelta, TracerSession, TracerSessionResult, ValueType, AnnotationQueue, RunWithAnnotationQueueInfo } from "./schemas.js";
2
+ import { ComparativeExperiment, DataType, Dataset, DatasetDiffInfo, DatasetShareSchema, Example, ExampleUpdate, ExampleUpdateWithId, Feedback, FeedbackConfig, FeedbackIngestToken, KVMap, LangChainBaseMessage, LangSmithSettings, LikePromptResponse, Prompt, PromptCommit, PromptSortField, Run, RunCreate, RunUpdate, ScoreType, ExampleSearch, TimeDelta, TracerSession, TracerSessionResult, ValueType, AnnotationQueue, RunWithAnnotationQueueInfo, Attachments } from "./schemas.js";
3
3
  import { EvaluationResult, EvaluationResults, RunEvaluator } from "./evaluation/evaluator.js";
4
4
  export interface ClientConfig {
5
5
  apiUrl?: string;
@@ -135,7 +135,7 @@ interface CreateRunParams {
135
135
  revision_id?: string;
136
136
  trace_id?: string;
137
137
  dotted_order?: string;
138
- attachments?: Record<string, [string, Uint8Array]>;
138
+ attachments?: Attachments;
139
139
  }
140
140
  interface ProjectOptions {
141
141
  projectName?: string;
@@ -199,7 +199,6 @@ export declare class Client {
199
199
  private autoBatchTracing;
200
200
  private autoBatchQueue;
201
201
  private autoBatchTimeout;
202
- private autoBatchInitialDelayMs;
203
202
  private autoBatchAggregationDelayMs;
204
203
  private batchSizeBytesLimit?;
205
204
  private fetchOptions;
package/dist/client.js CHANGED
@@ -231,17 +231,11 @@ export class Client {
231
231
  writable: true,
232
232
  value: void 0
233
233
  });
234
- Object.defineProperty(this, "autoBatchInitialDelayMs", {
235
- enumerable: true,
236
- configurable: true,
237
- writable: true,
238
- value: 250
239
- });
240
234
  Object.defineProperty(this, "autoBatchAggregationDelayMs", {
241
235
  enumerable: true,
242
236
  configurable: true,
243
237
  writable: true,
244
- value: 50
238
+ value: 250
245
239
  });
246
240
  Object.defineProperty(this, "batchSizeBytesLimit", {
247
241
  enumerable: true,
@@ -548,7 +542,6 @@ export class Client {
548
542
  }
549
543
  }
550
544
  async processRunOperation(item) {
551
- const oldTimeout = this.autoBatchTimeout;
552
545
  clearTimeout(this.autoBatchTimeout);
553
546
  this.autoBatchTimeout = undefined;
554
547
  if (item.action === "create") {
@@ -563,9 +556,7 @@ export class Client {
563
556
  this.autoBatchTimeout = setTimeout(() => {
564
557
  this.autoBatchTimeout = undefined;
565
558
  this.drainAutoBatchQueue(sizeLimitBytes);
566
- }, oldTimeout
567
- ? this.autoBatchAggregationDelayMs
568
- : this.autoBatchInitialDelayMs);
559
+ }, this.autoBatchAggregationDelayMs);
569
560
  }
570
561
  return itemPromise;
571
562
  }
@@ -587,7 +578,7 @@ export class Client {
587
578
  this._serverInfo = await this._getServerInfo();
588
579
  }
589
580
  catch (e) {
590
- console.warn(`[WARNING]: LangSmith failed to fetch info on supported operations. Falling back to single calls and default limits.`);
581
+ console.warn(`[WARNING]: LangSmith failed to fetch info on supported operations. Falling back to batch operations and default limits.`);
591
582
  }
592
583
  }
593
584
  return this._serverInfo ?? {};
@@ -677,19 +668,6 @@ export class Client {
677
668
  if (!rawBatch.post.length && !rawBatch.patch.length) {
678
669
  return;
679
670
  }
680
- const serverInfo = await this._ensureServerInfo();
681
- if (serverInfo.version === undefined) {
682
- this.autoBatchTracing = false;
683
- for (const preparedCreateParam of rawBatch.post) {
684
- await this.createRun(preparedCreateParam);
685
- }
686
- for (const preparedUpdateParam of rawBatch.patch) {
687
- if (preparedUpdateParam.id !== undefined) {
688
- await this.updateRun(preparedUpdateParam.id, preparedUpdateParam);
689
- }
690
- }
691
- return;
692
- }
693
671
  const batchChunks = {
694
672
  post: [],
695
673
  patch: [],
@@ -796,7 +774,7 @@ export class Client {
796
774
  ]) {
797
775
  for (const originalPayload of payloads) {
798
776
  // collect fields to be sent as separate parts
799
- const { inputs, outputs, events, ...payload } = originalPayload;
777
+ const { inputs, outputs, events, attachments, ...payload } = originalPayload;
800
778
  const fields = { inputs, outputs, events };
801
779
  // encode the main run payload
802
780
  const stringifiedPayload = stringifyForTracing(payload);
@@ -825,10 +803,16 @@ export class Client {
825
803
  if (attachments) {
826
804
  delete allAttachments[payload.id];
827
805
  for (const [name, [contentType, content]] of Object.entries(attachments)) {
806
+ // Validate that the attachment name doesn't contain a '.'
807
+ if (name.includes(".")) {
808
+ console.warn(`Skipping attachment '${name}' for run ${payload.id}: Invalid attachment name. ` +
809
+ `Attachment names must not contain periods ('.'). Please rename the attachment and try again.`);
810
+ continue;
811
+ }
828
812
  accumulatedParts.push({
829
813
  name: `attachment.${payload.id}.${name}`,
830
814
  payload: new Blob([content], {
831
- type: `${contentType}; length=${content.length}`,
815
+ type: `${contentType}; length=${content.byteLength}`,
832
816
  }),
833
817
  });
834
818
  }
@@ -842,30 +826,40 @@ export class Client {
842
826
  }
843
827
  async _sendMultipartRequest(parts, context) {
844
828
  try {
845
- const formData = new FormData();
829
+ // Create multipart form data manually using Blobs
830
+ const boundary = "----LangSmithFormBoundary" + Math.random().toString(36).slice(2);
831
+ const chunks = [];
846
832
  for (const part of parts) {
847
- formData.append(part.name, part.payload);
848
- }
849
- await this.batchIngestCaller.call(_getFetchImplementation(), `${this.apiUrl}/runs/multipart`, {
833
+ // Add field boundary
834
+ chunks.push(new Blob([`--${boundary}\r\n`]));
835
+ chunks.push(new Blob([
836
+ `Content-Disposition: form-data; name="${part.name}"\r\n`,
837
+ `Content-Type: ${part.payload.type}\r\n\r\n`,
838
+ ]));
839
+ chunks.push(part.payload);
840
+ chunks.push(new Blob(["\r\n"]));
841
+ }
842
+ // Add final boundary
843
+ chunks.push(new Blob([`--${boundary}--\r\n`]));
844
+ // Combine all chunks into a single Blob
845
+ const body = new Blob(chunks);
846
+ // Convert Blob to ArrayBuffer for compatibility
847
+ const arrayBuffer = await body.arrayBuffer();
848
+ const res = await this.batchIngestCaller.call(_getFetchImplementation(), `${this.apiUrl}/runs/multipart`, {
850
849
  method: "POST",
851
850
  headers: {
852
851
  ...this.headers,
852
+ "Content-Type": `multipart/form-data; boundary=${boundary}`,
853
853
  },
854
- body: formData,
854
+ body: arrayBuffer,
855
855
  signal: AbortSignal.timeout(this.timeout_ms),
856
856
  ...this.fetchOptions,
857
857
  });
858
+ await raiseForStatus(res, "ingest multipart runs", true);
859
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
858
860
  }
859
861
  catch (e) {
860
- let errorMessage = "Failed to multipart ingest runs";
861
- // eslint-disable-next-line no-instanceof/no-instanceof
862
- if (e instanceof Error) {
863
- errorMessage += `: ${e.stack || e.message}`;
864
- }
865
- else {
866
- errorMessage += `: ${String(e)}`;
867
- }
868
- console.warn(`${errorMessage.trim()}\n\nContext: ${context}`);
862
+ console.warn(`${e.message.trim()}\n\nContext: ${context}`);
869
863
  }
870
864
  }
871
865
  async updateRun(runId, run) {
@@ -1,14 +1,14 @@
1
1
  import { Client } from "../index.js";
2
2
  import { Example, KVMap, Run, TracerSession } from "../schemas.js";
3
3
  import { EvaluationResult, EvaluationResults, RunEvaluator } from "./evaluator.js";
4
- type TargetT<TInput = any, TOutput = KVMap> = ((input: TInput, config?: KVMap) => Promise<TOutput>) | ((input: TInput, config?: KVMap) => TOutput) | {
4
+ export type TargetT<TInput = any, TOutput = KVMap> = ((input: TInput, config?: KVMap) => Promise<TOutput>) | ((input: TInput, config?: KVMap) => TOutput) | {
5
5
  invoke: (input: TInput, config?: KVMap) => TOutput;
6
6
  } | {
7
7
  invoke: (input: TInput, config?: KVMap) => Promise<TOutput>;
8
8
  };
9
- type DataT = string | AsyncIterable<Example> | Example[];
10
- type SummaryEvaluatorT = ((runs: Array<Run>, examples: Array<Example>) => Promise<EvaluationResult | EvaluationResults>) | ((runs: Array<Run>, examples: Array<Example>) => EvaluationResult | EvaluationResults);
11
- type EvaluatorT = RunEvaluator | ((run: Run, example?: Example) => EvaluationResult | EvaluationResults) | ((run: Run, example?: Example) => Promise<EvaluationResult | EvaluationResults>);
9
+ export type DataT = string | AsyncIterable<Example> | Example[];
10
+ export type SummaryEvaluatorT = ((runs: Array<Run>, examples: Array<Example>) => Promise<EvaluationResult | EvaluationResults>) | ((runs: Array<Run>, examples: Array<Example>) => EvaluationResult | EvaluationResults);
11
+ export type EvaluatorT = RunEvaluator | ((run: Run, example?: Example) => EvaluationResult | EvaluationResults) | ((run: Run, example?: Example) => Promise<EvaluationResult | EvaluationResults>);
12
12
  interface _ForwardResults {
13
13
  run: Run;
14
14
  example: Example;
@@ -77,7 +77,7 @@ export declare function evaluate(
77
77
  * The target system or function to evaluate.
78
78
  */
79
79
  target: TargetT, options: EvaluateOptions): Promise<ExperimentResults>;
80
- interface ExperimentResultRow {
80
+ export interface ExperimentResultRow {
81
81
  run: Run;
82
82
  example: Example;
83
83
  evaluationResults: EvaluationResults;
@@ -1,4 +1,4 @@
1
1
  export { RunEvaluator, EvaluationResult } from "./evaluator.js";
2
2
  export { StringEvaluator, GradingFunctionParams, GradingFunctionResult, } from "./string_evaluator.js";
3
- export { evaluate, type EvaluateOptions } from "./_runner.js";
3
+ export { evaluate, type EvaluateOptions, type TargetT, type DataT, type SummaryEvaluatorT, type EvaluatorT, type ExperimentResultRow, } from "./_runner.js";
4
4
  export { evaluateComparative } from "./evaluate_comparative.js";
@@ -1,3 +1,3 @@
1
1
  export { StringEvaluator, } from "./string_evaluator.js";
2
- export { evaluate } from "./_runner.js";
2
+ export { evaluate, } from "./_runner.js";
3
3
  export { evaluateComparative } from "./evaluate_comparative.js";
package/dist/index.cjs CHANGED
@@ -8,4 +8,4 @@ Object.defineProperty(exports, "RunTree", { enumerable: true, get: function () {
8
8
  var fetch_js_1 = require("./singletons/fetch.cjs");
9
9
  Object.defineProperty(exports, "overrideFetchImplementation", { enumerable: true, get: function () { return fetch_js_1.overrideFetchImplementation; } });
10
10
  // Update using yarn bump-version
11
- exports.__version__ = "0.2.4";
11
+ exports.__version__ = "0.2.6";
package/dist/index.d.ts CHANGED
@@ -2,4 +2,4 @@ export { Client, type ClientConfig } from "./client.js";
2
2
  export type { Dataset, Example, TracerSession, Run, Feedback, RetrieverOutput, } from "./schemas.js";
3
3
  export { RunTree, type RunTreeConfig } from "./run_trees.js";
4
4
  export { overrideFetchImplementation } from "./singletons/fetch.js";
5
- export declare const __version__ = "0.2.4";
5
+ export declare const __version__ = "0.2.6";
package/dist/index.js CHANGED
@@ -2,4 +2,4 @@ export { Client } from "./client.js";
2
2
  export { RunTree } from "./run_trees.js";
3
3
  export { overrideFetchImplementation } from "./singletons/fetch.js";
4
4
  // Update using yarn bump-version
5
- export const __version__ = "0.2.4";
5
+ export const __version__ = "0.2.6";
@@ -221,6 +221,16 @@ class RunTree {
221
221
  writable: true,
222
222
  value: void 0
223
223
  });
224
+ /**
225
+ * Attachments associated with the run.
226
+ * Each entry is a tuple of [mime_type, bytes]
227
+ */
228
+ Object.defineProperty(this, "attachments", {
229
+ enumerable: true,
230
+ configurable: true,
231
+ writable: true,
232
+ value: void 0
233
+ });
224
234
  // If you pass in a run tree directly, return a shallow clone
225
235
  if (isRunTree(originalConfig)) {
226
236
  Object.assign(this, { ...originalConfig });
@@ -377,6 +387,7 @@ class RunTree {
377
387
  trace_id: run.trace_id,
378
388
  dotted_order: run.dotted_order,
379
389
  tags: run.tags,
390
+ attachments: run.attachments,
380
391
  };
381
392
  return persistedRun;
382
393
  }
@@ -410,6 +421,7 @@ class RunTree {
410
421
  dotted_order: this.dotted_order,
411
422
  trace_id: this.trace_id,
412
423
  tags: this.tags,
424
+ attachments: this.attachments,
413
425
  };
414
426
  await this.client.updateRun(this.id, runUpdate);
415
427
  }
@@ -1,4 +1,4 @@
1
- import { BaseRun, KVMap, RunCreate } from "./schemas.js";
1
+ import { Attachments, BaseRun, KVMap, RunCreate } from "./schemas.js";
2
2
  import { Client } from "./client.js";
3
3
  export declare function convertToDottedOrderFormat(epoch: number, runId: string, executionOrder?: number): string;
4
4
  export interface RunTreeConfig {
@@ -26,6 +26,7 @@ export interface RunTreeConfig {
26
26
  child_execution_order?: number;
27
27
  trace_id?: string;
28
28
  dotted_order?: string;
29
+ attachments?: Attachments;
29
30
  }
30
31
  export interface RunnableConfigLike {
31
32
  /**
@@ -72,6 +73,11 @@ export declare class RunTree implements BaseRun {
72
73
  tracingEnabled?: boolean;
73
74
  execution_order: number;
74
75
  child_execution_order: number;
76
+ /**
77
+ * Attachments associated with the run.
78
+ * Each entry is a tuple of [mime_type, bytes]
79
+ */
80
+ attachments?: Attachments;
75
81
  constructor(originalConfig: RunTreeConfig | RunTree);
76
82
  private static getDefaultConfig;
77
83
  private static getSharedClient;
package/dist/run_trees.js CHANGED
@@ -194,6 +194,16 @@ export class RunTree {
194
194
  writable: true,
195
195
  value: void 0
196
196
  });
197
+ /**
198
+ * Attachments associated with the run.
199
+ * Each entry is a tuple of [mime_type, bytes]
200
+ */
201
+ Object.defineProperty(this, "attachments", {
202
+ enumerable: true,
203
+ configurable: true,
204
+ writable: true,
205
+ value: void 0
206
+ });
197
207
  // If you pass in a run tree directly, return a shallow clone
198
208
  if (isRunTree(originalConfig)) {
199
209
  Object.assign(this, { ...originalConfig });
@@ -350,6 +360,7 @@ export class RunTree {
350
360
  trace_id: run.trace_id,
351
361
  dotted_order: run.dotted_order,
352
362
  tags: run.tags,
363
+ attachments: run.attachments,
353
364
  };
354
365
  return persistedRun;
355
366
  }
@@ -383,6 +394,7 @@ export class RunTree {
383
394
  dotted_order: this.dotted_order,
384
395
  trace_id: this.trace_id,
385
396
  tags: this.tags,
397
+ attachments: this.attachments,
386
398
  };
387
399
  await this.client.updateRun(this.id, runUpdate);
388
400
  }
package/dist/schemas.d.ts CHANGED
@@ -32,6 +32,8 @@ export interface BaseExample {
32
32
  metadata?: KVMap;
33
33
  source_run_id?: string;
34
34
  }
35
+ export type AttachmentData = Uint8Array | ArrayBuffer;
36
+ export type Attachments = Record<string, [string, AttachmentData]>;
35
37
  /**
36
38
  * A run can represent either a trace (root run)
37
39
  * or a child run (~span).
@@ -84,7 +86,7 @@ export interface BaseRun {
84
86
  * Attachments associated with the run.
85
87
  * Each entry is a tuple of [mime_type, bytes]
86
88
  */
87
- attachments?: Record<string, [string, Uint8Array]>;
89
+ attachments?: Attachments;
88
90
  }
89
91
  type S3URL = {
90
92
  ROOT: {
@@ -164,6 +166,11 @@ export interface RunUpdate {
164
166
  * - 20230915T223155647Z1b64098b-4ab7-43f6-afee-992304f198d8.20230914T223155650Zc8d9f4c5-6c5a-4b2d-9b1c-3d9d7a7c5c7c
165
167
  */
166
168
  dotted_order?: string;
169
+ /**
170
+ * Attachments associated with the run.
171
+ * Each entry is a tuple of [mime_type, bytes]
172
+ */
173
+ attachments?: Attachments;
167
174
  }
168
175
  export interface ExampleCreate extends BaseExample {
169
176
  id?: string;
@@ -8,7 +8,7 @@ const traceable_js_1 = require("./singletons/traceable.cjs");
8
8
  const constants_js_1 = require("./singletons/constants.cjs");
9
9
  const asserts_js_1 = require("./utils/asserts.cjs");
10
10
  traceable_js_1.AsyncLocalStorageProviderSingleton.initializeGlobalInstance(new node_async_hooks_1.AsyncLocalStorage());
11
- const handleRunInputs = (rawInputs, processInputs) => {
11
+ const runInputsToMap = (rawInputs) => {
12
12
  const firstInput = rawInputs[0];
13
13
  let inputs;
14
14
  if (firstInput == null) {
@@ -23,6 +23,9 @@ const handleRunInputs = (rawInputs, processInputs) => {
23
23
  else {
24
24
  inputs = { input: firstInput };
25
25
  }
26
+ return inputs;
27
+ };
28
+ const handleRunInputs = (inputs, processInputs) => {
26
29
  try {
27
30
  return processInputs(inputs);
28
31
  }
@@ -47,11 +50,26 @@ const handleRunOutputs = (rawOutputs, processOutputs) => {
47
50
  return outputs;
48
51
  }
49
52
  };
50
- const getTracingRunTree = (runTree, inputs, getInvocationParams, processInputs) => {
53
+ const handleRunAttachments = (rawInputs, extractAttachments) => {
54
+ if (!extractAttachments) {
55
+ return [undefined, rawInputs];
56
+ }
57
+ try {
58
+ const [attachments, remainingArgs] = extractAttachments(...rawInputs);
59
+ return [attachments, remainingArgs];
60
+ }
61
+ catch (e) {
62
+ console.error("Error occurred during extractAttachments:", e);
63
+ return [undefined, rawInputs];
64
+ }
65
+ };
66
+ const getTracingRunTree = (runTree, inputs, getInvocationParams, processInputs, extractAttachments) => {
51
67
  if (!(0, env_js_1.isTracingEnabled)(runTree.tracingEnabled)) {
52
68
  return undefined;
53
69
  }
54
- runTree.inputs = handleRunInputs(inputs, processInputs);
70
+ const [attached, args] = handleRunAttachments(inputs, extractAttachments);
71
+ runTree.attachments = attached;
72
+ runTree.inputs = handleRunInputs(args, processInputs);
55
73
  const invocationParams = getInvocationParams?.(...inputs);
56
74
  if (invocationParams != null) {
57
75
  runTree.extra ??= {};
@@ -213,9 +231,10 @@ const convertSerializableArg = (arg) => {
213
231
  */
214
232
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
215
233
  function traceable(wrappedFunc, config) {
216
- const { aggregator, argsConfigPath, __finalTracedIteratorKey, processInputs, processOutputs, ...runTreeConfig } = config ?? {};
234
+ const { aggregator, argsConfigPath, __finalTracedIteratorKey, processInputs, processOutputs, extractAttachments, ...runTreeConfig } = config ?? {};
217
235
  const processInputsFn = processInputs ?? ((x) => x);
218
236
  const processOutputsFn = processOutputs ?? ((x) => x);
237
+ const extractAttachmentsFn = extractAttachments ?? ((...x) => [undefined, runInputsToMap(x)]);
219
238
  const traceableFunc = (...args) => {
220
239
  let ensuredConfig;
221
240
  try {
@@ -273,7 +292,7 @@ function traceable(wrappedFunc, config) {
273
292
  // used for handoff between LangChain.JS and traceable functions
274
293
  if ((0, run_trees_js_1.isRunnableConfigLike)(firstArg)) {
275
294
  return [
276
- getTracingRunTree(run_trees_js_1.RunTree.fromRunnableConfig(firstArg, ensuredConfig), restArgs, config?.getInvocationParams, processInputsFn),
295
+ getTracingRunTree(run_trees_js_1.RunTree.fromRunnableConfig(firstArg, ensuredConfig), restArgs, config?.getInvocationParams, processInputsFn, extractAttachmentsFn),
277
296
  restArgs,
278
297
  ];
279
298
  }
@@ -289,7 +308,7 @@ function traceable(wrappedFunc, config) {
289
308
  if (firstArg === traceable_js_1.ROOT || (0, run_trees_js_1.isRunTree)(firstArg)) {
290
309
  const currentRunTree = getTracingRunTree(firstArg === traceable_js_1.ROOT
291
310
  ? new run_trees_js_1.RunTree(ensuredConfig)
292
- : firstArg.createChild(ensuredConfig), restArgs, config?.getInvocationParams, processInputsFn);
311
+ : firstArg.createChild(ensuredConfig), restArgs, config?.getInvocationParams, processInputsFn, extractAttachmentsFn);
293
312
  return [currentRunTree, [currentRunTree, ...restArgs]];
294
313
  }
295
314
  // Node.JS uses AsyncLocalStorage (ALS) and AsyncResource
@@ -297,11 +316,11 @@ function traceable(wrappedFunc, config) {
297
316
  const prevRunFromStore = asyncLocalStorage.getStore();
298
317
  if ((0, run_trees_js_1.isRunTree)(prevRunFromStore)) {
299
318
  return [
300
- getTracingRunTree(prevRunFromStore.createChild(ensuredConfig), processedArgs, config?.getInvocationParams, processInputsFn),
319
+ getTracingRunTree(prevRunFromStore.createChild(ensuredConfig), processedArgs, config?.getInvocationParams, processInputsFn, extractAttachmentsFn),
301
320
  processedArgs,
302
321
  ];
303
322
  }
304
- const currentRunTree = getTracingRunTree(new run_trees_js_1.RunTree(ensuredConfig), processedArgs, config?.getInvocationParams, processInputsFn);
323
+ const currentRunTree = getTracingRunTree(new run_trees_js_1.RunTree(ensuredConfig), processedArgs, config?.getInvocationParams, processInputsFn, extractAttachmentsFn);
305
324
  // If a context var is set by LangChain outside of a traceable,
306
325
  // it will be an object with a single property and we should copy
307
326
  // context vars over into the new run tree.
@@ -1,5 +1,5 @@
1
1
  import { RunTreeConfig } from "./run_trees.js";
2
- import { InvocationParamsSchema, KVMap } from "./schemas.js";
2
+ import { Attachments, InvocationParamsSchema, KVMap } from "./schemas.js";
3
3
  import { TraceableFunction } from "./singletons/types.js";
4
4
  /**
5
5
  * Higher-order function that takes function as input and returns a
@@ -19,6 +19,12 @@ export declare function traceable<Func extends (...args: any[]) => any>(wrappedF
19
19
  aggregator?: (args: any[]) => any;
20
20
  argsConfigPath?: [number] | [number, string];
21
21
  __finalTracedIteratorKey?: string;
22
+ /**
23
+ * Extract attachments from args and return remaining args.
24
+ * @param args Arguments of the traced function
25
+ * @returns Tuple of [Attachments, remaining args]
26
+ */
27
+ extractAttachments?: (...args: Parameters<Func>) => [Attachments | undefined, KVMap];
22
28
  /**
23
29
  * Extract invocation parameters from the arguments of the traced function.
24
30
  * This is useful for LangSmith to properly track common metadata like
package/dist/traceable.js CHANGED
@@ -5,7 +5,7 @@ import { ROOT, AsyncLocalStorageProviderSingleton, } from "./singletons/traceabl
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
  AsyncLocalStorageProviderSingleton.initializeGlobalInstance(new AsyncLocalStorage());
8
- const handleRunInputs = (rawInputs, processInputs) => {
8
+ const runInputsToMap = (rawInputs) => {
9
9
  const firstInput = rawInputs[0];
10
10
  let inputs;
11
11
  if (firstInput == null) {
@@ -20,6 +20,9 @@ const handleRunInputs = (rawInputs, processInputs) => {
20
20
  else {
21
21
  inputs = { input: firstInput };
22
22
  }
23
+ return inputs;
24
+ };
25
+ const handleRunInputs = (inputs, processInputs) => {
23
26
  try {
24
27
  return processInputs(inputs);
25
28
  }
@@ -44,11 +47,26 @@ const handleRunOutputs = (rawOutputs, processOutputs) => {
44
47
  return outputs;
45
48
  }
46
49
  };
47
- const getTracingRunTree = (runTree, inputs, getInvocationParams, processInputs) => {
50
+ const handleRunAttachments = (rawInputs, extractAttachments) => {
51
+ if (!extractAttachments) {
52
+ return [undefined, rawInputs];
53
+ }
54
+ try {
55
+ const [attachments, remainingArgs] = extractAttachments(...rawInputs);
56
+ return [attachments, remainingArgs];
57
+ }
58
+ catch (e) {
59
+ console.error("Error occurred during extractAttachments:", e);
60
+ return [undefined, rawInputs];
61
+ }
62
+ };
63
+ const getTracingRunTree = (runTree, inputs, getInvocationParams, processInputs, extractAttachments) => {
48
64
  if (!isTracingEnabled(runTree.tracingEnabled)) {
49
65
  return undefined;
50
66
  }
51
- runTree.inputs = handleRunInputs(inputs, processInputs);
67
+ const [attached, args] = handleRunAttachments(inputs, extractAttachments);
68
+ runTree.attachments = attached;
69
+ runTree.inputs = handleRunInputs(args, processInputs);
52
70
  const invocationParams = getInvocationParams?.(...inputs);
53
71
  if (invocationParams != null) {
54
72
  runTree.extra ??= {};
@@ -210,9 +228,10 @@ const convertSerializableArg = (arg) => {
210
228
  */
211
229
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
212
230
  export function traceable(wrappedFunc, config) {
213
- const { aggregator, argsConfigPath, __finalTracedIteratorKey, processInputs, processOutputs, ...runTreeConfig } = config ?? {};
231
+ const { aggregator, argsConfigPath, __finalTracedIteratorKey, processInputs, processOutputs, extractAttachments, ...runTreeConfig } = config ?? {};
214
232
  const processInputsFn = processInputs ?? ((x) => x);
215
233
  const processOutputsFn = processOutputs ?? ((x) => x);
234
+ const extractAttachmentsFn = extractAttachments ?? ((...x) => [undefined, runInputsToMap(x)]);
216
235
  const traceableFunc = (...args) => {
217
236
  let ensuredConfig;
218
237
  try {
@@ -270,7 +289,7 @@ export function traceable(wrappedFunc, config) {
270
289
  // used for handoff between LangChain.JS and traceable functions
271
290
  if (isRunnableConfigLike(firstArg)) {
272
291
  return [
273
- getTracingRunTree(RunTree.fromRunnableConfig(firstArg, ensuredConfig), restArgs, config?.getInvocationParams, processInputsFn),
292
+ getTracingRunTree(RunTree.fromRunnableConfig(firstArg, ensuredConfig), restArgs, config?.getInvocationParams, processInputsFn, extractAttachmentsFn),
274
293
  restArgs,
275
294
  ];
276
295
  }
@@ -286,7 +305,7 @@ export function traceable(wrappedFunc, config) {
286
305
  if (firstArg === ROOT || isRunTree(firstArg)) {
287
306
  const currentRunTree = getTracingRunTree(firstArg === ROOT
288
307
  ? new RunTree(ensuredConfig)
289
- : firstArg.createChild(ensuredConfig), restArgs, config?.getInvocationParams, processInputsFn);
308
+ : firstArg.createChild(ensuredConfig), restArgs, config?.getInvocationParams, processInputsFn, extractAttachmentsFn);
290
309
  return [currentRunTree, [currentRunTree, ...restArgs]];
291
310
  }
292
311
  // Node.JS uses AsyncLocalStorage (ALS) and AsyncResource
@@ -294,11 +313,11 @@ export function traceable(wrappedFunc, config) {
294
313
  const prevRunFromStore = asyncLocalStorage.getStore();
295
314
  if (isRunTree(prevRunFromStore)) {
296
315
  return [
297
- getTracingRunTree(prevRunFromStore.createChild(ensuredConfig), processedArgs, config?.getInvocationParams, processInputsFn),
316
+ getTracingRunTree(prevRunFromStore.createChild(ensuredConfig), processedArgs, config?.getInvocationParams, processInputsFn, extractAttachmentsFn),
298
317
  processedArgs,
299
318
  ];
300
319
  }
301
- const currentRunTree = getTracingRunTree(new RunTree(ensuredConfig), processedArgs, config?.getInvocationParams, processInputsFn);
320
+ const currentRunTree = getTracingRunTree(new RunTree(ensuredConfig), processedArgs, config?.getInvocationParams, processInputsFn, extractAttachmentsFn);
302
321
  // If a context var is set by LangChain outside of a traceable,
303
322
  // it will be an object with a single property and we should copy
304
323
  // context vars over into the new run tree.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "langsmith",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
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": [
@@ -114,7 +114,7 @@
114
114
  "@faker-js/faker": "^8.4.1",
115
115
  "@jest/globals": "^29.5.0",
116
116
  "@langchain/core": "^0.3.14",
117
- "@langchain/langgraph": "^0.2.18",
117
+ "@langchain/langgraph": "^0.2.20",
118
118
  "@langchain/openai": "^0.3.11",
119
119
  "@opentelemetry/sdk-trace-base": "^1.26.0",
120
120
  "@opentelemetry/sdk-trace-node": "^1.26.0",
@@ -133,6 +133,7 @@
133
133
  "eslint-plugin-prettier": "^4.2.1",
134
134
  "jest": "^29.5.0",
135
135
  "langchain": "^0.3.3",
136
+ "node-fetch": "^2.7.0",
136
137
  "openai": "^4.67.3",
137
138
  "prettier": "^2.8.8",
138
139
  "ts-jest": "^29.1.0",