mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-04 14:57:42 +00:00
🧠 feat: Add Claude Opus 4.8 Support (#13380)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions
* feat: add Claude Opus 4.8 support * fix: omit sampling params for Claude Opus 4.8 * fix: flatten Bedrock beta header merge * fix: strip Bedrock sampling params for Opus 4.8
This commit is contained in:
parent
0d981b08d8
commit
62dff69300
12 changed files with 234 additions and 17 deletions
|
|
@ -171,7 +171,7 @@ PROXY=
|
|||
#============#
|
||||
|
||||
ANTHROPIC_API_KEY=user_provided
|
||||
# ANTHROPIC_MODELS=claude-opus-4-7,claude-sonnet-4-6,claude-opus-4-6,claude-opus-4-20250514,claude-3-7-sonnet-20250219,claude-3-5-sonnet-20241022,claude-3-5-haiku-20241022,claude-3-opus-20240229,claude-3-sonnet-20240229,claude-3-haiku-20240307
|
||||
# ANTHROPIC_MODELS=claude-opus-4-8,claude-opus-4-7,claude-sonnet-4-6,claude-opus-4-6,claude-opus-4-20250514,claude-3-7-sonnet-20250219,claude-3-5-sonnet-20241022,claude-3-5-haiku-20241022,claude-3-opus-20240229,claude-3-sonnet-20240229,claude-3-haiku-20240307
|
||||
# ANTHROPIC_REVERSE_PROXY=
|
||||
|
||||
# Set to true to use Anthropic models through Google Vertex AI instead of direct API
|
||||
|
|
@ -237,8 +237,8 @@ ANTHROPIC_API_KEY=user_provided
|
|||
# BEDROCK_AWS_BEARER_TOKEN=yourBedrockApiKey
|
||||
|
||||
# Note: This example list is not meant to be exhaustive. If omitted, all known, supported model IDs will be included for you.
|
||||
# BEDROCK_AWS_MODELS=anthropic.claude-opus-4-7,anthropic.claude-sonnet-4-6,anthropic.claude-opus-4-6-v1,anthropic.claude-3-5-sonnet-20240620-v1:0,meta.llama3-1-8b-instruct-v1:0
|
||||
# Cross-region inference model IDs: us.anthropic.claude-opus-4-7,us.anthropic.claude-sonnet-4-6,us.anthropic.claude-opus-4-6-v1,global.anthropic.claude-opus-4-6-v1
|
||||
# BEDROCK_AWS_MODELS=anthropic.claude-opus-4-8,anthropic.claude-opus-4-7,anthropic.claude-sonnet-4-6,anthropic.claude-opus-4-6-v1,anthropic.claude-3-5-sonnet-20240620-v1:0,meta.llama3-1-8b-instruct-v1:0
|
||||
# Cross-region inference model IDs: us.anthropic.claude-opus-4-8,us.anthropic.claude-opus-4-7,us.anthropic.claude-sonnet-4-6,us.anthropic.claude-opus-4-6-v1,global.anthropic.claude-opus-4-6-v1
|
||||
|
||||
# See all Bedrock model IDs here: https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids.html#model-ids-arns
|
||||
|
||||
|
|
|
|||
|
|
@ -1459,6 +1459,37 @@ describe('Claude Model Tests', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should return correct context length for Claude Opus 4.8 (1M)', () => {
|
||||
expect(getModelMaxTokens('claude-opus-4-8', EModelEndpoint.anthropic)).toBe(
|
||||
maxTokensMap[EModelEndpoint.anthropic]['claude-opus-4-8'],
|
||||
);
|
||||
expect(getModelMaxTokens('claude-opus-4-8')).toBe(
|
||||
maxTokensMap[EModelEndpoint.anthropic]['claude-opus-4-8'],
|
||||
);
|
||||
});
|
||||
|
||||
it('should return correct max output tokens for Claude Opus 4.8 (128K)', () => {
|
||||
const { getModelMaxOutputTokens } = require('@librechat/api');
|
||||
expect(getModelMaxOutputTokens('claude-opus-4-8', EModelEndpoint.anthropic)).toBe(
|
||||
maxOutputTokensMap[EModelEndpoint.anthropic]['claude-opus-4-8'],
|
||||
);
|
||||
});
|
||||
|
||||
it('should match model names correctly for Claude Opus 4.8', () => {
|
||||
const modelVariations = [
|
||||
'claude-opus-4-8',
|
||||
'claude-opus-4-8-20260528',
|
||||
'claude-opus-4-8-latest',
|
||||
'anthropic/claude-opus-4-8',
|
||||
'claude-opus-4-8/anthropic',
|
||||
'claude-opus-4-8-preview',
|
||||
];
|
||||
|
||||
modelVariations.forEach((model) => {
|
||||
expect(matchModelName(model, EModelEndpoint.anthropic)).toBe('claude-opus-4-8');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return correct context length for Claude Sonnet 4.6 (1M)', () => {
|
||||
expect(getModelMaxTokens('claude-sonnet-4-6', EModelEndpoint.anthropic)).toBe(
|
||||
maxTokensMap[EModelEndpoint.anthropic]['claude-sonnet-4-6'],
|
||||
|
|
|
|||
|
|
@ -223,7 +223,7 @@
|
|||
"com_endpoint_agent": "Agent",
|
||||
"com_endpoint_agent_placeholder": "Please select an Agent",
|
||||
"com_endpoint_ai": "AI",
|
||||
"com_endpoint_anthropic_effort": "Controls how much computational effort Claude applies. Lower effort saves tokens and reduces latency; higher effort produces more thorough responses. 'Max' enables the deepest reasoning (Opus 4.6 only).",
|
||||
"com_endpoint_anthropic_effort": "Controls how much computational effort Claude applies. Lower effort saves tokens and reduces latency; higher effort produces more thorough responses. 'Max' enables the deepest reasoning on supported adaptive-thinking models.",
|
||||
"com_endpoint_anthropic_maxoutputtokens": "Maximum number of tokens that can be generated in the response. Specify a lower value for shorter responses and a higher value for longer responses. Note: models may stop before reaching this maximum.",
|
||||
"com_endpoint_anthropic_prompt_cache": "Prompt caching allows reusing large context or instructions across API calls, reducing costs and latency",
|
||||
"com_endpoint_anthropic_temp": "Ranges from 0 to 1. Use temp closer to 0 for analytical / multiple choice, and closer to 1 for creative and generative tasks. We recommend altering this or Top P but not both.",
|
||||
|
|
|
|||
|
|
@ -401,6 +401,7 @@ endpoints:
|
|||
# # Option 1: Simple array (legacy format - model name = deployment name)
|
||||
# # Use this if you want the technical model IDs to show in the UI
|
||||
# # models:
|
||||
# # - "claude-opus-4-8"
|
||||
# # - "claude-sonnet-4-6"
|
||||
# # - "claude-3-7-sonnet-20250219"
|
||||
# # - "claude-3-5-sonnet-v2@20241022"
|
||||
|
|
@ -411,6 +412,8 @@ endpoints:
|
|||
# # The deploymentName is the actual Vertex AI model ID used for API calls
|
||||
# # You can use friendly names (avoid spaces for cleaner YAML) or technical IDs as keys
|
||||
# models:
|
||||
# claude-opus-4.8:
|
||||
# deploymentName: claude-opus-4-8
|
||||
# claude-opus-4.5:
|
||||
# deploymentName: claude-opus-4-5@20251101
|
||||
# claude-sonnet-4:
|
||||
|
|
|
|||
|
|
@ -888,7 +888,7 @@ describe('getLLMConfig', () => {
|
|||
expect(opus64kResult.llmConfig.maxTokens).toBe(64000);
|
||||
|
||||
// opus-4-6+ get 128K
|
||||
const opus128kModels = ['claude-opus-4-7', 'claude-opus-4-10'];
|
||||
const opus128kModels = ['claude-opus-4-7', 'claude-opus-4-8', 'claude-opus-4-10'];
|
||||
opus128kModels.forEach((model) => {
|
||||
const result = getLLMConfig('test-key', {
|
||||
modelOptions: { model },
|
||||
|
|
@ -1066,6 +1066,22 @@ describe('getLLMConfig', () => {
|
|||
expect(thinking.display).toBe('summarized');
|
||||
});
|
||||
|
||||
it('should omit sampling parameters for Opus 4.8', () => {
|
||||
const result = getLLMConfig('test-key', {
|
||||
modelOptions: {
|
||||
model: 'claude-opus-4-8',
|
||||
thinking: false,
|
||||
temperature: 0.7,
|
||||
topP: 0.9,
|
||||
topK: 40,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.llmConfig).not.toHaveProperty('temperature');
|
||||
expect(result.llmConfig).not.toHaveProperty('topP');
|
||||
expect(result.llmConfig).not.toHaveProperty('topK');
|
||||
});
|
||||
|
||||
it('should NOT set thinking.display for pre-Opus-4.7 adaptive models', () => {
|
||||
const pre47Models = ['claude-opus-4-6', 'claude-sonnet-4-6'];
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { logger } from '@librechat/data-schemas';
|
|||
import { AnthropicClientOptions } from '@librechat/agents';
|
||||
import {
|
||||
anthropicSettings,
|
||||
omitsSamplingParameters,
|
||||
removeNullishValues,
|
||||
ThinkingDisplay,
|
||||
AuthKeys,
|
||||
|
|
@ -177,9 +178,12 @@ function getLLMConfig(
|
|||
);
|
||||
}
|
||||
|
||||
const resolvedModel = requestOptions.model ?? mergedOptions.model;
|
||||
const shouldOmitSamplingParameters = omitsSamplingParameters(resolvedModel);
|
||||
|
||||
requestOptions = configureReasoning(requestOptions, systemOptions);
|
||||
|
||||
if (supportsAdaptiveThinking(mergedOptions.model)) {
|
||||
if (supportsAdaptiveThinking(resolvedModel)) {
|
||||
if (
|
||||
systemOptions.effort &&
|
||||
(systemOptions.effort as string) !== '' &&
|
||||
|
|
@ -204,8 +208,8 @@ function getLLMConfig(
|
|||
|
||||
const hasActiveThinking = requestOptions.thinking != null;
|
||||
const isThinkingModel =
|
||||
/claude-3[-.]7/.test(mergedOptions.model) || supportsAdaptiveThinking(mergedOptions.model);
|
||||
if (!isThinkingModel || !hasActiveThinking) {
|
||||
/claude-3[-.]7/.test(resolvedModel) || supportsAdaptiveThinking(resolvedModel);
|
||||
if (!shouldOmitSamplingParameters && (!isThinkingModel || !hasActiveThinking)) {
|
||||
requestOptions.topP = mergedOptions.topP;
|
||||
requestOptions.topK = mergedOptions.topK;
|
||||
}
|
||||
|
|
@ -293,6 +297,12 @@ function getLLMConfig(
|
|||
});
|
||||
}
|
||||
|
||||
if (shouldOmitSamplingParameters) {
|
||||
delete requestOptions.temperature;
|
||||
delete requestOptions.topP;
|
||||
delete requestOptions.topK;
|
||||
}
|
||||
|
||||
const tools = [];
|
||||
|
||||
if (enableWebSearch) {
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ const anthropicModels = {
|
|||
'claude-sonnet-4-6': 1000000,
|
||||
'claude-opus-4-6': 1000000,
|
||||
'claude-opus-4-7': 1000000,
|
||||
'claude-opus-4-8': 1000000,
|
||||
};
|
||||
|
||||
const deepseekModels = {
|
||||
|
|
@ -394,6 +395,7 @@ const anthropicMaxOutputs = {
|
|||
'claude-opus-4-5': 64000,
|
||||
'claude-opus-4-6': 128000,
|
||||
'claude-opus-4-7': 128000,
|
||||
'claude-opus-4-8': 128000,
|
||||
'claude-3.5-sonnet': 8192,
|
||||
'claude-3-5-sonnet': 8192,
|
||||
'claude-3.7-sonnet': 128000,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { ThinkingDisplay } from '../src/schemas';
|
|||
import {
|
||||
BEDROCK_OUTPUT_128K_BETA,
|
||||
supportsAdaptiveThinking,
|
||||
omitsSamplingParameters,
|
||||
omitsThinkingByDefault,
|
||||
resolveThinkingDisplay,
|
||||
bedrockOutputParser,
|
||||
|
|
@ -222,7 +223,7 @@ describe('omitsThinkingByDefault', () => {
|
|||
expect(omitsThinkingByDefault('us.anthropic.claude-opus-4-7')).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true for claude-opus-4-8 (future Opus 4.x)', () => {
|
||||
test('returns true for claude-opus-4-8', () => {
|
||||
expect(omitsThinkingByDefault('claude-opus-4-8')).toBe(true);
|
||||
});
|
||||
|
||||
|
|
@ -238,6 +239,11 @@ describe('omitsThinkingByDefault', () => {
|
|||
expect(omitsThinkingByDefault('claude-opus-4-6')).toBe(false);
|
||||
});
|
||||
|
||||
test('returns false for base Opus 4 snapshot IDs', () => {
|
||||
expect(omitsThinkingByDefault('claude-opus-4-20250514')).toBe(false);
|
||||
expect(omitsThinkingByDefault('anthropic.claude-opus-4-20250514-v1:0')).toBe(false);
|
||||
});
|
||||
|
||||
test('returns false for claude-opus-4-5', () => {
|
||||
expect(omitsThinkingByDefault('claude-opus-4-5')).toBe(false);
|
||||
});
|
||||
|
|
@ -264,6 +270,36 @@ describe('omitsThinkingByDefault', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('omitsSamplingParameters', () => {
|
||||
test('returns true for Opus 4.7+ models', () => {
|
||||
const models = [
|
||||
'claude-opus-4-7',
|
||||
'claude-opus-4-8',
|
||||
'anthropic.claude-opus-4-8',
|
||||
'us.anthropic.claude-opus-4-8',
|
||||
'claude-opus-5',
|
||||
];
|
||||
|
||||
models.forEach((model) => {
|
||||
expect(omitsSamplingParameters(model)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('returns false for older Opus and non-Opus models', () => {
|
||||
const models = [
|
||||
'claude-opus-4-20250514',
|
||||
'anthropic.claude-opus-4-20250514-v1:0',
|
||||
'claude-opus-4-1-20250805',
|
||||
'claude-opus-4-6',
|
||||
'claude-sonnet-4-7',
|
||||
];
|
||||
|
||||
models.forEach((model) => {
|
||||
expect(omitsSamplingParameters(model)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveThinkingDisplay', () => {
|
||||
test('returns "summarized" for Opus 4.7 when explicit is auto/null/undefined', () => {
|
||||
expect(resolveThinkingDisplay('claude-opus-4-7', ThinkingDisplay.auto)).toBe('summarized');
|
||||
|
|
@ -541,6 +577,47 @@ describe('bedrockInputParser', () => {
|
|||
expect(additionalFields.effort).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should strip sampling parameters for Opus 4.8 Bedrock models', () => {
|
||||
const input = {
|
||||
model: 'anthropic.claude-opus-4-8',
|
||||
temperature: 0.7,
|
||||
topP: 0.9,
|
||||
topK: 40,
|
||||
top_p: 0.8,
|
||||
additionalModelRequestFields: {
|
||||
custom_flag: true,
|
||||
temperature: 0.5,
|
||||
topP: 0.95,
|
||||
top_k: 20,
|
||||
},
|
||||
};
|
||||
const result = bedrockInputParser.parse(input) as Record<string, unknown>;
|
||||
const additionalFields = result.additionalModelRequestFields as Record<string, unknown>;
|
||||
expect(result.temperature).toBeUndefined();
|
||||
expect(result.topP).toBeUndefined();
|
||||
expect(additionalFields.temperature).toBeUndefined();
|
||||
expect(additionalFields.topP).toBeUndefined();
|
||||
expect(additionalFields.top_p).toBeUndefined();
|
||||
expect(additionalFields.top_k).toBeUndefined();
|
||||
expect(additionalFields.custom_flag).toBe(true);
|
||||
expect(additionalFields.thinking).toEqual({ type: 'adaptive', display: 'summarized' });
|
||||
expect(additionalFields.anthropic_beta).toEqual(BEDROCK_CLAUDE_4_BETAS);
|
||||
});
|
||||
|
||||
test('should preserve sampling parameters for Opus 4.6 Bedrock models', () => {
|
||||
const input = {
|
||||
model: 'anthropic.claude-opus-4-6-v1',
|
||||
temperature: 0.7,
|
||||
topP: 0.9,
|
||||
topK: 40,
|
||||
};
|
||||
const result = bedrockInputParser.parse(input) as Record<string, unknown>;
|
||||
const additionalFields = result.additionalModelRequestFields as Record<string, unknown>;
|
||||
expect(result.temperature).toBe(0.7);
|
||||
expect(result.topP).toBe(0.9);
|
||||
expect(additionalFields.top_k).toBe(40);
|
||||
});
|
||||
|
||||
test('should set thinking.display to "summarized" so Opus 4.7 returns reasoning blocks', () => {
|
||||
const input = {
|
||||
model: 'anthropic.claude-opus-4-7',
|
||||
|
|
|
|||
|
|
@ -68,14 +68,14 @@ type AnthropicInput = BedrockConverseInput & {
|
|||
|
||||
/** Extracts opus major/minor version from both naming formats */
|
||||
function parseOpusVersion(model: string): { major: number; minor: number } | null {
|
||||
const nameFirst = model.match(/claude-opus[-.]?(\d+)(?:[-.](\d+))?/);
|
||||
const nameFirst = model.match(/claude-opus[-.]?(\d+)(?:[-.](\d{1,2})(?!\d))?/);
|
||||
if (nameFirst) {
|
||||
return {
|
||||
major: parseInt(nameFirst[1], 10),
|
||||
minor: nameFirst[2] != null ? parseInt(nameFirst[2], 10) : 0,
|
||||
};
|
||||
}
|
||||
const numFirst = model.match(/claude-(\d+)(?:[-.](\d+))?-opus/);
|
||||
const numFirst = model.match(/claude-(\d+)(?:[-.](\d{1,2})(?!\d))?-opus/);
|
||||
if (numFirst) {
|
||||
return {
|
||||
major: parseInt(numFirst[1], 10),
|
||||
|
|
@ -136,6 +136,14 @@ export function omitsThinkingByDefault(model: string): boolean {
|
|||
return false;
|
||||
}
|
||||
|
||||
export function omitsSamplingParameters(model: string): boolean {
|
||||
const opus = parseOpusVersion(model);
|
||||
if (opus && (opus.major > 4 || (opus.major === 4 && opus.minor >= 7))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Checks if a model has a 1M context window (Sonnet 4.6+, Opus 4.6+, Opus 5+) */
|
||||
export function supportsContext1m(model: string): boolean {
|
||||
const sonnet = parseSonnetVersion(model);
|
||||
|
|
@ -177,11 +185,12 @@ function getBedrockAnthropicBetaHeaders(model: string): string[] {
|
|||
}
|
||||
|
||||
function mergeBedrockAnthropicBetaHeaders(existing: unknown, generated: string[]): string[] {
|
||||
const existingValues: unknown[] = Array.isArray(existing)
|
||||
? existing
|
||||
: typeof existing === 'string'
|
||||
? [existing]
|
||||
: [];
|
||||
let existingValues: unknown[] = [];
|
||||
if (Array.isArray(existing)) {
|
||||
existingValues = existing;
|
||||
} else if (typeof existing === 'string') {
|
||||
existingValues = [existing];
|
||||
}
|
||||
|
||||
const betaHeaders = new Set<string>();
|
||||
|
||||
|
|
@ -308,6 +317,8 @@ export const bedrockInputParser = s.tConversationSchema
|
|||
|
||||
const additionalFields: Record<string, unknown> = {};
|
||||
const typedData = data as Record<string, unknown>;
|
||||
const shouldOmitSamplingParameters =
|
||||
typeof typedData.model === 'string' && omitsSamplingParameters(typedData.model);
|
||||
|
||||
Object.entries(typedData).forEach(([key, value]) => {
|
||||
if (!knownKeys.includes(key)) {
|
||||
|
|
@ -436,6 +447,24 @@ export const bedrockInputParser = s.tConversationSchema
|
|||
delete amrf.reasoning_config;
|
||||
delete amrf.reasoning_effort;
|
||||
}
|
||||
|
||||
if (shouldOmitSamplingParameters) {
|
||||
delete amrf.temperature;
|
||||
delete amrf.topP;
|
||||
delete amrf.top_p;
|
||||
delete amrf.topK;
|
||||
delete amrf.top_k;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldOmitSamplingParameters) {
|
||||
delete typedData.temperature;
|
||||
delete typedData.topP;
|
||||
delete additionalFields.temperature;
|
||||
delete additionalFields.topP;
|
||||
delete additionalFields.top_p;
|
||||
delete additionalFields.topK;
|
||||
delete additionalFields.top_k;
|
||||
}
|
||||
|
||||
/** Default promptCache for claude and nova models, if not defined */
|
||||
|
|
|
|||
|
|
@ -1542,6 +1542,7 @@ const sharedOpenAIModels = [
|
|||
];
|
||||
|
||||
const sharedAnthropicModels = [
|
||||
'claude-opus-4-8',
|
||||
'claude-opus-4-7',
|
||||
'claude-sonnet-4-6',
|
||||
'claude-opus-4-6',
|
||||
|
|
@ -1565,6 +1566,7 @@ const sharedAnthropicModels = [
|
|||
];
|
||||
|
||||
export const bedrockModels = [
|
||||
'anthropic.claude-opus-4-8',
|
||||
'anthropic.claude-opus-4-7',
|
||||
'anthropic.claude-sonnet-4-6',
|
||||
'anthropic.claude-opus-4-6-v1',
|
||||
|
|
|
|||
|
|
@ -2337,6 +2337,46 @@ describe('Claude Model Tests', () => {
|
|||
cacheTokenValues['claude-opus-4-7'].read,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return correct prompt and completion rates for Claude Opus 4.8', () => {
|
||||
expect(getMultiplier({ model: 'claude-opus-4-8', tokenType: 'prompt' })).toBe(
|
||||
tokenValues['claude-opus-4-8'].prompt,
|
||||
);
|
||||
expect(getMultiplier({ model: 'claude-opus-4-8', tokenType: 'completion' })).toBe(
|
||||
tokenValues['claude-opus-4-8'].completion,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle Claude Opus 4.8 model name variations', () => {
|
||||
const modelVariations = [
|
||||
'claude-opus-4-8',
|
||||
'claude-opus-4-8-20260528',
|
||||
'claude-opus-4-8-latest',
|
||||
'anthropic/claude-opus-4-8',
|
||||
'claude-opus-4-8/anthropic',
|
||||
'claude-opus-4-8-preview',
|
||||
];
|
||||
|
||||
modelVariations.forEach((model) => {
|
||||
const valueKey = getValueKey(model);
|
||||
expect(valueKey).toBe('claude-opus-4-8');
|
||||
expect(getMultiplier({ model, tokenType: 'prompt' })).toBe(
|
||||
tokenValues['claude-opus-4-8'].prompt,
|
||||
);
|
||||
expect(getMultiplier({ model, tokenType: 'completion' })).toBe(
|
||||
tokenValues['claude-opus-4-8'].completion,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return correct cache rates for Claude Opus 4.8', () => {
|
||||
expect(getCacheMultiplier({ model: 'claude-opus-4-8', cacheType: 'write' })).toBe(
|
||||
cacheTokenValues['claude-opus-4-8'].write,
|
||||
);
|
||||
expect(getCacheMultiplier({ model: 'claude-opus-4-8', cacheType: 'read' })).toBe(
|
||||
cacheTokenValues['claude-opus-4-8'].read,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Premium Token Pricing', () => {
|
||||
|
|
@ -2357,7 +2397,12 @@ describe('Premium Token Pricing', () => {
|
|||
});
|
||||
|
||||
it('should not apply premium pricing to Claude 1M GA models', () => {
|
||||
const claudeModels = ['claude-opus-4-6', 'claude-opus-4-7', 'claude-sonnet-4-6'];
|
||||
const claudeModels = [
|
||||
'claude-opus-4-6',
|
||||
'claude-opus-4-7',
|
||||
'claude-opus-4-8',
|
||||
'claude-sonnet-4-6',
|
||||
];
|
||||
claudeModels.forEach((model) => {
|
||||
expect(premiumTokenValues[model]).toBeUndefined();
|
||||
expect(getPremiumRate(model, 'prompt', wellAboveThreshold)).toBeNull();
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@ export const tokenValues: Record<string, { prompt: number; completion: number }>
|
|||
'claude-opus-4-5': { prompt: 5, completion: 25 },
|
||||
'claude-opus-4-6': { prompt: 5, completion: 25 },
|
||||
'claude-opus-4-7': { prompt: 5, completion: 25 },
|
||||
'claude-opus-4-8': { prompt: 5, completion: 25 },
|
||||
'claude-sonnet-4': { prompt: 3, completion: 15 },
|
||||
'claude-sonnet-4-5': { prompt: 3, completion: 15 },
|
||||
'claude-sonnet-4-6': { prompt: 3, completion: 15 },
|
||||
|
|
@ -294,6 +295,7 @@ export const cacheTokenValues: Record<string, { write: number; read: number }> =
|
|||
'claude-opus-4-5': { write: 6.25, read: 0.5 },
|
||||
'claude-opus-4-6': { write: 6.25, read: 0.5 },
|
||||
'claude-opus-4-7': { write: 6.25, read: 0.5 },
|
||||
'claude-opus-4-8': { write: 6.25, read: 0.5 },
|
||||
'gpt-4o': { write: 2.5, read: 1.25 },
|
||||
'gpt-4o-mini': { write: 0.15, read: 0.075 },
|
||||
'gpt-4.1': { write: 2, read: 0.5 },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue