Hello,
Hope everybody is safe and doing great!
Introduction:
I just did a small POC on how to leverage NLP capabilities from SAP CAI, without exposing the underlined OData Service. Now for demo purposes, I used the Northwind service (yes it's actually publicly exposed, but in my imagination, it is not!
😉 )
while going thru it I found SAP has introduced new Authentication Set up (OAuth) and so accessing bot API now slightly different than earlier. Hence I thought why not write up a blog post on this.
Set up:
The concept I referred to from an excellent article which you can find
here.
I picked the idea where it talks about how not to expose on-prem service while accessing the NLP engine (offering from SAP CAI) and get your job done!
Yes true it hides everything but needs more development efforts.
To start with I created a simple bot that can parse only the expressions and grab the entities and share them back as a response that's it.
With that, I am now ready to go with building my bot logic and bot connector.
Build BOT Logic and Connector:
Hmmm, what happened!! Looks like more security now SAP has added, soI have to write more lines code for handling the new Authentication process. Let's do that ...
Creating the sapcaiToken function that is containing the code for generating access_Token.
async function sapcaiToken() {
const auth_url = config.get("auth_url");
const client_id = config.get("client_id");
const clientSecret = config.get("client_secret");
const params = new URLSearchParams();
params.append("grant_type", "client_credentials");
params.append("client_id", client_id);
params.append("client_secret", clientSecret);
const result = await nodefetch(auth_url, {
method: "POST",
body: params,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
});
var output = await result.json();
The mentioned auth_url, client_id & client_secret I got from by bot's design-time API.
Bot Settings > Tokens > Design-Time API (Click on button Generate Token) - yes, now you have all the details that you need.
As a practice, I put them all in my config folder under default.json, so that I can hide these super important credentials from you
😉
Still, you are reading?
OK, no worries the above code will not work for you! The code actually placed on my GitHub link which you can find at the end of this blog post and everything you will see there except the super-secret confidential details which you need to generate of your own.
Now, the Authentication token as received has some validity so as not to call this sapcaiToken function each time, I wanted to store it locally and hence used the NPM module node-persist.
So, I build one upper layer function getToken, and doing all the token validation there itself and once expired call the sapcaiToken within.
code snap:
const getToken = async function () {
await localStorage.init();
let auth_token = await localStorage.getItem("auth_token");
let access_token, expiry, time_now;
if (!auth_token) {
access_token = await sapcaiToken();
} else {
access_token = auth_token.access_token;
expiry = auth_token.expiry;
time_now = auth_token.time_now;
if (access_token === "" || (Date.now() - time_now) / 1000 > expiry) {
access_token = await sapcaiToken();
}
}
return access_token;
};
Build the User Interface
Now the main step is to call the bot API using this above-generated token and passing the user expressions. To make the quick UI I used the below HTML form template to get the simple interface with which I can enter my expressions.
res.send(`
<form action="/sapcai" method="POST">
<p>Type your message</p>
<input name="query" autocomplete=off>
<button>Send</button>
</form>
`);
Everything I got, so let's call the BOT API and passing the user's expression to get the entities/intents by leveraging the NLP power.
Here is the function I wrote called bot_nlp.js
const bot_nlp = async function (access_token, msg) {
const bot_connector = config.get("bot_api");
const dev_token = config.get("dev_token");
//dialog playload
var dialogPayload = {
message: { type: "text", content: msg },
conversation_id: "test",
};
dialogPayload = JSON.stringify(dialogPayload);
//set headers
const dialog_headers = {
Authorization: "Bearer " + access_token,
"X-Token": "Token " + dev_token,
"Content-Type": "application/json",
};
//connect sap cai bot
const bot_res = await nodefetch(bot_connector, {
method: "POST",
headers: dialog_headers,
body: dialogPayload,
});
//parse the bot response
const results = await bot_res.json();
return results;
};
So as a result I received the bot response as like a JSON which contains all the intents/messages/entities. So let's now destructure it and have validated if this is the intent I am looking for.
const results = await bot_nlp(access_token, msg);
//build the output
const bot_output = {
intent: results.results.nlp.intents[0].slug,
entity: results.results.nlp.entities,
message: results.results.messages[0].content,
};
So now let's prepare the OData query filter parameter which I can now use to make a query on my backend service that I am NOT willing to expose to the cloud.
let filter = "";
if (bot_output.entity.productname) {
let product_name = bot_output.entity.productname;
product_name.map((key) => {
if (filter.length > 0) {
filter = filter + " or " + `ProductName eq '${key.value}'`;
} else {
filter = `ProductName eq '${key.value}'`;
}
});
} else {
res.send(bot_output);
return;
}
const urlparam = {};
if (filter) {
urlparam.filter = filter;
}
Next is the function which I need to create is to make a fetch request on the backend service:
const productInfo = async function (urlparam) {
const url = config.get("northwind_api");
const response = await axios({
method: "GET",
url: url,
params: {
$filter: urlparam.filter,
$format: "json",
},
headers: {
accept: "application/json",
},
});
return response.data.d.results;
};
yes, so now everything in place and let's test the application:
One thing to be noted: I used the NPM module
"nodemon" for testing the application, if you don't have it installed you may install it or change the start script in package.json or run it manually as node index, whichever option you prefer.
Testing the Application:
User Input
Response from Application
Conclusion:
So this Node JS application can now access the SAP CAI BOT using BOT API and can leverage the power of NLP, and keep the Odata service hidden from the outside world. Now, this application you may deploy to your private network and that's all.
Resources:
The complete code is here in my Github:
https://github.com/developedbysom/chatbot-ds.git
Cloning information:
git clone https://github.com/developedbysom/chatbot-ds.git --branch master && cd chatbot-ds && npm install
If you still want a complete tutorial including BOT development, you may take a look at this video on my youtube channel.
Related other chatbot stuff from this
playlist:
Happy Bot building!
Thanks, Somnath