Skip to content

Commit 983edf9

Browse files
chagelcrmneclaude
authored
Feat: Add support to Action Text enabled content (#365)
## What this does Improved the `extract_content` method to correctly process Action Text. When `message` model uses `has_rich_text :content`, `message.content` becomes `ActionText:RichText`, returning HTML tags unintentionally. Adjustments ensure only the desired content is extracted. ## 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 - [x] I updated documentation if needed - [x] I didn't modify auto-generated files manually (`models.json`, `aliases.json`) ## API changes - [ ] Breaking change - [ ] New public methods/classes - [ ] Changed method signatures - [ ] No API changes ## Related issues <!-- Link issues: "Fixes #123" or "Related to #123" --> --------- Co-authored-by: Carmine Paolino <carmine@paolino.me> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a0fc0c5 commit 983edf9

4 files changed

Lines changed: 72 additions & 3 deletions

File tree

lib/ruby_llm/active_record/acts_as_legacy.rb

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -473,9 +473,15 @@ def extract_tool_call_id
473473
end
474474

475475
def extract_content
476-
return content unless respond_to?(:attachments) && attachments.attached?
476+
text_content = if content.respond_to?(:to_plain_text)
477+
content.to_plain_text
478+
else
479+
content
480+
end
477481

478-
RubyLLM::Content.new(content).tap do |content_obj|
482+
return text_content unless respond_to?(:attachments) && attachments.attached?
483+
484+
RubyLLM::Content.new(text_content).tap do |content_obj|
479485
@_tempfiles = []
480486

481487
attachments.each do |attachment|

lib/ruby_llm/active_record/message_methods.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ def extract_tool_call_id
9999
def extract_content
100100
return RubyLLM::Content::Raw.new(content_raw) if has_attribute?(:content_raw) && content_raw.present?
101101

102-
content_value = self[:content]
102+
content_value = content
103+
content_value = content_value.to_plain_text if content_value.respond_to?(:to_plain_text)
103104

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

spec/dummy/config/application.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
require 'active_record/railtie'
88
require 'active_storage/engine'
99
require 'action_controller/railtie'
10+
require 'action_text/engine'
1011

1112
Bundler.require(*Rails.groups)
1213

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
5+
RSpec.describe RubyLLM::ActiveRecord::ActsAs do
6+
include_context 'with configured RubyLLM'
7+
8+
let(:chat) { Chat.create! }
9+
10+
def mock_action_text(plain_text)
11+
instance_double(ActionText::RichText).tap do |mock|
12+
allow(mock).to receive(:to_plain_text).and_return(plain_text)
13+
end
14+
end
15+
16+
def create_message_with_action_text(content_text)
17+
message = chat.messages.create!(role: :user)
18+
action_text_content = mock_action_text(content_text)
19+
allow(message).to receive(:content).and_return(action_text_content)
20+
[message, action_text_content]
21+
end
22+
23+
describe 'Action Text content extraction' do
24+
context 'when content responds to to_plain_text' do
25+
it 'extracts plain text from Action Text content' do
26+
message, action_text_content = create_message_with_action_text('This is plain text')
27+
28+
llm_message = message.to_llm
29+
30+
expect(action_text_content).to have_received(:to_plain_text)
31+
expect(llm_message.content).to eq('This is plain text')
32+
end
33+
end
34+
35+
context 'when content is a regular string' do
36+
it 'returns content unchanged' do
37+
message = chat.messages.create!(role: :user, content: 'Regular text content')
38+
39+
expect(message.to_llm.content).to eq('Regular text content')
40+
end
41+
end
42+
43+
context 'when Action Text content has attachments' do
44+
let(:test_attachment) do
45+
{ io: StringIO.new('test data'), filename: 'test.txt', content_type: 'text/plain' }
46+
end
47+
48+
it 'combines Action Text with attachments into RubyLLM::Content' do
49+
message, action_text_content = create_message_with_action_text('Rich text with attachment')
50+
message.attachments.attach(test_attachment)
51+
52+
llm_message = message.to_llm
53+
54+
expect(action_text_content).to have_received(:to_plain_text)
55+
expect(llm_message.content).to be_a(RubyLLM::Content)
56+
expect(llm_message.content.text).to eq('Rich text with attachment')
57+
expect(llm_message.content.attachments.first.mime_type).to eq('text/plain')
58+
end
59+
end
60+
end
61+
end

0 commit comments

Comments
 (0)