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

  1. Inspect the full response object (log response["output"].map { |o| o["type"] }) to see whether only reasoning items came back.
  2. Raise max_output_tokens / reduce batch size (LLM_MAX_ITEMS_PER_CALL) so reasoning models finish and emit the message item.
  3. Use a non-reasoning chat model (gpt-4.1) for native mode, or route the custom gateway through the generic extraction path.
  4. 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

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


AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21). Data as JSON: /api/errors/24a29a4564fa430f. Report an issue: GitHub.