نصائح التنفيذ (Dialogflow)

راجع النصائح التالية لتنفيذ ممارسات تصميم المحادثات الجيدة في الإجراء الخاص بك.

توقَّع بعض الاختلافات

يمكنك معالجة هذا في حقل "يقول المستخدم" في Dialogflow. استخدِم أيضًا أكثر من غرض واحد يمكن ربطه بالإجراء نفسه، ويمكن تشغيل كل هدف باستخدام مجموعات مختلفة من عبارات "يقول المستخدم".

تقديم طلبات مفيدة والفشل بشكلٍ ملائم

في بعض الأحيان، لا يمكن أن يمضي الإجراء الخاص بك لأنّه لم يتلقَّ إدخالاً (وهو ما يُعرف باسم no-input) أو لم يفهم البيانات التي أدخلها المستخدم (المعروف أيضًا باسم "no-match"). عند حدوث ذلك، يحاول "مساعد Google" أولاً تحديد ما إذا كان المستخدم يريد تشغيل إجراء مختلف. إذا لم يطابق "مساعد Google" البيانات التي أدخلها المستخدم مع إجراء آخر، يواصل المستخدم تنفيذ الإجراء في سياق الإجراء الخاص بك. يمكن أن يحدث هذا السيناريو في أي وقت، لذلك فإن أفضل الممارسات هي التعامل بشكل فريد مع مواقف عدم الإدخال وعدم التطابق في كل منعطف في المحادثة باستخدام عنصر احتياطي. باستخدام العناصر الاحتياطية، يمكنك مساعدة المستخدمين على العودة إلى المسار الصحيح.

لإجراء ذلك، يجب إعداد المتغيّر fallbackCount في الكائن conv.data، وضبطه على 0. إعداد مصفوفة من مطالبتين احتياطيتين (مع تصعيدها بوضوح)، وإشعار احتياطي نهائي ينهي المحادثة.

بعد ذلك، أنشئ هدفًا احتياطيًا (يفضَّل أن يكون لكل غرض قابل للتنفيذ في الوكيل). في معالج الغرض، اسحب العدد الاحتياطي من الكائن conv.data واعمل على زيادة قيمته. وإذا كان أقل من 3، اسحب الطلب من المصفوفة 3. إذا كان العدد 4 أو أكثر، أغلق المحادثة باستخدام الطلب النهائي. في جميع الأغراض التي ليست عناصر احتياطية، أعِد ضبط العدد الاحتياطي إلى 0. من الناحية المثالية، نسِّق العناصر الاحتياطية لأهداف معيّنة لتكون مخصّصة لتلك الأغراض.

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) باستخدام دالة خادم وكيل تضيف الناتج إلى conv.data.lastPrompt. أنشئ هدفًا متكررًا يستمع إلى الطلبات للتكرار من المستخدم مثل "ماذا؟"، أو "قل ذلك مرة أخرى" أو "هل يمكنك تكرار ذلك؟". أنشئ صفيفًا من البادئات المتكررة التي يمكن استخدامها للإقرار بأن المستخدم طلب تكرار شيء ما. في معالج نية التكرار، يمكنك استدعاء 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();
}

التخصيص للمستخدمين المكرّري الزيارة

يضمن الحفاظ على بعض الحالة بين المحادثات تجربة طبيعية أكثر بكثير للمستخدمين المتكررين. تتمثل الخطوة الأولى في صياغة هذه التجربة في الترحيب بالمستخدمين العائدين بشكل مختلف. على سبيل المثال، يمكنك تقليص الترحيب أو عرض معلومات مفيدة بناءً على المحادثات السابقة. لإجراء ذلك، استخدِم سمة lastSeen الصادرة عن AppRequest.User لتحديد ما إذا كان المستخدم قد تفاعل مع الإجراء الخاص بك من قبل. إذا كانت السمة lastSeen مضمَّنة في حمولة الطلب، يمكنك استخدام رسالة ترحيب مختلفة عن المعتاد.

يستخدم الرمز البرمجي أدناه مكتبة عميل 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. على سبيل المثال، قد يتلقى المستخدمون الذين حدث تفاعلهم الأخير قبل عدة أشهر من التفاعل الحالي تحية مختلفة عن أولئك الذين استخدموا الإجراء في اليوم السابق.

التحكم في مستوى صوت المحادثة

على الأجهزة المتوافقة، يتيح "مساعد Google" للمستخدمين التحكم في مستوى صوت الجهاز ضمن "الإجراء المتّبع في المحادثة" من خلال قول عبارات مثل "رفع مستوى الصوت" أو "ضبط الصوت على 50 في المئة". وإذا كانت لديك أغراض تتعامل مع عبارات تدريب مشابهة، ستكون أهدافك لها الأولوية. ننصحك بالسماح للمساعد بالتعامل مع طلبات المستخدمين هذه ما لم يكن للإجراء سبب محدد لذلك.