An example of a simple, queryable knowledge graph implemented using neo4j with a node command line interface implemented in TypeScript.
34 |
35 |
Knowledge Graph Demo Demo Running on Jibo (YouTube):
36 |
37 |
38 |
39 |
40 |
41 |
Note: The code described in this post is based on an example that I worked on with Roberto Pieraccini (http://robertopieraccini.com/home/) at Jibo, Inc. We used a similar example to test a knowledge-graph-enhanced dialog running on Jibo (as seen in the video referenced above)
42 |
Note: A nice tool for viewing and live-editing neo4j graphs is called Graph Editor and is available at http://wwlib.org/graph-editor/
One way to enhance NLU-driven dialog interactions is to make use of a knowledge graph. This example uses a neo4j graph (database) to store and query a knowledge graph (kg) containing information about animals. This simple example kg represents the relationships between a robot, and handful of animals, a few animal types and a couple of humans.
47 |
This command-line version uses node-nlp by default and can be configured to use Microsoft's LUIS NLU. A neo4j graph database is also required (ideally an empty graph). Some setup is required to get started.
48 |
Setup
49 |
1 - Clone and install the neo4j-knowledge-graph repo
To populate the graph, paste the contents of docs/animals.cypher into the neo4j browser query field. Then verify that the graph is ready by entering this cypher into the browser query field:
66 |
MATCH (n) return n limit 100
67 |
The result should look like the image below.
68 |
69 |
To see just the animal relationships, enter this cypher into the browser query field:
70 |
match (n)-[r:IS_A]-(m) return n, r, m
71 |
The result should look like the image below
72 |
73 |
4 - Configuring the neo4j-knowledge-graph command-line app
74 |
Copy data/config-example.json and rename it data/config.json
75 |
Fill out the user and password fields for your neo4j graph. (optional: fill out the access credentials for your LUIS agent).
In a terminal window, in the neo4j-knowledge-graph directory, build and run the command line app:
91 |
yarn build
92 | yarn debug
93 |
If everything is setup correctly, you should should be prompted with:
94 |
Ask a do-you-like question or say “[user] likes [something]”
95 |
Try asking (typing): do you like penguins
96 |
The response should look like the screen below:
97 |
98 |
The example graph contains a set of animal-related nodes and relationships that looks like this (as seen in Graph Editor):
99 |
100 |
The graph also contains nodes and relationship relating a robot and a couple of users to the animals:
101 |
102 |
According to the graph, the robot likes Flies, Zebras, Whales, Armadillos, Flamingos, and Penguins. Andrew likes whales. Cynthia likes the robot. Asking, “do you like whales” produces the response below: “You know it. One of my favorite animals...and I know that Andrew likes them too.”
103 |
Note: In the Penguin node has a special RobotLikes property which is used in the response. A generic response is generated for nodes that do not have this property.
104 |
105 |
To answer the question, “Do you like bats”, the app must make an upward reference because the robot does not have a direct LIKES relationship to bats. The app checks the bat’s parent node, AnimalType (Mammal), and then responds with a list of mammals that the robot does like:
106 |
“I don’t know if I like bats, but they are Mammals and I do like Mammals. Of all the Mammals I like Armadillos, Zebras, and Whales.”
107 |
108 |
109 |
The graph can be modified by asserting that a user likes something. For example, asserting that “cynthia likes penguins” produces the result:
110 |
OK. I understand that cynthia likes penguins. That’s cool.
111 |
112 |
Querying the graph now reveals that the fact that Cynthia likes penguins has been incorporated:
113 |
match (n)-[r:LIKES]-(m) return n, r, m
114 |
115 |
Now, asking if the robot likes penguins produces a response that includes the new information about Cynthia:
116 |
117 |
When asking about an AnimalType, like ‘Animal’, the answer will include all of the descendants that are liked by the robot.
118 |
119 |
Summary
120 |
This simple example offers a quick way to incorporate a knowledge graph into a dialog interaction. As seen in the intro video (at the start of the post) when this kind of interaction is embodied by a social robot like Jibo, the experience is compelling - especially when the app/robot remembers information asserted by the user.
121 |
The example suggests that knowledge graphs may offer a path to take to making automated dialog significantly more human-like
122 |
Appendix: NLU Setup Details
123 |
LUIS
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
--------------------------------------------------------------------------------
/docs/neo4j-knowledge-graph-intro.md:
--------------------------------------------------------------------------------
1 | ## neo4j-knowledge-graph
2 |
3 | *Subject*: Neo4j Knowledge Graph
4 | *Language*: TypeScript (node)
5 | *Repo*: git@github.com:wwlib/neo4j-knowledge-graph.git
6 | *Related*: Electron-based Graph Editor Tool: [https://wwlib.github.io/graph-editor/](https://wwlib.github.io/graph-editor/)
7 |
8 | [https://wwlib.github.io/neo4j-knowledge-graph/](https://wwlib.github.io/neo4j-knowledge-graph/)
9 |
10 | [https://github.com/wwlib/neo4j-knowledge-graph](https://github.com/wwlib/neo4j-knowledge-graph)
11 |
12 | [https://wwlib.github.io](https://wwlib.github.io/)
13 |
14 | An example of a simple, queryable knowledge graph implemented using neo4j with a node command line interface implemented in TypeScript.
15 |
16 | Knowledge Graph Demo Demo Running on Jibo (YouTube): [https://www.youtube.com/embed/0oVCR3pIz0Q](https://www.youtube.com/embed/0oVCR3pIz0Q)
17 |
18 | Note: The code described in this post is based on an example that I worked on with Roberto Pieraccini ([http://robertopieraccini.com/home/](http://robertopieraccini.com/home/)) at Jibo, Inc. We used a similar example to test a knowledge-graph-enhanced dialog running on Jibo (as seen in the video referenced above)
19 |
20 | Note: A nice tool for viewing and live-editing neo4j graphs is called Graph Editor and is available at [http://wwlib.org/graph-editor/](http://wwlib.org/graph-editor/)
21 |
22 | ### Overview
23 |
24 | Overview video (YouTube): [https://www.youtube.com/embed/YFRiWiZJPkU](https://www.youtube.com/embed/pEps_xaUWVo)
25 |
26 | ### Getting Started
27 |
28 | One way to enhance NLU-driven dialog interactions is to make use of a knowledge graph. This example uses a neo4j graph (database) to store and query a knowledge graph (kg) containing information about animals. This simple example kg represents the relationships between a robot, and handful of animals, a few animal types and a couple of humans.
29 |
30 | This command-line version uses node-nlp by default and can be configured to use Microsoft's LUIS NLU. A neo4j graph database is also required (ideally an empty graph). Some setup is required to get started.
31 |
32 |
33 | #### Setup
34 |
35 | 1 - Clone and install the neo4j-knowledge-graph repo
36 | ```
37 | git clone git@github.com:wwlib/neo4j-knowledge-graph.git
38 | cd neo4j-knowledge-graph
39 | yarn
40 | ```
41 |
42 | The project looks like this:
43 |
44 | 
45 |
46 | 2 - NLU
47 | (optional: create either a LUIS agent by uploading the included agent description files to your LUIS account:
48 |
49 | - docs/luis-knowledge-graph.lu
50 |
51 | See the NLU screenshots at the end of this post for descriptions of the LUIS agent.
52 |
53 | 3 - Neo4j
54 | Download and install the free Neo4j Desktop app from [https://neo4j.com/download-neo4j-now/](https://neo4j.com/download-neo4j-now/) and create a new graph.
55 |
56 | 
57 |
58 | Start the graph and access it in the browser at: [http://localhost:7474/browser/](http://localhost:7474/browser/)
59 |
60 | To populate the graph, paste the contents of docs/animals.cypher into the neo4j browser query field. Then verify that the graph is ready by entering this cypher into the browser query field:
61 |
62 | `MATCH (n) return n limit 100`
63 |
64 | The result should look like the image below.
65 |
66 | 
67 |
68 | To see just the animal relationships, enter this cypher into the browser query field:
69 |
70 | `match (n)-[r:IS_A]-(m) return n, r, m`
71 |
72 | The result should look like the image below
73 |
74 | 
75 |
76 | 4 - Configuring the neo4j-knowledge-graph command-line app
77 |
78 | Copy `data/config-example.json` and rename it `data/config.json`
79 |
80 | Fill out the user and password fields for your neo4j graph. (optional: fill out the access credentials for your LUIS agent).
81 |
82 | ```
83 | {
84 | "luis": {
85 | "endpoint": "",
86 | "appId": "",
87 | "subscriptionKey": ""
88 | },
89 | "neo4j": {
90 | "url": "bolt://localhost:7687",
91 | "user": "neo4j",
92 | "password": ""
93 | }
94 | }
95 | ```
96 |
97 | Save the `data/config.json` file.
98 |
99 | #### Running the command line app
100 |
101 | In a terminal window, in the `neo4j-knowledge-graph` directory, build and run the command line app:
102 |
103 | ```
104 | yarn build
105 | yarn debug
106 | ```
107 |
108 | If everything is setup correctly, you should should be prompted with:
109 |
110 | `Ask a do-you-like question or say “[user] likes [something]”`
111 |
112 | Try asking (typing): `do you like penguins`
113 |
114 | The response should look like the screen below:
115 |
116 | 
117 |
118 | The example graph contains a set of animal-related nodes and relationships that looks like this (as seen in Graph Editor):
119 |
120 | 
121 |
122 | The graph also contains nodes and relationship relating a robot and a couple of users to the animals:
123 |
124 | 
125 |
126 | According to the graph, the robot likes Flies, Zebras, Whales, Armadillos, Flamingos, and Penguins. Andrew likes whales. Cynthia likes the robot. Asking, “do you like whales” produces the response below: “You know it. One of my favorite animals...and I know that Andrew likes them too.”
127 |
128 | Note: In the Penguin node has a special RobotLikes property which is used in the response. A generic response is generated for nodes that do not have this property.
129 |
130 | 
131 |
132 | To answer the question, “Do you like bats”, the app must make an upward reference because the robot does not have a direct LIKES relationship to bats. The app checks the bat’s parent node, AnimalType (Mammal), and then responds with a list of mammals that the robot does like:
133 |
134 | “I don’t know if I like bats, but they are Mammals and I do like Mammals. Of all the Mammals I like Armadillos, Zebras, and Whales.”
135 |
136 | 
137 |
138 | 
139 |
140 | The graph can be modified by asserting that a user likes something. For example, asserting that “cynthia likes penguins” produces the result:
141 |
142 | OK. I understand that cynthia likes penguins. That’s cool.
143 |
144 |
145 | 
146 |
147 | Querying the graph now reveals that the fact that Cynthia likes penguins has been incorporated:
148 |
149 | `match (n)-[r:LIKES]-(m) return n, r, m`
150 |
151 | 
152 |
153 | Now, asking if the robot likes penguins produces a response that includes the new information about Cynthia:
154 |
155 | 
156 |
157 | When asking about an AnimalType, like ‘Animal’, the answer will include all of the descendants that are liked by the robot.
158 |
159 | 
160 |
161 | ### Summary
162 |
163 | This simple example offers a quick way to incorporate a knowledge graph into a dialog interaction. As seen in the intro video (at the start of the post) when this kind of interaction is embodied by a social robot like Jibo, the experience is compelling - especially when the app/robot remembers information asserted by the user.
164 |
165 | The example suggests that knowledge graphs may offer a path to take to making automated dialog significantly more human-like
166 |
167 | ### Appendix: NLU Setup Details
168 |
169 | #### LUIS
170 |
171 | 
172 |
173 | 
174 |
175 | 
176 |
177 | 
178 |
179 | 
180 |
181 | 
182 |
183 | 
184 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'ts-jest',
3 | testEnvironment: 'node',
4 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "knowledge-base-neo4j",
3 | "version": "0.0.1",
4 | "description": "",
5 | "main": "dist/index.js",
6 | "scripts": {
7 | "test": "jest",
8 | "build": "tsc",
9 | "start-js": "node dist/index.js",
10 | "debug-js": "node dist/index.js -d",
11 | "start": "ts-node src/index.ts",
12 | "debug": "ts-node src/index.ts -d",
13 | "test-neo4j": "ts-node ./tools/test-neo4j.ts",
14 | "test-node-nlp": "ts-node ./tools/test-node-nlp.ts",
15 | "test-luis-controller": "ts-node ./tools/test-luis-controller.ts",
16 | "test-dialog-manager": "ts-node ./tools/test-dialog-manager.ts"
17 | },
18 | "author": "Andrew Rapo ",
19 | "license": "MIT",
20 | "dependencies": {
21 | "commander": "^6.2.0",
22 | "inquirer": "^7.3.3",
23 | "jest": "^26.6.3",
24 | "jsonfile": "^6.1.0",
25 | "neo4j-driver": "^4.2.1",
26 | "node-nlp": "^4.16.0",
27 | "prettyjson": "^1.2.1",
28 | "request": "^2.88.2"
29 | },
30 | "devDependencies": {
31 | "@types/jest": "^26.0.15",
32 | "@types/node": "^14.14.9",
33 | "ts-jest": "^26.4.4",
34 | "ts-node": "^9.0.0",
35 | "typescript": "^4.1.2"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/DialogManager.ts:
--------------------------------------------------------------------------------
1 | import NLUController, {
2 | NLURequestOptions,
3 | NLUIntentAndEntities
4 | } from './nlu/NLUController';
5 |
6 | import NodeNlpController from './nlu/node-nlp/NodeNlpController';
7 | import LUISController from './nlu/microsoft/LUISController';
8 |
9 | import Neo4jController, { Neo4jControllerOptions } from './neo4j/Neo4jController';
10 |
11 | import { d3Types } from './d3/d3Types';
12 |
13 | const prettyjson = require('prettyjson');
14 |
15 | export type DialogManagerOptions = {
16 | config: {
17 | nluType: string;
18 | luis: any;
19 | neo4j: any;
20 | };
21 | debug: boolean;
22 | }
23 |
24 | export default class DialogManager {
25 |
26 | public neo4jController: Neo4jController;
27 | public nluController: NLUController;
28 | public sessionId: string = `robot_${Math.floor(Math.random() * 10000)}`;
29 | public languageCode: string = 'en-US';
30 |
31 | private _debug: boolean = false;
32 |
33 | constructor() {
34 |
35 | }
36 |
37 | async init(options: DialogManagerOptions) {
38 | if (options.debug) {
39 | this._debug = true;
40 | }
41 | if (options.config.nluType === 'luis') {
42 | const luisConfig: any = {
43 | Microsoft: {
44 | nluLUIS_endpoint: options.config.luis.endpoint,
45 | nluLUIS_appId: options.config.luis.appId,
46 | nluLUIS_subscriptionKey: options.config.luis.subscriptionKey,
47 | }
48 | }
49 | this.nluController = new LUISController({ debug: this._debug, config: luisConfig });
50 | } else {
51 | this.nluController = new NodeNlpController({ debug: this._debug });
52 | await this.nluController.init();
53 | }
54 | const neo4jOptions: Neo4jControllerOptions = {
55 | debug: this._debug,
56 | config: options.config.neo4j
57 | };
58 | this.neo4jController = new Neo4jController(neo4jOptions);
59 | }
60 |
61 | ask(question: string, context?: string): Promise {
62 | return new Promise((resolve, reject) => {
63 | const options: NLURequestOptions = {
64 | languageCode: this.languageCode,
65 | contexts: [context],
66 | sessionId: this.sessionId
67 | }
68 | this.nluController.getIntentAndEntities(question, options)
69 | .then((intentAndEntities: NLUIntentAndEntities) => {
70 | if (this._debug) {
71 | console.log(`DialogManager: ask: intentAndEntities:`, JSON.stringify(intentAndEntities, null, 2));
72 | }
73 | this.handleNLUIntentAndEntities(intentAndEntities)
74 | .then((answer) => {
75 | resolve(answer);
76 | })
77 | .catch((err: any) => {
78 | reject(err);
79 | });
80 | })
81 | .catch((err: any) => {
82 | reject(err);
83 | });
84 | });
85 | }
86 |
87 | handleNLUIntentAndEntities(intentAndEntities: NLUIntentAndEntities): Promise {
88 | return new Promise((resolve, reject) => {
89 | let intent: string = intentAndEntities.intent;
90 | let entities: any = intentAndEntities.entities;
91 | let answer: string;
92 |
93 | this.debug(prettyjson.render(intentAndEntities, {}));
94 |
95 | if (intent == 'launchDoYouLike') {
96 | console.log(`INTENT: launchDoYouLike`);
97 | let cypher: string = `match (a {name:'${entities.thing}'})<-[:LIKES]-(j:Robot {name:'global'}) return a`;
98 | this.debug(` STEP 1a: SEE IF THE ROBOT LIKES THAT NODE ALREADY...`)
99 | this.debug(` cypher: ${cypher}`);
100 | this.neo4jController.parseCypherWithD3Helper(cypher)
101 | .then((data: d3Types.d3Graph) => {
102 | this.debug(` cypher result:`, data);
103 | if (data.nodes.length == 1) { // when successful, there will be one matching node as long as the entity names are unique
104 | let node: d3Types.d3Node = data.nodes[0];
105 | let scriptedResponse: string = node.properties['RobotLikes'];
106 | answer = scriptedResponse ? scriptedResponse : `Yes, I do like ${entities.thingOriginal} very much.`;
107 |
108 | cypher = `match ({name:'${entities.thing}'})<-[:LIKES]-(user:User) return user`;
109 | this.debug(` STEP 1a: SEE IF THERE IS A USER THAT ALSO LIKES THAT NODE...`)
110 | this.debug(` cypher: ${cypher}`);
111 | this.neo4jController.parseCypherWithD3Helper(cypher)
112 | .then((data: d3Types.d3Graph) => {
113 | this.debug(` cypher result:`, data);
114 | if (data.nodes.length > 0) {
115 | answer += this.generateListWithPhrases(data.nodes, '...and I know that', 'likes them too.', 'like them too.');
116 | }
117 | resolve(answer);
118 | })
119 | .catch(() => {
120 | reject();
121 | });
122 | } else {
123 | let cypher = `match(v {name:'${entities.thing}'})<-[:IS_A *]-(p)<-[l:LIKES]-(j:Robot {name:'global'}) return p`;
124 | this.debug(` STEP 2: SEE IF THERE ARE DESCENDANTS OF THAT NODE LIKED BY THE ROBOT...`)
125 | this.debug(` cypher: ${cypher}`);
126 | this.neo4jController.parseCypherWithD3Helper(cypher)
127 | .then((data: d3Types.d3Graph) => {
128 | this.debug(` cypher result:`, data);
129 | let nodes: d3Types.d3Node[] = data.nodes;
130 | if (nodes.length > 1) {
131 | answer = `Yes I like many ${entities.thingOriginal} and in particular i love`;
132 | answer += this.pluralList(nodes);
133 | resolve(answer);
134 | } else if (nodes.length == 1) {
135 | let node: d3Types.d3Node = nodes[0];
136 | answer = `Yes, of all the ${entities.thingOriginal} i like ${this.getPluralNameWithNode(node)}.`;
137 | resolve(answer);
138 | } else { // try upward inference
139 | cypher = `match (a {name:'${entities.thing}'})-[:IS_A]->(b) with b match (b)<-[:IS_A *]-(p)<-[:LIKES]-(j:Robot {name:'global'}) return b, p`
140 | this.debug(` STEP 3: TRY AN UPWARD REFERENCE...`)
141 | this.debug(` cypher: ${cypher}`);
142 | this.neo4jController.parseCypherWithD3Helper(cypher)
143 | .then((data: d3Types.d3Graph) => {
144 | this.debug(` cypher result:`, data);
145 | answer = `Actually I don't know if I like ${entities.thingOriginal}.`;
146 | let nodes: d3Types.d3Node[] = data.nodes;
147 | if (nodes.length > 1) { // the first node will be the parent and its children will be other instances of that type
148 | let parentNode: d3Types.d3Node = nodes.shift();
149 | answer = `I don't know if I like ${entities.thingOriginal} but they are ${this.getPluralNameWithNode(parentNode)}`;
150 | answer = `${answer} and I do like ${this.getPluralNameWithNode(parentNode)}. Of all the ${this.getPluralNameWithNode(parentNode)}`;
151 | answer = `${answer} I like`;
152 | if (nodes.length > 1) {
153 | answer += this.pluralList(nodes);
154 | } else {
155 | answer = `${answer} ${this.getPluralNameWithNode(nodes[0])}.`;
156 | }
157 | }
158 | resolve(answer);
159 | })
160 | .catch(() => {
161 | reject();
162 | });
163 | }
164 | });
165 | }
166 | })
167 | .catch((error: any) => {
168 | console.error(error);
169 | reject(error);
170 | });
171 |
172 | } else if (intent == 'launchUserLikes') {
173 | let answer = `OK. I understand that ${entities.user} likes ${entities.thingOriginal}. That's cool!`;
174 |
175 | let cypher = `merge (user:User {name:'${entities.user}'})`;
176 | this.debug(` STEP 1: CREATE USER NODE IF IT DOES NOT EXIST YET...`)
177 | this.debug(` cypher: ${cypher}`);
178 | this.neo4jController.call(cypher)
179 | .then((data: any) => {
180 | // Make a LIKE relationship
181 | cypher = `match (like {name:'${entities.thing}'}) with like match(user:User {name:'${entities.user}'}) with like, user merge (user)-[:LIKES]->(like)`;
182 | this.debug(` STEP 1a: CREATE A LIKE RELATIONSHIP...`)
183 | this.debug(` cypher: ${cypher}`);
184 | this.neo4jController.call(cypher)
185 | .then((data: any) => {
186 | resolve(answer);
187 | })
188 | .catch(() => {
189 | reject();
190 | });
191 | })
192 | .catch(() => {
193 | reject();
194 | });
195 | } else {
196 | reject();
197 | }
198 | });
199 | }
200 |
201 | getPluralNameWithNode(node: d3Types.d3Node): string {
202 | let result: string = `${node.properties.name}s`;
203 | if (node.properties.plural) {
204 | result = node.properties.plural;
205 | }
206 | return result;
207 | }
208 |
209 | pluralList(nodes: d3Types.d3Node[]): string {
210 | let result: string = '';
211 | for (let i: number = 0; i < (nodes.length - 1); i++) {
212 | let node = nodes[i];
213 | result += ` ${this.getPluralNameWithNode(node)}`;
214 | if (nodes.length > 2) {
215 | result += ','
216 | }
217 | };
218 | result += ` and ${this.getPluralNameWithNode(nodes[nodes.length - 1])}.`
219 | return result;
220 | }
221 |
222 | generateListWithPhrases(nodes: d3Types.d3Node[], introPhrase: string = '', singularPhrase: string = '', pluralPhrase: string = ''): string {
223 | let result: string = '';
224 | if (nodes.length > 0) {
225 | result += introPhrase;
226 | if (nodes.length == 1) {
227 | result += ` ${nodes[0].properties['name']} ${singularPhrase}`;
228 | } else {
229 | for (let i: number = 0; i < (nodes.length - 1); i++) {
230 | let node = nodes[i];
231 | result += ` ${node.properties['name']}`;
232 | if (nodes.length > 2) {
233 | result += ',';
234 | }
235 | };
236 | result += ` and ${nodes[nodes.length - 1].properties['name']}`;
237 | result += ` ${pluralPhrase}`;
238 | }
239 | }
240 | return result;
241 | }
242 |
243 | deleteUsers(): Promise {
244 | return new Promise((resolve, reject) => {
245 | let cypher: string = `match (n:User)-[r:LIKES]->() delete n, r`;
246 | this.neo4jController.call(cypher)
247 | .then(() => {
248 | this.debug(`The User nodes have been deleted.`);
249 | resolve();
250 | });
251 | });
252 | }
253 |
254 | debug(text: string, object?: any): void {
255 | if (this._debug) {
256 | if (text && object) {
257 | console.log(text, object);
258 | } else {
259 | console.log(text);
260 | }
261 | }
262 | }
263 | }
264 |
--------------------------------------------------------------------------------
/src/d3/d3Types.ts:
--------------------------------------------------------------------------------
1 | export namespace d3Types {
2 | export type d3Node = {
3 | id: string,
4 | group?: number,
5 | properties?: any,
6 | labels?: string[]
7 | };
8 |
9 | export type d3Link = {
10 | source: string,
11 | target: string,
12 | value?: number,
13 | id?: string,
14 | type?: string,
15 | startNode?: string,
16 | endNode?: string,
17 | properties?: any,
18 | linknum?: number
19 | };
20 |
21 | export type d3Graph = {
22 | nodes: d3Node[],
23 | links: d3Link[]
24 | };
25 | }
26 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import DialogManager from './DialogManager';
2 |
3 | const path = require('path');
4 | const jsonfile = require('jsonfile');
5 | const program = require('commander');
6 | const inquirer = require('inquirer');
7 |
8 | const configPath = path.resolve('data/config.json');
9 | let config: any;
10 | try {
11 | config= jsonfile.readFileSync(configPath);
12 | } catch (error) {
13 | console.error(`Error: data/config.json not found.`);
14 | process.exit(0);
15 | }
16 |
17 | let dialogManager: DialogManager;
18 |
19 | program
20 | .version('0.0.1')
21 | .description('An application testing dialog interactions')
22 | .option('-d, --debug', 'Turn on debug messages')
23 | .option('-c, --context ')
24 | .option('-r, --reset', 'Reset Users')
25 | .parse(process.argv);
26 |
27 | let context: string = 'launch'
28 | let contexts: string[] = [context];
29 |
30 | if (program.context) {
31 | context = program.context;
32 | contexts = [program.context];
33 | }
34 |
35 | dialogManager = new DialogManager();
36 | dialogManager.init({ debug: program.debug, config: config })
37 | .then(() => {
38 | if (program.reset) {
39 | console.log('>>>Cleaning up User nodes');
40 | dialogManager.deleteUsers();
41 | }
42 | console.log(`Ask a do-you-like question or say "[user] likes [something]".`)
43 | mainPromptLoop('>');
44 | });
45 |
46 | function mainPrompt(input: string) {
47 | let result: any = input;
48 | if (typeof input === 'string') {
49 | result = {
50 | type: 'command',
51 | name: 'mainInput',
52 | message: `${input}`,
53 | };
54 | }
55 | return result;
56 | }
57 |
58 | function mainPromptLoop(msg: string) {
59 | inquirer.prompt(mainPrompt(msg)).then((answers: any) => {
60 | const input = answers.mainInput;
61 | if (input === 'quit' || input === 'bye' || input === 'exit' || input === 'x' || input === 'q') {
62 | console.log('bye');
63 | process.exit(0);
64 | } else {
65 | dialogManager.ask(input, context)
66 | .then((result: string) => {
67 | console.log(`${result}`);
68 | mainPromptLoop('>');
69 | })
70 | .catch(() => {
71 | mainPromptLoop('Please try that again >');
72 | });
73 | }
74 | })
75 | .catch((error: any) => {
76 | console.log(error);
77 | process.exit(0);
78 | });
79 | }
80 |
--------------------------------------------------------------------------------
/src/neo4j/Neo4jController.ts:
--------------------------------------------------------------------------------
1 | const neo4j = require('neo4j-driver');
2 |
3 | import D3Helper from './helpers/D3Helper';
4 |
5 | export type Neo4jControllerOptions = {
6 | config: any;
7 | debug: boolean;
8 | }
9 |
10 | export default class Neo4jController {
11 |
12 | public driver: any;
13 |
14 | protected _debug: boolean;
15 |
16 | constructor(options: Neo4jControllerOptions) {
17 | this._debug = false;
18 | if (options.debug) {
19 | this._debug = true;
20 | }
21 | this.driver = neo4j.driver(options.config.url, neo4j.auth.basic(options.config.user, options.config.password));
22 | }
23 |
24 | call(cypher:string, params?: any): Promise {
25 | return new Promise((resolve, reject) => {
26 | let session: any = this.driver.session();
27 | session.run(cypher, params)
28 | .then(function (result: any) {
29 | session.close();
30 | resolve(result);
31 | })
32 | .catch(function (error: any) {
33 | reject(error);
34 | });
35 | });
36 | }
37 |
38 | parseCypherWithD3Helper(cypher:string, params?: any): Promise {
39 | return new Promise((resolve, reject) => {
40 | this.call(cypher, params)
41 | .then(response => {
42 | resolve(D3Helper.data(response));
43 | })
44 | .catch(error => {
45 | reject(error);
46 | });
47 | });
48 | }
49 |
50 | getNodesWithPropertyAndValue(property: string, value: string): Promise {
51 | return new Promise((resolve, reject) => {
52 | let cypher: string = `
53 | MATCH (n {${property}: "${value}"})-[r]-(p)
54 | return n,r,p
55 | `;
56 | this.call(cypher)
57 | .then(response => {
58 | resolve(D3Helper.data(response));
59 | })
60 | .catch(error => {
61 | reject(error);
62 | });
63 | });
64 | }
65 |
66 |
67 |
68 | updateNodeWithIdAndProperties(id: number, properties: any): Promise {
69 | return new Promise((resolve, reject) => {
70 | let cypher: string = `
71 | match (n) WHERE ID(n) = ${id}
72 | set n = { props }
73 | `;
74 | this.call(cypher, {props: properties})
75 | .then(response => {
76 | resolve(D3Helper.data(response));
77 | })
78 | .catch(error => {
79 | reject(error);
80 | });
81 | });
82 | }
83 |
84 | test() {
85 | this.call('MATCH (n) return n LIMIT 10')
86 | .then(result => {
87 | console.log(result);
88 | })
89 | .catch(error => {
90 | console.log(error);
91 | })
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/neo4j/helpers/BoltToD3.ts:
--------------------------------------------------------------------------------
1 | const neo4j = require('neo4j-driver');
2 |
3 | export default class BoltToD3 {
4 |
5 | // public neo4j: any;
6 | public nodeDict: any;
7 | public nodes: any[];
8 | public relationships: any[];
9 |
10 | constructor() {
11 | }
12 |
13 | isLink(field: any) {
14 | return field.start && field.end;
15 | }
16 |
17 | isNode(field: any) {
18 | return !this.isLink(field);
19 | }
20 |
21 | parse(boltResponse: any) {
22 | let i;
23 | this.nodeDict = {};
24 | this.nodes = [];
25 | this.relationships = [];
26 | for (i = 0; i < boltResponse.records.length; i++) {
27 | // console.log(`Parsing node ${i}`);
28 | this.parseFields(boltResponse.records[i]._fields);
29 | }
30 | return {
31 | nodes: this.nodes,
32 | links: this.relationships
33 | }
34 | };
35 |
36 | // {
37 | // "id": "3",
38 | // "labels": ["Address"],
39 | // "properties": {
40 | // "zipCode": "90210",
41 | // "country": "US",
42 | // "city": "Beverly Hills",
43 | // "state": "CA"
44 | // }
45 | // }
46 |
47 | makeNode(field: any) {
48 | let id = this.getId(field);
49 | // console.log(`makeNode: ${id}`, field);
50 | let props = this.convertNumberProps(field.properties);
51 | if (!this.nodeDict[id]) {
52 | this.nodes.push({
53 | id: `${id}`,
54 | labels: field.labels,
55 | properties: props,
56 | group: 1
57 | });
58 | this.nodeDict[id] = true;
59 | }
60 | return id;
61 | };
62 |
63 | // {
64 | // "id": "13",
65 | // "type": "HAS_EMAIL",
66 | // "startNode": "1",
67 | // "endNode": "14",
68 | // "properties": {},
69 | // "source": "1",
70 | // "target": "14",
71 | // "linknum": 1
72 | // }
73 |
74 | makeLink(field: any, id1: number, id2: number) {
75 | let id = this.getId(field);
76 | let props = this.convertNumberProps(field.properties);
77 | this.relationships.push({
78 | id: `${id}`,
79 | type: field.type,
80 | startNode: `${id1}`,
81 | endNode: `${id2}`,
82 | properties: props,
83 | source: `${id1}`,
84 | target: `${id2}`,
85 | value: 1,
86 | linknum: 1
87 | });
88 | };
89 |
90 | convertNumberProps(props: any) {
91 | for (let key in props) {
92 | let prop = props[key];
93 | if (neo4j.isInt(prop)) {
94 | props[key] = {
95 | raw: prop,
96 | converted: this.convertInt(prop)
97 | };
98 | }
99 | }
100 | return props;
101 | };
102 |
103 | convertInt(neoInt: any) {
104 | return neo4j.integer.inSafeRange(neoInt) ? neo4j.integer.toNumber(neoInt) : neoInt;
105 | };
106 |
107 | getId(field: any): number {
108 | return this.convertInt(field.identity);
109 | };
110 |
111 | // Beware: IDs/identities in neo4j are unique to their type (so a node and link could have the same ID)
112 | parseFields(fields: any[]) {
113 | // console.log(`parseFields: `, fields);
114 | let neoIdDict = {};
115 | // first we parse the nodes
116 | for (let i: number = 0; i < fields.length; i++) {
117 | let field = fields[i];
118 | let id = this.getId(field);
119 | let neoId = (this.isNode(field) ? 'node' : 'link') + field.identity.toString();
120 | neoIdDict[neoId] = this.isNode(field) ? this.makeNode(field) : field;
121 | }
122 | // console.log(neoIdDict);
123 | // now we have valid node IDs and a dictionary, we can parse the links
124 | for (let key in neoIdDict) {
125 | let field = neoIdDict[key];
126 | if (this.isLink(field)) {
127 | let start = this.convertInt(field.start);
128 | let end = this.convertInt(field.end);
129 | this.makeLink(field, start, end);
130 | }
131 | }
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/neo4j/helpers/D3Helper.ts:
--------------------------------------------------------------------------------
1 | import BoltToD3 from './BoltToD3';
2 |
3 | export default class PartnersGraphHelper {
4 |
5 | static data(cypherResponse: any): any[] {
6 | let result: any = {};
7 |
8 | // console.log(cypherResponse);
9 | let parser = new BoltToD3();
10 | result = parser.parse(cypherResponse)
11 |
12 | return result;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/neo4j/index.ts:
--------------------------------------------------------------------------------
1 | import Neo4jController from './Neo4jController';
2 | import D3Helper from './helpers/D3Helper';
3 |
4 | export default Neo4jController;
5 |
6 | export {
7 | D3Helper
8 | }
9 |
--------------------------------------------------------------------------------
/src/nlu/NLUController.ts:
--------------------------------------------------------------------------------
1 | export type NLUIntentAndEntities = {
2 | intent: string;
3 | intents: any;
4 | entities: any;
5 | response: any;
6 | }
7 |
8 | export type NLURequestOptions = {
9 | languageCode?: string;
10 | contexts?: string[];
11 | sessionId?: string;
12 | }
13 |
14 | export enum NLULanguageCode {
15 | en_US = 'en-US'
16 | }
17 |
18 | export type NLUControllerOptions = {
19 | config?: any;
20 | debug?: boolean;
21 | }
22 |
23 | export default abstract class NLUController {
24 |
25 | protected _debug: boolean;
26 |
27 | constructor(options: NLUControllerOptions = {}) {
28 | this._debug = false;
29 | if (options.debug) {
30 | this._debug = true;
31 | }
32 | }
33 |
34 | abstract init(): Promise;
35 |
36 | abstract set config(config: any);
37 |
38 | abstract call(query: string, languageCode: string, context: string, sessionId?: string): Promise;
39 |
40 | abstract getEntitiesWithResponse(response: any): any | undefined;
41 |
42 | abstract getIntentAndEntities(utterance: string, options?: NLURequestOptions): Promise;
43 | }
44 |
--------------------------------------------------------------------------------
/src/nlu/microsoft/LUISController.ts:
--------------------------------------------------------------------------------
1 | import NLUController, { NLUControllerOptions, NLUIntentAndEntities, NLURequestOptions, NLULanguageCode } from '../NLUController';
2 |
3 | const request = require('request');
4 | const querystring = require('querystring');
5 |
6 | export type LUISIntent = {
7 | [intent: string]: {
8 | score: number;
9 | }
10 | };
11 |
12 | export type LUISEntity = {
13 | [entity: string]: [];
14 | }
15 |
16 | export type LUISResponse = {
17 | query: string;
18 | prediction: any;
19 | intents: LUISIntent[];
20 | entities: LUISEntity;
21 | }
22 |
23 | export default class LUISController extends NLUController {
24 |
25 | public endpoint: string = '';
26 | public luisAppId: string = '';
27 | public subscriptionKey: string = '';
28 |
29 | private _config: any = {};
30 |
31 | constructor(options: NLUControllerOptions = {}) {
32 | super(options);
33 | this.config = options.config;
34 | }
35 |
36 | init(): Promise {
37 | return Promise.resolve();
38 | }
39 |
40 | set config(config: any) {
41 | if (config && config.Microsoft && (config.Microsoft.nluLUIS_endpoint || config.Microsoft.LuisEndpoint) && (config.Microsoft.nluLUIS_appId || config.Microsoft.LuisAppId) && (config.Microsoft.nluLUIS_subscriptionKey || config.Microsoft.LuisSubscriptionKey)) {
42 | this._config = config;
43 | this.endpoint = this._config.Microsoft.nluLUIS_endpoint || config.Microsoft.LuisEndpoint;
44 | this.luisAppId = this._config.Microsoft.nluLUIS_appId || config.Microsoft.LuisAppId;
45 | this.subscriptionKey = this._config.Microsoft.nluLUIS_subscriptionKey || config.Microsoft.LuisSubscriptionKey;
46 | } else {
47 | throw new Error(`LUISController: set config: error: incomplete config:`);
48 | }
49 | }
50 |
51 | call(query: string): Promise {
52 | let endpoint = this.endpoint;
53 | let luisAppId = this.luisAppId;
54 | let queryParams = {
55 | "subscription-key": this.subscriptionKey,
56 | "timezoneOffset": "0",
57 | "verbose": true,
58 | "query": query
59 | }
60 |
61 | let luisRequest = `${endpoint}luis/prediction/v3.0/apps/${luisAppId}/slots/production/predict?` + querystring.stringify(queryParams);
62 | if (this._debug) {
63 | console.log(luisRequest);
64 | }
65 | return new Promise((resolve, reject) => {
66 | request(luisRequest,
67 | ((error: string, response: any, body: any) => {
68 | if (error) {
69 | if (this._debug) {
70 | console.log(`LUISController: call: error:`, error, response);
71 | }
72 | reject(error);
73 | } else {
74 | let body_obj: any = JSON.parse(body);
75 | resolve(body_obj);
76 | }
77 | }));
78 | });
79 | }
80 |
81 | /*
82 | "entities": {
83 | "thing": [
84 | [
85 | "Mammal"
86 | ]
87 | ],
88 | "$instance": {
89 | "thing": [
90 | {
91 | "type": "thing",
92 | "text": "mammals",
93 | "startIndex": 12,
94 | "length": 7,
95 | "modelTypeId": 5,
96 | "modelType": "List Entity Extractor",
97 | "recognitionSources": [
98 | "model"
99 | ]
100 | }
101 | ]
102 | }
103 | }
104 | */
105 |
106 | getEntitiesWithResponse(response: LUISResponse): any {
107 | let entitiesObject: any = {
108 | user: 'Someone',
109 | userOriginal: 'Someone',
110 | thing: 'that',
111 | thingOriginal: 'that'
112 | };
113 |
114 | if (response.prediction && response.prediction.entities && response.prediction.entities['$instance']) {
115 | const entityKeys: string[] = Object.keys(response.prediction.entities);
116 | entityKeys.forEach((entityKey: string) => {
117 | if (entityKey !== '$instance') {
118 | if (this._debug) console.log(entityKey);
119 | const entity: string[] = response.prediction.entities[entityKey][0];
120 | entitiesObject[entityKey] = entity[0];
121 | }
122 | });
123 | const instanceKeys: string[] = Object.keys(response.prediction.entities['$instance']);
124 | instanceKeys.forEach((instanceKey: string) => {
125 | if (this._debug) console.log(instanceKey);
126 | const entityObjects: any[] = response.prediction.entities['$instance'][instanceKey];
127 | if (this._debug) console.log(`entityObject:`, entityObjects[0]);
128 | const originalKey: string = `${instanceKey}Original`;
129 | entitiesObject[originalKey] = entityObjects[0].text;
130 | });
131 | }
132 | return entitiesObject;
133 | }
134 |
135 | getIntentAndEntities(utterance: string, options?: NLURequestOptions): Promise {
136 | options = options || {};
137 | let defaultOptions: NLURequestOptions = {
138 | languageCode: NLULanguageCode.en_US,
139 | contexts: undefined,
140 | sessionId: undefined
141 | }
142 | options = Object.assign(defaultOptions, options);
143 |
144 | return new Promise((resolve, reject) => {
145 | this.call(utterance)
146 | .then((response: LUISResponse) => {
147 | let intentAndEntities: NLUIntentAndEntities = {
148 | intent: '',
149 | intents: response.prediction.intents,
150 | entities: this.getEntitiesWithResponse(response),
151 | response: response
152 | }
153 | if (response && response.prediction && response.prediction.topIntent) {
154 | intentAndEntities.intent = response.prediction.topIntent
155 | } else {
156 | if (this._debug) {
157 | console.log(`LUISController: getIntentAndEntities: unknown response format:`);
158 | }
159 | // console.log(response);
160 | }
161 | resolve(intentAndEntities);
162 | })
163 | .catch((err: any) => {
164 | reject(err);
165 | });
166 | });
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/src/nlu/node-nlp/NodeNlpController.test.ts:
--------------------------------------------------------------------------------
1 | import NodeNlpController from './NodeNlpController';
2 | import { NLUIntentAndEntities } from '../NLUController';
3 |
4 | let nodeNlpController: NodeNlpController;
5 |
6 | // Note: To regenerate the current model, delete (or rename) ../../../data/model.nlp
7 |
8 | test('NodeNlpController: instantiate and ready', async () => {
9 | nodeNlpController = new NodeNlpController();
10 | await nodeNlpController.init();
11 | expect(nodeNlpController.ready).toBeTruthy;
12 | });
13 |
14 | test('NodeNlpController: parse intent: launchUserLikes', async () => {
15 | nodeNlpController.getIntentAndEntities('Andrew likes penguins')
16 | .then((result: NLUIntentAndEntities) => {
17 | expect(result.intent).toEqual('launchUserLikes');
18 | });
19 | });
20 |
21 | /*
22 | {
23 | intent: 'launchUserLikes',
24 | entities:
25 | {
26 | user: 'Andrew',
27 | userOriginal: 'Andrew',
28 | thing: 'Penguins',
29 | thingOriginal: 'penguins',
30 | entities: [[Object], [Object]]
31 | }
32 | }
33 | */
34 |
--------------------------------------------------------------------------------
/src/nlu/node-nlp/NodeNlpController.ts:
--------------------------------------------------------------------------------
1 | import NLUController, { NLUControllerOptions, NLUIntentAndEntities, NLURequestOptions, NLULanguageCode } from '../NLUController';
2 |
3 | const path = require('path');
4 | const { NlpManager } = require('node-nlp');
5 |
6 | export type NodeNlpIntent = {
7 | intent: string;
8 | score: number;
9 | };
10 |
11 | export type NodeNlpEntity = {
12 | start: number;
13 | end: number;
14 | len: number;
15 | accuracy: number;
16 | entity: string;
17 | type: string;
18 | option: string;
19 | sourceText: string;
20 | utteranceText: string;
21 | resolution: any;
22 | }
23 |
24 | export type NodeNlpClassification = {
25 | intent: string;
26 | score: number;
27 | }
28 |
29 | export type NodeNlpSentiment = {
30 | score: number;
31 | numWords: number;
32 | numHits: number;
33 | average: number;
34 | type: string;
35 | locale: string;
36 | vote: string;
37 | }
38 |
39 | export type NodeNlpResponse = {
40 | locale: string;
41 | utterance: string;
42 | languageGuessed: boolean;
43 | localeIso2: string;
44 | language: string;
45 | classifications: NodeNlpClassification[];
46 | intent: string;
47 | score: number;
48 | domain: string;
49 | sourceEntities: string[];
50 | entities: NodeNlpEntity[];
51 | answers: string[];
52 | answer: undefined;
53 | actions: string[];
54 | sentiment: NodeNlpSentiment;
55 | }
56 |
57 | export default class NodeNlpController extends NLUController {
58 |
59 | private _classifier: any;
60 | private _ready: boolean;
61 |
62 | /**
63 | * @constructor
64 | */
65 | constructor(options: NLUControllerOptions = {}) {
66 | super(options);
67 | this._ready = false;
68 | }
69 |
70 | get ready(): boolean {
71 | return this._ready;
72 | }
73 |
74 | get classifier(): any {
75 | return this._classifier;
76 | }
77 |
78 | set config(config: any) {
79 | if (config) {
80 | //
81 | } else {
82 | //
83 | }
84 | }
85 |
86 | async init(modelPath: string = '') {
87 | this._classifier = new NlpManager({ languages: ['en'], autoSave: false, nlu: { log: false } });
88 | if (modelPath) {
89 | let inputPath: string = path.resolve(modelPath); // (__dirname, '../../../data/model.nlp');
90 | if (this._debug) {
91 | console.info(`NodeNlpController: init: loading: ${inputPath}`);
92 | }
93 | try {
94 | this._classifier.load(inputPath);
95 | this._ready = true;
96 | } catch (err) {
97 | if (this._debug) {
98 | console.info(`NodeNlpController: init: model NOT FOUND: ${inputPath}`);
99 | }
100 | }
101 | }
102 | if (!this._ready) {
103 | if (this._debug) {
104 | console.info(`NodeNlpController: generating model...`);
105 | }
106 | await this.generateModel();
107 | this._ready = true;
108 | }
109 | }
110 |
111 | generateModel(outputPath: string = '') {
112 | if (this._debug) {
113 | console.info(`NodeNlpController: generateModel: outputPath: ${outputPath}`);
114 | }
115 | return new Promise(async (resolve, reject) => {
116 |
117 | this._classifier.addNamedEntityText('user', 'Andrew', ['en'], ['Andrew', 'andrew', 'andy']);
118 | this._classifier.addNamedEntityText('user', 'Robert', ['en'], ['Robert', 'robert', 'bob']);
119 | this._classifier.addNamedEntityText('user', 'Jane', ['en'], ['Jane', 'jane']);
120 | this._classifier.addNamedEntityText('user', 'Eric', ['en'], ['Rick', 'rick', 'eric']);
121 | this._classifier.addNamedEntityText('user', 'Cynthia', ['en'], ['Cynthia', 'cynthia']);
122 |
123 | this._classifier.addNamedEntityText('thing', 'Penguin', ['en'], ['Penguins', 'penguins', 'penguin']);
124 | this._classifier.addNamedEntityText('thing', 'Mammal', ['en'], ['Mammals', 'mammals', 'mammal']);
125 | this._classifier.addNamedEntityText('thing', 'Bird', ['en'], ['Birds', 'birds', 'bird']);
126 | this._classifier.addNamedEntityText('thing', 'Whale', ['en'], ['Whales', 'whales', 'whale']);
127 |
128 | this._classifier.addNamedEntityText('thing', 'Albatross', ['en'], ['Albatrosses', 'albatross']);
129 | this._classifier.addNamedEntityText('thing', 'Armadillo', ['en'], ['Armadillos', 'armadillo']);
130 | this._classifier.addNamedEntityText('thing', 'Flamingo', ['en'], ['Flamingos', 'flamingo']);
131 | this._classifier.addNamedEntityText('thing', 'Fly', ['en'], ['Flies', 'fly']);
132 | this._classifier.addNamedEntityText('thing', 'Zebra', ['en'], ['Zebras', 'zebra']);
133 | this._classifier.addNamedEntityText('thing', 'Mammal', ['en'], ['Mammals', 'mammal']);
134 | this._classifier.addNamedEntityText('thing', 'Bird', ['en'], ['Birds', 'bird']);
135 | this._classifier.addNamedEntityText('thing', 'Bat', ['en'], ['Bats', 'bat']);
136 | this._classifier.addNamedEntityText('thing', 'Insect', ['en'], ['Insects', 'insect']);
137 | this._classifier.addNamedEntityText('thing', 'Butterfly', ['en'], ['Butterflies', 'butterfly']);
138 | this._classifier.addNamedEntityText('thing', 'Vertebrate', ['en'], ['Vertebrates', 'vertebrate']);
139 | this._classifier.addNamedEntityText('thing', 'Animal', ['en'], ['Animals', 'animal']);
140 | this._classifier.addNamedEntityText('thing', 'Invertebrate', ['en'], ['Invertebrates', 'invertebrate']);
141 |
142 | this._classifier.addDocument('en', "%user% likes %thing%", 'launchUserLikes');
143 | this._classifier.addDocument('en', "do you like %thing%", 'launchDoYouLike');
144 |
145 | await this._classifier.train();
146 | if (!outputPath) {
147 | outputPath = path.resolve(__dirname, '../../../data/model-new.nlp');
148 | if (this._debug) {
149 | console.info(`No model outputPath specified. Using: ${outputPath}`);
150 | }
151 | }
152 | this._classifier.save(outputPath);
153 | resolve(outputPath);
154 | });
155 | }
156 |
157 | call(query: string): Promise {
158 | return new Promise(async (resolve, reject) => {
159 | const response: NodeNlpResponse = await this._classifier.process(query);
160 | resolve(response);
161 | });
162 | }
163 |
164 | getEntitiesWithResponse(response: NodeNlpResponse): any {
165 | let entitiesObject: any = {
166 | user: 'Someone',
167 | userOriginal: 'Someone',
168 | thing: 'that',
169 | thingOriginal: 'that',
170 | entities: undefined
171 | };
172 |
173 | if (response && response.entities) {
174 | entitiesObject.entities = response.entities;
175 | response.entities.forEach((entity: NodeNlpEntity) => {
176 | if (entity.entity === 'user') {
177 | const user: string = entity.option || entity.utteranceText;
178 | entitiesObject.user = user;
179 | entitiesObject.userOriginal = entity.utteranceText;
180 | } else if (entity.entity === 'thing') {
181 | const thing: string = entity.option || entity.utteranceText;
182 | entitiesObject.thing = thing;
183 | entitiesObject.thingOriginal = entity.utteranceText;
184 | }
185 | });
186 | }
187 | return entitiesObject;
188 | }
189 |
190 | getIntentAndEntities(utterance: string, options?: NLURequestOptions): Promise {
191 | options = options || {};
192 | let defaultOptions: NLURequestOptions = {
193 | languageCode: NLULanguageCode.en_US,
194 | contexts: undefined,
195 | sessionId: undefined
196 | }
197 | options = Object.assign(defaultOptions, options);
198 |
199 | return new Promise((resolve, reject) => {
200 | let intentAndEntities: NLUIntentAndEntities = {
201 | intent: '',
202 | intents: undefined,
203 | entities: undefined,
204 | response: undefined
205 | }
206 | if (this._ready && utterance) {
207 | this.call(utterance)
208 | .then((response: NodeNlpResponse) => {
209 | if (response && response.intent) {
210 | intentAndEntities = {
211 | intent: response.intent,
212 | intents: undefined,
213 | entities: this.getEntitiesWithResponse(response),
214 | response: response,
215 | }
216 | }
217 | resolve(intentAndEntities);
218 | })
219 | .catch((err: any) => {
220 | reject(err);
221 | });
222 | } else {
223 | resolve(intentAndEntities);
224 | }
225 |
226 | });
227 | }
228 | }
229 |
--------------------------------------------------------------------------------
/tools/test-dialog-manager.ts:
--------------------------------------------------------------------------------
1 | import DialogManager from '../src/DialogManager';
2 | const program = require('commander');
3 |
4 | program
5 | .version('0.0.1')
6 | .description('An application for testing the DialogManager class')
7 | .option('-q, --query ', 'The query to test')
8 | .option('-c, --context ')
9 | .option('-n, --nlu ', 'node-nlp, luis')
10 | .parse(process.argv);
11 |
12 | let context: string = 'launch';
13 | let contexts: string[] = [context];
14 | let query: string = 'do you like penguins';
15 | let nluType: string = 'node-nlp';
16 |
17 | if (program.context) {
18 | contexts = [program.context];
19 | }
20 | if (program.query) {
21 | // console.log(program.query);
22 | query = program.query;
23 | }
24 | if (program.nlu) {
25 | nluType = program.nlu;
26 | }
27 |
28 | const dialogManager = new DialogManager();
29 | dialogManager.init({debug: true, nluType: nluType})
30 | .then(() => {
31 | dialogManager.ask(query, context)
32 | .then((result: string) => {
33 | console.log(`result:\n`, result);
34 | })
35 | .catch((err: any) => {
36 | console.log(`error:\n`, err);
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/tools/test-luis-controller.ts:
--------------------------------------------------------------------------------
1 | import LUISController from '../src/nlu/microsoft/LUISController';
2 |
3 | const path = require('path');
4 | const jsonfile = require('jsonfile');
5 |
6 | const doTestLuisNlu = () => {
7 |
8 | const configPath = path.resolve('data/config.json');
9 | let config: any;
10 | try {
11 | config= jsonfile.readFileSync(configPath);
12 | } catch (error) {
13 | console.error(`Error: data/config.json not found.`);
14 | process.exit(0);
15 | }
16 |
17 | const luisConfig: any = {
18 | Microsoft: {
19 | nluLUIS_endpoint: config.luis.endpoint,
20 | nluLUIS_appId: config.luis.appId,
21 | nluLUIS_subscriptionKey: config.luis.subscriptionKey,
22 | }
23 | }
24 | const luisController = new LUISController({ config: luisConfig, debug: true });
25 |
26 | let timeLog = {
27 | timeStart: new Date().getTime(),
28 | complete: 0,
29 | cloudLatency: 0,
30 | }
31 | luisController.getIntentAndEntities('do you like mammals')
32 | .then((intentAndEntities: any) => {
33 | timeLog.complete = new Date().getTime();
34 | timeLog.cloudLatency = timeLog.complete - timeLog.timeStart;
35 | console.log(`NLUIntentAndEntities: `, JSON.stringify(intentAndEntities, null, 2));
36 | console.log(`timeLog:`, JSON.stringify(timeLog, null, 2));
37 | })
38 | .catch((error: any) => {
39 | console.log(error);
40 | });
41 | }
42 |
43 | doTestLuisNlu();
44 |
--------------------------------------------------------------------------------
/tools/test-neo4j.ts:
--------------------------------------------------------------------------------
1 | import Neo4jController, {D3Helper} from '../src/neo4j';
2 |
3 | const program = require('commander');
4 | const prettyjson = require('prettyjson');
5 |
6 | let neo4jController = new Neo4jController();
7 |
8 | program
9 | .version('0.0.1')
10 | .description('An application testing neo4j cyphers')
11 | .option('-c, --cypher ', 'Specify the cypher to test')
12 | .option('--d3', 'Parse results with D3Helper')
13 | .parse(process.argv);
14 |
15 | let cypher: string = 'match (n) return n LIMIT 25';
16 | if (program.cypher) {
17 | console.log(program.cypher);
18 | cypher = program.cypher;
19 | }
20 |
21 | let d3HelperFlag: boolean = false;
22 | if (program.d3) {
23 | console.log(program.d3);
24 | d3HelperFlag = true;
25 | }
26 |
27 | if (d3HelperFlag) {
28 | neo4jController.parseCypherWithD3Helper(cypher)
29 | .then((response: string) => {
30 | let output: string = prettyjson.render(response, {});
31 | console.log(output);
32 | })
33 | .catch((err) => {
34 | console.log(`ERROR: neo4jController\n`, err)
35 | });
36 | } else {
37 | neo4jController.call(cypher)
38 | .then((response: string) => {
39 | let output: string = prettyjson.render(response, {});
40 | console.log(output);
41 | })
42 | .catch((err) => {
43 | console.log(`ERROR: neo4jController\n`, err)
44 | });
45 | }
46 |
--------------------------------------------------------------------------------
/tools/test-node-nlp.ts:
--------------------------------------------------------------------------------
1 | const { NlpManager } = require('node-nlp');
2 |
3 | const manager = new NlpManager({ languages: ['en'] });
4 | manager.addNamedEntityText(
5 | 'hero',
6 | 'spiderman',
7 | ['en'],
8 | ['Spiderman', 'Spider-man'],
9 | );
10 | manager.addNamedEntityText(
11 | 'hero',
12 | 'iron man',
13 | ['en'],
14 | ['iron man', 'iron-man'],
15 | );
16 | manager.addNamedEntityText('hero', 'thor', ['en'], ['Thor']);
17 | manager.addNamedEntityText(
18 | 'food',
19 | 'burguer',
20 | ['en'],
21 | ['Burguer', 'Hamburguer'],
22 | );
23 | manager.addNamedEntityText('food', 'pizza', ['en'], ['pizza']);
24 | manager.addNamedEntityText('food', 'pasta', ['en'], ['Pasta', 'spaghetti']);
25 | manager.addDocument('en', 'I saw %hero% eating %food%', 'sawhero');
26 | manager.addDocument(
27 | 'en',
28 | 'I have seen %hero%, he was eating %food%',
29 | 'sawhero',
30 | );
31 | manager.addDocument('en', 'I want to eat %food%', 'wanteat');
32 | manager.train()
33 | .then(() => {
34 | manager
35 | .process('I saw spiderman eating spaghetti today in the city!')
36 | .then((result: any) => console.log(result));
37 | });
38 |
39 | // { locale: 'en',
40 | // localeIso2: 'en',
41 | // language: 'English',
42 | // utterance: 'I saw spiderman eating spaghetti today in the city!',
43 | // classification:
44 | // [ { label: 'sawhero', value: 0.9920519933583061 },
45 | // { label: 'wanteat', value: 0.00794800664169383 } ],
46 | // intent: 'sawhero',
47 | // score: 0.9920519933583061,
48 | // entities:
49 | // [ { start: 6,
50 | // end: 15,
51 | // levenshtein: 0,
52 | // accuracy: 1,
53 | // option: 'spiderman',
54 | // sourceText: 'Spiderman',
55 | // entity: 'hero',
56 | // utteranceText: 'spiderman' },
57 | // { start: 23,
58 | // end: 32,
59 | // levenshtein: 0,
60 | // accuracy: 1,
61 | // option: 'pasta',
62 | // sourceText: 'spaghetti',
63 | // entity: 'food',
64 | // utteranceText: 'spaghetti' } ],
65 | // sentiment:
66 | // { score: 0.708,
67 | // comparative: 0.07866666666666666,
68 | // vote: 'positive',
69 | // numWords: 9,
70 | // numHits: 2,
71 | // type: 'senticon',
72 | // language: 'en' } }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./dist/",
4 | "sourceMap": true,
5 | "noImplicitAny": true,
6 | "suppressImplicitAnyIndexErrors": true,
7 | "module": "commonjs",
8 | "target": "es6",
9 | "jsx": "react"
10 | },
11 | "include": [
12 | "./src/**/*"
13 | ],
14 | "exclude": [
15 | "./hide/**/*"
16 | ]
17 | }
--------------------------------------------------------------------------------