Skip to content

Conversation

@tooplick
Copy link
Contributor

@tooplick tooplick commented Feb 7, 2026

feat: 添加 Provider 级别代理支持及请求失败日志

Motivation / 动机

issue #4030
为每个 Provider 源添加独立的代理配置支持,允许用户为不同的模型提供商配置不同的代理地址。这对于以下场景特别有用:

  • Docker 环境中需要为外部 API 配置代理,但不影响内网通信
  • 不同提供商需要使用不同的代理服务器
  • 调试网络连接问题时需要详细的日志信息

Modifications / 改动点

后端更改:

  • astrbot/core/config/default.py:
    • 为所有 Provider 模板添加 proxy 字段
    • CONFIG_METADATA_2 中添加 proxy 字段的元数据(描述和提示)
  • astrbot/core/provider/sources/openai_source.py: 添加代理支持和连接失败日志
  • astrbot/core/provider/sources/anthropic_source.py: 添加代理支持和连接失败日志
  • astrbot/core/provider/sources/gemini_source.py: 添加代理支持和连接失败日志
  • astrbot/core/provider/sources/openai_embedding_source.py: 添加代理支持和日志
  • astrbot/core/provider/sources/openai_tts_api_source.py: 添加代理支持和日志
  • astrbot/core/provider/sources/gemini_embedding_source.py: 添加代理支持和日志
  • astrbot/core/provider/sources/gemini_tts_source.py: 添加代理支持和日志
  • astrbot/core/provider/sources/fishaudio_tts_api_source.py: 添加代理支持和日志
  • astrbot/core/provider/sources/azure_tts_source.py: 添加代理支持和日志

前端更改:

  • dashboard/src/composables/useProviderSources.ts: 添加 proxy 字段的 i18n 映射
  • dashboard/src/i18n/locales/zh-CN/features/provider.json: 添加中文翻译
  • dashboard/src/i18n/locales/en-US/features/provider.json: 添加英文翻译

