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

  1. Log/inspect the raw `data` JSON embedded in the error message to see the actual finishReason or error envelope Google returned.
  2. Handle non-SAFETY finishReasons (RECITATION, BLOCKLIST, OTHER) by rephrasing the prompt or regenerating.
  3. 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.
  4. Retry the request — transient RECITATION/OTHER stops often succeed on a second attempt.
  5. 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

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


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)