fix: sync fallback chat models when a provider is deleted or renamed#9171
fix: sync fallback chat models when a provider is deleted or renamed#9171kldsjfas wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a mechanism to synchronize provider ID changes (deletions and renames) with the fallback_chat_models list in the configuration, preventing stale references. It also adds a comprehensive suite of unit tests to verify this synchronization. The reviewer suggests expanding this synchronization logic to other configuration settings that reference provider IDs (such as default_provider_id, provider_stt_settings, and provider_tts_settings) to prevent similar stale reference issues elsewhere, along with corresponding test coverage.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| @staticmethod | ||
| def _sync_fallback_chat_models( | ||
| config: dict, old_id: str, new_id: str | None = None | ||
| ) -> None: | ||
| """Remove or remap a provider id in fallback_chat_models after delete/rename.""" | ||
| provider_settings = config.get("provider_settings") | ||
| if not isinstance(provider_settings, dict): | ||
| return | ||
| fallback_ids = provider_settings.get("fallback_chat_models") | ||
| if not isinstance(fallback_ids, list) or old_id not in fallback_ids: | ||
| return | ||
| updated_ids = [] | ||
| for fallback_id in fallback_ids: | ||
| mapped_id = new_id if fallback_id == old_id else fallback_id | ||
| if mapped_id is not None and mapped_id not in updated_ids: | ||
| updated_ids.append(mapped_id) | ||
| provider_settings["fallback_chat_models"] = updated_ids |
There was a problem hiding this comment.
当删除或重命名 Provider 时,除了 fallback_chat_models 之外,provider_settings.default_provider_id、provider_stt_settings.provider_id 以及 provider_tts_settings.provider_id 也可能会残留失效的旧 ID。这会导致在重命名或删除默认 Provider 后,系统依然尝试加载旧 ID 并打印警告日志。
建议将此方法重命名为 _sync_provider_id_references,并同时同步更新这些配置项。
@staticmethod
def _sync_provider_id_references(
config: dict, old_id: str, new_id: str | None = None
) -> None:
"""Remove or remap a provider id in fallback_chat_models and other settings after delete/rename."""
provider_settings = config.get("provider_settings")
if isinstance(provider_settings, dict):
# Sync fallback_chat_models
fallback_ids = provider_settings.get("fallback_chat_models")
if isinstance(fallback_ids, list) and old_id in fallback_ids:
updated_ids = []
for fallback_id in fallback_ids:
mapped_id = new_id if fallback_id == old_id else fallback_id
if mapped_id is not None and mapped_id not in updated_ids:
updated_ids.append(mapped_id)
provider_settings["fallback_chat_models"] = updated_ids
# Sync default_provider_id
if provider_settings.get("default_provider_id") == old_id:
if new_id is not None:
provider_settings["default_provider_id"] = new_id
else:
provider_settings.pop("default_provider_id", None)
# Sync provider_stt_settings
stt_settings = config.get("provider_stt_settings")
if isinstance(stt_settings, dict) and stt_settings.get("provider_id") == old_id:
if new_id is not None:
stt_settings["provider_id"] = new_id
else:
stt_settings.pop("provider_id", None)
# Sync provider_tts_settings
tts_settings = config.get("provider_tts_settings")
if isinstance(tts_settings, dict) and tts_settings.get("provider_id") == old_id:
if new_id is not None:
tts_settings["provider_id"] = new_id
else:
tts_settings.pop("provider_id", None)| config["provider"] = [ | ||
| prov for prov in config["provider"] if prov.get("id") != tpid | ||
| ] | ||
| self._sync_fallback_chat_models(config, tpid) |
| else: | ||
| raise ValueError(f"Provider ID {origin_provider_id} not found") | ||
| if npid != origin_provider_id: | ||
| self._sync_fallback_chat_models(config, origin_provider_id, npid) |
|
|
||
| await manager.update_provider("p1", {"id": "p1", "enable": False}) | ||
|
|
||
| assert config["provider_settings"]["fallback_chat_models"] == ["p1"] |
There was a problem hiding this comment.
建议增加单元测试,以覆盖 default_provider_id、provider_stt_settings 和 provider_tts_settings 的同步逻辑。
assert config["provider_settings"]["fallback_chat_models"] == ["p1"]
@pytest.mark.asyncio
async def test_update_provider_renames_other_references():
config = FakeConfig(
{
"provider": [{"id": "p1", "enable": True}],
"provider_settings": {
"fallback_chat_models": ["p1"],
"default_provider_id": "p1",
},
"provider_stt_settings": {"provider_id": "p1"},
"provider_tts_settings": {"provider_id": "p1"},
}
)
manager = _build_manager(config)
manager.reload = lambda x: asyncio.sleep(0)
await manager.update_provider("p1", {"id": "p1-renamed", "enable": True})
assert config["provider_settings"]["default_provider_id"] == "p1-renamed"
assert config["provider_stt_settings"]["provider_id"] == "p1-renamed"
assert config["provider_tts_settings"]["provider_id"] == "p1-renamed"|
关于 Gemini 提到的扩展建议:本 PR 有意只覆盖 #8479 报告的 fallback_chat_models——它是列表型引用,残留 ID 会在每次回退时反复打警告,清理语义没有歧义。default_provider_id、STT/TTS 的 provider_id 这类单值引用各自有运行时兜底(如默认模型留空时取第一个可用模型),删除时静默改写它们是否符合预期需要单独讨论,就不在这个 PR 里顺手扩了。如果维护者觉得有必要,我可以再开一个 PR 统一处理这类引用。 |
Motivation / 动机
修复 #8479。
删除或重命名对话模型后,
provider_settings.fallback_chat_models里的引用不会被同步:Fallback chat provider ... not found, skip.的警告;这可能是由于您修改了提供商(模型)ID 导致的正是这种情况)。Modifications / 改动点
astrbot/core/provider/manager.py:新增_sync_fallback_chat_models();delete_provider()删除 provider 时同步移除回退列表中的对应 ID,update_provider()在 ID 变更时把回退列表中的旧 ID 迁移为新 ID(若新 ID 已在列表中则去重),随后才save_config()。tests/unit/test_provider_manager_fallback_sync.py:新增 5 个用例,覆盖按 ID 删除、按 provider source 批量删除、改名迁移、改名撞残留 ID 去重、ID 不变不动列表。运行时的
_get_fallback_chat_providers()行为保持不变(仍会跳过找不到的 provider),本 PR 只是让配置不再残留失效引用。WebUI 下拉里已禁用模型仍可被选择的问题在前端ProviderSelector,不在本 PR 范围内。Screenshots or Test Results / 运行截图或测试结果
相关既有套件(
test_astr_main_agent.py、test_astr_agent_tool_exec.py、test_cron_manager.py)与 master 基线对比无新增失败;ruff check/ruff format --check通过。Checklist / 检查清单
😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
/ 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。
👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
/ 我的更改经过了良好的测试,并已在上方提供了"验证步骤"和"运行截图"。
🤓 I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in
requirements.txtandpyproject.toml./ 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到了
requirements.txt和pyproject.toml文件相应位置。Summary by Sourcery
Synchronize fallback chat model IDs in provider settings when providers are deleted or renamed to avoid stale references.
Bug Fixes:
Tests: