{"record":{"id":"3d4091a7b2bf0785","repo":"ZhuLinsen/daily_stock_analysis","slug":"internal-error-3d4091","errorCode":"internal_error","errorMessage":"图片提取失败","messagePattern":"图片提取失败","errorType":"http","errorClass":"HTTPException","httpStatus":500,"severity":"error","filePath":"api/v1/endpoints/stocks.py","lineNumber":192,"sourceCode":"            detail={\"error\": \"read_failed\", \"message\": \"读取上传文件失败\"},\n        )\n\n    try:\n        items, raw_text = extract_stock_codes_from_image(data, content_type)\n        extract_items = [\n            ExtractItem(code=code, name=name, confidence=conf) for code, name, conf in items\n        ]\n        codes = [i.code for i in extract_items]\n        return ExtractFromImageResponse(\n            codes=codes,\n            items=extract_items,\n            raw_text=raw_text if include_raw else None,\n        )\n    except ValueError as e:\n        raise HTTPException(status_code=400, detail={\"error\": \"extract_failed\", \"message\": str(e)})\n    except Exception as e:\n        logger.error(f\"图片提取失败: {e}\", exc_info=True)\n        raise HTTPException(\n            status_code=500,\n            detail={\"error\": \"internal_error\", \"message\": \"图片提取失败\"},\n        )\n\n\n@router.post(\n    \"/parse-import\",\n    response_model=ExtractFromImageResponse,\n    responses={\n        200: {\"description\": \"解析结果\"},\n        400: {\"description\": \"未提供数据或解析失败\", \"model\": ErrorResponse},\n        500: {\"description\": \"服务器错误\", \"model\": ErrorResponse},\n    },\n    summary=\"解析 CSV/Excel/剪贴板\",\n    description=\"上传 CSV/Excel 文件或粘贴文本，自动解析股票代码。文件上限 2MB，文本上限 100KB。\",\n)\nasync def parse_import(request: Request) -> ExtractFromImageResponse:\n    \"\"\"","sourceCodeStart":174,"sourceCodeEnd":210,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/api/v1/endpoints/stocks.py#L174-L210","documentation":"The catch-all 500 (internal_error, '图片提取失败') for /extract-from-image: any exception from extract_stock_codes_from_image that is not an HTTPException or ValueError — i.e. unexpected failures such as network errors calling the vision LLM API, provider SDK exceptions, or bugs. Full traceback is logged via logger.error with exc_info=True.","triggerScenarios":"Vision provider API unreachable (outbound network blocked, DNS failure, 5xx from Gemini/Anthropic/OpenAI); expired or revoked API key raising a non-ValueError SDK error; provider SDK version incompatibility raising TypeError/AttributeError inside the extractor.","commonSituations":"Containers without egress to llm provider domains; rate-limit responses surfacing as SDK exceptions; keys rotated after deployment; transient provider outages.","solutions":["Inspect the server log — the exc_info traceback identifies the exact provider and failure.","Verify outbound connectivity and credentials for the vision provider (curl the provider endpoint from the host).","Retry after transient provider errors; if persistent, check the installed provider SDK version against requirements.txt."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    result = await api.extractFromImage(fd)\nexcept ApiError as e:\n    if e.code == 'internal_error':\n        notify('图片识别服务暂时不可用，请稍后重试或手动输入代码')\n        offerManualCodeEntry()  # fallback path\n    else:\n        raise","preventionTips":["Monitor provider reachability and key validity with a cheap health probe before uploads.","Keep provider SDK versions pinned to tested releases.","Design the UI so manual code entry is always available as a fallback."],"tags":["llm","network","http-500","vision"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}