Skip to content

fix: sync fallback chat models when a provider is deleted or renamed#9171

Open
kldsjfas wants to merge 1 commit into
AstrBotDevs:masterfrom
kldsjfas:fix/8479-fallback-chat-models-sync
Open

fix: sync fallback chat models when a provider is deleted or renamed#9171
kldsjfas wants to merge 1 commit into
AstrBotDevs:masterfrom
kldsjfas:fix/8479-fallback-chat-models-sync

Conversation

@kldsjfas

@kldsjfas kldsjfas commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Motivation / 动机

修复 #8479

删除或重命名对话模型后,provider_settings.fallback_chat_models 里的引用不会被同步:

  • 删除 provider 后,失效的 ID 一直留在回退列表里,每次触发回退都会打出 Fallback chat provider ... not found, skip. 的警告;
  • 通过 WebUI 修改 provider 的 ID 后,回退列表仍指向旧 ID,回退时同样找不到(issue 日志里的 这可能是由于您修改了提供商(模型)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 范围内。

  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果

$ python -m pytest tests/unit/test_provider_manager_fallback_sync.py -v
tests/unit/test_provider_manager_fallback_sync.py::test_delete_provider_removes_fallback_reference PASSED
tests/unit/test_provider_manager_fallback_sync.py::test_delete_provider_source_removes_all_fallback_references PASSED
tests/unit/test_provider_manager_fallback_sync.py::test_update_provider_renames_fallback_reference PASSED
tests/unit/test_provider_manager_fallback_sync.py::test_update_provider_rename_dedupes_stale_target PASSED
tests/unit/test_provider_manager_fallback_sync.py::test_update_provider_same_id_keeps_fallback_list PASSED
5 passed

相关既有套件(test_astr_main_agent.pytest_astr_agent_tool_exec.pytest_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.txt and pyproject.toml.
    / 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到了 requirements.txtpyproject.toml 文件相应位置。

Summary by Sourcery

Synchronize fallback chat model IDs in provider settings when providers are deleted or renamed to avoid stale references.

Bug Fixes:

  • Remove deleted providers from provider_settings.fallback_chat_models so fallback lists no longer contain invalid IDs.
  • Remap fallback_chat_models entries when a provider ID is changed, deduplicating any existing target IDs to keep the list consistent.

Tests:

  • Add unit tests covering fallback list updates on provider deletion by ID and source, provider renames, deduplication of renamed IDs, and no-op behavior when IDs remain unchanged.

@dosubot dosubot Bot added size:S This PR changes 10-29 lines, ignoring generated files. area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. labels Jul 6, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +816 to +832
@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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

当删除或重命名 Provider 时,除了 fallback_chat_models 之外,provider_settings.default_provider_idprovider_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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

更新调用的辅助方法名称为 _sync_provider_id_references

Suggested change
self._sync_fallback_chat_models(config, tpid)
self._sync_provider_id_references(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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

更新调用的辅助方法名称为 _sync_provider_id_references

Suggested change
self._sync_fallback_chat_models(config, origin_provider_id, npid)
self._sync_provider_id_references(config, origin_provider_id, npid)


await manager.update_provider("p1", {"id": "p1", "enable": False})

assert config["provider_settings"]["fallback_chat_models"] == ["p1"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

建议增加单元测试,以覆盖 default_provider_idprovider_stt_settingsprovider_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"

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@kldsjfas

kldsjfas commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

关于 Gemini 提到的扩展建议:本 PR 有意只覆盖 #8479 报告的 fallback_chat_models——它是列表型引用,残留 ID 会在每次回退时反复打警告,清理语义没有歧义。default_provider_id、STT/TTS 的 provider_id 这类单值引用各自有运行时兜底(如默认模型留空时取第一个可用模型),删除时静默改写它们是否符合预期需要单独讨论,就不在这个 PR 里顺手扩了。如果维护者觉得有必要,我可以再开一个 PR 统一处理这类引用。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. size:S This PR changes 10-29 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant