abhigyanpatwari/GitNexus · error · Error
LLM returned empty response
Error message
LLM returned empty response
What it means
Non-streaming success path: the provider returned HTTP 200 with valid JSON, but json.choices[0].message.content is missing or falsy. GitNexus refuses to return an empty completion because downstream wiki rendering would emit blank pages. Note this checks only .content; providers that put text in reasoning_content or in a different envelope will trip it.
Source
Thrown at gitnexus/src/core/wiki/llm-client.ts:456
`Azure content filter blocked this request. The prompt triggered content policy. Details: ${errorText.slice(0, 300)}`,
);
}
// Any other non-OK response here is a terminal 4xx — resilientFetch
// already retried 5xx/429 to exhaustion and would have thrown above.
throw new Error(`LLM API error (${response.status}): ${errorText.slice(0, 500)}`);
}
// Streaming path
if (useStream && response.body) {
return await readSSEStream(response.body, options!.onChunk!);
}
// Non-streaming path
const json = (await response.json()) as any;
const choice = json.choices?.[0];
if (!choice?.message?.content) {
throw new Error('LLM returned empty response');
}
return {
content: choice.message.content,
promptTokens: json.usage?.prompt_tokens,
completionTokens: json.usage?.completion_tokens,
};
}
/**
* Read an SSE stream from an OpenAI-compatible streaming response.
*/
async function readSSEStream(
body: ReadableStream<Uint8Array>,
onChunk: (charsReceived: number) => void,
): Promise<LLMResponse> {
const decoder = new TextDecoder();
const reader = body.getReader();View on GitHub (pinned to d540b00184)
Solutions
- Raise config.maxTokens / max_completion_tokens so the model has room to emit text.
- If using a reasoning model, ensure it is detected (isReasoningModel) or pass provider correctly so the response envelope matches.
- If Azure null-content due to content filter, sanitize the prompt or relax the filter (see error 224).
- Switch to streaming (pass onChunk in options) which parses SSE chunks and surfaces partial content.
- Verify the server is actually OpenAI-compatible (choices[].message.content); if it uses a different shape, use a different baseUrl/provider.
Example fix
// before
await callLLM(prompt, { baseUrl, apiKey, model: 'o3-mini', maxTokens: 1 });
// -> LLM returned empty response
// after
await callLLM(prompt, { baseUrl, apiKey, model: 'o3-mini', maxTokens: 4096 });
// or stream:
await callLLM(prompt, { baseUrl, apiKey, model }, undefined, { onChunk: n => progress(n) }); Defensive patterns
Strategy: validation
Validate before calling
function looksReasoning(model) { return /^o[1-9]\d*/i.test(model); }
// give reasoning models enough tokens and parse via streaming for partial content
config.maxTokens = Math.max(config.maxTokens ?? 0, 1024);
if (looksReasoning(config.model)) config.isReasoningModel = true; Type guard
function isEmptyResponseError(e) {
return e instanceof Error && /LLM returned empty response/.test(e.message);
} Try / catch
try { return await callLLM(prompt, config); }
catch (e) {
if (isEmptyResponseError(e)) {
// retry with streaming + larger budget, or non-reasoning fallback
return await callLLM(prompt, { ...config, maxTokens: 4096 }, undefined, { onChunk: () => {} });
}
throw e;
} Prevention
- Set max_completion_tokens generously; tiny budgets cause empty completions.
- For reasoning models pass isReasoningModel=true or rely on isReasoningModel detection.
- Use streaming (onChunk) so partial content is captured even if the final envelope is unusual.
When it happens
Trigger: Provider returns 200 but choices[0].message.content is null/'' (some Azure deployments return null content when content_filter triggered without a 400; some OpenAI-compatible servers return empty for safety models; reasoning models that put everything in reasoning_content with empty primary content). Also: max_completion_tokens set so low the model emitted nothing.
Common situations: Reasoning model (o1/o3) parsed via non-reasoning path; Azure content filter returning 200 with null content; tiny max_completion_tokens budget; broken OpenAI-compatible server returning malformed choices array; tool-calling-format response with no text.
Related errors
- LLM API error (${err.response.status} after retries): ${erro
- Azure content filter blocked this request. The prompt trigge
- LLM API error (${response.status}): ${errorText.slice(0, 500
- Insecure http:// LLM base URLs are only allowed for localhos
- LLM endpoint circuit open: retry in ${Math.ceil(err.retryAft
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/1dc49cd226b62c16.
Report an issue: GitHub.