Actions-on-Google:类似的意图?
Actions-on-Google: Similar intents?
我正在使用 DialogFlow + Firebase 修改 Google 上的操作。这个想法是为默认 AoG 不支持的物联网设备定制构建。
就广播电视而言,目前有 2 个意向:
1) 渠道:它接受 3 个参数:device_name(自定义实体)、device_action(自定义实体)和值。
例如:请将广播频道更改为22.3
或将电视频道更改为 22
2) 体积:它接受 3 个参数:device_name(自定义实体)、device_action(自定义实体)和值。
例:请将收音机音量调到99
或者将电视的音量更改为 22
问题是代理似乎不能很好地区分两者。
执行有误吗?
// 编辑 2018 年 5 月 9 日:
index.js
// Copyright 2016, Google, Inc.
// Licensed under the Apache License, Version 2.0 (the 'License');
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an 'AS IS' BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
'use strict';
const util = require('util');
const functions = require('firebase-functions');
const {
dialogflow,
Suggestions,
BasicCard,
Button,
SimpleResponse,
} = require('actions-on-google');
const {values, concat, random, randomPop} = require('./util');
const responses = require('./responses');
/** Dialogflow Contexts {@link https://dialogflow.com/docs/contexts} */
const AppContexts = {
FACT: 'choose_fact-followup',
CATS: 'choose_cats-followup',
XXX: 'choose_xxx-followup',
};
/** Dialogflow Context Lifespans {@link https://dialogflow.com/docs/contexts#lifespan} */
const Lifespans = {
DEFAULT: 5,
};
const app = dialogflow({
debug: true,
init: () => ({
data: {
// Convert array of facts to map
facts: responses.categories.reduce((o, c) => {
o[c.category] = c.facts.slice();
return o;
}, {}),
cats: responses.cats.facts.slice(), // copy cat facts
},
}),
});
/**
* Greet the user and direct them to next turn
* @param {DialogflowConversation} conv DialogflowConversation instance
* @return {void}
*/
app.intent('Unrecognized Deep Link Fallback', (conv) => {
const response = util.format(responses.general.unhandled, conv.query);
const suggestions = responses.categories.map((c) => c.suggestion);
conv.ask(response, new Suggestions(suggestions));
});
// redirect to the intent handler for tell_fact
app.intent('choose_fact', 'tell_fact');
// Say a fact
app.intent('tell_fact', (conv, {category}) => {
const {facts, cats, xxx} = conv.data;
if (values(facts).every((c) => !c.length)) {
// If every fact category facts stored in conv.data is empty,
// close the conversation
return conv.close(responses.general.heardItAll);
}
const categoryResponse =
responses.categories.find((c) => c.category === category);
const fact = randomPop(facts[categoryResponse.category]);
if (!fact) {
const otherCategory =
responses.categories.find((other) => other !== categoryResponse);
const redirect = otherCategory.category;
const parameters = {
category: redirect,
};
// Add facts context to outgoing context list
conv.contexts.set(AppContexts.FACT, Lifespans.DEFAULT, parameters);
const response = [
util.format(responses.transitions.content.heardItAll, category, redirect),
];
// If cat facts not loaded or there still are cat facts left
if (cats.length) {
response.push(responses.transitions.content.alsoCats);
}
response.push(responses.general.wantWhat);
conv.ask(concat(...response));
conv.ask(new Suggestions(otherCategory.suggestion));
if (cats.length) {
conv.ask(new Suggestions(responses.cats.suggestion));
}
return;
}
const {factPrefix} = categoryResponse;
// conv.ask can be called multiple times to have the library construct
// a single response itself the response will get sent at the end of
// the function or if the function returns a promise, after the promise
// is resolved.
conv.ask(new SimpleResponse({
speech: concat(factPrefix, fact),
text: factPrefix,
}));
conv.ask(responses.general.nextFact);
conv.ask(new BasicCard({
title: fact,
image: random(responses.content.images),
buttons: new Button({
title: responses.general.linkOut,
url: responses.content.link,
}),
}));
console.log('hiwwxxxxxww thi is aaron');
conv.ask(responses.general.suggestions.confirmation);
});
// Redirect to the intent handler for tell_cat_fact
app.intent('choose_cats', 'tell_cat_fact');
// Say a cat fact
app.intent('tell_cat_fact', (conv) => {
const {cats} = conv.data;
console.log('this is cats data' + {cats});
const fact = randomPop(cats);
if (!fact) {
conv.contexts.delete(AppContexts.FACT);
conv.contexts.delete(AppContexts.CATS);
conv.ask(responses.transitions.cats.heardItAll);
return conv.ask(responses.general.suggestions.confirmation);
}
const {factPrefix, audio} = responses.cats;
// conv.ask can be called multiple times to have the library construct
// a single response itself. The response will get sent at the end of
// the function or if the function returns a promise, after the promise
// is resolved.
const sound = util.format(audio, random(responses.cats.sounds));
conv.ask(new SimpleResponse({
// <speak></speak> is needed here since factPrefix is a SSML string
// and contains audio.
speech: `<speak>${concat(factPrefix, sound, fact)}</speak>`,
text: factPrefix,
}));
conv.ask(responses.general.nextFact);
conv.ask(new BasicCard({
title: fact,
image: random(responses.cats.images),
buttons: new Button({
title: responses.general.linkOut,
url: responses.cats.link,
}),
}));
console.log('hiwwxxxxxww thi is aaron');
conv.ask(responses.general.suggestions.confirmation);
});
//say a tv channel
app.intent('volume', (conv, {device_name,device_action, value}) => {
var no_device_name = device_name;
var no_value = value;
var no_device_action = device_action;
var this_device_value = util.inspect(value, false, null);
var this_device_name = util.inspect(device_name, false, null);
var this_device_action = util.inspect(device_action, false, null);
console.log(this_device_action[0]);
if (no_device_name[0] == 'channel'){
console.log('inside tv but CHANNEL');
}
else{
console.log('VOLUME');
}
console.log('THIS IS VOL');
console.log(no_device_action[0]);
conv.ask(`Alright, ${device_name} VOLUM is now ${value}! `);
console.log('inside volume ' + value[0]);
console.log('inside volume' + device_name[0]);
console.log('inside volume' + device_action[0]);
console.log('hiwwxxxxxww thi is aaron');
});
//say a tv channel
app.intent('channel', (conv, {device_channel_name, device_channel_action, channel_value}) => {
console.log('THIS IS CHANNEL');
conv.ask(`Alright, ${device_channel_name} CHANNEL is now ${channel_value}! `);
console.log('inside CHANNEL ' + channel_value[0]);
console.log('inside CHANNEL' + device_channel_name[0]);
console.log('inside CHANNEL' + device_channel_action[0]);
console.log('hiwwxxxxxww thi is aaron');
});
app.intent('no_input', (conv) => {
const repromptCount = parseInt(conv.arguments.get('REPROMPT_COUNT'));
if (repromptCount === 0) {
conv.ask(`What was that?`);
} else if (repromptCount === 1) {
conv.ask(`Sorry I didn't catch that. Could you repeat yourself?`);
} else if (conv.arguments.get('IS_FINAL_REPROMPT')) {
conv.close(`Okay let's try this again later.`);
}
});
// The entry point to handle a http request
exports.factsAboutGoogle = functions.https.onRequest(app);
如果您有一个示例短语,该短语的一部分设置为代表一个参数,则该短语的该部分可以采用参数定义的 任何 值。 "resolved value" 列仅显示它在您的示例短语中解析的内容。
所以在你的 "channel" Intent 中,短语 "tv channel to 3" 也可以匹配 "radio volume to 4" 因为 "tv" 和 "radio" 都是 device_name
实体,"channel" 和 "volume" 都是 device_action
实体。
你有几个解决方案:
您可以将它们变成一个接受所有短语的 Intent,并在您的 webhook 中检查 device_action
的值以查看您应该做什么。
您可以将它们保留为单独的 Intent,并完全删除 device_action
参数。你不需要它们。只需在各种示例短语中使用同义词,这样学习系统就可以了解哪些同义词适用于哪些 Intent。
如果您仍然担心同义词,或者如果它仍然有意义,请将它们设为不同的实体类型。
例如,您可能希望将 "channel" 和 "preset" 作为同一实体类型下的不同实体。你关心差异是因为它改变了你对待数字的方式,但你仍在改变频道。 "Volume" 将是一个不同的实体,因为它代表完全不同的东西。
在您的评论中,您询问了设备的个性化别名。您没有询问具有不同名称的不止一种设备类型(因此您希望使用相同的 Intent 处理 "tv1" 和 "tv2",但能够检测到差异。这两种情况都得到了最好的处理与上面的选项 (3) 相同,但方式略有不同。
如果您想要多个 "tv",每个实体类型都相同(例如,"device tv"),但每个实体值都不同。它可能看起来像这样:
"But wait!"我听到你说"What if the user has three tvs? Or nine? Do I have to set all of these up? And what if they want to call one the 'bedroom tv' and another the 'den tv'?"
这表明您可以将电视的数量及其别名保存在某种数据库中(无论如何您都需要这样做 - 您可能需要将他们的电视映射到某个唯一的设备 ID 才能真正打开它们)并且,当用户与您的代理交谈时,用 User Entity.
更新它
用户实体具有专门为该用户设置的用户特定名称和别名。当用户首次与您的代理对话时,您将使用 Dialogflow API 设置它们。如果您使用的是 V1,您将使用 /userEntities endpoint. If you're using V2, you'll use the projects.agent.sessions.entityTypes 资源。
我正在使用 DialogFlow + Firebase 修改 Google 上的操作。这个想法是为默认 AoG 不支持的物联网设备定制构建。
就广播电视而言,目前有 2 个意向:
1) 渠道:它接受 3 个参数:device_name(自定义实体)、device_action(自定义实体)和值。
例如:请将广播频道更改为22.3
或将电视频道更改为 22
2) 体积:它接受 3 个参数:device_name(自定义实体)、device_action(自定义实体)和值。
例:请将收音机音量调到99 或者将电视的音量更改为 22
问题是代理似乎不能很好地区分两者。
执行有误吗?
// 编辑 2018 年 5 月 9 日:
index.js
// Copyright 2016, Google, Inc.
// Licensed under the Apache License, Version 2.0 (the 'License');
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an 'AS IS' BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
'use strict';
const util = require('util');
const functions = require('firebase-functions');
const {
dialogflow,
Suggestions,
BasicCard,
Button,
SimpleResponse,
} = require('actions-on-google');
const {values, concat, random, randomPop} = require('./util');
const responses = require('./responses');
/** Dialogflow Contexts {@link https://dialogflow.com/docs/contexts} */
const AppContexts = {
FACT: 'choose_fact-followup',
CATS: 'choose_cats-followup',
XXX: 'choose_xxx-followup',
};
/** Dialogflow Context Lifespans {@link https://dialogflow.com/docs/contexts#lifespan} */
const Lifespans = {
DEFAULT: 5,
};
const app = dialogflow({
debug: true,
init: () => ({
data: {
// Convert array of facts to map
facts: responses.categories.reduce((o, c) => {
o[c.category] = c.facts.slice();
return o;
}, {}),
cats: responses.cats.facts.slice(), // copy cat facts
},
}),
});
/**
* Greet the user and direct them to next turn
* @param {DialogflowConversation} conv DialogflowConversation instance
* @return {void}
*/
app.intent('Unrecognized Deep Link Fallback', (conv) => {
const response = util.format(responses.general.unhandled, conv.query);
const suggestions = responses.categories.map((c) => c.suggestion);
conv.ask(response, new Suggestions(suggestions));
});
// redirect to the intent handler for tell_fact
app.intent('choose_fact', 'tell_fact');
// Say a fact
app.intent('tell_fact', (conv, {category}) => {
const {facts, cats, xxx} = conv.data;
if (values(facts).every((c) => !c.length)) {
// If every fact category facts stored in conv.data is empty,
// close the conversation
return conv.close(responses.general.heardItAll);
}
const categoryResponse =
responses.categories.find((c) => c.category === category);
const fact = randomPop(facts[categoryResponse.category]);
if (!fact) {
const otherCategory =
responses.categories.find((other) => other !== categoryResponse);
const redirect = otherCategory.category;
const parameters = {
category: redirect,
};
// Add facts context to outgoing context list
conv.contexts.set(AppContexts.FACT, Lifespans.DEFAULT, parameters);
const response = [
util.format(responses.transitions.content.heardItAll, category, redirect),
];
// If cat facts not loaded or there still are cat facts left
if (cats.length) {
response.push(responses.transitions.content.alsoCats);
}
response.push(responses.general.wantWhat);
conv.ask(concat(...response));
conv.ask(new Suggestions(otherCategory.suggestion));
if (cats.length) {
conv.ask(new Suggestions(responses.cats.suggestion));
}
return;
}
const {factPrefix} = categoryResponse;
// conv.ask can be called multiple times to have the library construct
// a single response itself the response will get sent at the end of
// the function or if the function returns a promise, after the promise
// is resolved.
conv.ask(new SimpleResponse({
speech: concat(factPrefix, fact),
text: factPrefix,
}));
conv.ask(responses.general.nextFact);
conv.ask(new BasicCard({
title: fact,
image: random(responses.content.images),
buttons: new Button({
title: responses.general.linkOut,
url: responses.content.link,
}),
}));
console.log('hiwwxxxxxww thi is aaron');
conv.ask(responses.general.suggestions.confirmation);
});
// Redirect to the intent handler for tell_cat_fact
app.intent('choose_cats', 'tell_cat_fact');
// Say a cat fact
app.intent('tell_cat_fact', (conv) => {
const {cats} = conv.data;
console.log('this is cats data' + {cats});
const fact = randomPop(cats);
if (!fact) {
conv.contexts.delete(AppContexts.FACT);
conv.contexts.delete(AppContexts.CATS);
conv.ask(responses.transitions.cats.heardItAll);
return conv.ask(responses.general.suggestions.confirmation);
}
const {factPrefix, audio} = responses.cats;
// conv.ask can be called multiple times to have the library construct
// a single response itself. The response will get sent at the end of
// the function or if the function returns a promise, after the promise
// is resolved.
const sound = util.format(audio, random(responses.cats.sounds));
conv.ask(new SimpleResponse({
// <speak></speak> is needed here since factPrefix is a SSML string
// and contains audio.
speech: `<speak>${concat(factPrefix, sound, fact)}</speak>`,
text: factPrefix,
}));
conv.ask(responses.general.nextFact);
conv.ask(new BasicCard({
title: fact,
image: random(responses.cats.images),
buttons: new Button({
title: responses.general.linkOut,
url: responses.cats.link,
}),
}));
console.log('hiwwxxxxxww thi is aaron');
conv.ask(responses.general.suggestions.confirmation);
});
//say a tv channel
app.intent('volume', (conv, {device_name,device_action, value}) => {
var no_device_name = device_name;
var no_value = value;
var no_device_action = device_action;
var this_device_value = util.inspect(value, false, null);
var this_device_name = util.inspect(device_name, false, null);
var this_device_action = util.inspect(device_action, false, null);
console.log(this_device_action[0]);
if (no_device_name[0] == 'channel'){
console.log('inside tv but CHANNEL');
}
else{
console.log('VOLUME');
}
console.log('THIS IS VOL');
console.log(no_device_action[0]);
conv.ask(`Alright, ${device_name} VOLUM is now ${value}! `);
console.log('inside volume ' + value[0]);
console.log('inside volume' + device_name[0]);
console.log('inside volume' + device_action[0]);
console.log('hiwwxxxxxww thi is aaron');
});
//say a tv channel
app.intent('channel', (conv, {device_channel_name, device_channel_action, channel_value}) => {
console.log('THIS IS CHANNEL');
conv.ask(`Alright, ${device_channel_name} CHANNEL is now ${channel_value}! `);
console.log('inside CHANNEL ' + channel_value[0]);
console.log('inside CHANNEL' + device_channel_name[0]);
console.log('inside CHANNEL' + device_channel_action[0]);
console.log('hiwwxxxxxww thi is aaron');
});
app.intent('no_input', (conv) => {
const repromptCount = parseInt(conv.arguments.get('REPROMPT_COUNT'));
if (repromptCount === 0) {
conv.ask(`What was that?`);
} else if (repromptCount === 1) {
conv.ask(`Sorry I didn't catch that. Could you repeat yourself?`);
} else if (conv.arguments.get('IS_FINAL_REPROMPT')) {
conv.close(`Okay let's try this again later.`);
}
});
// The entry point to handle a http request
exports.factsAboutGoogle = functions.https.onRequest(app);
如果您有一个示例短语,该短语的一部分设置为代表一个参数,则该短语的该部分可以采用参数定义的 任何 值。 "resolved value" 列仅显示它在您的示例短语中解析的内容。
所以在你的 "channel" Intent 中,短语 "tv channel to 3" 也可以匹配 "radio volume to 4" 因为 "tv" 和 "radio" 都是 device_name
实体,"channel" 和 "volume" 都是 device_action
实体。
你有几个解决方案:
您可以将它们变成一个接受所有短语的 Intent,并在您的 webhook 中检查
device_action
的值以查看您应该做什么。您可以将它们保留为单独的 Intent,并完全删除
device_action
参数。你不需要它们。只需在各种示例短语中使用同义词,这样学习系统就可以了解哪些同义词适用于哪些 Intent。如果您仍然担心同义词,或者如果它仍然有意义,请将它们设为不同的实体类型。
例如,您可能希望将 "channel" 和 "preset" 作为同一实体类型下的不同实体。你关心差异是因为它改变了你对待数字的方式,但你仍在改变频道。 "Volume" 将是一个不同的实体,因为它代表完全不同的东西。
在您的评论中,您询问了设备的个性化别名。您没有询问具有不同名称的不止一种设备类型(因此您希望使用相同的 Intent 处理 "tv1" 和 "tv2",但能够检测到差异。这两种情况都得到了最好的处理与上面的选项 (3) 相同,但方式略有不同。
如果您想要多个 "tv",每个实体类型都相同(例如,"device tv"),但每个实体值都不同。它可能看起来像这样:
"But wait!"我听到你说"What if the user has three tvs? Or nine? Do I have to set all of these up? And what if they want to call one the 'bedroom tv' and another the 'den tv'?"
这表明您可以将电视的数量及其别名保存在某种数据库中(无论如何您都需要这样做 - 您可能需要将他们的电视映射到某个唯一的设备 ID 才能真正打开它们)并且,当用户与您的代理交谈时,用 User Entity.
更新它用户实体具有专门为该用户设置的用户特定名称和别名。当用户首次与您的代理对话时,您将使用 Dialogflow API 设置它们。如果您使用的是 V1,您将使用 /userEntities endpoint. If you're using V2, you'll use the projects.agent.sessions.entityTypes 资源。