Skip to content

Commit 61bccb4

Browse files
bubichenebel95crmne
authored
Fix ActiveStorage::Blob re-upload if used in with: param (#683)
## What this does Fixes #665 When an `ActiveStorage::Blob` is passed to `ask` or `create_user_message` via `with:`, it is downloaded and re-uploaded as a new blob. This is because the blob gets wrapped in a `RubyLLM::Attachment` during content building, so it goes to the `else` branch in `prepare_for_active_storage` and `convert_to_active_storage_format` is called, which always creates a new IO hash. => Fix by updating `convert_to_active_storage_format` to detect `ActiveStorage` objects and return it directly so it can be reused. ## Type of change - [x] Bug fix - [ ] New feature - [ ] Breaking change - [ ] Documentation - [ ] Performance improvement ## Scope check - [x] I read the [Contributing Guide](https://github.com/crmne/ruby_llm/blob/main/CONTRIBUTING.md) - [x] This aligns with RubyLLM's focus on **LLM communication** - [x] This isn't application-specific logic that belongs in user code - [x] This benefits most users, not just my specific use case ## Quality check - [x] I ran `overcommit --install` and all hooks pass - [x] I tested my changes thoroughly - [ ] For provider changes: Re-recorded VCR cassettes with `bundle exec rake vcr:record[provider_name]` - [x] All tests pass: `bundle exec rspec` - [ ] I updated documentation if needed - [x] I didn't modify auto-generated files manually (`models.json`, `aliases.json`) ## AI-generated code - [x] I used AI tools to help write this code - [x] I have reviewed and understand all generated code (required if above is checked) ## API changes - [ ] Breaking change - [ ] New public methods/classes - [ ] Changed method signatures - [x] No API changes Co-authored-by: nebel95 <nebel95@wallwisher.com> Co-authored-by: Carmine Paolino <carmine@paolino.me>
1 parent f63c65a commit 61bccb4

2 files changed

Lines changed: 30 additions & 5 deletions

File tree

lib/ruby_llm/active_record/chat_methods.rb

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -417,11 +417,18 @@ def convert_to_active_storage_format(source)
417417

418418
attachment = source.is_a?(RubyLLM::Attachment) ? source : RubyLLM::Attachment.new(source)
419419

420-
{
421-
io: StringIO.new(attachment.content),
422-
filename: attachment.filename,
423-
content_type: attachment.mime_type
424-
}
420+
if attachment.active_storage?
421+
case attachment.source
422+
when ActiveStorage::Blob then attachment.source
423+
when ActiveStorage::Attached::One, ActiveStorage::Attached::Many then attachment.source.blobs
424+
end
425+
else
426+
{
427+
io: StringIO.new(attachment.content),
428+
filename: attachment.filename,
429+
content_type: attachment.mime_type
430+
}
431+
end
425432
rescue StandardError => e
426433
RubyLLM.logger.warn "Failed to process attachment #{source}: #{e.message}"
427434
nil

spec/ruby_llm/active_record/acts_as_attachment_spec.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,24 @@ def attachment_io(path)
8888
user_message = chat.messages.find_by(role: 'user')
8989
expect(user_message.attachments.count).to eq(1)
9090
end
91+
92+
it 'reuses an existing ActiveStorage::Blob without re-uploading' do
93+
chat = Chat.create!(model: model)
94+
95+
existing_blob = ActiveStorage::Blob.create_and_upload!(
96+
io: attachment_io(image_path),
97+
filename: 'ruby.png',
98+
content_type: 'image/png'
99+
)
100+
101+
expect do
102+
chat.create_user_message('What do you see?', with: existing_blob)
103+
end.not_to change(ActiveStorage::Blob, :count)
104+
105+
user_message = chat.messages.find_by(role: 'user')
106+
expect(user_message.attachments.count).to eq(1)
107+
expect(user_message.attachments.first.blob_id).to eq(existing_blob.id)
108+
end
91109
end
92110

93111
describe 'attachment types' do

0 commit comments

Comments
 (0)