Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/controllers/stealth/controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
60 changes: 60 additions & 0 deletions app/controllers/stealth/controller/llm.rb
Original file line number Diff line number Diff line change
@@ -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
33 changes: 27 additions & 6 deletions app/controllers/stealth/controller/messages.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
10 changes: 7 additions & 3 deletions app/controllers/stealth/controller/replies.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 4 additions & 1 deletion lib/stealth/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ class FlowError < Errors
class FlowDefinitionError < Errors
end

class FlowTriggered < Errors
end

# Service errors
class InvalidSessionID < Errors
end
Expand All @@ -64,4 +67,4 @@ class Halted < Errors
end

end
end
end
4 changes: 2 additions & 2 deletions lib/stealth/logger.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -62,4 +62,4 @@ class << self
end

end
end
end
1 change: 1 addition & 0 deletions lib/stealth/service_event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class ServiceEvent
:payload,
:referral,
:nlp_result,
:llm_result,
:catch_all_reason,
:confidence

Expand Down
1 change: 1 addition & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

require 'rails'
require 'rails/engine'

require 'stealth'
require 'sidekiq/testing'
require 'mock_redis'
Expand Down