can1357/oh-my-pi · error · SearchProviderError

Gemini API returned an empty grounded response

Error message

Gemini API returned an empty grounded response

What it means

finalizeGeminiSearchResult() throws this after a Gemini grounded search completes when the result contains neither an answer text nor any grounding sources. Gemini is expected to return a grounded response (answer and/or grounding chunks); an entirely empty result indicates the model returned no usable grounded content, which is treated as a provider failure (HTTP 502).

Source

Thrown at packages/coding-agent/src/web/search/providers/gemini.ts:357

			redirect: "manual",
			signal: withHardTimeout(signal, 5000),
		});
		const location = response.headers.get("location");
		if (!location) return proxyUrl;
		const resolved = new URL(location, proxyUrl);
		return resolved.protocol === "http:" || resolved.protocol === "https:" ? resolved.toString() : proxyUrl;
	} catch {
		return proxyUrl;
	}
}

async function finalizeGeminiSearchResult(
	result: GeminiSearchResult,
	fetchImpl: FetchImpl | undefined,
	signal: AbortSignal | undefined,
): Promise<GeminiSearchResult> {
	if (!result.answer && result.sources.length === 0) {
		throw new SearchProviderError("gemini", "Gemini API returned an empty grounded response", 502);
	}

	const redirectUrls = new Set<string>();
	for (const source of result.sources) {
		if (isGroundingRedirectUrl(source.url)) redirectUrls.add(source.url);
	}
	for (const citation of result.citations) {
		if (isGroundingRedirectUrl(citation.url)) redirectUrls.add(citation.url);
	}
	if (redirectUrls.size === 0) return result;

	signal?.throwIfAborted();
	const resolvedEntries = await Promise.all(
		[...redirectUrls].map(async url => [url, await resolveGroundingRedirect(url, fetchImpl, signal)] as const),
	);
	signal?.throwIfAborted();
	const resolvedUrls = new Map(resolvedEntries);
	for (const source of result.sources) {

View on GitHub (pinned to 9690622007)

Solutions

  1. Retry with a rephrased, more specific query — empty grounded responses often follow prompts the model won't ground on
  2. Inspect the raw API response (log before parsing) to see whether candidates/groundingMetadata were present upstream
  3. Check safety settings and model configuration; an over-blocked prompt returns empty candidates
  4. Verify any proxy/gateway between you and Gemini preserves the grounding metadata fields
Defensive patterns

Strategy: fallback

Validate before calling

// No pre-call validation; shape the prompt so grounding is likely:
const safeQuery = query.trim();
if (!safeQuery) throw new Error("Refusing empty Gemini search query");

Type guard

function hasGroundedContent(r: { answer?: string; sources: unknown[] }): boolean {
  return Boolean(r.answer && r.answer.trim()) || r.sources.length > 0;
}

Try / catch

try {
  results = await searchGemini(query);
} catch (err) {
  if (err instanceof SearchProviderError && err.message === "Gemini API returned an empty grounded response") {
    logger.warn("Gemini grounded search empty — retrying with rephrased query, then fallback", { query });
    results = await searchGemini(rephrase(query)).catch(() => fallbackSearch(query));
  } else throw err;
}

Prevention

When it happens

Trigger: parseGeminiSearchStream produced a GeminiSearchResult with empty `answer` and zero `sources` — e.g. the model declined/refused to answer, the grounding metadata was empty in the stream, the prompt/query yielded no grounded output, or stream parsing dropped all content.

Common situations: Queries the model refuses or answers with empty candidates, proxy/gateway stripping grounding metadata from the response, overly restrictive safety settings, or a malformed/truncated stream that yielded no chunks.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/b6712ff2eab2c6b6. Report an issue: GitHub.