Skip to content

fix(providers): defer builtin provider instantiation to fix gunicorn startup crash#3403

Open
fancyboi999 wants to merge 2 commits intoagentscope-ai:mainfrom
fancyboi999:fix/3375-gunicorn-pydantic-conflict
Open

fix(providers): defer builtin provider instantiation to fix gunicorn startup crash#3403
fancyboi999 wants to merge 2 commits intoagentscope-ai:mainfrom
fancyboi999:fix/3375-gunicorn-pydantic-conflict

Conversation

@fancyboi999
Copy link
Copy Markdown
Contributor

Problem

gunicorn 启动时 worker 进程直接崩溃,报 pydantic ValidationError: Input should be a valid dictionary or instance of ModelInfo。uvicorn 不受影响。

Root Cause

provider_manager.py 在模块顶层创建了 ~80 个 ModelInfo pydantic 实例和 ~20 个 Provider 实例。

当用户用 PYTHONPATH=src/ 配合 gunicorn 启动时,同一个 .py 文件可以通过两条路径被 Python 加载——一次走 PYTHONPATH,一次走 site-packages。Python 把它们当成两个不同模块,分别创建了两个 ModelInfo 类。

MODELSCOPE_MODELS 的元素用的是 class-A 的 ModelInfo,但 OpenAIProvider 的 pydantic schema 绑的是 class-B。pydantic v2 做 isinstance() 检查时发现不匹配,直接抛 ValidationError。

本地复现脚本:

# 两个同名但不同身份的 ModelInfo 类
ns_a, ns_b = {}, {}
exec("from pydantic import BaseModel\nclass ModelInfo(BaseModel): id: str", ns_a)
exec("from pydantic import BaseModel\nclass ModelInfo(BaseModel): id: str", ns_b)

instance = ns_a['ModelInfo'](id='test')
isinstance(instance, ns_b['ModelInfo'])  # False — 触发 ValidationError

Fix

两处改动:

  1. 模型数据改成 plain dict — dict 没有类身份,pydantic 在验证时会用当前进程里的 ModelInfo 来构造,不存在 A/B 不匹配
  2. Provider 实例化从模块级常量移到 _create_builtin_providers() 工厂函数 — 在 ProviderManager.__init__() 时才调用,此时所有 import 已完成,类身份稳定

Validation

# 单元测试
pytest tests/unit/ → 518 passed

# gunicorn 真实启动 (2 workers)
gunicorn qwenpaw.app._app:app --worker-class uvicorn.workers.UvicornWorker --workers 2
→ [INFO] Booting worker with pid: 87119
→ [INFO] Booting worker with pid: 87120
→ [INFO] Application startup complete.  (×2)
→ 零 ValidationError,零 worker 崩溃

Closes #3375

…startup crash

When gunicorn workers start with PYTHONPATH pointing at src/ while the
package is also installed, the same module can be loaded under two
different paths.  Python then creates two distinct ModelInfo classes.
Instances built with class-A fail pydantic v2 isinstance checks against
class-B, crashing the worker at import time with:

  ValidationError: Input should be a valid dictionary or instance of ModelInfo

Root cause: provider_manager.py created ~80 ModelInfo pydantic instances
and ~20 Provider instances at module level (import time).

Fix: store model data as plain dicts instead of ModelInfo instances, and
move all Provider construction into a lazily-called factory function
_create_builtin_providers().  Plain dicts carry no class identity, so
pydantic coerces them using whichever ModelInfo class is current at
validation time — no mismatch possible.

Closes agentscope-ai#3375
Copilot AI review requested due to automatic review settings April 15, 2026 03:02
@github-project-automation github-project-automation bot moved this to Todo in QwenPaw Apr 15, 2026
@fancyboi999 fancyboi999 requested a deployment to maintainer-approved April 15, 2026 03:02 — with GitHub Actions Waiting
@github-actions
Copy link
Copy Markdown

Welcome to QwenPaw! 🐾

Hi @fancyboi999, this is your 55th Pull Request.

📋 About PR Template

To help maintainers review your PR faster, please make sure to include:

  • Description - What this PR does and why
  • Type of Change - Bug fix / Feature / Breaking change / Documentation / Refactoring
  • Component(s) Affected - Core / Console / Channels / Skills / CLI / Documentation / Tests / CI/CD / Scripts
  • Checklist:
    • Run and pass pre-commit run --all-files
    • Run and pass relevant tests (pytest or as applicable)
    • Update documentation if needed
  • Testing - How to test these changes
  • Local Verification Evidence:
    pre-commit run --all-files
    # paste summary result
    
    pytest
    # paste summary result

Complete PR information helps speed up the review process. You can edit the PR description to add these details.

🙌 Join Developer Community

Thanks so much for your contribution! We'd love to invite you to join the official QwenPaw developer group! You can find the Discord and DingTalk group links under the "Developer Community" section on our docs page:
https://qwenpaw.agentscope.io/docs/community

We truly appreciate your enthusiasm—and look forward to your future contributions! 😊

We'll review your PR soon.


Tip

⭐ If you find QwenPaw useful, please give us a Star!

Star QwenPaw

Staying ahead

Star QwenPaw on GitHub and be instantly notified of new releases.

Your star helps more developers discover this project! 🐾

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses a gunicorn multi-worker startup crash caused by Pydantic v2 class-identity mismatches when modules are imported via multiple paths. It does so by avoiding module-import-time creation of Pydantic ModelInfo/Provider instances and deferring built-in provider instantiation until ProviderManager initialization.

Changes:

  • Replace built-in model definitions from List[ModelInfo] to plain List[dict] to avoid cross-module isinstance() mismatches.
  • Move built-in provider instantiation from module-level constants to a lazy _create_builtin_providers() factory invoked by ProviderManager._init_builtins().
  • Update unit tests to validate built-in providers via _create_builtin_providers() / updated model definitions.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
src/qwenpaw/providers/provider_manager.py Defers built-in provider instantiation and switches model definitions to plain dicts to prevent gunicorn/Pydantic import-identity crashes.
tests/unit/providers/test_siliconflow_provider.py Adjusts tests to locate SiliconFlow providers via the new factory instead of removed module-level constants.
tests/unit/providers/test_opencode_provider.py Adjusts tests to locate OpenCode provider via the new factory instead of removed module-level constants.
tests/unit/providers/test_kimi_provider.py Adjusts tests for new Kimi model data structure (dicts) and provider factory usage.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/unit/providers/test_siliconflow_provider.py
Comment thread tests/unit/providers/test_opencode_provider.py
Comment thread tests/unit/providers/test_kimi_provider.py
Address Copilot review: _find_provider returns from a List[Provider],
so the annotation should be Provider, not OpenAIProvider.
@fancyboi999 fancyboi999 requested a deployment to maintainer-approved April 15, 2026 03:11 — with GitHub Actions Waiting
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

[Bug]:gunicorn启动与pydantic冲突问题

2 participants