langsmith 0.3.40 → 0.3.42

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
@@ -152,6 +152,8 @@ class AutoBatchQueue {
152
152
  action: item.action,
153
153
  payload: item.item,
154
154
  otelContext: item.otelContext,
155
+ apiKey: item.apiKey,
156
+ apiUrl: item.apiUrl,
155
157
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
156
158
  itemPromiseResolve: itemPromiseResolve,
157
159
  itemPromise,
@@ -189,6 +191,8 @@ class AutoBatchQueue {
189
191
  action: it.action,
190
192
  item: it.payload,
191
193
  otelContext: it.otelContext,
194
+ apiKey: it.apiKey,
195
+ apiUrl: it.apiUrl,
192
196
  })),
193
197
  () => popped.forEach((it) => it.itemPromiseResolve()),
194
198
  ];
@@ -451,6 +455,11 @@ class Client {
451
455
  }
452
456
  return headers;
453
457
  }
458
+ _getPlatformEndpointPath(path) {
459
+ // Check if apiUrl already ends with /v1 or /v1/ to avoid double /v1/v1/ paths
460
+ const needsV1Prefix = this.apiUrl.slice(-3) !== "/v1" && this.apiUrl.slice(-4) !== "/v1/";
461
+ return needsV1Prefix ? `/v1/platform/${path}` : `/platform/${path}`;
462
+ }
454
463
  async processInputs(inputs) {
455
464
  if (this.hideInputs === false) {
456
465
  return inputs;
@@ -623,14 +632,33 @@ class Client {
623
632
  done();
624
633
  break;
625
634
  }
626
- const batchPromise = this._processBatch(batch, done).catch(console.error);
627
- promises.push(batchPromise);
635
+ const batchesByDestination = batch.reduce((acc, item) => {
636
+ const apiUrl = item.apiUrl ?? this.apiUrl;
637
+ const apiKey = item.apiKey ?? this.apiKey;
638
+ const isDefault = item.apiKey === this.apiKey && item.apiUrl === this.apiUrl;
639
+ const batchKey = isDefault ? "default" : `${apiUrl}|${apiKey}`;
640
+ if (!acc[batchKey]) {
641
+ acc[batchKey] = [];
642
+ }
643
+ acc[batchKey].push(item);
644
+ return acc;
645
+ }, {});
646
+ const batchPromises = [];
647
+ for (const [batchKey, batch] of Object.entries(batchesByDestination)) {
648
+ const batchPromise = this._processBatch(batch, {
649
+ apiUrl: batchKey === "default" ? undefined : batchKey.split("|")[0],
650
+ apiKey: batchKey === "default" ? undefined : batchKey.split("|")[1],
651
+ });
652
+ batchPromises.push(batchPromise);
653
+ }
654
+ // Wait for all batches to complete, then call the overall done callback
655
+ const allBatchesPromise = Promise.all(batchPromises).finally(done);
656
+ promises.push(allBatchesPromise);
628
657
  }
629
658
  return Promise.all(promises);
630
659
  }
631
- async _processBatch(batch, done) {
660
+ async _processBatch(batch, options) {
632
661
  if (!batch.length) {
633
- done();
634
662
  return;
635
663
  }
636
664
  try {
@@ -648,19 +676,16 @@ class Client {
648
676
  };
649
677
  const serverInfo = await this._ensureServerInfo();
650
678
  if (serverInfo?.batch_ingest_config?.use_multipart_endpoint) {
651
- await this.multipartIngestRuns(ingestParams);
679
+ await this.multipartIngestRuns(ingestParams, options);
652
680
  }
653
681
  else {
654
- await this.batchIngestRuns(ingestParams);
682
+ await this.batchIngestRuns(ingestParams, options);
655
683
  }
656
684
  }
657
685
  }
658
686
  catch (e) {
659
687
  console.error("Error exporting batch:", e);
660
688
  }
661
- finally {
662
- done();
663
- }
664
689
  }
