langsmith 0.2.11 → 0.2.13

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
@@ -533,6 +533,10 @@ class Client {
533
533
  serverInfo.batch_ingest_config?.size_limit_bytes ??
534
534
  exports.DEFAULT_BATCH_SIZE_LIMIT_BYTES);
535
535
  }
536
+ async _getMultiPartSupport() {
537
+ const serverInfo = await this._ensureServerInfo();
538
+ return (serverInfo.instance_flags?.dataset_examples_multipart_enabled ?? false);
539
+ }
536
540
  drainAutoBatchQueue(batchSizeLimit) {
537
541
  while (this.autoBatchQueue.items.length > 0) {
538
542
  const [batch, done] = this.autoBatchQueue.pop(batchSizeLimit);
@@ -830,7 +834,16 @@ class Client {
830
834
  const attachments = allAttachments[payload.id];
831
835
  if (attachments) {
832
836
  delete allAttachments[payload.id];
833
- for (const [name, [contentType, content]] of Object.entries(attachments)) {
837
+ for (const [name, attachment] of Object.entries(attachments)) {
838
+ let contentType;
839
+ let content;
840
+ if (Array.isArray(attachment)) {
841
+ [contentType, content] = attachment;
842
+ }
843
+ else {
844
+ contentType = attachment.mimeType;
845
+ content = attachment.data;
846
+ }
834
847
  // Validate that the attachment name doesn't contain a '.'
835
848
  if (name.includes(".")) {
836
849
  console.warn(`Skipping attachment '${name}' for run ${payload.id}: Invalid attachment name. ` +
@@ -1938,9 +1951,21 @@ class Client {
1938
1951
  async readExample(exampleId) {
1939
1952
  (0, _uuid_js_1.assertUuid)(exampleId);
1940
1953
  const path = `/examples/${exampleId}`;
1941
- return await this._get(path);
1954
+ const rawExample = await this._get(path);
1955
+ const { attachment_urls, ...rest } = rawExample;
1956
+ const example = rest;
1957
+ if (attachment_urls) {
1958
+ // add attachments back to the example
1959
+ example.attachments = Object.entries(attachment_urls).reduce((acc, [key, value]) => {
1960
+ acc[key.slice("attachment.".length)] = {
1961
+ presigned_url: value.presigned_url,
1962
+ };
1963
+ return acc;
1964
+ }, {});
1965
+ }
1966
+ return example;
1942
1967
  }
1943
- async *listExamples({ datasetId, datasetName, exampleIds, asOf, splits, inlineS3Urls, metadata, limit, offset, filter, } = {}) {
1968
+ async *listExamples({ datasetId, datasetName, exampleIds, asOf, splits, inlineS3Urls, metadata, limit, offset, filter, includeAttachments, } = {}) {
1944
1969
  let datasetId_;
1945
1970
  if (datasetId !== undefined && datasetName !== undefined) {
1946
1971
  throw new Error("Must provide either datasetName or datasetId, not both");
@@ -1989,9 +2014,22 @@ class Client {
1989
2014
  if (filter !== undefined) {
1990
2015
  params.append("filter", filter);
1991
2016
  }
2017
+ if (includeAttachments === true) {
2018
+ ["attachment_urls", "outputs", "metadata"].forEach((field) => params.append("select", field));
2019
+ }
1992
2020
  let i = 0;
1993
- for await (const examples of this._getPaginated("/examples", params)) {
1994
- for (const example of examples) {
2021
+ for await (const rawExamples of this._getPaginated("/examples", params)) {
2022
+ for (const rawExample of rawExamples) {
2023
+ const { attachment_urls, ...rest } = rawExample;
2024
+ const example = rest;
2025
+ if (attachment_urls) {
2026
+ example.attachments = Object.entries(attachment_urls).reduce((acc, [key, value]) => {
2027
+ acc[key.slice("attachment.".length)] = {
2028
+ presigned_url: value.presigned_url,
2029
+ };
2030
+ return acc;
2031
+ }, {});
2032
+ }
1995
2033
  yield example;
1996
2034
  i++;
1997
2035
  }
@@ -2662,6 +2700,144 @@ class Client {
2662
2700
  const result = await response.json();
2663
2701
  return this._getPromptUrl(`${owner}/${promptName}${result.commit_hash ? `:${result.commit_hash}` : ""}`);
2664
2702
  }
2703
+ /**
2704
+ * Update examples with attachments using multipart form data.
2705
+ * @param updates List of ExampleUpdateWithAttachments objects to upsert
2706
+ * @returns Promise with the update response
2707
+ */
2708
+ async updateExamplesMultipart(datasetId, updates = []) {
2709
+ if (!(await this._getMultiPartSupport())) {
2710
+ throw new Error("Your LangSmith version does not allow using the multipart examples endpoint, please update to the latest version.");
2711
+ }
2712
+ const formData = new FormData();
2713
+ for (const example of updates) {
2714
+ const exampleId = example.id;
2715
+ // Prepare the main example body
2716
+ const exampleBody = {
2717
+ ...(example.metadata && { metadata: example.metadata }),
2718
+ ...(example.split && { split: example.split }),
2719
+ };
2720
+ // Add main example data
2721
+ const stringifiedExample = (0, index_js_2.stringify)(exampleBody);
2722
+ const exampleBlob = new Blob([stringifiedExample], {
2723
+ type: "application/json",
2724
+ });
2725
+ formData.append(exampleId, exampleBlob);
2726
+ // Add inputs
2727
+ if (example.inputs) {
2728
+ const stringifiedInputs = (0, index_js_2.stringify)(example.inputs);
2729
+ const inputsBlob = new Blob([stringifiedInputs], {
2730
+ type: "application/json",
2731
+ });
2732
+ formData.append(`${exampleId}.inputs`, inputsBlob);
2733
+ }
2734
+ // Add outputs if present
2735
+ if (example.outputs) {
2736
+ const stringifiedOutputs = (0, index_js_2.stringify)(example.outputs);
2737
+ const outputsBlob = new Blob([stringifiedOutputs], {
2738
+ type: "application/json",
2739
+ });
2740
+ formData.append(`${exampleId}.outputs`, outputsBlob);
2741
+ }
2742
+ // Add attachments if present
2743
+ if (example.attachments) {
2744
+ for (const [name, attachment] of Object.entries(example.attachments)) {
2745
+ let mimeType;
2746
+ let data;
2747
+ if (Array.isArray(attachment)) {
2748
+ [mimeType, data] = attachment;
2749
+ }
2750
+ else {
2751
+ mimeType = attachment.mimeType;
2752
+ data = attachment.data;
2753
+ }
2754
+ const attachmentBlob = new Blob([data], {
2755
+ type: `${mimeType}; length=${data.byteLength}`,
2756
+ });
2757
+ formData.append(`${exampleId}.attachment.${name}`, attachmentBlob);
2758
+ }
2759
+ }
2760
+ if (example.attachments_operations) {
2761
+ const stringifiedAttachmentsOperations = (0, index_js_2.stringify)(example.attachments_operations);
2762
+ const attachmentsOperationsBlob = new Blob([stringifiedAttachmentsOperations], {
2763
+ type: "application/json",
2764
+ });
2765
+ formData.append(`${exampleId}.attachments_operations`, attachmentsOperationsBlob);
2766
+ }
2767
+ }
2768
+ const response = await this.caller.call((0, fetch_js_1._getFetchImplementation)(), `${this.apiUrl}/v1/platform/datasets/${datasetId}/examples`, {
2769
+ method: "PATCH",
2770
+ headers: this.headers,
2771
+ body: formData,
2772
+ });
2773
+ const result = await response.json();
2774
+ return result;
2775
+ }
2776
+ /**
2777
+ * Upload examples with attachments using multipart form data.
2778
+ * @param uploads List of ExampleUploadWithAttachments objects to upload
2779
+ * @returns Promise with the upload response
2780
+ */
2781
+ async uploadExamplesMultipart(datasetId, uploads = []) {
2782
+ if (!(await this._getMultiPartSupport())) {
2783
+ throw new Error("Your LangSmith version does not allow using the multipart examples endpoint, please update to the latest version.");
2784
+ }
2785
+ const formData = new FormData();
2786
+ for (const example of uploads) {
2787
+ const exampleId = (example.id ?? uuid.v4()).toString();
2788
+ // Prepare the main example body
2789
+ const exampleBody = {
2790
+ created_at: example.created_at,
2791
+ ...(example.metadata && { metadata: example.metadata }),
2792
+ ...(example.split && { split: example.split }),
2793
+ };
2794
+ // Add main example data
2795
+ const stringifiedExample = (0, index_js_2.stringify)(exampleBody);
2796
+ const exampleBlob = new Blob([stringifiedExample], {
2797
+ type: "application/json",
2798
+ });
2799
+ formData.append(exampleId, exampleBlob);
2800
+ // Add inputs
2801
+ const stringifiedInputs = (0, index_js_2.stringify)(example.inputs);
2802
+ const inputsBlob = new Blob([stringifiedInputs], {
2803
+ type: "application/json",
2804
+ });
2805
+ formData.append(`${exampleId}.inputs`, inputsBlob);
2806
+ // Add outputs if present
2807
+ if (example.outputs) {
2808
+ const stringifiedOutputs = (0, index_js_2.stringify)(example.outputs);
2809
+ const outputsBlob = new Blob([stringifiedOutputs], {
2810
+ type: "application/json",
2811
+ });
2812
+ formData.append(`${exampleId}.outputs`, outputsBlob);
2813
+ }
2814
+ // Add attachments if present
2815
+ if (example.attachments) {
2816
+ for (const [name, attachment] of Object.entries(example.attachments)) {
2817
+ let mimeType;
2818
+ let data;
2819
+ if (Array.isArray(attachment)) {
2820
+ [mimeType, data] = attachment;
2821
+ }
2822
+ else {
2823
+ mimeType = attachment.mimeType;
2824
+ data = attachment.data;
2825
+ }
2826
+ const attachmentBlob = new Blob([data], {
2827
+ type: `${mimeType}; length=${data.byteLength}`,
2828
+ });
2829
+ formData.append(`${exampleId}.attachment.${name}`, attachmentBlob);
2830
+ }
2831
+ }
2832
+ }
2833
+ const response = await this.caller.call((0, fetch_js_1._getFetchImplementation)(), `${this.apiUrl}/v1/platform/datasets/${datasetId}/examples`, {
2834
+ method: "POST",
2835
+ headers: this.headers,
2836
+ body: formData,
2837
+ });
2838
+ const result = await response.json();
2839
+ return result;
2840
+ }
2665
2841
  async updatePrompt(promptIdentifier, options) {
2666
2842
  if (!(await this.promptExists(promptIdentifier))) {
2667
2843
  throw new Error("Prompt does not exist, you must create it first.");
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, Attachments } 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, ExampleUploadWithAttachments, UploadExamplesResponse, ExampleUpdateWithAttachments, UpdateExamplesResponse } from "./schemas.js";
3
3
  import { EvaluationResult, EvaluationResults, RunEvaluator } from "./evaluation/evaluator.js";
4
4
  export interface ClientConfig {
5
5
  apiUrl?: string;
@@ -226,6 +226,7 @@ export declare class Client implements LangSmithTracingClientInterface {
226
226
  private _getCursorPaginatedList;
227
227
  private _filterForSampling;
228
228
  private _getBatchSizeLimitBytes;
229
+ private _getMultiPartSupport;
229
230
  private drainAutoBatchQueue;
230
231
  private _processBatch;
231
232
  private processRunOperation;
@@ -531,7 +532,7 @@ export declare class Client implements LangSmithTracingClientInterface {
531
532
  createLLMExample(input: string, generation: string | undefined, options: CreateExampleOptions): Promise<Example>;
532
533
  createChatExample(input: KVMap[] | LangChainBaseMessage[], generations: KVMap | LangChainBaseMessage | undefined, options: CreateExampleOptions): Promise<Example>;
533
534
  readExample(exampleId: string): Promise<Example>;
534
- listExamples({ datasetId, datasetName, exampleIds, asOf, splits, inlineS3Urls, metadata, limit, offset, filter, }?: {
535
+ listExamples({ datasetId, datasetName, exampleIds, asOf, splits, inlineS3Urls, metadata, limit, offset, filter, includeAttachments, }?: {
535
536
  datasetId?: string;
536
537
  datasetName?: string;
537
538
  exampleIds?: string[];
@@ -542,6 +543,7 @@ export declare class Client implements LangSmithTracingClientInterface {
542
543
  limit?: number;
543
544
  offset?: number;
544
545
  filter?: string;
546
+ includeAttachments?: boolean;
545
547
  }): AsyncIterable<Example>;
546
548
  deleteExample(exampleId: string): Promise<void>;
547
549
  updateExample(exampleId: string, update: ExampleUpdate): Promise<object>;
@@ -726,6 +728,18 @@ export declare class Client implements LangSmithTracingClientInterface {
726
728
  createCommit(promptIdentifier: string, object: any, options?: {
727
729
  parentCommitHash?: string;
728
730
  }): Promise<string>;
731
+ /**
732
+ * Update examples with attachments using multipart form data.
733
+ * @param updates List of ExampleUpdateWithAttachments objects to upsert
734
+ * @returns Promise with the update response
735
+ */
736
+ updateExamplesMultipart(datasetId: string, updates?: ExampleUpdateWithAttachments[]): Promise<UpdateExamplesResponse>;
737
+ /**
738
+ * Upload examples with attachments using multipart form data.
739
+ * @param uploads List of ExampleUploadWithAttachments objects to upload
740
+ * @returns Promise with the upload response
741
+ */
742
+ uploadExamplesMultipart(datasetId: string, uploads?: ExampleUploadWithAttachments[]): Promise<UploadExamplesResponse>;
729
743
  updatePrompt(promptIdentifier: string, options?: {
730
744
  description?: string;
731
745
  readme?: string;
package/dist/client.js CHANGED
@@ -505,6 +505,10 @@ export class Client {
505
505
  serverInfo.batch_ingest_config?.size_limit_bytes ??
506
506
  DEFAULT_BATCH_SIZE_LIMIT_BYTES);
507
507
  }
508
+ async _getMultiPartSupport() {
509
+ const serverInfo = await this._ensureServerInfo();
510
+ return (serverInfo.instance_flags?.dataset_examples_multipart_enabled ?? false);
511
+ }
508
512
  drainAutoBatchQueue(batchSizeLimit) {
509
513
  while (this.autoBatchQueue.items.length > 0) {
510
514
  const [batch, done] = this.autoBatchQueue.pop(batchSizeLimit);
@@ -802,7 +806,16 @@ export class Client {
802
806
  const attachments = allAttachments[payload.id];
803
807
  if (attachments) {
804
808
  delete allAttachments[payload.id];
805
- for (const [name, [contentType, content]] of Object.entries(attachments)) {
809
+ for (const [name, attachment] of Object.entries(attachments)) {
810
+ let contentType;
811
+ let content;
812
+ if (Array.isArray(attachment)) {
813
+ [contentType, content] = attachment;
814
+ }
815
+ else {
816
+ contentType = attachment.mimeType;
817
+ content = attachment.data;
818
+ }
806
819
  // Validate that the attachment name doesn't contain a '.'
807
820
  if (name.includes(".")) {
808
821
  console.warn(`Skipping attachment '${name}' for run ${payload.id}: Invalid attachment name. ` +
@@ -1910,9 +1923,21 @@ export class Client {
1910
1923
  async readExample(exampleId) {
1911
1924
  assertUuid(exampleId);
1912
1925
  const path = `/examples/${exampleId}`;
1913
- return await this._get(path);
1926
+ const rawExample = await this._get(path);
1927
+ const { attachment_urls, ...rest } = rawExample;
1928
+ const example = rest;
1929
+ if (attachment_urls) {
1930
+ // add attachments back to the example
1931
+ example.attachments = Object.entries(attachment_urls).reduce((acc, [key, value]) => {
1932
+ acc[key.slice("attachment.".length)] = {
1933
+ presigned_url: value.presigned_url,
1934
+ };
1935
+ return acc;
1936
+ }, {});
1937
+ }
1938
+ return example;
1914
1939
  }
1915
- async *listExamples({ datasetId, datasetName, exampleIds, asOf, splits, inlineS3Urls, metadata, limit, offset, filter, } = {}) {
1940
+ async *listExamples({ datasetId, datasetName, exampleIds, asOf, splits, inlineS3Urls, metadata, limit, offset, filter, includeAttachments, } = {}) {
1916
1941
  let datasetId_;
1917
1942
  if (datasetId !== undefined && datasetName !== undefined) {
1918
1943
  throw new Error("Must provide either datasetName or datasetId, not both");
@@ -1961,9 +1986,22 @@ export class Client {
1961
1986
  if (filter !== undefined) {
1962
1987
  params.append("filter", filter);
1963
1988
  }
1989
+ if (includeAttachments === true) {
1990
+ ["attachment_urls", "outputs", "metadata"].forEach((field) => params.append("select", field));
1991
+ }
1964
1992
  let i = 0;
1965
- for await (const examples of this._getPaginated("/examples", params)) {
1966
- for (const example of examples) {
1993
+ for await (const rawExamples of this._getPaginated("/examples", params)) {
1994
+ for (const rawExample of rawExamples) {
1995
+ const { attachment_urls, ...rest } = rawExample;
1996
+ const example = rest;
1997
+ if (attachment_urls) {
1998
+ example.attachments = Object.entries(attachment_urls).reduce((acc, [key, value]) => {
1999
+ acc[key.slice("attachment.".length)] = {
2000
+ presigned_url: value.presigned_url,
2001
+ };
2002
+ return acc;
2003
+ }, {});
2004
+ }
1967
2005
  yield example;
1968
2006
  i++;
1969
2007
  }
@@ -2634,6 +2672,144 @@ export class Client {
2634
2672
  const result = await response.json();
2635
2673
  return this._getPromptUrl(`${owner}/${promptName}${result.commit_hash ? `:${result.commit_hash}` : ""}`);
2636
2674
  }
2675
+ /**
2676
+ * Update examples with attachments using multipart form data.
2677
+ * @param updates List of ExampleUpdateWithAttachments objects to upsert
2678
+ * @returns Promise with the update response
2679
+ */
2680
+ async updateExamplesMultipart(datasetId, updates = []) {
2681
+ if (!(await this._getMultiPartSupport())) {
2682
+ throw new Error("Your LangSmith version does not allow using the multipart examples endpoint, please update to the latest version.");
2683
+ }
2684
+ const formData = new FormData();
2685
+ for (const example of updates) {
2686
+ const exampleId = example.id;
2687
+ // Prepare the main example body
2688
+ const exampleBody = {
2689
+ ...(example.metadata && { metadata: example.metadata }),
2690
+ ...(example.split && { split: example.split }),
2691
+ };
2692
+ // Add main example data
2693
+ const stringifiedExample = stringifyForTracing(exampleBody);
2694
+ const exampleBlob = new Blob([stringifiedExample], {
2695
+ type: "application/json",
2696
+ });
2697
+ formData.append(exampleId, exampleBlob);
2698
+ // Add inputs
2699
+ if (example.inputs) {
2700
+ const stringifiedInputs = stringifyForTracing(example.inputs);
2701
+ const inputsBlob = new Blob([stringifiedInputs], {
2702
+ type: "application/json",
2703
+ });
2704
+ formData.append(`${exampleId}.inputs`, inputsBlob);
2705
+ }
2706
+ // Add outputs if present
2707
+ if (example.outputs) {
2708
+ const stringifiedOutputs = stringifyForTracing(example.outputs);
2709
+ const outputsBlob = new Blob([stringifiedOutputs], {
2710
+ type: "application/json",
2711
+ });
2712
+ formData.append(`${exampleId}.outputs`, outputsBlob);
2713
+ }
2714
+ // Add attachments if present
2715
+ if (example.attachments) {
2716
+ for (const [name, attachment] of Object.entries(example.attachments)) {
2717
+ let mimeType;
2718
+ let data;
2719
+ if (Array.isArray(attachment)) {
2720
+ [mimeType, data] = attachment;
2721
+ }
2722
+ else {
2723
+ mimeType = attachment.mimeType;
2724
+ data = attachment.data;
2725
+ }
2726
+ const attachmentBlob = new Blob([data], {
2727
+ type: `${mimeType}; length=${data.byteLength}`,
2728
+ });
2729
+ formData.append(`${exampleId}.attachment.${name}`, attachmentBlob);
2730
+ }
2731
+ }
2732
+ if (example.attachments_operations) {
2733
+ const stringifiedAttachmentsOperations = stringifyForTracing(example.attachments_operations);
2734
+ const attachmentsOperationsBlob = new Blob([stringifiedAttachmentsOperations], {
2735
+ type: "application/json",
2736
+ });
2737
+ formData.append(`${exampleId}.attachments_operations`, attachmentsOperationsBlob);
2738
+ }
2739
+ }
2740
+ const response = await this.caller.call(_getFetchImplementation(), `${this.apiUrl}/v1/platform/datasets/${datasetId}/examples`, {
2741
+ method: "PATCH",
2742
+ headers: this.headers,
2743
+ body: formData,
2744
+ });
2745
+ const result = await response.json();
2746
+ return result;
2747
+ }
2748
+ /**
2749
+ * Upload examples with attachments using multipart form data.
2750
+ * @param uploads List of ExampleUploadWithAttachments objects to upload
2751
+ * @returns Promise with the upload response
2752
+ */
2753
+ async uploadExamplesMultipart(datasetId, uploads = []) {
2754
+ if (!(await this._getMultiPartSupport())) {
2755
+ throw new Error("Your LangSmith version does not allow using the multipart examples endpoint, please update to the latest version.");
2756
+ }
2757
+ const formData = new FormData();
2758
+ for (const example of uploads) {
2759
+ const exampleId = (example.id ?? uuid.v4()).toString();
2760
+ // Prepare the main example body
2761
+ const exampleBody = {
2762
+ created_at: example.created_at,
2763
+ ...(example.metadata && { metadata: example.metadata }),
2764
+ ...(example.split && { split: example.split }),
2765
+ };
2766
+ // Add main example data
2767
+ const stringifiedExample = stringifyForTracing(exampleBody);
2768
+ const exampleBlob = new Blob([stringifiedExample], {
2769
+ type: "application/json",
2770
+ });
2771
+ formData.append(exampleId, exampleBlob);
2772
+ // Add inputs
2773
+ const stringifiedInputs = stringifyForTracing(example.inputs);
2774
+ const inputsBlob = new Blob([stringifiedInputs], {
2775
+ type: "application/json",
2776
+ });
2777
+ formData.append(`${exampleId}.inputs`, inputsBlob);
2778
+ // Add outputs if present
2779
+ if (example.outputs) {
2780
+ const stringifiedOutputs = stringifyForTracing(example.outputs);
2781
+ const outputsBlob = new Blob([stringifiedOutputs], {
2782
+ type: "application/json",
2783
+ });
2784
+ formData.append(`${exampleId}.outputs`, outputsBlob);
2785
+ }
2786
+ // Add attachments if present
2787
+ if (example.attachments) {
2788
+ for (const [name, attachment] of Object.entries(example.attachments)) {
2789
+ let mimeType;
2790
+ let data;
2791
+ if (Array.isArray(attachment)) {
2792
+ [mimeType, data] = attachment;
2793
+ }
2794
+ else {
2795
+ mimeType = attachment.mimeType;
2796
+ data = attachment.data;
2797
+ }
2798
+ const attachmentBlob = new Blob([data], {
2799
+ type: `${mimeType}; length=${data.byteLength}`,
2800
+ });
2801
+ formData.append(`${exampleId}.attachment.${name}`, attachmentBlob);
2802
+ }
2803
+ }
2804
+ }
2805
+ const response = await this.caller.call(_getFetchImplementation(), `${this.apiUrl}/v1/platform/datasets/${datasetId}/examples`, {
2806
+ method: "POST",
2807
+ headers: this.headers,
2808
+ body: formData,
2809
+ });
2810
+ const result = await response.json();
2811
+ return result;
2812
+ }
2637
2813
  async updatePrompt(promptIdentifier, options) {
2638
2814
  if (!(await this.promptExists(promptIdentifier))) {
2639
2815
  throw new Error("Prompt does not exist, you must create it first.");
@@ -38,7 +38,10 @@ class _ExperimentManager {
38
38
  if (!this._data) {
39
39
  throw new Error("Data not provided in this experiment.");
40
40
  }
41
- const unresolvedData = _resolveData(this._data, { client: this.client });
41
+ const unresolvedData = _resolveData(this._data, {
42
+ client: this.client,
43
+ includeAttachments: this._includeAttachments,
44
+ });
42
45
  if (!this._examples) {
43
46
  this._examples = [];
44
47
  }
@@ -169,6 +172,12 @@ class _ExperimentManager {
169
172
  writable: true,
170
173
  value: void 0
171
174
  });
175
+ Object.defineProperty(this, "_includeAttachments", {
176
+ enumerable: true,
177
+ configurable: true,
178
+ writable: true,
179
+ value: void 0
180
+ });
172
181
  this.client = args.client ?? new index_js_1.Client();
173
182
  if (!args.experiment) {
174
183
  this._experimentName = (0, _random_name_js_1.randomName)();
@@ -202,6 +211,7 @@ class _ExperimentManager {
202
211
  this._evaluationResults = args.evaluationResults;
203
212
  this._summaryResults = args.summaryResults;
204
213
  this._numRepetitions = args.numRepetitions;
214
+ this._includeAttachments = args.includeAttachments;
205
215
  }
206
216
  _getExperiment() {
207
217
  if (!this._experiment) {
@@ -289,6 +299,7 @@ class _ExperimentManager {
289
299
  client: this.client,
290
300
  evaluationResults: this._evaluationResults,
291
301
  summaryResults: this._summaryResults,
302
+ includeAttachments: this._includeAttachments,
292
303
  });
293
304
  }
294
305
  async withPredictions(target, options) {
@@ -303,6 +314,7 @@ class _ExperimentManager {
303
314
  yield pred.run;
304
315
  }
305
316
  })(),
317
+ includeAttachments: this._includeAttachments,
306
318
  });
307
319
  }
308
320
  async withEvaluators(evaluators, options) {
@@ -325,6 +337,7 @@ class _ExperimentManager {
325
337
  }
326
338
  })(),
327
339
  summaryResults: this._summaryResults,
340
+ includeAttachments: this._includeAttachments,
328
341
  });
329
342
  }
330
343
  async withSummaryEvaluators(summaryEvaluators) {
@@ -338,6 +351,7 @@ class _ExperimentManager {
338
351
  _runsArray: this._runsArray,
339
352
  evaluationResults: this._evaluationResults,
340
353
  summaryResults: aggregateFeedbackGen,
354
+ includeAttachments: this._includeAttachments,
341
355
  });
342
356
  }
343
357
  async *getResults() {
@@ -388,7 +402,7 @@ class _ExperimentManager {
388
402
  const examples = await this.getExamples();
389
403
  if (maxConcurrency === 0) {
390
404
  for (const example of examples) {
391
- yield await _forward(target, example, this.experimentName, this._metadata, this.client);
405
+ yield await _forward(target, example, this.experimentName, this._metadata, this.client, this._includeAttachments);
392
406
  }
393
407
  }
394
408
  else {
@@ -397,7 +411,7 @@ class _ExperimentManager {
397
411
  });
398
412
  const futures = [];
399
413
  for await (const example of examples) {
400
- futures.push(caller.call(_forward, target, example, this.experimentName, this._metadata, this.client));
414
+ futures.push(caller.call(_forward, target, example, this.experimentName, this._metadata, this.client, this._includeAttachments));
401
415
  }
402
416
  for await (const future of futures) {
403
417
  yield future;
@@ -659,6 +673,7 @@ async function _evaluate(target, fields) {
659
673
  experiment: experiment_ ?? fields.experimentPrefix,
660
674
  runs: newRuns ?? undefined,
661
675
  numRepetitions: fields.numRepetitions ?? 1,
676
+ includeAttachments: standardFields.includeAttachments,
662
677
  }).start();
663
678
  if (_isCallable(target)) {
664
679
  manager = await manager.withPredictions(target, {
@@ -678,7 +693,7 @@ async function _evaluate(target, fields) {
678
693
  await results.processData(manager);
679
694
  return results;
680
695
  }
681
- async function _forward(fn, example, experimentName, metadata, client) {
696
+ async function _forward(fn, example, experimentName, metadata, client, includeAttachments) {
682
697
  let run = null;
683
698
  const _getRun = (r) => {
684
699
  run = r;
@@ -708,16 +723,32 @@ async function _forward(fn, example, experimentName, metadata, client) {
708
723
  // no-op
709
724
  }
710
725
  // Issue with retrieving LangChain callbacks, rely on interop
711
- if (langChainCallbacks === undefined) {
726
+ if (langChainCallbacks === undefined && !includeAttachments) {
712
727
  return await fn.invoke(inputs);
713
728
  }
714
- else {
729
+ else if (langChainCallbacks === undefined && includeAttachments) {
730
+ return await fn.invoke(inputs, {
731
+ attachments: example.attachments,
732
+ });
733
+ }
734
+ else if (!includeAttachments) {
715
735
  return await fn.invoke(inputs, { callbacks: langChainCallbacks });
716
736
  }
737
+ else {
738
+ return await fn.invoke(inputs, {
739
+ attachments: example.attachments,
740
+ callbacks: langChainCallbacks,
741
+ });
742
+ }
717
743
  }, options)
718
744
  : (0, traceable_js_1.traceable)(fn, options);
719
745
  try {
720
- await wrappedFn(example.inputs);
746
+ if (includeAttachments && !("invoke" in fn)) {
747
+ await wrappedFn(example.inputs, { attachments: example.attachments });
748
+ }
749
+ else {
750
+ await wrappedFn(example.inputs);
751
+ }
721
752
  }
722
753
  catch (e) {
723
754
  console.error(`Error running target function: ${e}`);
@@ -747,11 +778,13 @@ function _resolveData(data, options) {
747
778
  if (typeof data === "string" && isUUID) {
748
779
  return options.client.listExamples({
749
780
  datasetId: data,
781
+ includeAttachments: options.includeAttachments,
750
782
  });
751
783
  }
752
784
  if (typeof data === "string") {
753
785
  return options.client.listExamples({
754
786
  datasetName: data,
787
+ includeAttachments: options.includeAttachments,
755
788
  });
756
789
  }
757
790
  return data;
@@ -1,11 +1,15 @@
1
1
  import { Client } from "../index.js";
2
- import { Example, KVMap, Run, TracerSession } from "../schemas.js";
2
+ import { AttachmentInfo, Example, KVMap, Run, TracerSession } from "../schemas.js";
3
3
  import { EvaluationResult, EvaluationResults, RunEvaluator } from "./evaluator.js";
4
4
  import { ComparisonEvaluationResults, ComparativeEvaluator } from "./evaluate_comparative.js";
5
- type StandardTargetT<TInput = any, TOutput = KVMap> = ((input: TInput, config?: KVMap) => Promise<TOutput>) | ((input: TInput, config?: KVMap) => TOutput) | {
6
- invoke: (input: TInput, config?: KVMap) => TOutput;
5
+ export type TargetConfigT = KVMap & {
6
+ attachments?: Record<string, AttachmentInfo>;
7
+ callbacks?: any;
8
+ };
9
+ type StandardTargetT<TInput = any, TOutput = KVMap> = ((input: TInput, config?: TargetConfigT) => Promise<TOutput>) | ((input: TInput, config?: TargetConfigT) => TOutput) | {
10
+ invoke: (input: TInput, config?: TargetConfigT) => TOutput;
7
11
  } | {
8
- invoke: (input: TInput, config?: KVMap) => Promise<TOutput>;
12
+ invoke: (input: TInput, config?: TargetConfigT) => Promise<TOutput>;
9
13
  };
10
14
  type ComparativeTargetT = Array<string> | Array<Promise<ExperimentResults> | ExperimentResults>;
11
15
  export type TargetT<TInput = any, TOutput = KVMap> = StandardTargetT<TInput, TOutput> | ComparativeTargetT;
@@ -39,12 +43,14 @@ export type EvaluatorT = DeprecatedRunEvaluator | DeprecatedFunctionEvaluator |
39
43
  inputs: Record<string, any>;
40
44
  outputs: Record<string, any>;
41
45
  referenceOutputs?: Record<string, any>;
46
+ attachments?: Record<string, any>;
42
47
  }) => EvaluationResult | EvaluationResults) | ((args: {
43
48
  run: Run;
44
49
  example: Example;
45
50
  inputs: Record<string, any>;
46
51
  outputs: Record<string, any>;
47
52
  referenceOutputs?: Record<string, any>;
53
+ attachments?: Record<string, any>;
48
54
  }) => Promise<EvaluationResult | EvaluationResults>);
49
55
  interface _ForwardResults {
50
56
  run: Run;
@@ -61,6 +67,7 @@ interface _ExperimentManagerArgs {
61
67
  examples?: Example[];
62
68
  numRepetitions?: number;
63
69
  _runsArray?: Run[];
70
+ includeAttachments?: boolean;
64
71
  }
65
72
  type BaseEvaluateOptions = {
66
73
  /**
@@ -110,6 +117,11 @@ export interface EvaluateOptions extends BaseEvaluateOptions {
110
117
  * examples, or a generator of examples.
111
118
  */
112
119
  data: DataT;
120
+ /**
121
+ * Whether to use attachments for the experiment.
122
+ * @default false
123
+ */
124
+ includeAttachments?: boolean;
113
125
  }
114
126
  export interface ComparativeEvaluateOptions extends BaseEvaluateOptions {
115
127
  /**
@@ -153,6 +165,7 @@ export declare class _ExperimentManager {
153
165
  _experimentName: string;
154
166
  _metadata: KVMap;
155
167
  _description?: string;
168
+ _includeAttachments?: boolean;
156
169
  get experimentName(): string;
157
170
  getExamples(): Promise<Array<Example>>;
158
171
  setExamples(examples: Example[]): void;
@@ -34,7 +34,10 @@ export class _ExperimentManager {
34
34
  if (!this._data) {
35
35
  throw new Error("Data not provided in this experiment.");
36
36
  }
37
- const unresolvedData = _resolveData(this._data, { client: this.client });
37
+ const unresolvedData = _resolveData(this._data, {
38
+ client: this.client,
39
+ includeAttachments: this._includeAttachments,
40
+ });
38
41
  if (!this._examples) {
39
42
  this._examples = [];
40
43
  }
@@ -165,6 +168,12 @@ export class _ExperimentManager {
165
168
  writable: true,
166
169
  value: void 0
167
170
  });
171
+ Object.defineProperty(this, "_includeAttachments", {
172
+ enumerable: true,
173
+ configurable: true,
174
+ writable: true,
175
+ value: void 0
176
+ });
168
177
  this.client = args.client ?? new Client();
169
178
  if (!args.experiment) {
170
179
  this._experimentName = randomName();
@@ -198,6 +207,7 @@ export class _ExperimentManager {
198
207
  this._evaluationResults = args.evaluationResults;
199
208
  this._summaryResults = args.summaryResults;
200
209
  this._numRepetitions = args.numRepetitions;
210
+ this._includeAttachments = args.includeAttachments;
201
211
  }
202
212
  _getExperiment() {
203
213
  if (!this._experiment) {
@@ -285,6 +295,7 @@ export class _ExperimentManager {
285
295
  client: this.client,
286
296
  evaluationResults: this._evaluationResults,
287
297
  summaryResults: this._summaryResults,
298
+ includeAttachments: this._includeAttachments,
288
299
  });
289
300
  }
290
301
  async withPredictions(target, options) {
@@ -299,6 +310,7 @@ export class _ExperimentManager {
299
310
  yield pred.run;
300
311
  }
301
312
  })(),
313
+ includeAttachments: this._includeAttachments,
302
314
  });
303
315
  }
304
316
  async withEvaluators(evaluators, options) {
@@ -321,6 +333,7 @@ export class _ExperimentManager {
321
333
  }
322
334
  })(),
323
335
  summaryResults: this._summaryResults,
336
+ includeAttachments: this._includeAttachments,
324
337
  });
325
338
  }
326
339
  async withSummaryEvaluators(summaryEvaluators) {
@@ -334,6 +347,7 @@ export class _ExperimentManager {
334
347
  _runsArray: this._runsArray,
335
348
  evaluationResults: this._evaluationResults,
336
349
  summaryResults: aggregateFeedbackGen,
350
+ includeAttachments: this._includeAttachments,
337
351
  });
338
352
  }
339
353
  async *getResults() {
@@ -384,7 +398,7 @@ export class _ExperimentManager {
384
398
  const examples = await this.getExamples();
385
399
  if (maxConcurrency === 0) {
386
400
  for (const example of examples) {
387
- yield await _forward(target, example, this.experimentName, this._metadata, this.client);
401
+ yield await _forward(target, example, this.experimentName, this._metadata, this.client, this._includeAttachments);
388
402
  }
389
403
  }
390
404
  else {
@@ -393,7 +407,7 @@ export class _ExperimentManager {
393
407
  });
394
408
  const futures = [];
395
409
  for await (const example of examples) {
396
- futures.push(caller.call(_forward, target, example, this.experimentName, this._metadata, this.client));
410
+ futures.push(caller.call(_forward, target, example, this.experimentName, this._metadata, this.client, this._includeAttachments));
397
411
  }
398
412
  for await (const future of futures) {
399
413
  yield future;
@@ -654,6 +668,7 @@ async function _evaluate(target, fields) {
654
668
  experiment: experiment_ ?? fields.experimentPrefix,
655
669
  runs: newRuns ?? undefined,
656
670
  numRepetitions: fields.numRepetitions ?? 1,
671
+ includeAttachments: standardFields.includeAttachments,
657
672
  }).start();
658
673
  if (_isCallable(target)) {
659
674
  manager = await manager.withPredictions(target, {
@@ -673,7 +688,7 @@ async function _evaluate(target, fields) {
673
688
  await results.processData(manager);
674
689
  return results;
675
690
  }
676
- async function _forward(fn, example, experimentName, metadata, client) {
691
+ async function _forward(fn, example, experimentName, metadata, client, includeAttachments) {
677
692
  let run = null;
678
693
  const _getRun = (r) => {
679
694
  run = r;
@@ -703,16 +718,32 @@ async function _forward(fn, example, experimentName, metadata, client) {
703
718
  // no-op
704
719
  }
705
720
  // Issue with retrieving LangChain callbacks, rely on interop
706
- if (langChainCallbacks === undefined) {
721
+ if (langChainCallbacks === undefined && !includeAttachments) {
707
722
  return await fn.invoke(inputs);
708
723
  }
709
- else {
724
+ else if (langChainCallbacks === undefined && includeAttachments) {
725
+ return await fn.invoke(inputs, {
726
+ attachments: example.attachments,
727
+ });
728
+ }
729
+ else if (!includeAttachments) {
710
730
  return await fn.invoke(inputs, { callbacks: langChainCallbacks });
711
731
  }
732
+ else {
733
+ return await fn.invoke(inputs, {
734
+ attachments: example.attachments,
735
+ callbacks: langChainCallbacks,
736
+ });
737
+ }
712
738
  }, options)
713
739
  : traceable(fn, options);
714
740
  try {
715
- await wrappedFn(example.inputs);
741
+ if (includeAttachments && !("invoke" in fn)) {
742
+ await wrappedFn(example.inputs, { attachments: example.attachments });
743
+ }
744
+ else {
745
+ await wrappedFn(example.inputs);
746
+ }
716
747
  }
717
748
  catch (e) {
718
749
  console.error(`Error running target function: ${e}`);
@@ -742,11 +773,13 @@ function _resolveData(data, options) {
742
773
  if (typeof data === "string" && isUUID) {
743
774
  return options.client.listExamples({
744
775
  datasetId: data,
776
+ includeAttachments: options.includeAttachments,
745
777
  });
746
778
  }
747
779
  if (typeof data === "string") {
748
780
  return options.client.listExamples({
749
781
  datasetName: data,
782
+ includeAttachments: options.includeAttachments,
750
783
  });
751
784
  }
752
785
  return data;
@@ -23,6 +23,7 @@ class DynamicRunEvaluator {
23
23
  inputs: example?.inputs,
24
24
  outputs: run?.outputs,
25
25
  referenceOutputs: example?.outputs,
26
+ attachments: example?.attachments,
26
27
  }, example);
27
28
  });
28
29
  }
@@ -20,6 +20,7 @@ export class DynamicRunEvaluator {
20
20
  inputs: example?.inputs,
21
21
  outputs: run?.outputs,
22
22
  referenceOutputs: example?.outputs,
23
+ attachments: example?.attachments,
23
24
  }, example);
24
25
  });
25
26
  }
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.11";
11
+ exports.__version__ = "0.2.13";
package/dist/index.d.ts CHANGED
@@ -2,4 +2,4 @@ export { Client, type ClientConfig, type LangSmithTracingClientInterface, } from
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.11";
5
+ export declare const __version__ = "0.2.13";
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.11";
5
+ export const __version__ = "0.2.13";
package/dist/schemas.d.ts CHANGED
@@ -32,8 +32,18 @@ export interface BaseExample {
32
32
  metadata?: KVMap;
33
33
  source_run_id?: string;
34
34
  }
35
+ export interface AttachmentInfo {
36
+ presigned_url: string;
37
+ }
35
38
  export type AttachmentData = Uint8Array | ArrayBuffer;
36
- export type Attachments = Record<string, [string, AttachmentData]>;
39
+ export type AttachmentDescription = {
40
+ mimeType: string;
41
+ data: AttachmentData;
42
+ };
43
+ export type Attachments = Record<string, [
44
+ string,
45
+ AttachmentData
46
+ ] | AttachmentDescription>;
37
47
  /**
38
48
  * A run can represent either a trace (root run)
39
49
  * or a child run (~span).
@@ -177,12 +187,50 @@ export interface ExampleCreate extends BaseExample {
177
187
  created_at?: string;
178
188
  split?: string | string[];
179
189
  }
190
+ export interface ExampleUploadWithAttachments {
191
+ id?: string;
192
+ inputs: KVMap;
193
+ outputs?: KVMap;
194
+ metadata?: KVMap;
195
+ split?: string | string[];
196
+ attachments?: Attachments;
197
+ created_at?: string;
198
+ }
199
+ export interface ExampleUpdateWithAttachments {
200
+ id: string;
201
+ inputs?: KVMap;
202
+ outputs?: KVMap;
203
+ metadata?: KVMap;
204
+ split?: string | string[];
205
+ attachments?: Attachments;
206
+ attachments_operations?: KVMap;
207
+ }
208
+ export interface UploadExamplesResponse {
209
+ count: number;
210
+ example_ids: string[];
211
+ }
212
+ export interface UpdateExamplesResponse extends UploadExamplesResponse {
213
+ }
180
214
  export interface Example extends BaseExample {
215
+ id: string;
216
+ created_at: string;
217
+ modified_at?: string;
218
+ source_run_id?: string;
219
+ runs: Run[];
220
+ attachments?: Record<string, AttachmentInfo>;
221
+ split?: string | string[];
222
+ }
223
+ interface RawAttachmentInfo {
224
+ presigned_url: string;
225
+ s3_url: string;
226
+ }
227
+ export interface RawExample extends BaseExample {
181
228
  id: string;
182
229
  created_at: string;
183
230
  modified_at: string;
184
231
  source_run_id?: string;
185
232
  runs: Run[];
233
+ attachment_urls?: Record<string, RawAttachmentInfo>;
186
234
  }
187
235
  export interface ExampleUpdate {
188
236
  dataset_id?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "langsmith",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
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": [