File size: 2,059 Bytes
5039d41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
"""统一错误响应、上游报文脱敏与错误分类。

目的:杜绝把含 JWT / token / 上游原始响应体的字符串回吐给客户端,
同时为 OpenAI / Claude 两种方言提供规范错误体,并按类驱动 token 冷却。
"""
import re
from fastapi.responses import JSONResponse

# 匹配形如 "upstream API error 492" / "Tabbit API error 401" / "error 502"
_STATUS_RE = re.compile(r"(upstream API error \d{3}|Tabbit API error \d{3}|error \d{3})", re.I)


def safe_upstream_message(e: Exception) -> str:
    """只保留上游状态码语义,丢弃可能含密钥 / JWT / 响应体的原始字符串。"""
    msg = str(e)
    m = _STATUS_RE.search(msg)
    if m:
        return m.group(1)
    low = msg.lower()
    if "session" in low:
        return "failed to create upstream chat session"
    if "timeout" in low or "timed out" in low:
        return "upstream request timed out"
    if "connect" in low or "connection" in low:
        return "failed to connect to upstream"
    return "upstream request failed"


def classify_upstream_error(e: Exception) -> str:
    """错误分类:auth / rate_limit / signature / transient。"""
    msg = str(e)
    m = re.search(r"\b(\d{3})\b", msg)
    status = int(m.group(1)) if m else None
    if status in (401, 403):
        return "auth"
    if status == 429:
        return "rate_limit"
    if status in (492, 493):
        return "signature"
    return "transient"


def openai_error(status: int, message: str, etype: str = "upstream_error", code=None) -> JSONResponse:
    """OpenAI 风格错误体:{"error": {...}}。"""
    return JSONResponse(
        status_code=status,
        content={"error": {"message": message, "type": etype, "code": code, "param": None}},
    )


def claude_error(status: int, message: str, etype: str = "api_error") -> JSONResponse:
    """Anthropic 风格错误体:{"type":"error","error":{...}}。"""
    return JSONResponse(
        status_code=status,
        content={"type": "error", "error": {"type": etype, "message": message}},
    )