在客户机应用程序中实现对话响应
对话节点可以使用包含文本、图像或交互式元素(例如,可单击选项)的响应来回应用户。 如果您正在开发自己的客户应用程序,则必须正确显示对话框返回的所有响应类型。 有关对话响应的更多信息,请参阅 响应。
响应输出格式
缺省情况下,在从 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
属性指定的图像并将其显示给用户。 如果提供了可选的 title
和 description
,那么应用程序可以用任何适当的方式显示标题和描述(例如,在图像下方呈现标题,并将描述呈现为悬停文本)。
视频
video
响应类型指示客户机应用程序显示视频,(可选) 附带 title
,description
和 alt_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
属性指定的视频并将其显示给用户。 如果提供了可选的 title
和 description
,那么应用程序可以采用任何适当的方式来显示它们。
可选的 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
属性指示您的应用程序使用的首选控制类型( button
或 dropdown
),如果支持的话。 为了获得最佳用户体验,较好的做法是在选项不超过三个时,将选项显示为按钮;选项超过三个时,将选项显示为下拉列表。
对于每个选项,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
响应的相同方法。
有关消歧功能的更多信息,请参阅消歧。
搜索
此功能仅对具有付费套餐的用户可用。
search
响应类型用于搜索技能返回 IBM Watson® Discovery的结果。 search
回复包括一系列 results
,其中每个都提供 Discovery返回的匹配信息:
{
"output": {
"generic": [
{
"response_type": "search",
"header": "I found the following information that might be helpful.",
"results": [
{
"title": "About",
"body": "IBM watsonx Assistant is a cognitive bot that you can customize for your business needs, and deploy across multiple channels to bring help to your customers where and when they need it.",
"url": "https://cloud.ibm.com/docs/watson-assistant?topic=watson-assistant-about",
"id": "6682eca3c5b3778ccb730b799a8063f3",
"result_metadata": {
"confidence": 0.08401551980328191,
"score": 0.73975396
},
"highlight": {
"Shortdesc": [
"IBM <em>watsonx</em> <em>Assistant</em> is a cognitive bot that you can customize for your business needs, and deploy across multiple channels to bring help to your customers where and when they need it."
],
"url": [
"https://cloud.ibm.com/docs/<em>assistant</em>?topic=<em>watson-assistant</em>-about"
],
"body": [
"IBM <em>watsonx</em> <em>Assistant</em> is a cognitive bot that you can customize for your business needs, and deploy across multiple channels to bring help to your customers where and when they need it."
]
}
}
]
}
]
},
"user_id": "58e1b04e-f4bb-469a-9e4c-dffe1d4ebf23"
}
对于每个搜索结果,title
、body
和 url
属性包含从 Discovery返回的内容。 搜索集成配置决定了在响应中,Discovery集合中的哪些字段映射到这些字段。 您的应用程序可以使用这些字段向用户显示结果(例如,您可以使用 body
文本显示匹配文档的摘要或描述,并使用 url
值创建一个链接,用户点击该链接即可打开文档)。
此外,header
属性提供一条消息,向用户显示有关搜索结果的信息。 当搜索成功时,header
会提供要在搜索结果之前显示的介绍性文本 (例如,I found the following information that might be helpful.
)。不同的消息文本指示搜索未返回任何结果,或者与 Discovery 服务的连接失败。 您可以在搜索技能配置中定制这些消息。
用户定义的
用户定义的响应类型最多可以包含 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
响应包含多个要向用户显示的选项。 每个选项都包含两个对象:label
和 value
。 label
是面向用户的字符串,用于标识选项; 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
响应,而改为支持 text
和 option
响应类型:
// 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
回复也采用同样的方法。 如果套餐支持消歧功能,那么在不清楚可能的选项中哪个是正确的情况下,可以使用类似的逻辑提示用户从列表中进行选择。 有关消歧功能的更多信息,请参阅消歧。