{"record":{"id":"91fdfa6e30af11b4","repo":"linshenkx/prompt-optimizer","slug":"api-returned-invalid-response-choices-is-empty-or","errorCode":null,"errorMessage":"API returned invalid response: choices is empty or missing","messagePattern":"API returned invalid response: choices is empty or missing","errorType":"exception","errorClass":"APIError","httpStatus":null,"severity":"error","filePath":"packages/core/src/services/llm/adapters/openai-adapter.ts","lineNumber":1148,"sourceCode":"      callbacks.onError(error instanceof Error ? error : new Error(String(error)))\n      throw error\n    }\n  }\n\n  protected async parseCompletionResponse(response: any, modelId: string): Promise<LLMResponse> {\n    // 处理原始 SSE 字符串响应（某些 API 返回未解析的 SSE 格式）\n    if (typeof response === 'string') {\n      return this.parseSSEResponse(response, modelId)\n    }\n\n    // 检测是否为流式响应（某些 API 强制返回流式响应）\n    if (this.isStreamResponse(response)) {\n      return await this.consumeStreamResponse(response as AsyncIterable<any>, modelId)\n    }\n\n    // 处理响应中的 reasoning_content 和普通 content\n    if (!response.choices || response.choices.length === 0) {\n      throw new APIError('API returned invalid response: choices is empty or missing')\n    }\n\n    const choice = response.choices[0]\n    if (!choice?.message) {\n      throw new APIError('No valid response received')\n    }\n\n    let content = choice.message.content || ''\n    let reasoning = ''\n\n    // 处理推理内容（如果存在）\n    // SiliconFlow 等提供商在 choice.message 中并列提供 reasoning_content 字段\n    if ((choice.message as any).reasoning_content) {\n      reasoning = (choice.message as any).reasoning_content\n    } else {\n      // 检测并分离content中的think标签\n      const processed = this.processThinkTags(content)\n      content = processed.content","sourceCodeStart":1130,"sourceCodeEnd":1166,"githubUrl":"https://github.com/linshenkx/prompt-optimizer/blob/3e677b1d9f7e0493c142c175560531e7ae786dce/packages/core/src/services/llm/adapters/openai-adapter.ts#L1130-L1166","documentation":"After a non-streaming chat-completions call, the response JSON had no choices array (or an empty one), so the adapter cannot extract a completion and throws. The HTTP call itself succeeded; the payload is invalid for the chat-completions contract.","triggerScenarios":"OpenAI-compatible server returning {error: ...} with 200, a truncated/empty body, embedding-style responses from wrong endpoints, or gateways that wrap completions differently.","commonSituations":"Gateways returning their own envelope objects, pointing chat calls at /v1/embeddings or models endpoint, JSON cut by proxy buffering, providers that omit choices on content-filter blocks.","solutions":["Log the full response body to see what the server actually returned","Verify the request went to the chat-completions (or correct) endpoint for that adapter","If a gateway wraps responses, unwrap/fix it or set the right baseURL","Retry once — truncated responses happen under proxy timeouts"],"exampleFix":"// before\nconst text = await adapter.sendMessage(msgs)\n\n// after\nlet text\ntry { text = await adapter.sendMessage(msgs) }\ncatch (e) {\n  if (e instanceof APIError && /choices is empty/.test(e.message)) {\n    text = await retryOnce(() => adapter.sendMessage(msgs))\n  } else throw e\n}","handlingStrategy":"validation","validationCode":"const r = await fetch(`${baseURL}/chat/completions`, opts)\nconst body = await r.json()\nif (!Array.isArray(body?.choices) || body.choices.length === 0) throw new Error('Server returned no choices — check endpoint/gateway')","typeGuard":"function hasChoices(r: unknown): r is { choices: { message?: { content?: string } }[] } {\n  return Array.isArray((r as any)?.choices) && (r as any).choices.length > 0\n}","tryCatchPattern":"try { return await adapter.sendMessage(msgs) }\ncatch (e) {\n  if (e instanceof APIError && /choices is empty/.test(e.message)) return retryOnce(() => adapter.sendMessage(msgs))\n  throw e\n}","preventionTips":["Confirm the gateway implements chat-completions responses faithfully","Avoid pointing chat adapters at non-chat endpoints","Log full response bodies on shape errors"],"tags":["openai","chat-completions","response-format"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"3e677b1d9f7e0493c142c175560531e7ae786dce","analyzedAt":"2026-08-27T21:29:16.709Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}