Files
sanguo_vnpy_v2/tests/api/test_llm_probe.py
T
claude_dev 85d14a1e56
CI/CD / test (push) Successful in 41s
CI/CD / nas-deploy (push) Successful in 31s
CI/CD / nas-verify (push) Successful in 8s
fix(api): LLM client 补 anthropic 协议+glm_cn 改指 coding plan 接入点——修生产「AI 生成草稿」429 1113 [vps]
根因(VPS 实测): glm_cn 默认 base_url=paas/v4 是按量计费端点,coding plan
的 key 打它=HTTP 429 code 1113 余额不足;同一 key 在 coding plan 接入点
/api/anthropic/v1/messages 实测 200(glm-5.2 与 glm-5.3-flash 均通)。
修复: ProviderSpec 增 protocol 字段(openai|anthropic,env SANGUO_LLM_PROTOCOL
可覆盖), glm_cn 改指 anthropic 端点; client/probe 共用 chat_endpoint() 按协议
选 URL+鉴权头; anthropic 差异面=system 提顶层/无 response_format/content 块
只取 text(glm-5.x thinking 块跳过)/usage input→prompt 归一(漏斗计量不漂移)。
草稿/分解/测试连接/漏斗 lane 四消费方同 client 一处修复全生效。
venv310 实测 82 绿(含 anthropic 协议 8 新测); 档随码走=总设计 §13 P5.1 补
端点勘误节。
2026-10-08 23:07:20 +08:00

67 lines
2.4 KiB
Python

# tests/api/test_llm_probe.py
"""probe(P5.1):单发最小 chat 探测,无重试无退避;MockTransport 零真调用."""
import asyncio
import httpx
from sanguo_api.llm.probe import probe_connection
from sanguo_api.llm.provider import LLMConfig
CFG = LLMConfig("glm_cn", "http://x/v4/", "m", "sk-test", timeout=5.0)
def _run(transport):
return asyncio.run(probe_connection(CFG, transport=transport))
def test_ok_with_latency():
t = httpx.MockTransport(
lambda req: httpx.Response(200, json={"choices": []}))
r = _run(t)
assert r["ok"] is True and r["error"] == ""
assert 0 <= r["latency_ms"] < 5000
def test_http_error_carries_status():
t = httpx.MockTransport(lambda req: httpx.Response(401, text="bad key"))
r = _run(t)
assert r["ok"] is False and "401" in r["error"]
def test_transport_error():
def boom(req):
raise httpx.ConnectError("refused")
r = _run(httpx.MockTransport(boom))
assert r["ok"] is False and "refused" in r["error"]
def test_malformed_base_url_no_raise():
"""畸形 base_url 也不 raise——设置页填错 URL 点测试连接是核心 UX.
'://bad'=UnsupportedProtocol(TransportError 系,本就被捕);
'http://[::1'=InvalidURL(Exception 直系,不在 TransportError 树内,
曾击穿「任何失败都不 raise」承诺 500,放宽 HTTPError 后归拢)."""
for url in ("://bad", "http://[::1"):
bad = LLMConfig("glm_cn", url, "m", "sk-test", timeout=5.0)
r = asyncio.run(probe_connection(bad))
assert r["ok"] is False and r["error"]
def test_anthropic_protocol_probes_messages_url():
"""10-08 coding plan:protocol=anthropic 探针打 /v1/messages+x-api-key."""
import json as j
seen = {}
def h(req):
seen["url"] = str(req.url)
seen["key"] = req.headers.get("x-api-key")
seen["body"] = j.loads(req.content)
return httpx.Response(200, json={"content": [{"type": "text",
"text": "pong"}]})
cfg = LLMConfig("glm_cn", "http://x/api/anthropic", "m", "sk-test",
timeout=5.0, protocol="anthropic")
r = asyncio.run(probe_connection(cfg, transport=httpx.MockTransport(h)))
assert r["ok"] is True
assert seen["url"] == "http://x/api/anthropic/v1/messages"
assert seen["key"] == "sk-test"
assert "response_format" not in seen["body"]