ZhuLinsen/daily_stock_analysis · error · HTTPException

extract_failed

extract_failed

Error message

extract_failed

What it means

Raised by /extract-from-image when extract_stock_codes_from_image raises a ValueError. That service signals expected, caller-facing failures via ValueError (per the endpoint's contract), and its message is passed through verbatim as the 400 detail with error code extract_failed — typical messages include unsupported MIME after content sniffing and 'no vision LLM configured'.

Source

Thrown at api/v1/endpoints/stocks.py:189

        logger.warning(f"读取上传文件失败: {e}")
        raise HTTPException(
            status_code=400,
            detail={"error": "read_failed", "message": "读取上传文件失败"},
        )

    try:
        items, raw_text = extract_stock_codes_from_image(data, content_type)
        extract_items = [
            ExtractItem(code=code, name=name, confidence=conf) for code, name, conf in items
        ]
        codes = [i.code for i in extract_items]
        return ExtractFromImageResponse(
            codes=codes,
            items=extract_items,
            raw_text=raw_text if include_raw else None,
        )
    except ValueError as e:
        raise HTTPException(status_code=400, detail={"error": "extract_failed", "message": str(e)})
    except Exception as e:
        logger.error(f"图片提取失败: {e}", exc_info=True)
        raise HTTPException(
            status_code=500,
            detail={"error": "internal_error", "message": "图片提取失败"},
        )


@router.post(
    "/parse-import",
    response_model=ExtractFromImageResponse,
    responses={
        200: {"description": "解析结果"},
        400: {"description": "未提供数据或解析失败", "model": ErrorResponse},
        500: {"description": "服务器错误", "model": ErrorResponse},
    },
    summary="解析 CSV/Excel/剪贴板",
    description="上传 CSV/Excel 文件或粘贴文本,自动解析股票代码。文件上限 2MB,文本上限 100KB。",

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Read the passthrough message: if it names a missing provider/key, configure one vision LLM credential (Gemini/Anthropic/OpenAI) and retry.
  2. Verify the payload is a real image: open it locally (PIL.Image.open(...).verify()) before sending.
  3. Re-download or re-export the source image if it is corrupt.

Example fix

# before
with open('quote.png', 'rb') as f:  # actually a renamed PDF
    requests.post(url, files={'file': f})  # extract_failed

# after
from PIL import Image
Image.open('quote.png').verify()  # raises here -> fix the file first
with open('quote.png', 'rb') as f:
    requests.post(url, files={'file': f})
Defensive patterns

Strategy: validation

Validate before calling

from PIL import Image
try:
    img = Image.open(io.BytesIO(data)); img.verify()
except Exception:
    raise ValueError('文件不是有效图片,请重新导出')
# plus: confirm a vision LLM key is configured before enabling the upload UI

Type guard

async function isDecodableImage(bytes: ArrayBuffer): Promise<boolean> {
  try { await createImageBitmap(new Blob([bytes])); return true; }
  catch { return false; }
}

Try / catch

resp = requests.post(url, files={'file': fh})
if resp.status_code == 400:
    err = resp.json().get('detail', {})
    if err.get('error') == 'extract_failed':
        showUserMessage(err.get('message'))  # provider/config guidance passthrough
        return
raise_for_status_otherwise(resp)

Prevention

When it happens

Trigger: Posting a file whose declared Content-Type is allowed but whose actual bytes are not a decodable image (corrupt or misnamed file); running the API without any vision-capable LLM credentials (Gemini/Anthropic/OpenAI) so the extractor cannot be constructed; the vision provider returning content the parser rejects.

Common situations: A .png that is actually a renamed PDF or text file; missing/invalid API keys in env for all three vision providers; truncated downloads; zero-byte uploads that pass the earlier checks.

Related errors


AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15). Data as JSON: /api/errors/a0b59dcef4d2955c. Report an issue: GitHub.