功能实现:

  1. 每个 Provider 可独立配置 proxy 字段(格式:http://127.0.0.1:7890
  2. 仅对该 Provider 的 API 请求生效,不影响其他网络通信
  3. 启用代理时输出 info 日志:[Provider名] 使用代理: {proxy}
  4. 连接失败时输出详细 error 日志,包含代理配置信息
  • This is NOT a breaking change. / 这不是一个破坏性变更。

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

前端 UI 显示:
在 Provider 源的"高级配置"中新增"代理地址"字段,带有中文描述和提示。
屏幕截图 2026-02-07 200922

日志输出示例:

INFO - [OpenAI] 使用代理: http://127.0.0.1:7890
ERROR - [OpenAI] 代理连接失败。代理地址: http://127.0.0.1:7890,错误: ConnectError...

Checklist / 检查清单

  • 😊 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。/ If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
  • 👀 我的更改经过了良好的测试,并已在上方提供了"验证步骤"和"运行截图"。/ My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
  • 🤓 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到了 requirements.txtpyproject.toml 文件相应位置。/ 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.
  • 😮 我的更改没有引入恶意代码。/ My changes do not introduce malicious code.

Summary by Sourcery

为多个 AI 和 TTS 服务提供商新增「按提供商级别」的代理配置,并增强连接错误日志记录。

新功能:

  • 允许在后端模板和提供商配置中,为每个提供商来源单独配置 HTTP/HTTPS 代理。
  • 在控制台界面中暴露提供商级别的代理配置字段,并为中文和英文用户提供本地化的标签与提示信息。

增强内容:

  • 扩展默认的 no_proxy 配置,覆盖常见的私有网络网段,使内部流量绕过 HTTP 代理。
  • 为 OpenAI、Anthropic、Gemini(聊天和向量嵌入)以及多个 TTS 提供商新增更详细的连接失败日志记录,并在配置了代理时包含代理相关信息。
Original summary in English

Summary by Sourcery

Add provider-level proxy configuration and enhanced connection error logging for multiple AI and TTS providers.

New Features:

  • Allow configuring an individual HTTP/HTTPS proxy per provider source in the backend templates and provider configs.
  • Expose a provider-level proxy field in the dashboard UI with localized labels and hints for Chinese and English users.

Enhancements:

  • Extend the default no_proxy configuration to cover common private network ranges so internal traffic bypasses HTTP proxies.
  • Add detailed connection failure logging for OpenAI, Anthropic, Gemini (chat and embedding), and multiple TTS providers, including proxy information when configured.

@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Feb 7, 2026
@dosubot
Copy link

dosubot bot commented Feb 7, 2026

Related Documentation

Checked 1 published document(s) in 1 knowledge base(s). No updates required.

How did I do? Any feedback?  Join Discord

@dosubot dosubot bot added the area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. label Feb 7, 2026
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - 我发现了 5 个问题,并给出了一些总体反馈:

  • 现在 OpenAI、Anthropic、Gemini、TTS 等的连接错误处理逻辑基本是复制粘贴的,只是字符串检查略有不同(例如有的检查 type(e).__name__,有的检查 e.message);建议把这部分逻辑集中到一个共享的 helper 中,这样各个 provider 在代理/连接失败的检测和日志记录上可以保持一致。
  • ChatProviderTemplate 的 metadata 里,你现在同时在 provider.items 下和后面与 api_base/model 同级的位置定义了 proxy;建议确认这种重复是有意而为之,并确保这两个定义能保持同步(相同的 description 和 hint),以避免在控制台中出现让人困惑的 schema 行为。
  • azure_tts_source.py 中的日志标签 "[Azure TTS OTTS] 使用代理" 看起来相比下面的 "[Azure TTS Native]" 像是一个拼写错误;统一标签名称会让与代理相关的日志更容易检索和理解。
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The connection-error handling logic for OpenAI, Anthropic, Gemini, TTS, etc. is now copy‑pasted with slightly different string checks (e.g., checking `type(e).__name__` vs `e.message`); consider centralizing this into a shared helper so providers use consistent detection and logging for proxy/connection failures.
- In `ChatProviderTemplate` metadata you now define `proxy` both under `provider.items` and again later alongside `api_base`/`model`; it would be good to verify this duplication is intentional and that the two definitions stay in sync (same description and hint) to avoid confusing schema behavior in the dashboard.
- The log tag `"[Azure TTS OTTS] 使用代理"` in `azure_tts_source.py` looks like a typo compared with `"[Azure TTS Native]"` below; aligning the tag names would make proxy‑related logs easier to search and interpret.

## Individual Comments

### Comment 1
<location> `astrbot/core/provider/sources/openai_source.py:36-41` </location>
<code_context>

         self.set_model(provider_config.get("model", "unknown"))

+    def _create_http_client(self, provider_config: dict) -> httpx.AsyncClient | None:
+        """创建带代理的 HTTP 客户端"""
+        proxy = provider_config.get("proxy", "")
+        if proxy:
+            logger.info(f"[Anthropic] 使用代理: {proxy}")
+            return httpx.AsyncClient(proxy=proxy)
+        return None
+
</code_context>

<issue_to_address>
**issue (bug_risk):** Custom `httpx.AsyncClient` instances passed into SDKs are never explicitly closed, which can leak connections.

This new helper (and similar ones in `anthropic_source`, `openai_embedding_source`, `openai_tts_api_source`) creates an `httpx.AsyncClient` and passes it into the SDK as `http_client`, but nothing ensures the client is ever closed. If providers are created repeatedly (e.g. config reloads), this can accumulate open connections/file descriptors.

Please either:
- Reuse a long‑lived client per provider and close it on app shutdown, or
- Introduce an explicit lifecycle (e.g. context manager / `aclose()` hook) so the SDK/client wrapper reliably closes the underlying `AsyncClient`.

Otherwise this risks resource leaks in long‑running or high‑churn scenarios.
</issue_to_address>

### Comment 2
<location> `astrbot/core/provider/sources/anthropic_source.py:221-230` </location>
<code_context>
-        completion = await self.client.messages.create(
-            **payloads, stream=False, extra_body=extra_body
-        )
+        try:
+            completion = await self.client.messages.create(
+                **payloads, stream=False, extra_body=extra_body
+            )
+        except Exception as e:
+            if "ConnectError" in str(type(e).__name__) or "Connection" in str(e):
+                proxy = self.provider_config.get("proxy", "")
+                if proxy:
+                    logger.error(f"[Anthropic] 代理连接失败。代理地址: {proxy},错误: {e}")
+                else:
+                    logger.error(f"[Anthropic] 连接失败,未配置代理。错误: {e}")
+            raise

         assert isinstance(completion, Message)
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Connection error detection via string inspection on `Exception` is brittle and may miss relevant network failures.

The handler infers connection issues via `"ConnectError" in str(type(e).__name__) or "Connection" in str(e)`, which is brittle and depends on message text. It can both miss other relevant `httpx` network errors (timeouts, DNS, etc.) and misclassify unrelated exceptions whose messages contain “Connection”.

Prefer catching the specific `httpx` (or SDK) network exceptions and using `isinstance` checks (e.g. `httpx.ConnectError`, `httpx.NetworkError`, `httpx.ReadTimeout`) instead of string matching so proxy diagnostics are more accurate and resilient.

Suggested implementation:

```python
        try:
            completion = await self.client.messages.create(
                **payloads, stream=False, extra_body=extra_body
            )
        except httpx.RequestError as e:
            proxy = self.provider_config.get("proxy", "")
            if proxy:
                logger.error(
                    f"[Anthropic] 网络/代理连接失败 ({type(e).__name__})。代理地址: {proxy},错误: {e}"
                )
            else:
                logger.error(
                    f"[Anthropic] 网络连接失败 ({type(e).__name__}),未配置代理。错误: {e}"
                )
            raise

```

To fully apply this change you should also:

1. Ensure `httpx` is imported at the top of `astrbot/core/provider/sources/anthropic_source.py`, e.g.:
   - `import httpx`
2. If your project uses a local wrapper or alias around `httpx`, adjust the caught exception type accordingly (e.g. replace `httpx.RequestError` with the appropriate base network error).
</issue_to_address>

### Comment 3
<location> `astrbot/core/provider/sources/gemini_source.py:122-128` </location>
<code_context>
-        #     f"发生了错误(gemini_source)。Provider 配置如下: {self.provider_config}",
-        # )
+        
+        # 连接错误处理
+        if "ConnectError" in str(type(e).__name__) or "Connection" in str(e.message):
+            proxy = self.provider_config.get("proxy", "")
+            if proxy:
+                logger.error(f"[Gemini] 代理连接失败。代理地址: {proxy},错误: {e}")
+            else:
+                logger.error(f"[Gemini] 连接失败,未配置代理。错误: {e}")
+        
         raise e
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Gemini connection error handling also relies on string matching, which may be unreliable across error types.

This branch currently detects connection failures via string checks on the exception type/name. If the client changes exception names/messages (or localizes them), this may stop recognizing real connection errors. Prefer matching on the concrete exception classes provided by the Gemini/HTTP client, or centralize connection-detection logic so all providers share a single, more robust implementation.

Suggested implementation:

```python
        # 连接错误处理
        # 使用具体异常类型来判断是否为连接类错误,而非依赖异常名称/文案匹配
        is_conn_error = isinstance(e, (TimeoutError, OSError))

        # 某些 HTTP / 客户端库会把底层连接错误包在 __cause__ 中
        cause = getattr(e, "__cause__", None)
        if not is_conn_error and cause is not None:
            is_conn_error = isinstance(cause, (TimeoutError, OSError))

        if is_conn_error:
            proxy = self.provider_config.get("proxy", "")
            if proxy:
                logger.error(f"[Gemini] 代理连接失败。代理地址: {proxy},错误: {e}")
            else:
                logger.error(f"[Gemini] 连接失败,未配置代理。错误: {e}")

        raise e

```

如果项目中已经统一使用某个 HTTP / Gemini 客户端(如 httpx、aiohttp 等),可以进一步改进为:

1. 在公共工具模块中实现一个 `is_connection_error(exc: BaseException) -> bool`,内部基于具体客户端异常类(如 `httpx.ConnectError`, `httpx.ConnectTimeout`, `aiohttp.ClientConnectorError` 等)进行判断,并同时兜底 `TimeoutError` / `OSError`2. 在本文件中改为调用该公共方法,例如:
   `from astrbot.core.utils.errors import is_connection_error`
   然后:
   `if is_connection_error(e): ...`
3. 将其他 provider 中的连接错误判断也统一迁移到该公共方法,避免各处重复且不一致的判断逻辑。
</issue_to_address>

### Comment 4
<location> `astrbot/core/provider/sources/azure_tts_source.py:32-35` </location>
<code_context>
         self.last_sync_time = 0
         self.timeout = Timeout(10.0)
         self.retry_count = 3
+        self.proxy = config.get("proxy", "")
+        if self.proxy:
+            logger.info(f"[Azure TTS OTTS] 使用代理: {self.proxy}")
         self._client: AsyncClient | None = None

</code_context>

<issue_to_address>
**nitpick (typo):** Log tag `[Azure TTS OTTS]` looks inconsistent and may be a typo, which can hinder log filtering.

The `[Azure TTS OTTS]` prefix is inconsistent with the later `[Azure TTS Native]` tag and existing naming patterns. If this isn’t an intentional acronym, please rename it to something like `[Azure TTS]` or `[Azure TTS API]` to keep log filtering consistent across Azure TTS implementations.

```suggestion
        self.proxy = config.get("proxy", "")
        if self.proxy:
            logger.info(f"[Azure TTS] 使用代理: {self.proxy}")
        self._client: AsyncClient | None = None
```
</issue_to_address>

### Comment 5
<location> `astrbot/core/provider/sources/anthropic_source.py:57` </location>
<code_context>

         self.set_model(provider_config.get("model", "unknown"))

+    def _create_http_client(self, provider_config: dict) -> httpx.AsyncClient | None:
+        """创建带代理的 HTTP 客户端"""
+        proxy = provider_config.get("proxy", "")
</code_context>

<issue_to_address>
**issue (complexity):** Consider centralizing proxy-aware HTTP client creation and extracting explicit connection-error logging helpers to streamline the Anthropic provider implementation.

You can keep all the new behavior and still simplify by (1) centralizing HTTP client creation and (2) isolating connection‑error handling.

### 1. Centralize HTTP client creation

Instead of a provider‑local `_create_http_client` that duplicates the OpenAI pattern, move this to a shared helper (e.g. on `Provider`) and call it here.

**On the base `Provider` (or a shared utility):**

```python
# provider.py (or a shared utils module)
import httpx
from astrbot import logger

class Provider:
    # ...

    def _create_http_client(
        self,
        provider_label: str,
        provider_config: dict,
    ) -> httpx.AsyncClient | None:
        proxy = provider_config.get("proxy", "")
        if proxy:
            logger.info(f"[{provider_label}] 使用代理: {proxy}")
            return httpx.AsyncClient(proxy=proxy)
        return None
```

**In the Anthropic provider:**

```python
self.client = AsyncAnthropic(
    api_key=self.chosen_api_key,
    timeout=self.timeout,
    base_url=self.base_url,
    http_client=self._create_http_client("Anthropic", provider_config),
)
```

This keeps your new proxy behavior but avoids per‑provider duplication.

### 2. Use explicit exception types and extract logging

Separate the connection‑error logging from `_query` and use explicit `httpx` exception classes where possible. You can still fall back to a general `Exception` handler if needed.

```python
import httpx

# inside Anthropic provider
def _log_connection_failure(self, error: Exception) -> None:
    proxy = self.provider_config.get("proxy", "")
    if proxy:
        logger.error(f"[Anthropic] 代理连接失败。代理地址: {proxy},错误: {error}")
    else:
        logger.error(f"[Anthropic] 连接失败,未配置代理。错误: {error}")
```

Then simplify `_query`:

```python
try:
    completion = await self.client.messages.create(
        **payloads,
        stream=False,
        extra_body=extra_body,
    )
except (httpx.ConnectError, httpx.ProxyError, httpx.ReadTimeout) as e:
    self._log_connection_failure(e)
    raise
except Exception:
    # keep existing behavior for non-connection errors
    raise
```

This preserves all behavior (proxy support + logging) but removes brittle string inspection and keeps `_query` focused on request/response logic.
</issue_to_address>

Sourcery 对开源项目免费使用——如果你觉得这次 Review 有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进 Review 质量。
Original comment in English

Hey - I've found 5 issues, and left some high level feedback:

  • The connection-error handling logic for OpenAI, Anthropic, Gemini, TTS, etc. is now copy‑pasted with slightly different string checks (e.g., checking type(e).__name__ vs e.message); consider centralizing this into a shared helper so providers use consistent detection and logging for proxy/connection failures.
  • In ChatProviderTemplate metadata you now define proxy both under provider.items and again later alongside api_base/model; it would be good to verify this duplication is intentional and that the two definitions stay in sync (same description and hint) to avoid confusing schema behavior in the dashboard.
  • The log tag "[Azure TTS OTTS] 使用代理" in azure_tts_source.py looks like a typo compared with "[Azure TTS Native]" below; aligning the tag names would make proxy‑related logs easier to search and interpret.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The connection-error handling logic for OpenAI, Anthropic, Gemini, TTS, etc. is now copy‑pasted with slightly different string checks (e.g., checking `type(e).__name__` vs `e.message`); consider centralizing this into a shared helper so providers use consistent detection and logging for proxy/connection failures.
- In `ChatProviderTemplate` metadata you now define `proxy` both under `provider.items` and again later alongside `api_base`/`model`; it would be good to verify this duplication is intentional and that the two definitions stay in sync (same description and hint) to avoid confusing schema behavior in the dashboard.
- The log tag `"[Azure TTS OTTS] 使用代理"` in `azure_tts_source.py` looks like a typo compared with `"[Azure TTS Native]"` below; aligning the tag names would make proxy‑related logs easier to search and interpret.

## Individual Comments

### Comment 1
<location> `astrbot/core/provider/sources/openai_source.py:36-41` </location>
<code_context>

         self.set_model(provider_config.get("model", "unknown"))

+    def _create_http_client(self, provider_config: dict) -> httpx.AsyncClient | None:
+        """创建带代理的 HTTP 客户端"""
+        proxy = provider_config.get("proxy", "")
+        if proxy:
+            logger.info(f"[Anthropic] 使用代理: {proxy}")
+            return httpx.AsyncClient(proxy=proxy)
+        return None
+
</code_context>

<issue_to_address>
**issue (bug_risk):** Custom `httpx.AsyncClient` instances passed into SDKs are never explicitly closed, which can leak connections.

This new helper (and similar ones in `anthropic_source`, `openai_embedding_source`, `openai_tts_api_source`) creates an `httpx.AsyncClient` and passes it into the SDK as `http_client`, but nothing ensures the client is ever closed. If providers are created repeatedly (e.g. config reloads), this can accumulate open connections/file descriptors.

Please either:
- Reuse a long‑lived client per provider and close it on app shutdown, or
- Introduce an explicit lifecycle (e.g. context manager / `aclose()` hook) so the SDK/client wrapper reliably closes the underlying `AsyncClient`.

Otherwise this risks resource leaks in long‑running or high‑churn scenarios.
</issue_to_address>

### Comment 2
<location> `astrbot/core/provider/sources/anthropic_source.py:221-230` </location>
<code_context>
-        completion = await self.client.messages.create(
-            **payloads, stream=False, extra_body=extra_body
-        )
+        try:
+            completion = await self.client.messages.create(
+                **payloads, stream=False, extra_body=extra_body
+            )
+        except Exception as e:
+            if "ConnectError" in str(type(e).__name__) or "Connection" in str(e):
+                proxy = self.provider_config.get("proxy", "")
+                if proxy:
+                    logger.error(f"[Anthropic] 代理连接失败。代理地址: {proxy},错误: {e}")
+                else:
+                    logger.error(f"[Anthropic] 连接失败,未配置代理。错误: {e}")
+            raise

         assert isinstance(completion, Message)
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Connection error detection via string inspection on `Exception` is brittle and may miss relevant network failures.

The handler infers connection issues via `"ConnectError" in str(type(e).__name__) or "Connection" in str(e)`, which is brittle and depends on message text. It can both miss other relevant `httpx` network errors (timeouts, DNS, etc.) and misclassify unrelated exceptions whose messages contain “Connection”.

Prefer catching the specific `httpx` (or SDK) network exceptions and using `isinstance` checks (e.g. `httpx.ConnectError`, `httpx.NetworkError`, `httpx.ReadTimeout`) instead of string matching so proxy diagnostics are more accurate and resilient.

Suggested implementation:

```python
        try:
            completion = await self.client.messages.create(
                **payloads, stream=False, extra_body=extra_body
            )
        except httpx.RequestError as e:
            proxy = self.provider_config.get("proxy", "")
            if proxy:
                logger.error(
                    f"[Anthropic] 网络/代理连接失败 ({type(e).__name__})。代理地址: {proxy},错误: {e}"
                )
            else:
                logger.error(
                    f"[Anthropic] 网络连接失败 ({type(e).__name__}),未配置代理。错误: {e}"
                )
            raise

```

To fully apply this change you should also:

1. Ensure `httpx` is imported at the top of `astrbot/core/provider/sources/anthropic_source.py`, e.g.:
   - `import httpx`
2. If your project uses a local wrapper or alias around `httpx`, adjust the caught exception type accordingly (e.g. replace `httpx.RequestError` with the appropriate base network error).
</issue_to_address>

### Comment 3
<location> `astrbot/core/provider/sources/gemini_source.py:122-128` </location>
<code_context>
-        #     f"发生了错误(gemini_source)。Provider 配置如下: {self.provider_config}",
-        # )
+        
+        # 连接错误处理
+        if "ConnectError" in str(type(e).__name__) or "Connection" in str(e.message):
+            proxy = self.provider_config.get("proxy", "")
+            if proxy:
+                logger.error(f"[Gemini] 代理连接失败。代理地址: {proxy},错误: {e}")
+            else:
+                logger.error(f"[Gemini] 连接失败,未配置代理。错误: {e}")
+        
         raise e
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Gemini connection error handling also relies on string matching, which may be unreliable across error types.

This branch currently detects connection failures via string checks on the exception type/name. If the client changes exception names/messages (or localizes them), this may stop recognizing real connection errors. Prefer matching on the concrete exception classes provided by the Gemini/HTTP client, or centralize connection-detection logic so all providers share a single, more robust implementation.

Suggested implementation:

```python
        # 连接错误处理
        # 使用具体异常类型来判断是否为连接类错误,而非依赖异常名称/文案匹配
        is_conn_error = isinstance(e, (TimeoutError, OSError))

        # 某些 HTTP / 客户端库会把底层连接错误包在 __cause__ 中
        cause = getattr(e, "__cause__", None)
        if not is_conn_error and cause is not None:
            is_conn_error = isinstance(cause, (TimeoutError, OSError))

        if is_conn_error:
            proxy = self.provider_config.get("proxy", "")
            if proxy:
                logger.error(f"[Gemini] 代理连接失败。代理地址: {proxy},错误: {e}")
            else:
                logger.error(f"[Gemini] 连接失败,未配置代理。错误: {e}")

        raise e

```

如果项目中已经统一使用某个 HTTP / Gemini 客户端(如 httpx、aiohttp 等),可以进一步改进为:

1. 在公共工具模块中实现一个 `is_connection_error(exc: BaseException) -> bool`,内部基于具体客户端异常类(如 `httpx.ConnectError`, `httpx.ConnectTimeout`, `aiohttp.ClientConnectorError` 等)进行判断,并同时兜底 `TimeoutError` / `OSError`2. 在本文件中改为调用该公共方法,例如:
   `from astrbot.core.utils.errors import is_connection_error`
   然后:
   `if is_connection_error(e): ...`
3. 将其他 provider 中的连接错误判断也统一迁移到该公共方法,避免各处重复且不一致的判断逻辑。
</issue_to_address>

### Comment 4
<location> `astrbot/core/provider/sources/azure_tts_source.py:32-35` </location>
<code_context>
         self.last_sync_time = 0
         self.timeout = Timeout(10.0)
         self.retry_count = 3
+        self.proxy = config.get("proxy", "")
+        if self.proxy:
+            logger.info(f"[Azure TTS OTTS] 使用代理: {self.proxy}")
         self._client: AsyncClient | None = None

</code_context>

<issue_to_address>
**nitpick (typo):** Log tag `[Azure TTS OTTS]` looks inconsistent and may be a typo, which can hinder log filtering.

The `[Azure TTS OTTS]` prefix is inconsistent with the later `[Azure TTS Native]` tag and existing naming patterns. If this isn’t an intentional acronym, please rename it to something like `[Azure TTS]` or `[Azure TTS API]` to keep log filtering consistent across Azure TTS implementations.

```suggestion
        self.proxy = config.get("proxy", "")
        if self.proxy:
            logger.info(f"[Azure TTS] 使用代理: {self.proxy}")
        self._client: AsyncClient | None = None
```
</issue_to_address>

### Comment 5
<location> `astrbot/core/provider/sources/anthropic_source.py:57` </location>
<code_context>

         self.set_model(provider_config.get("model", "unknown"))

+    def _create_http_client(self, provider_config: dict) -> httpx.AsyncClient | None:
+        """创建带代理的 HTTP 客户端"""
+        proxy = provider_config.get("proxy", "")
</code_context>

<issue_to_address>
**issue (complexity):** Consider centralizing proxy-aware HTTP client creation and extracting explicit connection-error logging helpers to streamline the Anthropic provider implementation.

You can keep all the new behavior and still simplify by (1) centralizing HTTP client creation and (2) isolating connection‑error handling.

### 1. Centralize HTTP client creation

Instead of a provider‑local `_create_http_client` that duplicates the OpenAI pattern, move this to a shared helper (e.g. on `Provider`) and call it here.

**On the base `Provider` (or a shared utility):**

```python
# provider.py (or a shared utils module)
import httpx
from astrbot import logger

class Provider:
    # ...

    def _create_http_client(
        self,
        provider_label: str,
        provider_config: dict,
    ) -> httpx.AsyncClient | None:
        proxy = provider_config.get("proxy", "")
        if proxy:
            logger.info(f"[{provider_label}] 使用代理: {proxy}")
            return httpx.AsyncClient(proxy=proxy)
        return None
```

**In the Anthropic provider:**

```python
self.client = AsyncAnthropic(
    api_key=self.chosen_api_key,
    timeout=self.timeout,
    base_url=self.base_url,
    http_client=self._create_http_client("Anthropic", provider_config),
)
```

This keeps your new proxy behavior but avoids per‑provider duplication.

### 2. Use explicit exception types and extract logging

Separate the connection‑error logging from `_query` and use explicit `httpx` exception classes where possible. You can still fall back to a general `Exception` handler if needed.

```python
import httpx

# inside Anthropic provider
def _log_connection_failure(self, error: Exception) -> None:
    proxy = self.provider_config.get("proxy", "")
    if proxy:
        logger.error(f"[Anthropic] 代理连接失败。代理地址: {proxy},错误: {error}")
    else:
        logger.error(f"[Anthropic] 连接失败,未配置代理。错误: {error}")
```

Then simplify `_query`:

```python
try:
    completion = await self.client.messages.create(
        **payloads,
        stream=False,
        extra_body=extra_body,
    )
except (httpx.ConnectError, httpx.ProxyError, httpx.ReadTimeout) as e:
    self._log_connection_failure(e)
    raise
except Exception:
    # keep existing behavior for non-connection errors
    raise
```

This preserves all behavior (proxy support + logging) but removes brittle string inspection and keeps `_query` focused on request/response logic.
</issue_to_address>

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.

@tooplick tooplick force-pushed the master branch 3 times, most recently from 6a39ca9 to 1e43f02 Compare February 7, 2026 12:50
@tooplick
Copy link
Contributor Author

tooplick commented Feb 7, 2026

如果还有没考虑到的地方,请指正

completion = await self.client.messages.create(
**payloads, stream=False, extra_body=extra_body
)
except httpx.RequestError as e:
Copy link
Member

Choose a reason for hiding this comment

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

openai source 我看记录了 os.environ 里面的 proxy 作为provider_config.get("proxy", "")为空时的fallback,但是其他source好像没有。是不是可以考虑把 os.environ 里面的 proxy fallback 放到 log_connection_failure 内。如 proxy 为空的时候,就换成os.environ 里面的 proxy 去输出日志。

Copy link
Contributor Author

Choose a reason for hiding this comment

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

okk我看到了

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Soulter review

@dosubot dosubot bot added the lgtm This PR has been approved by a maintainer label Feb 8, 2026
@Soulter Soulter requested a review from Copilot February 8, 2026 03:58
Copy link
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

为 AstrBot 的各个 Provider Source 增加“按提供商级别”的代理配置(proxy),并在网络/代理连接失败时输出更明确的错误日志;同时在 Dashboard 中补齐该字段的展示与 i18n 文案,改善 Docker 场景下代理与内网通信的兼容性。

Changes:

  • 后端:在 provider 模板与配置元数据中加入 proxy 字段,并为部分 Provider 增加代理注入与连接失败日志能力
  • 新增通用网络工具:统一识别连接类异常并输出包含代理信息的失败日志
  • 前端:在 Provider 源高级配置中暴露 proxy 字段,并添加中英文提示/标签

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
astrbot/core/config/default.py 扩展默认 no_proxy,并为各 Provider 模板与元数据增加 proxy 字段
astrbot/core/utils/network_utils.py 新增连接错误识别、失败日志、代理 http client 创建 helper
astrbot/core/provider/sources/openai_source.py OpenAI 适配器支持 provider-level 代理并改进连接失败日志
astrbot/core/provider/sources/anthropic_source.py Anthropic 适配器支持 provider-level 代理并增强连接失败日志
astrbot/core/provider/sources/gemini_source.py Gemini 适配器支持 provider-level 代理并在连接异常时记录日志
astrbot/core/provider/sources/gemini_embedding_source.py Gemini Embedding 支持代理并输出启用代理日志
astrbot/core/provider/sources/gemini_tts_source.py Gemini TTS 支持代理并输出启用代理日志
astrbot/core/provider/sources/openai_embedding_source.py OpenAI Embedding 支持代理并输出启用代理日志
astrbot/core/provider/sources/openai_tts_api_source.py OpenAI TTS API 支持代理并输出启用代理日志
astrbot/core/provider/sources/fishaudio_tts_api_source.py FishAudio TTS API 请求增加 proxy 透传并输出启用代理日志
astrbot/core/provider/sources/azure_tts_source.py Azure TTS 请求增加 proxy 透传并输出启用代理日志
dashboard/src/composables/useProviderSources.ts proxy 字段补齐 schema 描述/提示的 i18n 映射
dashboard/src/i18n/locales/zh-CN/features/provider.json 增加 proxy 的中文 label/hint 文案
dashboard/src/i18n/locales/en-US/features/provider.json 增加 proxy 的英文 label/hint 文案

@Soulter Soulter merged commit 30d1d55 into AstrBotDevs:master Feb 8, 2026
6 checks passed
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. lgtm This PR has been approved by a maintainer size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants