mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-03 22:32:42 +00:00
📖 feat: Add Claude Fable 5 Support (#13628)
* 📖 feat: Add Claude Fable 5 Support Claude Fable 5 (`claude-fable-5`) is Anthropic's most capable widely released model (GA 2026-06-09). Its naming drops the opus/sonnet/haiku tier, so LibreChat's name-parsing helpers miss it; this teaches them the Mythos-class family (Fable / Mythos) and registers the model. - Add `parseMythosClassVersion` and route Fable/Mythos through `supportsAdaptiveThinking`, `omitsThinkingByDefault`, `omitsSamplingParameters`, and `supportsContext1m` - Extend the Bedrock detection regexes (beta headers + adaptive-thinking branch) and `checkPromptCacheSupport` to match `claude-(fable|mythos)` - Return 128K max output for Fable/Mythos in `maxOutputTokens.reset`/`set` - Register `claude-fable-5` in shared Anthropic + Bedrock model lists, 1M context / 128K output token maps, and $10/$50 pricing with 12.5/1 cache rates (`claude-mythos-5` added to token + pricing maps only, since it is limited-availability) - Update `.env.example` and the Vertex `librechat.example.yaml` examples - Add parallel tests across tokens, Anthropic llm config, the Bedrock parser, and tx pricing * 🧹 refactor: Centralize Mythos-class detection; address review feedback - Add `isMythosClassModel` + `MYTHOS_CLASS_FAMILIES` in schemas.ts as the single source of truth for the Fable/Mythos family; route every gate (adaptive thinking, omit-thinking, omit-sampling, 1M context, prompt cache, 128K max-output reset/set) through it. A future sibling class is now a one-line edit. - [Codex P2] Exclude Mythos-class from getBedrockAnthropicBetaHeaders: Fable/ Mythos ship 128K output + fine-grained tool streaming by default, and the legacy output-128k-2025-02-19 beta is 3.7-Sonnet-only on Bedrock and risks request rejection. They still get adaptive thinking + effort. - [Copilot] Add Mythos 5 test parity (name variations, cache rates, pinned $10/$50) in tx.spec; add Mythos context/max-output/name-match in tokens.spec; fix the stale claude-3-7-sonnet-only comment in bedrock.ts. - Add isMythosClassModel unit tests covering all declared families. * 📝 docs: Clarify Mythos-class Bedrock requirements; correct beta-omit rationale Verified live against Bedrock (acct 951834775723, us-west-2): - anthropic.claude-fable-5 IS a real Bedrock catalog model, INFERENCE_PROFILE-only exactly like the existing anthropic.claude-opus-4-7/4-8 and claude-sonnet-4-6 default entries (refutes the "invalid model id" review claim). - Mythos-class also requires opting into Anthropic data sharing (Bedrock Data Retention API) before invocation. Changes: - .env.example: note that Mythos-class (Fable/Mythos) is inference-profile-only on Bedrock and needs the data-sharing opt-in. - bedrock.ts: reword the beta-omit comment to the verified rationale — output-128k / fine-grained-tool-streaming are built-in/no-op for the 4.7+ generation, so omitting them is lossless (dropped the unverified "Bedrock may reject" wording). * 🔄 refactor: Reorganize imports in schemas.ts and tx.spec.ts - Moved `TFeedback` and `Tools` imports to the top of `schemas.ts` for better readability. - Adjusted import order in `tx.spec.ts` to maintain consistency and improve clarity.
This commit is contained in:
parent
8fc2314208
commit
2aea5f4a3a
12 changed files with 349 additions and 12 deletions
10
.env.example
10
.env.example
|
|
@ -209,7 +209,7 @@ PROXY=
|
|||
#============#
|
||||
|
||||
ANTHROPIC_API_KEY=user_provided
|
||||
# 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_MODELS=claude-fable-5,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
|
||||
|
|
@ -275,8 +275,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-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
|
||||
# BEDROCK_AWS_MODELS=anthropic.claude-fable-5,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-fable-5,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
|
||||
|
||||
|
|
@ -287,6 +287,10 @@ ANTHROPIC_API_KEY=user_provided
|
|||
# The following models are not support due to not supporting conversation history:
|
||||
# ai21.j2-ultra-v1, cohere.command-text-v14, cohere.command-light-text-v14
|
||||
|
||||
# Claude Mythos-class models (anthropic.claude-fable-5, anthropic.claude-mythos-5) are inference-profile
|
||||
# only on Bedrock — use a profile ID (e.g. us.anthropic.claude-fable-5) — and require opting into Anthropic
|
||||
# data sharing via the Bedrock Data Retention API/console before they can be invoked.
|
||||
|
||||
#============#
|
||||
# Google #
|
||||
#============#
|
||||
|
|
|
|||
|
|
@ -1521,6 +1521,68 @@ describe('Claude Model Tests', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should return correct context length for Claude Fable 5 (1M)', () => {
|
||||
expect(getModelMaxTokens('claude-fable-5', EModelEndpoint.anthropic)).toBe(
|
||||
maxTokensMap[EModelEndpoint.anthropic]['claude-fable-5'],
|
||||
);
|
||||
expect(getModelMaxTokens('claude-fable-5')).toBe(
|
||||
maxTokensMap[EModelEndpoint.anthropic]['claude-fable-5'],
|
||||
);
|
||||
});
|
||||
|
||||
it('should return correct max output tokens for Claude Fable 5 (128K)', () => {
|
||||
const { getModelMaxOutputTokens } = require('@librechat/api');
|
||||
expect(getModelMaxOutputTokens('claude-fable-5', EModelEndpoint.anthropic)).toBe(
|
||||
maxOutputTokensMap[EModelEndpoint.anthropic]['claude-fable-5'],
|
||||
);
|
||||
});
|
||||
|
||||
it('should match model names correctly for Claude Fable 5', () => {
|
||||
const modelVariations = [
|
||||
'claude-fable-5',
|
||||
'claude-fable-5-20260609',
|
||||
'claude-fable-5-latest',
|
||||
'anthropic/claude-fable-5',
|
||||
'claude-fable-5/anthropic',
|
||||
'anthropic.claude-fable-5',
|
||||
];
|
||||
|
||||
modelVariations.forEach((model) => {
|
||||
expect(matchModelName(model, EModelEndpoint.anthropic)).toBe('claude-fable-5');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return correct context length for Claude Mythos 5 (1M)', () => {
|
||||
expect(getModelMaxTokens('claude-mythos-5', EModelEndpoint.anthropic)).toBe(
|
||||
maxTokensMap[EModelEndpoint.anthropic]['claude-mythos-5'],
|
||||
);
|
||||
expect(getModelMaxTokens('claude-mythos-5')).toBe(
|
||||
maxTokensMap[EModelEndpoint.anthropic]['claude-mythos-5'],
|
||||
);
|
||||
});
|
||||
|
||||
it('should return correct max output tokens for Claude Mythos 5 (128K)', () => {
|
||||
const { getModelMaxOutputTokens } = require('@librechat/api');
|
||||
expect(getModelMaxOutputTokens('claude-mythos-5', EModelEndpoint.anthropic)).toBe(
|
||||
maxOutputTokensMap[EModelEndpoint.anthropic]['claude-mythos-5'],
|
||||
);
|
||||
});
|
||||
|
||||
it('should match model names correctly for Claude Mythos 5', () => {
|
||||
const modelVariations = [
|
||||
'claude-mythos-5',
|
||||
'claude-mythos-5-20260609',
|
||||
'claude-mythos-5-latest',
|
||||
'anthropic/claude-mythos-5',
|
||||
'claude-mythos-5/anthropic',
|
||||
'anthropic.claude-mythos-5',
|
||||
];
|
||||
|
||||
modelVariations.forEach((model) => {
|
||||
expect(matchModelName(model, EModelEndpoint.anthropic)).toBe('claude-mythos-5');
|
||||
});
|
||||
});
|
||||
|
||||
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'],
|
||||
|
|
|
|||
|
|
@ -420,6 +420,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-fable-5"
|
||||
# # - "claude-opus-4-8"
|
||||
# # - "claude-sonnet-4-6"
|
||||
# # - "claude-3-7-sonnet-20250219"
|
||||
|
|
@ -431,6 +432,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-fable-5:
|
||||
# deploymentName: claude-fable-5
|
||||
# claude-opus-4.8:
|
||||
# deploymentName: claude-opus-4-8
|
||||
# claude-opus-4.5:
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
ThinkingDisplay,
|
||||
AnthropicEffort,
|
||||
anthropicSettings,
|
||||
isMythosClassModel,
|
||||
resolveThinkingDisplay,
|
||||
supportsAdaptiveThinking,
|
||||
} from 'librechat-data-provider';
|
||||
|
|
@ -49,7 +50,8 @@ function checkPromptCacheSupport(modelName: string): boolean {
|
|||
/claude-3-(?:sonnet|haiku|opus)?/.test(modelMatch) ||
|
||||
/claude-(?:sonnet|opus|haiku)-[4-9]/.test(modelMatch) ||
|
||||
/claude-[4-9]-(?:sonnet|opus|haiku)?/.test(modelMatch) ||
|
||||
/claude-4(?:-(?:sonnet|opus|haiku))?/.test(modelMatch)
|
||||
/claude-4(?:-(?:sonnet|opus|haiku))?/.test(modelMatch) ||
|
||||
isMythosClassModel(modelMatch)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1082,6 +1082,42 @@ describe('getLLMConfig', () => {
|
|||
expect(result.llmConfig).not.toHaveProperty('topK');
|
||||
});
|
||||
|
||||
it('should set adaptive thinking with summarized display for Fable 5', () => {
|
||||
const result = getLLMConfig('test-key', {
|
||||
modelOptions: { model: 'claude-fable-5', thinking: true },
|
||||
});
|
||||
|
||||
const thinking = result.llmConfig.thinking as unknown as {
|
||||
type: string;
|
||||
display?: string;
|
||||
};
|
||||
expect(thinking.type).toBe('adaptive');
|
||||
expect(thinking.display).toBe('summarized');
|
||||
});
|
||||
|
||||
it('should default Fable 5 max output tokens to 128K', () => {
|
||||
const result = getLLMConfig('test-key', {
|
||||
modelOptions: { model: 'claude-fable-5', thinking: true },
|
||||
});
|
||||
expect(result.llmConfig.maxTokens).toBe(128000);
|
||||
});
|
||||
|
||||
it('should omit sampling parameters for Fable 5', () => {
|
||||
const result = getLLMConfig('test-key', {
|
||||
modelOptions: {
|
||||
model: 'claude-fable-5',
|
||||
thinking: true,
|
||||
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'];
|
||||
|
||||
|
|
|
|||
|
|
@ -156,6 +156,8 @@ const anthropicModels = {
|
|||
'claude-opus-4-6': 1000000,
|
||||
'claude-opus-4-7': 1000000,
|
||||
'claude-opus-4-8': 1000000,
|
||||
'claude-fable-5': 1000000,
|
||||
'claude-mythos-5': 1000000,
|
||||
};
|
||||
|
||||
const deepseekModels = {
|
||||
|
|
@ -405,6 +407,8 @@ const anthropicMaxOutputs = {
|
|||
'claude-opus-4-6': 128000,
|
||||
'claude-opus-4-7': 128000,
|
||||
'claude-opus-4-8': 128000,
|
||||
'claude-fable-5': 128000,
|
||||
'claude-mythos-5': 128000,
|
||||
'claude-3.5-sonnet': 8192,
|
||||
'claude-3-5-sonnet': 8192,
|
||||
'claude-3.7-sonnet': 128000,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ThinkingDisplay } from '../src/schemas';
|
||||
import { ThinkingDisplay, isMythosClassModel, MYTHOS_CLASS_FAMILIES } from '../src/schemas';
|
||||
import {
|
||||
BEDROCK_OUTPUT_128K_BETA,
|
||||
supportsAdaptiveThinking,
|
||||
|
|
@ -14,6 +14,23 @@ import {
|
|||
|
||||
const BEDROCK_CLAUDE_4_BETAS = [BEDROCK_OUTPUT_128K_BETA, BEDROCK_FINE_GRAINED_TOOL_STREAMING_BETA];
|
||||
|
||||
describe('isMythosClassModel (single source of truth for Fable/Mythos)', () => {
|
||||
test('matches every declared family across naming variants', () => {
|
||||
MYTHOS_CLASS_FAMILIES.forEach((family) => {
|
||||
expect(isMythosClassModel(`claude-${family}-5`)).toBe(true);
|
||||
expect(isMythosClassModel(`anthropic.claude-${family}-5`)).toBe(true);
|
||||
expect(isMythosClassModel(`us.anthropic.claude-${family}-5`)).toBe(true);
|
||||
expect(isMythosClassModel(`claude-${family}-5-20260609`)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('does not match opus/sonnet/haiku or unrelated models', () => {
|
||||
['claude-opus-4-8', 'claude-sonnet-4-6', 'claude-haiku-4-5', 'gpt-4o', ''].forEach((model) => {
|
||||
expect(isMythosClassModel(model)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('supportsAdaptiveThinking', () => {
|
||||
test('should return true for claude-opus-4-6', () => {
|
||||
expect(supportsAdaptiveThinking('claude-opus-4-6')).toBe(true);
|
||||
|
|
@ -43,6 +60,13 @@ describe('supportsAdaptiveThinking', () => {
|
|||
expect(supportsAdaptiveThinking('claude-sonnet-6')).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true for Mythos-class models (Fable / Mythos)', () => {
|
||||
expect(supportsAdaptiveThinking('claude-fable-5')).toBe(true);
|
||||
expect(supportsAdaptiveThinking('claude-mythos-5')).toBe(true);
|
||||
expect(supportsAdaptiveThinking('anthropic.claude-fable-5')).toBe(true);
|
||||
expect(supportsAdaptiveThinking('us.anthropic.claude-fable-5')).toBe(true);
|
||||
});
|
||||
|
||||
test('should return false for claude-opus-4-5', () => {
|
||||
expect(supportsAdaptiveThinking('claude-opus-4-5')).toBe(false);
|
||||
});
|
||||
|
|
@ -161,6 +185,12 @@ describe('supportsContext1m', () => {
|
|||
expect(supportsContext1m('claude-opus-5')).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true for Mythos-class models (Fable / Mythos)', () => {
|
||||
expect(supportsContext1m('claude-fable-5')).toBe(true);
|
||||
expect(supportsContext1m('claude-mythos-5')).toBe(true);
|
||||
expect(supportsContext1m('anthropic.claude-fable-5')).toBe(true);
|
||||
});
|
||||
|
||||
test('should return false for claude-opus-4-5', () => {
|
||||
expect(supportsContext1m('claude-opus-4-5')).toBe(false);
|
||||
});
|
||||
|
|
@ -235,6 +265,12 @@ describe('omitsThinkingByDefault', () => {
|
|||
expect(omitsThinkingByDefault('claude-opus-9')).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true for Mythos-class models (Fable / Mythos)', () => {
|
||||
expect(omitsThinkingByDefault('claude-fable-5')).toBe(true);
|
||||
expect(omitsThinkingByDefault('claude-mythos-5')).toBe(true);
|
||||
expect(omitsThinkingByDefault('anthropic.claude-fable-5')).toBe(true);
|
||||
});
|
||||
|
||||
test('returns false for claude-opus-4-6 (adaptive but pre-4.7)', () => {
|
||||
expect(omitsThinkingByDefault('claude-opus-4-6')).toBe(false);
|
||||
});
|
||||
|
|
@ -285,6 +321,19 @@ describe('omitsSamplingParameters', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('returns true for Mythos-class models (Fable / Mythos)', () => {
|
||||
const models = [
|
||||
'claude-fable-5',
|
||||
'claude-mythos-5',
|
||||
'anthropic.claude-fable-5',
|
||||
'us.anthropic.claude-fable-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',
|
||||
|
|
@ -618,6 +667,34 @@ describe('bedrockInputParser', () => {
|
|||
expect(additionalFields.top_k).toBe(40);
|
||||
});
|
||||
|
||||
test('should set adaptive thinking and strip sampling params for Fable 5 Bedrock models', () => {
|
||||
const input = {
|
||||
model: 'anthropic.claude-fable-5',
|
||||
effort: 'high',
|
||||
temperature: 0.7,
|
||||
topP: 0.9,
|
||||
topK: 40,
|
||||
top_p: 0.8,
|
||||
additionalModelRequestFields: {
|
||||
custom_flag: true,
|
||||
temperature: 0.5,
|
||||
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.top_p).toBeUndefined();
|
||||
expect(additionalFields.top_k).toBeUndefined();
|
||||
expect(additionalFields.custom_flag).toBe(true);
|
||||
expect(additionalFields.thinking).toEqual({ type: 'adaptive', display: 'summarized' });
|
||||
expect(additionalFields.output_config).toEqual({ effort: 'high' });
|
||||
/** Mythos-class models do not receive the legacy output-128k / fine-grained-tool-streaming betas. */
|
||||
expect(additionalFields.anthropic_beta).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should set thinking.display to "summarized" so Opus 4.7 returns reasoning blocks', () => {
|
||||
const input = {
|
||||
model: 'anthropic.claude-opus-4-7',
|
||||
|
|
|
|||
|
|
@ -105,7 +105,13 @@ function parseSonnetVersion(model: string): { major: number; minor: number } | n
|
|||
return null;
|
||||
}
|
||||
|
||||
/** Checks if a model supports adaptive thinking (Opus 4.6+, Sonnet 4.6+) */
|
||||
/**
|
||||
* Mythos-class detection (Claude Fable / Mythos) lives in `schemas.ts` as
|
||||
* `isMythosClassModel` — the single source of truth for the family names.
|
||||
* The helpers below OR it in alongside the `opus`/`sonnet` version parsers.
|
||||
*/
|
||||
|
||||
/** Checks if a model supports adaptive thinking (Opus 4.6+, Sonnet 4.6+, Fable/Mythos) */
|
||||
export function supportsAdaptiveThinking(model: string): boolean {
|
||||
const opus = parseOpusVersion(model);
|
||||
if (opus && (opus.major > 4 || (opus.major === 4 && opus.minor >= 6))) {
|
||||
|
|
@ -115,6 +121,9 @@ export function supportsAdaptiveThinking(model: string): boolean {
|
|||
if (sonnet != null && (sonnet.major > 4 || (sonnet.major === 4 && sonnet.minor >= 6))) {
|
||||
return true;
|
||||
}
|
||||
if (s.isMythosClassModel(model)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -133,6 +142,9 @@ export function omitsThinkingByDefault(model: string): boolean {
|
|||
if (opus && (opus.major > 4 || (opus.major === 4 && opus.minor >= 7))) {
|
||||
return true;
|
||||
}
|
||||
if (s.isMythosClassModel(model)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -141,10 +153,13 @@ export function omitsSamplingParameters(model: string): boolean {
|
|||
if (opus && (opus.major > 4 || (opus.major === 4 && opus.minor >= 7))) {
|
||||
return true;
|
||||
}
|
||||
if (s.isMythosClassModel(model)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Checks if a model has a 1M context window (Sonnet 4.6+, Opus 4.6+, Opus 5+) */
|
||||
/** Checks if a model has a 1M context window (Sonnet 4.6+, Opus 4.6+, Opus 5+, Fable/Mythos) */
|
||||
export function supportsContext1m(model: string): boolean {
|
||||
const sonnet = parseSonnetVersion(model);
|
||||
if (sonnet != null && (sonnet.major > 4 || (sonnet.major === 4 && sonnet.minor >= 6))) {
|
||||
|
|
@ -154,6 +169,9 @@ export function supportsContext1m(model: string): boolean {
|
|||
if (opus && (opus.major > 4 || (opus.major === 4 && opus.minor >= 6))) {
|
||||
return true;
|
||||
}
|
||||
if (s.isMythosClassModel(model)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -167,6 +185,8 @@ export function supportsContext1m(model: string): boolean {
|
|||
function getBedrockAnthropicBetaHeaders(model: string): string[] {
|
||||
const betaHeaders: string[] = [];
|
||||
|
||||
/** Mythos-class (Fable/Mythos) is intentionally not matched: these betas are built-in/no-op for the
|
||||
* 4.7+ generation (Fable has native 128K output), so omitting them on Bedrock is lossless. */
|
||||
const isClaude4PlusModel =
|
||||
/anthropic\.claude-(?:[4-9](?:\.\d+)?(?:-\d+)?-(?:sonnet|opus|haiku)|(?:sonnet|opus|haiku)-[4-9])/.test(
|
||||
model,
|
||||
|
|
@ -334,13 +354,14 @@ export const bedrockInputParser = s.tConversationSchema
|
|||
}
|
||||
});
|
||||
|
||||
/** Default thinking and thinkingBudget for 'anthropic.claude-3-7-sonnet' models, if not defined */
|
||||
/** Configure thinking for Bedrock Anthropic models: 3.7 Sonnet, Claude 4+ (opus/sonnet/haiku), and Mythos-class (Fable/Mythos). */
|
||||
if (
|
||||
typeof typedData.model === 'string' &&
|
||||
(typedData.model.includes('anthropic.claude-3-7-sonnet') ||
|
||||
/anthropic\.claude-(?:[4-9](?:\.\d+)?(?:-\d+)?-(?:sonnet|opus|haiku)|(?:sonnet|opus|haiku)-[4-9])/.test(
|
||||
typedData.model,
|
||||
))
|
||||
) ||
|
||||
s.isMythosClassModel(typedData.model))
|
||||
) {
|
||||
const isAdaptive = supportsAdaptiveThinking(typedData.model as string);
|
||||
|
||||
|
|
|
|||
|
|
@ -1615,6 +1615,7 @@ const sharedOpenAIModels = [
|
|||
];
|
||||
|
||||
const sharedAnthropicModels = [
|
||||
'claude-fable-5',
|
||||
'claude-opus-4-8',
|
||||
'claude-opus-4-7',
|
||||
'claude-sonnet-4-6',
|
||||
|
|
@ -1639,6 +1640,7 @@ const sharedAnthropicModels = [
|
|||
];
|
||||
|
||||
export const bedrockModels = [
|
||||
'anthropic.claude-fable-5',
|
||||
'anthropic.claude-opus-4-8',
|
||||
'anthropic.claude-opus-4-7',
|
||||
'anthropic.claude-sonnet-4-6',
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { z } from 'zod';
|
||||
import { Tools } from './types/assistants';
|
||||
import type { TMessageContentParts, FunctionTool, FunctionToolCall } from './types/assistants';
|
||||
import { TFeedback, feedbackSchema } from './feedback';
|
||||
import type { SearchResultData } from './types/web';
|
||||
import type { TFile } from './types/files';
|
||||
import { TFeedback, feedbackSchema } from './feedback';
|
||||
import { Tools } from './types/assistants';
|
||||
|
||||
export const isUUID = z.string().uuid();
|
||||
|
||||
|
|
@ -459,6 +459,27 @@ const CLAUDE_4_64K_MAX_OUTPUT = 64000 as const;
|
|||
const CLAUDE_32K_MAX_OUTPUT = 32000 as const;
|
||||
const DEFAULT_MAX_OUTPUT = 8192 as const;
|
||||
const LEGACY_ANTHROPIC_MAX_OUTPUT = 4096 as const;
|
||||
|
||||
/**
|
||||
* Claude "Mythos-class" model families — new top-level classes (peers of
|
||||
* `opus`/`sonnet`/`haiku`) that ship with the post-Opus-4.7 modern profile:
|
||||
* adaptive thinking always on, raw thinking omitted by default (summarized
|
||||
* opt-in), sampling parameters rejected, and a 1M context window. The tier
|
||||
* word is the class name itself, so the `opus`/`sonnet` version parsers don't
|
||||
* cover them.
|
||||
*
|
||||
* Single source of truth: add a future sibling class name here and every
|
||||
* Mythos-class gate (adaptive thinking, sampling omission, prompt caching, 1M
|
||||
* context, 128K output) picks it up.
|
||||
*/
|
||||
export const MYTHOS_CLASS_FAMILIES = ['fable', 'mythos'] as const;
|
||||
const MYTHOS_CLASS_PATTERN = new RegExp(`claude-(?:${MYTHOS_CLASS_FAMILIES.join('|')})[-.]?\\d`);
|
||||
|
||||
/** Whether the model is a Claude Mythos-class model (e.g. `claude-fable-5`). */
|
||||
export function isMythosClassModel(model: string): boolean {
|
||||
return MYTHOS_CLASS_PATTERN.test(model);
|
||||
}
|
||||
|
||||
export const anthropicSettings = {
|
||||
model: {
|
||||
default: 'claude-3-5-sonnet-latest' as const,
|
||||
|
|
@ -487,6 +508,10 @@ export const anthropicSettings = {
|
|||
step: 1 as const,
|
||||
default: DEFAULT_MAX_OUTPUT,
|
||||
reset: (modelName: string) => {
|
||||
if (isMythosClassModel(modelName)) {
|
||||
return ANTHROPIC_MAX_OUTPUT;
|
||||
}
|
||||
|
||||
if (/claude-opus[-.]?(?:4[-.]?(?:[6-9]|\d{2,})|[5-9]|\d{2,})/.test(modelName)) {
|
||||
return ANTHROPIC_MAX_OUTPUT;
|
||||
}
|
||||
|
|
@ -506,6 +531,13 @@ export const anthropicSettings = {
|
|||
return DEFAULT_MAX_OUTPUT;
|
||||
},
|
||||
set: (value: number, modelName: string) => {
|
||||
if (isMythosClassModel(modelName)) {
|
||||
if (value > ANTHROPIC_MAX_OUTPUT) {
|
||||
return ANTHROPIC_MAX_OUTPUT;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
if (/claude-opus[-.]?(?:4[-.]?(?:[6-9]|\d{2,})|[5-9]|\d{2,})/.test(modelName)) {
|
||||
if (value > ANTHROPIC_MAX_OUTPUT) {
|
||||
return ANTHROPIC_MAX_OUTPUT;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
/** Note: No hard-coded values should be used in this file. */
|
||||
import { matchModelName, findMatchingPattern } from './test-helpers';
|
||||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import {
|
||||
createTxMethods,
|
||||
|
|
@ -8,6 +7,7 @@ import {
|
|||
premiumTokenValues,
|
||||
defaultRate,
|
||||
} from './tx';
|
||||
import { matchModelName, findMatchingPattern } from './test-helpers';
|
||||
|
||||
const { getValueKey, getMultiplier, getPremiumRate, getCacheMultiplier } = createTxMethods(
|
||||
{} as typeof import('mongoose'),
|
||||
|
|
@ -2377,6 +2377,94 @@ describe('Claude Model Tests', () => {
|
|||
cacheTokenValues['claude-opus-4-8'].read,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return correct prompt and completion rates for Claude Fable 5', () => {
|
||||
expect(getMultiplier({ model: 'claude-fable-5', tokenType: 'prompt' })).toBe(
|
||||
tokenValues['claude-fable-5'].prompt,
|
||||
);
|
||||
expect(getMultiplier({ model: 'claude-fable-5', tokenType: 'completion' })).toBe(
|
||||
tokenValues['claude-fable-5'].completion,
|
||||
);
|
||||
});
|
||||
|
||||
it('should pin Claude Fable 5 pricing to $10 / $50 per MTok', () => {
|
||||
expect(tokenValues['claude-fable-5']).toEqual({ prompt: 10, completion: 50 });
|
||||
});
|
||||
|
||||
it('should handle Claude Fable 5 model name variations', () => {
|
||||
const modelVariations = [
|
||||
'claude-fable-5',
|
||||
'claude-fable-5-20260609',
|
||||
'claude-fable-5-latest',
|
||||
'anthropic/claude-fable-5',
|
||||
'claude-fable-5/anthropic',
|
||||
'anthropic.claude-fable-5',
|
||||
];
|
||||
|
||||
modelVariations.forEach((model) => {
|
||||
const valueKey = getValueKey(model);
|
||||
expect(valueKey).toBe('claude-fable-5');
|
||||
expect(getMultiplier({ model, tokenType: 'prompt' })).toBe(
|
||||
tokenValues['claude-fable-5'].prompt,
|
||||
);
|
||||
expect(getMultiplier({ model, tokenType: 'completion' })).toBe(
|
||||
tokenValues['claude-fable-5'].completion,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return correct cache rates for Claude Fable 5', () => {
|
||||
expect(getCacheMultiplier({ model: 'claude-fable-5', cacheType: 'write' })).toBe(
|
||||
cacheTokenValues['claude-fable-5'].write,
|
||||
);
|
||||
expect(getCacheMultiplier({ model: 'claude-fable-5', cacheType: 'read' })).toBe(
|
||||
cacheTokenValues['claude-fable-5'].read,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return correct prompt and completion rates for Claude Mythos 5', () => {
|
||||
expect(getMultiplier({ model: 'claude-mythos-5', tokenType: 'prompt' })).toBe(
|
||||
tokenValues['claude-mythos-5'].prompt,
|
||||
);
|
||||
expect(getMultiplier({ model: 'claude-mythos-5', tokenType: 'completion' })).toBe(
|
||||
tokenValues['claude-mythos-5'].completion,
|
||||
);
|
||||
});
|
||||
|
||||
it('should pin Claude Mythos 5 pricing to $10 / $50 per MTok', () => {
|
||||
expect(tokenValues['claude-mythos-5']).toEqual({ prompt: 10, completion: 50 });
|
||||
});
|
||||
|
||||
it('should handle Claude Mythos 5 model name variations', () => {
|
||||
const modelVariations = [
|
||||
'claude-mythos-5',
|
||||
'claude-mythos-5-20260609',
|
||||
'claude-mythos-5-latest',
|
||||
'anthropic/claude-mythos-5',
|
||||
'claude-mythos-5/anthropic',
|
||||
'anthropic.claude-mythos-5',
|
||||
];
|
||||
|
||||
modelVariations.forEach((model) => {
|
||||
const valueKey = getValueKey(model);
|
||||
expect(valueKey).toBe('claude-mythos-5');
|
||||
expect(getMultiplier({ model, tokenType: 'prompt' })).toBe(
|
||||
tokenValues['claude-mythos-5'].prompt,
|
||||
);
|
||||
expect(getMultiplier({ model, tokenType: 'completion' })).toBe(
|
||||
tokenValues['claude-mythos-5'].completion,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return correct cache rates for Claude Mythos 5', () => {
|
||||
expect(getCacheMultiplier({ model: 'claude-mythos-5', cacheType: 'write' })).toBe(
|
||||
cacheTokenValues['claude-mythos-5'].write,
|
||||
);
|
||||
expect(getCacheMultiplier({ model: 'claude-mythos-5', cacheType: 'read' })).toBe(
|
||||
cacheTokenValues['claude-mythos-5'].read,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Premium Token Pricing', () => {
|
||||
|
|
@ -2401,6 +2489,8 @@ describe('Premium Token Pricing', () => {
|
|||
'claude-opus-4-6',
|
||||
'claude-opus-4-7',
|
||||
'claude-opus-4-8',
|
||||
'claude-fable-5',
|
||||
'claude-mythos-5',
|
||||
'claude-sonnet-4-6',
|
||||
];
|
||||
claudeModels.forEach((model) => {
|
||||
|
|
|
|||
|
|
@ -159,6 +159,8 @@ export const tokenValues: Record<string, { prompt: number; completion: number }>
|
|||
'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-fable-5': { prompt: 10, completion: 50 },
|
||||
'claude-mythos-5': { prompt: 10, completion: 50 },
|
||||
'claude-sonnet-4': { prompt: 3, completion: 15 },
|
||||
'claude-sonnet-4-5': { prompt: 3, completion: 15 },
|
||||
'claude-sonnet-4-6': { prompt: 3, completion: 15 },
|
||||
|
|
@ -296,6 +298,8 @@ export const cacheTokenValues: Record<string, { write: number; read: number }> =
|
|||
'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 },
|
||||
'claude-fable-5': { write: 12.5, read: 1 },
|
||||
'claude-mythos-5': { write: 12.5, read: 1 },
|
||||
'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