Skip to content

fix: 修复了国内配置一些模型不可用问题#7685

Merged
zouyonghe merged 7 commits intoAstrBotDevs:masterfrom
hjdhnx:master
Apr 21, 2026
Merged

fix: 修复了国内配置一些模型不可用问题#7685
zouyonghe merged 7 commits intoAstrBotDevs:masterfrom
hjdhnx:master

Conversation

@hjdhnx
Copy link
Copy Markdown
Contributor

@hjdhnx hjdhnx commented Apr 20, 2026

  1. 常见的openai和anthropic协议,如 智谱的codingpan https://open.bigmodel.cn/api/coding/paas/v4
  2. 新出的一些没有模型列表的自定义模型提供商,如科大讯飞 https://maas-coding-api.cn-huabei-1.xf-yun.com/v2

Modifications / 改动点

  • This is NOT a breaking change. / 这不是一个破坏性变更。

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

image

Checklist / 检查清单

  • 😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
    / 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。

  • 👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
    / 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”

  • 🤓 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.
    / 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到 requirements.txtpyproject.toml 文件相应位置。

  • 😮 My changes do not introduce malicious code.
    / 我的更改没有引入恶意代码。

Summary by Sourcery

Use a shared system SSL context for HTTP clients to avoid TLS verification issues with certain model providers while standardizing proxy-aware client creation.

Bug Fixes:

  • Resolve SSL verification failures when connecting to some domestic/OpenAI-compatible model endpoints by using the operating system certificate store instead of certifi.

Enhancements:

  • Unify HTTP client construction for OpenAI- and Anthropic-compatible providers through a common proxy-aware helper that supports custom headers and optional TLS verification overrides.

Tests:

  • Add unit tests for create_proxy_client to verify shared SSL context reuse, proxy/header handling, and support for explicit verify overrides.

1. 常见的openai和anthropic协议,如 智谱的codingpan
https://open.bigmodel.cn/api/coding/paas/v4
2. 新出的一些没有模型列表的自定义模型提供商,如科大讯飞
https://maas-coding-api.cn-huabei-1.xf-yun.com/v2
@auto-assign auto-assign bot requested review from Soulter and anka-afk April 20, 2026 05:51
@dosubot dosubot bot added size:S This PR changes 10-29 lines, ignoring generated files. area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. labels Apr 20, 2026
Copy link
Copy Markdown
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 - I've found 1 issue, and left some high level feedback:

  • Both anthropic_source._create_http_client and create_proxy_client now always return an AsyncClient instead of None; please double-check all call sites that previously handled a None return to avoid behavioral changes or leaked unused clients.
  • You are creating a new ssl.create_default_context() in each helper; consider centralizing or reusing a shared SSL context (or providing a small utility) so the behavior is consistent across providers and avoids repeated context construction.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Both `anthropic_source._create_http_client` and `create_proxy_client` now always return an `AsyncClient` instead of `None`; please double-check all call sites that previously handled a `None` return to avoid behavioral changes or leaked unused clients.
- You are creating a new `ssl.create_default_context()` in each helper; consider centralizing or reusing a shared SSL context (or providing a small utility) so the behavior is consistent across providers and avoids repeated context construction.

## Individual Comments

### Comment 1
<location path="astrbot/core/utils/network_utils.py" line_range="103" />
<code_context>

     Returns:
-        An httpx.AsyncClient configured with the proxy, or None if no proxy
+        An httpx.AsyncClient configured with the proxy and system SSL context
     """
+    ctx = ssl.create_default_context()
</code_context>
<issue_to_address>
**nitpick:** Return description is slightly misleading when no proxy is provided.

The function now always returns a client, even when `proxy` is `None`, but the phrase "configured with the proxy" suggests a proxy is always present. Please reword to reflect that the client is always created with the system SSL context and only applies the proxy when one is provided.
</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.

Comment thread astrbot/core/utils/network_utils.py Outdated
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request updates the HTTP client configuration to use the system SSL certificate store via ssl.create_default_context(), ensuring better compatibility with system-trusted CA chains. These changes are implemented in both the Anthropic provider and the shared network utilities. Feedback suggests refactoring the Anthropic provider to utilize the create_proxy_client utility from network_utils.py to eliminate redundant logic and maintain consistency across providers.

Comment on lines 1 to 5
import base64
import json
import ssl
from collections.abc import AsyncGenerator
from typing import Literal
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

既然逻辑已经迁移到 network_utils.py,这里可以移除不再需要的 ssl 导入,并引入 create_proxy_client

Suggested change
import base64
import json
import ssl
from collections.abc import AsyncGenerator
from typing import Literal
import base64
import json
from astrbot.core.utils.network_utils import create_proxy_client
from collections.abc import AsyncGenerator
from typing import Literal

Comment on lines +110 to +119
def _create_http_client(self, provider_config: dict) -> httpx.AsyncClient:
"""创建带代理的 HTTP 客户端,使用系统 SSL 证书"""
ctx = ssl.create_default_context()
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
return httpx.AsyncClient(
proxy=proxy, headers=self.custom_headers, verify=ctx
)
return httpx.AsyncClient(headers=self.custom_headers, verify=ctx)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

建议使用 network_utils.py 中的 create_proxy_client 辅助函数来替代重复的逻辑。这不仅能减少代码重复,还能确保所有提供商在使用代理和 SSL 配置时保持一致。

    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,
        )
References
  1. 当为不同情况实现类似功能时,应将逻辑重构为共享的辅助函数,以避免代码重复。

@hjdhnx
Copy link
Copy Markdown
Contributor Author

hjdhnx commented Apr 20, 2026

@Soulter #7636

@dosubot dosubot bot added size:M This PR changes 30-99 lines, ignoring generated files. and removed size:S This PR changes 10-29 lines, ignoring generated files. labels Apr 20, 2026
@hjdhnx
Copy link
Copy Markdown
Contributor Author

hjdhnx commented Apr 20, 2026

@Soulter 求大佬合并下代码,很重要,我没模型用啦

@zouyonghe
Copy link
Copy Markdown
Member

@sourcery-ai review

Copy link
Copy Markdown
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 - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="astrbot/core/utils/network_utils.py" line_range="107" />
<code_context>
-        An httpx.AsyncClient configured with the proxy, or None if no proxy
+        An httpx.AsyncClient created with the system SSL context; the proxy is applied only if one is provided.
     """