665
690
  _sendBatchToOTELTranslator(batch) {
666
691
  if (this.langSmithToOTELTranslator !== undefined) {
@@ -774,11 +799,14 @@ class Client {
774
799
  }
775
800
  return undefined;
776
801
  }
777
- async createRun(run) {
802
+ async createRun(run, options) {
778
803
  if (!this._filterForSampling([run]).length) {
779
804
  return;
780
805
  }
781
- const headers = { ...this.headers, "Content-Type": "application/json" };
806
+ const headers = {
807
+ ...this.headers,
808
+ "Content-Type": "application/json",
809
+ };
782
810
  const session_name = run.project_name;
783
811
  delete run.project_name;
784
812
  const runCreate = await this.prepareRunCreateOrUpdateInputs({
@@ -794,11 +822,16 @@ class Client {
794
822
  action: "create",
795
823
  item: runCreate,
796
824
  otelContext,
825
+ apiKey: options?.apiKey,
826
+ apiUrl: options?.apiUrl,
797
827
  }).catch(console.error);
798
828
  return;
799
829
  }
800
830
  const mergedRunCreateParam = mergeRuntimeEnvIntoRunCreate(runCreate);
801
- const response = await this.caller.call((0, fetch_js_1._getFetchImplementation)(this.debug), `${this.apiUrl}/runs`, {
831
+ if (options?.apiKey !== undefined) {
832
+ headers["x-api-key"] = options.apiKey;
833
+ }
834
+ const response = await this.caller.call((0, fetch_js_1._getFetchImplementation)(this.debug), `${options?.apiUrl ?? this.apiUrl}/runs`, {
802
835
  method: "POST",
803
836
  headers,
804
837
  body: (0, index_js_2.serialize)(mergedRunCreateParam, `Creating run with id: ${mergedRunCreateParam.id}`),
@@ -811,7 +844,7 @@ class Client {
811
844
  * Batch ingest/upsert multiple runs in the Langsmith system.
812
845
  * @param runs
813
846
  */
814
- async batchIngestRuns({ runCreates, runUpdates, }) {
847
+ async batchIngestRuns({ runCreates, runUpdates, }, options) {
815
848
  if (runCreates === undefined && runUpdates === undefined) {
816
849
  return;
817
850
  }
@@ -866,16 +899,19 @@ class Client {
866
899
  .map((item) => item.id)
867
900
  .concat(batchChunks.patch.map((item) => item.id))
868
901
  .join(",");
869
- await this._postBatchIngestRuns((0, index_js_2.serialize)(batchChunks, `Ingesting runs with ids: ${runIds}`));
902
+ await this._postBatchIngestRuns((0, index_js_2.serialize)(batchChunks, `Ingesting runs with ids: ${runIds}`), options);
870
903
  }
871
904
  }
872
- async _postBatchIngestRuns(body) {
905
+ async _postBatchIngestRuns(body, options) {
873
906
  const headers = {
874
907
  ...this.headers,
875
908
  "Content-Type": "application/json",
876
909
  Accept: "application/json",
877
910
  };
878
- const response = await this.batchIngestCaller.call((0, fetch_js_1._getFetchImplementation)(this.debug), `${this.apiUrl}/runs/batch`, {
911
+ if (options?.apiKey !== undefined) {
912
+ headers["x-api-key"] = options.apiKey;
913
+ }
914
+ const response = await this.batchIngestCaller.call((0, fetch_js_1._getFetchImplementation)(this.debug), `${options?.apiUrl ?? this.apiUrl}/runs/batch`, {
879
915
  method: "POST",
880
916
  headers,
881
917
  body: body,
@@ -888,7 +924,7 @@ class Client {
888
924
  * Batch ingest/upsert multiple runs in the Langsmith system.
889
925
  * @param runs
890
926
  */
891
- async multipartIngestRuns({ runCreates, runUpdates, }) {
927
+ async multipartIngestRuns({ runCreates, runUpdates, }, options) {
892
928
  if (runCreates === undefined && runUpdates === undefined) {
893
929
  return;
894
930
  }
@@ -1015,7 +1051,7 @@ class Client {
1015
1051
  accumulatedContext.push(`trace=${payload.trace_id},id=${payload.id}`);
1016
1052
  }
1017
1053
  }
1018
- await this._sendMultipartRequest(accumulatedParts, accumulatedContext.join("; "));
1054
+ await this._sendMultipartRequest(accumulatedParts, accumulatedContext.join("; "), options);
1019
1055
  }
1020
1056
  async _createNodeFetchBody(parts, boundary) {
1021
1057
  // Create multipart form data manually using Blobs
@@ -1080,19 +1116,23 @@ class Client {
1080
1116
  });
1081
1117
  return stream;
1082
1118
  }
1083
- async _sendMultipartRequest(parts, context) {
1119
+ async _sendMultipartRequest(parts, context, options) {
1084
1120
  try {
1085
1121
  // Create multipart form data boundary
1086
1122
  const boundary = "----LangSmithFormBoundary" + Math.random().toString(36).slice(2);
1087
1123
  const body = await ((0, fetch_js_1._globalFetchImplementationIsNodeFetch)()
1088
1124
  ? this._createNodeFetchBody(parts, boundary)
1089
1125
  : this._createMultipartStream(parts, boundary));
1090
- const res = await this.batchIngestCaller.call((0, fetch_js_1._getFetchImplementation)(this.debug), `${this.apiUrl}/runs/multipart`, {
1126
+ const headers = {
1127
+ ...this.headers,
1128
+ "Content-Type": `multipart/form-data; boundary=${boundary}`,
1129
+ };
1130
+ if (options?.apiKey !== undefined) {
1131
+ headers["x-api-key"] = options.apiKey;
1132
+ }
1133
+ const res = await this.batchIngestCaller.call((0, fetch_js_1._getFetchImplementation)(this.debug), `${options?.apiUrl ?? this.apiUrl}/runs/multipart`, {
1091
1134
  method: "POST",
1092
- headers: {
1093
- ...this.headers,
1094
- "Content-Type": `multipart/form-data; boundary=${boundary}`,
1095
- },
1135
+ headers,
1096
1136
  body,
1097
1137
  duplex: "half",
1098
1138
  signal: AbortSignal.timeout(this.timeout_ms),
@@ -1105,7 +1145,7 @@ class Client {
1105
1145
  console.warn(`${e.message.trim()}\n\nContext: ${context}`);
1106
1146
  }
1107
1147
  }
1108
- async updateRun(runId, run) {
1148
+ async updateRun(runId, run, options) {
1109
1149
  (0, _uuid_js_1.assertUuid)(runId);
1110
1150
  if (run.inputs) {
1111
1151
  run.inputs = await this.processInputs(run.inputs);
@@ -1132,6 +1172,8 @@ class Client {
1132
1172
  action: "update",
1133
1173
  item: data,
1134
1174
  otelContext,
1175
+ apiKey: options?.apiKey,
1176
+ apiUrl: options?.apiUrl,
1135
1177
  }).catch(console.error);
1136
1178
  return;
1137
1179
  }
@@ -1140,12 +1182,20 @@ class Client {
1140
1182
  action: "update",
1141
1183
  item: data,
1142
1184
  otelContext,
1185
+ apiKey: options?.apiKey,
1186
+ apiUrl: options?.apiUrl,
1143
1187
  }).catch(console.error);
1144
1188
  }
1145
1189
  return;
1146
1190
  }
1147
- const headers = { ...this.headers, "Content-Type": "application/json" };
1148
- const response = await this.caller.call((0, fetch_js_1._getFetchImplementation)(this.debug), `${this.apiUrl}/runs/${runId}`, {
1191
+ const headers = {
1192
+ ...this.headers,
1193
+ "Content-Type": "application/json",
1194
+ };
1195
+ if (options?.apiKey !== undefined) {
1196
+ headers["x-api-key"] = options.apiKey;
1197
+ }
1198
+ const response = await this.caller.call((0, fetch_js_1._getFetchImplementation)(this.debug), `${options?.apiUrl ?? this.apiUrl}/runs/${runId}`, {
1149
1199
  method: "PATCH",
1150
1200
  headers,
1151
1201
  body: (0, index_js_2.serialize)(run, `Serializing payload to update run with id: ${runId}`),
@@ -3190,7 +3240,7 @@ class Client {
3190
3240
  }
3191
3241
  }
3192
3242
  const datasetIdToUse = datasetId ?? updates[0]?.dataset_id;
3193
- const response = await this.caller.call((0, fetch_js_1._getFetchImplementation)(this.debug), `${this.apiUrl}/v1/platform/datasets/${datasetIdToUse}/examples`, {
3243
+ const response = await this.caller.call((0, fetch_js_1._getFetchImplementation)(this.debug), `${this.apiUrl}${this._getPlatformEndpointPath(`datasets/${datasetIdToUse}/examples`)}`, {
3194
3244
  method: "PATCH",
3195
3245
  headers: this.headers,
3196
3246
  body: formData,
@@ -3268,7 +3318,7 @@ class Client {
3268
3318
  }
3269
3319
  }
3270
3320
  }
3271
- const response = await this.caller.call((0, fetch_js_1._getFetchImplementation)(this.debug), `${this.apiUrl}/v1/platform/datasets/${datasetId}/examples`, {
3321
+ const response = await this.caller.call((0, fetch_js_1._getFetchImplementation)(this.debug), `${this.apiUrl}${this._getPlatformEndpointPath(`datasets/${datasetId}/examples`)}`, {
3272
3322
  method: "POST",
3273
3323
  headers: this.headers,
3274
3324
  body: formData,
package/dist/client.d.ts CHANGED
@@ -242,6 +242,8 @@ type AutoBatchQueueItem = {
242
242
  action: "create" | "update";
243
243
  item: RunCreate | RunUpdate;
244
244
  otelContext?: OTELContext;
245
+ apiKey?: string;
246
+ apiUrl?: string;
245
247
  };
246
248
  type Thread = {
247
249
  filter: string;
@@ -267,6 +269,8 @@ export declare class AutoBatchQueue {
267
269
  itemPromiseResolve: () => void;
268
270
  itemPromise: Promise<void>;
269
271
  size: number;
272
+ apiKey?: string;
273
+ apiUrl?: string;
270
274
  }[];
271
275
  sizeBytes: number;
272
276
  peek(): {
@@ -276,6 +280,8 @@ export declare class AutoBatchQueue {
276
280
  itemPromiseResolve: () => void;
277
281
  itemPromise: Promise<void>;
278
282
  size: number;
283
+ apiKey?: string;
284
+ apiUrl?: string;
279
285
  };
280
286
  push(item: AutoBatchQueueItem): Promise<void>;
281
287
  pop(upToSizeBytes: number): [AutoBatchQueueItem[], () => void];
@@ -317,6 +323,7 @@ export declare class Client implements LangSmithTracingClientInterface {
317
323
  };
318
324
  getHostUrl(): string;
319
325
  private get headers();
326
+ private _getPlatformEndpointPath;
320
327
  private processInputs;
321
328
  private processOutputs;
322
329
  private prepareRunCreateOrUpdateInputs;
@@ -340,7 +347,10 @@ export declare class Client implements LangSmithTracingClientInterface {
340
347
  */
341
348
  flush(): Promise<void>;
342
349
  private _cloneCurrentOTELContext;
343
- createRun(run: CreateRunParams): Promise<void>;
350
+ createRun(run: CreateRunParams, options?: {
351
+ apiKey?: string;
352
+ apiUrl?: string;
353
+ }): Promise<void>;
344
354
  /**
345
355
  * Batch ingest/upsert multiple runs in the Langsmith system.
346
356
  * @param runs
@@ -348,6 +358,9 @@ export declare class Client implements LangSmithTracingClientInterface {
348
358
  batchIngestRuns({ runCreates, runUpdates, }: {
349
359
  runCreates?: RunCreate[];
350
360
  runUpdates?: RunUpdate[];
361
+ }, options?: {
362
+ apiKey?: string;
363
+ apiUrl?: string;
351
364
  }): Promise<void>;
352
365
  private _postBatchIngestRuns;
353
366
  /**
@@ -357,11 +370,17 @@ export declare class Client implements LangSmithTracingClientInterface {
357
370
  multipartIngestRuns({ runCreates, runUpdates, }: {
358
371
  runCreates?: RunCreate[];
359
372
  runUpdates?: RunUpdate[];
373
+ }, options?: {
374
+ apiKey?: string;
375
+ apiUrl?: string;
360
376
  }): Promise<void>;
361
377
  private _createNodeFetchBody;
362
378
  private _createMultipartStream;
363
379
  private _sendMultipartRequest;
364
- updateRun(runId: string, run: RunUpdate): Promise<void>;
380
+ updateRun(runId: string, run: RunUpdate, options?: {
381
+ apiKey?: string;
382
+ apiUrl?: string;
383
+ }): Promise<void>;
365
384
  readRun(runId: string, { loadChildRuns }?: {
366
385
  loadChildRuns: boolean;
367
386
  }): Promise<Run>;
package/dist/client.js CHANGED
@@ -115,6 +115,8 @@ export class AutoBatchQueue {
115
115
  action: item.action,
116
116
  payload: item.item,
117
117
  otelContext: item.otelContext,
118
+ apiKey: item.apiKey,
119
+ apiUrl: item.apiUrl,
118
120
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
119
121
  itemPromiseResolve: itemPromiseResolve,
120
122
  itemPromise,
@@ -152,6 +154,8 @@ export class AutoBatchQueue {
152
154
  action: it.action,
153
155
  item: it.payload,
154
156
  otelContext: it.otelContext,
157
+ apiKey: it.apiKey,
158
+ apiUrl: it.apiUrl,
155
159
  })),
156
160
  () => popped.forEach((it) => it.itemPromiseResolve()),
157
161
  ];
@@ -413,6 +417,11 @@ export class Client {
413
417
  }
414
418
  return headers;
415
419
  }
420
+ _getPlatformEndpointPath(path) {
421
+ // Check if apiUrl already ends with /v1 or /v1/ to avoid double /v1/v1/ paths
422
+ const needsV1Prefix = this.apiUrl.slice(-3) !== "/v1" && this.apiUrl.slice(-4) !== "/v1/";
423
+ return needsV1Prefix ? `/v1/platform/${path}` : `/platform/${path}`;
424
+ }
416
425
  async processInputs(inputs) {
417
426
  if (this.hideInputs === false) {
418
427
  return inputs;
@@ -585,14 +594,33 @@ export class Client {
585
594
  done();
586
595
  break;
587
596
  }
588
- const batchPromise = this._processBatch(batch, done).catch(console.error);
589
- promises.push(batchPromise);
597
+ const batchesByDestination = batch.reduce((acc, item) => {
598
+ const apiUrl = item.apiUrl ?? this.apiUrl;
599
+ const apiKey = item.apiKey ?? this.apiKey;
600
+ const isDefault = item.apiKey === this.apiKey && item.apiUrl === this.apiUrl;
601
+ const batchKey = isDefault ? "default" : `${apiUrl}|${apiKey}`;
602
+ if (!acc[batchKey]) {
603
+ acc[batchKey] = [];
604
+ }
605
+ acc[batchKey].push(item);
606
+ return acc;
607
+ }, {});
608
+ const batchPromises = [];
609
+ for (const [batchKey, batch] of Object.entries(batchesByDestination)) {
610
+ const batchPromise = this._processBatch(batch, {
611
+ apiUrl: batchKey === "default" ? undefined : batchKey.split("|")[0],
612
+ apiKey: batchKey === "default" ? undefined : batchKey.split("|")[1],
613
+ });
614
+ batchPromises.push(batchPromise);
615
+ }
616
+ // Wait for all batches to complete, then call the overall done callback
617
+ const allBatchesPromise = Promise.all(batchPromises).finally(done);
618
+ promises.push(allBatchesPromise);
590
619
  }
591
620
  return Promise.all(promises);
592
621
  }
593
- async _processBatch(batch, done) {
622
+ async _processBatch(batch, options) {
594
623
  if (!batch.length) {
595
- done();
596
624
  return;
597
625
  }
598
626
  try {
@@ -610,19 +638,16 @@ export class Client {
610
638
  };
611
639
  const serverInfo = await this._ensureServerInfo();
612
640
  if (serverInfo?.batch_ingest_config?.use_multipart_endpoint) {
613
- await this.multipartIngestRuns(ingestParams);
641
+ await this.multipartIngestRuns(ingestParams, options);
614
642
  }
615
643
  else {
616
- await this.batchIngestRuns(ingestParams);
644
+ await this.batchIngestRuns(ingestParams, options);
617
645
  }
618
646
  }
619
647
  }
620
648
  catch (e) {
621
649
  console.error("Error exporting batch:", e);
622
650
  }
623
- finally {
624
- done();
625
- }
626
651
  }
627
652
  _sendBatchToOTELTranslator(batch) {
628
653
  if (this.langSmithToOTELTranslator !== undefined) {
@@ -736,11 +761,14 @@ export class Client {
736
761
  }
737
762
  return undefined;
738
763
  }
739
- async createRun(run) {
764
+ async createRun(run, options) {
740
765
  if (!this._filterForSampling([run]).length) {
741
766
  return;
742
767
  }
743
- const headers = { ...this.headers, "Content-Type": "application/json" };
768
+ const headers = {
769
+ ...this.headers,
770
+ "Content-Type": "application/json",
771
+ };
744
772
  const session_name = run.project_name;
745
773
  delete run.project_name;
746
774
  const runCreate = await this.prepareRunCreateOrUpdateInputs({
@@ -756,11 +784,16 @@ export class Client {
756
784
  action: "create",
757
785
  item: runCreate,
758
786
  otelContext,
787
+ apiKey: options?.apiKey,
788
+ apiUrl: options?.apiUrl,
759
789
  }).catch(console.error);
760
790
  return;
761
791
  }
762
792
  const mergedRunCreateParam = mergeRuntimeEnvIntoRunCreate(runCreate);
763
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/runs`, {
793
+ if (options?.apiKey !== undefined) {
794
+ headers["x-api-key"] = options.apiKey;
795
+ }
796
+ const response = await this.caller.call(_getFetchImplementation(this.debug), `${options?.apiUrl ?? this.apiUrl}/runs`, {
764
797
  method: "POST",
765
798
  headers,
766
799
  body: serializePayloadForTracing(mergedRunCreateParam, `Creating run with id: ${mergedRunCreateParam.id}`),
@@ -773,7 +806,7 @@ export class Client {
773
806
  * Batch ingest/upsert multiple runs in the Langsmith system.
774
807
  * @param runs
775
808
  */
776
- async batchIngestRuns({ runCreates, runUpdates, }) {
809
+ async batchIngestRuns({ runCreates, runUpdates, }, options) {
777
810
  if (runCreates === undefined && runUpdates === undefined) {
778
811
  return;
779
812
  }
@@ -828,16 +861,19 @@ export class Client {
828
861
  .map((item) => item.id)
829
862
  .concat(batchChunks.patch.map((item) => item.id))
830
863
  .join(",");
831
- await this._postBatchIngestRuns(serializePayloadForTracing(batchChunks, `Ingesting runs with ids: ${runIds}`));
864
+ await this._postBatchIngestRuns(serializePayloadForTracing(batchChunks, `Ingesting runs with ids: ${runIds}`), options);
832
865
  }
833
866
  }
834
- async _postBatchIngestRuns(body) {
867
+ async _postBatchIngestRuns(body, options) {
835
868
  const headers = {
836
869
  ...this.headers,
837
870
  "Content-Type": "application/json",
838
871
  Accept: "application/json",
839
872
  };
840
- const response = await this.batchIngestCaller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/runs/batch`, {
873
+ if (options?.apiKey !== undefined) {
874
+ headers["x-api-key"] = options.apiKey;
875
+ }
876
+ const response = await this.batchIngestCaller.call(_getFetchImplementation(this.debug), `${options?.apiUrl ?? this.apiUrl}/runs/batch`, {
841
877
  method: "POST",
842
878
  headers,
843
879
  body: body,
@@ -850,7 +886,7 @@ export class Client {
850
886
  * Batch ingest/upsert multiple runs in the Langsmith system.
851
887
  * @param runs
852
888
  */
853
- async multipartIngestRuns({ runCreates, runUpdates, }) {
889
+ async multipartIngestRuns({ runCreates, runUpdates, }, options) {
854
890
  if (runCreates === undefined && runUpdates === undefined) {
855
891
  return;
856
892
  }
@@ -977,7 +1013,7 @@ export class Client {
977
1013
  accumulatedContext.push(`trace=${payload.trace_id},id=${payload.id}`);
978
1014
  }
979
1015
  }
980
- await this._sendMultipartRequest(accumulatedParts, accumulatedContext.join("; "));
1016
+ await this._sendMultipartRequest(accumulatedParts, accumulatedContext.join("; "), options);
981
1017
  }
982
1018
  async _createNodeFetchBody(parts, boundary) {
983
1019
  // Create multipart form data manually using Blobs
@@ -1042,19 +1078,23 @@ export class Client {
1042
1078
  });
1043
1079
  return stream;
1044
1080
  }
1045
- async _sendMultipartRequest(parts, context) {
1081
+ async _sendMultipartRequest(parts, context, options) {
1046
1082
  try {
1047
1083
  // Create multipart form data boundary
1048
1084
  const boundary = "----LangSmithFormBoundary" + Math.random().toString(36).slice(2);
1049
1085
  const body = await (_globalFetchImplementationIsNodeFetch()
1050
1086
  ? this._createNodeFetchBody(parts, boundary)
1051
1087
  : this._createMultipartStream(parts, boundary));
1052
- const res = await this.batchIngestCaller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/runs/multipart`, {
1088
+ const headers = {
1089
+ ...this.headers,
1090
+ "Content-Type": `multipart/form-data; boundary=${boundary}`,
1091
+ };
1092
+ if (options?.apiKey !== undefined) {
1093
+ headers["x-api-key"] = options.apiKey;
1094
+ }
1095
+ const res = await this.batchIngestCaller.call(_getFetchImplementation(this.debug), `${options?.apiUrl ?? this.apiUrl}/runs/multipart`, {
1053
1096
  method: "POST",
1054
- headers: {
1055
- ...this.headers,
1056
- "Content-Type": `multipart/form-data; boundary=${boundary}`,
1057
- },
1097
+ headers,
1058
1098
  body,
1059
1099
  duplex: "half",
1060
1100
  signal: AbortSignal.timeout(this.timeout_ms),
@@ -1067,7 +1107,7 @@ export class Client {
1067
1107
  console.warn(`${e.message.trim()}\n\nContext: ${context}`);
1068
1108
  }
1069
1109
  }
1070
- async updateRun(runId, run) {
1110
+ async updateRun(runId, run, options) {
1071
1111
  assertUuid(runId);
1072
1112
  if (run.inputs) {
1073
1113
  run.inputs = await this.processInputs(run.inputs);
@@ -1094,6 +1134,8 @@ export class Client {
1094
1134
  action: "update",
1095
1135
  item: data,
1096
1136
  otelContext,
1137
+ apiKey: options?.apiKey,
1138
+ apiUrl: options?.apiUrl,
1097
1139
  }).catch(console.error);
1098
1140
  return;
1099
1141
  }
@@ -1102,12 +1144,20 @@ export class Client {
1102
1144
  action: "update",
1103
1145
  item: data,
1104
1146
  otelContext,
1147
+ apiKey: options?.apiKey,
1148
+ apiUrl: options?.apiUrl,
1105
1149
  }).catch(console.error);
1106
1150
  }
1107
1151
  return;
1108
1152
  }
1109
- const headers = { ...this.headers, "Content-Type": "application/json" };
1110
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/runs/${runId}`, {
1153
+ const headers = {
1154
+ ...this.headers,
1155
+ "Content-Type": "application/json",
1156
+ };
1157
+ if (options?.apiKey !== undefined) {
1158
+ headers["x-api-key"] = options.apiKey;
1159
+ }
1160
+ const response = await this.caller.call(_getFetchImplementation(this.debug), `${options?.apiUrl ?? this.apiUrl}/runs/${runId}`, {
1111
1161
  method: "PATCH",
1112
1162
  headers,
1113
1163
  body: serializePayloadForTracing(run, `Serializing payload to update run with id: ${runId}`),
@@ -3152,7 +3202,7 @@ export class Client {
3152
3202
  }
3153
3203
  }
3154
3204
  const datasetIdToUse = datasetId ?? updates[0]?.dataset_id;
3155
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/v1/platform/datasets/${datasetIdToUse}/examples`, {
3205
+ const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}${this._getPlatformEndpointPath(`datasets/${datasetIdToUse}/examples`)}`, {
3156
3206
  method: "PATCH",
3157
3207
  headers: this.headers,
3158
3208
  body: formData,
@@ -3230,7 +3280,7 @@ export class Client {
3230
3280
  }
3231
3281
  }
3232
3282
  }
3233
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/v1/platform/datasets/${datasetId}/examples`, {
3283
+ const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}${this._getPlatformEndpointPath(`datasets/${datasetId}/examples`)}`, {
3234
3284
  method: "POST",
3235
3285
  headers: this.headers,
3236
3286
  body: formData,
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AI_SDK_TOOL_OPERATIONS = exports.AI_SDK_LLM_OPERATIONS = exports.GEN_AI_CHOICE = exports.GEN_AI_ASSISTANT_MESSAGE = exports.GEN_AI_USER_MESSAGE = exports.GEN_AI_SYSTEM_MESSAGE = exports.LANGSMITH_USAGE_METADATA = exports.LANGSMITH_PARENT_RUN_ID = exports.LANGSMITH_DOTTED_ORDER = exports.LANGSMITH_TRACE_ID = exports.LANGSMITH_RUN_ID = exports.LANGSMITH_REQUEST_HEADERS = exports.LANGSMITH_REQUEST_STREAMING = exports.LANGSMITH_RUNTIME = exports.LANGSMITH_TAGS = exports.LANGSMITH_METADATA = exports.LANGSMITH_NAME = exports.LANGSMITH_RUN_TYPE = exports.LANGSMITH_SESSION_NAME = exports.LANGSMITH_SESSION_ID = exports.GEN_AI_USAGE_OUTPUT_TOKEN_DETAILS = exports.GEN_AI_USAGE_INPUT_TOKEN_DETAILS = exports.GEN_AI_RESPONSE_SYSTEM_FINGERPRINT = exports.GEN_AI_RESPONSE_SERVICE_TIER = exports.GEN_AI_RESPONSE_ID = exports.GEN_AI_SERIALIZED_DOC = exports.GEN_AI_SERIALIZED_SIGNATURE = exports.GEN_AI_SERIALIZED_NAME = exports.GEN_AI_REQUEST_EXTRA_BODY = exports.GEN_AI_REQUEST_EXTRA_QUERY = exports.GENAI_COMPLETION = exports.GENAI_PROMPT = exports.GEN_AI_RESPONSE_FINISH_REASONS = exports.GEN_AI_REQUEST_PRESENCE_PENALTY = exports.GEN_AI_REQUEST_FREQUENCY_PENALTY = exports.GEN_AI_REQUEST_TOP_P = exports.GEN_AI_REQUEST_TEMPERATURE = exports.GEN_AI_REQUEST_MAX_TOKENS = exports.GEN_AI_USAGE_TOTAL_TOKENS = exports.GEN_AI_USAGE_OUTPUT_TOKENS = exports.GEN_AI_USAGE_INPUT_TOKENS = exports.GEN_AI_RESPONSE_MODEL = exports.GEN_AI_REQUEST_MODEL = exports.GEN_AI_SYSTEM = exports.GEN_AI_OPERATION_NAME = void 0;
3
+ exports.AI_SDK_TOOL_OPERATIONS = exports.AI_SDK_LLM_OPERATIONS = exports.GEN_AI_CHOICE = exports.GEN_AI_ASSISTANT_MESSAGE = exports.GEN_AI_USER_MESSAGE = exports.GEN_AI_SYSTEM_MESSAGE = exports.LANGSMITH_REFERENCE_EXAMPLE_ID = exports.LANGSMITH_USAGE_METADATA = exports.LANGSMITH_PARENT_RUN_ID = exports.LANGSMITH_DOTTED_ORDER = exports.LANGSMITH_TRACE_ID = exports.LANGSMITH_RUN_ID = exports.LANGSMITH_REQUEST_HEADERS = exports.LANGSMITH_REQUEST_STREAMING = exports.LANGSMITH_RUNTIME = exports.LANGSMITH_TAGS = exports.LANGSMITH_METADATA = exports.LANGSMITH_NAME = exports.LANGSMITH_RUN_TYPE = exports.LANGSMITH_SESSION_NAME = exports.LANGSMITH_SESSION_ID = exports.GEN_AI_USAGE_OUTPUT_TOKEN_DETAILS = exports.GEN_AI_USAGE_INPUT_TOKEN_DETAILS = exports.GEN_AI_RESPONSE_SYSTEM_FINGERPRINT = exports.GEN_AI_RESPONSE_SERVICE_TIER = exports.GEN_AI_RESPONSE_ID = exports.GEN_AI_SERIALIZED_DOC = exports.GEN_AI_SERIALIZED_SIGNATURE = exports.GEN_AI_SERIALIZED_NAME = exports.GEN_AI_REQUEST_EXTRA_BODY = exports.GEN_AI_REQUEST_EXTRA_QUERY = exports.GENAI_COMPLETION = exports.GENAI_PROMPT = exports.GEN_AI_RESPONSE_FINISH_REASONS = exports.GEN_AI_REQUEST_PRESENCE_PENALTY = exports.GEN_AI_REQUEST_FREQUENCY_PENALTY = exports.GEN_AI_REQUEST_TOP_P = exports.GEN_AI_REQUEST_TEMPERATURE = exports.GEN_AI_REQUEST_MAX_TOKENS = exports.GEN_AI_USAGE_TOTAL_TOKENS = exports.GEN_AI_USAGE_OUTPUT_TOKENS = exports.GEN_AI_USAGE_INPUT_TOKENS = exports.GEN_AI_RESPONSE_MODEL = exports.GEN_AI_REQUEST_MODEL = exports.GEN_AI_SYSTEM = exports.GEN_AI_OPERATION_NAME = void 0;
4
4
  // OpenTelemetry GenAI semantic convention attribute names
5
5
  exports.GEN_AI_OPERATION_NAME = "gen_ai.operation.name";
6
6
  exports.GEN_AI_SYSTEM = "gen_ai.system";
@@ -42,6 +42,7 @@ exports.LANGSMITH_TRACE_ID = "langsmith.trace.id";
42
42
  exports.LANGSMITH_DOTTED_ORDER = "langsmith.span.dotted_order";
43
43
  exports.LANGSMITH_PARENT_RUN_ID = "langsmith.span.parent_id";
44
44
  exports.LANGSMITH_USAGE_METADATA = "langsmith.usage_metadata";
45
+ exports.LANGSMITH_REFERENCE_EXAMPLE_ID = "langsmith.reference_example_id";
45
46
  // GenAI event names
46
47
  exports.GEN_AI_SYSTEM_MESSAGE = "gen_ai.system.message";
47
48
  exports.GEN_AI_USER_MESSAGE = "gen_ai.user.message";
@@ -37,6 +37,7 @@ export declare const LANGSMITH_TRACE_ID = "langsmith.trace.id";
37
37
  export declare const LANGSMITH_DOTTED_ORDER = "langsmith.span.dotted_order";
38
38
  export declare const LANGSMITH_PARENT_RUN_ID = "langsmith.span.parent_id";
39
39
  export declare const LANGSMITH_USAGE_METADATA = "langsmith.usage_metadata";
40
+ export declare const LANGSMITH_REFERENCE_EXAMPLE_ID = "langsmith.reference_example_id";
40
41
  export declare const GEN_AI_SYSTEM_MESSAGE = "gen_ai.system.message";
41
42
  export declare const GEN_AI_USER_MESSAGE = "gen_ai.user.message";
42
43
  export declare const GEN_AI_ASSISTANT_MESSAGE = "gen_ai.assistant.message";
@@ -39,6 +39,7 @@ export const LANGSMITH_TRACE_ID = "langsmith.trace.id";
39
39
  export const LANGSMITH_DOTTED_ORDER = "langsmith.span.dotted_order";
40
40
  export const LANGSMITH_PARENT_RUN_ID = "langsmith.span.parent_id";
41
41
  export const LANGSMITH_USAGE_METADATA = "langsmith.usage_metadata";
42
+ export const LANGSMITH_REFERENCE_EXAMPLE_ID = "langsmith.reference_example_id";
42
43
  // GenAI event names
43
44
  export const GEN_AI_SYSTEM_MESSAGE = "gen_ai.system.message";
44
45
  export const GEN_AI_USER_MESSAGE = "gen_ai.user.message";
@@ -79,11 +79,11 @@ class LangSmithOTLPTraceExporter extends exporter_trace_otlp_proto_1.OTLPTraceEx
79
79
  // Configure headers with API key and project if available
80
80
  let defaultHeaderString = (0, env_js_2.getEnvironmentVariable)("OTEL_EXPORTER_OTLP_HEADERS") ?? "";
81
81
  if (!defaultHeaderString) {
82
- const apiKey = (0, env_js_2.getLangSmithEnvironmentVariable)("API_KEY");
82
+ const apiKey = config?.apiKey ?? (0, env_js_2.getLangSmithEnvironmentVariable)("API_KEY");
83
83
  if (apiKey) {
84
84
  defaultHeaderString = `x-api-key=${apiKey}`;
85
85
  }
86
- const project = (0, env_js_2.getLangSmithEnvironmentVariable)("PROJECT");
86
+ const project = config?.projectName ?? (0, env_js_2.getLangSmithEnvironmentVariable)("PROJECT");
87
87
  if (project) {
88
88
  defaultHeaderString += `,Langsmith-Project=${project}`;
89
89
  }
@@ -138,9 +138,16 @@ class LangSmithOTLPTraceExporter extends exporter_trace_otlp_proto_1.OTLPTraceEx
138
138
  // Iterate over all attributes starting with "ai.telemetry.metadata"
139
139
  for (const [key, value] of Object.entries(span.attributes)) {
140
140
  if (key.startsWith("ai.telemetry.metadata.")) {
141
- const metadataKey = key.replace("ai.telemetry.metadata.", "");
142
- span.attributes[`${constants.LANGSMITH_METADATA}.${metadataKey}`] =
143
- value;
141
+ if (key === "ai.telemetry.metadata.ls_project_name") {
142
+ span.attributes[constants.LANGSMITH_SESSION_NAME] = value;
143
+ }
144
+ else if (key === "ai.telemetry.metadata.ls_project_id") {
145
+ span.attributes[constants.LANGSMITH_SESSION_ID] = value;
146
+ }
147
+ else {
148
+ const metadataKey = key.replace("ai.telemetry.metadata.", "");
149
+ span.attributes[`${constants.LANGSMITH_METADATA}.${metadataKey}`] = value;
150
+ }
144
151
  delete span.attributes[key];
145
152
  }
146
153
  }
@@ -24,6 +24,14 @@ export type LangSmithOTLPTraceExporterConfig = ConstructorParameters<typeof OTLP
24
24
  * @returns A transformed version of the span.
25
25
  */
26
26
  transformExportedSpan?: (span: ReadableSpan) => ReadableSpan | Promise<ReadableSpan>;
27
+ /**
28
+ * The API key to use for the exporter.
29
+ */
30
+ apiKey?: string;
31
+ /**
32
+ * The name of the project to export traces to.
33
+ */
34
+ projectName?: string;
27
35
  };
28
36
  /**
29
37
  * LangSmith OpenTelemetry trace exporter that extends the standard OTLP trace exporter
@@ -43,11 +43,11 @@ export class LangSmithOTLPTraceExporter extends OTLPTraceExporter {
43
43
  // Configure headers with API key and project if available
44
44
  let defaultHeaderString = getEnvironmentVariable("OTEL_EXPORTER_OTLP_HEADERS") ?? "";
45
45
  if (!defaultHeaderString) {
46
- const apiKey = getLangSmithEnvironmentVariable("API_KEY");
46
+ const apiKey = config?.apiKey ?? getLangSmithEnvironmentVariable("API_KEY");
47
47
  if (apiKey) {
48
48
  defaultHeaderString = `x-api-key=${apiKey}`;
49
49
  }
50
- const project = getLangSmithEnvironmentVariable("PROJECT");
50
+ const project = config?.projectName ?? getLangSmithEnvironmentVariable("PROJECT");
51
51
  if (project) {
52
52
  defaultHeaderString += `,Langsmith-Project=${project}`;
53
53
  }
@@ -102,9 +102,16 @@ export class LangSmithOTLPTraceExporter extends OTLPTraceExporter {
102
102
  // Iterate over all attributes starting with "ai.telemetry.metadata"
103
103
  for (const [key, value] of Object.entries(span.attributes)) {
104
104
  if (key.startsWith("ai.telemetry.metadata.")) {
105
- const metadataKey = key.replace("ai.telemetry.metadata.", "");
106
- span.attributes[`${constants.LANGSMITH_METADATA}.${metadataKey}`] =
107
- value;
105
+ if (key === "ai.telemetry.metadata.ls_project_name") {
106
+ span.attributes[constants.LANGSMITH_SESSION_NAME] = value;
107
+ }
108
+ else if (key === "ai.telemetry.metadata.ls_project_id") {
109
+ span.attributes[constants.LANGSMITH_SESSION_ID] = value;
110
+ }
111
+ else {
112
+ const metadataKey = key.replace("ai.telemetry.metadata.", "");
113
+ span.attributes[`${constants.LANGSMITH_METADATA}.${metadataKey}`] = value;
114
+ }
108
115
  delete span.attributes[key];
109
116
  }
110
117
  }
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.40";
13
+ exports.__version__ = "0.3.42";
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.40";
6
+ export declare const __version__ = "0.3.42";
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.40";
6
+ export const __version__ = "0.3.42";
@@ -40,9 +40,11 @@ exports.isRunnableConfigLike = isRunnableConfigLike;
40
40
  const uuid = __importStar(require("uuid"));
41
41
  const client_js_1 = require("./client.cjs");
42
42
  const env_js_1 = require("./env.cjs");
43
+ const error_js_1 = require("./utils/error.cjs");
43
44
  const constants_js_1 = require("./singletons/constants.cjs");
44
45
  const env_js_2 = require("./utils/env.cjs");
45
46
  const project_js_1 = require("./utils/project.cjs");
47
+ const env_js_3 = require("./utils/env.cjs");
46
48
  const warn_js_1 = require("./utils/warn.cjs");
47
49
  function stripNonAlphanumeric(input) {
48
50
  return input.replace(/[-:.]/g, "");
@@ -300,6 +302,7 @@ class RunTree {
300
302
  this.trace_id = this.id;
301
303
  }
302
304
  }
305
+ this.replicas = _ensureWriteReplicas(this.replicas);
303
306
  this.execution_order ??= 1;
304
307
  this.child_execution_order ??= 1;
305
308
  if (!this.dotted_order) {
@@ -503,9 +506,12 @@ class RunTree {
503
506
  try {
504
507
  const runtimeEnv = (0, env_js_2.getRuntimeEnvironment)();
505
508
  if (this.replicas && this.replicas.length > 0) {
506
- for (const [projectName] of this.replicas) {
507
- const runCreate = this._remapForProject(projectName, runtimeEnv, true);
508
- await this.client.createRun(runCreate);
509
+ for (const { projectName, apiKey, apiUrl } of this.replicas) {
510
+ const runCreate = this._remapForProject(projectName ?? this.project_name, runtimeEnv, true);
511
+ await this.client.createRun(runCreate, {
512
+ apiKey,
513
+ apiUrl,
514
+ });
509
515
  }
510
516
  }
511
517
  else {
@@ -525,8 +531,8 @@ class RunTree {
525
531
  }
526
532
  async patchRun() {
527
533
  if (this.replicas && this.replicas.length > 0) {
528
- for (const [projectName, updates] of this.replicas) {
529
- const runData = this._remapForProject(projectName);
534
+ for (const { projectName, apiKey, apiUrl, updates } of this.replicas) {
535
+ const runData = this._remapForProject(projectName ?? this.project_name);
530
536
  await this.client.updateRun(runData.id, {
531
537
  inputs: runData.inputs,
532
538
  outputs: runData.outputs,
@@ -542,6 +548,9 @@ class RunTree {
542
548
  extra: runData.extra,
543
549
  attachments: this.attachments,
544
550
  ...updates,
551
+ }, {
552
+ apiKey,
553
+ apiUrl,
545
554
  });
546
555
  }
547
556
  }
@@ -742,3 +751,44 @@ function _parseDottedOrder(dottedOrder) {
742
751
  return [timestamp, uuidStr];
743
752
  });
744
753
  }
754
+ function _getWriteReplicasFromEnv() {
755
+ const envVar = (0, env_js_2.getEnvironmentVariable)("LANGSMITH_RUNS_ENDPOINTS");
756
+ if (!envVar)
757
+ return [];
758
+ try {
759
+ const parsed = JSON.parse(envVar);
760
+ _checkEndpointEnvUnset(parsed);
761
+ return Object.entries(parsed).map(([url, key]) => ({
762
+ apiUrl: url.replace(/\/$/, ""),
763
+ apiKey: key,
764
+ }));
765
+ }
766
+ catch (e) {
767
+ if ((0, error_js_1.isConflictingEndpointsError)(e)) {
768
+ throw e;
769
+ }
770
+ console.warn("Invalid LANGSMITH_RUNS_ENDPOINTS – must be valid JSON mapping of url->apiKey");
771
+ return [];
772
+ }
773
+ }
774
+ function _ensureWriteReplicas(replicas) {
775
+ // If null -> fetch from env
776
+ if (replicas) {
777
+ return replicas.map((replica) => {
778
+ if (Array.isArray(replica)) {
779
+ return {
780
+ projectName: replica[0],
781
+ update: replica[1],
782
+ };
783
+ }
784
+ return replica;
785
+ });
786
+ }
787
+ return _getWriteReplicasFromEnv();
788
+ }
789
+ function _checkEndpointEnvUnset(parsed) {
790
+ if (Object.keys(parsed).length > 0 &&
791
+ (0, env_js_3.getLangSmithEnvironmentVariable)("ENDPOINT")) {
792
+ throw new error_js_1.ConflictingEndpointsError();
793
+ }
794
+ }
@@ -27,7 +27,7 @@ export interface RunTreeConfig {
27
27
  trace_id?: string;
28
28
  dotted_order?: string;
29
29
  attachments?: Attachments;
30
- replicas?: [string, KVMap | undefined][];
30
+ replicas?: Replica[];
31
31
  }
32
32
  export interface RunnableConfigLike {
33
33
  /**
@@ -50,6 +50,15 @@ interface HeadersLike {
50
50
  get(name: string): string | null;
51
51
  set(name: string, value: string): void;
52
52
  }
53
+ type ProjectReplica = [string, KVMap | undefined];
54
+ type WriteReplica = {
55
+ apiUrl?: string;
56
+ apiKey?: string;
57
+ projectName?: string;
58
+ updates?: KVMap | undefined;
59
+ fromEnv?: boolean;
60
+ };
61
+ type Replica = ProjectReplica | WriteReplica;
53
62
  export declare class RunTree implements BaseRun {
54
63
  private static sharedClient;
55
64
  id: string;
@@ -82,7 +91,7 @@ export declare class RunTree implements BaseRun {
82
91
  /**
83
92
  * Projects to replicate this run to with optional updates.
84
93
  */
85
- replicas?: [string, KVMap | undefined][];
94
+ replicas?: WriteReplica[];
86
95
  constructor(originalConfig: RunTreeConfig | RunTree);
87
96
  set metadata(metadata: KVMap);
88
97
  get metadata(): KVMap;
package/dist/run_trees.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import * as uuid from "uuid";
2
2
  import { Client } from "./client.js";
3
3
  import { isTracingEnabled } from "./env.js";
4
+ import { isConflictingEndpointsError, ConflictingEndpointsError, } from "./utils/error.js";
4
5
  import { _LC_CONTEXT_VARIABLES_KEY } from "./singletons/constants.js";
5
6
  import { getEnvironmentVariable, getRuntimeEnvironment, } from "./utils/env.js";
6
7
  import { getDefaultProjectName } from "./utils/project.js";
8
+ import { getLangSmithEnvironmentVariable } from "./utils/env.js";
7
9
  import { warnOnce } from "./utils/warn.js";
8
10
  function stripNonAlphanumeric(input) {
9
11
  return input.replace(/[-:.]/g, "");
@@ -261,6 +263,7 @@ export class RunTree {
261
263
  this.trace_id = this.id;
262
264
  }
263
265
  }
266
+ this.replicas = _ensureWriteReplicas(this.replicas);
264
267
  this.execution_order ??= 1;
265
268
  this.child_execution_order ??= 1;
266
269
  if (!this.dotted_order) {
@@ -464,9 +467,12 @@ export class RunTree {
464
467
  try {
465
468
  const runtimeEnv = getRuntimeEnvironment();
466
469
  if (this.replicas && this.replicas.length > 0) {
467
- for (const [projectName] of this.replicas) {
468
- const runCreate = this._remapForProject(projectName, runtimeEnv, true);
469
- await this.client.createRun(runCreate);
470
+ for (const { projectName, apiKey, apiUrl } of this.replicas) {
471
+ const runCreate = this._remapForProject(projectName ?? this.project_name, runtimeEnv, true);
472
+ await this.client.createRun(runCreate, {
473
+ apiKey,
474
+ apiUrl,
475
+ });
470
476
  }
471
477
  }
472
478
  else {
@@ -486,8 +492,8 @@ export class RunTree {
486
492
  }
487
493
  async patchRun() {
488
494
  if (this.replicas && this.replicas.length > 0) {
489
- for (const [projectName, updates] of this.replicas) {
490
- const runData = this._remapForProject(projectName);
495
+ for (const { projectName, apiKey, apiUrl, updates } of this.replicas) {
496
+ const runData = this._remapForProject(projectName ?? this.project_name);
491
497
  await this.client.updateRun(runData.id, {
492
498
  inputs: runData.inputs,
493
499
  outputs: runData.outputs,
@@ -503,6 +509,9 @@ export class RunTree {
503
509
  extra: runData.extra,
504
510
  attachments: this.attachments,
505
511
  ...updates,
512
+ }, {
513
+ apiKey,
514
+ apiUrl,
506
515
  });
507
516
  }
508
517
  }
@@ -702,3 +711,44 @@ function _parseDottedOrder(dottedOrder) {
702
711
  return [timestamp, uuidStr];
703
712
  });
704
713
  }
714
+ function _getWriteReplicasFromEnv() {
715
+ const envVar = getEnvironmentVariable("LANGSMITH_RUNS_ENDPOINTS");
716
+ if (!envVar)
717
+ return [];
718
+ try {
719
+ const parsed = JSON.parse(envVar);
720
+ _checkEndpointEnvUnset(parsed);
721
+ return Object.entries(parsed).map(([url, key]) => ({
722
+ apiUrl: url.replace(/\/$/, ""),
723
+ apiKey: key,
724
+ }));
725
+ }
726
+ catch (e) {
727
+ if (isConflictingEndpointsError(e)) {
728
+ throw e;
729
+ }
730
+ console.warn("Invalid LANGSMITH_RUNS_ENDPOINTS – must be valid JSON mapping of url->apiKey");
731
+ return [];
732
+ }
733
+ }
734
+ function _ensureWriteReplicas(replicas) {
735
+ // If null -> fetch from env
736
+ if (replicas) {
737
+ return replicas.map((replica) => {
738
+ if (Array.isArray(replica)) {
739
+ return {
740
+ projectName: replica[0],
741
+ update: replica[1],
742
+ };
743
+ }
744
+ return replica;
745
+ });
746
+ }
747
+ return _getWriteReplicasFromEnv();
748
+ }
749
+ function _checkEndpointEnvUnset(parsed) {
750
+ if (Object.keys(parsed).length > 0 &&
751
+ getLangSmithEnvironmentVariable("ENDPOINT")) {
752
+ throw new ConflictingEndpointsError();
753
+ }
754
+ }
@@ -12,6 +12,7 @@ const env_js_2 = require("./utils/env.cjs");
12
12
  const index_js_1 = require("./index.cjs");
13
13
  const otel_js_1 = require("./singletons/otel.cjs");
14
14
  const utils_js_1 = require("./experimental/otel/utils.cjs");
15
+ const constants_js_2 = require("./experimental/otel/constants.cjs");
15
16
  traceable_js_1.AsyncLocalStorageProviderSingleton.initializeGlobalInstance(new node_async_hooks_1.AsyncLocalStorage());
16
17
  /**
17
18
  * Create OpenTelemetry context manager from RunTree if OTEL is enabled.
@@ -29,10 +30,13 @@ function maybeCreateOtelContext(runTree, tracer
29
30
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
30
31
  return (fn) => {
31
32
  const resolvedTracer = tracer ?? otel_trace.getTracer("langsmith", index_js_1.__version__);
33
+ const attributes = {};
34
+ if (runTree.reference_example_id) {
35
+ attributes[constants_js_2.LANGSMITH_REFERENCE_EXAMPLE_ID] =
36
+ runTree.reference_example_id;
37
+ }
32
38
  return resolvedTracer.startActiveSpan(runTree.name, {
33
- attributes: {
34
- "langsmith.traceable": "true",
35
- },
39
+ attributes,
36
40
  }, () => {
37
41
  otel_trace.setSpanContext(otel_context.active(), spanContext);
38
42
  return fn();
package/dist/traceable.js CHANGED
@@ -8,6 +8,7 @@ import { getEnvironmentVariable } from "./utils/env.js";
8
8
  import { __version__ } from "./index.js";
9
9
  import { getOTELTrace, getOTELContext } from "./singletons/otel.js";
10
10
  import { createOtelSpanContextFromRun } from "./experimental/otel/utils.js";
11
+ import { LANGSMITH_REFERENCE_EXAMPLE_ID } from "./experimental/otel/constants.js";
11
12
  AsyncLocalStorageProviderSingleton.initializeGlobalInstance(new AsyncLocalStorage());
12
13
  /**
13
14
  * Create OpenTelemetry context manager from RunTree if OTEL is enabled.
@@ -25,10 +26,13 @@ function maybeCreateOtelContext(runTree, tracer
25
26
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
27
  return (fn) => {
27
28
  const resolvedTracer = tracer ?? otel_trace.getTracer("langsmith", __version__);
29
+ const attributes = {};
30
+ if (runTree.reference_example_id) {
31
+ attributes[LANGSMITH_REFERENCE_EXAMPLE_ID] =
32
+ runTree.reference_example_id;
33
+ }
28
34
  return resolvedTracer.startActiveSpan(runTree.name, {
29
- attributes: {
30
- "langsmith.traceable": "true",
31
- },
35
+ attributes,
32
36
  }, () => {
33
37
  otel_trace.setSpanContext(otel_context.active(), spanContext);
34
38
  return fn();
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LangSmithConflictError = void 0;
3
+ exports.ConflictingEndpointsError = exports.LangSmithConflictError = void 0;
4
4
  exports.printErrorStackTrace = printErrorStackTrace;
5
5
  exports.raiseForStatus = raiseForStatus;
6
+ exports.isConflictingEndpointsError = isConflictingEndpointsError;
6
7
  function getErrorStackTrace(e) {
7
8
  if (typeof e !== "object" || e == null)
8
9
  return undefined;
@@ -96,3 +97,23 @@ async function raiseForStatus(response, context, consume) {
96
97
  err.status = response.status;
97
98
  throw err;
98
99
  }
100
+ const ERR_CONFLICTING_ENDPOINTS = "ERR_CONFLICTING_ENDPOINTS";
101
+ class ConflictingEndpointsError extends Error {
102
+ constructor() {
103
+ super("You cannot provide both LANGSMITH_ENDPOINT / LANGCHAIN_ENDPOINT " +
104
+ "and LANGSMITH_RUNS_ENDPOINTS.");
105
+ Object.defineProperty(this, "code", {
106
+ enumerable: true,
107
+ configurable: true,
108
+ writable: true,
109
+ value: ERR_CONFLICTING_ENDPOINTS
110
+ });
111
+ this.name = "ConflictingEndpointsError"; // helpful in logs
112
+ }
113
+ }
114
+ exports.ConflictingEndpointsError = ConflictingEndpointsError;
115
+ function isConflictingEndpointsError(err) {
116
+ return (typeof err === "object" &&
117
+ err !== null &&
118
+ err.code === ERR_CONFLICTING_ENDPOINTS);
119
+ }
@@ -43,3 +43,8 @@ export declare class LangSmithConflictError extends Error {
43
43
  * @throws {Error} For all other non-ok responses
44
44
  */
45
45
  export declare function raiseForStatus(response: Response, context: string, consume?: boolean): Promise<void>;
46
+ export declare class ConflictingEndpointsError extends Error {
47
+ readonly code = "ERR_CONFLICTING_ENDPOINTS";
48
+ constructor();
49
+ }
50
+ export declare function isConflictingEndpointsError(err: unknown): err is ConflictingEndpointsError;
@@ -90,3 +90,22 @@ export async function raiseForStatus(response, context, consume) {
90
90
  err.status = response.status;
91
91
  throw err;
92
92
  }
93
+ const ERR_CONFLICTING_ENDPOINTS = "ERR_CONFLICTING_ENDPOINTS";
94
+ export class ConflictingEndpointsError extends Error {
95
+ constructor() {
96
+ super("You cannot provide both LANGSMITH_ENDPOINT / LANGCHAIN_ENDPOINT " +
97
+ "and LANGSMITH_RUNS_ENDPOINTS.");
98
+ Object.defineProperty(this, "code", {
99
+ enumerable: true,
100
+ configurable: true,
101
+ writable: true,
102
+ value: ERR_CONFLICTING_ENDPOINTS
103
+ });
104
+ this.name = "ConflictingEndpointsError"; // helpful in logs
105
+ }
106
+ }
107
+ export function isConflictingEndpointsError(err) {
108
+ return (typeof err === "object" &&
109
+ err !== null &&
110
+ err.code === ERR_CONFLICTING_ENDPOINTS);
111
+ }
@@ -18,10 +18,52 @@ function defaultOptions() {
18
18
  function encodeString(str) {
19
19
  return encoder.encode(str);
20
20
  }
21
+ // Shared function to handle well-known types
22
+ function serializeWellKnownTypes(val) {
23
+ if (val && typeof val === "object" && val !== null) {
24
+ if (val instanceof Map) {
25
+ return Object.fromEntries(val);
26
+ }
27
+ else if (val instanceof Set) {
28
+ return Array.from(val);
29
+ }
30
+ else if (val instanceof Date) {
31
+ return val.toISOString();
32
+ }
33
+ else if (val instanceof RegExp) {
34
+ return val.toString();
35
+ }
36
+ else if (val instanceof Error) {
37
+ return {
38
+ name: val.name,
39
+ message: val.message,
40
+ };
41
+ }
42
+ }
43
+ else if (typeof val === "bigint") {
44
+ return val.toString();
45
+ }
46
+ return val;
47
+ }
48
+ // Default replacer function to handle well-known types
49
+ function createDefaultReplacer(userReplacer) {
50
+ return function (key, val) {
51
+ // Apply user replacer first if provided
52
+ if (userReplacer) {
53
+ const userResult = userReplacer.call(this, key, val);
54
+ // If user replacer returned undefined, fall back to our serialization
55
+ if (userResult !== undefined) {
56
+ return userResult;
57
+ }
58
+ }
59
+ // Fall back to our well-known type handling
60
+ return serializeWellKnownTypes(val);
61
+ };
62
+ }
21
63
  // Regular stringify
22
64
  function serialize(obj, errorContext, replacer, spacer, options) {
23
65
  try {
24
- const str = JSON.stringify(obj, replacer, spacer);
66
+ const str = JSON.stringify(obj, createDefaultReplacer(replacer), spacer);
25
67
  return encodeString(str);
26
68
  }
27
69
  catch (e) {
@@ -107,6 +149,8 @@ function decirc(val, k, edgeIndex, stack, parent, depth, options) {
107
149
  }
108
150
  }
109
151
  else {
152
+ // Handle well-known types before Object.keys iteration
153
+ val = serializeWellKnownTypes(val);
110
154
  var keys = Object.keys(val);
111
155
  for (i = 0; i < keys.length; i++) {
112
156
  var key = keys[i];
@@ -193,6 +237,8 @@ function deterministicDecirc(val, k, edgeIndex, stack, parent, depth, options) {
193
237
  }
194
238
  }
195
239
  else {
240
+ // Handle well-known types before Object.keys iteration
241
+ val = serializeWellKnownTypes(val);
196
242
  // Create a temporary object in the required way
197
243
  var tmp = {};
198
244
  var keys = Object.keys(val).sort(compareFunction);
@@ -15,10 +15,52 @@ function defaultOptions() {
15
15
  function encodeString(str) {
16
16
  return encoder.encode(str);
17
17
  }
18
+ // Shared function to handle well-known types
19
+ function serializeWellKnownTypes(val) {
20
+ if (val && typeof val === "object" && val !== null) {
21
+ if (val instanceof Map) {
22
+ return Object.fromEntries(val);
23
+ }
24
+ else if (val instanceof Set) {
25
+ return Array.from(val);
26
+ }
27
+ else if (val instanceof Date) {
28
+ return val.toISOString();
29
+ }
30
+ else if (val instanceof RegExp) {
31
+ return val.toString();
32
+ }
33
+ else if (val instanceof Error) {
34
+ return {
35
+ name: val.name,
36
+ message: val.message,
37
+ };
38
+ }
39
+ }
40
+ else if (typeof val === "bigint") {
41
+ return val.toString();
42
+ }
43
+ return val;
44
+ }
45
+ // Default replacer function to handle well-known types
46
+ function createDefaultReplacer(userReplacer) {
47
+ return function (key, val) {
48
+ // Apply user replacer first if provided
49
+ if (userReplacer) {
50
+ const userResult = userReplacer.call(this, key, val);
51
+ // If user replacer returned undefined, fall back to our serialization
52
+ if (userResult !== undefined) {
53
+ return userResult;
54
+ }
55
+ }
56
+ // Fall back to our well-known type handling
57
+ return serializeWellKnownTypes(val);
58
+ };
59
+ }
18
60
  // Regular stringify
19
61
  export function serialize(obj, errorContext, replacer, spacer, options) {
20
62
  try {
21
- const str = JSON.stringify(obj, replacer, spacer);
63
+ const str = JSON.stringify(obj, createDefaultReplacer(replacer), spacer);
22
64
  return encodeString(str);
23
65
  }
24
66
  catch (e) {
@@ -104,6 +146,8 @@ function decirc(val, k, edgeIndex, stack, parent, depth, options) {
104
146
  }
105
147
  }
106
148
  else {
149
+ // Handle well-known types before Object.keys iteration
150
+ val = serializeWellKnownTypes(val);
107
151
  var keys = Object.keys(val);
108
152
  for (i = 0; i < keys.length; i++) {
109
153
  var key = keys[i];
@@ -190,6 +234,8 @@ function deterministicDecirc(val, k, edgeIndex, stack, parent, depth, options) {
190
234
  }
191
235
  }
192
236
  else {
237
+ // Handle well-known types before Object.keys iteration
238
+ val = serializeWellKnownTypes(val);
193
239
  // Create a temporary object in the required way
194
240
  var tmp = {};
195
241
  var keys = Object.keys(val).sort(compareFunction);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "langsmith",
3
- "version": "0.3.40",
3
+ "version": "0.3.42",
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": [