chore: Update the behavior of Captain resolutions (#10794)
This PR ensures that only conversations from quick conversation channels are resolved, avoiding resolutions on the email channel (we still need to improve the UX here). It also updates the FAQ generation logic, limiting it to conversations that had at least one human interaction.
This commit is contained in:
@@ -9,7 +9,13 @@ module Enterprise::Account::ConversationsResolutionSchedulerJob
|
||||
|
||||
def resolve_captain_conversations
|
||||
CaptainInbox.all.find_each(batch_size: 100) do |captain_inbox|
|
||||
Captain::InboxPendingConversationsResolutionJob.perform_later(captain_inbox.inbox)
|
||||
inbox = captain_inbox.inbox
|
||||
|
||||
next if inbox.email?
|
||||
|
||||
Captain::InboxPendingConversationsResolutionJob.perform_later(
|
||||
inbox
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,7 +8,11 @@ class Captain::Llm::ConversationFaqService < Captain::Llm::BaseOpenAiService
|
||||
@content = conversation.to_llm_text
|
||||
end
|
||||
|
||||
# Generates and deduplicates FAQs from conversation content
|
||||
# Skips processing if there was no human interaction
|
||||
def generate_and_deduplicate
|
||||
return [] if no_human_interaction?
|
||||
|
||||
new_faqs = generate
|
||||
return [] if new_faqs.empty?
|
||||
|
||||
@@ -21,6 +25,10 @@ class Captain::Llm::ConversationFaqService < Captain::Llm::BaseOpenAiService
|
||||
|
||||
attr_reader :content, :conversation, :assistant
|
||||
|
||||
def no_human_interaction?
|
||||
conversation.first_reply_created_at.nil?
|
||||
end
|
||||
|
||||
def find_and_separate_duplicates(faqs)
|
||||
duplicate_faqs = []
|
||||
unique_faqs = []
|
||||
|
||||
@@ -1,29 +1,44 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Account::ConversationsResolutionSchedulerJob, type: :job do
|
||||
let!(:account_with_bot) { create(:account) }
|
||||
let(:account) { create(:account) }
|
||||
let(:assistant) { create(:captain_assistant, account: account_with_bot) }
|
||||
|
||||
let!(:account_without_bot) { create(:account) }
|
||||
let!(:inbox_with_bot) { create(:inbox, account: account_with_bot) }
|
||||
let!(:inbox_without_bot) { create(:inbox, account: account_without_bot) }
|
||||
let(:assistant) { create(:captain_assistant, account: account) }
|
||||
|
||||
describe '#perform - captain resolutions' do
|
||||
before do
|
||||
create(:captain_inbox, captain_assistant: assistant, inbox: inbox_with_bot)
|
||||
context 'when handling different inbox types' do
|
||||
let!(:regular_inbox) { create(:inbox, account: account) }
|
||||
let!(:email_inbox) { create(:inbox, :with_email, account: account) }
|
||||
|
||||
before do
|
||||
create(:captain_inbox, captain_assistant: assistant, inbox: regular_inbox)
|
||||
create(:captain_inbox, captain_assistant: assistant, inbox: email_inbox)
|
||||
end
|
||||
|
||||
it 'enqueues resolution jobs only for non-email inboxes with captain enabled' do
|
||||
expect do
|
||||
described_class.perform_now
|
||||
end.to have_enqueued_job(Captain::InboxPendingConversationsResolutionJob)
|
||||
.with(regular_inbox)
|
||||
.exactly(:once)
|
||||
end
|
||||
|
||||
it 'does not enqueue resolution jobs for email inboxes even with captain enabled' do
|
||||
expect do
|
||||
described_class.perform_now
|
||||
end.not_to have_enqueued_job(Captain::InboxPendingConversationsResolutionJob)
|
||||
.with(email_inbox)
|
||||
end
|
||||
end
|
||||
|
||||
it 'enqueues resolution jobs only for inboxes with captain enabled' do
|
||||
expect do
|
||||
described_class.perform_now
|
||||
end.to have_enqueued_job(Captain::InboxPendingConversationsResolutionJob).with(inbox_with_bot).and have_enqueued_job.exactly(:once)
|
||||
end
|
||||
context 'when inbox has no captain enabled' do
|
||||
let!(:inbox_without_captain) { create(:inbox, account: create(:account)) }
|
||||
|
||||
it 'does not enqueue resolution jobs for inboxes without captain enabled' do
|
||||
expect do
|
||||
described_class.perform_now
|
||||
end.not_to have_enqueued_job(Captain::InboxPendingConversationsResolutionJob).with(inbox_without_bot)
|
||||
it 'does not enqueue resolution jobs' do
|
||||
expect do
|
||||
described_class.perform_now
|
||||
end.not_to have_enqueued_job(Captain::InboxPendingConversationsResolutionJob)
|
||||
.with(inbox_without_captain)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@ require 'rails_helper'
|
||||
|
||||
RSpec.describe Captain::Llm::ConversationFaqService do
|
||||
let(:captain_assistant) { create(:captain_assistant) }
|
||||
let(:conversation) { create(:conversation) }
|
||||
let(:conversation) { create(:conversation, first_reply_created_at: Time.zone.now) }
|
||||
let(:service) { described_class.new(captain_assistant, conversation) }
|
||||
let(:client) { instance_double(OpenAI::Client) }
|
||||
let(:embedding_service) { instance_double(Captain::Llm::EmbeddingService) }
|
||||
@@ -57,6 +57,14 @@ RSpec.describe Captain::Llm::ConversationFaqService do
|
||||
end
|
||||
end
|
||||
|
||||
context 'without human interaction' do
|
||||
let(:conversation) { create(:conversation) }
|
||||
|
||||
it 'returns an empty array without generating FAQs' do
|
||||
expect(service.generate_and_deduplicate).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when finding duplicates' do
|
||||
let(:existing_response) do
|
||||
create(:captain_assistant_response, assistant: captain_assistant, question: 'Similar question', answer: 'Similar answer')
|
||||
|
||||
@@ -9,5 +9,10 @@ FactoryBot.define do
|
||||
after(:create) do |inbox|
|
||||
inbox.channel.save!
|
||||
end
|
||||
|
||||
trait :with_email do
|
||||
channel { FactoryBot.build(:channel_email, account: account) }
|
||||
name { 'Email Inbox' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user