we-promise/sure · error · Assistant::Responder::EmptyResponseError
Assistant returned neither text nor tool calls
Error message
Assistant returned neither text nor tool calls
What it means
Raised by Assistant::Responder#respond as EmptyResponseError when the tool-call loop finished (last LLM response contained no function_requests) but no round trip in the whole turn ever produced output text — the streamer only flags response_has_text on output_text chunks with present data, and the final check also looks for response.messages with non-blank output_text.
Source
Thrown at app/models/assistant/responder.rb:46
end
function_tool_calls = function_tool_caller.fulfill_requests(response.function_requests)
function_results = function_tool_calls.map(&:to_result)
in_flight_function_results.concat(function_results)
emit(:response, {
id: response.id,
function_tool_calls: function_tool_calls
})
response, response_has_text = request_response(
function_results: provider_preserves_response_context? ? function_results : in_flight_function_results.dup,
previous_response_id: response.id
)
any_response_has_text ||= response_has_text
end
raise EmptyResponseError, "Assistant returned neither text nor tool calls" unless any_response_has_text
emit(:response, { id: response.id })
end
private
attr_reader :message, :instructions, :function_tool_caller, :llm
def request_response(function_results: [], previous_response_id: nil)
response_has_text = false
streamer = proc do |chunk|
if chunk.type == "output_text" && chunk.data.present?
response_has_text = true
emit(:output_text, chunk.data)
end
end
response = get_llm_response(View on GitHub (pinned to e69894adb9)
Solutions
- Retry the turn once — transient empty completions usually succeed on re-ask; EmptyResponseError means no partial output was shown, so a retry is safe.
- Check the provider status page and the raw response captured for the turn (response.messages) to see whether content was filtered or merely mis-parsed.
- If you maintain the provider adapter, verify its streamer emits chunks with type "output_text" and non-empty data — otherwise the responder counts the turn as textless.
- Rescue Assistant::Responder::EmptyResponseError in the caller and show a retry affordance instead of a raw error.
Example fix
# before responder.respond # after — retry once, then degrade gracefully begin attempts = (attempts || 0) + 1 responder.respond rescue Assistant::Responder::EmptyResponseError retry if attempts == 1 raise end
Defensive patterns
Strategy: try-catch
Try / catch
begin responder.respond rescue Assistant::Responder::EmptyResponseError retry if (retries = (retries || 0) + 1) == 1 # nothing was shown, so a retry is safe raise end
Prevention
- Retry the turn once on empty responses — transient provider blanks usually clear immediately and no partial output was emitted.
- If you add provider adapters, ensure the streamer emits chunks with type "output_text" and non-blank data so text is counted.
- Track EmptyResponseError rate per provider — a spike usually means an outage or moderation filtering rather than a code bug.
When it happens
Trigger: Every request_response in the turn yields neither streamable output_text chunks nor messages with output_text: provider returns content: "" with no tool calls; content filtered/removed by provider-side moderation; the model emits only whitespace; a streaming adapter that fails to surface output_text for this provider's chunk shape (chunk.type never equals "output_text").
Common situations: Provider outage or degraded mode returning empty completions; safety filters blanking a response; a new provider adapter whose stream chunk type differs (so text arrives but is never counted); reasoning-only models that put everything in reasoning fields with no output text.
Related errors
- External assistant returned an empty response.
- Assistant exceeded the tool-call limit of #{max_tool_call_it
- OpenAI stream ended without a completion event. This usually
- No LLM provider configured that supports model '#{requested_
- External assistant connection was interrupted.
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/1f0a3b4a909765fd.
Report an issue: GitHub.