Skip to content

Add Content#empty? to fix Anthropic NoMethodError#734

Open
55728 wants to merge 1 commit intocrmne:mainfrom
55728:729-content-empty-method
Open

Add Content#empty? to fix Anthropic NoMethodError#734
55728 wants to merge 1 commit intocrmne:mainfrom
55728:729-content-empty-method

Conversation

@55728
Copy link
Copy Markdown

@55728 55728 commented Apr 16, 2026

What this does

Fixes #729

Summary

Adds RubyLLM::Content#empty? so msg.content.nil? || msg.content.empty? no longer raises NoMethodError when msg.content is a Content instance.

Why @text.to_s.strip.empty? && @attachments.empty?

Three shapes were considered:

  • def empty?; false; end, leaning on the constructor check raise ... if @text.nil? && @attachments.empty?. That check only rejects a nil text, though — Content.new('') and Content.new(' ') both construct successfully, so a blanket false would misrepresent those instances.
  • Updating the two call sites in anthropic/chat.rb:179 and anthropic/tools.rb:19 to use the defensive respond_to?(:empty?) && empty? idiom already present in anthropic/tools.rb:49, bedrock/media.rb:20, and gemini/tools.rb:19. Workable, but leaves Content as the only nearby domain object without #empty?, which is a bit surprising for a container-like Ruby type.
  • Chosen: give Content an #empty? with String#empty? / Array#empty? semantics — blank when there is no visible text and no attachments. The method name alone predicts the behavior, and the rule holds even if the constructor invariant is relaxed later.

Side effect (bonus)

bedrock/media.rb:20, gemini/tools.rb:19, gemini/tools.rb:50, and anthropic/tools.rb:49 already use the defensive content.respond_to?(:empty?) && content.empty? idiom. Until now Content did not respond to empty?, so those guards silently treated every Content instance as non-empty. With #empty? defined, those call sites behave as their authors intended for blank content.

Out of scope (happy to fold in if preferred)

anthropic/chat.rb:179 and anthropic/tools.rb:19 still use bare msg.content.empty?. Commit 4371a1b introduced the defensive respond_to?(:empty?) pattern in anthropic/tools.rb:49; aligning these two lines with that convention would be a small follow-up — happy to handle it in a separate PR, or fold it into this one if that fits the repo style better.

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation
  • Performance improvement

Scope check

  • I read the Contributing Guide
  • This aligns with RubyLLM's focus on LLM communication
  • This isn't application-specific logic that belongs in user code
  • This benefits most users, not just my specific use case

Quality check

  • I ran overcommit --install and all hooks pass
  • I tested my changes thoroughly
    • For provider changes: Re-recorded VCR cassettes — n/a, no provider code changed
    • New spec spec/ruby_llm/content_spec.rb passes (5 examples, 0 failures)
  • I updated documentation if needed — n/a, no public-API docs to update beyond the new method itself
  • I didn't modify auto-generated files manually (models.json, aliases.json)

AI-generated code

  • I used AI tools to help write this code
  • I have reviewed and understand all generated code

API changes

  • Breaking change
  • New public methods/classes (RubyLLM::Content#empty?)
  • Changed method signatures
  • No API changes

Fixes crmne#729

RubyLLM::Content did not implement #empty?, causing NoMethodError
in Anthropic provider code paths that check
`msg.content.nil? || msg.content.empty?` when content is a
Content instance (e.g. messages with ActiveStorage attachments).

Content#empty? now returns true when text is blank (nil, empty,
or whitespace-only) and there are no attachments, consistent with
String#empty? and Array#empty? semantics.
@fidalgo
Copy link
Copy Markdown

fidalgo commented Apr 27, 2026

I've opened a PR that aims to fix this on all providers and raising a meaningful error.
Have a look and let me know your thoughts: #733

@55728
Copy link
Copy Markdown
Author

55728 commented Apr 27, 2026

@fidalgo

Thanks for flagging this — I had a look at #733 and it’s a solid approach for the HTTP layer.
If I may, though, I wonder whether these two PRs might actually be complementary rather than overlapping? #733 guards against nil response bodies from Faraday, which is an important safety net at the transport level. This PR (#734), on the other hand, addresses Content as a domain object — it simply lacks #empty?, which means existing call sites like msg.content.empty? raise NoMethodError regardless of whether the response body was nil or not.

There’s also the matter of the existing respond_to?(:empty?) guards scattered across providers (bedrock/media.rb, gemini/tools.rb, anthropic/tools.rb) — without Content#empty?, those silently treat every Content instance as non-empty, which isn’t quite what their authors intended.

I’d be delighted if both could land — one hardens the transport, the other completes the domain model. But of course, happy to hear your thoughts.

@fidalgo
Copy link
Copy Markdown

fidalgo commented Apr 27, 2026

@55728 That's a broader decision: is an empty response an error or an acceptable case. My gut feeling tells me, we should have some content on the response, but I do not know every provider and that may pose an API change.

Edit: there's another PR open on the same: #747

Flagging this to @crmne (sorry to flag you, but if we can have a decision here both PR's can be addressed and the issue fixed)

@55728
Copy link
Copy Markdown
Author

55728 commented Apr 27, 2026

Good call flagging @crmne — thanks for that.

Agreed that the empty-response semantics are a broader design question. For what it’s worth, I believe this PR is independent of that decision: Content#empty? is about giving the domain object a method that Ruby containers conventionally have, and should be safe to land either way.

Happy to wait for a steer on the bigger picture.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] RubyLLM::Content missing #empty? method causes NoMethodError in Anthropic provider

2 participants