we-promise/sure · error · Provider::Openai::Error
No response from AI
Error message
No response from AI
What it means
Raised by Provider::Openai::BankStatementExtractor#process_chunk when client.chat returns but choices[0].message.content is blank. The request already sets response_format json_object, so an HTTP-level failure would have raised earlier; a 200 with empty content means the model produced nothing usable in that slot — content-filter refusal (null content with a finish_reason like content_filter), truncation at max tokens (finish_reason length), or a gateway returning a choices array whose message carries only role/finish fields.
Source
Thrown at app/models/provider/openai/bank_statement_extractor.rb:113
chunks << current_chunk.join("\n\n") if current_chunk.any?
chunks
end
def process_chunk(text, is_first_chunk)
params = {
model: model,
messages: [
{ role: "system", content: is_first_chunk ? instructions_with_metadata : instructions_transactions_only },
{ role: "user", content: "Extract transactions:\n\n#{text}" }
],
response_format: { type: "json_object" }
}
response = client.chat(parameters: params)
content = response.dig("choices", 0, "message", "content")
raise Provider::Openai::Error, "No response from AI" if content.blank?
parsed = parse_json_response(content)
{
transactions: normalize_transactions(parsed["transactions"] || []),
period: {
start_date: parsed.dig("statement_period", "start_date"),
end_date: parsed.dig("statement_period", "end_date")
},
account_holder: parsed["account_holder"],
account_number: parsed["account_number"],
bank_name: parsed["bank_name"],
opening_balance: parsed["opening_balance"],
closing_balance: parsed["closing_balance"]
}
end
def parse_json_response(content)View on GitHub (pinned to e69894adb9)
Solutions
- Log response.dig("choices", 0, "finish_reason") — content_filter vs length tells you the remedy immediately.
- For content_filter: rephrase/split the chunk, or catch and surface a user-facing 'statement could not be processed' message; do not retry the same text.
- For length: raise max tokens or shrink MAX_CHARS_PER_CHUNK so a single page fits.
- For gateways: verify with a minimal curl chat completion that the endpoint returns content for json_object mode at all.
Example fix
# before
content = response.dig("choices", 0, "message", "content")
raise Provider::Openai::Error, "No response from AI" if content.blank?
# after
choice = response.dig("choices", 0)
content = choice&.dig("message", "content")
if content.blank?
raise Provider::Openai::Error, "No response from AI (finish_reason=#{choice&.dig("finish_reason")})"
end Defensive patterns
Strategy: retry
Try / catch
attempts = 0
begin
process_chunk(chunk, first)
rescue Provider::Openai::Error => e
raise unless e.message.include?("No response from AI") && (attempts += 1) <= 1
retry # transient empty completion; give the model a second chance
end Prevention
- Inspect finish_reason in dev logs — content_filter and length need different fixes, and content_filter must not be blindly retried.
- Keep chunks under the response token budget so JSON-mode answers are never cut to empty.
- Confirm custom gateways honor response_format json_object before relying on non-empty content.
When it happens
Trigger: OpenAI content moderation refusing the chunk (statements with gambling/adult merchant strings can trip it) returning content: null; output cut by max_tokens with content present but empty after filtering; a custom OpenAI-compatible provider returning an empty content field on error instead of a failing status; empty assistant message on finish_reason length.
Common situations: Statements containing unusual merchant names or obfuscated strings; tight token budgets on dense statement pages; third-party gateways that answer 200 with empty content when overloaded; temperature/tool-usage settings on the gateway causing empty completions.
Related errors
- No message content found in response
- No message content found in response
- {e.record.errors.full_messages.to_sentence.presence || e.mes
- OpenAI stream ended without a completion event. This usually
- Assistant returned neither text nor tool calls
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/4fc35eb68ae37c89.
Report an issue: GitHub.