can1357/oh-my-pi · error · ToolError

completion() returned no structured response.

Error message

completion() returned no structured response.

What it means

When a `schema` is passed to `completion()`, the model is forced to call a synthetic `respond` tool to return structured output. If the model instead returned only plain text with no tool call, the bridge tries to recover by parsing the text as JSON; when the text is also empty, this ToolError is thrown. It means the model produced no usable output at all.

Source

Thrown at packages/coding-agent/src/eval/completion-bridge.ts:188

		),
	);

	if (response.stopReason === "error") {
		throw new ToolError(response.errorMessage ?? "completion() request failed.");
	}
	if (response.stopReason === "aborted") {
		throw new ToolError("completion() request aborted.");
	}

	let resultText: string;
	if (schema) {
		const call = extractToolCall(response, STRUCTURED_TOOL_NAME);
		let value: unknown;
		if (call) {
			value = call.arguments;
		} else {
			const text = extractTextContent(response);
			if (!text) throw new ToolError("completion() returned no structured response.");
			try {
				value = parseJsonPayload(text);
			} catch {
				throw new ToolError("completion() did not return a structured response matching the schema.");
			}
		}
		resultText = JSON.stringify(value);
	} else {
		resultText = extractTextContent(response);
		if (!resultText) throw new ToolError("completion() returned no text output.");
	}

	options.emitStatus?.({
		op: "completion",
		model: formatModelString(model),
		tier: finalTier,
		chars: resultText.length,
	});

View on GitHub (pinned to 9690622007)

Solutions

  1. Retry — degenerate empty responses are often transient.
  2. Switch to a stronger tier (`model: "slow"`) that reliably honors forced tool calls.
  3. Make the prompt more explicit that structured output is required, or simplify the schema (fewer required fields).
  4. If the provider supports it, verify the toolChoice forcing is honored for your model; some models on some providers ignore it.

Example fix

// before
const val = await completion("give data", { model: "smol", schema: { name: "string" } });
// after: retry on empty, or upgrade tier
let val = await completion("Return the data by calling the respond tool.", { model: "slow", schema: { name: "string" } });
Defensive patterns

Strategy: retry

Try / catch

for (let i = 0; i < 2; i++) {
  try {
    return await completion(prompt, { schema });
  } catch (e) {
    if (!String(e).includes("no structured response") || i === 1) throw e;
  }
}

Prevention

When it happens

Trigger: `completion(prompt, { schema })` where the model returns an empty response: no `respond` tool call and `extractTextContent` yields nothing (empty string or only whitespace/non-text blocks).

Common situations: Small/weak smol-tier models ignoring the forced tool call and emitting nothing; provider returning a truncated/filtered response (safety filter); degenerate output on very short prompts; model hitting an immediate stop token.

Related errors


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