@aj-archipelago/cortex 0.0.6 → 0.0.8

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/README.md CHANGED
@@ -1,14 +1,13 @@
1
1
  # Cortex
2
- Cortex is an extensible and open-source caching GraphQL API that provides an abstraction layer for interacting with modern natural language AI models. It simplifies and accelerates the task of querying NL AI models (e.g. LLMs like GPT-3) by providing a structured interface to the largely unstructured world of AI prompting.
3
-
2
+ Cortex simplifies and accelerates the process of creating applications that harness the power of modern AI models like chatGPT and GPT-4 by providing a structured interface (GraphQL or REST) to a powerful prompt execution environment. This enables complex augmented prompting and abstracts away most of the complexity of managing model connections like chunking input, rate limiting, formatting output, caching, and handling errors.
4
3
  ## Why build Cortex?
5
- Using modern NL AI models can be complex and costly. Most models require precisely formatted, carefully engineered and sequenced prompts to produce consistent results, and the responses are typically largely unstructured text without validation or formatting. Additionally, these models are evolving rapidly, are typically costly and slow to query and implement hard request size and rate restrictions that need to be carefully navigated for optimum throughput. Cortex offers a solution to these problems and provides a simple and extensible package for interacting with NL AI models.
6
-
4
+ Modern AI models are transformational, but a number of complexities emerge when developers start using them to deliver application-ready functions. Most models require precisely formatted, carefully engineered and sequenced prompts to produce consistent results, and the responses are typically largely unstructured text without validation or formatting. Additionally, these models are evolving rapidly, are typically costly and slow to query and implement hard request size and rate restrictions that need to be carefully navigated for optimum throughput. Cortex offers a solution to these problems and provides a simple and extensible package for interacting with NL AI models.
7
5
  ## Features
8
6
 
9
- * Simple architecture to build functional endpoints (called `pathways`), that implement common NL AI tasks. Included core pathways include chat, summarization, translation, paraphrasing, completion, spelling and grammar correction, entity extraction, topic classification, sentiment analysis, and bias analysis.
10
- * Allows for building multi-model, multi-vendor, and model-agnostic pathways (choose the right model or combination of models for the job, implement redundancy)
11
- * Easy, templatized prompt definition with flexible support for most prompt engineering techniques and strategies ranging from simple, single prompts to complex prompt chains with context continuity.
7
+ * Simple architecture to build custom functional endpoints (called `pathways`), that implement common NL AI tasks. Default pathways include chat, summarization, translation, paraphrasing, completion, spelling and grammar correction, entity extraction, sentiment analysis, and bias analysis.
8
+ * Allows for building multi-model, multi-vendor, and model-agnostic pathways (choose the right model or combination of models for the job, implement redundancy) with built-in support for OpenAI GPT-3, GPT-3.5 (chatGPT), and GPT-4 models - both from OpenAI directly and through Azure OpenAI, OpenAI Whisper, Azure Translator, and more.
9
+ * Easy, templatized prompt definition with flexible support for most prompt engineering techniques and strategies ranging from simple single prompts to complex custom prompt chains with context continuity.
10
+ * Built in support for long-running, asynchronous operations with progress updates or streaming responses
12
11
  * Integrated context persistence: have your pathways "remember" whatever you want and use it on the next request to the model
13
12
  * Automatic traffic management and content optimization: configurable model-specific input chunking, request parallelization, rate limiting, and chunked response aggregation
14
13
  * Extensible parsing and validation of input data - protect your model calls from bad inputs or filter prompt injection attempts.
@@ -20,15 +19,16 @@ In order to use Cortex, you must first have a working Node.js environment. The v
20
19
  ## Quick Start
21
20
  ```sh
22
21
  git clone git@github.com:aj-archipelago/cortex.git
22
+ cd cortex
23
23
  npm install
24
24
  export OPENAI_API_KEY=<your key>
25
25
  npm start
26
26
  ```
27
27
  Yup, that's it, at least in the simplest possible case. That will get you access to all of the built in pathways.
