{"record":{"id":"bae4c11f2e84ced3","repo":"alibaba/page-agent","slug":"invalid-schema","errorCode":"INVALID_SCHEMA","errorMessage":"No choices in response","messagePattern":"No choices in response","errorType":"exception","errorClass":"InvokeError","httpStatus":null,"severity":"error","filePath":"packages/llms/src/OpenAIClient.ts","lineNumber":144,"sourceCode":"\t\t\t)\n\t\t}\n\n\t\t// 4. Parse and validate response\n\t\tlet data: any\n\t\ttry {\n\t\t\tdata = await response.json()\n\t\t} catch (error) {\n\t\t\tif ((error as any)?.name === 'AbortError') throw error\n\t\t\tthrow new InvokeError(\n\t\t\t\tInvokeErrorTypes.INVALID_RESPONSE,\n\t\t\t\t'Response body is not valid JSON',\n\t\t\t\terror\n\t\t\t)\n\t\t}\n\n\t\tconst choice = data.choices?.[0]\n\t\tif (!choice) {\n\t\t\tthrow new InvokeError(InvokeErrorTypes.INVALID_SCHEMA, 'No choices in response', data)\n\t\t}\n\n\t\t// Check finish_reason\n\t\tswitch (choice.finish_reason) {\n\t\t\tcase 'tool_calls':\n\t\t\tcase 'function_call': // gemini\n\t\t\tcase 'stop': // some models use this even with tool calls\n\t\t\t\tbreak\n\t\t\tcase 'length':\n\t\t\t\tthrow new InvokeError(\n\t\t\t\t\tInvokeErrorTypes.CONTEXT_LENGTH,\n\t\t\t\t\t'Response truncated: max tokens reached',\n\t\t\t\t\tundefined,\n\t\t\t\t\tdata\n\t\t\t\t)\n\t\t\tcase 'content_filter':\n\t\t\t\tthrow new InvokeError(\n\t\t\t\t\tInvokeErrorTypes.CONTENT_FILTER,","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/alibaba/page-agent/blob/d02db1ee7c41f5315beda88bab2fe935c580f662/packages/llms/src/OpenAIClient.ts#L126-L162","documentation":"The response parsed as JSON but its shape doesn't match the OpenAI chat completion schema — data.choices is missing or empty. The library expects at least one choice to extract finish_reason and content/tool calls; the raw data is attached as details for inspection.","triggerScenarios":"invoke() against an endpoint that returns 200 with an OpenAI-incompatible body: an error object like {error: {...}} with 200 status, an empty choices array (some gateways do this on internal failure), or a provider whose /chat/completions shim is incomplete.","commonSituations":"Third-party 'OpenAI-compatible' gateways that don't fully implement the schema; provider returning a soft error in the body with HTTP 200; model returning empty choices under load; middleware stripping parts of the response.","solutions":["Inspect the attached data payload — often it contains an error object explaining the real problem","If using a compatible gateway, verify it fully implements the choices[] contract, or test with the official OpenAI endpoint to isolate","Upgrade or configure the gateway/proxy that is mangling the response","Retry once — some providers intermittently return empty choices under load"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// probe the endpoint once for schema compatibility\nconst probe = await fetch(url, { method: 'POST', headers, body: JSON.stringify({ model, messages: [{ role: 'user', content: 'ping' }] }) })\nconst json = await probe.json()\nif (!Array.isArray(json.choices) || json.choices.length === 0) throw new Error('Endpoint does not return OpenAI-compatible choices[]')","typeGuard":"const isNoChoicesError = (e: unknown): e is InvokeError =>\n  e instanceof InvokeError && e.code === 'INVALID_SCHEMA' && /No choices/.test(e.message)","tryCatchPattern":"try {\n  await client.invoke(req)\n} catch (e) {\n  if (isNoChoicesError(e)) {\n    console.error('Raw provider payload:', e.details) // often contains a soft error\n    throw e\n  }\n  throw e\n}","preventionTips":["Smoke-test third-party 'OpenAI-compatible' gateways with a minimal request first","Log e.details — providers often embed the real error in the body","Prefer official endpoints when debugging schema issues"],"tags":["schema","invalid-response","openai-client"],"backgroundTag":"schema-validation-failed","analyzedSha":"d02db1ee7c41f5315beda88bab2fe935c580f662","analyzedAt":"2026-08-28T19:39:48.634Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}