mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 11:33:14 +00:00
* fix: migrate stale admin model defaults Normalize retired model IDs before validation and in settings responses so full admin updates remain saveable after model upgrades. * fix: restrict retired model migration Only migrate explicitly retired model IDs so malformed provider model names and efforts continue to fail validation. (cherry picked from commit 62e0ca2d4898ebc3c2ae8887030a364779d907cb) Co-authored-by: Johannes du Plessis <johannes@langchain.dev>
This commit is contained in:
parent
7b9eff62e9
commit
22c517a54f
2 changed files with 149 additions and 2 deletions
|
|
@ -110,6 +110,39 @@ class TeamSettingsUpdate(BaseModel):
|
|||
|
||||
@model_validator(mode="after")
|
||||
def _validate_model_pairs(self) -> TeamSettingsUpdate:
|
||||
self.default_agent_model, self.default_agent_reasoning_effort = _normalize_stale_model_pair(
|
||||
self.default_agent_model,
|
||||
self.default_agent_reasoning_effort,
|
||||
)
|
||||
self.default_agent_subagent_model, self.default_agent_subagent_reasoning_effort = (
|
||||
_normalize_stale_model_pair(
|
||||
self.default_agent_subagent_model,
|
||||
self.default_agent_subagent_reasoning_effort,
|
||||
)
|
||||
)
|
||||
self.default_reviewer_model, self.default_reviewer_reasoning_effort = (
|
||||
_normalize_stale_model_pair(
|
||||
self.default_reviewer_model,
|
||||
self.default_reviewer_reasoning_effort,
|
||||
)
|
||||
)
|
||||
(
|
||||
self.default_reviewer_subagent_model,
|
||||
self.default_reviewer_subagent_reasoning_effort,
|
||||
) = _normalize_stale_model_pair(
|
||||
self.default_reviewer_subagent_model,
|
||||
self.default_reviewer_subagent_reasoning_effort,
|
||||
)
|
||||
self.default_grouping_model, self.default_grouping_reasoning_effort = (
|
||||
_normalize_stale_model_pair(
|
||||
self.default_grouping_model,
|
||||
self.default_grouping_reasoning_effort,
|
||||
)
|
||||
)
|
||||
self.default_chat_model, self.default_chat_reasoning_effort = _normalize_stale_model_pair(
|
||||
self.default_chat_model,
|
||||
self.default_chat_reasoning_effort,
|
||||
)
|
||||
_validate_model_effort_pair(
|
||||
self.default_agent_model, self.default_agent_reasoning_effort, "agent"
|
||||
)
|
||||
|
|
@ -168,6 +201,53 @@ def _validate_model_effort_pair(model: str | None, effort: str | None, role: str
|
|||
raise ValueError(f"effort {effort!r} not supported by {role} model {model!r}")
|
||||
|
||||
|
||||
# Model ids retired from SUPPORTED_MODELS mapped to their current successors,
|
||||
# so stored admin defaults stay saveable/readable across model upgrades. Direct
|
||||
# anthropic: ids moved to the Bedrock inference profile in the provider
|
||||
# migration (#62); OpenAI/Google were dropped entirely and take the global
|
||||
# default. anthropic:claude-fable-5 deliberately maps to Opus, not Bedrock
|
||||
# Fable: Fable is gated behind the provider-data-share opt-in and a stale
|
||||
# record must never resurface it.
|
||||
_RETIRED_MODEL_REPLACEMENTS: dict[str, str] = {
|
||||
"anthropic:claude-opus-4-7": "bedrock_converse:us.anthropic.claude-opus-4-8",
|
||||
"anthropic:claude-opus-4-8": "bedrock_converse:us.anthropic.claude-opus-4-8",
|
||||
"anthropic:claude-fable-5": "bedrock_converse:us.anthropic.claude-opus-4-8",
|
||||
"openai:gpt-5.5": "bedrock_converse:us.anthropic.claude-opus-4-8",
|
||||
"google_genai:gemini-3.5-flash": "bedrock_converse:us.anthropic.claude-opus-4-8",
|
||||
}
|
||||
|
||||
|
||||
def _normalize_stale_model_pair(
|
||||
model: str | None, effort: str | None
|
||||
) -> tuple[str | None, str | None]:
|
||||
if model is None:
|
||||
return model, effort
|
||||
return _RETIRED_MODEL_REPLACEMENTS.get(model, model), effort
|
||||
|
||||
|
||||
_MODEL_PAIR_FIELDS: tuple[tuple[str, str], ...] = (
|
||||
("default_agent_model", "default_agent_reasoning_effort"),
|
||||
("default_agent_subagent_model", "default_agent_subagent_reasoning_effort"),
|
||||
("default_reviewer_model", "default_reviewer_reasoning_effort"),
|
||||
("default_reviewer_subagent_model", "default_reviewer_subagent_reasoning_effort"),
|
||||
("default_grouping_model", "default_grouping_reasoning_effort"),
|
||||
("default_chat_model", "default_chat_reasoning_effort"),
|
||||
)
|
||||
|
||||
|
||||
def normalize_team_settings_for_response(settings: dict[str, Any]) -> dict[str, Any]:
|
||||
value = dict(settings)
|
||||
for model_field, effort_field in _MODEL_PAIR_FIELDS:
|
||||
model = value.get(model_field)
|
||||
effort = value.get(effort_field)
|
||||
if isinstance(model, str):
|
||||
value[model_field], value[effort_field] = _normalize_stale_model_pair(
|
||||
model,
|
||||
effort if isinstance(effort, str) else None,
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
def _client():
|
||||
return get_client()
|
||||
|
||||
|
|
@ -241,7 +321,7 @@ async def get_team_settings() -> dict[str, Any]:
|
|||
"review_author_context_enabled",
|
||||
):
|
||||
merged.pop(stale_field, None)
|
||||
return merged
|
||||
return normalize_team_settings_for_response(merged)
|
||||
|
||||
|
||||
async def upsert_team_settings(update: TeamSettingsUpdate) -> dict[str, Any]:
|
||||
|
|
|
|||
|
|
@ -11,12 +11,18 @@ from agent.dashboard.options import (
|
|||
gate_fable_model,
|
||||
provider_fallback_pair,
|
||||
)
|
||||
from agent.dashboard.team_settings import get_team_default_model
|
||||
from agent.dashboard.team_settings import (
|
||||
TeamSettingsUpdate,
|
||||
get_team_default_model,
|
||||
normalize_team_settings_for_response,
|
||||
)
|
||||
|
||||
STALE_ANTHROPIC = "bedrock_converse:us.anthropic.claude-opus-4-7"
|
||||
SUPPORTED_ANTHROPIC = "bedrock_converse:us.anthropic.claude-opus-4-8"
|
||||
STALE_SONNET = "bedrock_converse:us.anthropic.claude-sonnet-4-9"
|
||||
SUPPORTED_SONNET = "bedrock_converse:us.anthropic.claude-sonnet-5"
|
||||
RETIRED_DIRECT_ANTHROPIC = "anthropic:claude-opus-4-8"
|
||||
RETIRED_FABLE = "anthropic:claude-fable-5"
|
||||
|
||||
|
||||
def test_provider_fallback_preserves_provider_and_effort() -> None:
|
||||
|
|
@ -79,6 +85,67 @@ def test_profile_stale_anthropic_upgrades_to_supported() -> None:
|
|||
assert normalize_profile_overrides(profile) == (SUPPORTED_ANTHROPIC, "high")
|
||||
|
||||
|
||||
def test_team_settings_update_normalizes_retired_models() -> None:
|
||||
update = TeamSettingsUpdate(
|
||||
default_agent_model=SUPPORTED_ANTHROPIC,
|
||||
default_agent_reasoning_effort="medium",
|
||||
default_agent_subagent_model=RETIRED_DIRECT_ANTHROPIC,
|
||||
default_agent_subagent_reasoning_effort="medium",
|
||||
default_reviewer_model=RETIRED_DIRECT_ANTHROPIC,
|
||||
default_reviewer_reasoning_effort="medium",
|
||||
default_reviewer_subagent_model=RETIRED_DIRECT_ANTHROPIC,
|
||||
default_reviewer_subagent_reasoning_effort="low",
|
||||
)
|
||||
|
||||
assert update.default_agent_subagent_model == SUPPORTED_ANTHROPIC
|
||||
assert update.default_reviewer_model == SUPPORTED_ANTHROPIC
|
||||
assert update.default_reviewer_subagent_model == SUPPORTED_ANTHROPIC
|
||||
|
||||
|
||||
def test_team_settings_update_maps_retired_fable_to_opus_not_fable() -> None:
|
||||
update = TeamSettingsUpdate(
|
||||
fable_enabled=True,
|
||||
default_agent_model=RETIRED_FABLE,
|
||||
default_agent_reasoning_effort="high",
|
||||
)
|
||||
|
||||
assert update.default_agent_model == SUPPORTED_ANTHROPIC
|
||||
assert update.default_agent_model not in FABLE_MODEL_IDS
|
||||
|
||||
|
||||
def test_team_settings_update_rejects_unknown_model() -> None:
|
||||
with pytest.raises(ValueError, match="unsupported agent model"):
|
||||
TeamSettingsUpdate(
|
||||
default_agent_model="openai:gpt-6",
|
||||
default_agent_reasoning_effort="medium",
|
||||
)
|
||||
|
||||
|
||||
def test_team_settings_update_rejects_invalid_effort_for_retired_model() -> None:
|
||||
with pytest.raises(ValueError, match="effort 'bogus' not supported"):
|
||||
TeamSettingsUpdate(
|
||||
default_agent_model=RETIRED_DIRECT_ANTHROPIC,
|
||||
default_agent_reasoning_effort="bogus",
|
||||
)
|
||||
|
||||
|
||||
def test_team_settings_response_normalizes_retired_models() -> None:
|
||||
settings = normalize_team_settings_for_response(
|
||||
{
|
||||
"default_agent_subagent_model": RETIRED_DIRECT_ANTHROPIC,
|
||||
"default_agent_subagent_reasoning_effort": "medium",
|
||||
"default_reviewer_model": "openai:gpt-5.5",
|
||||
"default_reviewer_reasoning_effort": "medium",
|
||||
"default_reviewer_subagent_model": RETIRED_FABLE,
|
||||
"default_reviewer_subagent_reasoning_effort": "low",
|
||||
}
|
||||
)
|
||||
|
||||
assert settings["default_agent_subagent_model"] == SUPPORTED_ANTHROPIC
|
||||
assert settings["default_reviewer_model"] == SUPPORTED_ANTHROPIC
|
||||
assert settings["default_reviewer_subagent_model"] == SUPPORTED_ANTHROPIC
|
||||
|
||||
|
||||
def test_profile_without_model_defers_to_team_default() -> None:
|
||||
assert normalize_profile_overrides({"reasoning_effort": "high"}) == (None, None)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue