From e8d0f14554ef2fdbff05d2f62c7a54b28bc90eca Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 5 Jul 2023 14:07:47 +0530 Subject: [PATCH] feat: move label suggestions to enterprise --- .../integrations/openai_processor_service.rb | 54 +++++++++++++++++++ lib/integrations/openai/processor_service.rb | 2 + lib/integrations/openai_processor_service.rb | 12 +++-- 3 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 enterprise/lib/enterprise/integrations/openai_processor_service.rb diff --git a/enterprise/lib/enterprise/integrations/openai_processor_service.rb b/enterprise/lib/enterprise/integrations/openai_processor_service.rb new file mode 100644 index 000000000..3f0ee8c4c --- /dev/null +++ b/enterprise/lib/enterprise/integrations/openai_processor_service.rb @@ -0,0 +1,54 @@ +module Enterprise::Integrations::OpenaiProcessorService + ALLOWED_EVENT_NAMES = %w[rephrase summarize reply_suggestion label_suggestion].freeze + CACHEABLE_EVENTS = %w[label_suggestion].freeze + + def label_suggestion_message + payload = label_suggestion_body + return nil if payload.blank? + + response = make_api_call(label_suggestion_body) + + # LLMs are not deterministic, so this is bandaid solution + # To what you ask? Sometimes, the response includes + # "Labels:" in it's response in some format. This is a hacky way to remove it + # TODO: Fix with with a better prompt + response.gsub(/^(label|labels):/i, '') + end + + private + + def labels_with_messages + labels = hook.account.labels.pluck(:title).join(', ') + + character_count = labels.length + conversation = find_conversation + messages = init_messages_body(false) + add_messages_until_token_limit(conversation, messages, false, character_count) + + return nil if messages.blank? || labels.blank? + + "Messages:\n#{messages}\nLabels:\n#{labels}" + end + + def label_suggestion_body + content = labels_with_messages + return nil if content.blank? + + { + model: self.class::GPT_MODEL, + messages: [ + { + role: 'system', + content: 'Your role is as an assistant to a customer support agent. You will be provided with a transcript of a conversation between a ' \ + 'customer and the support agent, along with a list of potential labels. ' \ + 'Your task is to analyze the conversation and select the two labels from the given list that most accurately ' \ + 'represent the themes or issues discussed. Ensure you preserve the exact casing of the labels as they are provided in the list. ' \ + 'Do not create new labels; only choose from those provided. Once you have made your selections, please provide your response ' \ + 'as a comma-separated list of the provided labels. Remember, your response should only contain the labels you\'ve selected, ' \ + 'in their original casing, and nothing else. ' + }, + { role: 'user', content: content } + ] + }.to_json + end +end diff --git a/lib/integrations/openai/processor_service.rb b/lib/integrations/openai/processor_service.rb index ff9fa4ce0..9d90117ff 100644 --- a/lib/integrations/openai/processor_service.rb +++ b/lib/integrations/openai/processor_service.rb @@ -141,3 +141,5 @@ class Integrations::Openai::ProcessorService < Integrations::OpenaiProcessorServ }.to_json end end + +Integrations::Openai::ProcessorService.prepend_mod_with('Integrations::OpenaiProcessorService') diff --git a/lib/integrations/openai_processor_service.rb b/lib/integrations/openai_processor_service.rb index 9cf5a70c8..d6e69371e 100644 --- a/lib/integrations/openai_processor_service.rb +++ b/lib/integrations/openai_processor_service.rb @@ -6,8 +6,8 @@ class Integrations::OpenaiProcessorService API_URL = 'https://api.openai.com/v1/chat/completions'.freeze GPT_MODEL = 'gpt-3.5-turbo'.freeze - ALLOWED_EVENT_NAMES = %w[rephrase summarize reply_suggestion label_suggestion].freeze - CACHEABLE_EVENTS = %w[label_suggestion].freeze + ALLOWED_EVENT_NAMES = %w[rephrase summarize reply_suggestion].freeze + CACHEABLE_EVENTS = %w[].freeze pattr_initialize [:hook!, :event!] @@ -57,11 +57,15 @@ class Integrations::OpenaiProcessorService end def valid_event_name? - ALLOWED_EVENT_NAMES.include?(event_name) + # self.class::ALLOWED_EVENT_NAMES is way to access ALLOWED_EVENT_NAMES defined in the class hierarchy of the current object. + # This ensures that if ALLOWED_EVENT_NAMES is updated elsewhere in it's ancestors, we access the latest value. + self.class::ALLOWED_EVENT_NAMES.include?(event_name) end def event_is_cacheable? - CACHEABLE_EVENTS.include?(event_name) + # self.class::CACHEABLE_EVENTS is way to access CACHEABLE_EVENTS defined in the class hierarchy of the current object. + # This ensures that if CACHEABLE_EVENTS is updated elsewhere in it's ancestors, we access the latest value. + self.class::CACHEABLE_EVENTS.include?(event_name) end def make_api_call(body)