Calling OpenWeather API from Watson Assistant: "Direct CloudFunctions call was not successful" - ibm-cloud

I am trying to use the openweathermap API with Watson Assistant, but I am getting "Webhook call was not successful. Response code is [404]. (and there is 1 more error in the log)."
(I am working from the book by Sabharwal, et al., with my own improvisations for the obsolete elements, like #sys-location.)
I created a Cloud Functions Action called "https://us-south.functions.appdomain.cloud/api/v1/web/my-account-email%40dev/default/Weather-Connection" and checked Enable as Web Action. The action code was imported from the git repo for the book:
let rp = require('request-promise')
function main(params) {
const options = {
uri: "http://api.openweathermap.org/data/2.5/weather?q=" + encodeURIComponent(params.object_of_interest)+ "&units=metric&APPID=19e8588cb3d7d0623e3a5a8ec529232f" ,
json: true
}
return rp(options)
.then(res => {
WeatherReport = "Current Temperature : " +res.main.temp+ ", Pressure : " + res.main.pressure + ", Humidity : " + res.main.humidity + ", temp min : " + res.main.temp_min + " , temp max : " + res.main.temp_max
return { WeatherReport
}
})
}
In the Assistant Options the webhook URI is set to
https://us-south.functions.appdomain.cloud/api/v1/web/my-account-email%40dev/default/Weather-Connection.json.
The "Assistant responds" JSON is
The "Assistant responds" JSON is
{
"output": {
"text": {
"values": [],
"selection_policy": "sequential"
}
},
"actions": [
{
"name": "/my-account-email%40dev/default/Weather-Connection.json",
"type": "cloud_function",
"parameters": {
"object_of_interest": "$location"
},
"credentials": "$credentials",
"result_variable": "$response"
}
],
"context": {
"credentials": {
"api_key": "[my-openweathermap-api-key]"
},
"object_of_interest": "#object_of_interest"
}
}
For debugging, I included a dialog node that displays the value of $location, and it is okay (e.g. "London").
The "Try it out" pane prints {"cloud_functions_call_error":"The requested resource does not exist."} When I click on the Error icon I get a Runtime error pop-up saying, Direct CloudFunctions call was not successful. Http response code is [404]. (and there is 1 more error in the log).
I am not getting any output from running the CLI command ibmcloud fn activation list(I'm not sure that's the right way to check the logs).
I have tested the Weather-Connection function by invoking the Action with parameter {"object_of_interest": "London"}, and it works.
Everything is deployed in the same region (us-south) and namespace.
I can't think of anything else to try.

I just cracked it. I was trying to show the result using the text response is <? $webhook_result_1.response ?> when it should just have been response is <? $webhook_result_1 ?>.

Related

"The agent returned an empty TTS" when action is not opened separately to asking an intent

When I invoke the skill with 'okay Google, ask {skillname} to {utterance}' I get the response 'The agent returned an empty tts" and the conversation closes. It doesn't even seem to hit my backend - I've tried hosting the backend code on both a local server and on AWS and the same issue happens in both cases. There's no log of the request being made either on Stackdriver on Google or on CloudWatch on AWS. The only response I get is the following in the debug tab:
{
"response": "The agent returned an empty TTS.",
"expectUserResponse": false,
"conversationToken": "EosDS2o4d0...",
"audioResponse": "",
"ssmlMarkList": [],
"debugInfo": {
"sharedDebugInfoList": [
{
"name": "Response Validation",
"debugInfo": "The agent returned an empty TTS.",
"subDebugEntryList": []
}
],
"conversationBuilderExecutionEventsList": []
},
"visualResponse": {
"visualElementsList": [
{
"displayText": {
"content": "The agent returned an empty TTS."
}
}
],
"suggestionsList": [],
"agentLogoUrl": ""
},
"clientError": 0,
"is3pResponse": true,
"clientOperationList": [],
"projectName": "",
"renderedHtml": ""
}
Nothing in any of the other tabs. When I try invoking the phrases in the same way on my phone, I just see a loading symbol and again nothing in the logs to say it's even hitting the backend.
Weirdly, this doesn't happen when I say 'okay Google, talk to {skillname} {utterance}' - this works absolutely fine.
All the intents work perfectly well when I've opened my action first, and then invoke them. I've built this using Jovo and published on Alexa also and the problem is only happening on Google.

Dialogflow v2 API + Actions v2 API: MalformedResponse 'final_response' must be set

I'm trying to start working on Google Actions v2 API together with Dialgoflow v2 API.
I have the following example (so far in Dialogflow -> Fulfillment Webhook) taken from official Google Actions Migration Guide , but unfortunately I keep getting MalformedResponse 'final_response' must be set error.
'use strict';
const functions = require('firebase-functions');
const { dialogflow } = require('actions-on-google');
const app = dialogflow();
app.intent('Default Welcome Intent', conv => {
conv.ask('How are you?');
});
exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app);
And response is:
{
"responseMetadata": {
"status": {
"code": 13,
"message": "Failed to parse Dialogflow response into AppResponse because of empty speech response",
"details": [
{
"#type": "type.googleapis.com/google.protobuf.Value",
"value": "{\"id\":\"542fe4a8-6017-429f-81c3-61ba568e3659\",\"timestamp\":\"2018-04-19T20:16:25.606Z\",\"lang\":\"en-us\",\"result\":{},\"status\":{\"code\":200,\"errorType\":\"success\"},\"sessionId\":\"1524168985362\"}"
}
]
}
}
}
Please any idea why this can be happening?
Change this line:
conv.ask('How are you?');
to this:
conv.close('How are you?');
the close method configures the required final_response field for you

Only 2 intents work 'MAIN' and 'TEXT'

I'm trying building my first app with actions-on-google / google-assistant-sdk, I wanted to start using 3 intents, the MAIN, respond to input TEXT, and HELP that the user can call anytime:
The action.json is:
{
"actions": [
{
"description": "Default Welcome Intent",
"name": "MAIN",
"fulfillment": {
"conversationName": "conversation_1"
},
"intent": {
"name": "actions.intent.MAIN"
}
},
{
"description": "Help Intent",
"name": "Help",
"fulfillment": {
"conversationName": "conversation_1"
},
"intent": {
"name": "app.StandardIntents.HELP",
"trigger": {
"queryPatterns": [
"Help",
"HELP",
"help"
]
}
}
}
],
"conversations": {
"conversation_1": {
"name": "conversation_1",
"url": "https://us-central1-sillytest-16570.cloudfunctions.net/sayNumber",
"fulfillmentApiVersion": 2
}
}
}
The index.js:
'use strict';
process.env.DEBUG = 'actions-on-google:*';
const ActionsSdkApp = require('actions-on-google').ActionsSdkApp;
const functions = require('firebase-functions');
const NO_INPUTS = [
'I didn\'t hear that.',
'If you\'re still there, say that again.',
'We can stop here. See you soon.'
];
exports.sayNumber = functions.https.onRequest((request, response) => {
const app = new ActionsSdkApp({request, response});
function mainIntent (app) {
console.log('mainIntent');
let inputPrompt = app.buildInputPrompt(true, '<speak>Hi! <break time="1"/> ' +
'I can read out an ordinal like ' +
'<say-as interpret-as="ordinal">123</say-as>. Say a number.</speak>', NO_INPUTS);
app.ask(inputPrompt);
}
function rawInput (app) {
console.log('rawInput');
if (app.getRawInput() === 'bye') {
app.tell('Goodbye!');
} else {
let inputPrompt = app.buildInputPrompt(true, '<speak>You said, <say-as interpret-as="ordinal">' +
app.getRawInput() + '</say-as></speak>', NO_INPUTS);
app.ask(inputPrompt);
}
}
function helpHandler (app) {
console.log('rawInput');
app.ask('<speak>What kind of help do you need?</speak>');
}
let actionMap = new Map();
actionMap.set(app.StandardIntents.MAIN, mainIntent);
actionMap.set(app.StandardIntents.TEXT, rawInput);
actionMap.set(app.StandardIntents.HELP, helpHandler);
app.handleRequest(actionMap);
});
I pushed the firebase as:
firebase deploy --only functions
And pushed the Google Actions as:
gactions update --action_package action.json --project <YOUR_PROJECT_ID>
While testing the assistant here, it started in a good way, and repeat the number that I enter, wait for another number, and so on, but when I enter help it is terminated and not responding!
UPDATE
I tried the below, but did not work:
actionMap.set("app.StandardIntents.HELP", helpHandler);
I should expect the app to "What kind of help do you need?" when I enter/say "Help", but what happened is just re-writing it, same way it do with any other number.
Non-built-in Intents are only supported for the first message in a conversation. After that, while you can use them for speech biasing, you will only get a built-in one such as the TEXT Intent.
Your actionMap is looking for app.StandardIntents.HELP but it doesn't exist. You can view all of the standard intents in the GitHub repo.
app.StandardIntents.MAIN returns another string which corresponds to "'actions.intent.MAIN'". It does not read your action.json and generate new intents. Thus, app.StandardIntents.HELP actually returns undefined and is never called.
Your map should use a string for your help intent since it is not available as a constant in the app object.
actionMap.set("app.StandardIntents.HELP", helpHandler);
This should resolve your issue. Let me know if it does not.

How to ask permission in Actions on Google without the SDK?

I would like to know the name of the user, however I cannot use the nodejs sdk since I use another language.
How can I ask for permission?
I would prefer a way with the normal json responses.
I hacked this minimal script to get the JSON reponse which the nodejs sdk would return:
gaction.js:
const DialogflowApp = require('actions-on-google').DialogflowApp;
const app = new DialogflowApp({
request: {
body: {
result: {
action: 'Test',
contexts: []
}
},
get: (h) => h
},
response: {
append: (h, v) => console.log(`${h}: ${v}`),
status: (code) => {
return {send: (resp) => console.log(JSON.stringify(resp, null, 2))}
}
}
});
function testCode(app) {
app.askForPermission('To locate you', app.SupportedPermissions.DEVICE_PRECISE_LOCATION);
}
app.handleRequest(new Map().set('Test', testCode));
I'm still no node.js expert so this might be not an optimal solution. When you have installed node and run the command npm install actions-on-google, this will install the necessary dependencies.
When done you just need to run node gaction which will create this output:
Google-Assistant-API-Version: Google-Assistant-API-Version
Content-Type: application/json
{
"speech": "PLACEHOLDER_FOR_PERMISSION",
"contextOut": [
{
"name": "_actions_on_google_",
"lifespan": 100,
"parameters": {}
}
],
"data": {
"google": {
"expect_user_response": true,
"no_input_prompts": [],
"is_ssml": false,
"system_intent": {
"intent": "assistant.intent.action.PERMISSION",
"spec": {
"permission_value_spec": {
"opt_context": "To locate you",
"permissions": [
"DEVICE_PRECISE_LOCATION"
]
}
}
}
}
}
}
If you send now the JSON above you will be asked from Google Home. Have fun!
The request/response JSON formats for the API.AI webhooks with Actions is documented at https://developers.google.com/actions/apiai/webhook
As you've discovered, the data.google.permissions_request attribute contains two fields regarding the request:
opt_context contains a string which is read to give some context about why you're asking for the information.
permissions is an array of strings specifying what information you're requesting. The strings can have the values
NAME
DEVICE_COARSE_LOCATION
DEVICE_PRECISE_LOCATION
If you are using Java or Kotlin there is an Unofficial SDK. It matches the official SDK api nearly exactly.
https://github.com/TicketmasterMobileStudio/actions-on-google-kotlin

Google Cloud Print from Web

I wrote a script that prints some test pages from url on Web-site,
and every time I press a print button, a dialog frame for choosing printer appears . But I want to avoid this because my account synchronized with printer.
window.onload = function() {
var gadget = new cloudprint.Gadget();
gadget.setPrintButton(
cloudprint.Gadget.createDefaultPrintButton("print_button_container")); // div id to contain the button
gadget.setPrintDocument("url", "Test Page", "https://www.google.com/landing/cloudprint/testpage.pdf");
}
You could use oath and an html button rather than a gadget to accomplish this. This requires using the google developer console to get oauth permissions.
Then you need to authorize the cloud print service.
The following set of functions are specifically good for use in Google Apps Scripts, but can be adapted. The first thing to do is Log a url link that you can go to in order to Authorize the cloud print service.
function showURL() {
var cpService = getCloudPrintService();
if (!cpService.hasAccess()) {
Logger.log(cpService.getAuthorizationUrl());
}
}
In the following component of this set of functions, make sure to replace the client Id and Secret.
function getCloudPrintService() {
return OAuth2.createService('print')
.setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
.setClientId('**YOUR CLIENT ID FROM GOOGLE DEVELOPER CONSOLE**')
.setClientSecret('**YOUR CLIENT SECRET**')
.setCallbackFunction('authCallback')
.setPropertyStore(PropertiesService.getUserProperties())
.setScope('https://www.googleapis.com/auth/cloudprint')
.setParam('login_hint', Session.getActiveUser().getEmail())
.setParam('access_type', 'offline')
.setParam('approval_prompt', 'force');
}
function authCallback(request) {
var isAuthorized = getCloudPrintService().handleCallback(request);
if (isAuthorized) {
return HtmlService.createHtmlOutput('You can now use Google Cloud Print from Apps Script.');
} else {
return HtmlService.createHtmlOutput('Cloud Print Error: Access Denied');
}
}
Next, get the ID of the Cloud Print Printer that you want to use. This can be obtained in the settings menu of Chrome. Settings --> Show Advanced Settings --> Under Cloud Print " Manage" --> Select the Printer that you want to use "Manage" -->Advanced Details
To initiate cloud print, you need to add the details to a ticket:
var ticket = {
version: "1.0",
print: {
color: {
type: "STANDARD_COLOR",
vendor_id: "Color"
},
duplex: {
type: "LONG_EDGE"
},
copies: {copies: 1},
media_size: {
width_microns: 215900,
height_microns:279400
},
page_orientation: {
type: "PORTRAIT"
},
margins: {
top_microns:0,
bottom_microns:0,
left_microns:0,
right_microns:0
},
page_range: {
interval:
[{start:1,
end:????}]
}
}
};
There are many options that you can add to the ticket. See documentation
Finally, you need to initiate the Cloud Print Service. Here is where you get to define the specific printer that you want.
var payload = {
"printerid" : '**COPY YOUR PRINTER ID HERE**',
"title" : "Prep Print",
"content" : PUT YOUR CONTENT HERE...(e.g. If you do all of this using Google Apps Script...HtmlService.createHtmlOutput(VARIABLE).getAs('application/pdf')),
"contentType": 'text/html',
"ticket" : JSON.stringify(ticket)
};
var response = UrlFetchApp.fetch('https://www.google.com/cloudprint/submit', {
method: "POST",
payload: payload,
headers: {
Authorization: 'Bearer ' + getCloudPrintService().getAccessToken()
},
"muteHttpExceptions": true
});
response = JSON.parse(response);
if (response.success) {
Logger.log("%s", response.message);
} else {
Logger.log("Error Code: %s %s", response.errorCode, response.message);}
var outcome = response.message;
}