Skip to content

Commit 9f08090

Browse files
committed
fix(issue-1223): [bug]-llm-渠道测试将-your-request-was-blocked
1 parent fb6c71a commit 9f08090

5 files changed

Lines changed: 77 additions & 0 deletions

File tree

apps/dsa-web/src/components/settings/LLMChannelEditor.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,7 @@ const LLM_ERROR_LABELS: Record<string, string> = {
711711
model_not_found: '模型不可用',
712712
empty_response: '空响应',
713713
format_error: '格式异常',
714+
request_blocked: '请求被拦截',
714715
network_error: '网络异常',
715716
invalid_config: '配置无效',
716717
unsupported_protocol: '协议暂不支持',
@@ -724,6 +725,7 @@ const LLM_TROUBLESHOOTING_HINTS: Record<string, string> = {
724725
quota: '请检查余额、套餐额度、RPM/TPM 限流或并发设置,必要时稍后重试。',
725726
model_not_found: '请确认模型名与渠道协议匹配,并先用“获取模型”核对该渠道实际可用模型列表。',
726727
empty_response: '渠道已连通但未返回正文;可尝试切换兼容模型、关闭额外响应模式后再测试。',
728+
request_blocked: '服务商或中转网关拒绝了本次请求;请检查账号风控、地域限制、模型权限、网关策略、内容安全策略或请求来源限制。',
727729
network_error: '请检查 Base URL、代理、TLS/证书、中转网关或本地网络策略,并可稍后重试。',
728730
invalid_config: '先补齐协议、Base URL、API Key 和模型配置,再执行一键测试。',
729731
unsupported_protocol: '当前仅对 OpenAI Compatible / DeepSeek 渠道提供自动模型发现,请改为手动维护模型列表。',
@@ -739,6 +741,7 @@ const LLM_REASON_HINTS: Record<string, string> = {
739741
tls_error: 'TLS/证书握手失败;请检查 HTTPS 证书、中转网关或公司代理策略。',
740742
connection_refused: '目标服务拒绝连接;请确认 Base URL 端口、服务进程和防火墙配置。',
741743
model_access_denied: '当前账号无法使用该模型;请确认模型是否已开通、账号是否可见,或模型是否已被禁用。',
744+
provider_blocked: '服务商或中转网关拦截了请求;请检查账号风控、地域限制、模型权限、网关策略、内容安全策略或请求来源限制。',
742745
provider_prefix_mismatch: '模型 provider 前缀与当前渠道不匹配;请确认模型名是否应使用该渠道的 OpenAI-compatible 路由。',
743746
capability_unsupported: '当前模型或兼容层不支持该能力;这不影响基础文本连接,可换模型或关闭该能力依赖。',
744747
};

apps/dsa-web/src/components/settings/__tests__/LLMChannelEditor.test.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -988,6 +988,47 @@ describe('LLMChannelEditor', () => {
988988
}));
989989
});
990990

