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

Abierto
#10,980 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
3/5
Tiempo estimado
1-2 días
Aptitud para principiantes
45/100
Tipo de issue
Error
Claridad
Bastante claro
Estado de actividad
Activo
Stack tecnológico
python

Línea de trabajo

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.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

[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.
Lenguaje dominante
Python
Estrellas
46.4k
Forks
3.4k
Merge medio
1 d 11 h
PR fusionados (30 d)
120

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de AstrBotDevs/AstrBot

Todos los issues de AstrBotDevs/AstrBot

Issues similares

Más issues de Python

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.