{"record":{"id":"8ed749fa62278f10","repo":"ZhuLinsen/daily_stock_analysis","slug":"vision-api-api-key-last-error","errorCode":null,"errorMessage":"Vision API 调用失败，请检查 API Key 与网络: {last_error}","messagePattern":"Vision API 调用失败，请检查 API Key 与网络: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/services/image_stock_extractor.py","lineNumber":400,"sourceCode":"    for attempt in range(3):\n        try:\n            key = random.choice(keys) if keys else None\n            raw = _call_litellm_vision(image_b64, mime_type, api_key=key)\n            logger.debug(\"[ImageExtractor] raw LLM response:\\n%s\", raw)\n            items = _parse_items_from_text(raw)\n            logger.info(\n                f\"[ImageExtractor] {model} 提取 {len(items)} 个: \"\n                f\"{[(i[0], i[1]) for i in items[:5]]}{'...' if len(items) > 5 else ''}\"\n            )\n            return items, raw\n        except Exception as e:\n            last_error = e\n            if attempt < 2:\n                delay = 2 ** attempt\n                logger.warning(f\"[ImageExtractor] 尝试 {attempt + 1}/3 失败，{delay}s 后重试: {e}\")\n                time.sleep(delay)\n\n    raise ValueError(\n        f\"Vision API 调用失败，请检查 API Key 与网络: {last_error}\"\n    ) from last_error\n","sourceCodeStart":382,"sourceCodeEnd":403,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/services/image_stock_extractor.py#L382-L403","documentation":"Terminal failure after 3 attempts in extract_stock_codes_from_image: every vision API call raised (network error, auth error, provider 4xx/5xx). The loop retries with exponential backoff (1s, 2s) and random key rotation before giving up; last_error is chained for diagnosis.","triggerScenarios":"Invalid/revoked API key (401/403); network unreachable/timeout to provider; rate limit exhausted across all attempts; provider outage; wrong base_url (e.g. aihubmix APP-Code header mismatch).","commonSituations":"Expired or quota-exhausted key; corporate firewall blocking api.openai.com / generativelanguage.googleapis.com; mistyped OPENAI_BASE_URL; provider regional outage.","solutions":["Inspect the chained last_error (and the [ImageExtractor] warning logs per attempt) to classify: auth vs network vs rate-limit","Verify the API key works with a direct curl to the provider","Check network egress/DNS/proxy from the host running the app","If rate-limited, add more keys for rotation via _get_api_keys_for_model or slow the request rate"],"exampleFix":"// not applicable — diagnose via the chained exception and logs","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    items, raw = extract_stock_codes_from_image(b, mime)\nexcept ValueError as e:\n    cause = e.__cause__\n    if isinstance(cause, (ConnectionError, TimeoutError)):\n        schedule_retry_later()\n    elif '401' in str(cause) or '403' in str(cause):\n        alert_key_rotation_needed()\n    else:\n        raise","preventionTips":["Validate the vision API key with a cheap text-only call at startup","Keep multiple keys configured for rotation (the extractor already randomizes per attempt)","Alert on the per-attempt [ImageExtractor] warnings — persistent 3x failure means systemic issue, don't loop retries externally"],"tags":["network","api-key","retry","vision"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}