we-promise/sure · error · Provider::Openai::Error
No message content found in response
Error message
No message content found in response
What it means
Raised by Provider::Openai::AutoCategorizer#extract_categorizations_native when a Responses-API reply contains no output item of type "message" whose content[0].text is present. The native path expects response["output"] to be an array with a message item holding the JSON string; nil means the model produced only reasoning items, an empty output, or a refusal-shaped response. It fires before JSON parsing, so this is 'no answer text at all', distinct from error 351 (text present but unparseable).
Source
Thrown at app/models/provider/openai/auto_categorizer.rb:347
# Ensure string inputs for string operations
input_lower = input.to_s.downcase
category_lower = category.to_s.downcase
variations.each do |_key, synonyms|
if synonyms.include?(input_lower) && synonyms.include?(category_lower)
return true
end
end
false
end
def extract_categorizations_native(response)
# Find the message output (not reasoning output)
message_output = response["output"]&.find { |o| o["type"] == "message" }
raw = message_output&.dig("content", 0, "text")
raise Provider::Openai::Error, "No message content found in response" if raw.nil?
JSON.parse(raw).dig("categorizations")
rescue JSON::ParserError => e
raise Provider::Openai::Error, "Invalid JSON in native categorization: #{e.message}"
end
def extract_categorizations_generic(response)
raw = response.dig("choices", 0, "message", "content")
parsed = parse_json_flexibly(raw)
# Handle different response formats from various LLMs
categorizations = parsed.dig("categorizations") ||
parsed.dig("results") ||
(parsed.is_a?(Array) ? parsed : nil)
raise Provider::Openai::Error, "Could not find categorizations in response" if categorizations.nil?
# Normalize field names (some LLMs use different naming)View on GitHub (pinned to e69894adb9)
Solutions
- Inspect the full response object (log response["output"].map { |o| o["type"] }) to see whether only reasoning items came back.
- Raise max_output_tokens / reduce batch size (LLM_MAX_ITEMS_PER_CALL) so reasoning models finish and emit the message item.
- Use a non-reasoning chat model (gpt-4.1) for native mode, or route the custom gateway through the generic extraction path.
- If the output shape changed upstream, extend the finder to also accept o.dig("content", 0, "text") from other item types.
Example fix
# before
message_output = response["output"]&.find { |o| o["type"] == "message" }
raw = message_output&.dig("content", 0, "text")
# after
message_output = response["output"]&.find { |o| o["type"] == "message" }
raw = message_output&.dig("content", 0, "text")
if raw.nil?
Rails.logger.error("native output items: #{response.dig("output")&.map { |o| o["type"] }.inspect}")
raise Provider::Openai::Error, "No message content found in response"
end Defensive patterns
Strategy: fallback
Try / catch
begin
categorizations = extractor.extract_categorizations_native(response)
rescue Provider::Openai::Error => e
raise unless e.message.include?("No message content")
categorizations = extractor.extract_categorizations_generic(fallback_chat_response)
end Prevention
- Log output item types on every native-path failure to spot reasoning-only responses early.
- Budget max_output_tokens generously for reasoning models so the message item is actually emitted.
- Prefer non-reasoning chat models for structured extraction on the native path.
When it happens
Trigger: A reasoning model (o-series) exhausting output on reasoning items or hitting max_output_tokens before emitting the message; response.output filtered to tool/reasoning-only items; an API or gateway returning a nonstandard Responses-shaped body; model returning a refusal with empty content.
Common situations: Switching auto-categorization to an o1/o3-style model with a small token budget; an OpenAI-compatible gateway that mimics /v1/responses imperfectly; truncation from long transaction batches; upstream API changes to the Responses payload shape.
Related errors
- No message content found in response
- OpenAI stream ended without a completion event. This usually
- No categories available for auto-categorization
- No response from AI
- {e.record.errors.full_messages.to_sentence.presence || e.mes
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/24a29a4564fa430f.
Report an issue: GitHub.