Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions examples/chatapp-brain/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default {
clearMocks: true,
preset: 'ts-jest',
coverageDirectory: 'coverage',
coverageProvider: 'v8',
testEnvironment: 'node',
};
5,547 changes: 4,969 additions & 578 deletions examples/chatapp-brain/package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion examples/chatapp-brain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"author": "SberDevices Frontend Team <sberdevices.frontend@gmail.com>",
"main": "index.js",
"scripts": {
"start": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' ./src/index.ts"
"start": "nodemon -r dotenv/config --watch 'src/**/*.ts' --exec 'ts-node' ./src/index.ts",
"test": "jest"
},
"description": "",
"dependencies": {
Expand All @@ -18,8 +19,11 @@
},
"devDependencies": {
"@types/express": "4.17.11",
"@types/jest": "26.0.23",
"@types/node": "15.0.1",
"jest": "26.6.3",
"nodemon": "2.0.7",
"ts-jest": "26.5.5",
"ts-node": "9.1.1",
"typescript": "4.2.4"
}
Expand Down
45 changes: 4 additions & 41 deletions examples/chatapp-brain/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,16 @@
import express from 'express';
import { config as dotenv } from 'dotenv';
import { createIntents, createSystemScenario, createUserScenario, createMatchers } from '@salutejs/scenario';
import { SmartAppBrainRecognizer } from '@salutejs/recognizer-smartapp-brain';
import { SaluteMemoryStorage } from '@salutejs/storage-adapter-memory';

import { saluteExpressMiddleware } from './middleware';
import * as dictionary from './system.i18n';
import model from './intents.json';
import { handleNlpRequest } from './scenario';

dotenv();

const port = process.env.PORT || 3000;
const app = express();
app.use(express.json());

const { intent } = createMatchers();

app.post(
'/app-connector',
saluteExpressMiddleware({
intents: createIntents(model.intents),
recognizer: new SmartAppBrainRecognizer(),
systemScenario: createSystemScenario({
RUN_APP: ({ req, res }) => {
const keyset = req.i18n(dictionary);
res.setPronounceText(keyset('Привет'));
},
NO_MATCH: ({ req, res }) => {
const keyset = req.i18n(dictionary);
res.setPronounceText(keyset('404'));
},
}),
userScenario: createUserScenario({
calc: {
match: intent('/sum'),
handle: ({ req, res }) => {
const keyset = req.i18n(dictionary);
const { num1, num2 } = req.variables;

res.setPronounceText(
keyset('{result}. Это было легко!', {
result: Number(num1) + Number(num2),
}),
);
},
},
}),
storage: new SaluteMemoryStorage(),
}),
);
app.post('/app-connector', async ({ body }, httpRes) => {
httpRes.json(await handleNlpRequest(body));
});

