Skip to content

Commit 3be7e40

Browse files
authored
Add KVASS AI functionality (#20)
1 parent efa27da commit 3be7e40

10 files changed

Lines changed: 422 additions & 5 deletions

File tree

.travis.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,11 @@ node_js:
44
cache:
55
directories:
66
- node_modules
7-
before_install:
8-
- HASH_BEFORE=`sha1sum ./dist/main.bundle.js`
97
script:
108
- npm run lint
119
- npm run test
1210
- npm run webpack
1311
- npm run codacy
14-
- HASH_AFTER=`sha1sum ./dist/main.bundle.js`
15-
- if [ "$HASH_AFTER" != "$HASH_BEFORE" ]; then exit 1; fi
1612
notifications:
1713
slack:
1814
secure: CxYFqdiYHRL545MGDDDsP4N8D5qCY0OOmWZFsjo4VYl4gsNuo887TIxTr+6s8DTVlerQoMAYYD0VrG8RgeX2s2D5WLIbcvAsJ0JoL7ahCk5B0MQB9ewNReG7TKh5to+pKhT0TlugbyPKuHiNhLfoAs+UH7mjg6i41f7LhweVlq8STU3+dnSz9vQ1pXS9O83QNthCSsWoM36aB0gCldqKvJ0i4jrYmyMS100oLJVnNTdbtYunwFgONafKq3CWjNvuvFdd0qDxRlP065RLQMrkBV2vQvfdAIfpE79gVgNbI2eId/G2+Ct5HHtOcI4GjFEvhrewfMKUyWj6ssP96XGpHpuzoFz0h8n1J2V+cZSW/4rmyMDWA579lwrvc15OYfasDuS2IKTZ3+5J45BlJr4g4dylx0+eif5jDw+KxrV3h7KwaMwWrpaUqwzIWb4GueuUt8f6Gsl1r/Pag/hgqGkmM40rpvG04XaOkh4ICW7TFz9Az8lfpImoU1gBtpFj1lSknasZa6WhBcEeGko+qZZvNZIQ10xmJSNt8vWmO+K+Gb2bUA9NNo5WUPdJtoWEiPuAaCLg3tqmBPh1/tkhqjqJ+FjD4YoVKc2e0P+7UJmbDPH2EfckU55TsprO8JJ1T57YrkSrbNE0DpZwtOYOjIhf0MmNOOqNQkhoVasvF9Y0kDk=

dist/main.bundle.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/collection/aiModel.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const Components = require('./components');
2+
const {
3+
create,
4+
get,
5+
getAll,
6+
} = require('../utils/restFunctions');
7+
8+
class AIModel extends Components {
9+
constructor(props) {
10+
super(props);
11+
this.apiPath = 'ai/models';
12+
this.create = create.bind(this);
13+
this.get = get.bind(this);
14+
this.getAll = getAll.bind(this);
15+
}
16+
17+
train({ urlParam, json = false }, done) {
18+
return this.simpleQuery({
19+
type: 'post', id: this.id, urlParam, resource: 'train', json,
20+
}, done);
21+
}
22+
23+
getRecommendations({ body, urlParams }, done) {
24+
return this.simpleQuery({
25+
type: 'post', id: this.id || '', resource: 'invoke', body, urlParams, ResConstructor: null,
26+
}, done);
27+
}
28+
}
29+
30+
module.exports = AIModel;

src/main.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const request = require('./utils/request');
22
const Error = require('./utils/error');
33

4+
const AIModel = require('./collection/aiModel');
45
const Company = require('./collection/company');
56
const Event = require('./collection/event');
67
const Order = require('./collection/order');
@@ -34,6 +35,7 @@ class Kvass {
3435

3536
request(this.endpoint, this._constructHeaders());
3637

38+
this.aiModel = props => new AIModel(props);
3739
this.company = props => new Company(props);
3840
this.event = props => new Event(props);
3941
this.order = props => new Order(props);

test/collection/aiModel.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/* global it, describe, require, console */
2+
const Kvass = require('../../src/main.js');
3+
const assert = require('assert');
4+
5+
const request = require('../../src/utils/superagent');
6+
const mock = require('superagent-mocker')(request);
7+
8+
const modelCreatedFile = require('../fetchmock/modelCreated.json');
9+
const modelTrainingFile = require('../fetchmock/modelTraining.json');
10+
const modelReadyFile = require('../fetchmock/modelReady.json');
11+
const modelsFile = require('../fetchmock/models.json');
12+
const recommendationsFile = require('../fetchmock/recommendations.json');
13+
14+
const endpoint = 'https://example.com/';
15+
const sa = new Kvass({ apiKey: 'dummy', bearerToken: 'dummy', endpoint });
16+
let url;
17+
18+
describe('AI related tests', () => {
19+
beforeEach(() => {
20+
// Guarantee each test knows exactly which routes are defined
21+
mock.clearRoutes();
22+
});
23+
24+
describe('GET ai/models', () => {
25+
it('Should return a list of models', (done) => {
26+
url = `${endpoint}ai/models`;
27+
mock.get(url, () => ({ body: modelsFile, ok: true }));
28+
sa.aiModel().getAll({}, (err, models) => {
29+
if (err) throw err;
30+
assert.ok(Array.isArray(models));
31+
done();
32+
});
33+
});
34+
});
35+
36+
describe('POST ai/models', () => {
37+
it('Should create a new ai model based on type, source and destination', (done) => {
38+
url = `${endpoint}ai/models`;
39+
mock.post(url, () => ({ body: modelCreatedFile, ok: true }));
40+
sa.aiModel().create({ model_type: 'content_recommender', source: 'product', destination: 'product' }, (err, model) => {
41+
if (err) throw err;
42+
assert.ok(model.constructor.name === 'AIModel');
43+
assert.ok(model.training_status === 'CREATED');
44+
done();
45+
});
46+
});
47+
});
48+
49+
describe('GET ai/models/<modelId>', () => {
50+
it('Should return details about one specific model', (done) => {
51+
url = `${endpoint}ai/models/:modelId:`;
52+
mock.get(url, () => ({ body: modelReadyFile, ok: true }));
53+
sa.aiModel(':modelId:').get({}, (err, model) => {
54+
if (err) throw err;
55+
assert.ok(model.constructor.name === 'AIModel');
56+
assert.ok(model.training_status === 'READY');
57+
done();
58+
});
59+
});
60+
});
61+
62+
63+
describe('POST ai/models/<modelId>/train', () => {
64+
it('Should train an existing ai model', (done) => {
65+
url = `${endpoint}ai/models/:modelId:/train`;
66+
mock.post(url, () => ({ body: modelTrainingFile, ok: true }));
67+
sa.aiModel(':modelId:').train({}, (err, model) => {
68+
if (err) throw err;
69+
assert.ok(model.constructor.name === 'AIModel');
70+
assert.ok(model.training_status === 'TRAINING');
71+
done();
72+
});
73+
});
74+
});
75+
76+
describe('POST ai/models/<modelId>/invoke', () => {
77+
it('Should return recommendation for a given source based on a specific model', (done) => {
78+
url = `${endpoint}ai/models/:modelId/invoke`;
79+
mock.post(url, () => ({ body: recommendationsFile, ok: true }));
80+
sa.aiModel(':modelId:').getRecommendations({ source_id: '5aec176d1f7cdc0008848f87', size: 4 }, (err, recommendations) => {
81+
if (err) throw err;
82+
assert.ok(Array.isArray(recommendations.response));
83+
done();
84+
});
85+
});
86+
});
87+
88+
describe('POST ai/models/invoke', () => {
89+
it('Should return recommendation for a given source based on set model type, source and destination', (done) => {
90+
url = `${endpoint}ai/models/invoke`;
91+
mock.post(url, () => ({ body: recommendationsFile, ok: true }));
92+
sa.aiModel().getRecommendations({
93+
size: 4, model_type: 'content_recommender', source: 'product', destination: 'product', source_id: '5aec176d1f7cdc0008848f87',
94+
}, (err, recommendations) => {
95+
if (err) throw err;
96+
assert.ok(Array.isArray(recommendations.response));
97+
done();
98+
});
99+
});
100+
});
101+
});

test/fetchmock/modelCreated.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"model_type": "item_similarity_recommender",
3+
"training_status": "CREATED",
4+
"modified": {
5+
"$date": 1528108844887
6+
},
7+
"model_evaluations": [],
8+
"features": [],
9+
"deleted": false,
10+
"created": {
11+
"$date": 1528108844887
12+
},
13+
"company": {
14+
"$oid": "5ad0953c9a5d09000629d6c0"
15+
},
16+
"active": true,
17+
"_id": {
18+
"$oid": "5b15172ca5a05a000919c936"
19+
},
20+
"destination": "product",
21+
"version": 0,
22+
"source": "user",
23+
"_cls": "DataScienceBaseModel.RecommendationModel.ItemSimilarityRecommender"
24+
}

test/fetchmock/modelReady.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"model_type": "item_similarity_recommender",
3+
"training_status": "READY",
4+
"modified": {
5+
"$date": 1528108844887
6+
},
7+
"model_evaluations": [],
8+
"features": [],
9+
"deleted": false,
10+
"created": {
11+
"$date": 1528108844887
12+
},
13+
"company": {
14+
"$oid": "5ad0953c9a5d09000629d6c0"
15+
},
16+
"active": true,
17+
"_id": {
18+
"$oid": "5b15172ca5a05a000919c936"
19+
},
20+
"destination": "product",
21+
"version": 0,
22+
"source": "user",
23+
"_cls": "DataScienceBaseModel.RecommendationModel.ItemSimilarityRecommender"
24+
}

