טיפים להטמעה (Dialogflow)

ריכזנו כאן כמה טיפים שיעזרו לכם ליישם שיטות טובות של עיצוב שיחה ב-Action.

צפויים וריאציות

יש לטפל בכך בקלט 'המשתמש אומר' ב-Dialogflow. כמו כן, כדאי להשתמש ביותר מכוונת רכישה אחת שיכולה למפות לאותה פעולה, שבה ניתן להפעיל כל כוונה באמצעות קבוצות שונות של ביטויי "משתמש אומר".

נותנים תשובות מועילות ונכשלים בחינניות

בחלק מהמקרים אי אפשר להתקדם בפעולה, כי לא התקבל קלט (שנקרא'no-input') או שלא הבין קלט של משתמש (שנקרא'ללא התאמה'). כשזה קורה, Assistant קודם כול מנסה לקבוע אם המשתמש רוצה להפעיל פעולה אחרת. אם Assistant לא מתאימה בין הקלט של המשתמש לפעולה אחרת, המשתמש ממשיך בהקשר של הפעולה. התרחיש הזה יכול להתרחש בכל שלב, לכן השיטה המומלצת היא לטפל באופן ייחודי במצבים ללא קלט ובמצבים של חוסר התאמה בכל תור בשיחה, עם חלופה. שימוש בחלופות יכול לעזור למשתמשים לחזור לעניינים.

כדי לעשות את זה, מאתחלים את המשתנה fallbackCount באובייקט conv.data ומגדירים אותו ל-0. מכינים מערך של שתי הנחיות חלופיות (שמועברות באופן ברור לטיפול ברמה גבוהה יותר) ובקשת חזרה סופית לסיום השיחה.

לאחר מכן יוצרים Intent חלופי (מומלץ ליצור אובייקט כזה לכל Intent שניתן לבצע בו פעולות בסוכן). ב-handler של ה-Intent, שולפים את ספירת החלופה של האובייקט conv.data, מעלים אותו, ואם הוא קטן מ-3, שולפים את ההנחיה מהמערך של 3. אם המספר הוא 4 או יותר, סוגרים את השיחה בבקשה האחרונה. בכל אובייקטים מסוג Intent שאינם חלופות, צריך לאפס את מספר החלופות. באופן אידיאלי, כדאי ליצור תבניות לחלופות של כוונות ספציפיות כך שיהיו ספציפיות להן.

Node.js

const GENERAL_FALLBACK = [
   'Sorry, what was that?',
   'I didn\'t quite get that. I can help you find good local restaurants, what do you want to know about?',
];

const LIST_FALLBACK = [
   'Sorry, what was that?',
   'I didn\'t catch that. Could you tell me which one you prefer?',
];

const FINAL_FALLBACK = 'I\'m sorry I\'m having trouble here. Let\'s talk again later.';

const handleFallback = (conv, promptFetch, callback) => {
 conv.data.fallbackCount = parseInt(conv.data.fallbackCount, 10);
 conv.data.fallbackCount++;
 if (conv.data.fallbackCount > 2) {
   conv.close(promptFetch.getFinalFallbackPrompt());
 } else {
   callback();
 }
}
// Intent handlers below
const generalFallback = (conv) => {
  handleFallback = (conv, promptFetch, () => {
    conv.ask(GENERAL_FALLBACK[conv.data.fallbackCount],
      getGeneralNoInputPrompts());
 });
}

const listFallback = (conv) => {
  handleFallback = (conv, promptFetch, () => {
   conv.ask(LIST_FALLBACK[conv.data.fallbackCount],
       getGeneralNoInputPrompts());
 });
}

const nonFallback = (conv) => {
  conv.data.fallbackCount = 0;
  conv.ask('A non-fallback message here');
}

Java

private static final List<String> GENERAL_FALLBACK =
    Arrays.asList(
        "Sorry, what was that?",
        "I didn\'t quite get that. I can tell you all about IO, like date or location, or about the sessions. What do you want to know about?");
private static final List<String> LIST_FALLBACK =
    Arrays.asList(
        "Sorry, what was that?",
        "I didn\'t catch that. Could you tell me which one you liked?");
private static final List<String> FINAL_FALLBACK =
    Arrays.asList("I\'m sorry I\'m having trouble here. Maybe we should try this again later.");

@ForIntent("General Fallback")
public ActionResponse generalFallback(ActionRequest request) {
  ResponseBuilder responseBuilder = getResponseBuilder(request);
  int fallbackCount = (Integer) request.getConversationData().get("fallbackCount");
  fallbackCount++;
  request.getConversationData().put("fallbackCount", fallbackCount);
  if (fallbackCount > 2) {
    responseBuilder.add(getRandomPromptFromList(FINAL_FALLBACK)).endConversation();
  } else {
    responseBuilder.add(getRandomPromptFromList(GENERAL_FALLBACK));
  }
  return responseBuilder.build();
}

private String getRandomPromptFromList(List<String> prompts) {
  Random rand = new Random();
  int i = rand.nextInt(prompts.size());
  return prompts.get(i);
}

@ForIntent("List Fallback")
public ActionResponse listFallback(ActionRequest request) {
  ResponseBuilder responseBuilder = getResponseBuilder(request);
  int fallbackCount = (Integer) request.getConversationData().get("fallbackCount");
  fallbackCount++;
  request.getConversationData().put("fallbackCount", fallbackCount);
  if (fallbackCount > 2) {
    responseBuilder.add(getRandomPromptFromList(FINAL_FALLBACK)).endConversation();
  } else {
    responseBuilder.add(getRandomPromptFromList(LIST_FALLBACK));
  }
  return responseBuilder.build();
}

@ForIntent("Non Fallback")
public ActionResponse nonFallback(ActionRequest request) {
  ResponseBuilder responseBuilder = getResponseBuilder(request);
  request.getConversationData().put("fallbackCount", 0);
  responseBuilder.add("Non Fallback message");
  return responseBuilder.build();
}

להיות מוכנים לעזור בכל שלב

ליצור כוונה שמקשיבה לביטויי עזרה כמו "מה אפשר לעשות?", "מה את יכולה לומר לי" או "עזרה". לכן, כדאי להציע תשובה (סבבה) עם סקירה כללית של מה שהנציג יכול לעשות ולהפנות את המשתמשים לפעולה אפשרית. באופן אידאלי, כדאי להשתמש גם בכוונות עזרה המשך ב-Dialogflow כדי ליצור תרחישי עזרה שונים לכוונות פעולה שונות.

Node.js

const HELP_PROMPTS = [
   'There\'s a lot you might want to know about the local restaurants, and I can tell you all about it, like where it is and what kind of food they have. What do you want to know?',
   'I\'m here to help, so let me know if you need any help figuring out where or what to eat. What do you want to know?',
];

// Intent handler
const help = (conv) => {
 reply(conv, promptFetch.getHelpPrompt(), // fetches random entry from HELP_PROMPTS
     promptFetch.getGeneralNoInputPrompts());
}

Java

private static final List<String> HELP_PROMPTS =
    Arrays.asList(
        "There's a lot you might want to know about IO, and I can tell you all about it, like where it is and what the sessions are. What do you want to know?",
        "IO can be a little overwhelming, so I\'m here to help. Let me know if you need any help figuring out the event, like when it is, or what the sessions are. What do you want to know?");

@ForIntent("Help")
public ActionResponse help(ActionRequest request) {
  return getResponseBuilder(request).add(getRandomPromptFromList(HELP_PROMPTS)).build();
}

המשתמשים יכולים להפעיל מחדש את המידע

כוללים את כל השיטות של app.ask(output) באמצעות פונקציית proxy שמוסיפה את הפלט ל-conv.data.lastPrompt. עליכם ליצור כוונה חוזרת שמאזינה לבקשות חוזרות מהמשתמש כמו "מה?", "אמרי את זה שוב", או "תוכלי לחזור על זה?". יוצרים מערך של קידומות חוזרות שאפשר להשתמש בהן כדי לאשר שהמשתמש ביקש שמשהו יחזור על עצמו. ב-handler של כוונה חוזרת, מפעילים את ask() עם מחרוזת משורשר של קידומת החזרה ועם הערך של conv.data.lastPrompt. חשוב לזכור שתצטרכו להזיז את תגי הפתיחה מסוג SSML אם נעשה בהם שימוש בבקשה האחרונה.

Node.js

const REPEAT_PREFIX = [
    'Sorry, I said ',
    'Let me repeat that. ',
];