app.listen(port, () => console.log(`Salute on ${port}`));
3 changes: 2 additions & 1 deletion examples/chatapp-brain/src/intents.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"/sum": {
"matchers": [
{ "type": "phrase", "rule": "Сложи @duckling.number и @duckling.number" },
{ "type": "phrase", "rule": "Сколько будет @duckling.number и @duckling.number" }
{ "type": "phrase", "rule": "Сколько будет @duckling.number и @duckling.number" },
{ "type": "phrase", "rule": "@duckling.number" }
],
"variables": {
"num1": {
Expand Down
17 changes: 0 additions & 17 deletions examples/chatapp-brain/src/middleware.ts

This file was deleted.

61 changes: 61 additions & 0 deletions examples/chatapp-brain/src/scenario.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { SmartAppBrainRecognizer } from '@salutejs/recognizer-smartapp-brain';
import {
createIntents,
createMatchers,
createSaluteRequest,
createSaluteResponse,
createScenarioWalker,
createSystemScenario,
createUserScenario,
NLPRequest,
NLPResponse,
} from '@salutejs/scenario';
import { SaluteMemoryStorage } from '@salutejs/storage-adapter-memory';

import * as dictionary from './system.i18n';
import model from './intents.json';

const { intent } = createMatchers();
const storage = new SaluteMemoryStorage();

const scenarioWalker = createScenarioWalker({
intents: createIntents(model.intents),
recognizer: new SmartAppBrainRecognizer(),
systemScenario: createSystemScenario({
RUN_APP: ({ req, res }) => {
const keyset = req.i18n(dictionary);
res.setPronounceText(keyset('Привет'));
},
NO_MATCH: ({ req, res }) => {
const keyset = req.i18n(dictionary);
res.setPronounceText(keyset('404'));
},
}),
userScenario: createUserScenario({
calc: {
match: intent('/sum'),
handle: ({ req, res }) => {
const keyset = req.i18n(dictionary);
const { num1, num2 } = req.variables;

res.setPronounceText(
keyset('{result}. Это было легко!', {
result: Number(num1) + Number(num2),
}),
);
},
},
}),
});

export const handleNlpRequest = async (request: NLPRequest): Promise<NLPResponse> => {
const req = createSaluteRequest(request);
const res = createSaluteResponse(request);
const id = request.uuid.userId;
const session = await storage.resolve(id);

await scenarioWalker({ req, res, session });
await storage.save({ id, session });

return res.message;
};
173 changes: 173 additions & 0 deletions examples/chatapp-brain/src/scenarion.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import { Message, NLPRequestMTS, NLPResponseATU, Recognizer } from '@salutejs/scenario';

import { handleNlpRequest } from './scenario';

const sessionId = '9ec25354-275e-4426-9f75-7ca1e8095da7';

const createRequestWithMessage = (message: Pick<Message, 'original_text'>): NLPRequestMTS => ({
messageId: 0,
messageName: 'MESSAGE_TO_SKILL',
payload: {
app_info: {
projectId: 'test',
applicationId: 'test',
version: 'test',
appversionId: 'test',
frontendType: 'CHAT_APP',
},
character: {
id: 'sber',
name: 'Сбер',
gender: 'male',
appeal: 'official',
},
message: {
original_text: message.original_text,
normalized_text: '',
asr_normalized_message: '',
human_normalized_text: '',
human_normalized_text_with_anaphora: '',
tokenized_elements_list: [],
entities: { CCY_TOKEN: [], MONEY_TOKEN: [], NUM_TOKEN: [] },
},
intent: 'item_selector',
meta: {},
},
sessionId,
uuid: {
userChannel: 'test',
sub: 'test',
userId: 'test',
},
});

expect.extend({
toBeEqualResponse(received: NLPResponseATU, expected: NLPResponseATU) {
expect(expected.payload.pronounceText).toEqual(received.payload.pronounceText);
expect(expected.payload.items).toEqual(received.payload.items);
expect(expected.payload.suggestions).toEqual(received.payload.suggestions);
return { pass: true, message: () => '' };
},
});

let inference: Recognizer['inference'] = () => {
throw new Error('Not implemented');
};

jest.mock('@salutejs/recognizer-smartapp-brain', () => ({
__esModule: true, // this property makes it work
// eslint-disable-next-line object-shorthand, func-names
SmartAppBrainRecognizer: function () {
return {
inference: (params) => inference(params),
};
},
}));

describe('scenario', () => {
beforeEach(() => {
inference = () => {
throw new Error('Not implemented');
};
});

test('Простая фраза', async () => {
const phrase = 'Сложи 3 и 5';

inference = ({ req }) => {
if (req.message?.original_text === phrase) {
req.setInference({
variants: [
{
intent: {
id: 0,
path: '/sum',
slots: [
{ name: 'num1', entity: 'number', required: true, array: false, prompts: [] },
{ name: 'num2', entity: 'number', required: true, array: false, prompts: [] },
],
},
confidence: 1,
slots: [
{ name: 'num1', value: '3', array: false },
{ name: 'num2', value: '5', array: false },
],
},
],
});
}
};

const res = await handleNlpRequest(
createRequestWithMessage({
original_text: phrase,
}),
);
// @ts-ignore
expect(res).toBeEqualResponse({
payload: { pronounceText: '8. Это было легко!', items: [] },
} as NLPResponseATU);
});

test('Дозапрос числа', async () => {
const phrase1 = 'Сложи 4';
const phrase2 = '5';

inference = ({ req }) => {
if (req.message?.original_text === phrase1) {
req.setInference({
variants: [
{
intent: {
id: 0,
path: '/sum',
slots: [{ name: 'num1', entity: 'number', required: true, array: false, prompts: [] }],
},
confidence: 1,
slots: [{ name: 'num1', value: '4', array: false }],
},
],
});
} else if (req.message?.original_text === phrase2) {
req.setInference({
variants: [
{
intent: {
id: 0,
path: '/sum',
slots: [{ name: 'num1', entity: 'number', required: true, array: false, prompts: [] }],
},
confidence: 1,
slots: [{ name: 'num1', value: '5', array: false }],
},
],
});
}
};

const res1 = await handleNlpRequest(
createRequestWithMessage({
original_text: phrase1,
}),
);

// @ts-ignore
expect(res1).toBeEqualResponse({
payload: {
pronounceText: 'А какое второе число?',
items: [{ bubble: { text: 'А какое второе число?', expand_policy: 'auto_expand', markdown: false } }],
},
} as NLPResponseATU);

const res2 = await handleNlpRequest(
createRequestWithMessage({
original_text: phrase2,
}),
);

// @ts-ignore
expect(res2).toBeEqualResponse({
payload: { pronounceText: '9. Это было легко!', items: [] },
} as NLPResponseATU);
});
});