I'm messing around with my Google Home and creating an app that reads off facts about sloths. I've created the agent using API.AI and I am hosting my functions on Firebase and connecting it to API.AI via the webhook. You ask Google to tell you a fact about sloths and it will respond asking whether you would like to hear a "fun fact" or a "science fact." Your response dictates what kind of fact Google will read off for you.
While testing on API.AI, I'm getting my default failure response, though when I take a peek at the JSON, it's clearly parsing fact category. My javascript code is based on Google's sample "Facts about Google" app that their tutorial is using. Below is the JSON from the API.AI test as well as my tellFact()
function.
Why am I reaching my failure clause if the JSON clearly shows it's parsing a correct category?
JSON
{
"id": "2b920a5b-0d17-4c5a-9ac1-18071f078464",
"timestamp": "2017-07-13T20:43:33.307Z",
"lang": "en",
"result": {
"source": "agent",
"resolvedQuery": "tell me something scientific about sloths",
"action": "tell.fact",
"actionIncomplete": false,
"parameters": {
"fact-category": "science"
},
"contexts": [
{
"name": "_actions_on_google_",
"parameters": {
"fact-category.original": "scientific",
"fact-category": "science"
},
"lifespan": 100
}
],
"metadata": {
"intentId": "ca4fa7f1-aceb-4867-b7c3-cf16d1ce4d79",
"webhookUsed": "true",
"webhookForSlotFillingUsed": "false",
"webhookResponseTime": 195,
"intentName": "tell_fact"
},
"fulfillment": {
"speech": "Sorry, I didn't understand. I can tell you fun facts or science facts about sloths. Which one do you want to hear about?",
"messages": [
{
"type": 0,
"speech": "Sorry, I didn't understand. I can tell you fun facts or science facts about sloths. Which one do you want to hear about?"
}
],
...
index.js
const App = require('actions-on-google').ApiAiApp;
const functions = require('firebase-functions');
const TELL_FACT = 'tell.fact';
// API.AI parameter names
const CATEGORY_ARGUMENT = 'category';
const FACT_TYPE = {
FUN: 'fun',
SCIENCE: 'science'
};
const FUN_FACTS = new Set([...]);
const SCIENCE_FACTS = new Set([...]);
...
exports.factsAboutSloths = functions.https.onRequest((request, response) => {
const app = new App({ request, response });
console.log('Request headers: ' + JSON.stringify(request.headers));
console.log('Request body: ' + JSON.stringify(request.body));
// Greet the user and direct them to next turn
function unhandledDeepLinks(app) {
if (app.hasSurfaceCapability(app.SurfaceCapabilities.SCREEN_OUTPUT)) {
app.ask(app.buildRichResponse()
.addSimpleResponse(`Welcome to Facts about Sloths! I'd really rather not talk about ${app.getRawInput()}.` +
`Wouldn't you rather talk about Sloths? I can tell you a fun fact or a science fact about sloths.` +
`Which do you want to hear about?`).addSuggestions(['Fun', 'Science']));
} else {
app.ask(`Welcome to Facts about Sloths! I'd really rather not talk about ${app.getRawInput()}.` +
`Wouldn't you rather talk about Sloths? I can tell you a fun fact or a science fact about sloths.` +
`Which do you want to hear about?`, NO_INPUTS);
}
}
// Say a fact
function tellFact(app) {
let funFacts = app.data.funFacts ? new Set(app.data.funFacts) : FUN_FACTS;
let scienceFacts = app.data.scienceFacts ? new Set(app.data.scienceFacts) : SCIENCE_FACTS;
if (funFacts.size === 0 && scienceFacts.size === 0) {
app.tell('Actually it looks like you heard it all. Thanks for listening!');
return;
}
let factCategory = app.getArgument(CATEGORY_ARGUMENT);
if (factCategory === FACT_TYPE.FUN) {
let fact = getRandomFact(funFacts);
if (fact === null) {
if (app.hasSurfaceCapability(app.SurfaceCapabilities.SCREEN_OUTPUT)) {
let suggestions = ['Science'];
app.ask(app.buildRichResponse()
.addSimpleResponse(noFactsLeft(app, factCategory, FACT_TYPE.SCIENCE))
.addSuggestions(suggestions));
} else {
app.ask(noFactsLeft(app, factCategory, FACT_TYPE.SCIENCE), NO_INPUTS);
}
return;
}
let factPrefix = 'Sure, here\'s a fun fact. ';
app.data.funFacts = Array.from(funFacts);
if (app.hasSurfaceCapability(app.SurfaceCapabilities.SCREEN_OUTPUT)) {
let image = getRandomImage(SLOTH_IMAGES);
app.ask(app.buildRichResponse()
.addSimpleResponse(factPrefix)
.addBasicCard(app.buildBasicCard(fact)
.addButton(LINK_OUT_TEXT, WIKI_LINK)
.setImage(image[0], image[1]))
.addSimpleResponse(NEXT_FACT_DIRECTIVE)
.addSuggestions(CONFIRMATION_SUGGESTIONS));
} else {
app.ask(factPrefix + fact + NEXT_FACT_DIRECTIVE, NO_INPUTS);
}
return;
} else if (factCategory === FACT_TYPE.SCIENCE) {
let fact = getRandomFact(scienceFacts);
if (fact === null) {
if (app.hasSurfaceCapability(app.SurfaceCapabilities.SCREEN_OUTPUT)) {
let suggestions = ['Fun'];
app.ask(app.buildRichResponse()
.addSimpleResponse(noFactsLeft(app, factCategory, FACT_TYPE.FUN))
.addSuggestions(suggestions));
} else {
app.ask(noFactsLeft(app, factCategory, FACT_TYPE.FUN), NO_INPUTS);
}
return;
}
let factPrefix = 'Okay, here\'s a science fact. ';
app.data.scienceFacts = Array.from(scienceFacts);
if (app.hasSurfaceCapability(app.SurfaceCapabilities.SCREEN_OUTPUT)) {
let image = getRandomImage(SLOTH_IMAGES);
app.ask(app.buildRichResponse()
.addSimpleResponse(factPrefix)
.addBasicCard(app.buildBasicCard(fact)
.setImage(image[0], image[1])
.addButton(LINK_OUT_TEXT, WIKI_LINK))
.addSimpleResponse(NEXT_FACT_DIRECTIVE)
.addSuggestions(CONFIRMATION_SUGGESTIONS));
} else {
app.ask(factPrefix + fact + NEXT_FACT_DIRECTIVE, NO_INPUTS);
}
return;
} else {
// Conversation repair is handled in API.AI, but this is a safeguard
if (app.hasSurfaceCapability(app.SurfaceCapabilities.SCREEN_OUTPUT)) {
app.ask(app.buildRichResponse()
.addSimpleResponse(`Sorry, I didn't understand. ` +
`I can tell you fun facts or science facts about sloths. ` +
`Which one do you want to hear about?`)
.addSuggestions(['Fun', 'Science']));
} else {
app.ask(`Sorry, I didn't understand. ` +
`I can tell you fun facts or science facts about sloths. ` +
`Which one do you want to hear about?`, NO_INPUTS);
}
}
}
// Say they've heard it all about this category
function noFactsLeft(app, currentCategory, redirectCategory) {
let parameters = {};
parameters[CATEGORY_ARGUMENT] = redirectCategory;
// Replace the outgoing facts context with different parameters
app.setContext(FACTS_CONTEXT, DEFAULT_LIFESPAN, parameters);
let response = `Looks like you've heard all there is to know about the ${currentCategory} facts of sloths. ` +
`I could tell you about its ${redirectCategory} instead. So what would you like to hear about?`;
return response;
}
let actionMap = new Map();
actionMap.set(UNRECOGNIZED_DEEP_LINK, unhandledDeepLinks);
actionMap.set(TELL_FACT, tellFact);
app.handleRequest(actionMap);
});