sigoden/aichat · error
Invalid response data
Error message
Invalid response data: {data} What it means
Thrown by gemini_extract_chat_completions_text in src/client/vertexai.rs:296 when the Gemini/Vertex AI response contained no usable text or tool calls, and it was NOT a safety block. The library falls back to embedding the whole raw JSON response in the message so developers can inspect what Google actually returned. It signals an unexpected/unparseable response shape from the Gemini chat completions endpoint — the code cannot extract text, tool calls, or a recognized block reason.
Solutions
- Log/inspect the raw `data` JSON embedded in the error message to see the actual finishReason or error envelope Google returned.
- Handle non-SAFETY finishReasons (RECITATION, BLOCKLIST, OTHER) by rephrasing the prompt or regenerating.
- Verify the Vertex AI endpoint URL, project ID, and model name are correct so the real Gemini API is being hit, not a gateway error page.
- Retry the request — transient RECITATION/OTHER stops often succeed on a second attempt.
- Update the library if the Gemini API response schema changed (check for newer versions).
Example fix
// before
let out = client.chat_completions(data).await?; // opaque "Invalid response data"
// after
let out = client.chat_completions(data).await.map_err(|e| {
eprintln!("gemini raw response: {e}");
e
})?; Defensive patterns
Strategy: try-catch
Validate before calling
// Inspect the error message for the raw response JSON and finishReason before deciding next steps
let raw = err.to_string();
let finish_reason = serde_json::from_str::<serde_json::Value>(raw.trim_start_matches("Invalid response data: "))
.ok()
.and_then(|v| v["candidates"][0]["finishReason"].as_str().map(String::from)); Type guard
fn has_usable_candidates(data: &serde_json::Value) -> bool {
data["candidates"].as_array().map_or(false, |c| !c.is_empty())
&& data["candidates"][0]["finishReason"].as_str().map_or(true, |r| r != "SAFETY")
} Try / catch
let out = match client.chat_completions(data.clone()).await {
Ok(out) if !out.text.is_empty() || !out.tool_calls.is_empty() => Ok(out),
Ok(_) | Err(_) => {
// log the embedded raw JSON, then retry once or surface a clear message
retry_or_fallback(data).await
}
}; Prevention
- Log the full raw response JSON carried in the error message on every occurrence.
- Handle non-SAFETY finishReasons (RECITATION, BLOCKLIST, OTHER) explicitly with retries or prompt rewrites.
- Pin/verify the Vertex AI endpoint URL, project, and API version so you always hit the real Gemini API.
- Retry transient empty responses once with exponential backoff before failing.
- Keep the library updated in case Gemini response schema changes.
When it happens
Trigger: Calling gemini_chat_completions where the response JSON has empty text parts and no tool calls and neither promptFeedback.blockReason nor candidates[0].finishReason equals "SAFETY" — e.g. candidates[0].finishReason is "RECITATION", "OTHER", or "BLOCKLIST"; an empty candidates array; a proxy/gateway returning a different JSON schema; or the endpoint returning an HTML/JSON error page the code still parses as a value.
Common situations: Gemini stops generation with non-SAFETY finish reasons like RECITATION (output too close to training data) or BLOCKLIST; misconfigured Vertex project/URL returns a different error envelope; API version changes altering response fields; corporate proxies stripping or reshaping the response.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
Related errors
- The model does not support network images
- Invalid response data
- Invalid response data
- {err_msg}
- The call ' ' has invalid arguments
AI-assisted analysis of sigoden/aichat@82976d349a (2026-09-09).
Data as JSON: /api/errors/a51d458a2b1d8383.
Report an issue: GitHub.
Appendix: source
Thrown at src/client/vertexai.rs:296
}
if let (Some(name), Some(args)) = (
part["functionCall"]["name"].as_str(),
part["functionCall"]["args"].as_object(),
) {
tool_calls.push(ToolCall::new(name.to_string(), json!(args), None));
}
}
}
let text = text_parts.join("\n\n");
if text.is_empty() && tool_calls.is_empty() {
if let Some("SAFETY") = data["promptFeedback"]["blockReason"]
.as_str()
.or_else(|| data["candidates"][0]["finishReason"].as_str())
{
bail!("Blocked due to safety")
} else {
bail!("Invalid response data: {data}");
}
}
let output = ChatCompletionsOutput {
text,
tool_calls,
id: None,
input_tokens: data["usageMetadata"]["promptTokenCount"].as_u64(),
output_tokens: data["usageMetadata"]["candidatesTokenCount"].as_u64(),
};
Ok(output)
}
pub fn gemini_build_chat_completions_body(
data: ChatCompletionsData,
model: &Model,
) -> Result<Value> {
let ChatCompletionsData {
mut messages,View on GitHub (pinned to 82976d349a)