+    ctx = ssl.create_default_context()
     if proxy:
         logger.info(f"[{provider_label}] 使用代理: {proxy}")
</code_context>
<issue_to_address>
**suggestion (performance):** Consider reusing a shared SSLContext instead of creating a new one per client

Creating a new `ssl.SSLContext` on each call adds unnecessary overhead if this helper is used often. Consider defining a module-level context (e.g., `_SYSTEM_SSL_CTX = ssl.create_default_context()`) and reusing it for each client, while still relying on the system certificate store.

Suggested implementation:

```python
    Returns:
        An httpx.AsyncClient created with the shared system SSL context; the proxy is applied only if one is provided.
    """
    if proxy:
        logger.info(f"[{provider_label}] 使用代理: {proxy}")
        return httpx.AsyncClient(proxy=proxy, verify=_SYSTEM_SSL_CTX, headers=headers)
    return httpx.AsyncClient(verify=_SYSTEM_SSL_CTX, headers=headers)

```

` since the import section is not visible).

Here are the changes:

<file_operations>
<file_operation operation="edit" file_path="astrbot/core/utils/network_utils.py">
<<<<<<< SEARCH
    Returns:
        An httpx.AsyncClient created with the system SSL context; the proxy is applied only if one is provided.
    """
    ctx = ssl.create_default_context()
    if proxy:
        logger.info(f"[{provider_label}] 使用代理: {proxy}")
        return httpx.AsyncClient(proxy=proxy, verify=ctx, headers=headers)
    return httpx.AsyncClient(verify=ctx, headers=headers)
=======
    Returns:
        An httpx.AsyncClient created with the shared system SSL context; the proxy is applied only if one is provided.
    """
    if proxy:
        logger.info(f"[{provider_label}] 使用代理: {proxy}")
        return httpx.AsyncClient(proxy=proxy, verify=_SYSTEM_SSL_CTX, headers=headers)
    return httpx.AsyncClient(verify=_SYSTEM_SSL_CTX, headers=headers)
>>>>>>> REPLACE
</file_operation>
</file_operations>

<additional_changes>
1. At the top of `astrbot/core/utils/network_utils.py`, after the existing `import ssl` (or adding it if missing), define a shared SSL context:

   ```python
   import ssl

   _SYSTEM_SSL_CTX = ssl.create_default_context()
   ```

2. Ensure `_SYSTEM_SSL_CTX` is defined at module scope (not inside any function) so it is created once when the module is imported and then reused by this helper.
</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.

Comment thread astrbot/core/utils/network_utils.py Outdated
@zouyonghe
Copy link
Copy Markdown
Member

@sourcery-ai review

Copy link
Copy Markdown
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 - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="tests/unit/test_network_utils.py" line_range="8-17" />
<code_context>
+from astrbot.core.utils import network_utils
+
+
+def test_create_proxy_client_reuses_shared_ssl_context(
+    monkeypatch: pytest.MonkeyPatch,
+):
+    captured_calls: list[dict] = []
+
+    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")
+
+    assert len(captured_calls) == 2
+    assert isinstance(captured_calls[0]["verify"], ssl.SSLContext)
+    assert captured_calls[0]["verify"] is captured_calls[1]["verify"]
</code_context>
<issue_to_address>
**suggestion (testing):** Add assertions to verify that `proxy` and `headers` arguments are correctly forwarded to `httpx.AsyncClient`.

This test covers SSL context reuse but doesn’t yet validate that `headers` and `proxy` are forwarded correctly. Please extend it (or add a new test) to assert that:

- When `proxy` is omitted, `AsyncClient` is called without a `proxy` kwarg.
- When `proxy` is provided, `AsyncClient` receives the expected `proxy` value.
- When `headers` is provided to `create_proxy_client`, it is forwarded as the `headers` kwarg.

You can keep using `_FakeAsyncClient` and check `captured_calls[i]["proxy"]` and `captured_calls[i]["headers"]`.
</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.

Comment on lines +8 to +17
def test_create_proxy_client_reuses_shared_ssl_context(
monkeypatch: pytest.MonkeyPatch,
):
captured_calls: list[dict] = []

class _FakeAsyncClient:
def __init__(self, **kwargs):
captured_calls.append(kwargs)

monkeypatch.setattr(network_utils.httpx, "AsyncClient", _FakeAsyncClient)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (testing): Add assertions to verify that proxy and headers arguments are correctly forwarded to httpx.AsyncClient.

This test covers SSL context reuse but doesn’t yet validate that headers and proxy are forwarded correctly. Please extend it (or add a new test) to assert that:

  • When proxy is omitted, AsyncClient is called without a proxy kwarg.
  • When proxy is provided, AsyncClient receives the expected proxy value.
  • When headers is provided to create_proxy_client, it is forwarded as the headers kwarg.

You can keep using _FakeAsyncClient and check captured_calls[i]["proxy"] and captured_calls[i]["headers"].

@zouyonghe
Copy link
Copy Markdown
Member

@sourcery-ai review

Copy link
Copy Markdown
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 - I've found 1 issue, and left some high level feedback:

  • Since create_proxy_client now always returns an httpx.AsyncClient instead of None when no proxy is provided, double-check all call sites that previously handled a None return to ensure they no longer rely on the optional behavior.
  • Consider allowing create_proxy_client to optionally accept a custom ssl.SSLContext or verify parameter so callers that need a non-system CA bundle (e.g., on-prem/self-signed environments) can override the shared system context when necessary.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Since `create_proxy_client` now always returns an `httpx.AsyncClient` instead of `None` when no proxy is provided, double-check all call sites that previously handled a `None` return to ensure they no longer rely on the optional behavior.
- Consider allowing `create_proxy_client` to optionally accept a custom `ssl.SSLContext` or `verify` parameter so callers that need a non-system CA bundle (e.g., on-prem/self-signed environments) can override the shared system context when necessary.

## Individual Comments

### Comment 1
<location path="tests/unit/test_network_utils.py" line_range="20-22" />
<code_context>
+
+    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)
+
+    assert len(captured_calls) == 3
</code_context>
<issue_to_address>
**suggestion (testing):** Add an explicit test for an empty proxy string to match how `provider_config.get("proxy", "")` is used.

Since `create_proxy_client` is frequently called with `provider_config.get("proxy", "")`, it can receive an empty string, which the helper treats as "no proxy". Please add a case like `create_proxy_client("OpenAI", proxy="")` and assert it matches the no-proxy behavior (no `proxy` in kwargs, same `verify` handling) so tests cover this usage explicitly.
</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.

Comment on lines +20 to +22
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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (testing): Add an explicit test for an empty proxy string to match how provider_config.get("proxy", "") is used.

Since create_proxy_client is frequently called with provider_config.get("proxy", ""), it can receive an empty string, which the helper treats as "no proxy". Please add a case like create_proxy_client("OpenAI", proxy="") and assert it matches the no-proxy behavior (no proxy in kwargs, same verify handling) so tests cover this usage explicitly.

@hjdhnx
Copy link
Copy Markdown
Contributor Author

hjdhnx commented Apr 21, 2026

@zouyonghe 大佬为啥不直接合并我的代码呀,我目前只能这样改了后本地编译使用了,官方版这个坑不修复一堆模型都不能用

@zouyonghe
Copy link
Copy Markdown
Member

@sourcery-ai review

@SourceryAI
Copy link
Copy Markdown

Hi @zouyonghe! 👋

Only authors and team members can run @sourcery-ai commands on public repos.

If you are a team member, install the @sourcery-ai bot to get access ✨

@zouyonghe
Copy link
Copy Markdown
Member

先review一下,问题应该不大

Copy link
Copy Markdown
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 - I've left some high level feedback:

  • Now that create_proxy_client centralizes client construction, consider allowing additional httpx.AsyncClient options (e.g., timeouts, HTTP/2, limits) to be passed through so other providers don’t need to bypass this helper for more advanced configurations.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Now that `create_proxy_client` centralizes client construction, consider allowing additional `httpx.AsyncClient` options (e.g., timeouts, HTTP/2, limits) to be passed through so other providers don’t need to bypass this helper for more advanced configurations.

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.

@zouyonghe zouyonghe merged commit 08392c9 into AstrBotDevs:master Apr 21, 2026
21 checks passed
@hjdhnx
Copy link
Copy Markdown
Contributor Author

hjdhnx commented Apr 21, 2026

先review一下,问题应该不大

感谢感谢

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. size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants