Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions lib/ruby_llm/active_record/acts_as_legacy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -473,9 +473,15 @@ def extract_tool_call_id
end

def extract_content
return content unless respond_to?(:attachments) && attachments.attached?
text_content = if content.respond_to?(:to_plain_text)
content.to_plain_text
else
content
end

RubyLLM::Content.new(content).tap do |content_obj|
return text_content unless respond_to?(:attachments) && attachments.attached?

RubyLLM::Content.new(text_content).tap do |content_obj|
@_tempfiles = []

attachments.each do |attachment|
Expand Down
3 changes: 2 additions & 1 deletion lib/ruby_llm/active_record/message_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ def extract_tool_call_id
def extract_content
return RubyLLM::Content::Raw.new(content_raw) if has_attribute?(:content_raw) && content_raw.present?

content_value = self[:content]
content_value = content
content_value = content_value.to_plain_text if content_value.respond_to?(:to_plain_text)

return content_value unless respond_to?(:attachments) && attachments.attached?

Expand Down
1 change: 1 addition & 0 deletions spec/dummy/config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
require 'active_record/railtie'
require 'active_storage/engine'
require 'action_controller/railtie'
require 'action_text/engine'

Bundler.require(*Rails.groups)

Expand Down
61 changes: 61 additions & 0 deletions spec/ruby_llm/active_record/acts_as_action_text_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe RubyLLM::ActiveRecord::ActsAs do
include_context 'with configured RubyLLM'

let(:chat) { Chat.create! }

def mock_action_text(plain_text)
instance_double(ActionText::RichText).tap do |mock|
allow(mock).to receive(:to_plain_text).and_return(plain_text)
end
end

def create_message_with_action_text(content_text)
message = chat.messages.create!(role: :user)
action_text_content = mock_action_text(content_text)
allow(message).to receive(:content).and_return(action_text_content)
[message, action_text_content]
end

describe 'Action Text content extraction' do
context 'when content responds to to_plain_text' do
it 'extracts plain text from Action Text content' do
message, action_text_content = create_message_with_action_text('This is plain text')

llm_message = message.to_llm

expect(action_text_content).to have_received(:to_plain_text)
expect(llm_message.content).to eq('This is plain text')
end
end

context 'when content is a regular string' do
it 'returns content unchanged' do
message = chat.messages.create!(role: :user, content: 'Regular text content')

expect(message.to_llm.content).to eq('Regular text content')
end
end

context 'when Action Text content has attachments' do
let(:test_attachment) do
{ io: StringIO.new('test data'), filename: 'test.txt', content_type: 'text/plain' }
end

it 'combines Action Text with attachments into RubyLLM::Content' do
message, action_text_content = create_message_with_action_text('Rich text with attachment')
message.attachments.attach(test_attachment)

llm_message = message.to_llm

expect(action_text_content).to have_received(:to_plain_text)
expect(llm_message.content).to be_a(RubyLLM::Content)
expect(llm_message.content.text).to eq('Rich text with attachment')
expect(llm_message.content.attachments.first.mime_type).to eq('text/plain')
end
end
end
end