merge: remove unused header, retry, and Statsig utility files

This commit is contained in:
Chenyme committed 2026-02-11 11:32:54 +08:00
1 parent a864751543
commit 90fc016807
4 files changed
+13 -348

No files matched your search

-31
View File
@@ -1,31 +0,0 @@
"""
Common header helpers for Grok services.
"""
from __future__ import annotations
import uuid
from typing import Dict
from app.core.config import get_config
from app.services.grok.utils.statsig import StatsigService
def _normalize_token(token: str) -> str:
return token[4:] if token.startswith("sso=") else token
def build_sso_cookie(token: str, include_rw: bool = False) -> str:
token = _normalize_token(token)
cf = get_config("security.cf_clearance")
cookie = f"sso={token}"
if include_rw:
cookie = f"{cookie}; sso-rw={token}"
if cf:
cookie = f"{cookie};cf_clearance={cf}"
return cookie
def apply_statsig(headers: Dict[str, str]) -> None:
headers["x-statsig-id"] = StatsigService.gen_id()
headers["x-xai-request-id"] = str(uuid.uuid4())
-264
View File
@@ -1,264 +0,0 @@
"""
Grok API 重试工具
提供可配置的重试机制,支持:
- 指数退避 + decorrelated jitter
- Retry-After header 支持
- 429 专用退避策略
- 重试预算控制
"""
import asyncio
import random
from typing import Callable, Any, Optional
from functools import wraps
from app.core.logger import logger
from app.core.config import get_config
from app.core.exceptions import UpstreamException
class RetryContext:
"""重试上下文"""
def __init__(self):
self.attempt = 0
self.max_retry = int(get_config("retry.max_retry"))
self.retry_codes = get_config("retry.retry_status_codes")
self.last_error = None
self.last_status = None
self.total_delay = 0.0
self.retry_budget = float(get_config("retry.retry_budget"))
# 退避参数
self.backoff_base = float(get_config("retry.retry_backoff_base"))
self.backoff_factor = float(get_config("retry.retry_backoff_factor"))
self.backoff_max = float(get_config("retry.retry_backoff_max"))
# decorrelated jitter 状态
self._last_delay = self.backoff_base
def should_retry(self, status_code: int) -> bool:
"""判断是否重试"""
if self.attempt >= self.max_retry:
return False
if status_code not in self.retry_codes:
return False
if self.total_delay >= self.retry_budget:
return False
return True
def record_error(self, status_code: int, error: Exception):
"""记录错误信息"""
self.last_status = status_code
self.last_error = error
self.attempt += 1
def calculate_delay(
self, status_code: int, retry_after: Optional[float] = None
) -> float:
"""
计算退避延迟时间
Args:
status_code: HTTP 状态码
retry_after: Retry-After header 值(秒)
Returns:
延迟时间(秒)
"""
# 优先使用 Retry-After
if retry_after is not None and retry_after > 0:
delay = min(retry_after, self.backoff_max)
self._last_delay = delay
return delay
# 429 使用 decorrelated jitter
if status_code == 429:
# decorrelated jitter: delay = random(base, last_delay * 3)
delay = random.uniform(self.backoff_base, self._last_delay * 3)
delay = min(delay, self.backoff_max)
self._last_delay = delay
return delay
# 其他状态码使用指数退避 + full jitter
exp_delay = self.backoff_base * (self.backoff_factor**self.attempt)
delay = random.uniform(0, min(exp_delay, self.backoff_max))
return delay
def record_delay(self, delay: float):
"""记录延迟时间"""
self.total_delay += delay
def extract_retry_after(error: Exception) -> Optional[float]:
"""
从异常中提取 Retry-After 值
Args:
error: 异常对象
Returns:
Retry-After 秒数,或 None
"""
if not isinstance(error, UpstreamException):
return None
details = error.details or {}
# 尝试从 details 中获取
retry_after = details.get("retry_after")
if retry_after is not None:
try:
return float(retry_after)
except (ValueError, TypeError):
pass
# 尝试从 headers 中获取
headers = details.get("headers", {})
if isinstance(headers, dict):
retry_after = headers.get("Retry-After") or headers.get("retry-after")
if retry_after is not None:
try:
return float(retry_after)
except (ValueError, TypeError):
pass
return None
async def retry_on_status(
func: Callable,
*args,
extract_status: Callable[[Exception], Optional[int]] = None,
on_retry: Callable[[int, int, Exception, float], None] = None,
**kwargs,
) -> Any:
"""
通用重试函数
Args:
func: 重试的异步函数
*args: 函数参数
extract_status: 异常提取状态码的函数
on_retry: 重试时的回调函数 (attempt, status_code, error, delay)
**kwargs: 函数关键字参数
Returns:
函数执行结果
Raises:
最后一次失败的异常
"""
ctx = RetryContext()
# 状态码提取器
if extract_status is None:
def extract_status(e: Exception) -> Optional[int]:
if isinstance(e, UpstreamException):
# 优先从 details 获取,回退到 status_code 属性
if e.details and "status" in e.details:
return e.details["status"]
return getattr(e, "status_code", None)
return None
while ctx.attempt <= ctx.max_retry:
try:
result = await func(*args, **kwargs)
# 记录日志
if ctx.attempt > 0:
logger.info(
f"Retry succeeded after {ctx.attempt} attempts, "
f"total delay: {ctx.total_delay:.2f}s"
)
return result
except Exception as e:
# 提取状态码
status_code = extract_status(e)
if status_code is None:
# 错误无法识别
logger.error(f"Non-retryable error: {e}")
raise
# 记录错误
ctx.record_error(status_code, e)
# 判断是否重试
if ctx.should_retry(status_code):
# 提取 Retry-After
retry_after = extract_retry_after(e)
# 计算延迟
delay = ctx.calculate_delay(status_code, retry_after)
# 检查是否超出预算
if ctx.total_delay + delay > ctx.retry_budget:
logger.warning(
f"Retry budget exhausted: {ctx.total_delay:.2f}s + {delay:.2f}s > {ctx.retry_budget}s"
)
raise
ctx.record_delay(delay)
logger.warning(
f"Retry {ctx.attempt}/{ctx.max_retry} for status {status_code}, "
f"waiting {delay:.2f}s (total: {ctx.total_delay:.2f}s)"
+ (f", Retry-After: {retry_after}s" if retry_after else "")
)
# 回调
if on_retry:
on_retry(ctx.attempt, status_code, e, delay)
await asyncio.sleep(delay)
continue
else:
# 不可重试或重试次数耗尽
if status_code in ctx.retry_codes:
logger.error(
f"Retry exhausted after {ctx.attempt} attempts, "
f"last status: {status_code}, total delay: {ctx.total_delay:.2f}s"
)
else:
logger.error(f"Non-retryable status code: {status_code}")
# 抛出最后一次的错误
raise
def with_retry(
extract_status: Callable[[Exception], Optional[int]] = None,
on_retry: Callable[[int, int, Exception, float], None] = None,
):
"""
重试装饰器
Usage:
@with_retry()
async def my_api_call():
...
"""
def decorator(func: Callable):
@wraps(func)
async def wrapper(*args, **kwargs):
return await retry_on_status(
func, *args, extract_status=extract_status, on_retry=on_retry, **kwargs
)
return wrapper
return decorator
__all__ = [
"RetryContext",
"retry_on_status",
"with_retry",
"extract_retry_after",
]
-51
View File
@@ -1,51 +0,0 @@
"""
Statsig ID 生成服务
"""
import base64
import random
import string
from app.core.config import get_config
class StatsigService:
"""Statsig ID 生成服务"""
@staticmethod
def _rand(length: int, alphanumeric: bool = False) -> str:
"""生成随机字符串"""
chars = (
string.ascii_lowercase + string.digits
if alphanumeric
else string.ascii_lowercase
)
return "".join(random.choices(chars, k=length))
@staticmethod
def gen_id() -> str:
"""
生成 Statsig ID
Returns:
Base64 编码的 ID
"""
dynamic = get_config("chat.dynamic_statsig")
if not dynamic:
return "ZTpUeXBlRXJyb3I6IENhbm5vdCByZWFkIHByb3BlcnRpZXMgb2YgdW5kZWZpbmVkIChyZWFkaW5nICdjaGlsZE5vZGVzJyk="
# 随机格式
if random.choice([True, False]):
rand = StatsigService._rand(5, alphanumeric=True)
message = f"e:TypeError: Cannot read properties of null (reading 'children['{rand}']')"
else:
rand = StatsigService._rand(10)
message = (
f"e:TypeError: Cannot read properties of undefined (reading '{rand}')"
)
return base64.b64encode(message.encode()).decode()
__all__ = ["StatsigService"]
+13 -2
View File
@@ -3,7 +3,7 @@ Reverse interface: app chat conversations.
"""
import orjson
from typing import Any, Dict, List
from typing import Any, Dict, List, Optional
from curl_cffi.requests import AsyncSession
from app.core.logger import logger
@@ -165,7 +165,18 @@ class AppChatReverse:
return response
response = await retry_on_status(_do_request)
def extract_status(e: Exception) -> Optional[int]:
if isinstance(e, UpstreamException):
if e.details and "status" in e.details:
status = e.details["status"]
else:
status = getattr(e, "status_code", None)
if status == 429:
return None
return status
return None
response = await retry_on_status(_do_request, extract_status=extract_status)
# Stream response
async def stream_response():