@aj-archipelago/cortex 1.1.35 → 1.1.37

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
@@ -503,7 +503,8 @@ The configuration should include the following properties:
503
503
  "awsAccessKeyId": "your_access_key_id", // Only for AWS S3
504
504
  "awsSecretAccessKey": "your_secret_access_key", // Only for AWS S3
505
505
  "awsRegion": "your_aws_region", // Only for AWS S3
506
- "awsBucketName": "cortexdynamicpathways" // Optional, default is "cortexdynamicpathways"
506
+ "awsBucketName": "cortexdynamicpathways", // Optional, default is "cortexdynamicpathways"
507
+ "publishKey": "your_publish_key"
507
508
  }
508
509
  ```
509
510
 
@@ -559,7 +560,7 @@ query ExecuteWorkspace($userId: String!, $pathwayName: String!, $text: String!)
559
560
 
560
561
  To ensure the security of dynamic pathways:
561
562
 
562
- 1. A `PATHWAY_PUBLISH_KEY` environment variable must be set to enable pathway publishing.
563
+ 1. A `publishKey` must be set in the dynamic pathways configuration to enable pathway publishing.
563
564
  2. This key must be provided in the `key` parameter when adding, updating, or deleting pathways.
564
565
  3. Each pathway is associated with a `userId` and `secret`. The secret must be provided to modify or delete an existing pathway.
565
566
 
@@ -567,4 +568,4 @@ To ensure the security of dynamic pathways:
567
568
 
568
569
  Each instance of Cortex maintains its own local cache of pathways. On every dynamic pathway request, it checks if the local cache is up to date by comparing the last modified timestamp of the storage with the last update time of the local cache. If the local cache is out of date, it reloads the pathways from storage.
569
570
 
570
- This approach ensures that all instances of Cortex will eventually have access to the most up-to-date dynamic pathways without requiring immediate synchronization.
571
+ This approach ensures that all instances of Cortex will eventually have access to the most up-to-date dynamic pathways without requiring immediate synchronization.
@@ -1,4 +1,5 @@
1
1
  {
2
2
  "storageType": "azure",
3
3
  "filePath": "./pathways/dynamic/pathways.json",
4
+ "publishKey": "development"
4
5
  }
package/config.js CHANGED
@@ -321,6 +321,7 @@ const createDynamicPathwayManager = async (config, basePathway) => {
321
321
  awsSecretAccessKey: dynamicPathwayConfig.awsSecretAccessKey,
322
322
  awsRegion: dynamicPathwayConfig.awsRegion,
323
323
  awsBucketName: dynamicPathwayConfig.awsBucketName || 'cortexdynamicpathways',
324
+ publishKey: dynamicPathwayConfig.publishKey,
324
325
  };
325
326
 
326
327
  const pathwayManager = new PathwayManager(storageConfig, basePathway);
@@ -1,8 +1,9 @@
1
1
  import { BlobServiceClient } from '@azure/storage-blob';
2
2
  import { S3 } from '@aws-sdk/client-s3';
3
3
  import fs from 'fs';
4
+ import path from 'path';
4
5
  import logger from './logger.js';
5
- const { PATHWAY_PUBLISH_KEY } = process.env;
6
+ import { Prompt } from '../server/prompt.js';
6
7
 
7
8
  class StorageStrategy {
8
9
  async load() { throw new Error('Not implemented'); }
@@ -20,6 +21,8 @@ class LocalStorage extends StorageStrategy {
20
21
  if (!fs.existsSync(this.filePath)) {
21
22
  // create it. log
22
23
  logger.info(`Creating dynamic pathways local file: ${this.filePath}`);
24
+ // create directory if it doesn't exist
25
+ await fs.promises.mkdir(path.dirname(this.filePath), { recursive: true });
23
26
  await fs.promises.writeFile(this.filePath, JSON.stringify({}));
24
27
  }
25
28
 
@@ -219,6 +222,7 @@ class S3Storage extends StorageStrategy {
219
222
  class PathwayManager {
220
223
  constructor(config, basePathway) {
221
224
  this.storage = this.getStorageStrategy(config);
225
+ this.publishKey = config.publishKey;
222
226
  this.pathways = {};
223
227
  this.lastUpdated = 0;
224
228
  this.basePathway = basePathway;
@@ -226,6 +230,10 @@ class PathwayManager {
226
230
  if (config.storageType === 'local') {
227
231
  logger.warn('WARNING: Local file storage is being used for dynamic pathways. If there are multiple instances of Cortex, they will not be synced. Consider using cloud storage such as S3 or Azure for production environments.');
228
232
  }
233
+
234
+ if (!this.publishKey) {
235
+ logger.warn('WARNING: dynamicPathwaysConfig.publishKey is not set. Dynamic pathways will not be editable in this instance of Cortex.');
236
+ }
229
237
  }
230
238
 
231
239
  getStorageStrategy(config) {
@@ -278,11 +286,39 @@ class PathwayManager {
278
286
  await this.storage.save(pathways);
279
287
  }
280
288
 
289
+ /**
290
+ * Transforms the prompts in a pathway to include the system prompt.
291
+ * @param {Object} pathway - The pathway object to transform.
292
+ * @param {string[]} pathway.prompt - Array of user prompts.
293
+ * @param {string} pathway.systemPrompt - The system prompt to prepend to each user prompt.
294
+ * @returns {Object} A new pathway object with transformed prompts.
295
+ */
296
+ async transformPrompts(pathway) {
297
+ const { prompt, systemPrompt } = pathway;
298
+
299
+ const newPathway = { ...pathway };
300
+
301
+ // Transform each prompt in the array
302
+ newPathway.prompt = prompt.map(p => {
303
+ return new Prompt({
304
+ messages: [
305
+ // Prepend the system prompt as a system message
306
+ { "role": "system", "content": systemPrompt },
307
+ // Add the original prompt as a user message
308
+ { "role": "user", "content": p },
309
+ ]
310
+ })
311
+ });
312
+
313
+ return newPathway;
314
+ }
315
+
281
316
  async putPathway(name, pathway, userId, secret, displayName) {
282
317
  if (!userId || !secret) {
283
318
  throw new Error('Both userId and secret are mandatory for adding or updating a pathway');
284
319
  }
285
320
 
321
+ await this.getLatestPathways();
286
322
  this.pathways[userId] = this.pathways[userId] || {};
287
323
 
288
324
  if (this.pathways[userId][name] && this.pathways[userId][name].secret !== secret) {
@@ -296,9 +332,12 @@ class PathwayManager {
296
332
  }
297
333
 
298
334
  async removePathway(name, userId, secret) {
335
+ await this.getLatestPathways();
336
+
299
337
  if (!this.pathways[userId] || !this.pathways[userId][name]) {
300
338
  return;
301
339
  }
340
+
302
341
  if (this.pathways[userId][name].secret !== secret) {
303
342
  throw new Error('Invalid secret');
304
343
  }
@@ -318,6 +357,7 @@ class PathwayManager {
318
357
 
319
358
  input PathwayInput {
320
359
  prompt: [String!]!
360
+ systemPrompt: String
321
361
  inputParameters: JSONObject
322
362
  model: String
323
363
  enableCache: Boolean
@@ -344,11 +384,11 @@ class PathwayManager {
344
384
  return {
345
385
  Mutation: {
346
386
  putPathway: async (_, { name, pathway, userId, secret, displayName, key }) => {
347
- if (!PATHWAY_PUBLISH_KEY) {
387
+ if (!this.publishKey) {
348
388
  throw new Error("Invalid configuration. Pathway publishing key is not configured in Cortex.")
349
389
  }
350
390
 
351
- if (key !== PATHWAY_PUBLISH_KEY) {
391
+ if (key !== this.publishKey) {
352
392
  throw new Error('Invalid pathway publishing key. The key provided did not match the key configured in Cortex.');
353
393
  }
354
394
 
@@ -360,10 +400,10 @@ class PathwayManager {
360
400
  }
361
401
  },
362
402
  deletePathway: async (_, { name, userId, secret, key }) => {
363
- if (!PATHWAY_PUBLISH_KEY) {
403
+ if (!this.publishKey) {
364
404
  throw new Error("Invalid configuration. Pathway publishing key is not configured in Cortex.")
365
405
  }
366
- if (key !== PATHWAY_PUBLISH_KEY) {
406
+ if (key !== this.publishKey) {
367
407
  throw new Error('Invalid pathway publishing key. The key provided did not match the key configured in Cortex.');
368
408
  }
369
409
 
@@ -378,7 +418,7 @@ class PathwayManager {
378
418
  };
379
419
  }
380
420
 
381
- async getPathways() {
421
+ async getLatestPathways() {
382
422
  try {
383
423
  const currentTimestamp = await this.storage.getLastModified();
384
424
 
@@ -390,19 +430,19 @@ class PathwayManager {
390
430
 
391
431
  return this.pathways;
392
432
  } catch (error) {
393
- logger.error('Error in getPathways:', error);
433
+ logger.error('Error in getLatestPathways:', error);
394
434
  throw error;
395
435
  }
396
436
  }
397
437
 
398
438
  async getPathway(userId, pathwayName) {
399
- const pathways = await this.getPathways();
439
+ const pathways = await this.getLatestPathways();
400
440
 
401
441
  if (!pathways[userId] || !pathways[userId][pathwayName]) {
402
442
  throw new Error(`Pathway '${pathwayName}' not found for user '${userId}'`);
403
443
  }
404
444
 
405
- return pathways[userId][pathwayName];
445
+ return this.transformPrompts(pathways[userId][pathwayName]);
406
446
  }
407
447
  }
408
448
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aj-archipelago/cortex",
3
- "version": "1.1.35",
3
+ "version": "1.1.37",
4
4
  "description": "Cortex is a GraphQL API for AI. It provides a simple, extensible interface for using AI services from OpenAI, Azure and others.",
5
5
  "private": false,
6
6
  "repository": {
@@ -0,0 +1,71 @@
1
+ // replicateApiPlugin.js
2
+ import ModelPlugin from "./modelPlugin.js";
3
+ import logger from "../../lib/logger.js";
4
+
5
+ class ReplicateApiPlugin extends ModelPlugin {
6
+ constructor(pathway, model) {
7
+ super(pathway, model);
8
+ }
9
+
10
+ // Set up parameters specific to the Replicate API
11
+ getRequestParameters(text, parameters, prompt) {
12
+ const combinedParameters = { ...this.promptParameters, ...parameters };
13
+ const { modelPromptText } = this.getCompiledPrompt(
14
+ text,
15
+ parameters,
16
+ prompt,
17
+ );
18
+
19
+ const requestParameters = {
20
+ input: {
21
+ aspect_ratio: "1:1",
22
+ output_format: "webp",
23
+ output_quality: 80,
24
+ prompt: modelPromptText,
25
+ //prompt_upsampling: false,
26
+ //safety_tolerance: 5,
27
+ go_fast: true,
28
+ megapixels: "1",
29
+ num_outputs: combinedParameters.numberResults,
30
+ },
31
+ };
32
+
33
+ return requestParameters;
34
+ }
35
+
36
+ // Execute the request to the Replicate API
37
+ async execute(text, parameters, prompt, cortexRequest) {
38
+ const requestParameters = this.getRequestParameters(
39
+ text,
40
+ parameters,
41
+ prompt,
42
+ );
43
+
44
+ cortexRequest.data = requestParameters;
45
+ cortexRequest.params = requestParameters.params;
46
+
47
+ return this.executeRequest(cortexRequest);
48
+ }
49
+
50
+ // Parse the response from the Replicate API
51
+ parseResponse(data) {
52
+ if (data.data) {
53
+ return JSON.stringify(data.data);
54
+ }
55
+ return JSON.stringify(data);
56
+ }
57
+
58
+ // Override the logging function to display the request and response
59
+ logRequestData(data, responseData, prompt) {
60
+ const modelInput = data?.input?.prompt;
61
+
62
+ logger.verbose(`${modelInput}`);
63
+ logger.verbose(`${this.parseResponse(responseData)}`);
64
+
65
+ prompt &&
66
+ prompt.debugInfo &&
67
+ (prompt.debugInfo += `\n${JSON.stringify(data)}`);
68
+ }
69
+ }
70
+
71
+ export default ReplicateApiPlugin;