Fix provider model inventories
This commit is contained in:
+65
-35
@@ -1,7 +1,13 @@
|
|||||||
import { Request, Response, RequestHandler, Router } from "express";
|
import { Request, Response, RequestHandler, Router } from "express";
|
||||||
import { createProxyMiddleware } from "http-proxy-middleware";
|
import { createProxyMiddleware } from "http-proxy-middleware";
|
||||||
import { config } from "../config";
|
import { config } from "../config";
|
||||||
|
import { keyPool, AnthropicKey } from "../shared/key-management";
|
||||||
import { logger } from "../logger";
|
import { logger } from "../logger";
|
||||||
|
import {
|
||||||
|
AnthropicModelFamily,
|
||||||
|
getClaudeModelFamily,
|
||||||
|
ModelFamily,
|
||||||
|
} from "../shared/models";
|
||||||
import { createQueueMiddleware } from "./queue";
|
import { createQueueMiddleware } from "./queue";
|
||||||
import { ipLimiter } from "./rate-limit";
|
import { ipLimiter } from "./rate-limit";
|
||||||
import { handleProxyError } from "./middleware/common";
|
import { handleProxyError } from "./middleware/common";
|
||||||
@@ -21,46 +27,70 @@ import { sendErrorToClient } from "./middleware/response/error-generator";
|
|||||||
let modelsCache: any = null;
|
let modelsCache: any = null;
|
||||||
let modelsCacheTime = 0;
|
let modelsCacheTime = 0;
|
||||||
|
|
||||||
const getModelsResponse = () => {
|
export const KNOWN_ANTHROPIC_MODELS = [
|
||||||
if (new Date().getTime() - modelsCacheTime < 1000 * 60) {
|
"claude-3-haiku-20240307",
|
||||||
return modelsCache;
|
"claude-haiku-4-5-20251001",
|
||||||
|
"claude-opus-4-1-20250805",
|
||||||
|
"claude-opus-4-20250514",
|
||||||
|
"claude-opus-4-5-20251101",
|
||||||
|
"claude-opus-4-6",
|
||||||
|
"claude-sonnet-4-20250514",
|
||||||
|
"claude-sonnet-4-5-20250929",
|
||||||
|
"claude-sonnet-4-6",
|
||||||
|
];
|
||||||
|
|
||||||
|
export function generateModelList(models = KNOWN_ANTHROPIC_MODELS) {
|
||||||
|
let availableFamilies = new Set<AnthropicModelFamily>();
|
||||||
|
const availableModelIds = new Set<string>();
|
||||||
|
for (const key of keyPool.list()) {
|
||||||
|
if (key.isDisabled || key.service !== "anthropic") continue;
|
||||||
|
const anthropicKey = key as AnthropicKey;
|
||||||
|
anthropicKey.modelFamilies.forEach((family) =>
|
||||||
|
availableFamilies.add(family)
|
||||||
|
);
|
||||||
|
anthropicKey.modelIds.forEach((id) => availableModelIds.add(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config.anthropicKey) return { object: "list", data: [] };
|
const allowed = new Set<ModelFamily>(config.allowedModelFamilies);
|
||||||
|
availableFamilies = new Set(
|
||||||
|
[...availableFamilies].filter((family) => allowed.has(family))
|
||||||
|
);
|
||||||
|
|
||||||
const claudeVariants = [
|
const usingExactModelIds = availableModelIds.size > 0;
|
||||||
"claude-2.0",
|
const sourceModels = usingExactModelIds
|
||||||
"claude-2.1",
|
? [...availableModelIds].sort()
|
||||||
"claude-sonnet-4-5",
|
: models;
|
||||||
"claude-sonnet-4-5-20250929",
|
|
||||||
"claude-haiku-4-5",
|
|
||||||
"claude-haiku-4-5-20251001",
|
|
||||||
"claude-opus-4-1",
|
|
||||||
"claude-opus-4-1-20250805",
|
|
||||||
"claude-opus-4-20250514",
|
|
||||||
"claude-sonnet-4-20250514",
|
|
||||||
"claude-3-5-haiku-20241022",
|
|
||||||
"claude-3-5-haiku-latest",
|
|
||||||
];
|
|
||||||
|
|
||||||
const models = claudeVariants.map((id) => ({
|
return sourceModels
|
||||||
id,
|
.map((id) => ({
|
||||||
object: "model",
|
id,
|
||||||
created: new Date().getTime(),
|
object: "model",
|
||||||
owned_by: "anthropic",
|
created: new Date().getTime(),
|
||||||
permission: [],
|
owned_by: "anthropic",
|
||||||
root: "claude",
|
permission: [],
|
||||||
parent: null,
|
root: "claude",
|
||||||
}));
|
parent: null,
|
||||||
|
}))
|
||||||
modelsCache = { object: "list", data: models };
|
.filter((model) => {
|
||||||
modelsCacheTime = new Date().getTime();
|
if (usingExactModelIds) {
|
||||||
|
return (
|
||||||
return modelsCache;
|
allowed.has(getClaudeModelFamily(model.id)) &&
|
||||||
};
|
availableModelIds.has(model.id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return availableFamilies.has(getClaudeModelFamily(model.id));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const handleModelRequest: RequestHandler = (_req, res) => {
|
const handleModelRequest: RequestHandler = (_req, res) => {
|
||||||
res.status(200).json(getModelsResponse());
|
if (new Date().getTime() - modelsCacheTime < 1000 * 60) {
|
||||||
|
return res.status(200).json(modelsCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = config.anthropicKey ? generateModelList() : [];
|
||||||
|
modelsCache = { object: "list", data: result };
|
||||||
|
modelsCacheTime = new Date().getTime();
|
||||||
|
res.status(200).json(modelsCache);
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Only used for non-streaming requests. */
|
/** Only used for non-streaming requests. */
|
||||||
@@ -350,7 +380,7 @@ function maybeReassignModel(req: Request) {
|
|||||||
lower.startsWith("o1") ||
|
lower.startsWith("o1") ||
|
||||||
lower.startsWith("o3") ||
|
lower.startsWith("o3") ||
|
||||||
lower.startsWith("o4") ||
|
lower.startsWith("o4") ||
|
||||||
lower === "computer-use-preview"
|
lower.startsWith("computer-use-preview")
|
||||||
) {
|
) {
|
||||||
req.body.model = "claude-sonnet-4-5-20250929";
|
req.body.model = "claude-sonnet-4-5-20250929";
|
||||||
}
|
}
|
||||||
|
|||||||
+48
-11
@@ -2,7 +2,13 @@ import { Request, RequestHandler, Response, Router } from "express";
|
|||||||
import { createProxyMiddleware } from "http-proxy-middleware";
|
import { createProxyMiddleware } from "http-proxy-middleware";
|
||||||
import { v4 } from "uuid";
|
import { v4 } from "uuid";
|
||||||
import { config } from "../config";
|
import { config } from "../config";
|
||||||
|
import { keyPool, AwsBedrockKey } from "../shared/key-management";
|
||||||
import { logger } from "../logger";
|
import { logger } from "../logger";
|
||||||
|
import {
|
||||||
|
AwsBedrockModelFamily,
|
||||||
|
getAwsBedrockModelFamily,
|
||||||
|
ModelFamily,
|
||||||
|
} from "../shared/models";
|
||||||
import { createQueueMiddleware } from "./queue";
|
import { createQueueMiddleware } from "./queue";
|
||||||
import { ipLimiter } from "./rate-limit";
|
import { ipLimiter } from "./rate-limit";
|
||||||
import { handleProxyError } from "./middleware/common";
|
import { handleProxyError } from "./middleware/common";
|
||||||
@@ -16,7 +22,10 @@ import {
|
|||||||
ProxyResHandlerWithBody,
|
ProxyResHandlerWithBody,
|
||||||
createOnProxyResHandler,
|
createOnProxyResHandler,
|
||||||
} from "./middleware/response";
|
} from "./middleware/response";
|
||||||
import { transformAnthropicChatResponseToAnthropicText, transformAnthropicChatResponseToOpenAI } from "./anthropic";
|
import {
|
||||||
|
transformAnthropicChatResponseToAnthropicText,
|
||||||
|
transformAnthropicChatResponseToOpenAI,
|
||||||
|
} from "./anthropic";
|
||||||
import { sendErrorToClient } from "./middleware/response/error-generator";
|
import { sendErrorToClient } from "./middleware/response/error-generator";
|
||||||
|
|
||||||
const LATEST_AWS_V2_MINOR_VERSION = "1";
|
const LATEST_AWS_V2_MINOR_VERSION = "1";
|
||||||
@@ -37,6 +46,21 @@ const getModelsResponse = () => {
|
|||||||
|
|
||||||
if (!config.awsCredentials) return { object: "list", data: [] };
|
if (!config.awsCredentials) return { object: "list", data: [] };
|
||||||
|
|
||||||
|
let availableFamilies = new Set<AwsBedrockModelFamily>();
|
||||||
|
const availableModelIds = new Set<string>();
|
||||||
|
for (const key of keyPool.list()) {
|
||||||
|
if (key.isDisabled || key.service !== "aws") continue;
|
||||||
|
const awsKey = key as AwsBedrockKey;
|
||||||
|
awsKey.modelFamilies.forEach((family) => availableFamilies.add(family));
|
||||||
|
awsKey.modelIds.forEach((id) => availableModelIds.add(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
const allowed = new Set<ModelFamily>(config.allowedModelFamilies);
|
||||||
|
availableFamilies = new Set(
|
||||||
|
[...availableFamilies].filter((family) => allowed.has(family))
|
||||||
|
);
|
||||||
|
const usingExactModelIds = availableModelIds.size > 0;
|
||||||
|
|
||||||
// https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids.html
|
// https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids.html
|
||||||
const variants = [
|
const variants = [
|
||||||
"anthropic.claude-v2",
|
"anthropic.claude-v2",
|
||||||
@@ -48,16 +72,29 @@ const getModelsResponse = () => {
|
|||||||
AWS_CLAUDE_SONNET_4,
|
AWS_CLAUDE_SONNET_4,
|
||||||
AWS_CLAUDE_OPUS_4,
|
AWS_CLAUDE_OPUS_4,
|
||||||
];
|
];
|
||||||
|
const sourceModels = usingExactModelIds
|
||||||
|
? [...availableModelIds].sort()
|
||||||
|
: variants;
|
||||||
|
|
||||||
const models = variants.map((id) => ({
|
const models = sourceModels
|
||||||
id,
|
.map((id) => ({
|
||||||
object: "model",
|
id,
|
||||||
created: new Date().getTime(),
|
object: "model",
|
||||||
owned_by: "anthropic",
|
created: new Date().getTime(),
|
||||||
permission: [],
|
owned_by: "anthropic",
|
||||||
root: "claude",
|
permission: [],
|
||||||
parent: null,
|
root: "claude",
|
||||||
}));
|
parent: null,
|
||||||
|
}))
|
||||||
|
.filter((model) => {
|
||||||
|
if (usingExactModelIds) {
|
||||||
|
return (
|
||||||
|
allowed.has(getAwsBedrockModelFamily(model.id)) &&
|
||||||
|
availableModelIds.has(model.id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return availableFamilies.has(getAwsBedrockModelFamily(model.id));
|
||||||
|
});
|
||||||
|
|
||||||
modelsCache = { object: "list", data: models };
|
modelsCache = { object: "list", data: models };
|
||||||
modelsCacheTime = new Date().getTime();
|
modelsCacheTime = new Date().getTime();
|
||||||
@@ -302,7 +339,7 @@ function maybeReassignModel(req: Request) {
|
|||||||
lower.startsWith("o1") ||
|
lower.startsWith("o1") ||
|
||||||
lower.startsWith("o3") ||
|
lower.startsWith("o3") ||
|
||||||
lower.startsWith("o4") ||
|
lower.startsWith("o4") ||
|
||||||
lower === "computer-use-preview"
|
lower.startsWith("computer-use-preview")
|
||||||
) {
|
) {
|
||||||
req.body.model = AWS_CLAUDE_SONNET_45;
|
req.body.model = AWS_CLAUDE_SONNET_45;
|
||||||
return;
|
return;
|
||||||
|
|||||||
+1
-1
@@ -208,7 +208,7 @@ function maybeReassignModel(req: Request) {
|
|||||||
lower.startsWith("o1") ||
|
lower.startsWith("o1") ||
|
||||||
lower.startsWith("o3") ||
|
lower.startsWith("o3") ||
|
||||||
lower.startsWith("o4") ||
|
lower.startsWith("o4") ||
|
||||||
lower === "computer-use-preview"
|
lower.startsWith("computer-use-preview")
|
||||||
) {
|
) {
|
||||||
req.body.model = GCP_CLAUDE_SONNET_45;
|
req.body.model = GCP_CLAUDE_SONNET_45;
|
||||||
return;
|
return;
|
||||||
|
|||||||
+62
-6
@@ -33,6 +33,12 @@ export const KNOWN_OPENAI_MODELS = [
|
|||||||
"gpt-5.2-chat-latest",
|
"gpt-5.2-chat-latest",
|
||||||
"gpt-5.2-pro",
|
"gpt-5.2-pro",
|
||||||
"gpt-5.2-codex",
|
"gpt-5.2-codex",
|
||||||
|
"gpt-5.4",
|
||||||
|
"gpt-5.4-pro",
|
||||||
|
"gpt-5.4-mini",
|
||||||
|
"gpt-5.4-nano",
|
||||||
|
"gpt-5.3-chat-latest",
|
||||||
|
"gpt-5.3-codex",
|
||||||
"gpt-5.1",
|
"gpt-5.1",
|
||||||
"gpt-5.1-chat",
|
"gpt-5.1-chat",
|
||||||
"gpt-5.1-codex",
|
"gpt-5.1-codex",
|
||||||
@@ -48,9 +54,12 @@ export const KNOWN_OPENAI_MODELS = [
|
|||||||
"gpt-4.1-2025-04-14",
|
"gpt-4.1-2025-04-14",
|
||||||
"gpt-4.1-mini",
|
"gpt-4.1-mini",
|
||||||
"gpt-4.1-nano",
|
"gpt-4.1-nano",
|
||||||
|
"gpt-4.1-mini-2025-04-14",
|
||||||
|
"gpt-4.1-nano-2025-04-14",
|
||||||
"o3-pro",
|
"o3-pro",
|
||||||
"o3-deep-research",
|
"o3-deep-research",
|
||||||
"computer-use-preview",
|
"computer-use-preview",
|
||||||
|
"computer-use-preview-2025-03-11",
|
||||||
"o4-mini",
|
"o4-mini",
|
||||||
"o4-mini-deep-research",
|
"o4-mini-deep-research",
|
||||||
"o3",
|
"o3",
|
||||||
@@ -59,8 +68,13 @@ export const KNOWN_OPENAI_MODELS = [
|
|||||||
"o1-pro",
|
"o1-pro",
|
||||||
"gpt-4o",
|
"gpt-4o",
|
||||||
"gpt-4o-2024-08-06",
|
"gpt-4o-2024-08-06",
|
||||||
|
"gpt-4o-2024-11-20",
|
||||||
"gpt-4o-mini",
|
"gpt-4o-mini",
|
||||||
"gpt-4o-2024-05-13",
|
"gpt-4o-2024-05-13",
|
||||||
|
"gpt-4o-audio-preview",
|
||||||
|
"gpt-4o-mini-audio-preview",
|
||||||
|
"gpt-4o-search-preview",
|
||||||
|
"gpt-4o-mini-search-preview",
|
||||||
"gpt-4-turbo", // alias for latest gpt4-turbo stable
|
"gpt-4-turbo", // alias for latest gpt4-turbo stable
|
||||||
"gpt-4-turbo-2024-04-09", // gpt4-turbo stable, with vision
|
"gpt-4-turbo-2024-04-09", // gpt4-turbo stable, with vision
|
||||||
"gpt-4",
|
"gpt-4",
|
||||||
@@ -74,13 +88,45 @@ export const KNOWN_OPENAI_MODELS = [
|
|||||||
"text-embedding-3-small",
|
"text-embedding-3-small",
|
||||||
"text-embedding-3-large",
|
"text-embedding-3-large",
|
||||||
"text-embedding-ada-002",
|
"text-embedding-ada-002",
|
||||||
"gpt-image-1.5",
|
|
||||||
"gpt-image-1",
|
|
||||||
"gpt-image-1-mini",
|
|
||||||
"dall-e-3",
|
|
||||||
"dall-e-2",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const UNSUPPORTED_OPENAI_MODEL_PATTERNS = [
|
||||||
|
/^babbage-002$/,
|
||||||
|
/^chatgpt-image-latest$/,
|
||||||
|
/^davinci-002$/,
|
||||||
|
/^gpt-(?:4o(?:-mini)?-)?realtime(?:-|$)/,
|
||||||
|
/^gpt-(?:4o(?:-mini)?-)?transcribe(?:-|$)/,
|
||||||
|
/^gpt-.*-tts(?:-|$)/,
|
||||||
|
/^omni-moderation(?:-|$)/,
|
||||||
|
/^sora-/,
|
||||||
|
/^tts-1(?:-|$)/,
|
||||||
|
/^whisper-1$/,
|
||||||
|
];
|
||||||
|
|
||||||
|
const SUPPORTED_OPENAI_MODEL_PATTERNS = [
|
||||||
|
/^gpt-5(?:\.\d+)?(?:[-.].+)?$/,
|
||||||
|
/^o\d(?:[-.].+)?$/,
|
||||||
|
/^computer-use-preview(?:-\d{4}-\d{2}-\d{2})?$/,
|
||||||
|
/^gpt-4\.1(?:[-.].+)?$/,
|
||||||
|
/^gpt-4o(?:[-.].+)?$/,
|
||||||
|
/^gpt-4-turbo(?:-\d{4}-\d{2}-\d{2})?$/,
|
||||||
|
/^gpt-4-32k(?:-\d{4})?$/,
|
||||||
|
/^gpt-4(?:-\d{4})?$/,
|
||||||
|
/^gpt-3\.5-turbo(?:[-.].+)?$/,
|
||||||
|
/^text-embedding-(ada-002|3-small|3-large)$/,
|
||||||
|
/^gpt-image-1(?:[-.].+)?$/,
|
||||||
|
/^dall-e-\d$/,
|
||||||
|
];
|
||||||
|
|
||||||
|
function isSupportedOpenAIModelId(modelId: string) {
|
||||||
|
return (
|
||||||
|
!UNSUPPORTED_OPENAI_MODEL_PATTERNS.some((pattern) =>
|
||||||
|
pattern.test(modelId)
|
||||||
|
) &&
|
||||||
|
SUPPORTED_OPENAI_MODEL_PATTERNS.some((pattern) => pattern.test(modelId))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let modelsCache: any = null;
|
let modelsCache: any = null;
|
||||||
let modelsCacheTime = 0;
|
let modelsCacheTime = 0;
|
||||||
|
|
||||||
@@ -103,9 +149,18 @@ export function generateModelList(models = KNOWN_OPENAI_MODELS) {
|
|||||||
[...availableFamilies].filter((x) => allowed.has(x))
|
[...availableFamilies].filter((x) => allowed.has(x))
|
||||||
);
|
);
|
||||||
const usingExactModelIds = availableModelIds.size > 0;
|
const usingExactModelIds = availableModelIds.size > 0;
|
||||||
|
const supportedFamilies = new Set(
|
||||||
|
models.map((model) => getOpenAIModelFamily(model))
|
||||||
|
);
|
||||||
|
|
||||||
const sourceModels = usingExactModelIds
|
const sourceModels = usingExactModelIds
|
||||||
? [...new Set([...models, ...availableModelIds])]
|
? [...availableModelIds]
|
||||||
|
.filter(
|
||||||
|
(model) =>
|
||||||
|
isSupportedOpenAIModelId(model) &&
|
||||||
|
supportedFamilies.has(getOpenAIModelFamily(model))
|
||||||
|
)
|
||||||
|
.sort()
|
||||||
: models;
|
: models;
|
||||||
|
|
||||||
return sourceModels
|
return sourceModels
|
||||||
@@ -130,6 +185,7 @@ export function generateModelList(models = KNOWN_OPENAI_MODELS) {
|
|||||||
.filter((model) => {
|
.filter((model) => {
|
||||||
if (usingExactModelIds) {
|
if (usingExactModelIds) {
|
||||||
return (
|
return (
|
||||||
|
isSupportedOpenAIModelId(model.id) &&
|
||||||
allowed.has(getOpenAIModelFamily(model.id)) &&
|
allowed.has(getOpenAIModelFamily(model.id)) &&
|
||||||
availableModelIds.has(model.id)
|
availableModelIds.has(model.id)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import axios, { AxiosError, AxiosResponse } from "axios";
|
import axios, { AxiosError, AxiosResponse } from "axios";
|
||||||
import { KeyCheckerBase } from "../key-checker-base";
|
import { KeyCheckerBase } from "../key-checker-base";
|
||||||
import type { AnthropicKey, AnthropicKeyProvider } from "./provider";
|
import type { AnthropicKey, AnthropicKeyProvider } from "./provider";
|
||||||
|
import { AnthropicModelFamily, getClaudeModelFamily } from "../../models";
|
||||||
|
|
||||||
const MIN_CHECK_INTERVAL = 3 * 1000; // 3 seconds
|
const MIN_CHECK_INTERVAL = 3 * 1000; // 3 seconds
|
||||||
const KEY_CHECK_PERIOD = 1000 * 60 * 60 * 6; // 6 hours
|
const KEY_CHECK_PERIOD = 1000 * 60 * 60 * 6; // 6 hours
|
||||||
|
const GET_MODELS_URL = "https://api.anthropic.com/v1/models";
|
||||||
const POST_MESSAGES_URL = "https://api.anthropic.com/v1/messages";
|
const POST_MESSAGES_URL = "https://api.anthropic.com/v1/messages";
|
||||||
const TEST_MODEL = "claude-3-sonnet-20240229";
|
const DEFAULT_TEST_MODEL = "claude-3-haiku-20240307";
|
||||||
const SYSTEM = "Obey all instructions from the user.";
|
const SYSTEM = "Obey all instructions from the user.";
|
||||||
const DETECTION_PROMPT = [
|
const DETECTION_PROMPT = [
|
||||||
{
|
{
|
||||||
@@ -35,6 +37,10 @@ type MessageResponse = {
|
|||||||
content: { type: "text"; text: string }[];
|
content: { type: "text"; text: string }[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type GetModelsResponse = {
|
||||||
|
data: { id: string }[];
|
||||||
|
};
|
||||||
|
|
||||||
type AnthropicAPIError = {
|
type AnthropicAPIError = {
|
||||||
error: { type: string; message: string };
|
error: { type: string; message: string };
|
||||||
};
|
};
|
||||||
@@ -52,11 +58,21 @@ export class AnthropicKeyChecker extends KeyCheckerBase<AnthropicKey> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async testKeyOrFail(key: AnthropicKey) {
|
protected async testKeyOrFail(key: AnthropicKey) {
|
||||||
const [{ pozzed, tier }] = await Promise.all([this.testLiveness(key)]);
|
const isInitialCheck = !key.lastChecked;
|
||||||
const updates = { isPozzed: pozzed, tier };
|
const provisionedModels = isInitialCheck
|
||||||
|
? await this.getProvisionedModels(key)
|
||||||
|
: key.modelFamilies;
|
||||||
|
const keyFromPool = this.keys.find((k) => k.hash === key.hash)!;
|
||||||
|
const liveness = await this.testLiveness(keyFromPool);
|
||||||
|
|
||||||
|
const updates = {
|
||||||
|
isPozzed: liveness.pozzed,
|
||||||
|
tier: liveness.tier,
|
||||||
|
modelFamilies: provisionedModels,
|
||||||
|
};
|
||||||
this.updateKey(key.hash, updates);
|
this.updateKey(key.hash, updates);
|
||||||
this.log.info(
|
this.log.info(
|
||||||
{ key: key.hash, tier, models: key.modelFamilies },
|
{ key: key.hash, tier: liveness.tier, models: keyFromPool.modelIds },
|
||||||
"Checked key."
|
"Checked key."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -131,7 +147,7 @@ export class AnthropicKeyChecker extends KeyCheckerBase<AnthropicKey> {
|
|||||||
key: AnthropicKey
|
key: AnthropicKey
|
||||||
): Promise<{ pozzed: boolean; tier: AnthropicKey["tier"] }> {
|
): Promise<{ pozzed: boolean; tier: AnthropicKey["tier"] }> {
|
||||||
const payload = {
|
const payload = {
|
||||||
model: TEST_MODEL,
|
model: this.getLivenessModel(key),
|
||||||
max_tokens: 40,
|
max_tokens: 40,
|
||||||
temperature: 0,
|
temperature: 0,
|
||||||
stream: false,
|
stream: false,
|
||||||
@@ -162,6 +178,47 @@ export class AnthropicKeyChecker extends KeyCheckerBase<AnthropicKey> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getProvisionedModels(
|
||||||
|
key: AnthropicKey
|
||||||
|
): Promise<AnthropicModelFamily[]> {
|
||||||
|
const { data } = await axios.get<GetModelsResponse>(GET_MODELS_URL, {
|
||||||
|
headers: AnthropicKeyChecker.getRequestHeaders(key),
|
||||||
|
});
|
||||||
|
|
||||||
|
const modelIds = data.data.map(({ id }) => id);
|
||||||
|
const families = new Set<AnthropicModelFamily>();
|
||||||
|
modelIds.forEach((id) => families.add(getClaudeModelFamily(id)));
|
||||||
|
|
||||||
|
const familiesArray = [...families];
|
||||||
|
const keyFromPool = this.keys.find((k) => k.hash === key.hash)!;
|
||||||
|
this.updateKey(key.hash, {
|
||||||
|
modelIds,
|
||||||
|
modelFamilies: familiesArray,
|
||||||
|
lastChecked: keyFromPool.lastChecked,
|
||||||
|
});
|
||||||
|
return familiesArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getLivenessModel(key: AnthropicKey) {
|
||||||
|
const preferredModels = [
|
||||||
|
"claude-3-haiku-20240307",
|
||||||
|
"claude-haiku-4-5-20251001",
|
||||||
|
"claude-sonnet-4-20250514",
|
||||||
|
"claude-sonnet-4-5-20250929",
|
||||||
|
"claude-sonnet-4-6",
|
||||||
|
"claude-opus-4-1-20250805",
|
||||||
|
"claude-opus-4-20250514",
|
||||||
|
"claude-opus-4-5-20251101",
|
||||||
|
"claude-opus-4-6",
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
preferredModels.find((model) => key.modelIds.includes(model)) ||
|
||||||
|
key.modelIds[0] ||
|
||||||
|
DEFAULT_TEST_MODEL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static errorIsAnthropicAPIError(
|
static errorIsAnthropicAPIError(
|
||||||
error: AxiosError
|
error: AxiosError
|
||||||
): error is AxiosError<AnthropicAPIError> {
|
): error is AxiosError<AnthropicAPIError> {
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ type AnthropicKeyUsage = {
|
|||||||
export interface AnthropicKey extends Key, AnthropicKeyUsage {
|
export interface AnthropicKey extends Key, AnthropicKeyUsage {
|
||||||
readonly service: "anthropic";
|
readonly service: "anthropic";
|
||||||
readonly modelFamilies: AnthropicModelFamily[];
|
readonly modelFamilies: AnthropicModelFamily[];
|
||||||
|
/** Exact model IDs reported by the models API for this key. */
|
||||||
|
modelIds: string[];
|
||||||
/** The time at which this key was last rate limited. */
|
/** The time at which this key was last rate limited. */
|
||||||
rateLimitedAt: number;
|
rateLimitedAt: number;
|
||||||
/** The time until which this key is rate limited. */
|
/** The time until which this key is rate limited. */
|
||||||
@@ -108,6 +110,7 @@ export class AnthropicKeyProvider implements KeyProvider<AnthropicKey> {
|
|||||||
key,
|
key,
|
||||||
service: this.service,
|
service: this.service,
|
||||||
modelFamilies: ["claude", "claude-opus"],
|
modelFamilies: ["claude", "claude-opus"],
|
||||||
|
modelIds: [],
|
||||||
isDisabled: false,
|
isDisabled: false,
|
||||||
isOverQuota: false,
|
isOverQuota: false,
|
||||||
isRevoked: false,
|
isRevoked: false,
|
||||||
@@ -145,11 +148,18 @@ export class AnthropicKeyProvider implements KeyProvider<AnthropicKey> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get(rawModel: string) {
|
public get(rawModel: string) {
|
||||||
this.log.debug({ model: rawModel }, "Selecting key");
|
|
||||||
const needsMultimodal = rawModel.endsWith("-multimodal");
|
const needsMultimodal = rawModel.endsWith("-multimodal");
|
||||||
|
const model = needsMultimodal
|
||||||
|
? rawModel.replace(/-multimodal$/, "")
|
||||||
|
: rawModel;
|
||||||
|
this.log.debug({ model: rawModel, exactModel: model }, "Selecting key");
|
||||||
|
|
||||||
const availableKeys = this.keys.filter((k) => {
|
const availableKeys = this.keys.filter((k) => {
|
||||||
return !k.isDisabled && (!needsMultimodal || k.allowsMultimodality);
|
return (
|
||||||
|
!k.isDisabled &&
|
||||||
|
(!needsMultimodal || k.allowsMultimodality) &&
|
||||||
|
(!k.modelIds.length || k.modelIds.includes(model))
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (availableKeys.length === 0) {
|
if (availableKeys.length === 0) {
|
||||||
|
|||||||
@@ -17,6 +17,13 @@ const GET_INVOCATION_LOGGING_CONFIG_URL = (region: string) =>
|
|||||||
`https://bedrock.${region}.amazonaws.com/logging/modelinvocations`;
|
`https://bedrock.${region}.amazonaws.com/logging/modelinvocations`;
|
||||||
const POST_INVOKE_MODEL_URL = (region: string, model: string) =>
|
const POST_INVOKE_MODEL_URL = (region: string, model: string) =>
|
||||||
`https://${AMZ_HOST.replace("%REGION%", region)}/model/${model}/invoke`;
|
`https://${AMZ_HOST.replace("%REGION%", region)}/model/${model}/invoke`;
|
||||||
|
const AWS_CLAUDE_V2 = "anthropic.claude-v2";
|
||||||
|
const AWS_CLAUDE_SONNET_45 = "anthropic.claude-sonnet-4-5-20250929-v1:0";
|
||||||
|
const AWS_CLAUDE_HAIKU_45 = "anthropic.claude-haiku-4-5-20251001-v1:0";
|
||||||
|
const AWS_CLAUDE_OPUS_41 = "anthropic.claude-opus-4-1-20250805-v1:0";
|
||||||
|
const AWS_CLAUDE_35_HAIKU = "anthropic.claude-3-5-haiku-20241022-v1:0";
|
||||||
|
const AWS_CLAUDE_SONNET_4 = "anthropic.claude-sonnet-4-20250514-v1:0";
|
||||||
|
const AWS_CLAUDE_OPUS_4 = "anthropic.claude-opus-4-20250514-v1:0";
|
||||||
const TEST_MESSAGES = [
|
const TEST_MESSAGES = [
|
||||||
{ role: "user", content: "Hi!" },
|
{ role: "user", content: "Hi!" },
|
||||||
{ role: "assistant", content: "Hello!" },
|
{ role: "assistant", content: "Hello!" },
|
||||||
@@ -53,28 +60,60 @@ export class AwsKeyChecker extends KeyCheckerBase<AwsBedrockKey> {
|
|||||||
const isInitialCheck = !key.lastChecked;
|
const isInitialCheck = !key.lastChecked;
|
||||||
if (isInitialCheck) {
|
if (isInitialCheck) {
|
||||||
checks = [
|
checks = [
|
||||||
this.invokeModel("anthropic.claude-v2", key),
|
this.invokeModel(AWS_CLAUDE_V2, key),
|
||||||
this.invokeModel("anthropic.claude-sonnet-4-5-20250929-v1:0", key),
|
this.invokeModel(AWS_CLAUDE_SONNET_45, key),
|
||||||
this.invokeModel("anthropic.claude-haiku-4-5-20251001-v1:0", key),
|
this.invokeModel(AWS_CLAUDE_HAIKU_45, key),
|
||||||
this.invokeModel("anthropic.claude-opus-4-1-20250805-v1:0", key),
|
this.invokeModel(AWS_CLAUDE_OPUS_41, key),
|
||||||
this.invokeModel("anthropic.claude-3-5-haiku-20241022-v1:0", key),
|
this.invokeModel(AWS_CLAUDE_35_HAIKU, key),
|
||||||
|
this.invokeModel(AWS_CLAUDE_SONNET_4, key),
|
||||||
|
this.invokeModel(AWS_CLAUDE_OPUS_4, key),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
checks.unshift(this.checkLoggingConfiguration(key));
|
checks.unshift(this.checkLoggingConfiguration(key));
|
||||||
|
|
||||||
const [_logging, claudeV2, sonnet, haiku, opus, sonnet35] =
|
const [
|
||||||
await Promise.all(checks);
|
_logging,
|
||||||
|
claudeV2,
|
||||||
|
sonnet45,
|
||||||
|
haiku45,
|
||||||
|
opus41,
|
||||||
|
haiku35,
|
||||||
|
sonnet4,
|
||||||
|
opus4,
|
||||||
|
] = await Promise.all(checks);
|
||||||
|
|
||||||
this.log.debug(
|
this.log.debug(
|
||||||
{ key: key.hash, _logging, claudeV2, sonnet, haiku, opus, sonnet35 },
|
{
|
||||||
|
key: key.hash,
|
||||||
|
_logging,
|
||||||
|
claudeV2,
|
||||||
|
sonnet45,
|
||||||
|
haiku45,
|
||||||
|
opus41,
|
||||||
|
haiku35,
|
||||||
|
sonnet4,
|
||||||
|
opus4,
|
||||||
|
},
|
||||||
"AWS model tests complete."
|
"AWS model tests complete."
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isInitialCheck) {
|
if (isInitialCheck) {
|
||||||
|
const modelIds = [
|
||||||
|
claudeV2 && AWS_CLAUDE_V2,
|
||||||
|
sonnet45 && AWS_CLAUDE_SONNET_45,
|
||||||
|
haiku45 && AWS_CLAUDE_HAIKU_45,
|
||||||
|
opus41 && AWS_CLAUDE_OPUS_41,
|
||||||
|
haiku35 && AWS_CLAUDE_35_HAIKU,
|
||||||
|
sonnet4 && AWS_CLAUDE_SONNET_4,
|
||||||
|
opus4 && AWS_CLAUDE_OPUS_4,
|
||||||
|
].filter(Boolean) as string[];
|
||||||
|
|
||||||
const families: AwsBedrockModelFamily[] = [];
|
const families: AwsBedrockModelFamily[] = [];
|
||||||
if (claudeV2 || sonnet || sonnet35 || haiku) families.push("aws-claude");
|
if (claudeV2 || sonnet45 || haiku35 || haiku45 || sonnet4) {
|
||||||
if (opus) families.push("aws-claude-opus");
|
families.push("aws-claude");
|
||||||
|
}
|
||||||
|
if (opus41 || opus4) families.push("aws-claude-opus");
|
||||||
|
|
||||||
if (families.length === 0) {
|
if (families.length === 0) {
|
||||||
this.log.warn(
|
this.log.warn(
|
||||||
@@ -85,9 +124,10 @@ export class AwsKeyChecker extends KeyCheckerBase<AwsBedrockKey> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.updateKey(key.hash, {
|
this.updateKey(key.hash, {
|
||||||
sonnetEnabled: sonnet,
|
sonnetEnabled: sonnet45 || sonnet4,
|
||||||
haikuEnabled: haiku,
|
haikuEnabled: haiku45 || haiku35,
|
||||||
sonnet35Enabled: sonnet35,
|
sonnet35Enabled: haiku35,
|
||||||
|
modelIds,
|
||||||
modelFamilies: families,
|
modelFamilies: families,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -95,9 +135,10 @@ export class AwsKeyChecker extends KeyCheckerBase<AwsBedrockKey> {
|
|||||||
this.log.info(
|
this.log.info(
|
||||||
{
|
{
|
||||||
key: key.hash,
|
key: key.hash,
|
||||||
sonnet,
|
sonnet45,
|
||||||
haiku,
|
haiku45,
|
||||||
families: key.modelFamilies,
|
families: key.modelFamilies,
|
||||||
|
models: key.modelIds,
|
||||||
logged: key.awsLoggingStatus,
|
logged: key.awsLoggingStatus,
|
||||||
},
|
},
|
||||||
"Checked key."
|
"Checked key."
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ type AwsBedrockKeyUsage = {
|
|||||||
export interface AwsBedrockKey extends Key, AwsBedrockKeyUsage {
|
export interface AwsBedrockKey extends Key, AwsBedrockKeyUsage {
|
||||||
readonly service: "aws";
|
readonly service: "aws";
|
||||||
readonly modelFamilies: AwsBedrockModelFamily[];
|
readonly modelFamilies: AwsBedrockModelFamily[];
|
||||||
|
/** Exact Bedrock model IDs that have been verified for this key. */
|
||||||
|
modelIds: string[];
|
||||||
/** The time at which this key was last rate limited. */
|
/** The time at which this key was last rate limited. */
|
||||||
rateLimitedAt: number;
|
rateLimitedAt: number;
|
||||||
/** The time until which this key is rate limited. */
|
/** The time until which this key is rate limited. */
|
||||||
@@ -63,6 +65,7 @@ export class AwsBedrockKeyProvider implements KeyProvider<AwsBedrockKey> {
|
|||||||
key,
|
key,
|
||||||
service: this.service,
|
service: this.service,
|
||||||
modelFamilies: ["aws-claude"],
|
modelFamilies: ["aws-claude"],
|
||||||
|
modelIds: [],
|
||||||
isDisabled: false,
|
isDisabled: false,
|
||||||
isRevoked: false,
|
isRevoked: false,
|
||||||
promptCount: 0,
|
promptCount: 0,
|
||||||
@@ -115,12 +118,14 @@ export class AwsBedrockKeyProvider implements KeyProvider<AwsBedrockKey> {
|
|||||||
|
|
||||||
const availableKeys = this.keys.filter((k) => {
|
const availableKeys = this.keys.filter((k) => {
|
||||||
const isNotLogged = k.awsLoggingStatus !== "enabled";
|
const isNotLogged = k.awsLoggingStatus !== "enabled";
|
||||||
|
const hasExactInventory = k.modelIds.length > 0;
|
||||||
return (
|
return (
|
||||||
!k.isDisabled &&
|
!k.isDisabled &&
|
||||||
(isNotLogged || config.allowAwsLogging) &&
|
(isNotLogged || config.allowAwsLogging) &&
|
||||||
(k.sonnetEnabled || !needsSonnet) && // sonnet and haiku are both under aws-claude, while opus is not
|
(!hasExactInventory || k.modelIds.includes(model)) &&
|
||||||
(k.haikuEnabled || !needsHaiku) &&
|
(hasExactInventory || k.sonnetEnabled || !needsSonnet) && // sonnet and haiku are both under aws-claude, while opus is not
|
||||||
(k.sonnet35Enabled || !needsSonnet35) &&
|
(hasExactInventory || k.haikuEnabled || !needsHaiku) &&
|
||||||
|
(hasExactInventory || k.sonnet35Enabled || !needsSonnet35) &&
|
||||||
k.modelFamilies.includes(neededFamily)
|
k.modelFamilies.includes(neededFamily)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { getOpenAIModelFamily } from "../../models";
|
|||||||
const MIN_CHECK_INTERVAL = 3 * 1000; // 3 seconds
|
const MIN_CHECK_INTERVAL = 3 * 1000; // 3 seconds
|
||||||
const KEY_CHECK_PERIOD = 60 * 60 * 1000; // 1 hour
|
const KEY_CHECK_PERIOD = 60 * 60 * 1000; // 1 hour
|
||||||
const POST_CHAT_COMPLETIONS_URL = "https://api.openai.com/v1/chat/completions";
|
const POST_CHAT_COMPLETIONS_URL = "https://api.openai.com/v1/chat/completions";
|
||||||
|
const POST_COMPLETIONS_URL = "https://api.openai.com/v1/completions";
|
||||||
const GET_MODELS_URL = "https://api.openai.com/v1/models";
|
const GET_MODELS_URL = "https://api.openai.com/v1/models";
|
||||||
const GET_ORGANIZATIONS_URL = "https://api.openai.com/v1/organizations";
|
const GET_ORGANIZATIONS_URL = "https://api.openai.com/v1/organizations";
|
||||||
|
|
||||||
@@ -302,13 +303,9 @@ export class OpenAIKeyChecker extends KeyCheckerBase<OpenAIKey> {
|
|||||||
// the same trial key and even the text completion quota is exhausted, but
|
// the same trial key and even the text completion quota is exhausted, but
|
||||||
// it should work better than the alternative.
|
// it should work better than the alternative.
|
||||||
|
|
||||||
const payload = {
|
const payload = { model: "babbage-002", max_tokens: -1, prompt: "" };
|
||||||
model: "babbage-002",
|
|
||||||
max_tokens: -1,
|
|
||||||
messages: [{ role: "user", content: "" }],
|
|
||||||
};
|
|
||||||
const { headers, data } = await axios.post<OpenAIError>(
|
const { headers, data } = await axios.post<OpenAIError>(
|
||||||
POST_CHAT_COMPLETIONS_URL,
|
POST_COMPLETIONS_URL,
|
||||||
payload,
|
payload,
|
||||||
{
|
{
|
||||||
headers: OpenAIKeyChecker.getHeaders(key),
|
headers: OpenAIKeyChecker.getHeaders(key),
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export const LLM_SERVICES = (<A extends readonly LLMService[]>(
|
|||||||
export const OPENAI_MODEL_FAMILY_MAP: { [regex: string]: OpenAIModelFamily } = {
|
export const OPENAI_MODEL_FAMILY_MAP: { [regex: string]: OpenAIModelFamily } = {
|
||||||
"^gpt-5(\\.\\d+)?([-.].+)?$": "gpt5",
|
"^gpt-5(\\.\\d+)?([-.].+)?$": "gpt5",
|
||||||
"^o\\d([-.].+)?$": "o-series",
|
"^o\\d([-.].+)?$": "o-series",
|
||||||
"^computer-use-preview$": "o-series",
|
"^computer-use-preview(?:-\\d{4}-\\d{2}-\\d{2})?$": "o-series",
|
||||||
"^gpt-4\\.1([-.].+)?$": "gpt4o",
|
"^gpt-4\\.1([-.].+)?$": "gpt4o",
|
||||||
"^gpt-4o": "gpt4o",
|
"^gpt-4o": "gpt4o",
|
||||||
"^gpt-4-turbo(-\\d{4}-\\d{2}-\\d{2})?$": "gpt4-turbo",
|
"^gpt-4-turbo(-\\d{4}-\\d{2}-\\d{2})?$": "gpt4-turbo",
|
||||||
@@ -116,7 +116,7 @@ export const MODEL_FAMILY_SERVICE: {
|
|||||||
gpt4: "openai",
|
gpt4: "openai",
|
||||||
"gpt4-turbo": "openai",
|
"gpt4-turbo": "openai",
|
||||||
"gpt4-32k": "openai",
|
"gpt4-32k": "openai",
|
||||||
"gpt4o": "openai",
|
gpt4o: "openai",
|
||||||
gpt5: "openai",
|
gpt5: "openai",
|
||||||
"o-series": "openai",
|
"o-series": "openai",
|
||||||
"dall-e": "openai",
|
"dall-e": "openai",
|
||||||
|
|||||||
Reference in New Issue
Block a user