feat: add Mantle override for AWS Bedrock Anthropic endpoint

Introduce `model_capabilities.mantle` flag that installs a provider-specific
override in fast-agent's `ModelDatabase._PROVIDER_MODEL_OVERRIDES` to strip
features the AWS Bedrock Mantle endpoint rejects (beta headers, extended
thinking, task budgets, web tools, prompt caching).

Without this override, fast-agent sends default beta headers and `thinking`
parameters for modern Claude models that Mantle rejects with a misleading
404 "model does not exist" error.
This commit is contained in:
2026-05-12 07:41:41 -04:00
parent 4b954ed842
commit fe94f6a9a8
3 changed files with 482 additions and 17 deletions

View File

@@ -123,33 +123,85 @@ def _preflight_mcp_servers(agent_name: str, servers: dict[str, dict]) -> None:
# ── Model registration ────────────────────────────────────────────────────────
def _register_one_model(model_spec: str, capabilities: dict) -> None:
"""Register a single model with fast-agent's ModelDatabase if unknown."""
"""Register a single model with fast-agent's ModelDatabase.
Two cases:
1. **Unknown model** — if fast-agent has no built-in entry for this model,
register a minimal ``ModelParameters`` with the declared capabilities.
2. **Mantle-hosted model** (``capabilities.mantle: true``) — regardless of
whether the model has a built-in entry, install a provider-specific
override for ``(Provider.ANTHROPIC, model_name)`` in
``_PROVIDER_MODEL_OVERRIDES`` that strips the features the AWS Bedrock
Mantle endpoint rejects:
- ``anthropic_required_betas`` (no ``anthropic-beta`` header)
- ``reasoning`` / ``reasoning_effort_spec`` (no extended-thinking request)
- ``anthropic_task_budget_supported``
- ``anthropic_web_fetch_version`` / ``anthropic_web_search_version``
- ``cache_ttl`` (prompt caching is not advertised as supported on
Mantle for every model; disable the cache planner by default)
Without this override fast-agent sends beta headers and ``thinking``
parameters that Mantle rejects with a misleading ``"model does not
exist"`` 404.
"""
from fast_agent.llm.model_database import ModelDatabase, ModelParameters
from fast_agent.llm.provider_types import Provider
model_name = model_spec.split(".", 1)[-1] if "." in model_spec else model_spec
if ModelDatabase.get_model_params(model_name) is not None:
return
is_vision = capabilities.get("vision", False)
context_window = capabilities.get("context_window", 131072)
max_output_tokens = capabilities.get("max_output_tokens", 16384)
is_mantle = capabilities.get("mantle", False)
if is_vision:
tokenizes = list(ModelDatabase.QWEN_MULTIMODAL)
logger.info("Registered model '%s' with vision capabilities", model_name)
existing = ModelDatabase.get_model_params(model_name)
if existing is None:
# Unknown model — register a fresh runtime entry.
if is_vision:
tokenizes = list(ModelDatabase.QWEN_MULTIMODAL)
logger.info("Registered model '%s' with vision capabilities", model_name)
else:
tokenizes = list(ModelDatabase.TEXT_ONLY)
logger.info("Registered model '%s' as text-only", model_name)
ModelDatabase.register_runtime_model_params(
model_name,
ModelParameters(
context_window=context_window,
max_output_tokens=max_output_tokens,
tokenizes=tokenizes,
),
)
base_params = ModelDatabase.get_model_params(model_name)
else:
tokenizes = list(ModelDatabase.TEXT_ONLY)
logger.info("Registered model '%s' as text-only", model_name)
base_params = existing
if is_mantle and base_params is not None:
# Clone the base params and strip Mantle-incompatible features.
override = base_params.model_copy(
update={
"context_window": context_window,
"max_output_tokens": max_output_tokens,
"anthropic_required_betas": None,
"reasoning": None,
"reasoning_effort_spec": None,
"anthropic_task_budget_supported": False,
"anthropic_web_fetch_version": None,
"anthropic_web_search_version": None,
"cache_ttl": None,
}
)
normalized = ModelDatabase.normalize_model_name(model_name)
ModelDatabase._PROVIDER_MODEL_OVERRIDES[(Provider.ANTHROPIC, normalized)] = override
logger.info(
"Registered Mantle override for anthropic/'%s' (strips beta headers, thinking, web tools, caching)",
model_name,
)
ModelDatabase.register_runtime_model_params(
model_name,
ModelParameters(
context_window=context_window,
max_output_tokens=max_output_tokens,
tokenizes=tokenizes,
),
)
def _register_unknown_models(deployment_config: dict) -> None: