@langchain/core
Version:
Core LangChain.js abstractions and schemas
232 lines (231 loc) • 9.45 kB
JavaScript
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