chore(cleanup): Delete sentiment feature (#9304)

- The feature is unused, removing it for now, will bring it back with better models later.
This commit is contained in:
Pranav
2024-04-25 22:49:10 -07:00
committed by GitHub
parent 77db0d0701
commit ffd47081bd
11 changed files with 14 additions and 312 deletions

View File

@@ -249,9 +249,6 @@ AZURE_APP_SECRET=
## OpenAI key
# OPENAI_API_KEY=
# Sentiment analysis model file path
SENTIMENT_FILE_PATH=
# Housekeeping/Performance related configurations
# Set to true if you want to remove stale contact inboxes
# contact_inboxes with no conversation older than 90 days will be removed

View File

@@ -175,9 +175,6 @@ gem 'pgvector'
# Convert Website HTML to Markdown
gem 'reverse_markdown'
# Sentiment analysis
gem 'informers'
### Gems required only in specific deployment environments ###
##############################################################

View File

@@ -151,7 +151,6 @@ GEM
base64 (0.1.1)
bcrypt (3.1.20)
bindex (0.8.1)
blingfire (0.1.8)
bootsnap (1.16.0)
msgpack (~> 1.2)
brakeman (5.4.1)
@@ -316,15 +315,15 @@ GEM
google-cloud-translate-v3 (0.6.0)
gapic-common (>= 0.17.1, < 2.a)
google-cloud-errors (~> 1.0)
google-protobuf (3.25.2)
google-protobuf (3.25.2-arm64-darwin)
google-protobuf (3.25.2-x86_64-darwin)
google-protobuf (3.25.2-x86_64-linux)
google-protobuf (3.25.3)
google-protobuf (3.25.3-arm64-darwin)
google-protobuf (3.25.3-x86_64-darwin)
google-protobuf (3.25.3-x86_64-linux)
googleapis-common-protos (1.4.0)
google-protobuf (~> 3.14)
googleapis-common-protos-types (~> 1.2)
grpc (~> 1.27)
googleapis-common-protos-types (1.11.0)
googleapis-common-protos-types (1.14.0)
google-protobuf (~> 3.18)
googleauth (1.5.2)
faraday (>= 0.17.3, < 3.a)
@@ -335,14 +334,17 @@ GEM
signet (>= 0.16, < 2.a)
groupdate (6.2.1)
activesupport (>= 5.2)
grpc (1.54.3)
google-protobuf (~> 3.21)
grpc (1.62.0)
google-protobuf (~> 3.25)
googleapis-common-protos-types (~> 1.0)
grpc (1.54.3-x86_64-darwin)
google-protobuf (~> 3.21)
grpc (1.62.0-arm64-darwin)
google-protobuf (~> 3.25)
googleapis-common-protos-types (~> 1.0)
grpc (1.54.3-x86_64-linux)
google-protobuf (~> 3.21)
grpc (1.62.0-x86_64-darwin)
google-protobuf (~> 3.25)
googleapis-common-protos-types (~> 1.0)
grpc (1.62.0-x86_64-linux)
google-protobuf (~> 3.25)
googleapis-common-protos-types (~> 1.0)
haikunator (1.1.1)
hairtrigger (1.0.0)
@@ -371,10 +373,6 @@ GEM
image_processing (1.12.2)
mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3)
informers (0.2.0)
blingfire (>= 0.1.7)
numo-narray
onnxruntime (>= 0.5.1)
io-console (0.6.0)
irb (1.7.2)
reline (>= 0.3.6)
@@ -500,7 +498,6 @@ GEM
racc (~> 1.4)
nokogiri (1.16.4-x86_64-linux)
racc (~> 1.4)
numo-narray (0.9.2.1)
oauth (1.1.0)
oauth-tty (~> 1.0, >= 1.0.1)
snaky_hash (~> 2.0)
@@ -529,14 +526,6 @@ GEM
omniauth-rails_csrf_protection (1.0.1)
actionpack (>= 4.2)
omniauth (~> 2.0)
onnxruntime (0.7.6)
ffi
onnxruntime (0.7.6-arm64-darwin)
ffi
onnxruntime (0.7.6-x86_64-darwin)
ffi
onnxruntime (0.7.6-x86_64-linux)
ffi
openssl (3.1.0)
orm_adapter (0.5.0)
os (1.1.4)
@@ -892,7 +881,6 @@ DEPENDENCIES
hashie
html2text!
image_processing
informers
jbuilder
json_refs
json_schemer

View File

