From 58245d9c3c49a30676f9668972cfa298b3ea0a74 Mon Sep 17 00:00:00 2001 From: Steve Jensen Date: Sat, 2 Dec 2023 13:49:58 -0600 Subject: [PATCH] Start of MOH --- .../cloud/cleo/squareup/ChatGPTLambda.java | 95 ++++++------------- .../squareup/functions/AbstractFunction.java | 2 +- .../cleo/squareup/functions/MusicOnHold.java | 2 +- .../cleo/chimesma/squareup/ChimeSMA.java | 10 +- 4 files changed, 36 insertions(+), 73 deletions(-) diff --git a/ChatGPT/src/main/java/cloud/cleo/squareup/ChatGPTLambda.java b/ChatGPT/src/main/java/cloud/cleo/squareup/ChatGPTLambda.java index 3b9464d..ac7bf0d 100644 --- a/ChatGPT/src/main/java/cloud/cleo/squareup/ChatGPTLambda.java +++ b/ChatGPT/src/main/java/cloud/cleo/squareup/ChatGPTLambda.java @@ -35,6 +35,9 @@ import java.time.LocalTime; import java.time.ZoneId; import java.time.ZonedDateTime; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; import java.util.Optional; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -139,7 +142,7 @@ private LexV2Response processGPT(LexV2Event lexRequest) { if (count > 2) { log.debug("Two blank responses, sending to Quit Intent"); // Hang up on caller after 2 silience requests - return buildQuitResponse(lexRequest, "Thank you for calling, goodbye."); + return buildTerminatingResponse(lexRequest, "hangup_call", Map.of(), "Thank you for calling, goodbye."); } else { attrs.put("blankCounter", count.toString()); // If we get slience (timeout without speech), then we get empty string on the transcript @@ -172,8 +175,8 @@ private LexV2Response processGPT(LexV2Event lexRequest) { session.addUserMessage(input); String botResponse; - // Set for special case of transfer and hangup - ChatFunctionCall transferCall = null, hangupCall = null; + // Store all the calls made + List callsMade = new LinkedList<>(); try { FunctionExecutor functionExecutor = AbstractFunction.getFunctionExecuter(lexRequest); functionExecutor.setObjectMapper(mapper); @@ -206,14 +209,6 @@ private LexV2Response processGPT(LexV2Event lexRequest) { if (functionCall != null) { log.debug("Trying to execute " + functionCall.getName() + "..."); - // Track these special case functions because executing them does nothing really, we just need to know they were called - switch (functionCall.getName()) { - case TRANSFER_FUNCTION_NAME -> - transferCall = functionCall; - case HANGUP_FUNCTION_NAME -> - hangupCall = functionCall; - } - Optional message = functionExecutor.executeAndConvertToMessageSafely(functionCall); /* You can also try 'executeAndConvertToMessage' inside a try-catch block, and add the following line inside the catch: "message = executor.handleException(exception);" @@ -228,6 +223,8 @@ private LexV2Response processGPT(LexV2Event lexRequest) { log.debug("Executed " + functionCall.getName() + "."); session.addMessage(message.get()); + // Track each call made + callsMade.add(functionCall); continue; } else { log.debug("Something went wrong with the execution of " + functionCall.getName() + "..."); @@ -258,14 +255,22 @@ private LexV2Response processGPT(LexV2Event lexRequest) { log.debug("botResponse is [" + botResponse + "]"); - // Check to see if GPT called the TRANSFER function - if (transferCall != null) { - return buildTransferResponse(lexRequest, transferCall.getArguments().findValue("phone_number").asText(), botResponse); - } - - // Check to see if the GPT says the conversation is done - if (hangupCall != null) { - return buildQuitResponse(lexRequest, botResponse); + if ( ! callsMade.isEmpty() ) { + // Did a terminating function get executed + final var termCalled = callsMade.stream() + .map(f -> AbstractFunction.getFunctionByName(f.getName())) + .filter(f -> f.isTerminating() ) + .findAny() + .orElse(null); + if ( termCalled != null ) { + log.debug("A termianting function was called = [" + termCalled.getName() + "]"); + final ChatFunctionCall gptFunCall = callsMade.stream().filter(f -> f.getName().equals(termCalled.getName())).findAny().get(); + final var args = mapper.convertValue(gptFunCall.getArguments(), Map.class); + return buildTerminatingResponse(lexRequest, gptFunCall.getName(), args, botResponse); + } else { + log.debug("The following funtion calls were made " + callsMade + " but none are terminating"); + } + } // Since we have a general response, add message asking if there is anything else @@ -279,66 +284,24 @@ private LexV2Response processGPT(LexV2Event lexRequest) { return buildResponse(lexRequest, botResponse); } - private LexV2Response buildQuitResponse(LexV2Event lexRequest, String botResponse) { - - final var attrs = lexRequest.getSessionState().getSessionAttributes(); - - // The controller (Chime SAM Lambda) will grab this from the session - attrs.put("botResponse", botResponse); - attrs.put("action", "quit"); - - log.debug("Responding with quit action"); - log.debug("Bot Response is " + botResponse); - - // State to return - final var ss = SessionState.builder() - // Retain the current session attributes - .withSessionAttributes(attrs) - .withIntent(Intent.builder().withName("FallbackIntent").withState("Fulfilled").build()) - .withDialogAction(DialogAction.builder().withType("Close").build()) - .build(); - - final var lexV2Res = LexV2Response.builder() - .withSessionState(ss) - .build(); - log.debug("Response is " + mapper.valueToTree(lexV2Res)); - return lexV2Res; - } /** - * Response that will trigger a transfer of a voice call (not applicable to Text interface) + * Response that will Lex to be done so some action can be performed at the Chime Level (hang up, transfer, MOH, etc.) * * @param lexRequest * @param transferNumber * @param botResponse * @return */ - private LexV2Response buildTransferResponse(LexV2Event lexRequest, String transferNumber, String botResponse) { + private LexV2Response buildTerminatingResponse(LexV2Event lexRequest, String function_name, Map functionArgs, String botResponse) { final var attrs = lexRequest.getSessionState().getSessionAttributes(); // The controller (Chime SAM Lambda) will grab this from the session, then transfer the call for us - attrs.put("transferNumber", transferNumber); - attrs.put("action", "transfer"); - - if (botResponse != null) { - // Check to make sure transfer number is not in the response - if (botResponse.contains(transferNumber)) { - botResponse = botResponse.replace(transferNumber, ""); - } - - botResponse = botResponse.trim(); - if (!botResponse.isBlank()) { - // Split the response on newline because sometimes GPT adds halucination about not being able to transfer a call even though it just did - attrs.put("botResponse", botResponse.split("\n")[0]); - } else { - // Use a default transfer message if somehow no bot message - attrs.put("botResponse", "Your call will no be transferred."); - } - } + attrs.put("action", function_name); + attrs.put("bot_response", botResponse); + attrs.putAll(functionArgs); - log.debug("Responding with transfer number [" + transferNumber + "]"); - log.debug("Bot Response is " + botResponse); // State to return final var ss = SessionState.builder() diff --git a/ChatGPT/src/main/java/cloud/cleo/squareup/functions/AbstractFunction.java b/ChatGPT/src/main/java/cloud/cleo/squareup/functions/AbstractFunction.java index d836acf..74e557c 100644 --- a/ChatGPT/src/main/java/cloud/cleo/squareup/functions/AbstractFunction.java +++ b/ChatGPT/src/main/java/cloud/cleo/squareup/functions/AbstractFunction.java @@ -176,7 +176,7 @@ public static AbstractFunction getFunctionByName(String name) { * * @return */ - protected abstract String getName(); + public abstract String getName(); /** * Description for the function diff --git a/ChatGPT/src/main/java/cloud/cleo/squareup/functions/MusicOnHold.java b/ChatGPT/src/main/java/cloud/cleo/squareup/functions/MusicOnHold.java index 90c95c0..9547ae0 100644 --- a/ChatGPT/src/main/java/cloud/cleo/squareup/functions/MusicOnHold.java +++ b/ChatGPT/src/main/java/cloud/cleo/squareup/functions/MusicOnHold.java @@ -18,7 +18,7 @@ public String getName() { @Override public String getDescription() { - return "Should be called when the caller needs more time to respond or requests more time"; + return "Should be called when the caller needs more time to respond or requests more time so they can be put on hold"; } @Override diff --git a/ChimeSMA/src/main/java/cloud/cleo/chimesma/squareup/ChimeSMA.java b/ChimeSMA/src/main/java/cloud/cleo/chimesma/squareup/ChimeSMA.java index 2cdc7bd..4beb9c6 100644 --- a/ChimeSMA/src/main/java/cloud/cleo/chimesma/squareup/ChimeSMA.java +++ b/ChimeSMA/src/main/java/cloud/cleo/chimesma/squareup/ChimeSMA.java @@ -79,7 +79,7 @@ public static Action getMainMenu() { .withLocale(english) .withContent("You can ask about our products, hours, location, or speak to one of our team members. Tell us how we can help today?") // Send the calling number in so we can send texts if need be - .withSessionAttributesF(action -> Map.of("callingNumber", action.getEvent().getCallDetails().getParticipants().get(0).getFrom())) + .withSessionAttributesF(action -> Map.of("calling_number", action.getEvent().getCallDetails().getParticipants().get(0).getFrom())) .build(); // Will add Spanish later if needed @@ -88,17 +88,17 @@ public static Action getMainMenu() { .withLocale(spanish) .withContent("¿En qué puede ayudarte Chat GPT?") // Send the calling number in so we can send texts if need be - .withSessionAttributesF(action -> Map.of("callingNumber", action.getEvent().getCallDetails().getParticipants().get(0).getFrom())) + .withSessionAttributesF(action -> Map.of("calling_number", action.getEvent().getCallDetails().getParticipants().get(0).getFrom())) .build(); // Two invocations of the bot, so create one function and use for both Function botNextAction = (a) -> { final var attrs = a.getActionData().getIntentResult().getSessionState().getSessionAttributes(); - final var botResponse = attrs.get("botResponse"); // When transferring or hanging up, play back GPT's last response + final var botResponse = attrs.get("bot_response"); // When transferring or hanging up, play back GPT's last response final var action = attrs.get("action"); // We don't need or want real intents, so the action when exiting the Bot will be set return switch (action) { case "transfer_call" -> { - final var phone = attrs.get("transferNumber"); + final var phone = attrs.get("transfer_number"); final var transfer = CallAndBridgeAction.builder() .withDescription("Send Call to Team Member") .withRingbackToneKey("ringing.wav") @@ -118,7 +118,7 @@ public static Action getMainMenu() { } case "hold_call" -> { final var transfer = CallAndBridgeAction.builder() - .withDescription("Send Call Music On Hold") + .withDescription("Send Call to Music On Hold") .withCallTimeoutSeconds(10) .withUri("music@iptel.org") .withArn(VC_ARN)