Skip to content

Commit

Permalink
Prompting Updates, slight reorg on SMS functions
Browse files Browse the repository at this point in the history
  • Loading branch information
docwho2 committed Aug 5, 2024
1 parent 524e6b3 commit 1d3cfe7
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 133 deletions.
29 changes: 14 additions & 15 deletions ChatGPT/src/main/java/cloud/cleo/squareup/ChatGPTLambda.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ public abstract class ChatGPTLambda {
public final static String HANGUP_FUNCTION_NAME = "hangup_call";
public final static String FACEBOOK_HANDOVER_FUNCTION_NAME = "facebook_inbox";
public final static String SWITCH_LANGUAGE_FUNCTION_NAME = "switch_language";
public final static String DRIVING_DIRECTIONS_FUNCTION_NAME = "driving_directions";
public final static String PRIVATE_SHOOPING_FUNCTION_NAME = "private_shopping_url";


public final static String WEBSITE_URL = "CopperFoxGifts.com";

// Eveverything here will be done at SnapStart init
static {
Expand Down Expand Up @@ -108,7 +113,7 @@ protected LexV2Response processGPT(LexV2EventWrapper lexRequest) {
// Will be phone if from SMS, Facebook the Page Scoped userID, Chime unique generated ID
final var session_id = lexRequest.getSessionId();

// For Voice we support 2 Locales, English and Spanish
// For Voice we support 9 Locales
log.debug("Java Locale is " + lexRequest.getLocale());

// Special Facebook Short Circut
Expand Down Expand Up @@ -170,9 +175,9 @@ protected LexV2Response processGPT(LexV2EventWrapper lexRequest) {
ChatCompletionRequest request = ChatCompletionRequest.builder()
.messages(chatMessages)
.model(OPENAI_MODEL)
.maxTokens(500)
.temperature(0.2) // More focused
.n(1) // Only return 1 completion
.maxTokens(500) // Limit the response tokens to something reasonable
.temperature(0.2) // More focused
.n(1) // Only return 1 completion
.functions(functionExecutor.getFunctions())
.functionCall(ChatCompletionRequest.ChatCompletionRequestFunctionCall.of("auto"))
.build();
Expand Down Expand Up @@ -240,12 +245,12 @@ protected LexV2Response processGPT(LexV2EventWrapper lexRequest) {
.findAny()
.orElse(null);
if (termCalled != null) {
log.debug("A termianting function was called = [" + termCalled.getName() + "]");
log.debug("A terminating function was called = [" + termCalled.getName() + "]");
final ChatFunctionCall gptFunCall = functionCallsMade.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 " + functionCallsMade + " but none are terminating");
log.debug("The following function calls were made " + functionCallsMade + " but none are terminating");
}

// Special Facebook handoff check
Expand All @@ -258,13 +263,6 @@ protected LexV2Response processGPT(LexV2EventWrapper lexRequest) {
}
}

// Since we have a general response, add message asking if there is anything else
// For voice it just seems more natural to always end with a question.
//if (lexRequest.isVoice() && !botResponse.endsWith("?")) {
// If ends with question, then we don't need to further append question
//botResponse = botResponse + lexRequest.getLangString(ANYTHING_ELSE);
//}

if (session_new && lexRequest.isFacebook()) {
// If this a new Session send back a Welcome card for Facebook Channel
// This works for Twilio/SMS, but sends a MMS and costs more money (it sends logo, but of course doesn't support the buttons)
Expand Down Expand Up @@ -379,7 +377,8 @@ private ImageResponseCard buildWelcomeCard() {
.withButtons(List.of(
Button.builder().withText("Hours").withValue("What are you business hours?").build(),
Button.builder().withText("Location").withValue("What is your address and driving directions?").build(),
Button.builder().withText("Person").withValue("Please hand this conversation over to a person").build()
Button.builder().withText("Person").withValue("Please hand this conversation over to a person").build(),
Button.builder().withText("Private Shopping").withValue("Info about Private Shopping and link").build()
).toArray(Button[]::new))
.build();
}
Expand All @@ -393,7 +392,7 @@ private ImageResponseCard buildTransferCard() {
return LexV2Response.ImageResponseCard.builder()
.withTitle("Conversation will move to Inbox")
.withImageUrl("https://www.copperfoxgifts.com/logo.png")
.withSubtitle("Please tell us how our AI ChatBot did?")
.withSubtitle("Please tell us how Copper Bot did?")
.withButtons(List.of(
Button.builder().withText("Epic Fail").withValue("Chatbot was not Helpful.").build(),
Button.builder().withText("Needs Work").withValue("Chatbot needs some work.").build(),
Expand Down
38 changes: 27 additions & 11 deletions ChatGPT/src/main/java/cloud/cleo/squareup/ChatGPTSessionState.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package cloud.cleo.squareup;

import static cloud.cleo.squareup.ChatGPTLambda.DRIVING_DIRECTIONS_FUNCTION_NAME;
import static cloud.cleo.squareup.ChatGPTLambda.HANGUP_FUNCTION_NAME;
import static cloud.cleo.squareup.ChatGPTLambda.PRIVATE_SHOOPING_FUNCTION_NAME;
import static cloud.cleo.squareup.ChatGPTLambda.SWITCH_LANGUAGE_FUNCTION_NAME;
import static cloud.cleo.squareup.ChatGPTLambda.TRANSFER_FUNCTION_NAME;
import static cloud.cleo.squareup.ChatGPTLambda.WEBSITE_URL;
import cloud.cleo.squareup.enums.Language;
import cloud.cleo.squareup.functions.AbstractFunction;
import com.theokanning.openai.completion.chat.ChatMessage;
Expand Down Expand Up @@ -63,28 +66,38 @@ public ChatGPTSessionState(LexV2EventWrapper lexRequest) {

final var sb = new StringBuilder();

// We need to tell GPT the date so it has a reference, when calling via API it has no date knowledge
// We don't let sessions storage span days, so the date should always be relevant.
sb.append("The current date is ").append(date).append(". ");

// General Prompting
sb.append("""
Please be a helpfull assistant named "Copper Bot" for a retail store named "Copper Fox Gifts",
which has clothing items, home decor, gifts of all kinds, speciality foods, and much more.
The store is located at 160 Main Street, Wahkon Minnesota, near Lake Mille Lacs.
The website url for the store is "copperfoxgifts.com".
The store is located at 160 Main Street, Wahkon Minnesota, near Lake Mille Lacs.
The store opened in October of 2021 and moved to its larger location in May of 2023.
Outside normal business hours, we offer a "Private Shopping Experience" where a staff member will open
the store outside normal hours, and this can be scheduled on our website from one of the top level menu choices.
the store outside normal hours, and this can be scheduled on our website from one of the top level menu "Private Shoppimg".
We have a one hour lead time on appointments so if we're closed, they could be shopping privately within one hour!
Do mention how great it would be to have the store all to themselves and how we try to accomodate all requests ASAP.
The store opened in October of 2021 and moved to its larger location in May of 2023.
Do mention how great it would be to have the store all to themselves and how we try to accomodate all requests.
""");

// We need to tell GPT the date so it has a reference, when calling via API it has no date knowledge
// We don't let sessions storage span days, so the date should always be relevant.
sb.append("The current date is ").append(date).append(". ");
sb.append("Please call the ").append(PRIVATE_SHOOPING_FUNCTION_NAME)
.append("""
function to get the direct booking URL when the caller is interested in the private shopping experience. This is
really one of the more innovative services we provide and we want to ensure its as easy as possible for customers
to book their appointments.
""");

// Main Website adn FB
sb.append("The Web Site for Copper Fix Gifts is ").append(WEBSITE_URL).append(" and we frequently post our events and informaiton on sales ")
.append(" on our Facebook Page which is also linked at top level menu on our website. ");

// Local Stuff to recommend
sb.append("""
Muggs of Mille Lacs is a great restaurant next door that serves some on the best burgers
in the lake area and has a large selection draft beers and great pub fare.
Tulibee Tavern is another great restaurant across the street that serves more home cooked type meals at reasonable prices.
Tulibee Tavern is another great restaurant across the street that serves more home cooked type meals at reasonable prices.
""");

// We want to receieve all emails in English so we can understand them :-)
Expand Down Expand Up @@ -143,11 +156,14 @@ public ChatGPTSessionState(LexV2EventWrapper lexRequest) {

// Blank input, meaning silienece timeout which is a speech only thing
sb.append("When the prompt is exactly blank, this means the caller did not say anything, so try and engage in conversation and also suggest ")
.append("queries the caller might be interested in (Hours,Location,Product Search,Private Shopping, Language Change, etc.). ");
.append("queries the caller might be interested in (Hours, Private Shopping, Location, Product Search, Language Change, etc.). ");

// Hangup
sb.append("When the caller indicates they are done with the conversation, execute the ").append(HANGUP_FUNCTION_NAME).append(" function. ");

// Offer up Driving directions for callers
sb.append("When asking about location, you can send the caller a directions link if they are interested, execute the ").append(DRIVING_DIRECTIONS_FUNCTION_NAME).append(" function. ");

// Always answer with a question to illicit the next repsonse, this makes the voice interaction more natural
sb.append("When responding always end the response with a question to illicit the next input since we are interacting via telephone. ");

Expand All @@ -162,7 +178,7 @@ public ChatGPTSessionState(LexV2EventWrapper lexRequest) {
sb.append("To transfer or speak with an employee that has a phone number, execute the ").append(TRANSFER_FUNCTION_NAME).append(" function. ");
sb.append("Do not provide callers employee phone numbers, you can only use the phone numbers to execute the ").append(TRANSFER_FUNCTION_NAME).append(" function. ");
}
sb.append("If the caller wants to just speak to a person in general or leave a voicemail, execute ")
sb.append("If the caller wants to just speak to any person in general or leave a voicemail, execute ")
.append(TRANSFER_FUNCTION_NAME).append(" with ").append(System.getenv("MAIN_NUMBER"))
.append(" which rings the main phone in the store. ");

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package cloud.cleo.squareup;

import com.fasterxml.jackson.databind.ObjectMapper;
import static cloud.cleo.squareup.ChatGPTLambda.mapper;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
Expand All @@ -20,7 +20,6 @@ public class FaceBookOperations {
// Initialize the Log4j logger.
private static final Logger log = LogManager.getLogger(FaceBookOperations.class);

private final static ObjectMapper mapper = new ObjectMapper();

/**
* Transfer control of Messenger Thread Session from Bot control to the
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package cloud.cleo.squareup.functions;

import static cloud.cleo.squareup.ChatGPTLambda.DRIVING_DIRECTIONS_FUNCTION_NAME;

/**
* Base class for Driving Directions.
*
Expand All @@ -17,7 +19,7 @@ public abstract class DrivingDirections<Request> extends AbstractFunction {

@Override
public final String getName() {
return "driving_directions";
return DRIVING_DIRECTIONS_FUNCTION_NAME;
}


Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,15 @@
package cloud.cleo.squareup.functions;

import static cloud.cleo.squareup.ChatGPTLambda.crtAsyncHttpClient;
import java.util.concurrent.CompletionException;
import java.util.function.Function;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sns.SnsAsyncClient;

/**
* Driving Directions when user is interacting via Voice interface.
*
* @author sjensen
*/
public class DrivingDirectionsVoice extends DrivingDirections {
public class DrivingDirectionsVoice extends DrivingDirections implements SendSMS {


final static SnsAsyncClient snsAsyncClient = SnsAsyncClient.builder()
// Force SMS sending to east because that's where all the 10DLC and campaign crap setup is done
// Otherwise have to pay for registrations and numbers in 2 regions, HUGE HASSLE (and more monthly cost)
// Also then all texts are sourced from the same phone number for consistancy
.region(Region.US_EAST_1)
.httpClient(crtAsyncHttpClient)
.build();

@Override
protected String getDescription() {
return "Sends the caller a URL with driving directions to the store via SMS.";
Expand All @@ -30,7 +18,7 @@ protected String getDescription() {
@Override
protected Function getExecutor() {
return (var r) -> {
try {

final var callingNumber = getCallingNumber();

// Only send SMS to validated US Phone Numbers (in case callerID block, or some weird deal)
Expand All @@ -43,16 +31,7 @@ protected Function getExecutor() {
return mapper.createObjectNode().put("status","FAILED").put("message", "Caller is not calling from a mobile device");
}

final var result = snsAsyncClient.publish(b -> b.phoneNumber(callingNumber).message(DRIVING_DIRECTIONS_URL) ).join();
log.info("SMS Directions sent to " + callingNumber + " with SNS id of " + result.messageId());
return mapper.createObjectNode().put("status","SUCCESS").put("message", "The directions have been sent");
} catch (CompletionException e) {
log.error("Could not send Directions via SMS to caller",e.getCause());
return mapper.createObjectNode().put("status","FAILED").put("message", "An error has occurred, this function may be down");
} catch (Exception e) {
log.error("Could not send Directions via SMS to caller",e);
return mapper.createObjectNode().put("status","FAILED").put("message", "An error has occurred, this function may be down");
}
return SendSMS.sendSMS(callingNumber,DRIVING_DIRECTIONS_URL);
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@
*/
package cloud.cleo.squareup.functions;

import static cloud.cleo.squareup.ChatGPTLambda.PRIVATE_SHOOPING_FUNCTION_NAME;
import static cloud.cleo.squareup.ChatGPTLambda.WEBSITE_URL;

/**
*
* @author sjensen
* @param <Request>
*/
public abstract class Website<Request> extends AbstractFunction {
public abstract class PrivateShoppingLink<Request> extends AbstractFunction {

protected final static String WEBSITE_URL = "www.CopperFoxGifts.com";
protected final static String PRIVATE_SHOPPING_URL = WEBSITE_URL + "/book";

@Override
public final String getName() {
return "store_website";
return PRIVATE_SHOOPING_FUNCTION_NAME;
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@
*
* @author sjensen
*/
public class WebsiteText extends Website {
public class PrivateShoppingLinkText extends PrivateShoppingLink {
@Override
protected String getDescription() {
return "Returns a URL for Driving directions to the Store";
return "Returns a URL for direct booking of Private Shopping";
}

@Override
protected Function getExecutor() {
return (var r) -> {
return mapper.createObjectNode().put("url", WEBSITE_URL);
return mapper.createObjectNode().put("url", PRIVATE_SHOPPING_URL);
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
*/
package cloud.cleo.squareup.functions;

import static cloud.cleo.squareup.functions.AbstractFunction.mapper;
import java.util.function.Function;

/**
*
* @author sjensen
*/
public class PrivateShoppingLinkVoice extends PrivateShoppingLink implements SendSMS {

@Override
protected String getDescription() {
return "Sends the caller the direct URL to book private shopping";
}

@Override
protected Function getExecutor() {
return (var r) -> {
final var callingNumber = getCallingNumber();

// Only send SMS to validated US Phone Numbers (in case callerID block, or some weird deal)
if (!hasValidUSE164Number()) {
return mapper.createObjectNode().put("status", "FAILED").put("message", "Calling number is not a valid US phone number");
}

// Do not attempt to send to non-mobile numbers
if (!hasValidUSMobileNumber()) {
return mapper.createObjectNode().put("status", "FAILED").put("message", "Caller is not calling from a mobile device");
}

return SendSMS.sendSMS(callingNumber, PRIVATE_SHOPPING_URL);
};
}

/**
* This function is Voice only so don't use this for text interface
*
* @return
*/
@Override
protected boolean isText() {
return false;
}
}
Loading

0 comments on commit 1d3cfe7

Please sign in to comment.