diff --git a/app/models/article.rb b/app/models/article.rb index 48e0529a2..14450b574 100644 --- a/app/models/article.rb +++ b/app/models/article.rb @@ -33,6 +33,7 @@ # class Article < ApplicationRecord include PgSearch::Model + include LlmFormattable has_many :associated_articles, class_name: :Article, diff --git a/app/models/concerns/llm_formattable.rb b/app/models/concerns/llm_formattable.rb index 086ccc46a..0cdc76718 100644 --- a/app/models/concerns/llm_formattable.rb +++ b/app/models/concerns/llm_formattable.rb @@ -1,7 +1,7 @@ module LlmFormattable extend ActiveSupport::Concern - def to_llm_text - LlmFormatter::LlmTextFormatterService.new(self).format + def to_llm_text(config = {}) + LlmFormatter::LlmTextFormatterService.new(self).format(config) end end diff --git a/app/services/llm_formatter/article_llm_formatter.rb b/app/services/llm_formatter/article_llm_formatter.rb new file mode 100644 index 000000000..5df7976b7 --- /dev/null +++ b/app/services/llm_formatter/article_llm_formatter.rb @@ -0,0 +1,22 @@ +class LlmFormatter::ArticleLlmFormatter + attr_reader :article + + def initialize(article) + @article = article + end + + def format(*) + <<~TEXT + Title: #{article.title} + ID: #{article.id} + Status: #{article.status} + Category: #{article.category&.name || 'Uncategorized'} + Author: #{article.author&.name || 'Unknown'} + Views: #{article.views} + Created At: #{article.created_at} + Updated At: #{article.updated_at} + Content: + #{article.content} + TEXT + end +end diff --git a/app/services/llm_formatter/contact_llm_formatter.rb b/app/services/llm_formatter/contact_llm_formatter.rb index 9dcfcd299..586f8743b 100644 --- a/app/services/llm_formatter/contact_llm_formatter.rb +++ b/app/services/llm_formatter/contact_llm_formatter.rb @@ -1,5 +1,5 @@ class LlmFormatter::ContactLlmFormatter < LlmFormatter::DefaultLlmFormatter - def format + def format(*) sections = [] sections << "Contact ID: ##{@record.id}" sections << 'Contact Attributes:' diff --git a/app/services/llm_formatter/conversation_llm_formatter.rb b/app/services/llm_formatter/conversation_llm_formatter.rb index 07afc2cac..1444d75c1 100644 --- a/app/services/llm_formatter/conversation_llm_formatter.rb +++ b/app/services/llm_formatter/conversation_llm_formatter.rb @@ -1,5 +1,5 @@ class LlmFormatter::ConversationLlmFormatter < LlmFormatter::DefaultLlmFormatter - def format + def format(config = {}) sections = [] sections << "Conversation ID: ##{@record.display_id}" sections << "Channel: #{@record.inbox.channel.name}" @@ -10,6 +10,7 @@ class LlmFormatter::ConversationLlmFormatter < LlmFormatter::DefaultLlmFormatter 'No messages in this conversation' end + sections << "Contact Details: #{@record.contact.to_llm_text}" if config[:include_contact_details] sections.join("\n") end diff --git a/app/services/llm_formatter/default_llm_formatter.rb b/app/services/llm_formatter/default_llm_formatter.rb index b82a039cf..3ac1fa7f4 100644 --- a/app/services/llm_formatter/default_llm_formatter.rb +++ b/app/services/llm_formatter/default_llm_formatter.rb @@ -3,7 +3,7 @@ class LlmFormatter::DefaultLlmFormatter @record = record end - def format + def format(*) # override this end end diff --git a/app/services/llm_formatter/llm_text_formatter_service.rb b/app/services/llm_formatter/llm_text_formatter_service.rb index 0f1f37cf8..198f09d58 100644 --- a/app/services/llm_formatter/llm_text_formatter_service.rb +++ b/app/services/llm_formatter/llm_text_formatter_service.rb @@ -3,9 +3,9 @@ class LlmFormatter::LlmTextFormatterService @record = record end - def format + def format(config = {}) formatter_class = find_formatter - formatter_class.new(@record).format + formatter_class.new(@record).format(config) end private diff --git a/spec/models/article_spec.rb b/spec/models/article_spec.rb index 0dc17d472..161f3541d 100644 --- a/spec/models/article_spec.rb +++ b/spec/models/article_spec.rb @@ -167,4 +167,26 @@ RSpec.describe Article do end end end + + describe '#to_llm_text' do + it 'returns formatted article text' do + category = create(:category, name: 'Test Category', slug: 'test_category', portal_id: portal_1.id) + article = create(:article, title: 'Test Article', category_id: category.id, content: 'This is the content', portal_id: portal_1.id, + author_id: user.id) + expected_output = <<~TEXT + Title: #{article.title} + ID: #{article.id} + Status: #{article.status} + Category: #{category.name} + Author: #{user.name} + Views: #{article.views} + Created At: #{article.created_at} + Updated At: #{article.updated_at} + Content: + #{article.content} + TEXT + + expect(article.to_llm_text).to eq(expected_output) + end + end end diff --git a/spec/services/llm_formatter/article_llm_formatter_spec.rb b/spec/services/llm_formatter/article_llm_formatter_spec.rb new file mode 100644 index 000000000..0f47beddb --- /dev/null +++ b/spec/services/llm_formatter/article_llm_formatter_spec.rb @@ -0,0 +1,44 @@ +require 'rails_helper' + +RSpec.describe LlmFormatter::ArticleLlmFormatter do + let(:account) { create(:account) } + let(:portal) { create(:portal, account: account) } + let(:category) { create(:category, slug: 'test_category', portal: portal, account: account) } + let(:author) { create(:user, account: account) } + let(:formatter) { described_class.new(article) } + + describe '#format' do + context 'when article has all details' do + let(:article) do + create(:article, + slug: 'test_article', + portal: portal, category: category, author: author, views: 100, account: account) + end + + it 'formats article details correctly' do + expected_output = <<~TEXT + Title: #{article.title} + ID: #{article.id} + Status: #{article.status} + Category: #{category.name} + Author: #{author.name} + Views: #{article.views} + Created At: #{article.created_at} + Updated At: #{article.updated_at} + Content: + #{article.content} + TEXT + + expect(formatter.format).to eq(expected_output) + end + end + + context 'when article has no category' do + let(:article) { create(:article, portal: portal, category: nil, author: author, account: account) } + + it 'shows Uncategorized for category' do + expect(formatter.format).to include('Category: Uncategorized') + end + end + end +end diff --git a/spec/services/llm_formatter/contact_llm_formatter_spec.rb b/spec/services/llm_formatter/contact_llm_formatter_spec.rb new file mode 100644 index 000000000..bf3345b98 --- /dev/null +++ b/spec/services/llm_formatter/contact_llm_formatter_spec.rb @@ -0,0 +1,78 @@ +require 'rails_helper' + +RSpec.describe LlmFormatter::ContactLlmFormatter do + let(:account) { create(:account) } + let(:contact) { create(:contact, account: account, name: 'John Doe', email: 'john@example.com', phone_number: '+1234567890') } + let(:formatter) { described_class.new(contact) } + + describe '#format' do + context 'when contact has no notes' do + it 'formats contact details correctly' do + expected_output = [ + "Contact ID: ##{contact.id}", + 'Contact Attributes:', + 'Name: John Doe', + 'Email: john@example.com', + 'Phone: +1234567890', + 'Location: ', + 'Country Code: ', + 'Contact Notes:', + 'No notes for this contact' + ].join("\n") + + expect(formatter.format).to eq(expected_output) + end + end + + context 'when contact has notes' do + before do + create(:note, account: account, contact: contact, content: 'First interaction') + create(:note, account: account, contact: contact, content: 'Follow up needed') + end + + it 'includes notes in the output' do + expected_output = [ + "Contact ID: ##{contact.id}", + 'Contact Attributes:', + 'Name: John Doe', + 'Email: john@example.com', + 'Phone: +1234567890', + 'Location: ', + 'Country Code: ', + 'Contact Notes:', + ' - First interaction', + ' - Follow up needed' + ].join("\n") + + expect(formatter.format).to eq(expected_output) + end + end + + context 'when contact has custom attributes' do + let!(:custom_attribute) do + create(:custom_attribute_definition, account: account, attribute_model: 'contact_attribute', attribute_display_name: 'Company') + end + + before do + contact.update(custom_attributes: { custom_attribute.attribute_key => 'Acme Inc' }) + end + + it 'includes custom attributes in the output' do + expected_output = [ + "Contact ID: ##{contact.id}", + 'Contact Attributes:', + 'Name: John Doe', + 'Email: john@example.com', + 'Phone: +1234567890', + 'Location: ', + 'Country Code: ', + 'Company: Acme Inc', + 'Contact Notes:', + 'No notes for this contact' + ].join("\n") + + expect(formatter.format).to eq(expected_output) + end + end + end +end diff --git a/spec/services/llm_formatter/conversation_llm_formatter_spec.rb b/spec/services/llm_formatter/conversation_llm_formatter_spec.rb index 3838b8126..93fec14f7 100644 --- a/spec/services/llm_formatter/conversation_llm_formatter_spec.rb +++ b/spec/services/llm_formatter/conversation_llm_formatter_spec.rb @@ -47,5 +47,19 @@ RSpec.describe LlmFormatter::ConversationLlmFormatter do expect(formatter.format).to eq(expected_output) end end + + context 'when include_contact_details is true' do + it 'includes contact details' do + expected_output = [ + "Conversation ID: ##{conversation.display_id}", + "Channel: #{conversation.inbox.channel.name}", + 'Message History:', + 'No messages in this conversation', + "Contact Details: #{conversation.contact.to_llm_text}" + ].join("\n") + + expect(formatter.format(include_contact_details: true)).to eq(expected_output) + end + end end end