UNPKG

@langchain/core

Version:
232 lines (231 loc) 9.45 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const require_runtime = require("../_virtual/_rolldown/runtime.cjs"); const require_errors_index = require("../errors/index.cjs"); const require_signal = require("./signal.cjs"); const require_index = require("./p-retry/index.cjs"); let p_queue = require("p-queue"); p_queue = require_runtime.__toESM(p_queue, 1); //#region src/utils/async_caller.ts var async_caller_exports = /* @__PURE__ */ require_runtime.__exportAll({ AsyncCaller: () => AsyncCaller, classifyRateLimitError: () => classifyRateLimitError, parseRetryAfterMs: () => parseRetryAfterMs }); const STATUS_NO_RETRY = [ 400, 401, 402, 403, 404, 405, 406, 407, 409, 413 ]; const RETRY_AFTER_AUTO_RETRY_THRESHOLD_MS = 6e4; const QUOTA_EXHAUSTED_MESSAGE_PATTERNS = [ /insufficient[_ -]?quota/i, /exceeded (?:your|the current|the available).+quota/i, /usage quota/i, /quota (?:has been )?exhausted/i, /billing/i, /credit balance/i, /out of credits/i, /will reset at/i ]; const RETRY_AFTER_MESSAGE_PATTERN = /(?:try again in|retry after)\s+(\d+(?:\.\d+)?)\s*(milliseconds?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h)\b/i; function getResponseStatus(error) { return typeof error === "object" && error !== null && "response" in error && typeof error.response === "object" && error.response !== null && "status" in error.response && typeof error.response.status === "number" ? error.response.status : void 0; } function getDirectStatus(error) { if (typeof error !== "object" || error === null) return; if ("status" in error && typeof error.status === "number") return error.status; if ("statusCode" in error && typeof error.statusCode === "number") return error.statusCode; } function getErrorMessage(error) { return typeof error === "object" && error !== null && "message" in error && typeof error.message === "string" ? error.message : void 0; } function getErrorCode(error) { if (typeof error !== "object" || error === null) return; if ("code" in error && typeof error.code === "string") return error.code; return "error" in error && typeof error.error === "object" && error.error !== null && "code" in error.error && typeof error.error.code === "string" ? error.error.code : void 0; } function _getRetryAfterHeader(error) { if (error?.headers) { if (typeof error.headers.get === "function") return error.headers.get("retry-after"); return error.headers["retry-after"] ?? error.headers["Retry-After"]; } if (error?.response?.headers) { if (typeof error.response.headers.get === "function") return error.response.headers.get("retry-after"); return error.response.headers["retry-after"] ?? error.response.headers["Retry-After"]; } } function parseRetryAfterFromMessageMs(message) { if (message == null) return; const match = RETRY_AFTER_MESSAGE_PATTERN.exec(message); if (!match) return; const rawValue = Number(match[1]); const unit = match[2]?.toLowerCase(); if (Number.isNaN(rawValue) || !unit) return; if (unit === "ms" || unit.startsWith("millisecond")) return rawValue; if (unit === "m" || unit.startsWith("min")) return rawValue * 6e4; if (unit === "h" || unit.startsWith("hr") || unit.startsWith("hour")) return rawValue * 36e5; return rawValue * 1e3; } function coerceError(error, fallbackMessage) { if (error instanceof Error) return error; const coerced = new Error(fallbackMessage); if (typeof error === "object" && error !== null) Object.assign(coerced, error); return coerced; } function setRateLimitMetadata(error, classification) { if (typeof error !== "object" || error === null) return; const mutableError = error; mutableError.rateLimitType = classification.action; mutableError.rateLimitReason = classification.reason; if (classification.retryAfterMs !== void 0) mutableError.retryAfterMs = classification.retryAfterMs; } function parseRetryAfterMs(headerValue) { if (headerValue == null) return; const trimmed = headerValue.trim(); if (!trimmed) return; const seconds = Number(trimmed); if (!Number.isNaN(seconds) && seconds >= 0) return seconds * 1e3; const date = Date.parse(trimmed); if (!Number.isNaN(date)) { const delayMs = date - Date.now(); return delayMs > 0 ? delayMs : 0; } } function classifyRateLimitError(error) { if ((getResponseStatus(error) ?? getDirectStatus(error)) !== 429) return; if (getErrorCode(error) === "insufficient_quota") return { action: "stop", reason: "insufficient_quota" }; const message = getErrorMessage(error); if (message && QUOTA_EXHAUSTED_MESSAGE_PATTERNS.some((pattern) => pattern.test(message))) return { action: "stop", reason: "quota_message" }; const retryAfterMs = parseRetryAfterMs(_getRetryAfterHeader(error)) ?? parseRetryAfterFromMessageMs(message); if (retryAfterMs !== void 0) { if (retryAfterMs <= RETRY_AFTER_AUTO_RETRY_THRESHOLD_MS) return { action: "wait", retryAfterMs, reason: "retry_after_hint" }; return { action: "capacity", retryAfterMs, reason: "retry_after_too_large" }; } return { action: "capacity", reason: "headerless_429" }; } /** * The default failed attempt handler for the AsyncCaller. * @param error - The error to handle. * @returns void */ const defaultFailedAttemptHandler = (error) => { if (typeof error !== "object" || error === null) return; if (require_errors_index.getRetryable(error) === false) throw error; if ("message" in error && typeof error.message === "string" && (error.message.startsWith("Cancel") || error.message.startsWith("AbortError")) || "name" in error && typeof error.name === "string" && error.name === "AbortError") throw require_errors_index.stampRetryable(error, false); if ("code" in error && typeof error.code === "string" && error.code === "ECONNABORTED") throw error; const status = getResponseStatus(error) ?? getDirectStatus(error); if (status && STATUS_NO_RETRY.includes(+status)) throw require_errors_index.stampRetryable(error, false); if (getErrorCode(error) === "insufficient_quota") { const err = coerceError(error, getErrorMessage(error) ?? "Insufficient quota"); err.name = "InsufficientQuotaError"; setRateLimitMetadata(err, { action: "stop", reason: "insufficient_quota" }); throw require_errors_index.stampRetryable(err, false); } const rateLimitClassification = classifyRateLimitError(error); if (rateLimitClassification) { if (rateLimitClassification.action === "wait") { setRateLimitMetadata(error, rateLimitClassification); require_errors_index.stampRetryable(error, true); return; } const err = coerceError(error, getErrorMessage(error) ?? "Rate limit exceeded"); if (err.name === "Error") err.name = rateLimitClassification.action === "stop" ? "RateLimitQuotaExhaustedError" : "RateLimitCapacityError"; setRateLimitMetadata(err, rateLimitClassification); throw require_errors_index.stampRetryable(err, rateLimitClassification.action !== "stop"); } }; /** * A class that can be used to make async calls with concurrency and retry logic. * * This is useful for making calls to any kind of "expensive" external resource, * be it because it's rate-limited, subject to network issues, etc. * * Concurrent calls are limited by the `maxConcurrency` parameter, which defaults * to `Infinity`. This means that by default, all calls will be made in parallel. * * Retries are limited by the `maxRetries` parameter, which defaults to 6. This * means that by default, each call will be retried up to 6 times, with an * exponential backoff between each attempt. */ var AsyncCaller = class { maxConcurrency; maxRetries; onFailedAttempt; queue; constructor(params) { this.maxConcurrency = params.maxConcurrency ?? Infinity; this.maxRetries = params.maxRetries ?? 6; this.onFailedAttempt = params.onFailedAttempt ?? defaultFailedAttemptHandler; const PQueue = "default" in p_queue.default ? p_queue.default.default : p_queue.default; this.queue = new PQueue({ concurrency: this.maxConcurrency }); } async call(callable, ...args) { return this.callWithRetries(this.maxRetries, callable, args); } callWithRetries(retries, callable, args) { return this.queue.add(() => require_index.default(() => callable(...args).catch((error) => { if (error instanceof Error) throw error; else throw new Error(error); }), { onFailedAttempt: ({ error }) => this.onFailedAttempt?.(error), retries, randomize: true }), { throwOnTimeout: true }); } callWithOptions(options, callable, ...args) { const retries = options.maxRetries ?? this.maxRetries; if (options.signal) { let listener; return Promise.race([this.callWithRetries(retries, callable, args), new Promise((_, reject) => { listener = () => { reject(require_signal.getAbortSignalError(options.signal)); }; options.signal?.addEventListener("abort", listener, { once: true }); })]).finally(() => { if (options.signal && listener) options.signal.removeEventListener("abort", listener); }); } return this.callWithRetries(retries, callable, args); } fetch(...args) { return this.call(() => fetch(...args).then((res) => res.ok ? res : Promise.reject(res))); } }; //#endregion exports.AsyncCaller = AsyncCaller; Object.defineProperty(exports, "async_caller_exports", { enumerable: true, get: function() { return async_caller_exports; } }); exports.classifyRateLimitError = classifyRateLimitError; exports.parseRetryAfterMs = parseRetryAfterMs; //# sourceMappingURL=async_caller.cjs.map