ZhuLinsen/daily_stock_analysis · error · ValueError
LiteLLM vision returned empty response
Error message
LiteLLM vision returned empty response
What it means
Raised after a successful litellm.completion() call when the response has no choices, an empty first message, or empty content. The HTTP call itself succeeded, but nothing text-like came back, so the extractor cannot parse stock codes.
Source
Thrown at src/services/image_stock_extractor.py:342
call_kwargs["api_key"] = effective_api_key
if deployment_params.get("api_base"):
call_kwargs["api_base"] = deployment_params["api_base"]
if deployment_params.get("extra_headers"):
call_kwargs["extra_headers"] = dict(deployment_params["extra_headers"])
# Add api_base and custom headers for OpenAI-compatible providers
if not deployment_params and not model.startswith("gemini/") and not model.startswith("anthropic/") and not model.startswith("vertex_ai/"):
if cfg.openai_base_url:
call_kwargs["api_base"] = cfg.openai_base_url
if cfg.openai_base_url and "aihubmix.com" in cfg.openai_base_url:
call_kwargs["extra_headers"] = {"APP-Code": "GPIJ3886"}
if getattr(litellm, "completion", None) is None:
import litellm as litellm_module
litellm = litellm_module
response = litellm.completion(**call_kwargs)
if response and response.choices and response.choices[0].message.content:
return response.choices[0].message.content
raise ValueError("LiteLLM vision returned empty response")
def extract_stock_codes_from_image(
image_bytes: bytes,
mime_type: str,
) -> Tuple[List[Tuple[str, Optional[str], str]], str]:
"""
从图片中提取股票代码及名称(使用 Vision LLM)。
优先级:Gemini -> Anthropic -> OpenAI(首个可用)。
支持多 Key 轮询与重试(最多 3 次,指数退避)。
Args:
image_bytes: 原始图片字节
mime_type: MIME 类型(如 image/jpeg, image/png)
Returns:
(items, raw_text) - items 为 [(code, name?, confidence), ...],raw_text 为原始 LLM 响应。View on GitHub (pinned to 5159bd72e8)
Solutions
- Retry the call — transient empty responses often succeed on retry (the outer loop already retries 3x, so persistent failure means provider/config issue)
- Log response.choices[0].finish_reason and full response to see why content is empty
- Verify the wire model actually supports vision input (check deployment_params['model'])
- If content_filter, change image or use a different provider/model
Example fix
// not applicable — runtime/provider issue, no caller-side code fix
Defensive patterns
Strategy: retry
Type guard
def has_content(resp) -> bool:
try:
return bool(resp and resp.choices and resp.choices[0].message.content)
except AttributeError:
return False Try / catch
for attempt in range(3):
try:
return _call_litellm_vision(b64, mime, key)
except ValueError as e:
if 'empty response' not in str(e) or attempt == 2:
raise
time.sleep(2 ** attempt) Prevention
- Log finish_reason and raw response on empty content to distinguish filter vs truncation
- Set explicit max_tokens high enough to avoid finish_reason=length
- Verify the wire model supports vision before enabling image import
When it happens
Trigger: Provider returns 200 with empty content (safety filter, finish_reason=length/content_filter, tool-call-only response, or a misrouted proxy model returning an object without message content).
Common situations: Image flagged by provider content filters; model alias on a proxy (e.g. aihubmix) resolving to a non-vision model; truncated response due to max_tokens; flaky provider returning malformed payloads.
Related errors
- LLM returned empty response
- No API key found for vision model {model}
- extract_failed
- internal_error
- [AlphaVantage] No time series data for {symbol}
AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15).
Data as JSON: /api/errors/864c92b89408e6dd.
Report an issue: GitHub.