This commit is contained in:
reanon
2025-08-05 19:51:02 +02:00
parent e974da8a58
commit 9cc86c2d68
9 changed files with 189 additions and 11 deletions
+82
View File
@@ -0,0 +1,82 @@
import { Request } from "express";
/**
* Claude Opus 4.1 has stricter API validation that doesn't allow both temperature
* and top_p parameters to be specified simultaneously. This function validates and
* adjusts the request parameters for Claude Opus 4.1 models ONLY.
*
* Rules:
* - If both parameters are at default values (1.0), omit top_p
* - If only one parameter is at default, omit the default one
* - If both are non-default, throw an error
*/
export function validateClaude41OpusParameters(req: Request): void {
const model = req.body.model;
// Only apply this validation to Claude Opus 4.1 models
if (!isClaude41OpusModel(model)) {
return;
}
const temperature = req.body.temperature;
const topP = req.body.top_p;
// If neither parameter is specified, no validation needed
if (temperature === undefined && topP === undefined) {
return;
}
// Default values for Claude API
const DEFAULT_TEMPERATURE = 1.0;
const DEFAULT_TOP_P = 1.0;
const tempIsDefault = temperature === undefined || temperature === DEFAULT_TEMPERATURE;
const topPIsDefault = topP === undefined || topP === DEFAULT_TOP_P;
// If both are at default values, omit top_p (keep temperature)
if (tempIsDefault && topPIsDefault) {
delete req.body.top_p;
req.log?.info("Claude Opus 4.1: Both temperature and top_p at default, omitting top_p");
return;
}
// If only one is at default, omit the default one
if (tempIsDefault && !topPIsDefault) {
delete req.body.temperature;
req.log?.info("Claude Opus 4.1: Temperature at default, omitting temperature");
return;
}
if (!tempIsDefault && topPIsDefault) {
delete req.body.top_p;
req.log?.info("Claude Opus 4.1: top_p at default, omitting top_p");
return;
}
// If both are non-default, throw an error
if (!tempIsDefault && !topPIsDefault) {
throw new Error(
"Claude Opus 4.1 does not support both temperature and top_p parameters being set to non-default values simultaneously. " +
"Please specify only one of these parameters or set one to its default value (1.0)."
);
}
}
/**
* Checks if the given model is a Claude Opus 4.1 model.
* This includes all provider formats for Claude Opus 4.1 ONLY.
*/
function isClaude41OpusModel(model: string): boolean {
if (!model) return false;
// Anthropic API format
if (model.includes("claude-opus-4-1")) return true;
// AWS Bedrock format
if (model.includes("anthropic.claude-opus-4-1")) return true;
// GCP Vertex AI format
if (model.includes("claude-opus-4-1@")) return true;
return false;
}
+9 -3
View File
@@ -19,16 +19,22 @@ export const claudeModels: ClaudeModelMapping[] = [
{ awsId: "anthropic.claude-3-opus-20240229-v1:0", anthropicId: "claude-3-opus-latest", displayName: "Claude 3 Opus (Latest)" },
{ awsId: "anthropic.claude-sonnet-4-20250514-v1:0", anthropicId: "claude-sonnet-4-20250514", displayName: "Claude 4 Sonnet" },
{ awsId: "anthropic.claude-sonnet-4-20250514-v1:0", anthropicId: "claude-sonnet-4-latest", displayName: "Claude 4 Sonnet (Latest)" },
{ awsId: "anthropic.claude-opus-4-20250514-v1:0", anthropicId: "claude-opus-4-20250514", displayName: "Claude 4 Opus" },
{ awsId: "anthropic.claude-opus-4-20250514-v1:0", anthropicId: "claude-opus-4-latest", displayName: "Claude 4 Opus (Latest)" },
{ awsId: "anthropic.claude-opus-4-20250514-v1:0", anthropicId: "claude-opus-4-20250514", displayName: "Claude 4.0 Opus" },
{ awsId: "anthropic.claude-opus-4-1-20250805-v1:0", anthropicId: "claude-opus-4-1-20250805", displayName: "Claude 4.1 Opus" },
{ awsId: "anthropic.claude-opus-4-1-20250805-v1:0", anthropicId: "claude-opus-4-latest", displayName: "Claude 4 Opus (Latest)" },
{ awsId: "anthropic.claude-opus-4-1-20250805-v1:0", anthropicId: "claude-opus-4-1", displayName: "Claude 4.1 Opus" },
{ awsId: "anthropic.claude-sonnet-4-20250514-v1:0", anthropicId: "claude-sonnet-4-0", displayName: "Claude 4 Sonnet" },
{ awsId: "anthropic.claude-opus-4-20250514-v1:0", anthropicId: "claude-opus-4-0", displayName: "Claude 4 Opus" },
{ awsId: "anthropic.claude-opus-4-20250514-v1:0", anthropicId: "claude-opus-4-0", displayName: "Claude 4.0 Opus" },
];
export function findByAwsId(awsId: string): ClaudeModelMapping | undefined {
return claudeModels.find(model => model.awsId === awsId);
}
export function findByAnthropicId(anthropicId: string): ClaudeModelMapping | undefined {
return claudeModels.find(model => model.anthropicId === anthropicId);
}
export function getAllClaudeModels(): ClaudeModelMapping[] {
return claudeModels;
}
+1
View File
@@ -27,6 +27,7 @@ const KNOWN_MODEL_IDS: ModuleAliasTuple[] = [
["anthropic.claude-3-7-sonnet-20250219-v1:0"],
["anthropic.claude-sonnet-4-20250514-v1:0"],
["anthropic.claude-opus-4-20250514-v1:0"],
["anthropic.claude-opus-4-1-20250805-v1:0"],
["mistral.mistral-7b-instruct-v0:2"],
["mistral.mixtral-8x7b-instruct-v0:1"],
["mistral.mistral-large-2402-v1:0"],
+10
View File
@@ -3,6 +3,7 @@ import { config } from "../../../config";
import { logger } from "../../../logger";
import { PaymentRequiredError } from "../../errors";
import { AwsBedrockModelFamily, getAwsBedrockModelFamily } from "../../models";
import { findByAnthropicId } from "../../claude-models";
import { createGenericGetLockoutPeriod, Key, KeyProvider } from "..";
import { prioritizeKeys } from "../prioritize-keys";
import { AwsKeyChecker } from "./checker";
@@ -96,6 +97,15 @@ export class AwsBedrockKeyProvider implements KeyProvider<AwsBedrockKey> {
// Claude 2 is the only model that breaks this convention; Anthropic calls
// it claude-2 but AWS calls it claude-v2.
if (model.includes("claude-2")) neededVariantId = "claude-v2";
// For Claude models, try to resolve aliases to AWS model IDs
if (model.includes("claude") && !model.includes("anthropic.")) {
const claudeMapping = findByAnthropicId(model);
if (claudeMapping) {
neededVariantId = claudeMapping.awsId;
}
}
const neededFamily = getAwsBedrockModelFamily(model);
const availableKeys = this.keys.filter((k) => {
+5 -3
View File
@@ -42,19 +42,20 @@ export class GcpKeyChecker extends KeyCheckerBase<GcpKey> {
this.invokeModel("claude-3-haiku@20240307", key, true),
this.invokeModel("claude-3-sonnet@20240229", key, true),
this.invokeModel("claude-3-opus@20240229", key, true),
this.invokeModel("claude-opus-4-1@20250805", key, true),
this.invokeModel("claude-3-5-sonnet-v2@20241022", key, true),
];
const [sonnet, haiku, opus, sonnet35] = await Promise.all(checks);
const [sonnet, haiku, opus3, opus41, sonnet35] = await Promise.all(checks);
this.log.debug(
{ key: key.hash, sonnet, haiku, opus, sonnet35 },
{ key: key.hash, sonnet, haiku, opus3, opus41, sonnet35 },
"GCP model initial tests complete."
);
const families: GcpModelFamily[] = [];
if (sonnet || sonnet35 || haiku) families.push("gcp-claude");
if (opus) families.push("gcp-claude-opus");
if (opus3 || opus41) families.push("gcp-claude-opus");
if (families.length === 0) {
this.log.warn(
@@ -81,6 +82,7 @@ export class GcpKeyChecker extends KeyCheckerBase<GcpKey> {
await this.invokeModel("claude-3-5-sonnet-v2@20241022", key, false);
} else {
await this.invokeModel("claude-3-opus@20240229", key, false);
await this.invokeModel("claude-opus-4-1@20250805", key, false);
}
this.updateKey(key.hash, { lastChecked: Date.now() });