Fix provider model inventories
This commit is contained in:
+57
-27
@@ -1,7 +1,13 @@
|
||||
import { Request, Response, RequestHandler, Router } from "express";
|
||||
import { createProxyMiddleware } from "http-proxy-middleware";
|
||||
import { config } from "../config";
|
||||
import { keyPool, AnthropicKey } from "../shared/key-management";
|
||||
import { logger } from "../logger";
|
||||
import {
|
||||
AnthropicModelFamily,
|
||||
getClaudeModelFamily,
|
||||
ModelFamily,
|
||||
} from "../shared/models";
|
||||
import { createQueueMiddleware } from "./queue";
|
||||
import { ipLimiter } from "./rate-limit";
|
||||
import { handleProxyError } from "./middleware/common";
|
||||
@@ -21,29 +27,42 @@ import { sendErrorToClient } from "./middleware/response/error-generator";
|
||||
let modelsCache: any = null;
|
||||
let modelsCacheTime = 0;
|
||||
|
||||
const getModelsResponse = () => {
|
||||
if (new Date().getTime() - modelsCacheTime < 1000 * 60) {
|
||||
return modelsCache;
|
||||
}
|
||||
|
||||
if (!config.anthropicKey) return { object: "list", data: [] };
|
||||
|
||||
const claudeVariants = [
|
||||
"claude-2.0",
|
||||
"claude-2.1",
|
||||
"claude-sonnet-4-5",
|
||||
"claude-sonnet-4-5-20250929",
|
||||
"claude-haiku-4-5",
|
||||
export const KNOWN_ANTHROPIC_MODELS = [
|
||||
"claude-3-haiku-20240307",
|
||||
"claude-haiku-4-5-20251001",
|
||||
"claude-opus-4-1",
|
||||
"claude-opus-4-1-20250805",
|
||||
"claude-opus-4-20250514",
|
||||
"claude-opus-4-5-20251101",
|
||||
"claude-opus-4-6",
|
||||
"claude-sonnet-4-20250514",
|
||||
"claude-3-5-haiku-20241022",
|
||||
"claude-3-5-haiku-latest",
|
||||
];
|
||||
"claude-sonnet-4-5-20250929",
|
||||
"claude-sonnet-4-6",
|
||||
];
|
||||
|
||||
const models = claudeVariants.map((id) => ({
|
||||
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));
|
||||
}
|
||||
|
||||
const allowed = new Set<ModelFamily>(config.allowedModelFamilies);
|
||||
availableFamilies = new Set(
|
||||
[...availableFamilies].filter((family) => allowed.has(family))
|
||||
);
|
||||
|
||||
const usingExactModelIds = availableModelIds.size > 0;
|
||||
const sourceModels = usingExactModelIds
|
||||
? [...availableModelIds].sort()
|
||||
: models;
|
||||
|
||||
return sourceModels
|
||||
.map((id) => ({
|
||||
id,
|
||||
object: "model",
|
||||
created: new Date().getTime(),
|
||||
@@ -51,16 +70,27 @@ const getModelsResponse = () => {
|
||||
permission: [],
|
||||
root: "claude",
|
||||
parent: null,
|
||||
}));
|
||||
|
||||
modelsCache = { object: "list", data: models };
|
||||
modelsCacheTime = new Date().getTime();
|
||||
|
||||
return modelsCache;
|
||||
};
|
||||
}))
|
||||
.filter((model) => {
|
||||
if (usingExactModelIds) {
|
||||
return (
|
||||
allowed.has(getClaudeModelFamily(model.id)) &&
|
||||
availableModelIds.has(model.id)
|
||||
);
|
||||
}
|
||||
return availableFamilies.has(getClaudeModelFamily(model.id));
|
||||
});
|
||||
}
|
||||
|
||||
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. */
|
||||
@@ -350,7 +380,7 @@ function maybeReassignModel(req: Request) {
|
||||
lower.startsWith("o1") ||
|
||||
lower.startsWith("o3") ||
|
||||
lower.startsWith("o4") ||
|
||||
lower === "computer-use-preview"
|
||||
lower.startsWith("computer-use-preview")
|
||||
) {
|
||||
req.body.model = "claude-sonnet-4-5-20250929";
|
||||
}
|
||||
|
||||
+41
-4
@@ -2,7 +2,13 @@ import { Request, RequestHandler, Response, Router } from "express";
|
||||
import { createProxyMiddleware } from "http-proxy-middleware";
|
||||
import { v4 } from "uuid";
|
||||
import { config } from "../config";
|
||||
import { keyPool, AwsBedrockKey } from "../shared/key-management";
|
||||
import { logger } from "../logger";
|
||||
import {
|
||||
AwsBedrockModelFamily,
|
||||
getAwsBedrockModelFamily,
|
||||
ModelFamily,
|
||||
} from "../shared/models";
|
||||
import { createQueueMiddleware } from "./queue";
|
||||
import { ipLimiter } from "./rate-limit";
|
||||
import { handleProxyError } from "./middleware/common";
|
||||
@@ -16,7 +22,10 @@ import {
|
||||
ProxyResHandlerWithBody,
|
||||
createOnProxyResHandler,
|
||||
} from "./middleware/response";
|
||||
import { transformAnthropicChatResponseToAnthropicText, transformAnthropicChatResponseToOpenAI } from "./anthropic";
|
||||
import {
|
||||
transformAnthropicChatResponseToAnthropicText,
|
||||
transformAnthropicChatResponseToOpenAI,
|
||||
} from "./anthropic";
|
||||
import { sendErrorToClient } from "./middleware/response/error-generator";
|
||||
|
||||
const LATEST_AWS_V2_MINOR_VERSION = "1";
|
||||
@@ -37,6 +46,21 @@ const getModelsResponse = () => {
|
||||
|
||||
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
|
||||
const variants = [
|
||||
"anthropic.claude-v2",
|
||||
@@ -48,8 +72,12 @@ const getModelsResponse = () => {
|
||||
AWS_CLAUDE_SONNET_4,
|
||||
AWS_CLAUDE_OPUS_4,
|
||||
];
|
||||
const sourceModels = usingExactModelIds
|
||||
? [...availableModelIds].sort()
|
||||
: variants;
|
||||
|
||||
const models = variants.map((id) => ({
|
||||
const models = sourceModels
|
||||
.map((id) => ({
|
||||
id,
|
||||
object: "model",
|
||||
created: new Date().getTime(),
|
||||
@@ -57,7 +85,16 @@ const getModelsResponse = () => {
|
||||
permission: [],
|
||||
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 };
|
||||
modelsCacheTime = new Date().getTime();
|
||||
@@ -302,7 +339,7 @@ function maybeReassignModel(req: Request) {
|
||||
lower.startsWith("o1") ||
|
||||
lower.startsWith("o3") ||
|
||||
lower.startsWith("o4") ||
|
||||
lower === "computer-use-preview"
|
||||
lower.startsWith("computer-use-preview")
|
||||
) {
|
||||
req.body.model = AWS_CLAUDE_SONNET_45;
|
||||
return;
|
||||
|
||||
+1
-1
@@ -208,7 +208,7 @@ function maybeReassignModel(req: Request) {
|
||||
lower.startsWith("o1") ||
|
||||
lower.startsWith("o3") ||
|
||||
lower.startsWith("o4") ||
|
||||
lower === "computer-use-preview"
|
||||
lower.startsWith("computer-use-preview")
|
||||
) {
|
||||
req.body.model = GCP_CLAUDE_SONNET_45;
|
||||
return;
|
||||
|
||||
+62
-6
@@ -33,6 +33,12 @@ export const KNOWN_OPENAI_MODELS = [
|
||||
"gpt-5.2-chat-latest",
|
||||
"gpt-5.2-pro",
|
||||
"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-chat",
|
||||
"gpt-5.1-codex",
|
||||
@@ -48,9 +54,12 @@ export const KNOWN_OPENAI_MODELS = [
|
||||
"gpt-4.1-2025-04-14",
|
||||
"gpt-4.1-mini",
|
||||
"gpt-4.1-nano",
|
||||
"gpt-4.1-mini-2025-04-14",
|
||||
"gpt-4.1-nano-2025-04-14",
|
||||
"o3-pro",
|
||||
"o3-deep-research",
|
||||
"computer-use-preview",
|
||||
"computer-use-preview-2025-03-11",
|
||||
"o4-mini",
|
||||
"o4-mini-deep-research",
|
||||
"o3",
|
||||
@@ -59,8 +68,13 @@ export const KNOWN_OPENAI_MODELS = [
|
||||
"o1-pro",
|
||||
"gpt-4o",
|
||||
"gpt-4o-2024-08-06",
|
||||
"gpt-4o-2024-11-20",
|
||||
"gpt-4o-mini",
|
||||
"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-2024-04-09", // gpt4-turbo stable, with vision
|
||||
"gpt-4",
|
||||
@@ -74,13 +88,45 @@ export const KNOWN_OPENAI_MODELS = [
|
||||
"text-embedding-3-small",
|
||||
"text-embedding-3-large",
|
||||
"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 modelsCacheTime = 0;
|
||||
|
||||
@@ -103,9 +149,18 @@ export function generateModelList(models = KNOWN_OPENAI_MODELS) {
|
||||
[...availableFamilies].filter((x) => allowed.has(x))
|
||||
);
|
||||
const usingExactModelIds = availableModelIds.size > 0;
|
||||
const supportedFamilies = new Set(
|
||||
models.map((model) => getOpenAIModelFamily(model))
|
||||
);
|
||||
|
||||
const sourceModels = usingExactModelIds
|
||||
? [...new Set([...models, ...availableModelIds])]
|
||||
? [...availableModelIds]
|
||||
.filter(
|
||||
(model) =>
|
||||
isSupportedOpenAIModelId(model) &&
|
||||
supportedFamilies.has(getOpenAIModelFamily(model))
|
||||
)
|
||||
.sort()
|
||||
: models;
|
||||
|
||||
return sourceModels
|
||||
@@ -130,6 +185,7 @@ export function generateModelList(models = KNOWN_OPENAI_MODELS) {
|
||||
.filter((model) => {
|
||||
if (usingExactModelIds) {
|
||||
return (
|
||||
isSupportedOpenAIModelId(model.id) &&
|
||||
allowed.has(getOpenAIModelFamily(model.id)) &&
|
||||
availableModelIds.has(model.id)
|
||||
);
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import axios, { AxiosError, AxiosResponse } from "axios";
|
||||
import { KeyCheckerBase } from "../key-checker-base";
|
||||
import type { AnthropicKey, AnthropicKeyProvider } from "./provider";
|
||||
import { AnthropicModelFamily, getClaudeModelFamily } from "../../models";
|
||||
|
||||
const MIN_CHECK_INTERVAL = 3 * 1000; // 3 seconds
|
||||
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 TEST_MODEL = "claude-3-sonnet-20240229";
|
||||
const DEFAULT_TEST_MODEL = "claude-3-haiku-20240307";
|
||||
const SYSTEM = "Obey all instructions from the user.";
|
||||
const DETECTION_PROMPT = [
|
||||
{
|
||||
@@ -35,6 +37,10 @@ type MessageResponse = {
|
||||
content: { type: "text"; text: string }[];
|
||||
};
|
||||
|
||||
type GetModelsResponse = {
|
||||
data: { id: string }[];
|
||||
};
|
||||
|
||||
type AnthropicAPIError = {
|
||||
error: { type: string; message: string };
|
||||
};
|
||||
@@ -52,11 +58,21 @@ export class AnthropicKeyChecker extends KeyCheckerBase<AnthropicKey> {
|
||||
}
|
||||
|
||||
protected async testKeyOrFail(key: AnthropicKey) {
|
||||
const [{ pozzed, tier }] = await Promise.all([this.testLiveness(key)]);
|
||||
const updates = { isPozzed: pozzed, tier };
|
||||
const isInitialCheck = !key.lastChecked;
|
||||
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.log.info(
|
||||
{ key: key.hash, tier, models: key.modelFamilies },
|
||||
{ key: key.hash, tier: liveness.tier, models: keyFromPool.modelIds },
|
||||
"Checked key."
|
||||
);
|
||||
}
|
||||
@@ -131,7 +147,7 @@ export class AnthropicKeyChecker extends KeyCheckerBase<AnthropicKey> {
|
||||
key: AnthropicKey
|
||||
): Promise<{ pozzed: boolean; tier: AnthropicKey["tier"] }> {
|
||||
const payload = {
|
||||
model: TEST_MODEL,
|
||||
model: this.getLivenessModel(key),
|
||||
max_tokens: 40,
|
||||
temperature: 0,
|
||||
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(
|
||||
error: AxiosError
|
||||
): error is AxiosError<AnthropicAPIError> {
|
||||
|
||||
@@ -23,6 +23,8 @@ type AnthropicKeyUsage = {
|
||||
export interface AnthropicKey extends Key, AnthropicKeyUsage {
|
||||
readonly service: "anthropic";
|
||||
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. */
|
||||
rateLimitedAt: number;
|
||||
/** The time until which this key is rate limited. */
|
||||
@@ -108,6 +110,7 @@ export class AnthropicKeyProvider implements KeyProvider<AnthropicKey> {
|
||||
key,
|
||||
service: this.service,
|
||||
modelFamilies: ["claude", "claude-opus"],
|
||||
modelIds: [],
|
||||
isDisabled: false,
|
||||
isOverQuota: false,
|
||||
isRevoked: false,
|
||||
@@ -145,11 +148,18 @@ export class AnthropicKeyProvider implements KeyProvider<AnthropicKey> {
|
||||
}
|
||||
|
||||
public get(rawModel: string) {
|
||||
this.log.debug({ model: rawModel }, "Selecting key");
|
||||
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) => {
|
||||
return !k.isDisabled && (!needsMultimodal || k.allowsMultimodality);
|
||||
return (
|
||||
!k.isDisabled &&
|
||||
(!needsMultimodal || k.allowsMultimodality) &&
|
||||
(!k.modelIds.length || k.modelIds.includes(model))
|
||||
);
|
||||
});
|
||||
|
||||
if (availableKeys.length === 0) {
|
||||
|
||||
@@ -17,6 +17,13 @@ const GET_INVOCATION_LOGGING_CONFIG_URL = (region: string) =>
|
||||
`https://bedrock.${region}.amazonaws.com/logging/modelinvocations`;
|
||||
const POST_INVOKE_MODEL_URL = (region: string, model: string) =>
|
||||
`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 = [
|
||||
{ role: "user", content: "Hi!" },
|
||||
{ role: "assistant", content: "Hello!" },
|
||||
@@ -53,28 +60,60 @@ export class AwsKeyChecker extends KeyCheckerBase<AwsBedrockKey> {
|
||||
const isInitialCheck = !key.lastChecked;
|
||||
if (isInitialCheck) {
|
||||
checks = [
|
||||
this.invokeModel("anthropic.claude-v2", key),
|
||||
this.invokeModel("anthropic.claude-sonnet-4-5-20250929-v1:0", key),
|
||||
this.invokeModel("anthropic.claude-haiku-4-5-20251001-v1:0", key),
|
||||
this.invokeModel("anthropic.claude-opus-4-1-20250805-v1:0", key),
|
||||
this.invokeModel("anthropic.claude-3-5-haiku-20241022-v1:0", key),
|
||||
this.invokeModel(AWS_CLAUDE_V2, key),
|
||||
this.invokeModel(AWS_CLAUDE_SONNET_45, key),
|
||||
this.invokeModel(AWS_CLAUDE_HAIKU_45, key),
|
||||
this.invokeModel(AWS_CLAUDE_OPUS_41, 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));
|
||||
|
||||
const [_logging, claudeV2, sonnet, haiku, opus, sonnet35] =
|
||||
await Promise.all(checks);
|
||||
const [
|
||||
_logging,
|
||||
claudeV2,
|
||||
sonnet45,
|
||||
haiku45,
|
||||
opus41,
|
||||
haiku35,
|
||||
sonnet4,
|
||||
opus4,
|
||||
] = await Promise.all(checks);
|
||||
|
||||
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."
|
||||
);
|
||||
|
||||
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[] = [];
|
||||
if (claudeV2 || sonnet || sonnet35 || haiku) families.push("aws-claude");
|
||||
if (opus) families.push("aws-claude-opus");
|
||||
if (claudeV2 || sonnet45 || haiku35 || haiku45 || sonnet4) {
|
||||
families.push("aws-claude");
|
||||
}
|
||||
if (opus41 || opus4) families.push("aws-claude-opus");
|
||||
|
||||
if (families.length === 0) {
|
||||
this.log.warn(
|
||||
@@ -85,9 +124,10 @@ export class AwsKeyChecker extends KeyCheckerBase<AwsBedrockKey> {
|
||||
}
|
||||
|
||||
this.updateKey(key.hash, {
|
||||
sonnetEnabled: sonnet,
|
||||
haikuEnabled: haiku,
|
||||
sonnet35Enabled: sonnet35,
|
||||
sonnetEnabled: sonnet45 || sonnet4,
|
||||
haikuEnabled: haiku45 || haiku35,
|
||||
sonnet35Enabled: haiku35,
|
||||
modelIds,
|
||||
modelFamilies: families,
|
||||
});
|
||||
}
|
||||
@@ -95,9 +135,10 @@ export class AwsKeyChecker extends KeyCheckerBase<AwsBedrockKey> {
|
||||
this.log.info(
|
||||
{
|
||||
key: key.hash,
|
||||
sonnet,
|
||||
haiku,
|
||||
sonnet45,
|
||||
haiku45,
|
||||
families: key.modelFamilies,
|
||||
models: key.modelIds,
|
||||
logged: key.awsLoggingStatus,
|
||||
},
|
||||
"Checked key."
|
||||
|
||||
@@ -13,6 +13,8 @@ type AwsBedrockKeyUsage = {
|
||||
export interface AwsBedrockKey extends Key, AwsBedrockKeyUsage {
|
||||
readonly service: "aws";
|
||||
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. */
|
||||
rateLimitedAt: number;
|
||||
/** The time until which this key is rate limited. */
|
||||
@@ -63,6 +65,7 @@ export class AwsBedrockKeyProvider implements KeyProvider<AwsBedrockKey> {
|
||||
key,
|
||||
service: this.service,
|
||||
modelFamilies: ["aws-claude"],
|
||||
modelIds: [],
|
||||
isDisabled: false,
|
||||
isRevoked: false,
|
||||
promptCount: 0,
|
||||
@@ -115,12 +118,14 @@ export class AwsBedrockKeyProvider implements KeyProvider<AwsBedrockKey> {
|
||||
|
||||
const availableKeys = this.keys.filter((k) => {
|
||||
const isNotLogged = k.awsLoggingStatus !== "enabled";
|
||||
const hasExactInventory = k.modelIds.length > 0;
|
||||
return (
|
||||
!k.isDisabled &&
|
||||
(isNotLogged || config.allowAwsLogging) &&
|
||||
(k.sonnetEnabled || !needsSonnet) && // sonnet and haiku are both under aws-claude, while opus is not
|
||||
(k.haikuEnabled || !needsHaiku) &&
|
||||
(k.sonnet35Enabled || !needsSonnet35) &&
|
||||
(!hasExactInventory || k.modelIds.includes(model)) &&
|
||||
(hasExactInventory || k.sonnetEnabled || !needsSonnet) && // sonnet and haiku are both under aws-claude, while opus is not
|
||||
(hasExactInventory || k.haikuEnabled || !needsHaiku) &&
|
||||
(hasExactInventory || k.sonnet35Enabled || !needsSonnet35) &&
|
||||
k.modelFamilies.includes(neededFamily)
|
||||
);
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import { getOpenAIModelFamily } from "../../models";
|
||||
const MIN_CHECK_INTERVAL = 3 * 1000; // 3 seconds
|
||||
const KEY_CHECK_PERIOD = 60 * 60 * 1000; // 1 hour
|
||||
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_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
|
||||
// it should work better than the alternative.
|
||||
|
||||
const payload = {
|
||||
model: "babbage-002",
|
||||
max_tokens: -1,
|
||||
messages: [{ role: "user", content: "" }],
|
||||
};
|
||||
const payload = { model: "babbage-002", max_tokens: -1, prompt: "" };
|
||||
const { headers, data } = await axios.post<OpenAIError>(
|
||||
POST_CHAT_COMPLETIONS_URL,
|
||||
POST_COMPLETIONS_URL,
|
||||
payload,
|
||||
{
|
||||
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 } = {
|
||||
"^gpt-5(\\.\\d+)?([-.].+)?$": "gpt5",
|
||||
"^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-4o": "gpt4o",
|
||||
"^gpt-4-turbo(-\\d{4}-\\d{2}-\\d{2})?$": "gpt4-turbo",
|
||||
@@ -116,7 +116,7 @@ export const MODEL_FAMILY_SERVICE: {
|
||||
gpt4: "openai",
|
||||
"gpt4-turbo": "openai",
|
||||
"gpt4-32k": "openai",
|
||||
"gpt4o": "openai",
|
||||
gpt4o: "openai",
|
||||
gpt5: "openai",
|
||||
"o-series": "openai",
|
||||
"dall-e": "openai",
|
||||
|
||||
Reference in New Issue
Block a user