diff --git a/enterprise/app/services/enterprise/message_templates/response_bot_service.rb b/enterprise/app/services/enterprise/message_templates/response_bot_service.rb index ddb986975..a8a4d4907 100644 --- a/enterprise/app/services/enterprise/message_templates/response_bot_service.rb +++ b/enterprise/app/services/enterprise/message_templates/response_bot_service.rb @@ -3,8 +3,8 @@ class Enterprise::MessageTemplates::ResponseBotService def perform ActiveRecord::Base.transaction do - response = get_response(conversation.messages.last.content) - process_response(response['response']) + @response = get_response(conversation.messages.last.content) + process_response end rescue StandardError => e process_action('handoff') # something went wrong, pass to agent @@ -44,11 +44,11 @@ class Enterprise::MessageTemplates::ResponseBotService message.message_type == 'incoming' ? 'user' : 'system' end - def process_response(response) - if response == 'conversation_handoff' + def process_response + if @response['response'] == 'conversation_handoff' process_action('handoff') else - create_messages(response) + create_messages end end @@ -61,61 +61,47 @@ class Enterprise::MessageTemplates::ResponseBotService end end - def create_messages(response) - response = process_response_content(response).first - create_outgoing_message(response) + def create_messages + message_content = @response['response'] + + message_content = append_message_with_sources(message_content) + + create_outgoing_message(message_content) end - def process_response_content(response) - # Regular expression to match '{context_ids: [ids]}' - regex = /{context_ids: \[(\d+(?:, *\d+)*)\]}/ + def append_message_with_sources(message_content) + article_ids = @response['context_ids'] + return message_content if article_ids.blank? - # Extract ids from string - id_string = response[regex, 1] # This will give you '42, 43' - article_ids = id_string.split(',').map(&:to_i) if id_string # This will give you [42, 43] + message_content += "\n \n \n **Sources** \n" + articles_hash = get_article_hash(article_ids.uniq) - # Remove '{context_ids: [ids]}' from string - response = response.sub(regex, '') - - [response, article_ids] + articles_hash.first(3).each do |article_hash| + message_content += " - [#{article_hash[:response].question}](#{article_hash[:response_document].document_link}) \n" + end + message_content end - def create_outgoing_message(response) + def create_outgoing_message(message_content) conversation.messages.create!( { message_type: :outgoing, account_id: conversation.account_id, inbox_id: conversation.inbox_id, - content: response - } - ) - end - - def create_outgoing_message_with_cards(article_ids, conversation) - content_attributes = get_article_hash(article_ids.uniq) - return if content_attributes.blank? - - conversation.messages.create!( - { - message_type: :outgoing, - account_id: conversation.account_id, - inbox_id: conversation.inbox_id, - content: 'suggested articles', - content_type: 'article', - content_attributes: content_attributes + content: message_content } ) end def get_article_hash(article_ids) - items = [] - article_ids.each do |article_id| + seen_documents = Set.new + article_ids.uniq.filter_map do |article_id| response = Response.find(article_id) - next if response.nil? + response_document = response.response_document + next if response_document.blank? || seen_documents.include?(response_document) - items << { title: response.question, description: response.answer[0, 120], link: response.response_document.document_link } + seen_documents << response_document + { response: response, response_document: response_document } end - - items.present? ? { items: items } : {} end end diff --git a/enterprise/app/services/openai/embeddings_service.rb b/enterprise/app/services/openai/embeddings_service.rb index 2ace41f1c..b79167203 100644 --- a/enterprise/app/services/openai/embeddings_service.rb +++ b/enterprise/app/services/openai/embeddings_service.rb @@ -8,7 +8,7 @@ class Openai::EmbeddingsService def fetch_embeddings(input) url = 'https://api.openai.com/v1/embeddings' headers = { - 'Authorization' => "Bearer #{ENV.fetch('OPENAI_API_KEY')}", + 'Authorization' => "Bearer #{ENV.fetch('OPENAI_API_KEY', '')}", 'Content-Type' => 'application/json' } data = { @@ -17,6 +17,6 @@ class Openai::EmbeddingsService } response = Net::HTTP.post(URI(url), data.to_json, headers) - JSON.parse(response.body)['data'].pick('embedding') + JSON.parse(response.body)['data']&.pick('embedding') end end diff --git a/spec/enterprise/services/enterprise/message_templates/response_bot_service_spec.rb b/spec/enterprise/services/enterprise/message_templates/response_bot_service_spec.rb index 5b3d89f17..f8d8863b1 100644 --- a/spec/enterprise/services/enterprise/message_templates/response_bot_service_spec.rb +++ b/spec/enterprise/services/enterprise/message_templates/response_bot_service_spec.rb @@ -4,27 +4,32 @@ RSpec.describe Enterprise::MessageTemplates::ResponseBotService, type: :service let!(:conversation) { create(:conversation, status: :pending) } let(:service) { described_class.new(conversation: conversation) } let(:chat_gpt_double) { instance_double(ChatGpt) } + let(:response_source) { create(:response_source, account: conversation.account) } let(:response_object) { instance_double(Response, id: 1, question: 'Q1', answer: 'A1') } before do # Uncomment if you want to run the spec in your local machine # Features::ResponseBotService.new.enable_in_installation skip('Skipping since vector is not enabled in this environment') unless Features::ResponseBotService.new.vector_extension_enabled? - + stub_request(:post, 'https://api.openai.com/v1/embeddings').to_return(status: 200, body: {}.to_json, + headers: { Content_Type: 'application/json' }) create(:message, message_type: :incoming, conversation: conversation, content: 'Hi') + 4.times { create(:response, response_source: response_source) } allow(ChatGpt).to receive(:new).and_return(chat_gpt_double) - allow(chat_gpt_double).to receive(:generate_response).and_return({ 'response' => 'some_response', :context_ids => [1, 2] }) + allow(chat_gpt_double).to receive(:generate_response).and_return({ 'response' => 'some_response', 'context_ids' => Response.all.map(&:id) }) allow(conversation.inbox).to receive(:get_responses).and_return([response_object]) end describe '#perform' do context 'when successful' do - it 'creates an outgoing message' do + it 'creates an outgoing message along with article references' do expect do service.perform end.to change { conversation.messages.where(message_type: :outgoing).count }.by(1) - expect(conversation.messages.last.content).to eq('some_response') + last_message = conversation.messages.last + expect(last_message.content).to include('some_response') + expect(last_message.content).to include(Response.first.question) end end diff --git a/spec/factories/response.rb b/spec/factories/response.rb new file mode 100644 index 000000000..b4d3e15e8 --- /dev/null +++ b/spec/factories/response.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :response do + response_document + response_source + question { Faker::Lorem.sentence } + answer { Faker::Lorem.paragraph } + account + end +end diff --git a/spec/factories/response_document.rb b/spec/factories/response_document.rb new file mode 100644 index 000000000..666e64e0a --- /dev/null +++ b/spec/factories/response_document.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :response_document do + response_source + content { Faker::Lorem.paragraph } + document_link { Faker::Internet.url } + account + end +end