feat(api): DeepSeek V4 Flash 默认、HTTP 错讯与多供应商分层

- 主链路默认 deepseek-v4-flash,DEEPSEEK_THINKING_ENABLED 对齐旧非思考 chat
- 评测台评审装配迁入 adapters/llm(deepseek_eval_judge、zhipu_eval_judge)与 eval_judge_spec
- 拆分 llm_http_openai_chat_errors 与 llm_errors(DeepSeek/智谱品牌与文档链),llm_call 支持 http_error_vendor
- EvalJudgeService 按 spec.provider 传入 allm_json_call;评测台前端文案改为 V4 Flash
- 更新 .env 示例与 staging/production 的 DEEPSEEK_MODEL;补充 openai/供应商错讯测试

Made-with: Cursor
This commit is contained in:
Kevin
2026-04-27 14:34:30 +08:00
parent 3121d1384d
commit 80833f7033
25 changed files with 521 additions and 81 deletions

View File

@@ -51,7 +51,8 @@ def test_build_eval_judge_deepseek_context_budget(
spec = build_eval_judge_llm_spec("deepseek", None)
assert spec is not None
assert spec.provider == "deepseek"
assert spec.resolved_model == "deepseek-reasoner"
# 旧名 deepseek-reasoner 规范为 v4-flash 思考模式
assert spec.resolved_model == "deepseek-v4-flash"
assert spec.context_window_tokens == 64_000
n = eval_judge_conversation_transcript_max_chars_for_context(64_000)
glm_n = eval_judge_conversation_transcript_max_chars_for_context(200_000)

View File

@@ -2,8 +2,10 @@
from __future__ import annotations
import httpx
import pytest
from langchain_core.messages import AIMessage
from openai import APIStatusError
from pydantic import BaseModel, Field
from app.core.langchain_llm import ensure_json_object_prompt_has_json_keyword
@@ -128,6 +130,41 @@ def test_llm_json_call_no_fallback_raises() -> None:
assert ei.value.kind == "validation"
def _api_status_402() -> APIStatusError:
req = httpx.Request("POST", "https://api.deepseek.com/v1/chat/completions")
resp = httpx.Response(
402, request=req, json={"error": {"message": "Insufficient balance"}}
)
return APIStatusError("Payment required", response=resp, body=resp.json())
class _LlmInvokeRaises:
def bind(self, **_kwargs: object):
return self
def invoke(self, _prompt: str) -> object:
raise _api_status_402()
async def ainvoke(self, _prompt: str) -> object:
return self.invoke(_prompt)
def test_llm_json_call_openai_status_maps_to_friendly_chinese() -> None:
with pytest.raises(LLMCallError) as ei:
llm_json_call(
_LlmInvokeRaises(),
"p",
_SmallOut,
max_tokens=8,
agent="t",
)
assert ei.value.kind == "invoke"
s = str(ei.value)
assert "402" in s
assert "余额" in s
assert "DeepSeek" in s
@pytest.mark.asyncio
async def test_allm_json_call_parity_with_sync() -> None:
llm = _SyncFakeLlm(['{"answer": "async", "score": 7}'])

View File

@@ -0,0 +1,36 @@
"""供应商层 HTTP 错讯DeepSeek / 智谱 品牌与文档链。"""
import httpx
from openai import APIStatusError
from app.core.llm_errors import format_llm_http_error_message
def _status_402() -> APIStatusError:
req = httpx.Request("POST", "https://x/v1/chat/completions")
resp = httpx.Response(402, request=req, json={"error": {"message": "x"}})
return APIStatusError("u", response=resp, body=resp.json())
def test_vendor_deepseek_includes_brand_and_doc() -> None:
e = _status_402()
m = format_llm_http_error_message(e, "deepseek")
assert m is not None
assert "DeepSeek" in m
assert "api-docs.deepseek.com" in m
def test_vendor_zhipu_includes_brand_and_doc() -> None:
e = _status_402()
m = format_llm_http_error_message(e, "zhipu")
assert m is not None
assert "智谱" in m
assert "bigmodel" in m
def test_vendor_none_is_transport_only() -> None:
e = _status_402()
m = format_llm_http_error_message(e, None)
assert m is not None
assert "" not in m
assert "402" in m

View File

@@ -0,0 +1,68 @@
"""传输层 `llm_http_openai_chat_errors` 的中性错讯;兼容 re-export 仍经 openai_compatible_errors。"""
import httpx
import pytest
from openai import APIStatusError
from app.core.openai_compatible_errors import (
extract_openai_http_status,
format_openai_compatible_http_error_message,
should_log_openai_error_as_warning,
)
def _status_error(status: int, *, body: object | None = None) -> APIStatusError:
req = httpx.Request("POST", "https://api.deepseek.com/v1/chat/completions")
resp = httpx.Response(status, request=req, json=body if body is not None else {})
return APIStatusError("upstream", response=resp, body=body)
def test_extract_status_from_api_status_error() -> None:
e = _status_error(429)
assert extract_openai_http_status(e) == 429
def test_format_402_balance_chinese_message() -> None:
e = _status_error(
402,
body={"error": {"message": "Insufficient balance", "type": "insufficient_quota"}},
)
msg = format_openai_compatible_http_error_message(e)
assert msg is not None
assert "402" in msg
assert "余额" in msg
def test_format_401_and_warning_flag() -> None:
e = _status_error(401, body={"error": {"message": "invalid api key"}})
assert should_log_openai_error_as_warning(e) is True
m = format_openai_compatible_http_error_message(e)
assert m is not None
assert "401" in m
assert "密钥" in m
def test_format_503_server_busy() -> None:
e = _status_error(503)
m = format_openai_compatible_http_error_message(e)
assert m is not None
assert "503" in m
assert should_log_openai_error_as_warning(e) is False
def test_format_httpx_http_status_error() -> None:
req = httpx.Request("GET", "https://api.deepseek.com/v1/models")
resp = httpx.Response(429, request=req)
try:
resp.raise_for_status()
except httpx.HTTPStatusError as e:
m = format_openai_compatible_http_error_message(e)
assert m is not None
assert "429" in m
def test_unknown_status_418() -> None:
e = _status_error(418)
m = format_openai_compatible_http_error_message(e)
assert m is not None
assert "418" in m