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
- Retry — degenerate empty responses are often transient.
- Switch to a stronger tier (`model: "slow"`) that reliably honors forced tool calls.
- Make the prompt more explicit that structured output is required, or simplify the schema (fewer required fields).
- 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
- Use the slow tier for structured-output calls; small models ignore forced tool calls more often.
- State explicitly in the prompt that output must come via the respond tool.
- Keep schemas small and flat.
- Retry once automatically — empty responses are frequently transient.
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
- completion() did not return a structured response matching t
- completion() returned no text output.
- No response returned by agent creation architect.
- Devin API error: response body is empty
- Devin auth error: GetUserJwt returned an empty user JWT
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/7d8c57bf28f6dfd9.
Report an issue: GitHub.