Skip to content

Commit

Permalink
Start of MOH
Browse files Browse the repository at this point in the history
  • Loading branch information
docwho2 committed Dec 2, 2023
1 parent c966069 commit 58245d9
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 73 deletions.
95 changes: 29 additions & 66 deletions ChatGPT/src/main/java/cloud/cleo/squareup/ChatGPTLambda.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<ChatFunctionCall> callsMade = new LinkedList<>();
try {
FunctionExecutor functionExecutor = AbstractFunction.getFunctionExecuter(lexRequest);
functionExecutor.setObjectMapper(mapper);
Expand Down Expand Up @@ -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<ChatMessage> 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);"
Expand All @@ -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() + "...");
Expand Down Expand Up @@ -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
Expand All @@ -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<String,String> 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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ public static AbstractFunction getFunctionByName(String name) {
*
* @return
*/
protected abstract String getName();
public abstract String getName();

/**
* Description for the function
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<StartBotConversationAction, Action> 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")
Expand All @@ -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("[email protected]")
.withArn(VC_ARN)
Expand Down

0 comments on commit 58245d9

Please sign in to comment.