langsmith 0.1.5 → 0.1.7

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
@@ -23,7 +23,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.Client = exports.Queue = void 0;
26
+ exports.Client = exports.DEFAULT_BATCH_SIZE_LIMIT_BYTES = exports.Queue = void 0;
27
27
  const uuid = __importStar(require("uuid"));
28
28
  const async_caller_js_1 = require("./utils/async_caller.cjs");
29
29
  const messages_js_1 = require("./utils/messages.cjs");
@@ -98,6 +98,18 @@ function assertUuid(str) {
98
98
  throw new Error(`Invalid UUID: ${str}`);
99
99
  }
100
100
  }
101
+ const handle429 = async (response) => {
102
+ if (response?.status === 429) {
103
+ const retryAfter = parseInt(response.headers.get("retry-after") ?? "30", 10) * 1000;
104
+ if (retryAfter > 0) {
105
+ await new Promise((resolve) => setTimeout(resolve, retryAfter));
106
+ // Return directly after calling this check
107
+ return true;
108
+ }
109
+ }
110
+ // Fall back to existing status checks
111
+ return false;
112
+ };
101
113
  class Queue {
102
114
  constructor() {
103
115
  Object.defineProperty(this, "items", {
@@ -135,6 +147,8 @@ class Queue {
135
147
  }
136
148
  }
137
149
  exports.Queue = Queue;
150
+ // 20 MB
151
+ exports.DEFAULT_BATCH_SIZE_LIMIT_BYTES = 20971520;
138
152
  class Client {
139
153
  constructor(config = {}) {
140
154
  Object.defineProperty(this, "apiKey", {
@@ -161,6 +175,12 @@ class Client {
161
175
  writable: true,
162
176
  value: void 0
163
177
  });
178
+ Object.defineProperty(this, "batchIngestCaller", {
179
+ enumerable: true,
180
+ configurable: true,
181
+ writable: true,
182
+ value: void 0
183
+ });
164
184
  Object.defineProperty(this, "timeout_ms", {
165
185
  enumerable: true,
166
186
  configurable: true,
@@ -239,6 +259,12 @@ class Client {
239
259
  writable: true,
240
260
  value: 50
241
261
  });
262
+ Object.defineProperty(this, "serverInfo", {
263
+ enumerable: true,
264
+ configurable: true,
265
+ writable: true,
266
+ value: void 0
267
+ });
242
268
  const defaultConfig = Client.getDefaultClientConfig();
243
269
  this.tracingSampleRate = getTracingSamplingRate();
244
270
  this.apiUrl = trimQuotes(config.apiUrl ?? defaultConfig.apiUrl) ?? "";
@@ -246,6 +272,10 @@ class Client {
246
272
  this.webUrl = trimQuotes(config.webUrl ?? defaultConfig.webUrl);
247
273
  this.timeout_ms = config.timeout_ms ?? 12000;
248
274
  this.caller = new async_caller_js_1.AsyncCaller(config.callerOptions ?? {});
275
+ this.batchIngestCaller = new async_caller_js_1.AsyncCaller({
276
+ ...(config.callerOptions ?? {}),
277
+ onFailedResponseHook: handle429,
278
+ });
249
279
  this.hideInputs = config.hideInputs ?? defaultConfig.hideInputs;
250
280
  this.hideOutputs = config.hideOutputs ?? defaultConfig.hideOutputs;
251
281
  this.autoBatchTracing = config.autoBatchTracing ?? this.autoBatchTracing;
@@ -448,14 +478,16 @@ class Client {
448
478
  if (this.autoBatchQueue.size > 0) {
449
479
  this.autoBatchTimeout = setTimeout(() => {
450
480
  this.autoBatchTimeout = undefined;
451
- void this.drainAutoBatchQueue();
481
+ // This error would happen in the background and is uncatchable
482
+ // from the outside. So just log instead.
483
+ void this.drainAutoBatchQueue().catch(console.error);
452
484
  }, oldTimeout
453
485
  ? this.autoBatchAggregationDelayMs
454
486
  : this.autoBatchInitialDelayMs);
455
487
  }
456
488
  return itemPromise;
457
489
  }
458
- async batchEndpointIsSupported() {
490
+ async _getServerInfo() {
459
491
  const response = await fetch(`${this.apiUrl}/info`, {
460
492
  method: "GET",
461
493
  headers: { Accept: "application/json" },
@@ -465,6 +497,15 @@ class Client {
465
497
  // consume the response body to release the connection
466
498
  // https://undici.nodejs.org/#/?id=garbage-collection
467
499
  await response.text();
500
+ throw new Error("Failed to retrieve server info.");
501
+ }
502
+ return response.json();
503
+ }
504
+ async batchEndpointIsSupported() {
505
+ try {
506
+ this.serverInfo = await this._getServerInfo();
507
+ }
508
+ catch (e) {
468
509
  return false;
469
510
  }
470
511
  return true;
@@ -487,7 +528,7 @@ class Client {
487
528
  void this.processRunOperation({
488
529
  action: "create",
489
530
  item: runCreate,
490
- });
531
+ }).catch(console.error);
491
532
  return;
492
533
  }
493
534
  const mergedRunCreateParams = await mergeRuntimeEnvIntoRunCreates([
@@ -534,11 +575,11 @@ class Client {
534
575
  preparedCreateParams = Object.values(createById);
535
576
  preparedUpdateParams = standaloneUpdates;
536
577
  }
537
- const body = {
578
+ const rawBatch = {
538
579
  post: this._filterForSampling(preparedCreateParams),
539
580
  patch: this._filterForSampling(preparedUpdateParams, true),
540
581
  };
541
- if (!body.post.length && !body.patch.length) {
582
+ if (!rawBatch.post.length && !rawBatch.patch.length) {
542
583
  return;
543
584
  }
544
585
  preparedCreateParams = await mergeRuntimeEnvIntoRunCreates(preparedCreateParams);
@@ -547,33 +588,58 @@ class Client {
547
588
  }
548
589
  if (!this.batchEndpointSupported) {
549
590
  this.autoBatchTracing = false;
550
- for (const preparedCreateParam of body.post) {
591
+ for (const preparedCreateParam of rawBatch.post) {
551
592
  await this.createRun(preparedCreateParam);
552
593
  }
553
- for (const preparedUpdateParam of body.patch) {
594
+ for (const preparedUpdateParam of rawBatch.patch) {
554
595
  if (preparedUpdateParam.id !== undefined) {
555
596
  await this.updateRun(preparedUpdateParam.id, preparedUpdateParam);
556
597
  }
557
598
  }
558
599
  return;
559
600
  }
601
+ const sizeLimitBytes = this.serverInfo?.batch_ingest_config?.size_limit_bytes ??
602
+ exports.DEFAULT_BATCH_SIZE_LIMIT_BYTES;
603
+ const batchChunks = {
604
+ post: [],
605
+ patch: [],
606
+ };
607
+ let currentBatchSizeBytes = 0;
608
+ for (const k of ["post", "patch"]) {
609
+ const key = k;
610
+ const batchItems = rawBatch[key].reverse();
611
+ let batchItem = batchItems.pop();
612
+ while (batchItem !== undefined) {
613
+ const stringifiedBatchItem = JSON.stringify(batchItem);
614
+ if (currentBatchSizeBytes > 0 &&
615
+ currentBatchSizeBytes + stringifiedBatchItem.length > sizeLimitBytes) {
616
+ await this._postBatchIngestRuns(JSON.stringify(batchChunks));
617
+ currentBatchSizeBytes = 0;
618
+ batchChunks.post = [];
619
+ batchChunks.patch = [];
620
+ }
621
+ currentBatchSizeBytes += stringifiedBatchItem.length;
622
+ batchChunks[key].push(batchItem);
623
+ batchItem = batchItems.pop();
624
+ }
625
+ }
626
+ if (batchChunks.post.length > 0 || batchChunks.patch.length > 0) {
627
+ await this._postBatchIngestRuns(JSON.stringify(batchChunks));
628
+ }
629
+ }
630
+ async _postBatchIngestRuns(body) {
560
631
  const headers = {
561
632
  ...this.headers,
562
633
  "Content-Type": "application/json",
563
634
  Accept: "application/json",
564
635
  };
565
- try {
566
- const response = await this.caller.call(fetch, `${this.apiUrl}/runs/batch`, {
567
- method: "POST",
568
- headers,
569
- body: JSON.stringify(body),
570
- signal: AbortSignal.timeout(this.timeout_ms),
571
- });
572
- await raiseForStatus(response, "batch create run");
573
- }
574
- catch (e) {
575
- console.error(`Failed to batch create runs: ${e}`);
576
- }
636
+ const response = await this.batchIngestCaller.call(fetch, `${this.apiUrl}/runs/batch`, {
637
+ method: "POST",
638
+ headers,
639
+ body: body,
640
+ signal: AbortSignal.timeout(this.timeout_ms),
641
+ });
642
+ await raiseForStatus(response, "batch create run");
577
643
  }
578
644
  async updateRun(runId, run) {
579
645
  assertUuid(runId);
@@ -598,7 +664,7 @@ class Client {
598
664
  return;
599
665
  }
600
666
  else {
601
- void this.processRunOperation({ action: "update", item: data });
667
+ void this.processRunOperation({ action: "update", item: data }).catch(console.error);
602
668
  }
603
669
  return;
604
670
  }
package/dist/client.d.ts CHANGED
@@ -72,11 +72,13 @@ export declare class Queue<T> {
72
72
  push(item: T): Promise<void>;
73
73
  pop(upToN: number): [T[], () => void];
74
74
  }
75
+ export declare const DEFAULT_BATCH_SIZE_LIMIT_BYTES = 20971520;
75
76
  export declare class Client {
76
77
  private apiKey?;
77
78
  private apiUrl;
78
79
  private webUrl?;
79
80
  private caller;
81
+ private batchIngestCaller;
80
82
  private timeout_ms;
81
83
  private _tenantId;
82
84
  private hideInputs?;
@@ -90,6 +92,7 @@ export declare class Client {
90
92
  private autoBatchTimeout;
91
93
  private autoBatchInitialDelayMs;
92
94
  private autoBatchAggregationDelayMs;
95
+ private serverInfo;
93
96
  constructor(config?: ClientConfig);
94
97
  static getDefaultClientConfig(): {
95
98
  apiUrl: string;
@@ -110,6 +113,7 @@ export declare class Client {
110
113
  private _filterForSampling;
111
114
  private drainAutoBatchQueue;
112
115
  private processRunOperation;
116
+ protected _getServerInfo(): Promise<any>;
113
117
  protected batchEndpointIsSupported(): Promise<boolean>;
114
118
  createRun(run: CreateRunParams): Promise<void>;
115
119
  /**
@@ -120,6 +124,7 @@ export declare class Client {
120
124
  runCreates?: RunCreate[];
121
125
  runUpdates?: RunUpdate[];
122
126
  }): Promise<void>;
127
+ private _postBatchIngestRuns;
123
128
  updateRun(runId: string, run: RunUpdate): Promise<void>;
124
129
  readRun(runId: string, { loadChildRuns }?: {
125
130
  loadChildRuns: boolean;
package/dist/client.js CHANGED
@@ -72,6 +72,18 @@ function assertUuid(str) {
72
72
  throw new Error(`Invalid UUID: ${str}`);
73
73
  }
74
74
  }
75
+ const handle429 = async (response) => {
76
+ if (response?.status === 429) {
77
+ const retryAfter = parseInt(response.headers.get("retry-after") ?? "30", 10) * 1000;
78
+ if (retryAfter > 0) {
79
+ await new Promise((resolve) => setTimeout(resolve, retryAfter));
80
+ // Return directly after calling this check
81
+ return true;
82
+ }
83
+ }
84
+ // Fall back to existing status checks
85
+ return false;
86
+ };
75
87
  export class Queue {
76
88
  constructor() {
77
89
  Object.defineProperty(this, "items", {
@@ -108,6 +120,8 @@ export class Queue {
108
120
  return [popped.map((it) => it[0]), () => popped.forEach((it) => it[1]())];
109
121
  }
110
122
  }
123
+ // 20 MB
124
+ export const DEFAULT_BATCH_SIZE_LIMIT_BYTES = 20971520;
111
125
  export class Client {
112
126
  constructor(config = {}) {
113
127
  Object.defineProperty(this, "apiKey", {
@@ -134,6 +148,12 @@ export class Client {
134
148
  writable: true,
135
149
  value: void 0
136
150
  });
151
+ Object.defineProperty(this, "batchIngestCaller", {
152
+ enumerable: true,
153
+ configurable: true,
154
+ writable: true,
155
+ value: void 0
156
+ });
137
157
  Object.defineProperty(this, "timeout_ms", {
138
158
  enumerable: true,
139
159
  configurable: true,
@@ -212,6 +232,12 @@ export class Client {
212
232
  writable: true,
213
233
  value: 50
214
234
  });
235
+ Object.defineProperty(this, "serverInfo", {
236
+ enumerable: true,
237
+ configurable: true,
238
+ writable: true,
239
+ value: void 0
240
+ });
215
241
  const defaultConfig = Client.getDefaultClientConfig();
216
242
  this.tracingSampleRate = getTracingSamplingRate();
217
243
  this.apiUrl = trimQuotes(config.apiUrl ?? defaultConfig.apiUrl) ?? "";
@@ -219,6 +245,10 @@ export class Client {
219
245
  this.webUrl = trimQuotes(config.webUrl ?? defaultConfig.webUrl);
220
246
  this.timeout_ms = config.timeout_ms ?? 12000;
221
247
  this.caller = new AsyncCaller(config.callerOptions ?? {});
248
+ this.batchIngestCaller = new AsyncCaller({
249
+ ...(config.callerOptions ?? {}),
250
+ onFailedResponseHook: handle429,
251
+ });
222
252
  this.hideInputs = config.hideInputs ?? defaultConfig.hideInputs;
223
253
  this.hideOutputs = config.hideOutputs ?? defaultConfig.hideOutputs;
224
254
  this.autoBatchTracing = config.autoBatchTracing ?? this.autoBatchTracing;
@@ -421,14 +451,16 @@ export class Client {
421
451
  if (this.autoBatchQueue.size > 0) {
422
452
  this.autoBatchTimeout = setTimeout(() => {
423
453
  this.autoBatchTimeout = undefined;
424
- void this.drainAutoBatchQueue();
454
+ // This error would happen in the background and is uncatchable
455
+ // from the outside. So just log instead.
456
+ void this.drainAutoBatchQueue().catch(console.error);
425
457
  }, oldTimeout
426
458
  ? this.autoBatchAggregationDelayMs
427
459
  : this.autoBatchInitialDelayMs);
428
460
  }
429
461
  return itemPromise;
430
462
  }
431
- async batchEndpointIsSupported() {
463
+ async _getServerInfo() {
432
464
  const response = await fetch(`${this.apiUrl}/info`, {
433
465
  method: "GET",
434
466
  headers: { Accept: "application/json" },
@@ -438,6 +470,15 @@ export class Client {
438
470
  // consume the response body to release the connection
439
471
  // https://undici.nodejs.org/#/?id=garbage-collection
440
472
  await response.text();
473
+ throw new Error("Failed to retrieve server info.");
474
+ }
475
+ return response.json();
476
+ }
477
+ async batchEndpointIsSupported() {
478
+ try {
479
+ this.serverInfo = await this._getServerInfo();
480
+ }
481
+ catch (e) {
441
482
  return false;
442
483
  }
443
484
  return true;
@@ -460,7 +501,7 @@ export class Client {
460
501
  void this.processRunOperation({
461
502
  action: "create",
462
503
  item: runCreate,
463
- });
504
+ }).catch(console.error);
464
505
  return;
465
506
  }
466
507
  const mergedRunCreateParams = await mergeRuntimeEnvIntoRunCreates([
@@ -507,11 +548,11 @@ export class Client {
507
548
  preparedCreateParams = Object.values(createById);
508
549
  preparedUpdateParams = standaloneUpdates;
509
550
  }
510
- const body = {
551
+ const rawBatch = {
511
552
  post: this._filterForSampling(preparedCreateParams),
512
553
  patch: this._filterForSampling(preparedUpdateParams, true),
513
554
  };
514
- if (!body.post.length && !body.patch.length) {
555
+ if (!rawBatch.post.length && !rawBatch.patch.length) {
515
556
  return;
516
557
  }
517
558
  preparedCreateParams = await mergeRuntimeEnvIntoRunCreates(preparedCreateParams);
@@ -520,33 +561,58 @@ export class Client {
520
561
  }
521
562
  if (!this.batchEndpointSupported) {
522
563
  this.autoBatchTracing = false;
523
- for (const preparedCreateParam of body.post) {
564
+ for (const preparedCreateParam of rawBatch.post) {
524
565
  await this.createRun(preparedCreateParam);
525
566
  }
526
- for (const preparedUpdateParam of body.patch) {
567
+ for (const preparedUpdateParam of rawBatch.patch) {
527
568
  if (preparedUpdateParam.id !== undefined) {
528
569
  await this.updateRun(preparedUpdateParam.id, preparedUpdateParam);
529
570
  }
530
571
  }
531
572
  return;
532
573
  }
574
+ const sizeLimitBytes = this.serverInfo?.batch_ingest_config?.size_limit_bytes ??
575
+ DEFAULT_BATCH_SIZE_LIMIT_BYTES;
576
+ const batchChunks = {
577
+ post: [],
578
+ patch: [],
579
+ };
580
+ let currentBatchSizeBytes = 0;
581
+ for (const k of ["post", "patch"]) {
582
+ const key = k;
583
+ const batchItems = rawBatch[key].reverse();
584
+ let batchItem = batchItems.pop();
585
+ while (batchItem !== undefined) {
586
+ const stringifiedBatchItem = JSON.stringify(batchItem);
587
+ if (currentBatchSizeBytes > 0 &&
588
+ currentBatchSizeBytes + stringifiedBatchItem.length > sizeLimitBytes) {
589
+ await this._postBatchIngestRuns(JSON.stringify(batchChunks));
590
+ currentBatchSizeBytes = 0;
591
+ batchChunks.post = [];
592
+ batchChunks.patch = [];
593
+ }
594
+ currentBatchSizeBytes += stringifiedBatchItem.length;
595
+ batchChunks[key].push(batchItem);
596
+ batchItem = batchItems.pop();
597
+ }
598
+ }
599
+ if (batchChunks.post.length > 0 || batchChunks.patch.length > 0) {
600
+ await this._postBatchIngestRuns(JSON.stringify(batchChunks));
601
+ }
602
+ }
603
+ async _postBatchIngestRuns(body) {
533
604
  const headers = {
534
605
  ...this.headers,
535
606
  "Content-Type": "application/json",
536
607
  Accept: "application/json",
537
608
  };
538
- try {
539
- const response = await this.caller.call(fetch, `${this.apiUrl}/runs/batch`, {
540
- method: "POST",
541
- headers,
542
- body: JSON.stringify(body),
543
- signal: AbortSignal.timeout(this.timeout_ms),
544
- });
545
- await raiseForStatus(response, "batch create run");
546
- }
547
- catch (e) {
548
- console.error(`Failed to batch create runs: ${e}`);
549
- }
609
+ const response = await this.batchIngestCaller.call(fetch, `${this.apiUrl}/runs/batch`, {
610
+ method: "POST",
611
+ headers,
612
+ body: body,
613
+ signal: AbortSignal.timeout(this.timeout_ms),
614
+ });
615
+ await raiseForStatus(response, "batch create run");
550
616
  }
551
617
  async updateRun(runId, run) {
552
618
  assertUuid(runId);
@@ -571,7 +637,7 @@ export class Client {
571
637
  return;
572
638
  }
573
639
  else {
574
- void this.processRunOperation({ action: "update", item: data });
640
+ void this.processRunOperation({ action: "update", item: data }).catch(console.error);
575
641
  }
576
642
  return;
577
643
  }
package/dist/index.cjs CHANGED
@@ -6,4 +6,4 @@ Object.defineProperty(exports, "Client", { enumerable: true, get: function () {
6
6
  var run_trees_js_1 = require("./run_trees.cjs");
7
7
  Object.defineProperty(exports, "RunTree", { enumerable: true, get: function () { return run_trees_js_1.RunTree; } });
8
8
  // Update using yarn bump-version
9
- exports.__version__ = "0.1.5";
9
+ exports.__version__ = "0.1.7";
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { Client } from "./client.js";
2
2
  export type { Dataset, Example, TracerSession, Run, Feedback, } from "./schemas.js";
3
3
  export { RunTree, type RunTreeConfig } from "./run_trees.js";
4
- export declare const __version__ = "0.1.5";
4
+ export declare const __version__ = "0.1.7";
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export { Client } from "./client.js";
2
2
  export { RunTree } from "./run_trees.js";
3
3
  // Update using yarn bump-version
4
- export const __version__ = "0.1.5";
4
+ export const __version__ = "0.1.7";
@@ -52,13 +52,21 @@ class AsyncCaller {
52
52
  writable: true,
53
53
  value: void 0
54
54
  });
55
+ Object.defineProperty(this, "onFailedResponseHook", {
56
+ enumerable: true,
57
+ configurable: true,
58
+ writable: true,
59
+ value: void 0
60
+ });
55
61
  this.maxConcurrency = params.maxConcurrency ?? Infinity;
56
62
  this.maxRetries = params.maxRetries ?? 6;
57
63
  const PQueue = "default" in p_queue_1.default ? p_queue_1.default.default : p_queue_1.default;
58
64
  this.queue = new PQueue({ concurrency: this.maxConcurrency });
65
+ this.onFailedResponseHook = params?.onFailedResponseHook;
59
66
  }
60
67
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
68
  call(callable, ...args) {
69
+ const onFailedResponseHook = this.onFailedResponseHook;
62
70
  return this.queue.add(() => (0, p_retry_1.default)(() => callable(...args).catch((error) => {
63
71
  // eslint-disable-next-line no-instanceof/no-instanceof
64
72
  if (error instanceof Error) {
@@ -68,7 +76,7 @@ class AsyncCaller {
68
76
  throw new Error(error);
69
77
  }
70
78
  }), {
71
- onFailedAttempt(error) {
79
+ async onFailedAttempt(error) {
72
80
  if (error.message.startsWith("Cancel") ||
73
81
  error.message.startsWith("TimeoutError") ||
74
82
  error.message.startsWith("AbortError")) {
@@ -79,7 +87,8 @@ class AsyncCaller {
79
87
  throw error;
80
88
  }
81
89
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
82
- const status = error?.response?.status;
90
+ const response = error?.response;
91
+ const status = response?.status;
83
92
  if (status) {
84
93
  if (STATUS_NO_RETRY.includes(+status)) {
85
94
  throw error;
@@ -87,6 +96,9 @@ class AsyncCaller {
87
96
  else if (STATUS_IGNORE.includes(+status)) {
88
97
  return;
89
98
  }
99
+ if (onFailedResponseHook) {
100
+ await onFailedResponseHook(response);
101
+ }
90
102
  }
91
103
  },
92
104
  // If needed we can change some of the defaults here,
@@ -1,3 +1,4 @@
1
+ type ResponseCallback = (response?: Response) => Promise<boolean>;
1
2
  export interface AsyncCallerParams {
2
3
  /**
3
4
  * The maximum number of concurrent calls that can be made.
@@ -9,6 +10,7 @@ export interface AsyncCallerParams {
9
10
  * with an exponential backoff between each attempt. Defaults to 6.
10
11
  */
11
12
  maxRetries?: number;
13
+ onFailedResponseHook?: ResponseCallback;
12
14
  }
13
15
  export interface AsyncCallerCallOptions {
14
16
  signal?: AbortSignal;
@@ -30,8 +32,10 @@ export declare class AsyncCaller {
30
32
  protected maxConcurrency: AsyncCallerParams["maxConcurrency"];
31
33
  protected maxRetries: AsyncCallerParams["maxRetries"];
32
34
  private queue;
35
+ private onFailedResponseHook?;
33
36
  constructor(params: AsyncCallerParams);
34
37
  call<A extends any[], T extends (...args: A) => Promise<any>>(callable: T, ...args: Parameters<T>): Promise<Awaited<ReturnType<T>>>;
35
38
  callWithOptions<A extends any[], T extends (...args: A) => Promise<any>>(options: AsyncCallerCallOptions, callable: T, ...args: Parameters<T>): Promise<Awaited<ReturnType<T>>>;
36
39
  fetch(...args: Parameters<typeof fetch>): ReturnType<typeof fetch>;
37
40
  }
41
+ export {};
@@ -46,13 +46,21 @@ export class AsyncCaller {
46
46
  writable: true,
47
47
  value: void 0
48
48
  });
49
+ Object.defineProperty(this, "onFailedResponseHook", {
50
+ enumerable: true,
51
+ configurable: true,
52
+ writable: true,
53
+ value: void 0
54
+ });
49
55
  this.maxConcurrency = params.maxConcurrency ?? Infinity;
50
56
  this.maxRetries = params.maxRetries ?? 6;
51
57
  const PQueue = "default" in PQueueMod ? PQueueMod.default : PQueueMod;
52
58
  this.queue = new PQueue({ concurrency: this.maxConcurrency });
59
+ this.onFailedResponseHook = params?.onFailedResponseHook;
53
60
  }
54
61
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
55
62
  call(callable, ...args) {
63
+ const onFailedResponseHook = this.onFailedResponseHook;
56
64
  return this.queue.add(() => pRetry(() => callable(...args).catch((error) => {
57
65
  // eslint-disable-next-line no-instanceof/no-instanceof
58
66
  if (error instanceof Error) {
@@ -62,7 +70,7 @@ export class AsyncCaller {
62
70
  throw new Error(error);
63
71
  }
64
72
  }), {
65
- onFailedAttempt(error) {
73
+ async onFailedAttempt(error) {
66
74
  if (error.message.startsWith("Cancel") ||
67
75
  error.message.startsWith("TimeoutError") ||
68
76
  error.message.startsWith("AbortError")) {
@@ -73,7 +81,8 @@ export class AsyncCaller {
73
81
  throw error;
74
82
  }
75
83
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
76
- const status = error?.response?.status;
84
+ const response = error?.response;
85
+ const status = response?.status;
77
86
  if (status) {
78
87
  if (STATUS_NO_RETRY.includes(+status)) {
79
88
  throw error;
@@ -81,6 +90,9 @@ export class AsyncCaller {
81
90
  else if (STATUS_IGNORE.includes(+status)) {
82
91
  return;
83
92
  }
93
+ if (onFailedResponseHook) {
94
+ await onFailedResponseHook(response);
95
+ }
84
96
  }
85
97
  },
86
98
  // If needed we can change some of the defaults here,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "langsmith",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
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": [