@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.
@@ -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,4 @@
1
+ const { PubSub } = require('graphql-subscriptions');
2
+ const pubsub = new PubSub();
3
+
4
+ module.exports = pubsub;
@@ -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,7 @@
1
+ const { config } = require('./config');
2
+ const { build } = require('./graphql/graphql');
3
+
4
+ module.exports = (configParams) => {
5
+ configParams && config.load(configParams);
6
+ return build(config);
7
+ }
@@ -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
+ };
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ prompt: `{{text}}\n\nIs the above text written objectively? Why or why not, explain with details:\n`
3
+ }
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ prompt: `Continue and complete the following:\n\n{{text}}`
3
+ }
@@ -0,0 +1,4 @@
1
+ module.exports = {
2
+ temperature: 0,
3
+ prompt: `Correct all spelling and grammar errors in the input text.\n\nInput:\n{{text}}\n\nOutput:\n`
4
+ }
@@ -0,0 +1,9 @@
1
+ module.exports = {
2
+ temperature: 0,
3
+ prompt: `{{text}}\n\nList the top {{count}} entities and their definitions for the above in the format {{format}}:`,
4
+ format: `(name: definition)`,
5
+ inputParameters: {
6
+ count: 5,
7
+ },
8
+ list: true,
9
+ }
@@ -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,3 @@
1
+ module.exports = {
2
+ prompt: `Rewrite the following:\n\n{{text}}`
3
+ }
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ prompt: `How does this article make you feel?\n\n{{text}}`,
3
+ }
@@ -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
+ }
@@ -0,0 +1,9 @@
1
+ // Description: Translate a text from one language to another
2
+
3
+ module.exports = {
4
+ temperature: 0,
5
+ prompt: `Translate the following text to {{to}}:\n\nOriginal Language:\n{{{text}}}\n\n{{to}}:\n`,
6
+ inputParameters: {
7
+ to: `Arabic`,
8
+ },
9
+ }
package/start.js ADDED
@@ -0,0 +1,3 @@
1
+ const { startServer } = require('./index')();
2
+
3
+ startServer && startServer();