const reply = (conv, inputPrompt, noInputPrompts) => {
  conv.data.lastPrompt = inputPrompt;
  conv.data.lastNoInputPrompts = noInputPrompts;
  conv.ask(inputPrompt, noInputPrompts);
}
// Intent handlers
const normalIntent = (conv) => {
  reply(conv, 'Hey this is a question', SOME_NO_INPUT_PROMPTS);
}

const repeat = (conv) => {
  let repeatPrefix = promptFetch.getRepeatPrefix(); // randomly chooses from REPEAT_PREFIX
  // Move SSML start tags over
  if (conv.data.lastPrompt.startsWith(promptFetch.getSSMLPrefix())) {
    conv.data.lastPrompt =
        conv.data.lastPrompt.slice(promptFetch.getSSMLPrefix().length);
    repeatPrefix = promptFetch.getSSMLPrefix() + repeatPrefix;
  }
  conv.ask(repeatPrefix + conv.data.lastPrompt,
      conv.data.lastNoInputPrompts);
}

Java

private final List<String> REPEAT_PREFIX = Arrays.asList("Sorry, I said ", "Let me repeat that.");

private final String SsmlPrefix = "<speak>";

@ForIntent("Normal Intent")
public ActionResponse normalIntent(ActionRequest request) {
  ResponseBuilder responseBuilder = getResponseBuilder(request);
  responseBuilder.getConversationData().put("lastPrompt", "Hey this is a question");
  return responseBuilder.build();
}

@ForIntent("repeat")
public ActionResponse repeat(ActionRequest request) {
  ResponseBuilder responseBuilder = getResponseBuilder(request);
  String repeatPrefix = getRandomPromptFromList(REPEAT_PREFIX);
  // Move SSML start tags over
  String lastPrompt = (String) responseBuilder.getConversationData().get("lastPrompt");
  if (lastPrompt.startsWith(SsmlPrefix)) {
    String newLastPrompt = lastPrompt.substring(SsmlPrefix.length());
    responseBuilder.getConversationData().put("lastPrompt", newLastPrompt);
    repeatPrefix = SsmlPrefix + repeatPrefix;
  }
  responseBuilder.add(repeatPrefix + lastPrompt);
  return responseBuilder.build();
}

התאמה אישית של השיחה לפי ההעדפות של המשתמשים

הפעולה יכולה לבקש מהמשתמשים את ההעדפות שלהם ולזכור את ההעדפות שלהם לשימוש מאוחר יותר, וכך להתאים אישית את השיחות העתידיות עם המשתמשים.

הפעולה לדוגמה הזו מספקת למשתמשים דיווח מזג אוויר לפי מיקוד. הקוד לדוגמה הבא שואל את המשתמש אם הוא רוצה שהפעולה תזכור את המיקוד שלו לשיחות מאוחרות יותר.

Node.js

app.intent('weather_report', (conv) => {
  let zip = conv.arguments.get('zipcode');
  conv.data.zip = zip;
  conv.ask(getWeatherReport(zip));
  conv.ask(new Confirmation(`Should I remember ${zip} for next time?`));
});

app.intent('remember_zip', (conv, params, confirmation) => {
  if (confirmation) {
    conv.user.storage.zip = conv.data.zip;
    conv.close('Great! See you next time.');
  } else conv.close('Ok, no problem.');
});

Java

@ForIntent("weather_report")
public ActionResponse weatherReport(ActionRequest request) {
  ResponseBuilder responseBuilder = getResponseBuilder(request);
  String zip = (String) request.getArgument("location").getStructuredValue().get("zipCode");
  responseBuilder.getConversationData().put("zip", zip);
  responseBuilder.add(getWeatherReport(zip));
  responseBuilder.add(
      new Confirmation().setConfirmationText("Should I remember " + zip + " for next time?"));
  return responseBuilder.build();
}

@ForIntent("remember_zip")
public ActionResponse rememberZip(ActionRequest request) {
  ResponseBuilder responseBuilder = getResponseBuilder(request);
  if (request.getUserConfirmation()) {
    responseBuilder.getUserStorage().put("zip", responseBuilder.getConversationData().get("zip"));
    responseBuilder.add("Great! See you next time.").endConversation();
  } else {
    responseBuilder.add("Ok, no problem.").endConversation();
  }
  return responseBuilder.build();
}

אחרי ששואלים את המשתמש באיזה מיקוד הוא נמצא בתיבת הדו-שיח הראשונה, אפשר לדלג על ההודעה בהפעלה הבאה ולהשתמש באותו מיקוד. רצוי לספק מסלול מילוט (כמו צ'יפ הצעה שמאפשר להם לבחור מיקוד אחר), אבל אם תקצר את תור השיחה במקרה הנפוץ, החוויה תהיה הרבה יותר חלקה.

Node.js

app.intent('weather_report', (conv) => {
  let zip = conv.arguments.get('zipcode');
  if (zip) {
    conv.close(getWeatherReport(zip));
  } else if (conv.user.storage.zip) {
    conv.ask(new SimpleResponse(getWeatherReport(conv.user.storage.zip)));
    conv.ask(new Suggestions('Try another zipcode'));
  } else {
    conv.ask('What\'s your zip code?');
  }
});

app.intent('provide_zip_df', (conv) => {
  conv.user.storage.zip = conv.arguments.get('zipcode');
  conv.close(getWeatherReport(conv.user.storage.zip));
});

Java

public ActionResponse weatherReport2(ActionRequest request) {
  ResponseBuilder responseBuilder = getResponseBuilder(request);
  String zip = (String) request.getArgument("location").getStructuredValue().get("zipCode");
  if (zip != null) {
    responseBuilder.add(getWeatherReport(zip)).endConversation();
  } else if ((zip = (String) responseBuilder.getUserStorage().get("zip")) != null) {
    responseBuilder.add(new SimpleResponse().setTextToSpeech(getWeatherReport(zip)));
    responseBuilder.add(new Suggestion().setTitle("Try another zipcode"));
  } else {
    responseBuilder.add("What's your zip code?");
  }
  return responseBuilder.build();
}

התאמה אישית למשתמשים חוזרים

שמירה על מצב מסוים בין השיחות מבטיחה חוויה טבעית הרבה יותר למשתמשים חוזרים. השלב הראשון ביצירת החוויה הזו הוא להציג למשתמשים חוזרים באופן שונה. לדוגמה, תוכלו לצמצם את הודעת הפתיחה או להציף מידע שימושי בהתאם לשיחות קודמות. לשם כך, עליכם להשתמש בנכס AppRequest.User lastSeen הנכנס כדי לבדוק אם למשתמש הייתה אינטראקציה עם הפעולה בעבר. אם הנכס lastSeen נכלל במטען הייעודי (payload) של הבקשה, אפשר להשתמש בהודעת פתיחה שונה מהרגיל.

הקוד שבהמשך משתמש בספריית הלקוח של Node.js כדי לאחזר את הערך של last.seen.

Node.js

// This function is used to handle the welcome intent
// In Dialogflow, the Default Welcome Intent ('input.welcome' action)
// In Actions SDK, the 'actions.intent.MAIN' intent
const welcome = (conv) => {
  if (conv.user.last.seen) {
    conv.ask(`Hey you're back...`);
  } else {
    conv.ask('Welcome to World Cities Trivia!...');
  }
}

Java

// This function is used to handle the welcome intent
// In Dialogflow, the Default Welcome Intent ('input.welcome' action)
// In Actions SDK, the 'actions.intent.MAIN' intent
public ActionResponse welcome(ActionRequest request) {
  ResponseBuilder responseBuilder = getResponseBuilder(request);
  if (request.getUser().getLastSeen() != null) {
    responseBuilder.add("Hey you're back...");
  } else {
    responseBuilder.add("Welcome to Number Genie!...");
  }
  return responseBuilder.build();
}

כדי לשפר את הודעת הפתיחה, אפשר להתאים את התשובה לערך האמיתי של lastSeen. לדוגמה, משתמשים שהאינטראקציה האחרונה שלהם התרחשה חודשים רבים לפני האינטראקציה הנוכחית, עשויים לקבל הודעת פתיחה שונה מזו של אלה שהשתמשו ב-Action ביום הקודם.

בקרת עוצמת קול במהלך השיחה

במכשירים נתמכים, Assistant מאפשרת למשתמשים לשלוט בעוצמת הקול של המכשיר באמצעות אמירת משהו כמו "הגבירי את עוצמת הקול" או "הגדירו את עוצמת הקול ל-50 אחוזים". אם יש לכם כוונות שמטפלות בביטויים דומים לאימון, לכוונות שלכם יש עדיפות. מומלץ לאפשר ל-Assistant לטפל בבקשות המשתמשים האלה, אלא אם יש לפעולה סיבה ספציפית לכך.