mirror of
https://github.com/lingble/chatwoot.git
synced 2025-10-29 18:22:53 +00:00
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:
@@ -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
|
||||
|
||||
3
Gemfile
3
Gemfile
@@ -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 ###
|
||||
##############################################################
|
||||
|
||||
|
||||
40
Gemfile.lock
40
Gemfile.lock
@@ -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
|
||||
|
||||
@@ -310,5 +310,4 @@ class Conversation < ApplicationRecord
|
||||
end
|
||||
|
||||
Conversation.include_mod_with('Concerns::Conversation')
|
||||
Conversation.include_mod_with('SentimentAnalysisHelper')
|
||||
Conversation.prepend_mod_with('Conversation')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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) }
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user