nexu-io/open-design · error · ValueError
Expected JSON response, got empty text
Error message
Expected JSON response, got empty text
What it means
Raised by extract_json after stripping the model response text. An empty (or whitespace-only) response cannot even enter the json.loads fallback regex path, so it is rejected up front with ValueError. This is a model-output contract failure, not a config problem.
Source
Thrown at design-templates/last30days/scripts/lib/providers.py:359
preferred = (config.get("LAST30DAYS_X_BACKEND") or "").lower()
if preferred in {"xai", "bird"}:
return preferred
return env.get_x_source(config)
def _require_gemini_31_preview(model: str, *, role: str) -> None:
if model.startswith("gemini-3.1-") and model.endswith("-preview"):
return
raise RuntimeError(
f"{role} must use a Gemini 3.1 preview model. Got: {model}"
)
def extract_json(text: str) -> dict[str, Any]:
"""Extract the first JSON object from a model response."""
text = text.strip()
if not text:
raise ValueError("Expected JSON response, got empty text")
try:
return json.loads(text)
except json.JSONDecodeError:
match = re.search(r"\{[\s\S]*\}", text)
if not match:
raise
return json.loads(match.group(0))
def extract_gemini_text(payload: dict[str, Any]) -> str:
for candidate in payload.get("candidates", []):
content = candidate.get("content") or {}
for part in content.get("parts", []):
text = part.get("text")
if text:
return text
if payload:
print(f"[Providers] extract_gemini_text: no text in payload keys: {list(payload.keys())}", file=sys.stderr)View on GitHub (pinned to 5be4028344)
Solutions
- Inspect the raw model payload (enable response logging) to confirm whether the body was actually empty.
- Retry the call; transient empty responses from safety filters or load balancers often succeed on retry.
- Tighten the prompt to avoid tripping safety filters, or request a non-empty structured schema explicitly.
- If using a streaming/relay layer, verify it forwards the full body to extract_json.
Example fix
# before
text = ""
extract_json(text) # ValueError
# after
from lib import providers
if not text.strip():
raise RuntimeError("provider returned empty body; retry or inspect filter")
providers.extract_json(text) Defensive patterns
Strategy: retry
Validate before calling
null
Type guard
def has_json_body(text: str) -> bool:
return bool(text and text.strip()) Try / catch
from lib.providers import extract_json
last_err = None
for attempt in range(3):
text = client.complete(prompt)
if text and text.strip():
try:
return extract_json(text)
except (ValueError, json.JSONDecodeError) as e:
last_err = e
# empty or unparseable -> retry
if last_err:
raise last_err
raise RuntimeError("provider returned empty body after retries") Prevention
- Log the raw provider response when extract_json fails so the empty-body cause is visible.
- Retry empty responses once or twice; safety-filter blanks are often transient across prompt variations.
- If empties persist, tighten the prompt to avoid tripping safety filters.
When it happens
Trigger: A reasoning client (Gemini/OpenAI/xAI/OpenRouter) returns an empty string for a prompt that expected JSON. Causes include content filters blanking the response, token-budget exhaustion, model returning only whitespace, or a transport error returning an empty body.
Common situations: Safety filters triggered on the prompt and the API returned an empty completion. A streaming/middleware bug dropped the body. Rate-limited responses parsed as empty. Models configured with max_tokens=0 or broken tool-call wrappers.
Related errors
- Vela ${command} returned no JSON output
- Vela ${command} returned invalid JSON: ${detail}
- invalid JSON in ${filePath}: ${message}
- ${filePath} must contain a JSON object
- proposal patch.after is not valid JSON
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/3968da50337a781e.
Report an issue: GitHub.