diff --git a/app/controllers/stealth/controller.rb b/app/controllers/stealth/controller.rb index 9e7b5bb..cb99289 100644 --- a/app/controllers/stealth/controller.rb +++ b/app/controllers/stealth/controller.rb @@ -7,6 +7,7 @@ class Controller < ApplicationController include Stealth::Controller::InterruptDetect include Stealth::Controller::Messages include Stealth::Controller::Nlp + include Stealth::Controller::Llm include Stealth::Controller::Replies include Stealth::Controller::CatchAll include Stealth::Controller::UnrecognizedMessage @@ -193,6 +194,8 @@ def step(flow:, state:, pos: nil) rescue StandardError => e if e.is_a?(Stealth::Errors::UnrecognizedMessage) run_unrecognized_message(err: e) + elsif e.is_a?(Stealth::Errors::FlowTriggered) + redirect_to_llm_intent else run_catch_all(err: e) end diff --git a/app/controllers/stealth/controller/llm.rb b/app/controllers/stealth/controller/llm.rb new file mode 100644 index 0000000..04cc5af --- /dev/null +++ b/app/controllers/stealth/controller/llm.rb @@ -0,0 +1,60 @@ +# coding: utf-8 +# frozen_string_literal: true + +module Stealth + class Controller + module Llm + + extend ActiveSupport::Concern + + included do + def perform_llm! + Stealth::Logger.l( + topic: :llm, + message: "User #{current_session_id} -> Querying LLM for intent detection." + ) + + @llm_result ||= begin + llm_response = get_intent(current_message.message) + current_message.llm_result = llm_response + + if llm_response.blank? + Stealth::Logger.l( + topic: :llm, + message: "User #{current_session_id} -> No intent detected." + ) + return nil + end + + intent_name = llm_response[:intent].to_sym + Stealth::Logger.l( + topic: :llm, + message: "User #{current_session_id} -> LLM resulting intent: #{intent_name}." + ) + + llm_response + rescue StandardError => e + Stealth::Logger.l( + topic: :llm, + message: "User #{current_session_id} -> LLM API Error: #{e.message}" + ) + nil + end + end + + def redirect_to_llm_intent + intent = current_message.llm_result&.dig(:intent) + + unless intent.present? + error = Stealth::Errors::UnrecognizedMessage.new("Missing intent in LLM result") + return run_unrecognized_message(err: error) + end + + step_to(flow: intent.to_sym) + end + + end + + end + end +end diff --git a/app/controllers/stealth/controller/messages.rb b/app/controllers/stealth/controller/messages.rb index abcea39..dadeb52 100644 --- a/app/controllers/stealth/controller/messages.rb +++ b/app/controllers/stealth/controller/messages.rb @@ -69,6 +69,7 @@ def homophone_translated_msg # "100k" => proc { step_back }, "200k" => proc { step_to flow :hello } # } def handle_message(message_tuples) + @message_tuples = message_tuples match = NO_MATCH # dummy value since nils are used for matching if reserved_homophones_used = contains_homophones?(message_tuples.keys) @@ -179,15 +180,35 @@ def get_match(messages, raise_on_mismatch: true, fuzzy_match: true) def handle_mismatch(raise_on_mismatch) log_nlp_result unless Stealth.config.log_all_nlp_results # Already logged + return current_message.message unless raise_on_mismatch - if raise_on_mismatch - raise( - Stealth::Errors::UnrecognizedMessage, - "The reply '#{current_message.message}' was not recognized." + llm_response = perform_llm! + unless llm_response.present? + raise Stealth::Errors::UnrecognizedMessage, "The reply '#{current_message.message}' was not recognized." + end + + intent_name = llm_response[:intent].to_sym + + # Check if message_tuples match + if @message_tuples&.key?(intent_name) + instance_eval(&@message_tuples[intent_name]) + return + end + + if Stealth::FlowManager.instance.flow_exists?(intent_name) + Stealth::Logger.l( + topic: :llm, + message: "User #{current_session_id} -> Redirecting to flow '#{intent_name}'." ) - else - current_message.message + # Stops execution in the DSL state that triggered the mismatch if a recognized flow exists + raise Stealth::Errors::FlowTriggered, intent_name end + + Stealth::Logger.l( + topic: :llm, + message: "User #{current_session_id} -> No flow found for intent '#{intent_name}'. Falling back to UnrecognizedMessage." + ) + raise Stealth::Errors::UnrecognizedMessage, "The reply '#{current_message.message}' was not recognized." end def contains_homophones?(arr) diff --git a/app/controllers/stealth/controller/replies.rb b/app/controllers/stealth/controller/replies.rb index b12da8d..8eb60ee 100644 --- a/app/controllers/stealth/controller/replies.rb +++ b/app/controllers/stealth/controller/replies.rb @@ -70,9 +70,13 @@ module Replies # release_lock! # end - def send_replies - flow = current_session.flow_string - state = current_session.state_string + def send_replies(custom_reply: nil) + flow, state = if custom_reply + custom_reply.split('/') + else + [current_session.flow_string, current_session.state_string] + end + Stealth.trigger_reply(flow, state, current_message) end diff --git a/lib/stealth/errors.rb b/lib/stealth/errors.rb index 019f35d..b34fdaa 100644 --- a/lib/stealth/errors.rb +++ b/lib/stealth/errors.rb @@ -43,6 +43,9 @@ class FlowError < Errors class FlowDefinitionError < Errors end + class FlowTriggered < Errors + end + # Service errors class InvalidSessionID < Errors end @@ -64,4 +67,4 @@ class Halted < Errors end end -end \ No newline at end of file +end diff --git a/lib/stealth/logger.rb b/lib/stealth/logger.rb index 7514187..83a3ebe 100644 --- a/lib/stealth/logger.rb +++ b/lib/stealth/logger.rb @@ -44,7 +44,7 @@ def self.print_topic(topic) :magenta when :alexa, :voice, :twilio_voice, :unrecognized_message :light_cyan - when :nlp + when :nlp, :llm :cyan when :catch_all, :err :red @@ -62,4 +62,4 @@ class << self end end -end \ No newline at end of file +end diff --git a/lib/stealth/service_event.rb b/lib/stealth/service_event.rb index e9983ab..76c211b 100644 --- a/lib/stealth/service_event.rb +++ b/lib/stealth/service_event.rb @@ -16,6 +16,7 @@ class ServiceEvent :payload, :referral, :nlp_result, + :llm_result, :catch_all_reason, :confidence diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 944f614..4db42bc 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -7,6 +7,7 @@ require 'rails' require 'rails/engine' + require 'stealth' require 'sidekiq/testing' require 'mock_redis'