@@ -310,5 +310,4 @@ class Conversation < ApplicationRecord
end
Conversation.include_mod_with('Concerns::Conversation')
Conversation.include_mod_with('SentimentAnalysisHelper')
Conversation.prepend_mod_with('Conversation')

View File

@@ -269,7 +269,6 @@ class Message < ApplicationRecord
reopen_conversation
notify_via_mail
set_conversation_activity
update_message_sentiments
dispatch_create_events
send_reply
execute_message_template_hooks
@@ -406,10 +405,6 @@ class Message < ApplicationRecord
conversation.update_columns(last_activity_at: created_at)
# rubocop:enable Rails/SkipsModelValidations
end
def update_message_sentiments
# override in the enterprise ::Enterprise::SentimentAnalysisJob.perform_later(self)
end
end
Message.prepend_mod_with('Message')

View File

@@ -1,57 +0,0 @@
class Enterprise::SentimentAnalysisJob < ApplicationJob
queue_as :low
def perform(message)
return if message.account.locale != 'en' || !valid_incoming_message?(message)
save_message_sentiment(message)
rescue StandardError => e
Rails.logger.error("Sentiment Analysis Error for message #{message.id}: #{e}")
ChatwootExceptionTracker.new(e, account: message.account).capture_exception
end
def save_message_sentiment(message)
# We are truncating the data here to avoind the OnnxRuntime::Error
# Indices element out of data bounds, idx=512 must be within the inclusive range [-512,511]
# While gathering the maningfull node the Array/tensor index is going out of bound
text = message.content&.truncate(2900)
return if model.blank?
sentiment = model.predict(text)
message.sentiment = sentiment.merge(value: label_val(sentiment))
message.save!
end
# Model initializes OnnxRuntime::Model, with given file for inference session and to create the tensor
def model
model_file = save_and_open_sentiment_file
return if File.empty?(model_file)
Informers::SentimentAnalysis.new(model_file)
end
def label_val(sentiment)
sentiment[:label] == 'positive' ? 1 : -1
end
def valid_incoming_message?(message)
message.incoming? && message.content.present? && !message.private?
end
# returns the sentiment file from vendor folder else download it to the path from AWS-S3
def save_and_open_sentiment_file
model_path = ENV.fetch('SENTIMENT_FILE_PATH', nil)
sentiment_file = Rails.root.join('vendor/db/sentiment-analysis.onnx')
return sentiment_file if File.exist?(sentiment_file)
source_file = Down.download(model_path) # Download file from AWS-S3
File.rename(source_file, sentiment_file) # Change the file path
sentiment_file
end
end

View File

@@ -1,5 +0,0 @@
module Enterprise::Message
def update_message_sentiments
::Enterprise::SentimentAnalysisJob.perform_later(self) if ENV.fetch('SENTIMENT_FILE_PATH', nil).present?
end
end

View File

@@ -1,45 +0,0 @@
module Enterprise::SentimentAnalysisHelper
extend ActiveSupport::Concern
included do
def opening_sentiments
records = incoming_messages.first(average_message_count)
average_sentiment(records)
end
def closing_sentiments
return unless resolved?
records = incoming_messages.last(average_message_count)
average_sentiment(records)
end
def average_sentiment(records)
{
label: average_sentiment_label(records),
score: average_sentiment_score(records)
}
end
private
def average_sentiment_label(records)
value = records.pluck(:sentiment).sum { |a| a['value'].to_i }
value.negative? ? 'negative' : 'positive'
end
def average_sentiment_score(records)
total = records.pluck(:sentiment).sum { |a| a['score'].to_f }
total / average_message_count
end
def average_message_count
# incoming_messages.count >= 10 ? 5 : ((incoming_messages.count / 2) - 1)
5
end
def incoming_messages
messages.incoming.where(private: false)
end
end
end

View File