991+
it('shows provider blocked troubleshooting hints without network wording', async () => {
992+
testLLMChannel.mockResolvedValue({
993+
success: false,
994+
message: 'LLM request was blocked by provider or gateway policy',
995+
error: 'litellm.APIError: APIError: OpenAIException - Your request was blocked.',
996+
errorCode: 'request_blocked',
997+
stage: 'chat_completion',
998+
retryable: false,
999+
details: { reason: 'provider_blocked', model: 'openai/gpt-5.5' },
1000+
resolvedProtocol: 'openai',
1001+
resolvedModel: 'openai/gpt-5.5',
1002+
latencyMs: null,
1003+
});
1004+
1005+
render(
1006+
<LLMChannelEditor
1007+
items={[
1008+
{ key: 'LLM_CHANNELS', value: 'openai' },
1009+
{ key: 'LLM_OPENAI_PROTOCOL', value: 'openai' },
1010+
{ key: 'LLM_OPENAI_BASE_URL', value: 'https://proxy.example.com/v1' },
1011+
{ key: 'LLM_OPENAI_ENABLED', value: 'true' },
1012+
{ key: 'LLM_OPENAI_API_KEY', value: 'secret-key' },
1013+
{ key: 'LLM_OPENAI_MODELS', value: 'gpt-5.5,gpt-4o-mini' },
1014+
]}
1015+
configVersion="v1"
1016+
maskToken="******"
1017+
onSaved={() => {}}
1018+
/>
1019+
);
1020+
1021+
fireEvent.click(screen.getByRole('button', { name: /OpenAI /i }));
1022+
fireEvent.click(screen.getByRole('button', { name: '测试连接' }));
1023+
1024+
expect(await screen.findByText(/ · LLM request was blocked/i)).toBeInTheDocument();
1025+
expect(screen.getByText(/openai\/gpt-5\.5/i)).toBeInTheDocument();
1026+
expect(screen.getByText(/使gpt-5\.5/i)).toBeInTheDocument();
1027+
expect(screen.getByText(//i)).toBeInTheDocument();
1028+
expect(screen.queryByText(/Base URLTLS/i)).not.toBeInTheDocument();
1029+
expect(screen.queryByText(//i)).not.toBeInTheDocument();
1030+
});
1031+
9911032
it('shows focused quota exceeded troubleshooting hints', async () => {
9921033
testLLMChannel.mockResolvedValue({
9931034
success: false,

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
1515
- [改进] 放宽 LiteLLM 依赖约束,保留 `>=1.80.10` 最低版本并显式排除 PyPI 事故版本 `1.82.7` / `1.82.8`,允许安装后续 1.x 修复版本。
1616
- [改进] 补齐通知渠道 P0 基线、Actions 映射与 `--check-notify` 只读诊断,完善 AstrBot 配置入口和通知回归快照。
1717
- [修复] 修正 LLM 渠道测试中 `Model disabled` 被误报为网络异常的问题,并在失败提示中展示本次实际测试模型。
18+
- [修复] 修正 LLM 渠道测试中 `Your request was blocked` 等上游拦截错误被误报为网络异常的问题。
1819
- [chore] 清理仓库根目录:移除误入库的 `.codex``review.md` 跟踪记录,将 smoke 测试入口迁移到 `scripts/`、环境检查脚本迁移为 `scripts/check_env.py`,并将 LiteLLM YAML 示例迁移到 `docs/examples/`
1920
- [新功能] Web 设置页新增通知渠道一键测试,支持临时配置、耗时与脱敏 attempts 展示。
2021

src/services/system_config_service.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2572,6 +2572,13 @@ def _classify_llm_http_error(status_code: int, error_text: str) -> _LLMDiagnosti
25722572
"Configured model could not be found on this channel",
25732573
"model_not_found",
25742574
)
2575+
if SystemConfigService._has_provider_blocked_signal(error_text or ""):
2576+
return _LLMDiagnostic(
2577+
"request_blocked",
2578+
False,
2579+
"LLM request was blocked by provider or gateway policy",
2580+
"provider_blocked",
2581+
)
25752582
if status_code in {401, 403} or any(token in lowered for token in ("unauthorized", "forbidden", "invalid api key", "authentication")):
25762583
return _LLMDiagnostic("auth", False, "LLM authentication failed", "api_key_rejected")
25772584
if status_code == 402 or any(token in lowered for token in ("billing", "balance", "insufficient balance")):
@@ -2656,6 +2663,18 @@ def _has_model_access_denied_signal(text: str) -> bool:
26562663
)
26572664
return any(token in lowered for token in access_denied_tokens)
26582665

2666+
@staticmethod
2667+
def _has_provider_blocked_signal(text: str) -> bool:
2668+
lowered = text.lower()
2669+
blocked_tokens = (
2670+
"your request was blocked",
2671+
"request was blocked",
2672+
"request blocked",
2673+
"blocked by policy",
2674+
"moderation_blocked",
2675+
)
2676+
return any(token in lowered for token in blocked_tokens)
2677+
26592678
@staticmethod
26602679
def _has_provider_prefix_mismatch_signal(text: str) -> bool:
26612680
lowered = text.lower()
@@ -2696,6 +2715,13 @@ def _classify_llm_exception(exc: Exception) -> _LLMDiagnostic:
26962715
"LLM request was rejected by quota or rate limiting",
26972716
"rate_limit",
26982717
)
2718+
if SystemConfigService._has_provider_blocked_signal(str(exc)):
2719+
return _LLMDiagnostic(
2720+
"request_blocked",
2721+
False,
2722+
"LLM request was blocked by provider or gateway policy",
2723+
"provider_blocked",
2724+
)
26992725
if SystemConfigService._has_provider_prefix_mismatch_signal(text):
27002726
return _LLMDiagnostic(
27012727
"model_not_found",

tests/test_system_config_service.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,6 +1411,9 @@ class RateLimitError(Exception):
14111411
(Exception("model gpt-4o is not authorized for this account"), "model_not_found", "model_access_denied"),
14121412
(Exception("litellm.APIError: APIError: OpenAIException - Model disabled."), "model_not_found", "model_access_denied"),
14131413
(Exception("Model is disabled for this account"), "model_not_found", "model_access_denied"),
1414+
(Exception("litellm.APIError: APIError: OpenAIException - Your request was blocked."), "request_blocked", "provider_blocked"),
1415+
(Exception("request was blocked by policy"), "request_blocked", "provider_blocked"),
1416+
(Exception("moderation_blocked"), "request_blocked", "provider_blocked"),
14141417
(Exception("LLM Provider NOT provided for model foo"), "model_not_found", "provider_prefix_mismatch"),
14151418
]
14161419

@@ -1496,6 +1499,8 @@ def test_discover_llm_channel_models_classifies_error_scenarios(self, mock_get)
14961499
quota_exceeded_response.json.return_value = {"error": {"message": "insufficient_quota"}}
14971500
rate_limit_response = Mock(ok=False, status_code=429, text="too many requests")
14981501
rate_limit_response.json.return_value = {"error": {"message": "too many requests"}}
1502+
blocked_response = Mock(ok=False, status_code=403, text="Your request was blocked.")
1503+
blocked_response.json.return_value = {"error": {"message": "Your request was blocked."}}
14991504
invalid_json_response = Mock(ok=True, status_code=200, text="<html>bad gateway</html>")
15001505
invalid_json_response.json.side_effect = ValueError("invalid json")
15011506

@@ -1506,6 +1511,7 @@ def test_discover_llm_channel_models_classifies_error_scenarios(self, mock_get)
15061511
(billing_rate_limit_response, "quota", "model_discovery", True, "insufficient_balance"),
15071512
(quota_exceeded_response, "quota", "model_discovery", True, "quota_exceeded"),
15081513
(rate_limit_response, "quota", "model_discovery", True, "rate_limit"),
1514+
(blocked_response, "request_blocked", "model_discovery", False, "provider_blocked"),
15091515
(invalid_json_response, "format_error", "response_parse", False, "non_json"),
15101516
]:
15111517
with self.subTest(error_code=error_code):

0 commit comments

Comments
 (0)