From b614778f137681608d3508c895bd64aa606a8460 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Sat, 25 Apr 2026 02:58:06 +0800 Subject: [PATCH] Relax V4 scan timeout and trim output --- .env.example | 5 +-- frontend/app/api/scan/terminal/ai/route.ts | 7 ++-- web/scan_terminal_service.py | 37 +++++++++++++++++++--- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/.env.example b/.env.example index 5f4d3354..39432b0b 100644 --- a/.env.example +++ b/.env.example @@ -133,10 +133,11 @@ POLYWEATHER_SCAN_AI_ENABLED=false POLYWEATHER_DEEPSEEK_API_KEY= POLYWEATHER_DEEPSEEK_BASE_URL=https://api.deepseek.com POLYWEATHER_SCAN_AI_MODEL=deepseek-v4-flash -POLYWEATHER_SCAN_AI_TIMEOUT_SEC=12 +POLYWEATHER_SCAN_AI_TIMEOUT_SEC=40 POLYWEATHER_SCAN_AI_CACHE_TTL_SEC=600 POLYWEATHER_SCAN_AI_MAX_ROWS=40 -POLYWEATHER_SCAN_AI_PROXY_TIMEOUT_MS=18000 +POLYWEATHER_SCAN_AI_MAX_TOKENS=1600 +POLYWEATHER_SCAN_AI_PROXY_TIMEOUT_MS=45000 POLYWEATHER_PREWARM_CITIES=ankara,istanbul,shanghai,beijing,shenzhen,guangzhou,wuhan,chengdu,chongqing,hong kong,taipei,singapore,tokyo,seoul,busan,london,paris,madrid # Weekly reward / leaderboard diff --git a/frontend/app/api/scan/terminal/ai/route.ts b/frontend/app/api/scan/terminal/ai/route.ts index 8dfe39ad..616bf616 100644 --- a/frontend/app/api/scan/terminal/ai/route.ts +++ b/frontend/app/api/scan/terminal/ai/route.ts @@ -5,12 +5,13 @@ import { } from "@/lib/backend-auth"; const API_BASE = process.env.POLYWEATHER_API_BASE_URL; -const SCAN_AI_PROXY_TIMEOUT_MS = Number( - process.env.POLYWEATHER_SCAN_AI_PROXY_TIMEOUT_MS || "18000", +const SCAN_AI_PROXY_TIMEOUT_MS = Math.max( + 35_000, + Number(process.env.POLYWEATHER_SCAN_AI_PROXY_TIMEOUT_MS || "45000") || 45_000, ); export const dynamic = "force-dynamic"; -export const maxDuration = 20; +export const maxDuration = 60; export async function POST(req: NextRequest) { if (!API_BASE) { diff --git a/web/scan_terminal_service.py b/web/scan_terminal_service.py index b87ac615..fc2581d8 100644 --- a/web/scan_terminal_service.py +++ b/web/scan_terminal_service.py @@ -39,8 +39,8 @@ SCAN_AI_ENABLED = str( os.getenv("POLYWEATHER_SCAN_AI_ENABLED") or "false" ).strip().lower() in {"1", "true", "yes", "on"} SCAN_AI_TIMEOUT_SEC = max( - 3, - int(os.getenv("POLYWEATHER_SCAN_AI_TIMEOUT_SEC", "12")), + 30, + int(os.getenv("POLYWEATHER_SCAN_AI_TIMEOUT_SEC", "40")), ) SCAN_AI_CACHE_TTL_SEC = max( 30, @@ -50,6 +50,10 @@ SCAN_AI_MAX_ROWS = max( 1, int(os.getenv("POLYWEATHER_SCAN_AI_MAX_ROWS", "40")), ) +SCAN_AI_MAX_TOKENS = max( + 600, + int(os.getenv("POLYWEATHER_SCAN_AI_MAX_TOKENS", "1600")), +) def _safe_float(value: Any) -> Optional[float]: @@ -490,11 +494,19 @@ def _call_deepseek_scan_ai(ai_input: Dict[str, Any]) -> Dict[str, Any]: "city_theses items need city, thesis_zh, thesis_en, model_cluster_note, confidence, " "recommended_row_ids, vetoed_row_ids. recommendations items need row_id, rank, decision, " "confidence, reason_zh, reason_en, model_cluster_note. vetoed/downgraded items need row_id, " - "reason_zh/reason_en. watchlist items are optional and need row_id plus reason." + "reason_zh/reason_en. watchlist items are optional and need row_id plus reason. " + "Keep every thesis and reason concise: one sentence only, no markdown, no repeated data tables." ), "snapshot": model_snapshot, } - with httpx.Client(timeout=float(SCAN_AI_TIMEOUT_SEC)) as client: + timeout = httpx.Timeout( + timeout=float(SCAN_AI_TIMEOUT_SEC), + connect=min(8.0, float(SCAN_AI_TIMEOUT_SEC)), + read=float(SCAN_AI_TIMEOUT_SEC), + write=10.0, + pool=5.0, + ) + with httpx.Client(timeout=timeout) as client: response = client.post( f"{SCAN_AI_BASE_URL}/chat/completions", headers={ @@ -504,7 +516,7 @@ def _call_deepseek_scan_ai(ai_input: Dict[str, Any]) -> Dict[str, Any]: json={ "model": SCAN_AI_MODEL, "temperature": 0.1, - "max_tokens": 2200, + "max_tokens": SCAN_AI_MAX_TOKENS, "response_format": {"type": "json_object"}, "messages": [ {"role": "system", "content": system_prompt}, @@ -1182,6 +1194,21 @@ def build_scan_terminal_ai_payload( duration_ms=duration_ms, input_rows=len(payload.get("rows") or []), ) + except httpx.TimeoutException as exc: + duration_ms = int((time.time() - ai_started_at) * 1000) + reason = f"V4 provider timed out after {SCAN_AI_TIMEOUT_SEC}s" + logger.warning( + "scan terminal AI review timeout snapshot={} duration_ms={} error={}", + current_snapshot_id, + duration_ms, + exc, + ) + return _build_scan_ai_unavailable_payload( + payload, + status="timeout", + reason=reason, + duration_ms=duration_ms, + ) except Exception as exc: duration_ms = int((time.time() - ai_started_at) * 1000) logger.warning(