{"record":{"id":"7d8c57bf28f6dfd9","repo":"can1357/oh-my-pi","slug":"completion-returned-no-structured-response","errorCode":null,"errorMessage":"completion() returned no structured response.","messagePattern":"completion\\(\\) returned no structured response\\.","errorType":"exception","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/eval/completion-bridge.ts","lineNumber":188,"sourceCode":"\t\t),\n\t);\n\n\tif (response.stopReason === \"error\") {\n\t\tthrow new ToolError(response.errorMessage ?? \"completion() request failed.\");\n\t}\n\tif (response.stopReason === \"aborted\") {\n\t\tthrow new ToolError(\"completion() request aborted.\");\n\t}\n\n\tlet resultText: string;\n\tif (schema) {\n\t\tconst call = extractToolCall(response, STRUCTURED_TOOL_NAME);\n\t\tlet value: unknown;\n\t\tif (call) {\n\t\t\tvalue = call.arguments;\n\t\t} else {\n\t\t\tconst text = extractTextContent(response);\n\t\t\tif (!text) throw new ToolError(\"completion() returned no structured response.\");\n\t\t\ttry {\n\t\t\t\tvalue = parseJsonPayload(text);\n\t\t\t} catch {\n\t\t\t\tthrow new ToolError(\"completion() did not return a structured response matching the schema.\");\n\t\t\t}\n\t\t}\n\t\tresultText = JSON.stringify(value);\n\t} else {\n\t\tresultText = extractTextContent(response);\n\t\tif (!resultText) throw new ToolError(\"completion() returned no text output.\");\n\t}\n\n\toptions.emitStatus?.({\n\t\top: \"completion\",\n\t\tmodel: formatModelString(model),\n\t\ttier: finalTier,\n\t\tchars: resultText.length,\n\t});","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/eval/completion-bridge.ts#L170-L206","documentation":"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.","triggerScenarios":"`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).","commonSituations":"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.","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."],"exampleFix":"// before\nconst val = await completion(\"give data\", { model: \"smol\", schema: { name: \"string\" } });\n// after: retry on empty, or upgrade tier\nlet val = await completion(\"Return the data by calling the respond tool.\", { model: \"slow\", schema: { name: \"string\" } });","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"for (let i = 0; i < 2; i++) {\n  try {\n    return await completion(prompt, { schema });\n  } catch (e) {\n    if (!String(e).includes(\"no structured response\") || i === 1) throw e;\n  }\n}","preventionTips":["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."],"tags":["llm-output","structured-output","empty-response"],"backgroundTag":"empty-model-response","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}