28
- ## Using Cortex
29
- Cortex speaks GraphQL, and by default it enables the GraphQL playground. If you're just using default options, that's at [http://localhost:4000/graphql](http://localhost:4000/graphql). From there you can begin making requests and test out the pathways (listed under Query) to your heart's content.
28
+ ## Connecting Applications to Cortex
29
+ Cortex speaks GraphQL and by default it enables the GraphQL playground. If you're just using default options, that's at [http://localhost:4000/graphql](http://localhost:4000/graphql). From there you can begin making requests and test out the pathways (listed under Query) to your heart's content. If GraphQL isn't your thing or if you have a client that would rather have REST that's fine - Cortex speaks REST as well.
30
30
 
31
- When it's time to talk to Cortex from an app, that's simple as well - you just use standard GraphQL client conventions:
31
+ Connecting an application to Cortex using GraphQL is simple too:
32
32
 
33
33
  ```js
34
34
  import { useApolloClient, gql } from "@apollo/client"
@@ -52,85 +52,113 @@ apolloClient.query({
52
52
  // catch errors
53
53
  })
54
54
  ```
55
- ## Default Queries (pathways)
56
- Below are the default pathways provided with Cortex. These can be used as is, overridden, or disabled via configuration. For documentation on each one including input and output parameters, please look at them in the GraphQL Playground.
57
- - `bias`: Identifies and measures any potential biases in a text
58
- - `chat`: Enables users to have a conversation with the chatbot
59
- - `complete`: Autocompletes words or phrases based on user input
60
- - `edit`: Checks for and suggests corrections for spelling and grammar errors
61
- - `entities`: Identifies and extracts important entities from text
62
- - `paraphrase`: Suggests alternative phrasing for text
63
- - `sentiment`: Analyzes and identifies the overall sentiment or mood of a text
64
- - `summary`: Condenses long texts or articles into shorter summaries
65
- - `topics`: Analyzes and identifies the main topic or subject of a text
66
- - `translate`: Translates text from one language to another
67
- ## Extensibility
68
- Cortex is designed to be highly extensible. This allows you to customize the API to fit your needs. You can add new features, modify existing features, and even add integrations with other APIs and models.
69
- ## Configuration
70
- Configuration of Cortex is done via a [convict](https://github.com/mozilla/node-convict/tree/master) object called `config`. The `config` object is built by combining the default values and any values specified in a configuration file or environment variables. The environment variables take precedence over the values in the configuration file. Below are the configurable properties and their defaults:
71
-
72
- - `basePathwayPath`: The path to the base pathway (the prototype pathway) for Cortex. Default properties for the pathway are set from their values in this basePathway. Default is path.join(__dirname, 'pathways', 'basePathway.js').
73
- - `corePathwaysPath`: The path to the core pathways for Cortex. Default is path.join(__dirname, 'pathways').
74
- - `cortexConfigFile`: The path to a JSON configuration file for the project. Default is null. The value can be set using the `CORTEX_CONFIG_FILE` environment variable.
75
- - `defaultModelName`: The default model name for the project. Default is null. The value can be set using the `DEFAULT_MODEL_NAME` environment variable.
76
- - `enableCache`: A boolean flag indicating whether to enable caching. Default is true. The value can be set using the `CORTEX_ENABLE_CACHE` environment variable.
77
- - `models`: An object containing the different models used by the project. The value can be set using the `CORTEX_MODELS` environment variable. Cortex is model and vendor agnostic - you can use this config to set up models of any type from any vendor.
78
- - `openaiApiKey`: The API key used for accessing the OpenAI API. This is sensitive information and has no default value. The value can be set using the `OPENAI_API_KEY` environment variable.
79
- - `openaiApiUrl`: The URL used for accessing the OpenAI API. Default is https://api.openai.com/v1/completions. The value can be set using the `OPENAI_API_URL` environment variable.
80
- - `openaiDefaultModel`: The default model name used for the OpenAI API. Default is text-davinci-003. The value can be set using the `OPENAI_DEFAULT_MODEL` environment variable.
81
- - `pathways`: An object containing pathways for the project. The default is an empty object that is filled in during the `buildPathways` step.
82
- - `pathwaysPath`: The path to custom pathways for the project. Default is null.
83
- - `PORT`: The port number for the Cortex server. Default is 4000. The value can be set using the `CORTEX_PORT` environment variable.
84
- - `storageConnectionString`: The connection string used for accessing storage. This is sensitive information and has no default value. The value can be set using the `STORAGE_CONNECTION_STRING` environment variable.
55
+ ## Cortex Pathways: Supercharged Prompts
56
+ Pathways are a core concept in Cortex. Each pathway is a single JavaScript file that encapsulates the data and logic needed to define a functional API endpoint. When the client makes a request via the API, one or more pathways are executed and the result is sent back to the client. Pathways can be very simple:
57
+ ```js
58
+ module.exports = {
59
+ prompt: `{{text}}\n\nRewrite the above using British English spelling:`
60
+ }
61
+ ```
62
+ The real power of Cortex starts to show as the pathways get more complex. This pathway, for example, uses a three-part sequential prompt to ensure that specific people and place names are correctly translated:
63
+ ```js
64
+ prompt:
65
+ [
66
+ `{{{text}}}\nCopy the names of all people and places exactly from this document in the language above:\n`,
67
+ `Original Language:\n{{{previousResult}}}\n\n{{to}}:\n`,
68
+ `Entities in the document:\n\n{{{previousResult}}}\n\nDocument:\n{{{text}}}\nRewrite the document in {{to}}. If the document is already in {{to}}, copy it exactly below:\n`
69
+ ]
70
+ ```
71
+ Cortex pathway prompt enhancements include:
72
+ * **Templatized prompt definition**: Pathways allow for easy and flexible prompt definition using Handlebars templating. This makes it simple to create and modify prompts using variables and context from the application as well as extensible internal functions provided by Cortex.
73
+ * **Multi-step prompt sequences**: Pathways support complex prompt chains with context continuity. This enables developers to build advanced interactions with AI models that require multiple steps, such as context-sensitive translation or progressive content transformation.
74
+ * **Integrated context persistence**: Cortex pathways can "remember" context across multiple requests, allowing for more seamless and context-aware interactions with AI models.
75
+ * **Automatic content optimization**: Pathways handle input chunking, request parallelization, rate limiting, and chunked response aggregation, optimizing throughput and efficiency when interacting with AI models.
76
+ * **Built-in input and output processing**: Cortex provides extensible input validation, output parsing, and validation functions to ensure that the data sent to and received from AI models is well-formatted and useful for the application.
85
77
 
86
- The `buildPathways` function takes the config object and builds the `pathways` object by loading the core pathways and any custom pathways specified in the `pathwaysPath` property of the config object. The function returns the `pathways` object.
78
+ ### Pathway Development
79
+ To add a new pathway to Cortex, you create a new JavaScript file and define the prompts, properties, and functions that implement the desired functionality. Cortex provides defaults for almost everything, so in the simplest case a pathway can really just consist of a string prompt like the spelling example above. You can then save this file in the `pathways` directory in your Cortex project and it will be picked up and made available as a GraphQL query.
87
80
 
88
- The `buildModels` function takes the `config` object and builds the `models` object by compiling handlebars templates for each model specified in the `models` property of the config object. The function returns the `models` object.
81
+ ### Prompt
82
+ When you define a new pathway, you need to at least specify a prompt that will be passed to the model for processing. In the simplest case, a prompt is really just a string, but the prompt is polymorphic - it can be a string or an object that contains information for the model API that you wish to call. Prompts can also be an array of strings or an array of objects for sequential operations. In this way Cortex aims to support the most simple to advanced prompting scenarios.
89
83
 
90
- The `config` object can be used to access configuration values throughout the project. For example, to get the port number for the server, use
91
84
  ```js
92
- config.get('PORT')
85
+ // a prompt can be a string
86
+ prompt: `{{{text}}}\nCopy the names of all people and places exactly from this document in the language above:\n`
87
+
88
+ // or an array of strings
89
+ prompt: [
90
+ `{{{text}}}\nCopy the names of all people and places exactly from this document in the language above:\n`,
91
+ `Original Language:\n{{{previousResult}}}\n\n{{to}}:\n`,
92
+ `Entities in the document:\n\n{{{previousResult}}}\n\nDocument:\n{{{text}}}\nRewrite the document in {{to}}. If the document is already in {{to}}, copy it exactly below:\n`
93
+ ]
94
+
95
+ // or an array of one or more Prompt objects
96
+ // as you can see below a Prompt object can also have a messages array, which is how you can
97
+ // express your prompts for chat-style interfaces
98
+ prompt: [
99
+ new Prompt({ messages: [
100
+ {"role": "system", "content": "Assistant is a highly skilled multilingual translator for a prestigious news agency. When the user posts any text in any language, assistant will create a translation of that text in {{to}}. Assistant will produce only the translation and no additional notes or commentary."},
101
+ {"role": "user", "content": "{{{text}}}"}
102
+ ]}),
103
+ ]
93
104
  ```
94
- ## Pathways
95
- Pathways are a core concept in Cortex. They let users define new functionality and extend the platform. Each pathway is a single JavaScript file that encapsulates the data and logic needed to define a functional API endpoint. Effectively, pathways define how a request from a client is processed when sent to Cortex.
96
105
 
97
- To add a new pathway to Cortex, you create a new JavaScript file and define the prompts, properties, and functions that define the function you want to implement. Cortex provides defaults for almost everything, so in the simplest case a pathway can really just consist of a string prompt. You can then save this file in the `pathways` directory in your Cortex project and it will be picked up and made available as a GraphQL query.
106
+ If a prompt is an array, the individual prompts in the array will be executed sequentially by the Cortex prompt execution engine. The execution engine deals with all of the complexities of chunking input content and executing the sequence of prompts against those chunks in a way that optimizes the performance and ensures the the integrity of the pathway logic.
98
107
 
99
- Example of a very simple pathway (`spelling.js`):
108
+ If you look closely at the examples above, you'll notice embedded parameters like `{{text}}`. In Cortex, all prompt strings are actually [Handlebars](https://handlebarsjs.com/) templates. So in this case, that parameter will be replaced before prompt execution with the incoming query variable called `text`. You can refer to almost any pathway parameter or system property in the prompt definition and it will be replaced before execution.
109
+ ### Parameters
110
+ Pathways support an arbitrary number of input parameters. These are defined in the pathway like this:
100
111
  ```js
101
112
  module.exports = {
102
- prompt: `{{text}}\n\nRewrite the above using British English spelling:`
113
+ prompt:
114
+ [
115
+ `{{{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`,
116
+ `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: `,
117
+ ],
118
+ inputParameters: {
119
+ chatContext: `User: Starting conversation.`,
120
+ },
121
+ useInputChunking: false,
103
122
  }
104
123
  ```
105
- ### Prompt
106
- When you define a new pathway, you need to at least specify a prompt that will be passed to the model for processing. In the simplest case, a prompt is really just a string, but the prompt is polymorphic - it can be a string or an object that contains information for the model API that you wish to call. Prompts can also be an array of strings or an array of objects for sequential operations. In this way Cortex aims to support the most simple to advanced prompting scenarios.
107
-
108
- In the above example, the pathway simply prompts the model to rewrite some text using British English spelling. If you look closely, you'll notice the embedded `{{text}}` parameter. In Cortex, all prompt strings are actually [Handlebars](https://handlebarsjs.com/) templates. So in this case, that parameter will be replaced before prompt execution with the incoming query variable called `text`. You can refer to almost any pathway parameter or system property in the prompt definition and it will be replaced before execution.
124
+ The input parameters are added to the GraphQL Query and the values are made available to the prompt when it is compiled and executed.
109
125
 
110
126
  ### Cortex System Properties
127
+
111
128
  As Cortex executes the prompts in your pathway, it creates and maintains certain system properties that can be injected into prompts via Handlebars templating. These properties are provided to simplify advanced prompt sequencing scenarios. The system properties include:
129
+
112
130
  - `text`: Always stores the value of the `text` parameter passed into the query. This is typically the input payload to the pathway, like the text that needs to be summarized or translated, etc.
131
+
113
132
  - `now`: This is actually a Handlebars helper function that will return the current date and time - very useful for injecting temporal context into a prompt.
133
+
114
134
  - `previousResult`: This stores the value of the previous prompt execution if there is one. `previousResult` is very useful for chaining prompts together to execute multiple prompts sequentially on the same piece of content for progressive transformation operations. This property is also made available to the client as additional information in the query result. Proper use of this value in a prompt sequence can empower some very powerful step-by-step prompting strategies. For example, this three part sequential prompt implements a context-sensitive translation that is significantly better at translating specific people and place names:
115
135
  ```js
116
- prompt:
136
+ prompt:
117
137
  [
118
138
  `{{{text}}}\nCopy the names of all people and places exactly from this document in the language above:\n`,
119
139
  `Original Language:\n{{{previousResult}}}\n\n{{to}}:\n`,
120
140
  `Entities in the document:\n\n{{{previousResult}}}\n\nDocument:\n{{{text}}}\nRewrite the document in {{to}}. If the document is already in {{to}}, copy it exactly below:\n`
121
141
  ]
122
142
  ```
123
- - `savedContext`: The savedContext property is an object that the pathway can define the properties of. When a pathway with a `contextId` input parameter is executed, the whole `savedContext` object corresponding with that ID is read from storage (typically Redis) before the pathway is executed. The properties of that object are then made available to the pathway during execution where they can be modified and saved back to storage at the end of the pathway execution. Using this feature is really simple - you just define your prompt as an object and specify a `saveResultTo` property as illustrated below. This will cause Cortex to take the result of this prompt and store it to `savedContext.userContext` from which it will then be persisted to storage.
143
+ - `savedContext`: The savedContext property is an object that the pathway can define the properties of. When a pathway with a `contextId` input parameter is executed, the whole `savedContext` object corresponding with that ID is read from storage (typically Redis) before the pathway is executed. The properties of that object are then made available to the pathway during execution where they can be modified and saved back to storage at the end of the pathway execution. Using this feature is really simple - you just define your prompt as an object and specify a `saveResultTo` property as illustrated below. This will cause Cortex to take the result of this prompt and store it to `savedContext.userContext` from which it will then be persisted to storage.
124
144
  ```js
125
145
  new Prompt({ prompt: `User details:\n{{{userContext}}}\n\nExtract all personal details about the user that you can find in either the user details above or the conversation below and list them below.\n\nChat History:\n{{{conversationSummary}}}\n\nChat:\n{{{text}}}\n\nPersonal Details:\n`, saveResultTo: `userContext` }),
126
146
  ```
147
+
127
148
  ### Input Processing
149
+
128
150
  A core function of Cortex is dealing with token limited interfaces. To this end, Cortex has built-in strategies for dealing with long input. These strategies are `chunking`, `summarization`, and `truncation`. All are configurable at the pathway level.
151
+
129
152
  - `useInputChunking`: If true, Cortex will calculate the optimal chunk size from the model max tokens and the size of the prompt and then will split the input `text` into `n` chunks of that size. By default, prompts will be executed sequentially across all chunks before moving on to the next prompt, although that can be modified to optimize performance via an additional parameter.
153
+
130
154
  - `useParallelChunkProcessing`: If this parameter is true, then sequences of prompts will be executed end to end on each chunk in parallel. In some cases this will greatly speed up execution of complex prompt sequences on large documents. Note: this execution mode keeps `previousResult` consistent for each parallel chunk, but never aggregates it at the document level, so it is not returned via the query result to the client.
155
+
131
156
  - `truncateFromFront`: If true, when Cortex needs to truncate input, it will choose the first N characters of the input instead of the default which is to take the last N characters.
157
+
132
158
  - `useInputSummarization`: If true, Cortex will call the `summarize` core pathway on the input `text` before passing it on to the prompts.
159
+
133
160
  ### Output Processing
161
+
134
162
  Cortex provides built in functions to turn loosely formatted text output from the model API calls into structured objects for return to the application. Specifically, Cortex provides parsers for numbered lists of strings and numbered lists of objects. These are used in pathways like this:
135
163
  ```js
136
164
  module.exports = {
@@ -144,26 +172,17 @@ module.exports = {
144
172
  }
145
173
  ```
146
174
  By simply specifying a `format` property and a `list` property, this pathway invokes a built in parser that will take the result of the prompt and try to parse it into an array of 5 objects. The `list` property can be set with or without a `format` property. If there is no `format`, the list will simply try to parse the string into a list of strings. All of this default behavior is implemented in `parser.js`, and you can override it to do whatever you want by providing your own `parser` function in your pathway.
147
- ## Custom Pathways
148
- Pathways in Cortex OS are implemented as JavaScript files that export a module. A pathway module is an object that contains properties that define the prompts and behavior of the pathway. Most properties have functional defaults, so you can only implement the bits that are important to you. The main properties of a pathway module are:
149
-
150
- * `prompt`: The prompt that the pathway uses to interact with the model.
151
- * `inputParameters`: Any custom parameters to the GraphQL query that the pathway requires to run.
152
- * `resolver`: The resolver function that processes the input, executes the prompts, and returns the result.
153
- * `parser`: The parser function that processes the output from the prompts and formats the result for return.
154
175
 
155
176
  ### Custom Resolver
177
+
156
178
  The resolver property defines the function that processes the input and returns the result. The resolver function is an asynchronous function that takes four parameters: `parent`, `args`, `contextValue`, and `info`. The `parent` parameter is the parent object of the resolver function. The `args` parameter is an object that contains the input parameters and any other parameters that are passed to the resolver. The `contextValue` parameter is an object that contains the context and configuration of the pathway. The `info` parameter is an object that contains information about the GraphQL query that triggered the resolver.
157
179
 
158
180
  The core pathway `summary.js` below is implemented using custom pathway logic and a custom resolver to effectively target a specific summary length:
159
-
160
181
  ```js
161
182
  const { semanticTruncate } = require('../graphql/chunker');
162
183
  const { PathwayResolver } = require('../graphql/pathwayResolver');
163
-
164
184
  module.exports = {
165
185
  prompt: `{{{text}}}\n\nWrite a summary of the above text:\n\n`,
166
-
167
186
  inputParameters: {
168
187
  targetLength: 500,
169
188
  },
@@ -173,26 +192,22 @@ module.exports = {
173
192
  const errorMargin = 0.2;
174
193
  const lowTargetLength = originalTargetLength * (1 - errorMargin);
175
194
  const targetWords = Math.round(originalTargetLength / 6.6);
176
-
177
195
  // if the text is shorter than the summary length, just return the text
178
196
  if (args.text.length <= originalTargetLength) {
179
197
  return args.text;
180
198
  }
181
-
182
199
  const MAX_ITERATIONS = 5;
183
200
  let summary = '';
184
201
  let bestSummary = '';
185
202
  let pathwayResolver = new PathwayResolver({ config, pathway, requestState });
186
203
  // modify the prompt to be words-based instead of characters-based
187
204
  pathwayResolver.pathwayPrompt = `{{{text}}}\n\nWrite a summary of the above text in exactly ${targetWords} words:\n\n`
188
-
189
205
  let i = 0;
190
206
  // reprompt if summary is too long or too short
191
207
  while (((summary.length > originalTargetLength) || (summary.length < lowTargetLength)) && i < MAX_ITERATIONS) {
192
208
  summary = await pathwayResolver.resolve(args);
193
209
  i++;
194
210
  }
195
-
196
211
  // if the summary is still too long, truncate it
197
212
  if (summary.length > originalTargetLength) {
198
213
  return semanticTruncate(summary, originalTargetLength);
@@ -203,8 +218,53 @@ module.exports = {
203
218
  }
204
219
  ```
205
220
  ### Building and Loading Pathways
221
+
206
222
  Pathways are loaded from modules in the `pathways` directory. The pathways are built and loaded to the `config` object using the `buildPathways` function. The `buildPathways` function loads the base pathway, the core pathways, and any custom pathways. It then creates a new object that contains all the pathways and adds it to the pathways property of the config object. The order of loading means that custom pathways will always override any core pathways that Cortext provides. While pathways are designed to be self-contained, you can override some pathway properties - including whether they're even available at all - in the `pathways` section of the config file.
207
223
 
224
+ ## Core (Default) Pathways
225
+
226
+ Below are the default pathways provided with Cortex. These can be used as is, overridden, or disabled via configuration. For documentation on each one including input and output parameters, please look at them in the GraphQL Playground.
227
+
228
+ - `bias`: Identifies and measures any potential biases in a text
229
+ - `chat`: Enables users to have a conversation with the chatbot
230
+ - `complete`: Autocompletes words or phrases based on user input
231
+ - `edit`: Checks for and suggests corrections for spelling and grammar errors
232
+ - `entities`: Identifies and extracts important entities from text
233
+ - `paraphrase`: Suggests alternative phrasing for text
234
+ - `sentiment`: Analyzes and identifies the overall sentiment or mood of a text
235
+ - `summary`: Condenses long texts or articles into shorter summaries
236
+ - `translate`: Translates text from one language to another
237
+ ## Extensibility
238
+
239
+ Cortex is designed to be highly extensible. This allows you to customize the API to fit your needs. You can add new features, modify existing features, and even add integrations with other APIs and models.
240
+ ## Configuration
241
+ Configuration of Cortex is done via a [convict](https://github.com/mozilla/node-convict/tree/master) object called `config`. The `config` object is built by combining the default values and any values specified in a configuration file or environment variables. The environment variables take precedence over the values in the configuration file. Below are the configurable properties and their defaults:
242
+
243
+ - `basePathwayPath`: The path to the base pathway (the prototype pathway) for Cortex. Default properties for the pathway are set from their values in this basePathway. Default is path.join(__dirname, 'pathways', 'basePathway.js').
244
+ - `corePathwaysPath`: The path to the core pathways for Cortex. Default is path.join(__dirname, 'pathways').
245
+ - `cortexConfigFile`: The path to a JSON configuration file for the project. Default is null. The value can be set using the `CORTEX_CONFIG_FILE` environment variable.
246
+ - `defaultModelName`: The default model name for the project. Default is null. The value can be set using the `DEFAULT_MODEL_NAME` environment variable.
247
+ - `enableCache`: A boolean flag indicating whether to enable Axios-level request caching. Default is true. The value can be set using the `CORTEX_ENABLE_CACHE` environment variable.
248
+ - `enableGraphqlCache`: A boolean flag indicating whether to enable GraphQL query caching. Default is false. The value can be set using the `CORTEX_ENABLE_GRAPHQL_CACHE` environment variable.
249
+ - `enableRestEndpoints`: A boolean flag indicating whether create REST endpoints for pathways as well as GraphQL queries. Default is false. The value can be set using the `CORTEX_ENABLE_REST` environment variable.
250
+ - `cortexApiKey`: A string containing an API key that the client must pass to Cortex for authorization. Default is null in which case Cortex is unprotected. The value can be set using the `CORTEX_API_KEY` environment variable
251
+ - `models`: An object containing the different models used by the project. The value can be set using the `CORTEX_MODELS` environment variable. Cortex is model and vendor agnostic - you can use this config to set up models of any type from any vendor.
252
+ - `openaiApiKey`: The API key used for accessing the OpenAI API. This is sensitive information and has no default value. The value can be set using the `OPENAI_API_KEY` environment variable.
253
+ - `openaiApiUrl`: The URL used for accessing the OpenAI API. Default is https://api.openai.com/v1/completions. The value can be set using the `OPENAI_API_URL` environment variable.
254
+ - `openaiDefaultModel`: The default model name used for the OpenAI API. Default is text-davinci-003. The value can be set using the `OPENAI_DEFAULT_MODEL` environment variable.
255
+ - `pathways`: An object containing pathways for the project. The default is an empty object that is filled in during the `buildPathways` step.
256
+ - `pathwaysPath`: The path to custom pathways for the project. Default is null.
257
+ - `PORT`: The port number for the Cortex server. Default is 4000. The value can be set using the `CORTEX_PORT` environment variable.
258
+ - `storageConnectionString`: The connection string used for accessing storage. This is sensitive information and has no default value. The value can be set using the `STORAGE_CONNECTION_STRING` environment variable.
259
+
260
+ The `buildPathways` function takes the config object and builds the `pathways` object by loading the core pathways and any custom pathways specified in the `pathwaysPath` property of the config object. The function returns the `pathways` object.
261
+
262
+ The `buildModels` function takes the `config` object and builds the `models` object by compiling handlebars templates for each model specified in the `models` property of the config object. The function returns the `models` object.
263
+
264
+ The `config` object can be used to access configuration values throughout the project. For example, to get the port number for the server, use
265
+ ```js
266
+ config.get('PORT')
267
+ ```
208
268
  ## Troubleshooting
209
269
  If you encounter any issues while using Cortex, there are a few things you can do. First, check the Cortex documentation for any common errors and their solutions. If that does not help, you can also open an issue on the Cortex GitHub repository.
210
270
 
package/config.js CHANGED
@@ -46,6 +46,16 @@ var config = convict({
46
46
  default: false,
47
47
  env: 'CORTEX_ENABLE_GRAPHQL_CACHE'
48
48
  },
49
+ enableRestEndpoints: {
50
+ format: Boolean,
51
+ default: false,
52
+ env: 'CORTEX_ENABLE_REST'
53
+ },
54
+ cortexApiKey: {
55
+ format: String,
56
+ default: null,
57
+ env: 'CORTEX_API_KEY'
58
+ },
49
59
  defaultModelName: {
50
60
  format: String,
51
61
  default: null,
@@ -64,6 +74,16 @@ var config = convict({
64
74
  "params": {
65
75
  "model": "{{openaiDefaultModel}}"
66
76
  },
77
+ },
78
+ "oai-whisper": {
79
+ "type": "OPENAI_WHISPER",
80
+ "url": "https://api.openai.com/v1/audio/transcriptions",
81
+ "headers": {
82
+ "Authorization": "Bearer {{OPENAI_API_KEY}}"
83
+ },
84
+ "params": {
85
+ "model": "whisper-1"
86
+ },
67
87
  }
68
88
  },
69
89
  env: 'CORTEX_MODELS'
@@ -41,6 +41,40 @@ const getPlugins = (config) => {
41
41
  return { plugins, cache };
42
42
  }
43
43
 
44
+ const buildRestEndpoints = (pathways, app, server, config) => {
45
+ for (const [name, pathway] of Object.entries(pathways)) {
46
+ // Only expose endpoints for enabled pathways that explicitly want to expose a REST endpoint
47
+ if (pathway.disabled || !config.get('enableRestEndpoints')) continue;
48
+
49
+ const fieldVariableDefs = pathway.typeDef(pathway).restDefinition || [];
50
+
51
+ app.post(`/rest/${name}`, async (req, res) => {
52
+ const variables = fieldVariableDefs.reduce((acc, variableDef) => {
53
+ if (req.body.hasOwnProperty(variableDef.name)) {
54
+ acc[variableDef.name] = req.body[variableDef.name];
55
+ }
56
+ return acc;
57
+ }, {});
58
+
59
+ const variableParams = fieldVariableDefs.map(({ name, type }) => `$${name}: ${type}`).join(', ');
60
+ const queryArgs = fieldVariableDefs.map(({ name }) => `${name}: $${name}`).join(', ');
61
+
62
+ const query = `
63
+ query ${name}(${variableParams}) {
64
+ ${name}(${queryArgs}) {
65
+ contextId
66
+ previousResult
67
+ result
68
+ }
69
+ }
70
+ `;
71
+
72
+ const result = await server.executeOperation({ query, variables });
73
+ res.json(result.data[name]);
74
+ });
75
+ }
76
+ };
77
+
44
78
  //typeDefs
45
79
  const getTypedefs = (pathways) => {
46
80
 
@@ -75,7 +109,7 @@ const getTypedefs = (pathways) => {
75
109
  }
76
110
  `;
77
111
 
78
- const typeDefs = [defaultTypeDefs, ...Object.values(pathways).filter(p=>!p.disabled).map(p => p.typeDef(p))];
112
+ const typeDefs = [defaultTypeDefs, ...Object.values(pathways).filter(p=>!p.disabled).map(p => p.typeDef(p).gqlDefinition)];
79
113
  return typeDefs.join('\n');
80
114
  }
81
115
 
@@ -120,6 +154,7 @@ const build = (config) => {
120
154
 
121
155
  const { ApolloServer, gql } = require('apollo-server-express');
122
156
  const app = express()
157
+
123
158
  const httpServer = createServer(app);
124
159
 
125
160
  // Creating the WebSocket server
@@ -154,6 +189,23 @@ const build = (config) => {
154
189
  context: ({ req, res }) => ({ req, res, config, requestState }),
155
190
  });
156
191
 
192
+ // If CORTEX_API_KEY is set, we roll our own auth middleware - usually not used if you're being fronted by a proxy
193
+ const cortexApiKey = config.get('cortexApiKey');
194
+
195
+ app.use((req, res, next) => {
196
+ if (cortexApiKey && req.headers.cortexApiKey !== cortexApiKey && req.query.cortexApiKey !== cortexApiKey) {
197
+ res.status(401).send('Unauthorized');
198
+ } else {
199
+ next();
200
+ }
201
+ });
202
+
203
+ // Use the JSON body parser middleware for REST endpoints
204
+ app.use(express.json());
205
+
206
+ // add the REST endpoints
207
+ buildRestEndpoints(pathways, app, server, config);
208
+
157
209
  // if local start server
158
210
  const startServer = async () => {
159
211
  await server.start();
@@ -165,14 +217,6 @@ const build = (config) => {
165
217
  });
166
218
  };
167
219
 
168
- app.use((req, res, next) => {
169
- if (process.env.API_KEY && req.headers.api_key !== process.env.API_KEY && req.query.api_key !== process.env.API_KEY) {
170
- res.status(401).send('Unauthorized');
171
- }
172
-
173
- next();
174
- })
175
-
176
220
  return { server, startServer, cache, plugins, typeDefs, resolvers }
177
221
  }
178
222
 
@@ -2,6 +2,7 @@
2
2
  const OpenAIChatPlugin = require('./plugins/openAIChatPlugin');
3
3
  const OpenAICompletionPlugin = require('./plugins/openAICompletionPlugin');
4
4
  const AzureTranslatePlugin = require('./plugins/azureTranslatePlugin');
5
+ const OpenAIWhisperPlugin = require('./plugins/openAiWhisperPlugin');
5
6
  const handlebars = require("handlebars");
6
7
  const { Exception } = require("handlebars");
7
8
 
@@ -14,10 +15,10 @@ handlebars.registerHelper('now', function () {
14
15
  return new Date().toISOString();
15
16
  });
16
17
 
17
- handlebars.registerHelper('toJSON', function(object) {
18
+ handlebars.registerHelper('toJSON', function (object) {
18
19
  return JSON.stringify(object);
19
20
  });
20
-
21
+
21
22
 
22
23
  class PathwayPrompter {
23
24
  constructor({ config, pathway }) {
@@ -26,7 +27,7 @@ class PathwayPrompter {
26
27
  const model = config.get('models')[modelName];
27
28
 
28
29
  if (!model) {
29
- throw new Exception(`Model ${modelName} not found in config`);
30
+ throw new Exception(`Model ${modelName} not found in config`);
30
31
  }
31
32
 
32
33
  let plugin;
@@ -41,6 +42,9 @@ class PathwayPrompter {
41
42
  case 'OPENAI-COMPLETION':
42
43
  plugin = new OpenAICompletionPlugin(config, pathway);
43
44
  break;
45
+ case 'OPENAI_WHISPER':
46
+ plugin = new OpenAIWhisperPlugin(config, pathway);
47
+ break;
44
48
  default:
45
49
  throw new Exception(`Unsupported model type: ${model.type}`);
46
50
  }
@@ -48,11 +52,11 @@ class PathwayPrompter {
48
52
  this.plugin = plugin;
49
53
  }
50
54
 
51
- async execute(text, parameters, prompt) {
52
- return await this.plugin.execute(text, parameters, prompt);
55
+ async execute(text, parameters, prompt, pathwayResolver) {
56
+ return await this.plugin.execute(text, parameters, prompt, pathwayResolver);
53
57
  }
54
58
  }
55
59
 
56
60
  module.exports = {
57
- PathwayPrompter
61
+ PathwayPrompter
58
62
  };