{"record":{"id":"a51d458a2b1d8383","repo":"sigoden/aichat","slug":"invalid-response-data-data-a51d45","errorCode":null,"errorMessage":"Invalid response data: {data}","messagePattern":"Invalid response data: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/client/vertexai.rs","lineNumber":296,"sourceCode":"            }\n            if let (Some(name), Some(args)) = (\n                part[\"functionCall\"][\"name\"].as_str(),\n                part[\"functionCall\"][\"args\"].as_object(),\n            ) {\n                tool_calls.push(ToolCall::new(name.to_string(), json!(args), None));\n            }\n        }\n    }\n\n    let text = text_parts.join(\"\\n\\n\");\n    if text.is_empty() && tool_calls.is_empty() {\n        if let Some(\"SAFETY\") = data[\"promptFeedback\"][\"blockReason\"]\n            .as_str()\n            .or_else(|| data[\"candidates\"][0][\"finishReason\"].as_str())\n        {\n            bail!(\"Blocked due to safety\")\n        } else {\n            bail!(\"Invalid response data: {data}\");\n        }\n    }\n    let output = ChatCompletionsOutput {\n        text,\n        tool_calls,\n        id: None,\n        input_tokens: data[\"usageMetadata\"][\"promptTokenCount\"].as_u64(),\n        output_tokens: data[\"usageMetadata\"][\"candidatesTokenCount\"].as_u64(),\n    };\n    Ok(output)\n}\n\npub fn gemini_build_chat_completions_body(\n    data: ChatCompletionsData,\n    model: &Model,\n) -> Result<Value> {\n    let ChatCompletionsData {\n        mut messages,","sourceCodeStart":278,"sourceCodeEnd":314,"githubUrl":"https://github.com/sigoden/aichat/blob/82976d349ad97ac9aae0655ad631dace5e2a6385/src/client/vertexai.rs#L278-L314","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"// before\nlet out = client.chat_completions(data).await?; // opaque \"Invalid response data\"\n// after\nlet out = client.chat_completions(data).await.map_err(|e| {\n    eprintln!(\"gemini raw response: {e}\");\n    e\n})?;","handlingStrategy":"try-catch","validationCode":"// Inspect the error message for the raw response JSON and finishReason before deciding next steps\nlet raw = err.to_string();\nlet finish_reason = serde_json::from_str::<serde_json::Value>(raw.trim_start_matches(\"Invalid response data: \"))\n    .ok()\n    .and_then(|v| v[\"candidates\"][0][\"finishReason\"].as_str().map(String::from));","typeGuard":"fn has_usable_candidates(data: &serde_json::Value) -> bool {\n    data[\"candidates\"].as_array().map_or(false, |c| !c.is_empty())\n        && data[\"candidates\"][0][\"finishReason\"].as_str().map_or(true, |r| r != \"SAFETY\")\n}","tryCatchPattern":"let out = match client.chat_completions(data.clone()).await {\n    Ok(out) if !out.text.is_empty() || !out.tool_calls.is_empty() => Ok(out),\n    Ok(_) | Err(_) => {\n        // log the embedded raw JSON, then retry once or surface a clear message\n        retry_or_fallback(data).await\n    }\n};","preventionTips":["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."],"tags":["gemini","vertex-ai","unexpected-response","json"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"82976d349ad97ac9aae0655ad631dace5e2a6385","analyzedAt":"2026-09-09T18:33:06.139Z","contentChangedAt":"2026-09-09T18:33:06.139Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}