Constructing a Chatbot in Neo4j (Half Two)

Prepared for Half Two?
You might also like: Constructing a Chatbot in Neo4j

In half one our this constructing a chatbot sequence, we discovered how you can use OpenNLP to “hear” what a consumer is saying and determine each their intent and any entities they might have talked about. At this time we will discover ways to use Neo4j to speak again…like an impudent little one.

We have not achieved any graph modeling but, so let’s deal with a part of this. Our chatbot will likely be utilized by a workforce of Shadowrunners beneath a single account, however by totally different members of the workforce. We’d like Account nodes and these nodes can have Members that ship us messages, and we’ll need to reply to these messages. The messages will likely be so as, so we are able to chain them collectively in an inventory. It appears like this:

Let’s construct one other saved process to talk with our members. It can take the id of the account and the telephone variety of the member chatting with us and what they’re saying.

 @Process(title = "", mode = Mode.WRITE) @Description("CALL id, String telephone, String textual content)") public Stream<IntentResult> chat(@Identify(worth = "id") String id, @Identify(worth = "telephone") String telephone, @Identify(worth = "textual content") String textual content) { ArrayList<IntentResult> outcomes = new ArrayList<>();

The very first thing we’ll do is use the account and member, so we all know who’s chatting with us.

 Node account = db.findNode(Labels.Account, ID, id); Node member = null; for (Relationship hasMember : account.getRelationships(Route.OUTGOING, RelationshipTypes.HAS_MEMBER)) { member = hasMember.getEndNode(); if (member.getProperty(PHONE).equals(telephone)) { break; } else { member = null; } }

Subsequent, we have to create the message node to retailer what they chatted and when:

 Node subsequent = db.createNode(Labels.Message); subsequent.setProperty(TEXT, textual content); subsequent.setProperty(DATE,;

…and we have to join this message into the Message chain. We do that by checking to see if the member has any earlier messages and if that’s the case we get the final message, delete the connection from the member to this final message and create a brand new relationship from this subsequent message to the final message.

 if (member.hasRelationship(Route.OUTGOING, PREV_MESSAGE)) { Relationship prev = member.getSingleRelationship(PREV_MESSAGE, Route.OUTGOING); Node final = prev.getEndNode(); prev.delete(); subsequent.createRelationshipTo(final, PREV_MESSAGE); }

That final bit kinda leaves us dangling, to not fear we’ll handle connecting the chain up subsequent by including a relationship between this subsequent message to the Member node.

 member.createRelationshipTo(subsequent, PREV_MESSAGE);

Okay, now what? Nicely, we have to discover the intents of the message and reply.

 ArrayList<IntentResult> outcomes = new ArrayList<>(); findIntents(textual content, outcomes); for (IntentResult consequence : outcomes) { reply(member, consequence, subsequent); } return;

It sounds easy, however we’ve not discovered how the “reply” technique goes to provide you with a response. For this primary stab at constructing a chatbot, I figured every Intent can have a set of selections resulting in a reply primarily based on the data now we have. A few of these replies could also be requests for extra info, others will merely show the data requested, and others nonetheless will likely be actions we have to take. Like making an order, canceling an order, making an current order “Rushed”, and so forth.

What is going to this appear to be:

Let’s begin with one thing easy just like the “greeting” intent. If we all know the title of the Member, we wish to greet them with their title. If not, we’ll nice them with a generic greeting.

This resolution tree will likely be named “greeting” and have a single rule node. The rule is an expression that asks if the “title” property parameter handed in is empty or not.

 "CREATE (tree:Tree { id: 'greeting' })" + "CREATE (name_blank_rule:Rule { parameter_names: 'title', parameter_types:'String', expression:'title.isEmpty()' })" + "CREATE (answer_yes:Reply { id: 'greeting-yes' })" + "CREATE (answer_no:Reply { id: 'greeting-no' })" + "CREATE (tree)-[:HAS]->(name_blank_rule)" +

If the expression evaluates to true, we go to the “sure” Reply node, in any other case the “no” Reply node.

 "CREATE (name_blank_rule)-[:IS_TRUE]->(answer_yes)" + "CREATE (name_blank_rule)-[:IS_FALSE]->(answer_no)" +

Off every Reply node, we’ll hold “Responses”. We’d like a minimum of one Response per Reply node, however we are able to have as many as we wish. After we go to reply we’ll select one, however by having a couple of the chatbot will not really feel so robotic. We may even add a persona property to those Responses so members can really feel like they’re “chatting” with totally different folks, or add a language property to make our chatbot multilingual:

 "CREATE (i1r1:Response {textual content:'Hello $title!', parameter_names:['name']})" + "CREATE (i1r2:Response {textual content:'Hi there $title!', parameter_names:['name']})" + "CREATE (i1r3:Response {textual content:'Hi there there!', parameter_names:[]})" + "CREATE (i1r4:Response {textual content:'Hiya!', parameter_names:[]})" + "CREATE (answer_no)-[:HAS_RESPONSE]->(i1r1)" + "CREATE (answer_no)-[:HAS_RESPONSE]->(i1r2)" + "CREATE (answer_yes)-[:HAS_RESPONSE]->(i1r3)" + "CREATE (answer_yes)-[:HAS_RESPONSE]->(i1r4)" +

Let’s begin with the reply technique. The very first thing we have to do is use the basis of the choice tree named after the intent we’re replying to.

personal void reply(Node account, IntentResult consequence, Node subsequent) { // Which Determination Tree are we enthusiastic about? Node tree = db.findNode(Labels.Tree, ID, consequence.intent); if ( tree != null) {

You could have seen above among the responses have parameters that must be stuffed in. How are we going to try this? Nicely, we have to collect some info concerning the account. For now, we’ll collect info concerning the member and the time of day. We’ll take a look at these later.

FactGenerator factGenerator = new FactGenerator(db, account);
Map<String, Object> info = new HashMap<>();

As soon as now we have a bunch of info, we’ll run the decisionPath technique to get an Reply node. From this reply node, we’ll get the potential responses.

Stream<Path> paths = decisionPath(tree, info);
Path path = paths.findFirst().get();
ArrayList<String> potentialResponses = new ArrayList<>();
for (Relationship responses : path.endNode().getRelationships(Route.OUTGOING, RelationshipTypes.HAS_RESPONSE)) { Node response = responses.getEndNode(); potentialResponses.add((String)response.getProperty(TEXT, ""));

We’ll choose a type of potential responses at random for now, however we may get actually fancy right here with personalities and languages as talked about earlier.

Random rand = new Random();
String response = potentialResponses.get(rand.nextInt(potentialResponses.measurement()));
response = fillResponseWithFacts(info, response);

We have to fill these responses with among the info we gathered earlier. However earlier than we dive into info, let’s save our reply within the graph to finish this technique:

Node reply = db.createNode(Labels.Reply);
reply.setProperty(INTENT, consequence.intent);
reply.setProperty(TEXT, consequence.response);
String[] args = convertArgsToStringArray(consequence);
reply.setProperty(ARGS, args); subsequent.createRelationshipTo(reply, RelationshipTypes.HAS_REPLY);

In regards to the info. We wish to present probably the most info doable to our resolution tree so we take the precise branches and get an excellent response. So we are able to begin by gathering what we all know of the member. For instance, if we knew the “title” or “birthdate” property of the Member, we may greet them with their title or inform them Glad Birthday!

 public void getMemberFacts( Map<String, Object> info) { info.put("member_node_id", member.getId()); Outcome factResult = db.execute("MATCH (member:Member) WHERE ID(member) = $member_node_id RETURN PROPERTIES(member) AS properties", info); Map<String, Object> factMap = (Map<String, Object>)factResult.subsequent().get("properties"); info.putAll(factMap); info.putIfAbsent("title", ""); }

We are able to additionally differ our response primarily based on the time of day. So we are able to collect that and add it to our info.

 public void getTimeFacts(Map<String, Object> info) { info.put("time_of_day", new TimeOfDay(; }

Should you recall the “greeting” resolution tree, we checked the title property earlier than deciding how you can reply. If now we have it we are able to reply with it, however to be able to do this, we have to fill it in. So we take our response textual content and exchange the parameters with any info we might have.

personal String fillResponseWithFacts(Map<String, Object> info, String response) { // Fill in info for (Map.Entry<String, Object> entry : info.entrySet()) { String key = "$" + entry.getKey(); response = response.replaceAll(key, entry.getValue().toString() ); } return response;

Let’s take a look at this out. Given an current graph with two accounts and two members, one with a reputation and one with out:

 personal static last String MODEL_STATEMENT = "CREATE (a1:Account {id:'a1'})" + "CREATE (m1:Member {title:'Max De Marzi', telephone:'123'})" + "CREATE (a1)-[:HAS_MEMBER]->(m1)" + "CREATE (a2:Account {id:'a2'})" + "CREATE (m2:Member { telephone:'456'})" + "CREATE (a2)-[:HAS_MEMBER]->(m2)";

When the primary member chats “Hi there?”

consequence = "CALL$id, $telephone, $textual content)", parameters( "id", "a1", "telephone", "123", "textual content", "Hi there?" ) );

We get a reply that could be a greeting and ends with their title.

File report = consequence.single();
assertThat(report.get("response").asString()).endsWith("Max De Marzi!");

When the second member chats “Hi there?”:

consequence = "CALL$id, $telephone, $textual content)",
parameters( "id", "a2", "telephone", "456", "textual content", "Hi there?" ) );

We don’t reply with their title, as a result of we do not have it.

report = consequence.single();

Alright, in the event you made it this far, we now have a chatbot that may say good day and goodbye. Which is nice, however not tremendous helpful. Our Shadowrunners wish to get info on gear, make purchases, rush orders, and extra than simply have a brief chat. We’ll dive into a few of that partly three if you wish to get forward of me, check out the supply code.

Additional Studying

A Newbie’s Information to Creating an Interactive Chatbot Circulation in Teneo

A Information on Chatbots

The Anatomy of a Chatbot

0 Comment

Leave a comment