mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-20 02:55:08 +08:00
fix: 修复了国内配置一些模型不可用问题 (#7685)
* fix: 修复了国内配置一些模型不可用问题 1. 常见的openai和anthropic协议,如 智谱的codingpan https://open.bigmodel.cn/api/coding/paas/v4 2. 新出的一些没有模型列表的自定义模型提供商,如科大讯飞 https://maas-coding-api.cn-huabei-1.xf-yun.com/v2 * feat: 提高代码复用性 * fix(network): reuse shared SSL context * test(network): cover proxy and header forwarding * fix(network): support verify overrides --------- Co-authored-by: Taois <taoist.han@vertechs.com> Co-authored-by: 邹永赫 <1259085392@qq.com>
This commit is contained in:
@@ -18,6 +18,7 @@ from astrbot.core.provider.entities import LLMResponse, TokenUsage
|
||||
from astrbot.core.provider.func_tool_manager import ToolSet
|
||||
from astrbot.core.utils.io import download_image_by_url
|
||||
from astrbot.core.utils.network_utils import (
|
||||
create_proxy_client,
|
||||
is_connection_error,
|
||||
log_connection_failure,
|
||||
)
|
||||
@@ -106,15 +107,13 @@ class ProviderAnthropic(Provider):
|
||||
http_client=self._create_http_client(provider_config),
|
||||
)
|
||||
|
||||
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, headers=self.custom_headers)
|
||||
if self.custom_headers:
|
||||
return httpx.AsyncClient(headers=self.custom_headers)
|
||||
return None
|
||||
def _create_http_client(self, provider_config: dict) -> httpx.AsyncClient:
|
||||
"""创建带代理的 HTTP 客户端,使用系统 SSL 证书"""
|
||||
return create_proxy_client(
|
||||
"Anthropic",
|
||||
provider_config.get("proxy", ""),
|
||||
headers=self.custom_headers,
|
||||
)
|
||||
|
||||
def _apply_thinking_config(self, payloads: dict) -> None:
|
||||
thinking_type = self.thinking_config.get("type", "")
|
||||
|
||||
@@ -438,7 +438,7 @@ class ProviderOpenAIOfficial(Provider):
|
||||
image_fallback_used,
|
||||
)
|
||||
|
||||
def _create_http_client(self, provider_config: dict) -> httpx.AsyncClient | None:
|
||||
def _create_http_client(self, provider_config: dict) -> httpx.AsyncClient:
|
||||
"""创建带代理的 HTTP 客户端"""
|
||||
proxy = provider_config.get("proxy", "")
|
||||
return create_proxy_client("OpenAI", proxy)
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
"""Network error handling utilities for providers."""
|
||||
|
||||
import ssl
|
||||
|
||||
import httpx
|
||||
|
||||
from astrbot import logger
|
||||
|
||||
_SYSTEM_SSL_CTX = ssl.create_default_context()
|
||||
|
||||
|
||||
def is_connection_error(exc: BaseException) -> bool:
|
||||
"""Check if an exception is a connection/network related error.
|
||||
@@ -83,20 +87,30 @@ def log_connection_failure(
|
||||
def create_proxy_client(
|
||||
provider_label: str,
|
||||
proxy: str | None = None,
|
||||
) -> httpx.AsyncClient | None:
|
||||
headers: dict[str, str] | None = None,
|
||||
verify: ssl.SSLContext | str | bool | None = None,
|
||||
) -> httpx.AsyncClient:
|
||||
"""Create an httpx AsyncClient with proxy configuration if provided.
|
||||
|
||||
Uses the system SSL certificate store instead of certifi, which avoids
|
||||
SSL verification failures for endpoints whose CA chain is not in certifi
|
||||
but is trusted by the operating system.
|
||||
|
||||
Note: The caller is responsible for closing the client when done.
|
||||
Consider using the client as a context manager or calling aclose() explicitly.
|
||||
|
||||
Args:
|
||||
provider_label: The provider name for log prefix (e.g., "OpenAI", "Gemini")
|
||||
proxy: The proxy address (e.g., "http://127.0.0.1:7890"), or None/empty
|
||||
headers: Optional custom headers to include in every request
|
||||
verify: Optional override for TLS verification. Defaults to the shared
|
||||
system SSL context when not provided.
|
||||
|
||||
Returns:
|
||||
An httpx.AsyncClient configured with the proxy, or None if no proxy
|
||||
An httpx.AsyncClient created with the shared system SSL context; the proxy is applied only if one is provided.
|
||||
"""
|
||||
resolved_verify = _SYSTEM_SSL_CTX if verify is None else verify
|
||||
if proxy:
|
||||
logger.info(f"[{provider_label}] 使用代理: {proxy}")
|
||||
return httpx.AsyncClient(proxy=proxy)
|
||||
return None
|
||||
return httpx.AsyncClient(proxy=proxy, verify=resolved_verify, headers=headers)
|
||||
return httpx.AsyncClient(verify=resolved_verify, headers=headers)
|
||||
|
||||
51
tests/unit/test_network_utils.py
Normal file
51
tests/unit/test_network_utils.py
Normal file
@@ -0,0 +1,51 @@
|
||||
import ssl
|
||||
|
||||
import pytest
|
||||
|
||||
from astrbot.core.utils import network_utils
|
||||
|
||||
|
||||
def test_create_proxy_client_reuses_shared_ssl_context(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
):
|
||||
captured_calls: list[dict] = []
|
||||
headers = {"X-Test-Header": "value"}
|
||||
|
||||
class _FakeAsyncClient:
|
||||
def __init__(self, **kwargs):
|
||||
captured_calls.append(kwargs)
|
||||
|
||||
monkeypatch.setattr(network_utils.httpx, "AsyncClient", _FakeAsyncClient)
|
||||
|
||||
network_utils.create_proxy_client("OpenAI")
|
||||
network_utils.create_proxy_client("OpenAI", proxy="http://127.0.0.1:7890")
|
||||
network_utils.create_proxy_client("OpenAI", headers=headers)
|
||||
network_utils.create_proxy_client("OpenAI", proxy="")
|
||||
|
||||
assert len(captured_calls) == 4
|
||||
assert "proxy" not in captured_calls[0]
|
||||
assert captured_calls[1]["proxy"] == "http://127.0.0.1:7890"
|
||||
assert captured_calls[2]["headers"] is headers
|
||||
assert "proxy" not in captured_calls[3]
|
||||
assert isinstance(captured_calls[0]["verify"], ssl.SSLContext)
|
||||
assert captured_calls[0]["verify"] is captured_calls[1]["verify"]
|
||||
assert captured_calls[1]["verify"] is captured_calls[2]["verify"]
|
||||
assert captured_calls[2]["verify"] is captured_calls[3]["verify"]
|
||||
|
||||
|
||||
def test_create_proxy_client_allows_verify_override(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
):
|
||||
captured_calls: list[dict] = []
|
||||
custom_verify = ssl.create_default_context()
|
||||
|
||||
class _FakeAsyncClient:
|
||||
def __init__(self, **kwargs):
|
||||
captured_calls.append(kwargs)
|
||||
|
||||
monkeypatch.setattr(network_utils.httpx, "AsyncClient", _FakeAsyncClient)
|
||||
|
||||
network_utils.create_proxy_client("OpenAI", verify=custom_verify)
|
||||
|
||||
assert len(captured_calls) == 1
|
||||
assert captured_calls[0]["verify"] is custom_verify
|
||||
Reference in New Issue
Block a user