linshenkx/prompt-optimizer · error · APIError
API returned invalid response: choices is empty or missing
Error message
API returned invalid response: choices is empty or missing
What it means
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.
Source
Thrown at packages/core/src/services/llm/adapters/openai-adapter.ts:1148
callbacks.onError(error instanceof Error ? error : new Error(String(error)))
throw error
}
}
protected async parseCompletionResponse(response: any, modelId: string): Promise<LLMResponse> {
// 处理原始 SSE 字符串响应(某些 API 返回未解析的 SSE 格式)
if (typeof response === 'string') {
return this.parseSSEResponse(response, modelId)
}
// 检测是否为流式响应(某些 API 强制返回流式响应)
if (this.isStreamResponse(response)) {
return await this.consumeStreamResponse(response as AsyncIterable<any>, modelId)
}
// 处理响应中的 reasoning_content 和普通 content
if (!response.choices || response.choices.length === 0) {
throw new APIError('API returned invalid response: choices is empty or missing')
}
const choice = response.choices[0]
if (!choice?.message) {
throw new APIError('No valid response received')
}
let content = choice.message.content || ''
let reasoning = ''
// 处理推理内容(如果存在)
// SiliconFlow 等提供商在 choice.message 中并列提供 reasoning_content 字段
if ((choice.message as any).reasoning_content) {
reasoning = (choice.message as any).reasoning_content
} else {
// 检测并分离content中的think标签
const processed = this.processThinkTags(content)
content = processed.contentView on GitHub (pinned to 3e677b1d9f)
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
Example fix
// before
const text = await adapter.sendMessage(msgs)
// after
let text
try { text = await adapter.sendMessage(msgs) }
catch (e) {
if (e instanceof APIError && /choices is empty/.test(e.message)) {
text = await retryOnce(() => adapter.sendMessage(msgs))
} else throw e
} Defensive patterns
Strategy: validation
Validate before calling
const r = await fetch(`${baseURL}/chat/completions`, opts)
const body = await r.json()
if (!Array.isArray(body?.choices) || body.choices.length === 0) throw new Error('Server returned no choices — check endpoint/gateway') Type guard
function hasChoices(r: unknown): r is { choices: { message?: { content?: string } }[] } {
return Array.isArray((r as any)?.choices) && (r as any).choices.length > 0
} Try / catch
try { return await adapter.sendMessage(msgs) }
catch (e) {
if (e instanceof APIError && /choices is empty/.test(e.message)) return retryOnce(() => adapter.sendMessage(msgs))
throw e
} Prevention
- Confirm the gateway implements chat-completions responses faithfully
- Avoid pointing chat adapters at non-chat endpoints
- Log full response bodies on shape errors
When it happens
Trigger: OpenAI-compatible server returning {error: ...} with 200, a truncated/empty body, embedding-style responses from wrong endpoints, or gateways that wrap completions differently.
Common situations: 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.
Related errors
- No valid response received
- Unexpected API response format
- UNSUPPORTED_TEST_TYPE
- Unexpected API response format
- Cloudflare model search returned an unexpected response form
AI-assisted analysis of linshenkx/prompt-optimizer@3e677b1d9f (2026-08-27).
Data as JSON: /api/errors/91fdfa6e30af11b4.
Report an issue: GitHub.