mirror of
https://github.com/lingble/chatwoot.git
synced 2025-10-30 18:47:51 +00:00
chore: Update LLM formatter classes to include additional details (#11491)
This PR introduces support for optionally exposing more data during LLM function calls. This will be useful as we expand Copilot’s capabilities. Changes included: - Add support for ArticleLlmFormatter - Add missing specs for ContactLLMFormatter and ArticleLLMFormatter - Add additional spec for ConversationLLMFormatter based on config
This commit is contained in:
@@ -33,6 +33,7 @@
|
|||||||
#
|
#
|
||||||
class Article < ApplicationRecord
|
class Article < ApplicationRecord
|
||||||
include PgSearch::Model
|
include PgSearch::Model
|
||||||
|
include LlmFormattable
|
||||||
|
|
||||||
has_many :associated_articles,
|
has_many :associated_articles,
|
||||||
class_name: :Article,
|
class_name: :Article,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
module LlmFormattable
|
module LlmFormattable
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
def to_llm_text
|
def to_llm_text(config = {})
|
||||||
LlmFormatter::LlmTextFormatterService.new(self).format
|
LlmFormatter::LlmTextFormatterService.new(self).format(config)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
22
app/services/llm_formatter/article_llm_formatter.rb
Normal file
22
app/services/llm_formatter/article_llm_formatter.rb
Normal file
@@ -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
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
class LlmFormatter::ContactLlmFormatter < LlmFormatter::DefaultLlmFormatter
|
class LlmFormatter::ContactLlmFormatter < LlmFormatter::DefaultLlmFormatter
|
||||||
def format
|
def format(*)
|
||||||
sections = []
|
sections = []
|
||||||
sections << "Contact ID: ##{@record.id}"
|
sections << "Contact ID: ##{@record.id}"
|
||||||
sections << 'Contact Attributes:'
|
sections << 'Contact Attributes:'
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
class LlmFormatter::ConversationLlmFormatter < LlmFormatter::DefaultLlmFormatter
|
class LlmFormatter::ConversationLlmFormatter < LlmFormatter::DefaultLlmFormatter
|
||||||
def format
|
def format(config = {})
|
||||||
sections = []
|
sections = []
|
||||||
sections << "Conversation ID: ##{@record.display_id}"
|
sections << "Conversation ID: ##{@record.display_id}"
|
||||||
sections << "Channel: #{@record.inbox.channel.name}"
|
sections << "Channel: #{@record.inbox.channel.name}"
|
||||||
@@ -10,6 +10,7 @@ class LlmFormatter::ConversationLlmFormatter < LlmFormatter::DefaultLlmFormatter
|
|||||||
'No messages in this conversation'
|
'No messages in this conversation'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sections << "Contact Details: #{@record.contact.to_llm_text}" if config[:include_contact_details]
|
||||||
sections.join("\n")
|
sections.join("\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ class LlmFormatter::DefaultLlmFormatter
|
|||||||
@record = record
|
@record = record
|
||||||
end
|
end
|
||||||
|
|
||||||
def format
|
def format(*)
|
||||||
# override this
|
# override this
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ class LlmFormatter::LlmTextFormatterService
|
|||||||
@record = record
|
@record = record
|
||||||
end
|
end
|
||||||
|
|
||||||
def format
|
def format(config = {})
|
||||||
formatter_class = find_formatter
|
formatter_class = find_formatter
|
||||||
formatter_class.new(@record).format
|
formatter_class.new(@record).format(config)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -167,4 +167,26 @@ RSpec.describe Article do
|
|||||||
end
|
end
|
||||||
end
|
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
|
end
|
||||||
|
|||||||
44
spec/services/llm_formatter/article_llm_formatter_spec.rb
Normal file
44
spec/services/llm_formatter/article_llm_formatter_spec.rb
Normal file
@@ -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
|
||||||
78
spec/services/llm_formatter/contact_llm_formatter_spec.rb
Normal file
78
spec/services/llm_formatter/contact_llm_formatter_spec.rb
Normal file
@@ -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
|
||||||
@@ -47,5 +47,19 @@ RSpec.describe LlmFormatter::ConversationLlmFormatter do
|
|||||||
expect(formatter.format).to eq(expected_output)
|
expect(formatter.format).to eq(expected_output)
|
||||||
end
|
end
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user