mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-04 12:32:12 +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")
|
@model_validator(mode="after")
|
||||||
def _validate_model_pairs(self) -> TeamSettingsUpdate:
|
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(
|
_validate_model_effort_pair(
|
||||||
self.default_agent_model, self.default_agent_reasoning_effort, "agent"
|
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}")
|
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():
|
def _client():
|
||||||
return get_client()
|
return get_client()
|
||||||
|
|
||||||
|
|
@ -241,7 +321,7 @@ async def get_team_settings() -> dict[str, Any]:
|
||||||
"review_author_context_enabled",
|
"review_author_context_enabled",
|
||||||
):
|
):
|
||||||
merged.pop(stale_field, None)
|
merged.pop(stale_field, None)
|
||||||
return merged
|
return normalize_team_settings_for_response(merged)
|
||||||
|
|
||||||
|
|
||||||
async def upsert_team_settings(update: TeamSettingsUpdate) -> dict[str, Any]:
|
async def upsert_team_settings(update: TeamSettingsUpdate) -> dict[str, Any]:
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,18 @@ from agent.dashboard.options import (
|
||||||
gate_fable_model,
|
gate_fable_model,
|
||||||
provider_fallback_pair,
|
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"
|
STALE_ANTHROPIC = "bedrock_converse:us.anthropic.claude-opus-4-7"
|
||||||
SUPPORTED_ANTHROPIC = "bedrock_converse:us.anthropic.claude-opus-4-8"
|
SUPPORTED_ANTHROPIC = "bedrock_converse:us.anthropic.claude-opus-4-8"
|
||||||
STALE_SONNET = "bedrock_converse:us.anthropic.claude-sonnet-4-9"
|
STALE_SONNET = "bedrock_converse:us.anthropic.claude-sonnet-4-9"
|
||||||
SUPPORTED_SONNET = "bedrock_converse:us.anthropic.claude-sonnet-5"
|
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:
|
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")
|
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:
|
def test_profile_without_model_defers_to_team_default() -> None:
|
||||||
assert normalize_profile_overrides({"reasoning_effort": "high"}) == (None, None)
|
assert normalize_profile_overrides({"reasoning_effort": "high"}) == (None, None)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue