구현 도움말 (Dialogflow)

다음 팁을 검토하여 적절한 대화 설계 권장사항을 작업에 구현하세요.

예상되는 변화

Dialogflow의 '사용자가 말함' 입력에서 이를 처리합니다. 또한 동일한 작업에 매핑할 수 있는 인텐트를 2개 이상 사용합니다. 이때 각 인텐트는 서로 다른 '사용자의 말' 문구 세트로 트리거될 수 있습니다.

유용한 메시지를 다시 표시하고 적절하게 실패함

작업이 입력을 받지 못했거나(입력 없음이라고 함) 사용자의 입력을 이해하지 못해서 (일치하지 않음) 작업을 진행할 수 없는 경우가 있습니다. 이 경우 어시스턴트는 먼저 사용자가 다른 작업을 트리거할지 판단하려고 시도합니다. 어시스턴트가 사용자의 입력을 다른 작업에 매칭하지 않으면 사용자는 작업의 컨텍스트에서 계속 진행합니다. 이 시나리오는 언제든지 발생할 수 있으므로 대체가 포함된 대화에서 각 차례에 입력 없음 및 일치하지 않는 상황을 고유하게 처리하는 것이 좋습니다. 대체를 사용하면 사용자가 서비스를 정상적으로 이용할 수 있도록 지원할 수 있습니다.

이렇게 하려면 conv.data 객체에서 fallbackCount 변수를 초기화하고 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();
}

언제든지 도울 수 있도록 준비하세요

'What can I do?'과 같은 도움말 문구를 수신 대기하는 인텐트를 만듭니다. "무엇을 말씀해 주시겠어요?" 또는 "도와줘" 이 인텐트에서는 에이전트가 할 수 있는 작업에 관한 개요를 제공하고 사용자에게 가능한 작업을 안내하는 몇 가지 (순환식) 응답을 제공합니다. 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();
}

사용자가 정보를 다시 볼 수 있도록 허용

출력을 conv.data.lastPrompt에 추가하는 프록시 함수로 모든 app.ask(output) 메서드를 래핑합니다. '무엇을?'과 같은 사용자의 반복 메시지를 수신 대기하는 반복 인텐트를 만듭니다. "다시 말해 줘" 또는 "반복할 수 있어?"라고 말해보세요. 사용자가 무언가를 반복하도록 요청했음을 확인하는 데 사용할 수 있는 반복 프리픽스 배열을 만듭니다. 반복 인텐트 핸들러에서 반복 접두사의 연결된 문자열과 conv.data.lastPrompt 값을 사용하여 ask()를 호출합니다. 마지막 프롬프트에서 사용된 경우 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 속성이 요청 페이로드에 포함된 경우 평소와 다른 인사말을 사용할 수 있습니다.

아래 코드는 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의 실제 값에 맞게 응답을 조정하여 이 인사말을 더욱 향상시킬 수 있습니다. 예를 들어 현재 상호작용이 발생하기 몇 개월 전에 마지막 상호작용이 발생한 사용자는 전날 작업을 사용한 사용자와는 다른 인사말을 받을 수 있습니다.

대화 중 볼륨 조절

지원되는 기기에서 어시스턴트를 사용하면 사용자가 "볼륨 높여 줘" 또는 "볼륨 50퍼센트로 설정해"와 같이 말하여 대화 작업 내에서 기기 볼륨을 제어할 수 있습니다. 유사한 학습 문구를 처리하는 인텐트가 있는 경우 인텐트가 우선 적용됩니다. 작업에 특별한 이유가 없다면 어시스턴트가 이러한 사용자 요청을 처리하도록 하는 것이 좋습니다.