@@ -1,117 +0,0 @@
require 'rails_helper'
RSpec.describe Enterprise::SentimentAnalysisJob do
context 'when account locale set to english language' do
let(:account) { create(:account, locale: 'en') }
let(:message) { build(:message, content_type: nil, account: account) }
context 'when update the message sentiments' do
let(:model_path) { Rails.root.join('vendor/db/sentiment-analysis.onnx') }
let(:model) { double }
before do
allow(Informers::SentimentAnalysis).to receive(:new).with(model_path).and_return(model)
allow(model).to receive(:predict).and_return({ label: 'positive', score: '0.6' })
end
it 'with incoming message' do
with_modified_env SENTIMENT_FILE_PATH: 'sentiment-analysis.onnx' do
message.update(message_type: :incoming)
described_class.perform_now(message)
expect(message.sentiment).not_to be_empty
end
end
it 'update sentiment label for positive message' do
with_modified_env SENTIMENT_FILE_PATH: 'sentiment-analysis.onnx' do
message.update(message_type: :incoming, content: 'I like your product')
described_class.perform_now(message)
expect(message.sentiment).not_to be_empty
expect(message.sentiment['label']).to eq('positive')
expect(message.sentiment['value']).to eq(1)
end
end
it 'update sentiment label for negative message' do
with_modified_env SENTIMENT_FILE_PATH: 'sentiment-analysis.onnx' do
message.update(message_type: :incoming, content: 'I did not like your product')
allow(model).to receive(:predict).and_return({ label: 'negative', score: '0.6' })
described_class.perform_now(message)
expect(message.sentiment).not_to be_empty
expect(message.sentiment['label']).to eq('negative')
expect(message.sentiment['value']).to eq(-1)
end
end
end
context 'with download sentiment files' do
let(:model_path) { nil }
let(:model) { double }
before do
allow(Informers::SentimentAnalysis).to receive(:new).with(model_path).and_return(model)
allow(model).to receive(:predict).and_return({ label: 'positive', score: '0.6' })
end
it 'fetch saved file in the server' do
with_modified_env SENTIMENT_FILE_PATH: 'sentiment-analysis.onnx' do
message.update(message_type: :incoming, content: 'I did not like your product')
described_class.new(message).save_and_open_sentiment_file
sentiment_file = Rails.root.join('vendor/db/sentiment-analysis.onnx')
expect(File).to exist(sentiment_file)
end
end
it 'fetch file from the storage' do
with_modified_env SENTIMENT_FILE_PATH: 'sentiment-analysis.onnx' do
message.update(message_type: :incoming, content: 'I did not like your product')
allow(File).to receive(:exist?).and_return(false)
allow(Down).to receive(:download).and_return('./sentiment-analysis.onnx')
allow(File).to receive(:rename).and_return(100)
described_class.new(message).save_and_open_sentiment_file
sentiment_file = Rails.root.join('vendor/db/sentiment-analysis.onnx')
expect(sentiment_file).to be_present
end
end
end
context 'when does not update the message sentiments' do
it 'with outgoing message' do
message.update(message_type: :outgoing)
described_class.perform_now(message)
expect(message.sentiment).to be_empty
end
it 'with private message' do
message.update(private: true)
described_class.perform_now(message)
expect(message.sentiment).to be_empty
end
end
end
context 'when account locale is not set to english language' do
let(:account) { create(:account, locale: 'es') }
let(:message) { build(:message, content_type: nil, account: account) }
it 'does not update the message sentiments' do
described_class.perform_now(message)
expect(message.sentiment).to be_empty
end
end
end

View File

@@ -36,36 +36,6 @@ RSpec.describe Conversation, type: :model do
# end
end
describe 'conversation sentiments' do
include ActiveJob::TestHelper
let(:conversation) { create(:conversation, additional_attributes: { referer: 'https://www.chatwoot.com/' }) }
before do
10.times do
message = create(:message, conversation_id: conversation.id, account_id: conversation.account_id, message_type: 'incoming')
message.update(sentiment: { 'label': 'positive', score: '0.4' })
end
end
it 'returns opening sentiments' do
sentiments = conversation.opening_sentiments
expect(sentiments[:label]).to eq('positive')
end
it 'returns closing sentiments if conversation is not resolved' do
sentiments = conversation.closing_sentiments
expect(sentiments).to be_nil
end
it 'returns closing sentiments if it is resolved' do
conversation.resolved!
sentiments = conversation.closing_sentiments
expect(sentiments[:label]).to eq('positive')
end
end
describe 'sla_policy' do
let(:account) { create(:account) }
let(:conversation) { create(:conversation, account: account) }

View File

@@ -1,20 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
require Rails.root.join 'spec/models/concerns/liquidable_shared.rb'
RSpec.describe Message do
context 'with sentiment analysis' do
let(:message) { build(:message, message_type: :incoming, content_type: nil, account: create(:account)) }
it 'calls SentimentAnalysisJob' do
with_modified_env SENTIMENT_FILE_PATH: 'sentiment-analysis.onnx' do
allow(Enterprise::SentimentAnalysisJob).to receive(:perform_later).and_return(:perform_later).with(message)
message.save!
expect(Enterprise::SentimentAnalysisJob).to have_received(:perform_later)
end
end
end
end