test/fetchmock/modelTraining.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"model_type": "item_similarity_recommender",
3+
"training_status": "TRAINING",
4+
"modified": {
5+
"$date": 1528108844887
6+
},
7+
"model_evaluations": [],
8+
"features": [],
9+
"deleted": false,
10+
"created": {
11+
"$date": 1528108844887
12+
},
13+
"company": {
14+
"$oid": "5ad0953c9a5d09000629d6c0"
15+
},
16+
"active": true,
17+
"_id": {
18+
"$oid": "5b15172ca5a05a000919c936"
19+
},
20+
"destination": "product",
21+
"version": 0,
22+
"source": "user",
23+
"_cls": "DataScienceBaseModel.RecommendationModel.ItemSimilarityRecommender"
24+
}

test/fetchmock/models.json

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
[
2+
{
3+
"version": 0,
4+
"active": true,
5+
"_cls": "DataScienceBaseModel.RecommendationModel.ItemSimilarityRecommender",
6+
"_id": {
7+
"$oid": "5b15172ca5a05a000919c936"
8+
},
9+
"created": {
10+
"$date": 1528108844887
11+
},
12+
"features": [],
13+
"company": {
14+
"$oid": "5ad0953c9a5d09000629d6c0"
15+
},
16+
"deleted": false,
17+
"modified": {
18+
"$date": 1528108844887
19+
},
20+
"training_status": "CREATED",
21+
"model_evaluations": [],
22+
"destination": "product",
23+
"source": "user"
24+
},
25+
{
26+
"version": 0,
27+
"active": true,
28+
"_cls": "DataScienceBaseModel.RecommendationModel.ContentRecommender",
29+
"_id": {
30+
"$oid": "5b14ecfaa5a05a000c404496"
31+
},
32+
"created": {
33+
"$date": 1528098042493
34+
},
35+
"features": [
36+
"tags"
37+
],
38+
"company": {
39+
"$oid": "5ad0953c9a5d09000629d6c0"
40+
},
41+
"deleted": false,
42+
"modified": {
43+
"$date": 1528098042502
44+
},
45+
"training_status": "CREATED",
46+
"model_evaluations": [],
47+
"destination": "product",
48+
"source": "user"
49+
}
50+
]

0 commit comments

Comments
 (0)