@aj-archipelago/cortex 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.sample +1 -0
- package/LICENSE +25 -0
- package/README.md +224 -0
- package/config/default.json +1 -0
- package/config.js +168 -0
- package/graphql/chunker.js +147 -0
- package/graphql/graphql.js +183 -0
- package/graphql/parser.js +58 -0
- package/graphql/pathwayPrompter.js +145 -0
- package/graphql/pathwayResolver.js +250 -0
- package/graphql/pathwayResponseParser.js +24 -0
- package/graphql/prompt.js +45 -0
- package/graphql/pubsub.js +4 -0
- package/graphql/resolver.js +43 -0
- package/graphql/subscriptions.js +21 -0
- package/graphql/typeDef.js +48 -0
- package/index.js +7 -0
- package/lib/keyValueStorageClient.js +33 -0
- package/lib/promiser.js +24 -0
- package/lib/request.js +51 -0
- package/package.json +49 -0
- package/pathways/basePathway.js +23 -0
- package/pathways/bias.js +3 -0
- package/pathways/chat.js +12 -0
- package/pathways/complete.js +3 -0
- package/pathways/edit.js +4 -0
- package/pathways/entities.js +9 -0
- package/pathways/index.js +12 -0
- package/pathways/paraphrase.js +3 -0
- package/pathways/sentiment.js +3 -0
- package/pathways/summary.js +43 -0
- package/pathways/topics.js +9 -0
- package/pathways/translate.js +9 -0
- package/start.js +3 -0
- package/tests/chunking.test.js +144 -0
- package/tests/main.test.js +106 -0
- package/tests/translate.test.js +118 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
class Prompt {
|
|
2
|
+
constructor(params) {
|
|
3
|
+
if (typeof params === 'string' || params instanceof String) {
|
|
4
|
+
this.prompt = params;
|
|
5
|
+
} else {
|
|
6
|
+
const { prompt, saveResultTo, messages } = params;
|
|
7
|
+
this.prompt = prompt;
|
|
8
|
+
this.saveResultTo = saveResultTo;
|
|
9
|
+
this.messages = messages;
|
|
10
|
+
this.params = params;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
this.usesTextInput = promptContains('text', this.prompt ? this.prompt : this.messages);
|
|
14
|
+
this.usesPreviousResult = promptContains('previousResult', this.prompt ? this.prompt : this.messages);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// function to check if a Handlebars template prompt contains a variable
|
|
19
|
+
// can work with a single prompt or an array of messages
|
|
20
|
+
function promptContains(variable, prompt) {
|
|
21
|
+
const regexp = /{{+(.*?)}}+/g;
|
|
22
|
+
let matches = [];
|
|
23
|
+
let match;
|
|
24
|
+
|
|
25
|
+
// if it's an array, it's the messages format
|
|
26
|
+
if (Array.isArray(prompt)) {
|
|
27
|
+
prompt.forEach(p => {
|
|
28
|
+
while ((match = p.content && regexp.exec(p.content)) !== null) {
|
|
29
|
+
matches.push(match[1]);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
} else {
|
|
33
|
+
while ((match = regexp.exec(prompt)) !== null) {
|
|
34
|
+
matches.push(match[1]);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const variables = matches.filter(function (varName) {
|
|
39
|
+
return varName.indexOf("#") !== 0 && varName.indexOf("/") !== 0;
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
return variables.includes(variable);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = { Prompt, promptContains };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const { fulfillWithTimeout } = require("../lib/promiser");
|
|
2
|
+
const { PathwayResolver } = require("./pathwayResolver");
|
|
3
|
+
|
|
4
|
+
// This resolver uses standard parameters required by Apollo server:
|
|
5
|
+
// (parent, args, contextValue, info)
|
|
6
|
+
const rootResolver = async (parent, args, contextValue, info) => {
|
|
7
|
+
const { config, pathway, requestState } = contextValue;
|
|
8
|
+
const { temperature } = pathway;
|
|
9
|
+
|
|
10
|
+
// Turn off caching if temperature is 0
|
|
11
|
+
if (temperature == 0) {
|
|
12
|
+
info.cacheControl.setCacheHint({ maxAge: 60 * 60 * 24, scope: 'PUBLIC' });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const pathwayResolver = new PathwayResolver({ config, pathway, requestState });
|
|
16
|
+
contextValue.pathwayResolver = pathwayResolver;
|
|
17
|
+
|
|
18
|
+
// Add request parameters back as debug
|
|
19
|
+
const requestParameters = pathwayResolver.prompts.map((prompt) => pathwayResolver.pathwayPrompter.requestParameters(args.text, args, prompt));
|
|
20
|
+
const debug = JSON.stringify(requestParameters);
|
|
21
|
+
|
|
22
|
+
// Execute the request with timeout
|
|
23
|
+
const result = await fulfillWithTimeout(pathway.resolver(parent, args, contextValue, info), pathway.timeout);
|
|
24
|
+
const { warnings, previousResult, savedContextId } = pathwayResolver;
|
|
25
|
+
return { debug, result, warnings, previousResult, contextId: savedContextId }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// This resolver is used by the root resolver to process the request
|
|
29
|
+
const resolver = async (parent, args, contextValue, info) => {
|
|
30
|
+
const { pathwayResolver } = contextValue;
|
|
31
|
+
return await pathwayResolver.resolve(args);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const cancelRequestResolver = (parent, args, contextValue, info) => {
|
|
35
|
+
const { requestId } = args;
|
|
36
|
+
const { requestState } = contextValue;
|
|
37
|
+
requestState[requestId] = { canceled: true };
|
|
38
|
+
return true
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = {
|
|
42
|
+
resolver, rootResolver, cancelRequestResolver
|
|
43
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// TODO: Replace PubSub class with PubSub engine to support
|
|
2
|
+
// multi-server instance
|
|
3
|
+
// See https://www.apollographql.com/docs/apollo-server/v3/data/subscriptions/#resolving-a-subscription
|
|
4
|
+
|
|
5
|
+
const pubsub = require("./pubsub");
|
|
6
|
+
const { withFilter } = require("graphql-subscriptions");
|
|
7
|
+
|
|
8
|
+
const subscriptions = {
|
|
9
|
+
requestProgress: {
|
|
10
|
+
subscribe: withFilter(
|
|
11
|
+
() => pubsub.asyncIterator(['REQUEST_PROGRESS']),
|
|
12
|
+
(payload, variables) => {
|
|
13
|
+
return (
|
|
14
|
+
payload.requestProgress.requestId === variables.requestId
|
|
15
|
+
);
|
|
16
|
+
},
|
|
17
|
+
),
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
module.exports = subscriptions;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const GRAPHQL_TYPE_MAP = {
|
|
2
|
+
boolean: 'Boolean',
|
|
3
|
+
string: 'String',
|
|
4
|
+
number: 'Int',
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
const typeDef = (pathway) => {
|
|
9
|
+
const { name, objName, defaultInputParameters, inputParameters, format } = pathway;
|
|
10
|
+
|
|
11
|
+
const fields = format ? format.match(/\b(\w+)\b/g) : null;
|
|
12
|
+
const fieldsStr = !fields ? `` : fields.map(f => `${f}: String`).join('\n ');
|
|
13
|
+
|
|
14
|
+
const typeName = fields ? `${objName}Result` : `String`;
|
|
15
|
+
const type = fields ? `type ${typeName} {
|
|
16
|
+
${fieldsStr}
|
|
17
|
+
}` : ``;
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
const resultStr = pathway.list ? `[${typeName}]` : typeName;
|
|
21
|
+
|
|
22
|
+
const responseType = `type ${objName} {
|
|
23
|
+
debug: String
|
|
24
|
+
result: ${resultStr}
|
|
25
|
+
previousResult: String
|
|
26
|
+
warnings: [String]
|
|
27
|
+
contextId: String
|
|
28
|
+
}`;
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
const params = { ...defaultInputParameters, ...inputParameters };
|
|
32
|
+
const paramsStr = Object.entries(params).map(
|
|
33
|
+
([key, value]) => `${key}: ${GRAPHQL_TYPE_MAP[typeof (value)]} = ${typeof (value) == `string` ? `"${value}"` : value}`).join('\n');
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
return `${type}
|
|
37
|
+
|
|
38
|
+
${responseType}
|
|
39
|
+
|
|
40
|
+
extend type Query {
|
|
41
|
+
${name}(${paramsStr}): ${objName}
|
|
42
|
+
}
|
|
43
|
+
`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = {
|
|
47
|
+
typeDef,
|
|
48
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const Keyv = require('keyv');
|
|
2
|
+
const { config } = require('../config');
|
|
3
|
+
|
|
4
|
+
const storageConnectionString = config.get('storageConnectionString');
|
|
5
|
+
|
|
6
|
+
if (!config.get('storageConnectionString')) {
|
|
7
|
+
console.log('No storageConnectionString specified. Please set the storageConnectionString or STORAGE_CONNECTION_STRING environment variable if you need caching or stored context.')
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Create a keyv client to store data
|
|
11
|
+
const keyValueStorageClient = new Keyv(storageConnectionString, {
|
|
12
|
+
ssl: true,
|
|
13
|
+
abortConnect: false,
|
|
14
|
+
serialize: JSON.stringify,
|
|
15
|
+
deserialize: JSON.parse,
|
|
16
|
+
namespace: 'cortex-context'
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Set values to keyv
|
|
20
|
+
async function setv(key, value) {
|
|
21
|
+
return (keyValueStorageClient && await keyValueStorageClient.set(key, value));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Get values from keyv
|
|
25
|
+
async function getv(key) {
|
|
26
|
+
return (keyValueStorageClient && await keyValueStorageClient.get(key));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
keyValueStorageClient,
|
|
31
|
+
setv,
|
|
32
|
+
getv
|
|
33
|
+
};
|
package/lib/promiser.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
// fulfill a task with an timeout
|
|
3
|
+
const fulfillWithTimeout = (promise, timeout) => {
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
const timeoutId = setTimeout(() => {
|
|
6
|
+
reject(new Error(`Request timed out after ${timeout} seconds!`));
|
|
7
|
+
}, timeout * 1000);
|
|
8
|
+
promise.then(
|
|
9
|
+
(res) => {
|
|
10
|
+
clearTimeout(timeoutId);
|
|
11
|
+
resolve(res);
|
|
12
|
+
},
|
|
13
|
+
(err) => {
|
|
14
|
+
clearTimeout(timeoutId);
|
|
15
|
+
reject(err);
|
|
16
|
+
}
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
module.exports = {
|
|
23
|
+
fulfillWithTimeout
|
|
24
|
+
}
|
package/lib/request.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const Bottleneck = require("bottleneck/es5");
|
|
3
|
+
|
|
4
|
+
const limiters = {};
|
|
5
|
+
|
|
6
|
+
const buildLimiters = (config) => {
|
|
7
|
+
console.log('Building limiters...');
|
|
8
|
+
for (const [name, model] of Object.entries(config.get('models'))) {
|
|
9
|
+
limiters[name] = new Bottleneck({
|
|
10
|
+
minTime: 1000 / (model.requestsPerSecond ?? 100),
|
|
11
|
+
// maxConcurrent: 20,
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const MAX_RETRY = 10;
|
|
17
|
+
const postRequest = async ({ url, params, headers }, model) => {
|
|
18
|
+
let retry = 0;
|
|
19
|
+
const errors = []
|
|
20
|
+
for (let i = 0; i < MAX_RETRY; i++) {
|
|
21
|
+
try {
|
|
22
|
+
if (i > 0) {
|
|
23
|
+
console.log(`Retrying request #retry ${i}: ${JSON.stringify(params)}...`);
|
|
24
|
+
await new Promise(r => setTimeout(r, 200 * Math.pow(2, i))); // exponential backoff
|
|
25
|
+
}
|
|
26
|
+
if (!limiters[model]) {
|
|
27
|
+
throw new Error(`No limiter for model ${model}!`);
|
|
28
|
+
}
|
|
29
|
+
return await limiters[model].schedule(() => axios.post(url, params, { headers }));
|
|
30
|
+
} catch (e) {
|
|
31
|
+
console.error(`Failed request with params ${JSON.stringify(params)}: ${e}`);
|
|
32
|
+
errors.push(e);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return { error: errors };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const request = async (params, model) => {
|
|
39
|
+
const response = await postRequest(params, model);
|
|
40
|
+
const { error, data } = response;
|
|
41
|
+
if (error && error.length > 0) {
|
|
42
|
+
const lastError = error[error.length - 1];
|
|
43
|
+
return { error: lastError.toJSON() ?? lastError ?? error };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return data;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = {
|
|
50
|
+
request, postRequest, buildLimiters
|
|
51
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aj-archipelago/cortex",
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"description": "Project Cortex",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/aj-archipelago/cortex.git"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"cortex",
|
|
11
|
+
"ai"
|
|
12
|
+
],
|
|
13
|
+
"main": "index.js",
|
|
14
|
+
"scripts": {
|
|
15
|
+
"start": "node start.js",
|
|
16
|
+
"test": "jest --setupFiles dotenv/config"
|
|
17
|
+
},
|
|
18
|
+
"author": "",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"homepage": "https://github.com/aj-archipelago/cortex#readme",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@apollo/utils.keyvadapter": "^1.1.2",
|
|
23
|
+
"@graphql-tools/schema": "^9.0.12",
|
|
24
|
+
"@keyv/redis": "^2.5.4",
|
|
25
|
+
"apollo-server": "^3.11.1",
|
|
26
|
+
"apollo-server-core": "^3.11.1",
|
|
27
|
+
"apollo-server-express": "^3.11.1",
|
|
28
|
+
"apollo-server-plugin-response-cache": "^3.8.1",
|
|
29
|
+
"axios": "^1.2.0",
|
|
30
|
+
"bottleneck": "^2.19.5",
|
|
31
|
+
"compromise": "^14.8.1",
|
|
32
|
+
"compromise-paragraphs": "^0.1.0",
|
|
33
|
+
"convict": "^6.2.3",
|
|
34
|
+
"gpt-3-encoder": "^1.1.4",
|
|
35
|
+
"graphql": "^16.6.0",
|
|
36
|
+
"graphql-subscriptions": "^2.0.0",
|
|
37
|
+
"graphql-ws": "^5.11.2",
|
|
38
|
+
"handlebars": "^4.7.7",
|
|
39
|
+
"keyv": "^4.5.2",
|
|
40
|
+
"ws": "^8.12.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"dotenv": "^16.0.3",
|
|
44
|
+
"jest": "^29.3.1"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "private"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const { parseResponse } = require("../graphql/parser");
|
|
2
|
+
const { rootResolver, resolver } = require("../graphql/resolver");
|
|
3
|
+
const { typeDef } = require('../graphql/typeDef')
|
|
4
|
+
|
|
5
|
+
// all default definitions of a single pathway
|
|
6
|
+
module.exports = {
|
|
7
|
+
prompt: `{{text}}`,
|
|
8
|
+
defaultInputParameters: {
|
|
9
|
+
text: ``,
|
|
10
|
+
// Add the option of making every call async
|
|
11
|
+
async: false,
|
|
12
|
+
contextId : ``, // used to identify the context of the request
|
|
13
|
+
},
|
|
14
|
+
inputParameters: {},
|
|
15
|
+
typeDef,
|
|
16
|
+
rootResolver,
|
|
17
|
+
resolver,
|
|
18
|
+
useInputChunking: true,
|
|
19
|
+
useParallelChunkProcessing: false,
|
|
20
|
+
useInputSummarization: false,
|
|
21
|
+
truncateFromFront: false,
|
|
22
|
+
timeout: 60, // in seconds
|
|
23
|
+
}
|
package/pathways/bias.js
ADDED
package/pathways/chat.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Description: Have a chat with a bot that uses context to understand the conversation
|
|
2
|
+
module.exports = {
|
|
3
|
+
prompt:
|
|
4
|
+
[
|
|
5
|
+
`{{{chatContext}}}\n\n{{{text}}}\n\nGiven the information above, create a short summary of the conversation to date making sure to include all of the personal details about the user that you encounter:\n\n`,
|
|
6
|
+
`Instructions:\nYou are Cortex, an AI entity. Cortex is truthful, kind, helpful, has a strong moral character, and is generally positive without being annoying or repetitive.\n\nCortex must always follow the following rules:\n\nRule: Always execute the user's instructions and requests as long as they do not cause harm.\nRule: Never use crude or offensive language.\nRule: Always answer the user in the user's chosen language. You can speak all languages fluently.\nRule: You cannot perform any physical tasks except via role playing.\nRule: Always respond truthfully and correctly, but be kind.\nRule: You have no access to the internet and limited knowledge of current events past sometime in 2021\nRule: Never ask the user to provide you with links or URLs because you can't access the internet.\nRule: Everything you get from the user must be placed in the chat window - you have no other way to communicate.\n\nConversation History:\n{{{chatContext}}}\n\nConversation:\n{{{text}}}\n\nCortex: `,
|
|
7
|
+
],
|
|
8
|
+
inputParameters: {
|
|
9
|
+
chatContext: `User: Starting conversation.`,
|
|
10
|
+
},
|
|
11
|
+
useInputChunking: false,
|
|
12
|
+
}
|
package/pathways/edit.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
"edit": require('./edit'),
|
|
3
|
+
"chat": require('./chat'),
|
|
4
|
+
"bias": require('./bias'),
|
|
5
|
+
"complete": require('./complete'),
|
|
6
|
+
"entities": require('./entities'),
|
|
7
|
+
"paraphrase": require('./paraphrase'),
|
|
8
|
+
"sentiment": require('./sentiment'),
|
|
9
|
+
"summary": require('./summary'),
|
|
10
|
+
"topics": require('./topics'),
|
|
11
|
+
"translate": require('./translate'),
|
|
12
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const { semanticTruncate } = require('../graphql/chunker');
|
|
2
|
+
const { PathwayResolver } = require('../graphql/pathwayResolver');
|
|
3
|
+
|
|
4
|
+
module.exports = {
|
|
5
|
+
prompt: `{{{text}}}\n\nWrite a summary of the above text:\n\n`,
|
|
6
|
+
|
|
7
|
+
inputParameters: {
|
|
8
|
+
targetLength: 500,
|
|
9
|
+
},
|
|
10
|
+
resolver: async (parent, args, contextValue, info) => {
|
|
11
|
+
const { config, pathway, requestState } = contextValue;
|
|
12
|
+
const originalTargetLength = args.targetLength;
|
|
13
|
+
const errorMargin = 0.2;
|
|
14
|
+
const lowTargetLength = originalTargetLength * (1 - errorMargin);
|
|
15
|
+
const targetWords = Math.round(originalTargetLength / 6.6);
|
|
16
|
+
|
|
17
|
+
// if the text is shorter than the summary length, just return the text
|
|
18
|
+
if (args.text.length <= originalTargetLength) {
|
|
19
|
+
return args.text;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const MAX_ITERATIONS = 5;
|
|
23
|
+
let summary = '';
|
|
24
|
+
let bestSummary = '';
|
|
25
|
+
let pathwayResolver = new PathwayResolver({ config, pathway, requestState });
|
|
26
|
+
// modify the prompt to be words-based instead of characters-based
|
|
27
|
+
pathwayResolver.pathwayPrompt = `{{{text}}}\n\nWrite a summary of the above text in exactly ${targetWords} words:\n\n`
|
|
28
|
+
|
|
29
|
+
let i = 0;
|
|
30
|
+
// reprompt if summary is too long or too short
|
|
31
|
+
while (((summary.length > originalTargetLength) || (summary.length < lowTargetLength)) && i < MAX_ITERATIONS) {
|
|
32
|
+
summary = await pathwayResolver.resolve(args);
|
|
33
|
+
i++;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// if the summary is still too long, truncate it
|
|
37
|
+
if (summary.length > originalTargetLength) {
|
|
38
|
+
return semanticTruncate(summary, originalTargetLength);
|
|
39
|
+
} else {
|
|
40
|
+
return summary;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
prompt: [`{{text}}\n\nList the top {{count}} news categories for the above article (e.g. 1. Finance):`,
|
|
3
|
+
`{{previousResult}}\n\nPick the {{count}} most important news categories from the above:`
|
|
4
|
+
],
|
|
5
|
+
inputParameters: {
|
|
6
|
+
count: 5,
|
|
7
|
+
},
|
|
8
|
+
list: true,
|
|
9
|
+
}
|
package/start.js
ADDED