Skip to content

[Bug] request_max_retries 与 OpenAI SDK 内建重试叠加,导致实际请求次数超出配置 #9663

Description

@chinkeikin

问题描述

AstrBot v4.27.2 已提供全局配置 provider_settings.request_max_retries,其代码注释和 WebUI 提示均表示该值是“可重试请求错误的最大尝试次数,包含首次请求”。

但是 OpenAI Compatible provider 在创建 AsyncOpenAI / AsyncAzureOpenAI 客户端时没有显式传入 max_retries,因此仍会继承 OpenAI Python SDK 的默认值 max_retries=2

结果是 AstrBot 的重试层与 OpenAI SDK 的重试层发生嵌套。即使将:

"provider_settings": {
  "request_max_retries": 1
}

设置为只尝试一次,单次 AstrBot provider 请求内部仍可能实际发送 3 次 HTTP 请求:

1 次 AstrBot 尝试 ×(1 次 SDK 首次请求 + 2 次 SDK 重试)= 3 次 HTTP 请求

这会使 request_max_retries=1 无法真正关闭重试,也会显著延迟 fallback provider 的切换。

如何复现

环境:

  • AstrBot: v4.27.2
  • 部署方式:Docker / Linux
  • Provider: OpenAI Compatible (openai_chat_completion)
  • OpenAI Python SDK: 2.53.0
  • 消息平台:OneBot v11(与问题本身无关)

配置:

{
  "provider_sources": [
    {
      "type": "openai_chat_completion",
      "timeout": 120
    }
  ],
  "provider_settings": {
    "request_max_retries": 1
  }
}

让兼容 API 的单次请求超过 120 秒,打开 Debug 日志。

尽管 request_max_retries=1,仍可看到 OpenAI SDK 的内部自动重试:

[openai._base_client] 2 retries left
[openai._base_client] Retrying request to /chat/completions ...
[openai._base_client] 1 retry left
[openai._base_client] Retrying request to /chat/completions ...

单个 provider 最终约等待:

3 × 120 秒 ≈ 360 秒

之后 AstrBot 才切换到 fallback provider。

若 AstrBot 外层设置为更大的尝试次数,理论请求数还会相乘。例如 request_max_retries=5 时,OpenAI provider 最多可能产生 5 × 3 = 15 次 HTTP 请求。

根因定位

当前 openai_source.py 创建客户端时只设置了 timeout,未覆盖 SDK 的 max_retries

self.client = AsyncOpenAI(
    api_key=self.chosen_api_key,
    base_url=provider_config.get("api_base", None),
    default_headers=self.custom_headers,
    timeout=self.timeout,
    http_client=self._create_http_client(provider_config),
)

随后 AstrBot 又在 _query() / _query_stream() 外层使用:

retry_provider_request(
    "OpenAI",
    ...,
    max_attempts=request_max_retries,
)

因此形成两层重试。

预期行为

request_max_retries 应表示一次模型请求的真实最大尝试次数,且包含首次请求:

  • 1:只发送一次请求,不重试;
  • 2:首次请求 + 最多一次重试;
  • 3:首次请求 + 最多两次重试。

OpenAI provider 不应再在 AstrBot 重试层内部隐式执行 SDK 自动重试。

建议修复

既然 AstrBot 已通过统一的 retry_provider_request() 控制 OpenAI、Gemini、Anthropic 等 provider 的请求重试,建议在创建 OpenAI 客户端时关闭 SDK 内建重试:

self.client = AsyncOpenAI(
    ...,
    timeout=self.timeout,
    max_retries=0,
)

AsyncAzureOpenAI 同样设置 max_retries=0

这样 provider_settings.request_max_retries 就能成为单一、可预测的重试控制源。建议同时增加测试,断言 OpenAI/Azure OpenAI 客户端的 max_retries == 0,并验证 request_max_retries=1 时 transport 只被调用一次。

如果维护者希望保留 SDK 内建重试,另一种方案是把 SDK 重试次数明确暴露为独立配置,并在 WebUI 说明总请求次数是两层配置的乘积;不过统一由 AstrBot 控制重试的语义会更清晰。

相关 Issue / PR

我愿意在维护者确认预期方案后提交一个包含测试的修复 PR。

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:providerThe bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions