5

I created a chatbot which informs the user about the members of my (extended) family and about where they are living. I have created a small database with MySQL which has these data stored and I fetch them with a PHP script whenever this is appropriate depending on the interaction of the user with the chatbot.

My chatbot contains two intents additionally to the Default Fallback Intent and to the Default Welcome Intent:

  • Names
  • Location_context

The first intent (Names) is trained by phrases such as 'Who is John Smith?' and has an output context (called context with duration of 10 questions). A possible answer to this question is 'John is my uncle.'. The second intent (Location_context) is trained by phrases such as 'Where is he living?' and has an input context (from Names). A possible answer to this question is 'John is living in New York.'

The Names intent contains two parameters:

  • parameter names: given-name, last-name.
  • entities: @sys.given-name, @sys.last-name.
  • value: $given-name, $last-name.

These two parameters represent the full name given by the user. The Location_context intent does not contain any parameters.

The PHP script is the following:

<?php

$dbServername = '******************';
$dbUsername = '******************';
$dbPassword = '******************';
$dbName = '******************';
$conn = mysqli_connect($dbServername, $dbUsername, $dbPassword, $dbName);

// error_reporting(E_ALL);
// ini_set('display_errors', 'on');

header('Content-Type: application/json');
$method = $_SERVER['REQUEST_METHOD'];

if($method == 'POST'){
    $requestBody = file_get_contents('php://input');
    $json = json_decode($requestBody);

    $action = $json->result->action;
    $first_name = $json->result->contexts[0]->parameters->{'given-name'};
    $last_name = $json->result->contexts[0]->parameters->{'last-name'};
    $lifespan = $json->result->contexts[0]->lifespan;

    $sql = "SELECT * FROM family WHERE name LIKE '%$first_name%$last_name%';";
    $result = mysqli_query($conn, $sql);
    $resultCheck = mysqli_num_rows($result);
    if ($resultCheck > 0) {
       while ($row = mysqli_fetch_assoc($result)) {
            $person = $row;
       }

       switch ($action) {
           case 'Name':
               $speech= "$first_name is my" . $person["name"] . ".";
               break;  
           case 'Location':
               $speech = "$first_name is living in {$person["location"]}.";
               break;
           default:
               $speech = "Please ask me something more relevant to my family";
               break;
       } 
    }
    else {

        $speech = "Sorry, $first_name $last_name is not a member of my family.";

    }

    $response = new \stdClass();
    $response->speech = $speech;
    $response->displayText = $speech;
    $response->source = "agent";
    echo json_encode($response);
}
else
{
    echo "Method not allowed";
}
?>

In Dialogflow, after asking e.g. "Who is John Smith?" and getting the correct answer "John is my uncle." then I am asking "Where is he living?" and I am getting the correct answer "John is living in New York.". The json response from Dialogflow for the second question is:

{
  "id": "*****************************",
  "timestamp": "2018-04-04T08:26:39.993Z",
  "lang": "en",
  "result": {
    "source": "agent",
    "resolvedQuery": "Where is he living"
    "action": "Location",
    "actionIncomplete": false,
    "parameters": {},
    "contexts": [
      {
        "name": "context",
        "parameters": {
          "given-name.original": "John",
          "last-name.original": "Smith",
          "given-name": "John",
          "last-name": "Smith"
        },
        "lifespan": 9
      }
    ],
    "metadata": {
      "intentId": "*****************************",
      "webhookUsed": "true",
      "webhookForSlotFillingUsed": "false",
      "webhookResponseTime": 93,
      "intentName": "Location_context"
    },
    "fulfillment": {
      "speech": "John is living in New York.”,
      "displayText": "John is living in New York.",
      "messages": [
        {
          "type": 0,
          "speech": "John is living in New York."
        }
      ]
    },
    "score": 1
  },
  "status": {
    "code": 200,
    "errorType": "success",
    "webhookTimedOut": false
  },
  "sessionId": "*****************************"
}

However, when I enter exactly the same questions (after entering Talk to my test app) in Google assistant, I am getting the same answer at the first question but I am getting "is living in Los Angeles." for the second question. Notice two things in this answer. Firstly, the variable $first_name does not have any value (because it is not set) and that the location 'Los Angeles' is the location of the family member which is last in the database. Therefore this location is returned because $first_name and $last_name have no value assigned (as they are not set) in the mysql query and for some reason the location of the last person of the database is returned.

It is quite frustrating that I cannot inspect the json response of Google Assistant as I can easily do it in Dialogflow. However, after experimenting a bit I found out that in Google Assistant $lifespan is always 0 (both in the first and the second question) and that $first_name and $last_name are not set at all in the json response of the second question even though in Dialoglow they are set and they contain the full name as it shown above in the json response I posted. Also Google Assistant returns actions_capability_screen_output for $json->result->contexts[0]->name in both questions while obviously in Dialogflow $json->result->contexts[0]->name is context (the name of the context).

Therefore, the contexts branch of the json response in the second question in Google Assistant seems to be like this:

"contexts": [
          {
            "name": "actions_capability_screen_output",
            "parameters": {},
            "lifespan": 0
          }
        ]

On the other hand, as I showed above, the contexts branch of the json response in the second question in DIalogflow is:

"contexts": [
      {
        "name": "context",
        "parameters": {
          "given-name.original": "John",
          "last-name.original": "Smith",
          "given-name": "John",
          "last-name": "Smith"
        },
        "lifespan": 9
      }
    ]

Why Google Assistant does not recognise context and it does not process the same json response as shown by Dialoglow?

How can I inspect the entire json response from Google Assistant as I do it in Dialogflow?

Outcast
  • 4,967
  • 5
  • 44
  • 99
  • That seems odd, and I can't duplicate it at the moment in my tests using just that JSON. Can you update your question to include the results of the "Debug" tab from the Assistant simulator? Can you also clarify if you are using the Dialogflow V1 or V2 protocol (include a screen shot of your settings screen, masking sensitive information, if you're not sure). – Prisoner Apr 03 '18 at 13:22
  • If possible, can you also include the PHP source that is generating that reply? – Prisoner Apr 03 '18 at 13:22
  • Thanks for your response. I am willing to provide all these details however I think that my post will become extremely long. I am thinking that it is better to post a new question about it related to a more complex chatbot I created based on this one. – Outcast Apr 03 '18 at 13:54
  • The best thing is to have something that is simple and reproducible. If I can't reproduce it with the JSON, it suggests that there is something else slightly different that is wrong. Finding the error in the simplest thing should help you fix it in the more complex one. – Prisoner Apr 03 '18 at 14:01
  • So edited my post and I provided all the information that you asked for. (I tried to conceal any sensitive information but if I missed something then you may edit my post to correct this) – Outcast Apr 03 '18 at 14:20
  • This is, indeed, puzzling. This shouldn't be it, but try changing the `echo "Method not allowed";` line to something that sends back a JSON response that would be valid (ie - text form of an object that has speech and displayText set). – Prisoner Apr 03 '18 at 15:05
  • Now, I do not what I did (!!) but I am finally getting a response though an wrong one. The Google Assistant returns the following response "'s is living in Los Angeles". This means that firstly it misses the name for some reason and secondly that (because of this?) it is always returning the location of the last family member in the database (only the last family member in the database lives in Los Angeles). The frustrating thing is that Dialogflow itself or even its web demo responds absolutely fine. What can you understand from this? – Outcast Apr 03 '18 at 15:10
  • For all of this, it suggests that either the method isn't actually POST and/or you're not getting a JSON body. I'm not entirely sure why this might be happening, but adding logging to see exactly what is being sent to your webhook in these cases would help. – Prisoner Apr 03 '18 at 15:18
  • Ok but if it is not POST or it is not JSON then why am I getting the right response on Dialogflow? Also I said before some minutes, I finally get a response even on the Google Assistant so there is no problem with the POST method or the JSON format but I have the impression that for some reason it does not pass in the right way the name of the person through the context. – Outcast Apr 03 '18 at 15:30
  • (while it does pass the name through the context in Dialogflow itself) – Outcast Apr 03 '18 at 15:38
  • !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!I UPDATED MY POST ABOVE SO YOU MAY IGNORE ALL THE COMMENTS ABOVE. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! – Outcast Apr 04 '18 at 11:25

3 Answers3

4

There is a lot going on here in addition to your questions. Let's try to break them down bit by bit.

Why was I getting errors in the response initially?

Because ini_set('display_errors', 'on'); sends any errors to standard output which is what you're sending back to Dialogflow. Dialogflow's parser that sends things to Google was strict, so the extra output was causing problems here.

How can I see what is going on?

You should log things using something like error_log(). This will record in a file whatever you want, so you can see the exact JSON that has come from Dialogflow and exactly what you think you are sending back.

You can use this with something like

error_log( $request_body );

to see exactly what the JSON is that has been sent from Dialogflow. This will log the body to the system error logger (which is probably the HTTP error_log unless you set it elsewhere) so you can examine it and see everything that is being sent to you.

Ok, I can see what is going on. Why is the JSON different?

Because Actions on Google has additional information than what is available through other agents. These are sent to you in the same way (or should be), but there will be more of it. For example, you'll see an originalRequest object delivered through JSON.

However - all the information you expect should be there. Just more of it.

Why can't I see what Dialogflow gets from my webhook before changing things to send to Actions on Google?

Good question. This does sometimes get logged in the Debug tab under "agentToAssistantDebug", but not always. You'll need to use other tools to see exactly what you're replying as part of a test infrastructure.

Why am I not getting the context context with Actions on Google?

You haven't actually posted evidence that you're not. All you've shown is that context[0] isn't named "context". You should log the entire context array to see all of them with something like

error_log( $json->result->context );

What you will see is that there are many contexts that have been set. This includes one named actions_capability_screen_output which is created by Actions on Google to indicate that you're running on a device that can display to a screen. It will probably also have one named actions_capability_audio_output to indicate it can speak the result. It should also include one named context, which is the one that you set.

But why don't these other contexts have the given-name and last-name parameters set?

Because these parameters weren't set when those contexts were active.

You can't just assume the parameters will be set on the first context - you'll need to look for the context where you have set them and get the value out of those specific contexts.

Parameters will only be on the contexts where they are set. (In fact, you can set additional parameters in output contexts as part of your reply.)

If there is more than one context, how do I find the one that contains my parameters?

Loop through the context array and look for one that matches the name you are expecting. You can then get the parameters you want from there.

Why is Actions on Google resetting the lifespan to 0?

It isn't. Actions on Google does set additional contexts (which you see one of) which contain additional information specific to AoG, and it has a lifespan of 0 (meaning it will be removed after this round, but they'll just set it again the next time through). They set it to 0 because some of the contexts may change each time (particularly which surfaces are supported).

Is there a better way to do this without contexts?

Not really - contexts are pretty awesome and they're the best solution for this.

Prisoner
  • 49,922
  • 7
  • 53
  • 105
  • Thanks for your thorough answer. Let me respond to to some points of it. Regarding your `'Why am I not getting the context context with Actions on Google?'` point, I have tested with `isset()` if the parameters `given-name` and `last-name` exist when the second question is asked and they do not. Also, the `name` in the `contexts` branch is `"actions_capability_screen_output"` for some reason. – Outcast Apr 04 '18 at 13:47
  • Regarding your `Is there a better way to do this without contexts?` point, I totally agree and this why I was using context but I could do what I wanted to in Dialogflow without even explicitly passing the parameters value by defining new parameters in the `Location_context` intent. The principal question which is still unanswered is why the json output that reaches my PHP script from Google Assistant is different than the one shown in Dialogflow. – Outcast Apr 04 '18 at 13:50
  • * the json output that reaches my PHP script from Google Assistant -> the json output that reaches my PHP script when using Google Assistant – Outcast Apr 04 '18 at 13:57
  • Also to be honest I do not get your point about `error_log()`. My PHP script is uploaded as an app on Heroku so that Dialogflow can send a POST request with its webhook. Probably, what I need is a tool for inspecting the content sent from webhooks but I do not get what is the usefulness of `error_log()` for my purpose. If I am missing something then you are welcome to explain me. – Outcast Apr 04 '18 at 14:05
  • Hmm...I may be receiving more data than I thought...let me check it out a bit...but I must find the right tool for to examine this properly.. – Outcast Apr 04 '18 at 14:36
  • Ok, ok....you won :) ...very good answer and you were absolutely right in your "Why am I not getting the context `context` with Actions on Google?" section.... – Outcast Apr 06 '18 at 10:29
  • Glad that it helps! – Prisoner Apr 06 '18 at 10:35
0

I'm not the biggest expert on PHP, but I have used it a bit. I think your issue is that Dialogflow is not interpreting your result as JSON but rather a simple string.

From the error, I see:

Expected BEGIN_OBJECT but was STRING at line 2 column 1 path $.

Assuming your response is valid JSON then you may need to manually tell Dialogflow that the response needs to be interpreted as JSON.

It does seem like you're already doing that at the top of your PHP response, but maybe you should verify the JSON response doesn't have anything out of the ordinary that would cause that error above.

Nick Felker
  • 11,536
  • 1
  • 21
  • 35
  • (THIS IS AN ANSWER TO MY POST BEFORE I UPDATED IT. FOR AN UPDATED STATEMENT OF MY PROBLEM PLEASE SEE MY EDITED POST ABOVE.) – Outcast Apr 04 '18 at 13:02
0

-- ANSWER TO MY POST BEFORE UPDATING IT --

Finally I discovered that I am getting an Expected BEGIN_OBJECT but was STRING at line 2 column 1 path $. error unless I comment out these lines in my source code:

error_reporting(E_ALL);
ini_set('display_errors', 'on');

If I comment out these two lines then I am not getting any error but the final response is that "is living in Los Angeles" while "John is living in New York Angeles" is the complete/correct response. Obviously, commenting out these two lines does not really solve my problem. This problem occurs because for some reason Google Assistant does not recognise the context defined for the Names and Location_context intents while Dialogflow properly recognises it. Because of this, $first_name and $last_name are not even set in the second question ('Where is he living?) and this is why I am getting an incomplete/wrong response to this question.

FOR AN UPDATED STATEMENT OF MY PROBLEM PLEASE SEE MY EDITED POST ABOVE.

-- ANSWER TO MY POST AFTER UPDATING IT --

I can solve my problem if I add the following two parameters in the Location_context intent:

  • parameter names: given-name, last-name.
  • entities: @sys.given-name, @sys.last-name.
  • value: #context.given-name, #context.last-name.

Therefore, I essentially pass the value of the given-name and last-name parameters from the Names intent to the Location_context intent.

If I do this then the contexts branch of the json response in the second question in Google Assistant seems to become like this:

"contexts": [
      {
        "name": "actions_capability_screen_output",
        "parameters": {
          "given-name.original": "John",
          "last-name.original": "Smith",
          "given-name": "John",
          "last-name": "Smith"
        },
        "lifespan": 0
      }
    ]

(Again I cannot directly inspect the json output from Google Assistant but I am just making some tests to understand the format and the content of the json response from it)

In this way I can retrieve the full name of the person through my PHP script and get the right answer ("John is living in New York.") to the second question ("Where is he living?").

However, I am still wondering why it is necessary to do this while in Dialogflow this is done without adding any parameters in the Location_context intent...

Also I am not sure that in this way Google Assistant recognises the context between the two intents since lifespan is again 0 (even though I am getting what I want to get)...

Outcast
  • 4,967
  • 5
  • 44
  • 99