can1357/oh-my-pi · error · ToolError

completion() did not return a structured response matching t

Error message

completion() did not return a structured response matching the schema.

What it means

With a `schema` supplied, the bridge expects either a forced `respond` tool call or JSON text it can parse with `parseJsonPayload`. When the model returns non-empty plain text that is not parseable JSON, this ToolError is thrown. It means the model answered but not in the structured shape requested.

Source

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

		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,
	});

	return {
		text: resultText,
		details: { model: formatModelString(model), tier: finalTier, structured: Boolean(schema) },

View on GitHub (pinned to 9690622007)

Solutions

  1. Retry, or move to a stronger tier (`model: "slow"`) that reliably makes the forced tool call.
  2. Reword the prompt to demand strict JSON with no prose ("Respond only with JSON matching ...").
  3. Simplify the schema — fewer/nested-fewer fields improves tool-call compliance.
  4. In the caller, catch this error and fall back to a second attempt or a heuristic parse of the raw text.

Example fix

// before
const v = JSON.parse((await completion(q, { schema })).text); // may throw
// after: tolerate prose replies
try {
  var val = JSON.parse((await completion(q, { schema })).text);
} catch {
  var val = await completion(q + " Respond ONLY with JSON.", { model: "slow", schema });
}
Defensive patterns

Strategy: retry

Try / catch

try {
  const r = await completion(prompt, { schema });
  return JSON.parse(r.text);
} catch (e) {
  if (String(e).includes("structured response")) {
    const r2 = await completion(prompt + "\nRespond ONLY with JSON.", { model: "slow", schema });
    return JSON.parse(r2.text);
  }
  throw e;
}

Prevention

When it happens

Trigger: `completion(prompt, { schema })` where the model declines the tool call and replies with prose like "Here is the data: ..." or a fenced code block with trailing commentary that `parseJsonPayload` cannot extract.

Common situations: Weaker models ignoring toolChoice on providers that don't enforce it; models wrapping JSON in markdown fences with explanation text; prompts that ask a question conversationally so the model answers in prose; schemas too complex for a smol model.

Related errors


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