IBM Cloud Docs
在客户机应用程序中实现对话响应

在客户机应用程序中实现对话响应

对话节点可以使用包含文本、图像或交互式元素(例如,可单击选项)的响应来回应用户。 如果您正在开发自己的客户应用程序,则必须正确显示对话框返回的所有响应类型。 有关对话响应的更多信息,请参阅 响应

响应输出格式

缺省情况下,在从 output.generic API 返回的响应 JSON 中的 /message 对象中指定对话节点的响应。 generic 对象包含由最多 5 个响应元素组成的数组,这些响应元素可用于任何通道。 以下 JSON 示例显示了包含文本和图像的响应:

{
  "output": {
    "generic": [
      {
        "response_type": "text",
        "text": "OK, here's a picture of a dog."
      },
      {
        "response_type": "image",
        "source": "http://example.com/dog.jpg"
      }
    ],
    "text" : ["OK, here's a picture of a dog."]
  },
  "user_id": "faf4a112-f09f-4a95-a0be-43c496e6ac9a"
}

如本示例所示,还会在 output.text 数组中返回文本响应 (OK, here's a picture of a dog.)。 包含该数组是为了与不支持 output.generic 格式的较早应用程序兼容。

您的客户端应用程序负责处理所有响应类型。 在本例中,应用程序需要向用户显示指定的文本和图像。

响应类型

响应的每个元素都属于其中一种受支持的响应类型。 每种响应类型都由一组不同的JSON属性指定,因此每个响应包含的属性因响应类型而异。 有关 /message API响应模型的完整信息,请参阅 API参考

此部分描述了可用的响应类型以及这些类型在 /message API 响应 JSON 中的表示方式。 如果您正在使用 Watson,您可以使用为您的语言提供的接口来访问相同的对象。

此部分中的示例显示在运行时从 /message API 返回的 JSON 数据的格式,并且与用于在对话节点中定义响应的 JSON 格式不同。 您可以使用示例中的格式通过 webhook 更新 output.generic。 要使用 JSON 编辑器更新 output.generic,请参阅 使用 JSON 编辑器定义响应

文本

text 响应类型用于对话中的普通文本响应:

{
  "output": {
    "generic":[
      {
        "response_type": "text",
        "text": "OK, you want to fly to Boston next Monday."
      }
    ]
  },
  "user_id": "faf4a112-f09f-4a95-a0be-43c496e6ac9a"
}

为了实现兼容性,对话响应中的 output.text 数组中也包含相同的文本。

图像

image 响应类型指示客户机应用程序显示图像,并可选择附带标题和描述:

{
  "output": {
    "generic":[
      {
        "response_type": "image",
        "source": "http://example.com/image.jpg",
        "title": "Image example",
        "description": "This is an example image"
      }
    ]
  },
  "user_id": "faf4a112-f09f-4a95-a0be-43c496e6ac9a"
}

您的应用程序负责检索 source 属性指定的图像并将其显示给用户。 如果提供了可选的 titledescription,那么应用程序可以用任何适当的方式显示标题和描述(例如,在图像下方呈现标题,并将描述呈现为悬停文本)。

视频

video 响应类型指示客户机应用程序显示视频,(可选) 附带 titledescriptionalt_text 以实现辅助功能选项:

{
  "output": {
    "generic":[
      {
        "response_type": "video",
        "source": "http://example.com/video.mp4",
        "title": "Video example",
        "description": "This is an example video",
        "alt_text": "A video showing a great example",
        "channel_options": {
          "chat": {
            "dimensions": {
              "base_height": 180
            }
          }
        }
      }
    ]
  },
  "user_id": "faf4a112-f09f-4a95-a0be-43c496e6ac9a"
}

您的应用程序负责检索 source 属性指定的视频并将其显示给用户。 如果提供了可选的 titledescription,那么应用程序可以采用任何适当的方式来显示它们。

可选的 channel_options.chat.dimensions.base_height 属性采用一个数字,表示视频以 360 像素的宽度呈现。 如果视频以非标准大小呈现,那么应用程序将使用此值来保持视频的正确宽高比。

音频

audio 响应类型指示客户机应用程序播放音频文件:

{
  "output": {
    "generic":[
      {
        "response_type": "audio",
        "source": "http://example.com/audio.mp3",
        "channel_options": {
          "voice_telephony": {
            "loop": true
          }
        }
      }
    ]
  },
  "user_id": "faf4a112-f09f-4a95-a0be-43c496e6ac9a"
}

您的应用程序负责播放音频文件。

可选的 channel_options.voice_telephony.loop 属性采用指示音频文件是否作为连续循环播放的布尔值。 此选项通常用于保存可能需要在未定义的时间内继续的音乐。

iframe

iframe 响应类型指示客户机应用程序在嵌入式 iframe 元素中显示内容,可以选择附带标题:

{
  "output": {
    "generic":[
      {
        "response_type": "iframe",
        "source": "http://example.com/iframe.html",
        "title": "My IFrame"
      }
    ]
  },
  "user_id": "faf4a112-f09f-4a95-a0be-43c496e6ac9a"
}

您的应用程序负责显示 iframe 内容。 嵌入式 iframe 中的内容对于显示第三方内容或对于您自己站点中不想使用 user_defined 响应类型重新编写的内容很有用。

暂停

pause 响应类型指示应用程序在下一个响应之前等待指定的时间间隔:

{
  "output": {
    "generic":[
      {
        "response_type": "pause",
        "time": 500,
        "typing": false
      }
    ]
  },
  "user_id": "faf4a112-f09f-4a95-a0be-43c496e6ac9a"
}

对话可能会要求暂停,以便有时间完成请求,或者模拟人工代理在回答之间暂停的样子。 暂停可以是不超过 10 秒的任何持续时间。

pause 响应通常与其他响应组合在一起发送。 在数组中的下一个响应之前,您的应用程序将暂停 time 属性指定的间隔时间(以毫秒为单位)。 可选的 typing 属性要求客户端应用程序显示一个 user is typing 指示器(如果支持),以模拟真人客服。

选项

option 响应类型指示客户机应用程序显示用户界面控件,以支持用户从选项列表中进行选择,然后根据所选的选项将输入发送回助手:

{
  "output": {
    "generic":[
      {
        "response_type": "option",
        "title": "Available options",
        "description": "Please select one of the following options:",
        "preference": "button",
        "options": [
          {
            "label": "Option 1",
            "value": {
              "input": {
                "text": "option 1"
              }
            }
          },
          {
            "label": "Option 2",
            "value": {
              "input": {
                "text": "option 2"
              }
            }
          }
        ]
      }
    ]
  },
  "user_id": "faf4a112-f09f-4a95-a0be-43c496e6ac9a"
}

应用程序可以使用任何适用的用户界面控件(例如,一组按钮或一个下拉列表)来显示指定的选项。 可选的 preference 属性指示您的应用程序使用的首选控制类型( buttondropdown ),如果支持的话。 为了获得最佳用户体验,较好的做法是在选项不超过三个时,将选项显示为按钮;选项超过三个时,将选项显示为下拉列表。

对于每个选项,label 属性指定了在UI控件中显示的选项标签文本。 value 属性指定了当用户选择相应选项时,发送回助手(使用 /message API)的输入。

有关在简单客户机应用程序中实现 option 响应的示例,请参阅示例:实现 option 响应

建议

Plus

歧义消除功能使用 suggestion 响应类型来建议可能的匹配项,以明确用户想要做什么。 suggestion 响应包含 suggestions 数组,其中每个建议对应于一个可能的匹配对话节点:

{
  "output": {
    "generic": [
      {
        "response_type": "suggestion",
        "title": "Did you mean:",
        "suggestions": [
          {
            "label": "I'd like to order a drink.",
            "value": {
              "intents": [
                {
                  "intent": "order_drink",
                  "confidence": 0.7330395221710206
                }
              ],
              "entities": [],
              "input": {
                "suggestion_id": "576aba3c-85b9-411a-8032-28af2ba95b13",
                "text": "I want to place an order"
              }
            },
            "output": {
              "text": [
                "I'll get you a drink."
              ],
              "generic": [
                {
                  "response_type": "text",
                  "text": "I'll get you a drink."
                }
              ],
              "nodes_visited_details": [
                {
                  "dialog_node": "node_1_1547675028546",
                  "title": "order drink",
                  "user_label": "I'd like to order a drink.",
                  "conditions": "#order_drink"
                }
              ]
            },
            "source_dialog_node": "root"
          },
          {
            "label": "I need a drink refill.",
            "value": {
              "intents": [
                {
                  "intent": "refill_drink",
                  "confidence": 0.2529746770858765
                }
              ],
              "entities": [],
              "input": {
                "suggestion_id": "6583b547-53ff-4e7b-97c6-4d062270abcd",
                "text": "I need a drink refill"
              }
            },
            "output": {
              "text": [
                "I'll get you a refill."
              ],
              "generic": [
                {
                  "response_type": "text",
                  "text": "I'll get you a refill."
                }
              ],
              "nodes_visited_details": [
                {
                  "dialog_node": "node_2_1547675097178",
                  "title": "refill drink",
                  "user_label": "I need a drink refill.",
                  "conditions": "#refill_drink"
                }
              ]
            },
            "source_dialog_node": "root"
          }
        ]
      }
    ],
  },
  "user_id": "faf4a112-f09f-4a95-a0be-43c496e6ac9a"
}

suggestion 回复的结构与 option 回复的结构类似。 与选项一样,每条建议都包含一个可向用户显示的 label 和一个 value,用于在用户选择相应建议时向助手发送输入。 要在应用程序中实现 suggestion 响应,可以使用您会用于 option 响应的相同方法。

有关消歧功能的更多信息,请参阅消歧

用户定义的

用户定义的响应类型最多可以包含 5000 KB 数据,以支持您在客户机中实现的响应类型。 例如,您可以定义用户定义的响应类型以显示特殊的颜色编码卡,或者在表或图形中格式化数据。

响应的 user_defined 属性是可以包含任何有效 JSON 数据的对象:

{
  "output": {
    "generic":[
      {
        "response_type": "user_defined",
        "user_defined": {
          "field_1": "String value",
          "array_1": [
            1,
            2
          ],
          "object_1": {
            "property_1": "Another string value"
          }
        }
      }
    ]
  },
  "user_id": "faf4a112-f09f-4a95-a0be-43c496e6ac9a"
}

应用程序可以通过您选择的任何方式来解析和显示数据。

示例:实现 option 响应

为了展示客户端应用程序如何处理选项响应(即提示用户从选项列表中进行选择),我们可以扩展 构建客户端应用程序中 使用的客户端示例。 示例是一个简化的客户端应用程序,它使用标准输入和输出来处理三个意图(发送问候、显示当前时间和退出应用程序):

Welcome to the example!
>> hello
Good day to you.
>> what time is it?
The current time is 12:40:42 PM.
>> goodbye
OK! See you later.

如果要尝试示例代码,请设置所需的工作空间并获取所需的 API 详细信息。 有关更多信息,请参阅构建客户机应用程序

接收 option 响应

如果您希望向用户提供有限的选项列表,而不是解释自然语言输入,可以使用 option 响应。 该响应可用于任何希望用户快速从一组明确选项中进行选择的情况。

在我们的简化版客户应用程序中,我们使用此功能从助手支持的列表中选择操作(问候、显示时间和退出)。 除了先前显示的三个意向(#hello#time#goodbye)之外,示例工作空间还支持第四个意向:#menu,当用户要求查看可用操作列表时,即与此意向相匹配。

工作空间识别到 #menu 意向时,对话会使用 option 响应进行响应:

{
  "output": {
    "generic": [
      {
        "title": "What do you want to do?",
        "options": [
          {
            "label": "Send greeting",
            "value": {
              "input": {
                "text": "hello"
              }
            }
          },
          {
            "label": "Display the local time",
            "value": {
              "input": {
                "text": "time"
              }
            }
          },
          {
            "label": "Exit",
            "value": {
              "input": {
                "text": "goodbye"
              }
            }
          }
        ],
        "response_type": "option"
      }
    ],
    "intents": [
      {
        "intent": "menu",
        "confidence": 0.6178638458251953
      }
    ],
    "entities": []
  },
  "user_id": "faf4a112-f09f-4a95-a0be-43c496e6ac9a"
}

option 响应包含多个要向用户显示的选项。 每个选项都包含两个对象:labelvaluelabel 是面向用户的字符串,用于标识选项; value 指定了相应的消息输入,如果用户选择了该选项,则该消息将发送回助手。

我们的客户端应用程序需要使用此响应中的数据来构建我们向用户显示的输出,并向助手发送适当的消息。

列出可用选项

处理 option 响应的第一步是使用每个选项的 label 属性指定的文本向用户显示选项。 可以使用应用程序支持的任何方法来显示选项,通常是下拉列表或一组可单击的按钮。 如果指定了可选 preference 属性,则该属性将尽可能指示应用程序使用的显示类型。

简化的示例使用标准输入和输出,因此我们无权访问真正的 UI。 我们以编号列表的形式呈现选项。

// Option example 1: lists options.

const prompt = require('prompt-sync')();
const AssistantV2 = require('ibm-watson/assistant/v2');
const { IamAuthenticator } = require('ibm-watson/auth');

// Set up Assistant service wrapper.
const service = new AssistantV2({
  version: '2019-02-28',
  authenticator: new IamAuthenticator({
    apikey: '{apikey}', // replace with API key
  })
});

const assistantId = '{assistant_id}'; // replace with assistant ID
let sessionId;

// Create session.
service
  .createSession({
    assistantId,
  })
  .then(res => {
    sessionId = res.result.session_id;
    sendMessage({
      messageType: 'text',
      text: '',  // start conversation with empty message
    });
  })
  .catch(err => {
    console.log(err); // something went wrong
  });

// Send message to assistant.
function sendMessage(messageInput) {
  service
    .message({
      assistantId,
      sessionId,
      input: messageInput,
    })
    .then(res => {
      processResponse(res.result);
    })
    .catch(err => {
      console.log(err); // something went wrong
    });
}

// Process the response.
function processResponse(response) {

  let endConversation = false;

  // Check for client actions requested by the assistant.
  if (response.output.actions) {
    if (response.output.actions[0].type === 'client'){
      if (response.output.actions[0].name === 'display_time') {
        // User asked what time it is, so we output the local system time.
        console.log('The current time is ' + new Date().toLocaleTimeString() + '.');
      } else if (response.output.actions[0].name === 'end_conversation') {
        // User said goodbye, so we're done.
        console.log(response.output.generic[0].text);
        endConversation = true;
      }
    }
  } else {
    // Display the output from assistant, if any. Supports only a single
    // response.
    if (response.output.generic) {
      if (response.output.generic.length > 0) {
        switch (response.output.generic[0].response_type) {
          case 'text':
            // It's a text response, so we just display it.
            console.log(response.output.generic[0].text);
            break;
          case 'option':
            // It's an option response, so we'll need to show the user
            // a list of choices.
            console.log(response.output.generic[0].title);
            const options = response.output.generic[0].options;
            // List the options by label.
            for (let i = 0; i < options.length; i++) {
              console.log((i+1).toString() + '. ' + options[i].label);
            }
            break;
        }
      }
    }
  }

  // If we're not done, prompt for the next round of input.
  if (!endConversation) {
    const newMessageFromUser = prompt('>> ');
    newMessageInput = {
      messageType: 'text',
      text: newMessageFromUser,
    }
    sendMessage(newMessageInput);
  } else {
    // We're done, so we delete the session.
    service
      .deleteSession({
        assistantId,
        sessionId,
      })
      .then(res => {
        return;
      })
      .catch(err => {
        console.log(err); // something went wrong
      });
  }
}

下面将进一步讨论用于输出来自助手的响应的代码。 现在,应用程序不采用 text 响应,而改为支持 textoption 响应类型:

    // Display the output from assistant, if any. Supports only a single
    // response.
    if (response.output.generic) {
      if (response.output.generic.length > 0) {
        switch (response.output.generic[0].response_type) {
          case 'text':
            // It's a text response, so we just display it.
            console.log(response.output.generic[0].text);
            break;
          case 'option':
            // It's an option response, so we'll need to show the user
            // a list of choices.
            console.log(response.output.generic[0].title);
            const options = response.output.generic[0].options;
            // List the options by label.
            for (let i = 0; i < options.length; i++) {
              console.log((i+1).toString() + '. ' + options[i].label);
            }
            break;
        }
      }
    }

如果 response_type = text,我们将像之前一样显示输出。 但如果 response_type = option,我们就必须做更多的工作。 首先,我们显示 title 属性的值,作为介绍选项列表的引导文本;然后,我们列出选项,使用 label 属性的值来标识每个选项。 (实际应用程序会在下拉列表中显示这些标签,或者将其显示为可单击按钮上的标签。)

可以通过触发 #menu 意向来查看结果:

Welcome to the example!
>> what are the available actions?
What do you want to do?
1. Send greeting
2. Display the local time
3. Exit
>> 2
Sorry, I have no idea what you're talking about.
>>

如您所见,应用程序现在通过列出可用选项来正确处理 option 响应。 不过,我们尚未将用户的选择转化为有意义的输入。

选择选项

除了 label,回复中的每个选项还包含一个 value 对象,其中包含输入数据,如果用户选择相应的选项,这些数据将发送回助手。 value.input 对象与 /message API的 input 属性相同,这意味着我们可以原封不动地将该对象发送给助手。

当客户机接收到 option 响应时,我们设置新的 promptOption 标志。 此标志为 true 时,表示我们知道要从 value.input 中获取下一轮输入,而不是接受来自用户的自然语言文本输入。 我们仍然没有真正的用户界面,因此我们提示用户从列表中选择一个有效的选项。

// Option example 2: sends back selected option value.

const prompt = require('prompt-sync')();
const AssistantV2 = require('ibm-watson/assistant/v2');
const { IamAuthenticator } = require('ibm-watson/auth');

// Set up Assistant service wrapper.
const service = new AssistantV2({
  version: '2019-02-28',
  authenticator: new IamAuthenticator({
    apikey: '{apikey}', // replace with API key
  })
});

const assistantId = '{assistant_id}'; // replace with assistant ID
let sessionId;

// Create session.
service
  .createSession({
    assistantId,
  })
  .then(res => {
    sessionId = res.result.session_id;
    sendMessage({
      messageType: 'text',
      text: '',  // start conversation with empty message
    });
  })
  .catch(err => {
    console.log(err); // something went wrong
  });

// Send message to assistant.
function sendMessage(messageInput) {
  service
    .message({
      assistantId,
      sessionId,
      input: messageInput,
    })
    .then(res => {
      processResponse(res.result);
    })
    .catch(err => {
      console.log(err); // something went wrong
    });
}

// Process the response.
function processResponse(response) {

  let endConversation = false;
  let promptOption = false;

  // Check for client actions requested by the assistant.
  if (response.output.actions) {
    if (response.output.actions[0].type === 'client'){
      if (response.output.actions[0].name === 'display_time') {
        // User asked what time it is, so we output the local system time.
        console.log('The current time is ' + new Date().toLocaleTimeString() + '.');
      } else if (response.output.actions[0].name === 'end_conversation') {
        // User said goodbye, so we're done.
        console.log(response.output.generic[0].text);
        endConversation = true;
      }
    }
  } else {
    // Display the output from assistant, if any. Supports only a single
    // response.
    if (response.output.generic) {
      if (response.output.generic.length > 0) {
        switch (response.output.generic[0].response_type) {
          case 'text':
            // It's a text response, so we just display it.
            console.log(response.output.generic[0].text);
            break;
          case 'option':
              // It's an option response, so we'll need to show the user
              // a list of choices.
              console.log(response.output.generic[0].title);
              const options = response.output.generic[0].options;
              // List the options by label.
              for (let i = 0; i < options.length; i++) {
                console.log((i+1).toString() + '. ' + options[i].label);
              }
            promptOption = true;
            break;
        }
      }
    }
  }

  // If we're not done, prompt for the next round of input.
  if (!endConversation) {
    let messageInput;
    if (promptOption == true) {
      // Prompt for a valid selection from the list of options.
      let choice;
      do {
        choice = prompt('? ');
          if (isNaN(choice)) {
            choice = 0;
          }
      } while (choice < 1 || choice > response.output.generic[0].options.length);
      const value = response.output.generic[0].options[choice-1].value;
      // Use message input from the selected option.
      messageInput = value.input;
    } else {
      // We're not showing options, so we just prompt for the next
      // round of input.
      const newText = prompt('>> ');
      messageInput = {
        text: newText,
      }
    }
    sendMessage(messageInput);
  } else {
    // We're done, so we delete the session.
    service
      .deleteSession({
        assistantId,
        sessionId,
      })
      .then(res => {
        return;
      })
      .catch(err => {
        console.log(err); // something went wrong
      });
  }
}

我们要做的就是使用所选答案中的 value.input 对象作为下一轮消息输入,而不是使用文本输入来创建一个新的 input 对象。 然后,助手会做出与用户直接输入文本完全相同的回应。

Welcome to the example!
>> hi
Good day to you.
>> what are the choices?
What do you want to do?
1. Send greeting
2. Display the local time
3. Exit
? 2
The current time is 1:29:14 PM.
>> bye
OK! See you later.

现在可以通过发出自然语言请求或从选项菜单中进行选择来访问助手的所有功能。

suggestion 回复也采用同样的方法。 如果套餐支持消歧功能,那么在不清楚可能的选项中哪个是正确的情况下,可以使用类似的逻辑提示用户从列表中进行选择。 有关消歧功能的更多信息,请参阅消歧