1

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);
});

tell.fact Intent Screenshots

tell.fact intent 1

tell.fact intent 2

Mike
  • 1,307
  • 3
  • 17
  • 29
  • Can you also include what your require('actions-on-google') line looks like, and a screen shot of the API.AI Intent tell_fact? – Prisoner Jul 14 '17 at 03:58
  • Hi @Prisoner, I've added the screenshots and code you're requesting. `const App = require('actions-on-google').ApiAiApp;` `const functions = require('firebase-functions');` – Mike Jul 14 '17 at 04:05

1 Answers1

3

Aha! A sneaky problem that was easy to overlook.

In API.AI, you've named the parameter "fact-category".

You're asking for that parameter with

 let factCategory = app.getArgument(CATEGORY_ARGUMENT);

And defining CATEGORY_ARGUMENT as

 const CATEGORY_ARGUMENT = 'category';

So it is falling through to the error condition because you're getting the parameter named "category" which isn't set, so factCategory ends up being undefined in every case.

Prisoner
  • 49,922
  • 7
  • 53
  • 105
  • Yes! Thank you that fixed it. I changed `const CATEGORY_ARGUMENT = 'fact-category';` I would not have caught that myself. I wish there were more tutorials on making these Google Assistant apps. I basically had to reverse engineer my code using the provided code in their sample app. Cheers! – Mike Jul 14 '17 at 04:30