[Bug] OpenAI 兼容 provider 的混合 SSL 上下文导致特定中转站稳定 ReadTimeout(APIConnectionError)

Đang mở
#10,980 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

Chưa có ai nhận issue này.

Đánh giá

Độ khó
3/5
Thời gian dự kiến
1-2 ngày
Mức phù hợp với người mới
45/100
Loại issue
Lỗi
Độ rõ ràng
Khá rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
python
Lĩnh vực
backend, networking

Hướng nghiên cứu

Start in astrbot/core/utils/network_utils.py, at create_proxy_client() and the _SYSTEM_SSL_CTX built by build_ssl_context_with_certifi(), and check how resolved_verify is passed to httpx.AsyncClient. Run the reproduce script from the issue against a relay that reproduces the hang, comparing the hybrid context with the default verify. Done means the maintainers have chosen between a per-provider SSL option and a ReadTimeout fallback, and the chosen path works with that relay.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

[Bug] OpenAI 兼容 provider 使用混合 SSL 上下文时,对特定中转站(78code)稳定 ReadTimeout → APIConnectionError

What happened / 发生了什么

在 AstrBot 中配置一个 OpenAI 兼容中转站(https://www.78code.cc/v1,Gemini 系模型)后,Dashboard 的「测试」按钮对所有模型都返回:

openai.APIConnectionError: Connection error.

日志里连续重试 5 次全部失败:

[v4.29.4] [sources.request_retry:68]: [OpenAI] Request failed with retryable error; retrying (2/5): APIConnectionError: Connection error.
[v4.29.4] [sources.request_retry:68]: [OpenAI] Request failed with retryable error; retrying (3/5): APIConnectionError: Connection error.
[v4.29.4] [sources.request_retry:68]: [OpenAI] Request failed with retryable error; retrying (4/5): APIConnectionError: Connection error.
[v4.29.4] [sources.request_retry:68]: [OpenAI] Request failed with retryable error; retrying (5/5): APIConnectionError: Connection error.
[v4.29.4] [utils.network_utils:86]: [OpenAI] Network connection failed (APIConnectionError): Connection error.

但同一个 key、同一个 URL、同样的请求体,用别的客户端发都能成功:

  • PowerShell Invoke-RestMethod:200,gemini-3-flash-preview 约 1.9 秒返回
  • AstrBot 自带 Python 里用 requests:200
  • AstrBot 自带 Python 里用 openai SDK + 默认 httpx client:200,正常返回内容
    所以不是 key、不是网络、也不是上游慢。

根因定位 / Root cause

astrbot/core/utils/network_utils.py 的 create_proxy_client() 会给每个 OpenAI 系 provider 构造一个带「系统证书库 + certifi」混合 SSL 上下文的 httpx client:

# network_utils.py:120
resolved_verify = _SYSTEM_SSL_CTX if verify is None else verify   # _SYSTEM_SSL_CTX = build_ssl_context_with_certifi()
...
return httpx_module.AsyncClient(verify=resolved_verify, headers=headers)

对这个中转站,只要把这个上下文传给 httpx.AsyncClient,请求就会挂住直到读超时;换成 httpx 默认校验(certifi)则 2 秒内返回 200。
对照实验(在 AstrBot 自带 Python 中运行,其余条件完全相同):

http_client 结果
httpx.AsyncClient(verify=build_ssl_context_with_certifi()) ReadTimeout(60 秒后超时),SDK 最终抛 APITimeoutError → APIConnectionError
httpx.AsyncClient()(默认 certifi) 200 OK,约 2 秒
补充一个反直觉的点:同一个混合上下文,用同步 httpx.get(verify=ctx) 请求同一地址是 200。所以问题只出现在「异步 httpx + 该上下文 + 该站点」的组合上,不是证书链缺失、也不是证书校验失败(没有 CERTIFICATE_VERIFY_FAILED,纯粹是连接建立后读不到响应)。
上下文本身看起来也正常:
SSLContext | check_hostname=True | verify_mode=CERT_REQUIRED
min tls: TLSv1_2 | max tls: -1 | ALPN: None | ca certs loaded: 342

Reproduce / 如何复现

在 AstrBot 自带 Python 环境下运行(需要一个 OpenAI 兼容中转站的 key,我这里用 78code):

import asyncio, httpx
from astrbot.core.utils import network_utils as nu
from openai import AsyncOpenAI
KEY  = "sk-***"
BASE = "https://www.78code.cc/v1"
async def try_client(label, client):
    c = AsyncOpenAI(api_key=KEY, base_url=BASE, timeout=60, max_retries=0, http_client=client)
    try:
        r = await c.chat.completions.create(
            model="gemini-3-flash-preview",
            messages=[{"role": "user", "content": "say OK"}],
            max_tokens=16,
        )
        print(label, "-> OK:", r.choices[0].message.content)
    except Exception as e:
        print(label, "-> EXC:", type(e).__name__, e)
    finally:
        await c.close()
async def main():
    await try_client("hybrid-ctx", httpx.AsyncClient(verify=nu._SYSTEM_SSL_CTX))
    await try_client("default",    httpx.AsyncClient())
asyncio.run(main())

实际输出:

hybrid-ctx -> EXC: APITimeoutError Request timed out.
   cause: ReadTimeout
default    -> OK: 'OK'

期望行为 / Expected

  1. 不应因为「用了系统证书库」而让某些站点完全不可用。
  2. 建议二选一或都做:
    • 给 provider 配置增加一个开关(例如 skip_system_ssl_store / ssl_verify: default|certifi|system),让用户能对这类中转站退回 certifi 默认校验;
    • 或者在 create_proxy_client 里做兜底:当使用混合上下文出现 ReadTimeout/ConnectError 时,用默认校验重试一次,并在日志里标注降级原因。
  3. 顺带一提:APIConnectionError: Connection error. 这个报错信息完全无法区分「DNS 失败 / TLS 挂住 / 上游拒绝」,排查成本很高(参见 #9527)。

AstrBot version, deployment method, provider, messaging platform / 版本与部署方式

  • AstrBot:v4.29.4(Desktop 版,Windows)
  • Python:3.12.12(AstrBot 内置运行时)
  • httpx:0.28.1;openai SDK:3.42.0
  • OS:Windows 11 家庭中文版,build 26200
  • Provider:OpenAI 兼容(https://www.78code.cc/v1),模型 gemini-3-flash-preview / gemini-4-argon / gemini-3.8-flash
  • 消息平台:aiocqhttp(NapCat 反向 WebSocket)
  • 同一实例下 DeepSeek / SiliconFlow 等 provider 均正常,只有这个中转站受影响

OS

Windows 11 (26200)

Logs / 报错日志

见上方「What happened」段落中的 5 次重试日志;完整失败链路为:

openai.APIConnectionError: Connection error.
  ← openai.APITimeoutError / ReadTimeout
  ← httpx.AsyncClient(verify=<system+certifi 混合上下文>)

Dashboard /api/v1/providers/test 返回:

{"id":"78code/gemini-3-flash-preview","model":"gemini-3-flash-preview",
 "type":"chat_completion","status":"unavailable","error":"Connection error."}

Are you willing to submit a PR? / 你愿意提交 PR 吗?

  • Yes!(如需,我可以提供补丁:给 provider 增加证书校验策略开关,并在 ReadTimeout 时降级重试)

Code of Conduct

  • I have read and agree to abide by the project's Code of Conduct.
Ngôn ngữ chính
Python
Star
46.4k
Fork
3.4k
Merge trung bình
1 ngày 11 giờ
Pull request đã merge (30 ngày)
120

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của AstrBotDevs/AstrBot

Tất cả issue của AstrBotDevs/AstrBot

Issue tương tự

Thêm issue về Python

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.