mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 19:23:42 +08:00
feat(llm): add Claude Fable 5 support
This commit is contained in:
parent
c1fd39a1e2
commit
7478491652
@ -137,11 +137,27 @@ public class AnthropicChatModelBuilder implements ChatModelBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* True for any Claude 4.7+ model — the family that drops temperature /
|
||||
* top_p / top_k and exposes the "xhigh" thinking tier between high and max.
|
||||
* Detect the Claude Fable model line (e.g. {@code claude-fable-5}).
|
||||
* Fable is a reasoning-first family that follows the same strict API
|
||||
* contract as Claude 4.7+: temperature / top_p / top_k must be unset
|
||||
* (any non-default value returns HTTP 400) and the "xhigh" adaptive
|
||||
* thinking tier is available. Matching on the {@code claude-fable} token
|
||||
* covers the direct-API id, the OpenRouter-prefixed form
|
||||
* ({@code anthropic/claude-fable-5}), and future {@code claude-fable-N}
|
||||
* revisions without a per-version code change.
|
||||
*/
|
||||
static boolean isClaudeFable(String modelName) {
|
||||
if (modelName == null) return false;
|
||||
return modelName.toLowerCase().contains("claude-fable");
|
||||
}
|
||||
|
||||
/**
|
||||
* True for any modern Claude model that drops temperature / top_p / top_k
|
||||
* and exposes the "xhigh" thinking tier between high and max — the Claude
|
||||
* 4.7 / 4.8 generations and the Fable reasoning line.
|
||||
*/
|
||||
static boolean isClaude47OrLater(String modelName) {
|
||||
return isClaude47(modelName) || isClaude48(modelName);
|
||||
return isClaude47(modelName) || isClaude48(modelName) || isClaudeFable(modelName);
|
||||
}
|
||||
|
||||
AnthropicChatOptions buildAnthropicOptions(ModelConfigEntity runtimeModel) {
|
||||
|
||||
@ -86,6 +86,7 @@ public class ModelCapabilityService {
|
||||
|
||||
// ===== Anthropic Claude =====
|
||||
// Vision yes (image), native video no — Anthropic's API only accepts images.
|
||||
m.put("claude-fable", EnumSet.of(Modality.VISION));
|
||||
m.put("claude-4.7", EnumSet.of(Modality.VISION));
|
||||
m.put("claude-4.5", EnumSet.of(Modality.VISION));
|
||||
m.put("claude-4", EnumSet.of(Modality.VISION));
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
-- Add Claude Fable 5 model entries to mate_model_config. Unlike earlier Claude
|
||||
-- families, the Fable rows live ONLY here, not in the data-{en,zh}.sql seed:
|
||||
-- Flyway runs every version (V1..) on a fresh database, so the migration seeds
|
||||
-- new installs and upgrades existing deployments alike. The trade-off is that
|
||||
-- the description below is English-only (seed files carry localized copy).
|
||||
--
|
||||
-- Fable 5 is a reasoning-first model with a 1M-token context window and native
|
||||
-- vision input. It follows the same strict API contract as Claude 4.7+:
|
||||
-- temperature / top_p / top_k must be NULL (otherwise HTTP 400), and the
|
||||
-- "xhigh" adaptive thinking tier is available. Both are handled in
|
||||
-- AnthropicChatModelBuilder via the isClaudeFable() / isClaude47OrLater()
|
||||
-- detectors. Vision capability is resolved in ModelCapabilityService.
|
||||
--
|
||||
-- MERGE INTO is the H2 idempotent upsert; running this twice is a no-op.
|
||||
-- Same V number is used in mysql/ for cross-dialect parity.
|
||||
|
||||
MERGE INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
|
||||
KEY (id)
|
||||
VALUES
|
||||
-- Direct Anthropic
|
||||
(1000000300, 'Claude Fable 5', 'anthropic', 'claude-fable-5', 'Anthropic Claude Fable 5 (1M context, vision, xhigh adaptive thinking)', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
|
||||
-- OpenRouter passthrough
|
||||
(1000000301, 'Claude Fable 5', 'openrouter', 'anthropic/claude-fable-5', 'Claude Fable 5 via OpenRouter', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
|
||||
-- Claude Code OAuth (Pro/Max subscription)
|
||||
(1000000302, 'Claude Fable 5', 'anthropic-claude-code', 'claude-fable-5', 'Claude Fable 5 via Claude Code Pro/Max subscription', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0);
|
||||
@ -0,0 +1,37 @@
|
||||
-- Add Claude Fable 5 model entries to mate_model_config. Unlike earlier Claude
|
||||
-- families, the Fable rows live ONLY here, not in the data-mysql-{en,zh}.sql
|
||||
-- seed: Flyway runs every version (V1..) on a fresh database, so the migration
|
||||
-- seeds new installs and upgrades existing deployments alike. The trade-off is
|
||||
-- that the description below is English-only (seed files carry localized copy).
|
||||
--
|
||||
-- Fable 5 is a reasoning-first model with a 1M-token context window and native
|
||||
-- vision input. It follows the same strict API contract as Claude 4.7+:
|
||||
-- temperature / top_p / top_k must be NULL (otherwise HTTP 400), and the
|
||||
-- "xhigh" adaptive thinking tier is available. Both are handled in
|
||||
-- AnthropicChatModelBuilder via the isClaudeFable() / isClaude47OrLater()
|
||||
-- detectors. Vision capability is resolved in ModelCapabilityService.
|
||||
--
|
||||
-- INSERT ... ON DUPLICATE KEY UPDATE is the MySQL idempotent upsert.
|
||||
-- Same V number is used in h2/ for cross-dialect parity.
|
||||
|
||||
INSERT INTO mate_model_config (id, name, provider, model_name, description, temperature, max_tokens, top_p, builtin, enabled, is_default, create_time, update_time, deleted)
|
||||
VALUES
|
||||
-- Direct Anthropic
|
||||
(1000000300, 'Claude Fable 5', 'anthropic', 'claude-fable-5', 'Anthropic Claude Fable 5 (1M context, vision, xhigh adaptive thinking)', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
|
||||
-- OpenRouter passthrough
|
||||
(1000000301, 'Claude Fable 5', 'openrouter', 'anthropic/claude-fable-5', 'Claude Fable 5 via OpenRouter', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0),
|
||||
-- Claude Code OAuth (Pro/Max subscription)
|
||||
(1000000302, 'Claude Fable 5', 'anthropic-claude-code', 'claude-fable-5', 'Claude Fable 5 via Claude Code Pro/Max subscription', NULL, 4096, NULL, TRUE, TRUE, FALSE, NOW(), NOW(), 0)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
name = VALUES(name),
|
||||
provider = VALUES(provider),
|
||||
model_name = VALUES(model_name),
|
||||
description = VALUES(description),
|
||||
temperature = VALUES(temperature),
|
||||
max_tokens = VALUES(max_tokens),
|
||||
top_p = VALUES(top_p),
|
||||
builtin = VALUES(builtin),
|
||||
enabled = VALUES(enabled),
|
||||
is_default = VALUES(is_default),
|
||||
update_time = VALUES(update_time),
|
||||
deleted = VALUES(deleted);
|
||||
@ -0,0 +1,60 @@
|
||||
package vip.mate.llm.chatmodel;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* {@link AnthropicChatModelBuilder#isClaudeFable} must classify the Claude
|
||||
* Fable reasoning line as a modern model so it inherits the strict 4.7+ API
|
||||
* contract: temperature / top_p / top_k must be unset (any non-default value
|
||||
* returns HTTP 400) and the "xhigh" adaptive thinking tier is available.
|
||||
*
|
||||
* <p>{@link AnthropicChatModelBuilder#isClaude47OrLater} unifies the gating
|
||||
* logic across the 4.7 / 4.8 generations and the Fable family.</p>
|
||||
*/
|
||||
class AnthropicChatModelBuilderFableTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("isClaudeFable detects the direct-API and OpenRouter ids")
|
||||
void detect_directAndOpenRouter() {
|
||||
assertTrue(AnthropicChatModelBuilder.isClaudeFable("claude-fable-5"));
|
||||
assertTrue(AnthropicChatModelBuilder.isClaudeFable("anthropic/claude-fable-5"));
|
||||
// Case-insensitive
|
||||
assertTrue(AnthropicChatModelBuilder.isClaudeFable("Claude-Fable-5"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isClaudeFable tolerates date-stamped and future revisions")
|
||||
void detect_futureRevisions() {
|
||||
assertTrue(AnthropicChatModelBuilder.isClaudeFable("claude-fable-5-20260609"));
|
||||
assertTrue(AnthropicChatModelBuilder.isClaudeFable("claude-fable-6"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isClaudeFable ignores other Claude families and unrelated names")
|
||||
void detect_negatives() {
|
||||
assertFalse(AnthropicChatModelBuilder.isClaudeFable("claude-opus-4-8"));
|
||||
assertFalse(AnthropicChatModelBuilder.isClaudeFable("claude-sonnet-4-6"));
|
||||
// The "claude-fable" token guard prevents a stray "fable" elsewhere from matching.
|
||||
assertFalse(AnthropicChatModelBuilder.isClaudeFable("some-fable-model"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isClaudeFable null-safe")
|
||||
void detect_nullSafe() {
|
||||
assertFalse(AnthropicChatModelBuilder.isClaudeFable(null));
|
||||
assertFalse(AnthropicChatModelBuilder.isClaudeFable(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("isClaude47OrLater routes Fable onto the sampling-forbidden contract")
|
||||
void claude47OrLater_includesFable() {
|
||||
assertTrue(AnthropicChatModelBuilder.isClaude47OrLater("claude-fable-5"));
|
||||
assertTrue(AnthropicChatModelBuilder.isClaude47OrLater("anthropic/claude-fable-5"));
|
||||
// Sanity: existing generations still classify modern, legacy still falls through.
|
||||
assertTrue(AnthropicChatModelBuilder.isClaude47OrLater("claude-opus-4-8"));
|
||||
assertFalse(AnthropicChatModelBuilder.isClaude47OrLater("claude-opus-4-6"));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user