set input or output context dialogflow nodejs v2 - node.js

I am using dialogflow NPM module and I want to send input/output context but I am not sure how to do.
I know I can do in google-assistant NPM with
I can set contexts with parameter using below method,
const parameters = { // Custom parameters to pass with context
welcome: true,
};
conv.contexts.set('welcome-context', 5, parameters);

First, some clarification on packages
The google-assistant package is one person's implementation/wrapper for the Assistant SDK, allowing you to embed the Assistant in any program or device. This is like building your own Google Home. (This package isn't from Google, it appears, but does wrap the official protobuf API, so you don't have to compile it yourself.)
The actions-on-google package is for making a webhook that works either with the Action SDK or Dialogflow. This is for making a program that works with the Assistant and sends back responses. (ie - it is for writing the code that responds to "Hey Google, talk to my app")
The dialogflow package lets you work with Dialogflow agents, including configuring them and sending queries to them to see which Intents match the queries. This is a very rough equivalent of the "google-assistant" package, but does a lot more.
The dialogflow-fulfillment package is for making a webhook that provides logic and responses for any of the platforms that Dialogflow supports. It is the equivalent of the actions-on-google package.
The code fragment you provided looks like it is from the "actions-on-google" package, and is setting the output context as part of the reply.
The equivalent of your code using the "dialogflow-fulfillment" package would be
const parameters = { // Custom parameters to pass with context
welcome: true,
};
agent.context.set('welcome-context', 5, parameters);
Note that it is "context", without the "s" in this package.
Similarly, to get an input context, you would use
agent.context.get('name-of-context;);
(You may see some examples that use something like agent.setContext(). These methods have been deprecated in favor of the above.)

In order to send a context using the Dialogflow NPM module, you have to first create a context, using dialogflow.ContextsClient and then send it on the query.
To convert the parameters to the format required by Dialogflow you will need to use this module: pb-util
const dialogflow = require('dialogflow');
const { struct } = require('pb-util');
const projectId = 'projectId';
const contextsClient = new dialogflow.ContextsClient();
const sessionClient = new dialogflow.SessionsClient();
async function createContext(sessionId, contextId, parameters, lifespanCount = 5) {
const sessionPath = contextsClient.sessionPath(projectId, sessionId);
const contextPath = contextsClient.contextPath(
projectId,
sessionId,
contextId
);
const request = {
parent: sessionPath,
context: {
name: contextPath,
parameters: struct.encode(parameters)
lifespanCount
}
};
const [context] = await contextsClient.createContext(request);
return context;
}
function sendQuery(sessionId, query, context) {
const session = sessionClient.sessionPath(projectId, sessionId);
const request = {
session,
queryInput: {
text: {
text: query
}
},
queryParams: {
contexts: [context] // You can pass multiple contexts if you wish
}
};
return sessionClient.detectIntent(request);
}
(async() => {
const parameters = { // Custom parameters to pass with context
welcome: true
};
const sessionId = 'my-session';
const context = await createContext(sessionId, 'welcome-context', parameters);
const response = await sendQuery(sessionId, 'Hi', context);
console.log(response);
})();
Have in mind that you send an input context. The output context is generated in the intent.
If you have trouble authenticating the clients SessionClient & ContextClient you can check this other question: Dialogflow easy way for authorization

Building on Marco's excellent answer-- here's a simpler version:
package.json
{
"name": "credtest",
"version": "1.0.0",
"dependencies": {
"dialogflow": "^1.2.0",
"pb-util": "^0.1.3"
}
}
index.js
const {ContextsClient} = require('dialogflow')
const { struct } = require('pb-util');
// REPLACE THESE:
/*
1. Service Account Credential file (KEEP SAFE)
// where/how to get this key: https://cloud.google.com/iam/docs/creating-managing-service-account-keys
// For different auth options, see Marco Casagrande's explainer: https://stackoverflow.com/questions/50545943/dialogflow-easy-way-for-authorization/50546430#50546430)
// Note: JSON hard-coded below for copy/paste, just import/require in when using for real
*/
const credential = {
"type": "__REPLACE__ME___",
"project_id": "__REPLACE__ME___",
"private_key_id": "__REPLACE__ME___",
"private_key": "__REPLACE__ME___",
"client_email": "__REPLACE__ME___",
"client_id": "__REPLACE__ME___",
"auth_uri": "__REPLACE__ME___",
"token_uri": "__REPLACE__ME___",
"auth_provider_x509_cert_url": "__REPLACE__ME___",
"client_x509_cert_url": "__REPLACE__ME___"
}
/*
2. Project ID, Google cloud project id
Tip: notice the service-account naturally already has your product id
*/
const project_id = '__REPLACE__ME___' // This is your google cloud project
const client = new ContextsClient({
credentials: credential
});
createContext('123456789', {name: 'bongo_context', lifespanCount: 3, parameters: {a:1, b:2, c:3 }} )
function createContext(sessionID, {name, lifespanCount, parameters = {} } = {} ) {
const formattedParent = client.sessionPath(project_id, sessionID);
const formattedContextName = `projects/${project_id}/agent/sessions/${sessionID}/contexts/${name}`
const context = {
"name": formattedContextName,
"lifespanCount": 2,
"parameters": struct.encode(parameters)
}
const request = {
parent: formattedParent,
context: context,
}
client.createContext(request)
.then(responses => {
const response = responses[0];
console.log('context now active:', response)
})
.catch(err => {
console.error(err);
});
}

Related

Next.js - using BigQuery client library gives an error : Module not found: Can't resolve 'child_process'

I am trying to query bigQuery dataset from a next.js project.
I have installed #google-cloud/bigquery and followed the steps from here
I have also tried next.js related solutions from this link but still getting below error.
It looks like next.config.js needs to be configured for this to allow this api call. I am not sure what needs to be changed.
Could someone please help me resolve this issue?
here is my code :
const { BigQuery } = require("#google-cloud/bigquery");
const bigquery = new BigQuery();
useEffect(() => {
async function queryBigQuery() {
const query = `
SELECT fieldname
FROM \`db.dataset.tablename\` WHERE columnname = 50
LIMIT 10`;
const options = {
query: query,
};
// Run the query
const [rows] = await bigquery.query(options);
console.log("Query Results:");
rows.forEach((row) => {
const url = row["url"];
const viewCount = row["view_count"];
console.log(`url: ${url}, ${viewCount} views`);
});
}
queryBigQuery();
}, []);
**wait - compiling...
error - ./node_modules/google-auth-library/build/src/auth/googleauth.js:17:0
Module not found: Can't resolve 'child_process'**
UPDATED:
I am able to load bigQuery library I think on client side but its giving me new error.
Here is my latest next.config.js file
module.exports = {
webpack: (config, { isServer, webpack }) => {
if (!isServer) {
config.node = {
dgram: "empty",
fs: "empty",
net: "empty",
tls: "empty",
child_process: "empty",
};
}
return config;
},
env: {
project variables.
};
New Error:
#google-cloud/bigquery is meant to run on a Node.js environment, it won't work in the browser.
You'll need to move your code to a data fetching method like getStaticProps/getServerSideProps or to an API route, as they all run server-side.
Here's an example using an API route, as it seems to fit your use-case best.
// pages/api/bigquery
const { BigQuery } = require("#google-cloud/bigquery");
const bigquery = new BigQuery();
export default function handler(req, res) {
const query = `
SELECT fieldname
FROM \`db.dataset.tablename\` WHERE columnname = 50
LIMIT 10
`;
const options = {
query: query,
};
// Run your query/logic here
res.json(data); // Return your JSON data after logic has been applied
}
Then, in your React component's useEffect:
const queryBigQuery = async () => {
const res = await fetch('api/bigquery');
const data = await res.json(); // Returns JSON data from API route
console.log(data);
}
useEffect(() => {
queryBigQuery();
}, []);

Sample Apollo Client code to test APQ (Automated Persistent Queries)

I was trying to test APQ with a server written in haskell. The following is the sample Apollo client code, I wrote to test it:
const { createPersistedQueryLink } = require("apollo-link-persisted-queries")
const { createHttpLink } = require("apollo-link-http")
const { InMemoryCache } = require("apollo-cache-inmemory")
const { ApolloClient } = require("apollo-client")
const { gql } = require('apollo-server');
const { ApolloLink } = require("apollo-link")
const fetch = require("node-fetch")
const link = ApolloLink.from([
createPersistedQueryLink(),
createHttpLink({
uri: "http://localhost:8080/v1/graphql",
fetch: fetch,
headers: {
"admin-secret":"password"
}
})
]);
const client = new ApolloClient({
cache: new InMemoryCache(),
link: link
})
async function main() {
const response = await client
.query({
query: gql`
query {
child {
name
}
}`
})
console.log(response.data)
}
main().catch(err => console.log("Err:", err))
But whenever I run this file, I get the following error:
graphQLErrors: [
{
extensions: [Object],
message: "the key 'query' was not present"
}
],
When I check the Request body sent in POST Body, I get the following thing:
{"operationName":null,"variables":{},"extensions":{"persistedQuery":{"version":1,"sha256Hash":"0832c514aef4b1a6d84702e8b2fab452cbb0af61f0a1c4a4c30405e671d40527"}}}
It tells that the query is not sent in the Post Body. Which might be the reason I'm getting the above error.
Hence, I am confused at this point :see_no_evil:
I read through a tons of blogs, but It's not clear as to what HTTP method is used when { useGETForHashedQueries: true } option is not given. From my experiment above, it looks as if - POST method is used.
But if POST method is used, why isn't the query sent in the POST body.
BUT
When I use the { useGETForHashedQueries: true } option, it works correctly. What might I be doing wrong here?
It would be really great, if someone would clear this out for me.

Extensions not returned in GraphQL query results

I'm creating an Apollo Client like this:
var { ApolloClient } = require("apollo-boost");
var { InMemoryCache } = require('apollo-cache-inmemory');
var { createHttpLink } = require('apollo-link-http');
var { setContext } = require('apollo-link-context');
exports.createClient = (shop, accessToken) => {
const httpLink = createHttpLink({
uri: `https://${shop}/admin/api/2019-07/graphql.json`,
});
const authLink = setContext((_, { headers }) => {
return {
headers: {
"X-Shopify-Access-Token": accessToken,
"User-Agent": `shopify-app-node 1.0.0 | Shopify App CLI`,
}
}
});
return new ApolloClient({
cache: new InMemoryCache(),
link: authLink.concat(httpLink),
});
};
to hit the Shopify GraphQL API and then running a query like that:
return client.query({
query: gql` {
productVariants(first: 250) {
edges {
node {
price
product {
id
}
}
cursor
}
pageInfo {
hasNextPage
}
}
}
`})
but the returned object only contain data and no extensions which is a problem to figure out the real cost of the query.
Any idea why?
Many thanks for your help
There's a bit of a hacky way to do it that we wrote up before:
You'll need to create a custom apollo link (Apollo’s equivalent of middleware) to intercept the response data as it’s returned from the server, but before it’s inserted into the cache and the components re-rendered.
Here's an example were we pull metrics data from the extensions in our API:
import { ApolloClient, InMemoryCache, HttpLink, ApolloLink } from 'apollo-boost'
const link = new HttpLink({
uri: 'https://serve.onegraph.com/dynamic?show_metrics=true&app_id=<app_id>',
})
const metricsWatchers = {}
let id = 0
export function addMetricsWatcher(f) {
const watcherId = (id++).toString(36)
metricsWatchers[watcherId] = f
return () => {
delete metricsWatchers[watcherId]
}
}
function runWatchers(requestMetrics) {
for (const watcherId of Object.keys(metricsWatchers)) {
try {
metricsWatchers[watcherId](requestMetrics)
} catch (e) {
console.error('error running metrics watcher', e)
}
}
}
// We intercept the response, extract our extensions, mutatively store them,
// then forward the response to the next link
const trackMetrics = new ApolloLink((operation, forward) => {
return forward(operation).map(response => {
runWatchers(
response
? response.extensions
? response.extensions.metrics
: null
: null
)
return response
})
})
function create(initialState) {
return new ApolloClient({
link: trackMetrics.concat(link),
cache: new InMemoryCache().restore(initialState || {}),
})
}
const apolloClient = create(initialState);
Then to use the result in our React components:
import { addMetricsWatcher } from '../integration/apolloClient'
const Page = () => {
const [requestMetrics, updateRequestMetrics] = useState(null)
useEffect(() => {
return addMetricsWatcher(requestMetrics =>
updateRequestMetrics(requestMetrics)
)
})
// Metrics from extensions are available now
return null;
}
Then use a bit of mutable state to track each request and its result, and the use that state to render the metrics inside the app.
Depending on how you're looking to use the extensions data, this may or may not work for you. The implementation is non-deterministic, and can have some slight race conditions between the data that’s rendered and the data that you've extracted from the extensions.
In our case, we store performance metrics data in the extensions - very useful, but ancillary - so we felt the tradeoff was acceptable.
There's also an open issue on the Apollo client repo tracking this feature request
I dont have any idea of ApolloClient but i tried to run your query in shopify graphql app. It return results with extensions. Please find screenshot below. Also You can put questions in ApolloClient github.

How could I detect an Intent with a context ? (node.js SDK for Dialogflow API V2)

How could I detect an Intent with a context ?
I defined an Intent "Book" with an input Context "Activity", then trained my bot with "Book this activity in my schedule".
I don't want to use dialog flow to manage context flow because it may evolve from other inputs in my app. So I want pass it as a parameter for each intent detection.
I use node.js SDK for Dialogflow API V2.
I precise it works fine with REST API v1 ... I think i'm stuck with API v2 Contexts :)
I'm looking to do exactly the same as the following code with API v2
//code below is working fine
import apiai from 'apiai';// Dialogflow SDK v1
import uuidv4 from 'uuid/v4';
const sessionId = uuidv4();
const api = apiai('myToken');
const lang = 'en-US';
const request = api.textRequest('Book this activity in my schedule', {
sessionId,
lang,
contexts : [{ name : 'activity' }],
});
request.on('response', function(response) {
console.log(response);
});
request.on('error', function(error) {
console.log(error);
});
request.end();
I didn't found documentation or exemple to do that except API documentation so I probably did something wrong
Whenever I pass a context as a queryParams or create a Context it don't work.
It also seems that context creation from API don't work at all.
import dialogflow from 'dialogflow';//Dialogflow SDK v2
import uuidv4 from 'uuid/v4';
const projectId = 'myID';
const sessionId = uuidv4();
const languageCode = 'en-US';
const sessionClient = new dialogflow.SessionsClient();
const contextClient = new dialogflow.ContextsClient();
const sessionPath = sessionClient.sessionPath(projectId, sessionId);
...
First try returned my Default Fallback Intent :
const request = {
session : sessionPath,
queryParams : {
context : {
parent : sessionPath,
name : contextClient.contextPath(
projectId,
sessionId,
'activity'
),
},
},
queryInput : {
text : {
text : 'Book this activity in my schedule',
languageCode : languageCode,
},
},
};
sessionClient.detectIntent(request)
.then(responses => {
const result = responses[0].queryResult;
if (result.intent) {
console.log(result.intent.displayName);
} else {
console.log(' No intent matched.');
}
});
Second try returned my Default Fallback Intent :
contextClient
.createContext({
parent : sessionPath,
context : {
name : contextClient.contextPath(
projectId,
sessionId,
'activity'
),
},
})
.then(() => contextClient.listContexts({ parent : sessionPath }))
.then(contexts => {
console.log(contexts);//returned an empty list
return sessionClient.detectIntent(request);
}).then(responses => {
const result = responses[0].queryResult;
if (result.intent) {
console.log(result.intent.displayName);
} else {
console.log(' No intent matched.');
}
});
Anybody see what's wrong with it ?
Help needed :)
Had same issue.
Found that I need to send "lifespanCount" greated than 0.
{
"session": "projects\/verifier-26084\/agent\/sessions\/1gg5b76c37f7111b",
"queryParams":
{
"contexts": [
{
"name": "projects\/verifier-26084\/agent\/sessions\/1gg5b76c37f7111b\/contexts\/ask-children",
"lifespanCount": 1
}]
},
"queryInput":
{
"text":
{
"text": "\u0442\u0440\u0438",
"languageCode": "ru-RU"
}
}
}

issues with Claudia.js text responses and Alexa

I'm working on a Claudia.js bot that can be reached through Slack, FB messenger, and as an Alexa skill. Supposedly in claudia, you can return plain text and the framework will give it back to the "frontend" correctly... What I have currently have here works fine with FB and Slack, but when I access it through the Alexa "Service Simulator" I always get "The response is invalid." Here is the lambda that uses Claudia.js. Basically it gets messages from the client and then shunts them to another Lambda that is the "AI". Alexa seems to be choking on line 67. Ideas?
const promiseDelay = require('promise-delay');
// const aws = require('aws-sdk');
// const lambda = new aws.Lambda();
const lambda = require('aws-lambda-invoke');
const botBuilder = require('claudia-bot-builder');
const stackTrace = require('stack-trace');
//const slackDelayedReply = botBuilder.slackDelayedReply;
const getIntentName = alexaPayload =>
alexaPayload &&
alexaPayload.request &&
alexaPayload.request.type === 'IntentRequest' &&
alexaPayload.request.intent &&
alexaPayload.request.intent.name;
const api = botBuilder((message, apiRequest) => {
console.log = console.log.bind(null, '[LOG]');
console.info = console.info.bind(null, '[INFO]');
console.error = console.error.bind(null, '[ERROR]');
console.warn = console.warn.bind(null, '[WARN]');
console.info(message, apiRequest);
console.log(apiRequest.body);
const requestData = {
'user-id': {
type: message.type,
ID: message.sender
},
epoch: 1484771343.01,
'payload-type': 'luis',
facets: {},
utterance: 'Seek Showtimes',
payload: {
query: 'Seek Showtime',
topScoringIntent: {
intent: 'SeekShowtime',
score: 1.0
},
intents: [{
intent: 'SeekShowtime',
score: 1
}],
entities: []
}
};
if (message.text) {
return new Promise((resolve, reject) => {
lambda.raw.invoke({
FunctionName: 'ca2',
Payload: JSON.stringify(requestData),
}, (err, done) => {
if (err) {
const trace = stackTrace.parse(err);
console.warn(err);
console.error(trace);
return reject(err);
}
resolve(done);
});
}).then((result) => { // the initial response
const payload = JSON.parse(result.Payload);
console.log(payload.utterance);
return payload.utterance;
}).catch((error) => {
const trace = stackTrace.parse(error);
console.warn(error);
console.error(trace);
return 'Could not setup';
});
} else if (getIntentName(apiRequest.body) === 'ExitApp') {
return {
response: {
outputSpeech: {
type: 'PlainText',
text: 'Bye from Bot!'
},
shouldEndSession: true
}
};
} else {
return {};
}
},
{ platforms: ['facebook', 'slackSlashCommand', 'alexa'] }
);
module.exports = api;
Update -- even if I hardcode a plain text string response or use Alexa Message Builder I still get "The response is invalid." as the Service Response is coming back "undefined."
Looking at logs, as soon as the response is returned (for parsing by botBuilder and a pass to Alexa) this error occurs [TypeError: Cannot read property 'replace' of undefined]
Another update:
If I replace return payload.utterance with something like
if (type === 'alexa-skill') {
Console.warn('trying to contact alexa');
return "Hi from Alexa";
}
The problem persists.
Here is where the Json Request comes in, no problem:
2017-04-27T18:06:30.552Z 3d70c273-2b74-11e7-a1c8-bf3fec00cbff STORE Map { "user-id": Map { "type": "alexa-skill", "ID": "amzn1.ask.account.AF6FUNJDSHGCXPVSAO5HUSRYFBD3SPCJJLILC4HLPS3K3L4AOWIMXPS4ZDDCXQ3ZVIV5L4FOMYD23PWZXEIAKYQBVXIQTPE2WW2PMBIXQIY3TUATXADCVNYO7NYUR2B45EU5GRIWBFHQIPLQVDQZMXD7IYVGTKAV3OWPHROCPR7XIUGNSJEAGQZJOMULSKT5HYSNUNJONASE34Y" }, "epoch": 1484771343.01, "payload-type": "luis", "utterance": "when is Logan playing", "payload": Map { "query": "when is Logan playing" } }
Here is the response I get back from the other lambda (the payload):
017-04-27T18:06:32.513Z 3d70c273-2b74-11e7-a1c8-bf3fec00cbff [LOG] mnlpData { StatusCode: 200,
Payload: '{"utterance": "To find movies playing near you, I need to know where you are. Please tell me your zip code.", "AskLocation": 1, "context": {"updated": 1493316392.162429, "user_id": "TEST_ID_TUES_14", "sessions": [{"intents": ["SeekShowtime", "SeekShowtime"], "facet-delta": {}, "facets": {"ity.location": {"ity.zip": "", "ity.code": "", "ity.theatre-name": ""}, "ity.movie": {"ity.title": "", "ity.code": ""}, "ity.time": [], "ity.date": []}, "modes": ["", "SHOWTIME_SWITCH", "AskLocation", "SHOWTIME_SWITCH", "AskLocation"]}], "created": 1493316379.950335, "mode_process_count": 2, "user-id": {"type": "alexa-skill", "ID": "amzn1.ask.account.AF6FUNJDSHGCXPVSAO5HUSRYFBD3SPCJJLILC4HLPS3K3L4AOWIMXPS4ZDDCXQ3ZVIV5L4FOMYD23PWZXEIAKYQBVXIQTPE2WW2PMBIXQIY3TUATXADCVNYO7NYUR2B45EU5GRIWBFHQIPLQVDQZMXD7IYVGTKAV3OWPHROCPR7XIUGNSJEAGQZJOMULSKT5HYSNUNJONASE34Y"}, "utterance": ["To find movies playing near you, I need to know where you are. Please tell me your zip code."]}}' }
then:
2017-04-27T18:06:32.514Z 3d70c273-2b74-11e7-a1c8-bf3fec00cbff [WARN] trying to contact alexa
and then the error:
2017-04-27T18:06:32.514Z 3d70c273-2b74-11e7-a1c8-bf3fec00cbff [TypeError: Cannot read property 'replace' of undefined]
First, if you notice the payload that is sent to you from the other lambda, you can see the utterance is an array, so you might have to pass the first element if present.
By looking at the code of the bot builder, my best bet is that the error you're getting is due to the fact your alexaAppName is undefined, and when passing it down to the responder that is encoded in base64, it's not possible to run a replace of this variable.
I would try to make sure my app name is correctly configured and what's given is a valid string as shown in the alexa claudia example.
If you haven't already, run claudia update --configure-alexa-skill and enter your bot's name, then use the url it gives in your alexa skill builder setup. Select HTTPS instead of lambda arn.
Currently, message.text is being passed as an empty string, which means that none of your log blocks are firing, and you are returning an empty object at the very bottom of your code. Test it yourself by replacing that empty object with a string, the alexa tester no longer complains.
So why is message.text an empty string? Claudia js populates the text field by concatenating all the slots that have been filled for your intent. If your intent has no slots, then claudia passes an empty string.
Add slots to your intent, make sure the skill is configured, and fix the logic statement issues.

Resources