@aj-archipelago/cortex 1.1.34 → 1.1.36
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 `
|
|
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.
|
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);
|
package/lib/pathwayManager.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
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
6
|
|
|
7
7
|
class StorageStrategy {
|
|
8
8
|
async load() { throw new Error('Not implemented'); }
|
|
@@ -20,6 +20,8 @@ class LocalStorage extends StorageStrategy {
|
|
|
20
20
|
if (!fs.existsSync(this.filePath)) {
|
|
21
21
|
// create it. log
|
|
22
22
|
logger.info(`Creating dynamic pathways local file: ${this.filePath}`);
|
|
23
|
+
// create directory if it doesn't exist
|
|
24
|
+
await fs.promises.mkdir(path.dirname(this.filePath), { recursive: true });
|
|
23
25
|
await fs.promises.writeFile(this.filePath, JSON.stringify({}));
|
|
24
26
|
}
|
|
25
27
|
|
|
@@ -80,7 +82,7 @@ class AzureBlobStorage extends StorageStrategy {
|
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
const downloadBlockBlobResponse = await blockBlobClient.download();
|
|
83
|
-
const data = await
|
|
85
|
+
const data = await streamToString(downloadBlockBlobResponse.readableStreamBody);
|
|
84
86
|
const parsedData = JSON.parse(data);
|
|
85
87
|
logger.info(`Loaded pathways from Azure Blob Storage. ${Object.keys(parsedData).map(user => `${user}(${Object.keys(parsedData[user])})`).join(', ')}`);
|
|
86
88
|
return parsedData;
|
|
@@ -124,18 +126,33 @@ class AzureBlobStorage extends StorageStrategy {
|
|
|
124
126
|
class S3Storage extends StorageStrategy {
|
|
125
127
|
constructor(config) {
|
|
126
128
|
super();
|
|
127
|
-
this.s3 = new S3(
|
|
128
|
-
|
|
129
|
+
this.s3 = new S3({
|
|
130
|
+
credentials: {
|
|
131
|
+
accessKeyId: config.awsAccessKeyId,
|
|
132
|
+
secretAccessKey: config.awsSecretAccessKey
|
|
133
|
+
},
|
|
134
|
+
region: config.awsRegion
|
|
135
|
+
});
|
|
136
|
+
this.bucketName = config.awsBucketName;
|
|
129
137
|
}
|
|
130
138
|
|
|
131
139
|
async load() {
|
|
132
140
|
try {
|
|
141
|
+
// Check if bucket exists, create if it doesn't
|
|
142
|
+
await this.ensureBucketExists();
|
|
143
|
+
|
|
144
|
+
// Check if file exists, create if it doesn't
|
|
145
|
+
await this.ensureFileExists();
|
|
146
|
+
|
|
133
147
|
const params = {
|
|
134
148
|
Bucket: this.bucketName,
|
|
135
149
|
Key: 'pathways.json'
|
|
136
150
|
};
|
|
137
151
|
const data = await this.s3.getObject(params);
|
|
138
|
-
|
|
152
|
+
|
|
153
|
+
const readableStream = data.Body;
|
|
154
|
+
const dataString = await streamToString(readableStream);
|
|
155
|
+
return JSON.parse(dataString);
|
|
139
156
|
} catch (error) {
|
|
140
157
|
logger.error('Error loading pathways from S3:', error);
|
|
141
158
|
throw error;
|
|
@@ -164,11 +181,47 @@ class S3Storage extends StorageStrategy {
|
|
|
164
181
|
const data = await this.s3.headObject(params);
|
|
165
182
|
return new Date(data.LastModified).getTime();
|
|
166
183
|
}
|
|
184
|
+
|
|
185
|
+
async ensureBucketExists() {
|
|
186
|
+
try {
|
|
187
|
+
await this.s3.headBucket({ Bucket: this.bucketName });
|
|
188
|
+
} catch (error) {
|
|
189
|
+
if (error.name === 'NotFound') {
|
|
190
|
+
logger.info(`Bucket ${this.bucketName} does not exist, creating it`);
|
|
191
|
+
await this.s3.createBucket({ Bucket: this.bucketName });
|
|
192
|
+
} else {
|
|
193
|
+
throw error;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async ensureFileExists() {
|
|
199
|
+
try {
|
|
200
|
+
await this.s3.headObject({
|
|
201
|
+
Bucket: this.bucketName,
|
|
202
|
+
Key: 'pathways.json'
|
|
203
|
+
});
|
|
204
|
+
} catch (error) {
|
|
205
|
+
if (error.name === 'NotFound') {
|
|
206
|
+
logger.info('pathways.json does not exist, creating it with empty object');
|
|
207
|
+
const emptyContent = JSON.stringify({});
|
|
208
|
+
await this.s3.putObject({
|
|
209
|
+
Bucket: this.bucketName,
|
|
210
|
+
Key: 'pathways.json',
|
|
211
|
+
Body: emptyContent,
|
|
212
|
+
ContentType: 'application/json'
|
|
213
|
+
});
|
|
214
|
+
} else {
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
167
219
|
}
|
|
168
220
|
|
|
169
221
|
class PathwayManager {
|
|
170
222
|
constructor(config, basePathway) {
|
|
171
223
|
this.storage = this.getStorageStrategy(config);
|
|
224
|
+
this.publishKey = config.publishKey;
|
|
172
225
|
this.pathways = {};
|
|
173
226
|
this.lastUpdated = 0;
|
|
174
227
|
this.basePathway = basePathway;
|
|
@@ -176,6 +229,10 @@ class PathwayManager {
|
|
|
176
229
|
if (config.storageType === 'local') {
|
|
177
230
|
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.');
|
|
178
231
|
}
|
|
232
|
+
|
|
233
|
+
if (!this.publishKey) {
|
|
234
|
+
logger.warn('WARNING: dynamicPathwaysConfig.publishKey is not set. Dynamic pathways will not be editable in this instance of Cortex.');
|
|
235
|
+
}
|
|
179
236
|
}
|
|
180
237
|
|
|
181
238
|
getStorageStrategy(config) {
|
|
@@ -233,6 +290,7 @@ class PathwayManager {
|
|
|
233
290
|
throw new Error('Both userId and secret are mandatory for adding or updating a pathway');
|
|
234
291
|
}
|
|
235
292
|
|
|
293
|
+
await this.getLatestPathways();
|
|
236
294
|
this.pathways[userId] = this.pathways[userId] || {};
|
|
237
295
|
|
|
238
296
|
if (this.pathways[userId][name] && this.pathways[userId][name].secret !== secret) {
|
|
@@ -246,9 +304,12 @@ class PathwayManager {
|
|
|
246
304
|
}
|
|
247
305
|
|
|
248
306
|
async removePathway(name, userId, secret) {
|
|
307
|
+
await this.getLatestPathways();
|
|
308
|
+
|
|
249
309
|
if (!this.pathways[userId] || !this.pathways[userId][name]) {
|
|
250
310
|
return;
|
|
251
311
|
}
|
|
312
|
+
|
|
252
313
|
if (this.pathways[userId][name].secret !== secret) {
|
|
253
314
|
throw new Error('Invalid secret');
|
|
254
315
|
}
|
|
@@ -294,11 +355,11 @@ class PathwayManager {
|
|
|
294
355
|
return {
|
|
295
356
|
Mutation: {
|
|
296
357
|
putPathway: async (_, { name, pathway, userId, secret, displayName, key }) => {
|
|
297
|
-
if (!
|
|
358
|
+
if (!this.publishKey) {
|
|
298
359
|
throw new Error("Invalid configuration. Pathway publishing key is not configured in Cortex.")
|
|
299
360
|
}
|
|
300
361
|
|
|
301
|
-
if (key !==
|
|
362
|
+
if (key !== this.publishKey) {
|
|
302
363
|
throw new Error('Invalid pathway publishing key. The key provided did not match the key configured in Cortex.');
|
|
303
364
|
}
|
|
304
365
|
|
|
@@ -310,10 +371,10 @@ class PathwayManager {
|
|
|
310
371
|
}
|
|
311
372
|
},
|
|
312
373
|
deletePathway: async (_, { name, userId, secret, key }) => {
|
|
313
|
-
if (!
|
|
374
|
+
if (!this.publishKey) {
|
|
314
375
|
throw new Error("Invalid configuration. Pathway publishing key is not configured in Cortex.")
|
|
315
376
|
}
|
|
316
|
-
if (key !==
|
|
377
|
+
if (key !== this.publishKey) {
|
|
317
378
|
throw new Error('Invalid pathway publishing key. The key provided did not match the key configured in Cortex.');
|
|
318
379
|
}
|
|
319
380
|
|
|
@@ -328,7 +389,7 @@ class PathwayManager {
|
|
|
328
389
|
};
|
|
329
390
|
}
|
|
330
391
|
|
|
331
|
-
async
|
|
392
|
+
async getLatestPathways() {
|
|
332
393
|
try {
|
|
333
394
|
const currentTimestamp = await this.storage.getLastModified();
|
|
334
395
|
|
|
@@ -340,13 +401,13 @@ class PathwayManager {
|
|
|
340
401
|
|
|
341
402
|
return this.pathways;
|
|
342
403
|
} catch (error) {
|
|
343
|
-
logger.error('Error in
|
|
404
|
+
logger.error('Error in getLatestPathways:', error);
|
|
344
405
|
throw error;
|
|
345
406
|
}
|
|
346
407
|
}
|
|
347
408
|
|
|
348
409
|
async getPathway(userId, pathwayName) {
|
|
349
|
-
const pathways = await this.
|
|
410
|
+
const pathways = await this.getLatestPathways();
|
|
350
411
|
|
|
351
412
|
if (!pathways[userId] || !pathways[userId][pathwayName]) {
|
|
352
413
|
throw new Error(`Pathway '${pathwayName}' not found for user '${userId}'`);
|
|
@@ -354,20 +415,19 @@ class PathwayManager {
|
|
|
354
415
|
|
|
355
416
|
return pathways[userId][pathwayName];
|
|
356
417
|
}
|
|
418
|
+
}
|
|
357
419
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
});
|
|
365
|
-
readableStream.on('end', () => {
|
|
366
|
-
resolve(chunks.join(''));
|
|
367
|
-
});
|
|
368
|
-
readableStream.on('error', reject);
|
|
420
|
+
// Helper function to convert a readable stream to a string
|
|
421
|
+
async function streamToString(readableStream) {
|
|
422
|
+
return new Promise((resolve, reject) => {
|
|
423
|
+
const chunks = [];
|
|
424
|
+
readableStream.on('data', (data) => {
|
|
425
|
+
chunks.push(data.toString());
|
|
369
426
|
});
|
|
370
|
-
|
|
427
|
+
readableStream.on('end', () => {
|
|
428
|
+
resolve(chunks.join(''));
|
|
429
|
+
});
|
|
430
|
+
readableStream.on('error', reject);
|
|
431
|
+
});
|
|
371
432
|
}
|
|
372
|
-
|
|
373
|
-
export default PathwayManager;
|
|
433
|
+
export default PathwayManager;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aj-archipelago/cortex",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.36",
|
|
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": {
|
|
@@ -27,17 +27,18 @@ class OpenAIVisionPlugin extends OpenAIChatPlugin {
|
|
|
27
27
|
}
|
|
28
28
|
if (Array.isArray(message.content)) {
|
|
29
29
|
message.content = message.content.map(item => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
const parsedItem = safeJsonParse(item);
|
|
31
|
+
|
|
32
|
+
if (typeof parsedItem === 'string') {
|
|
33
|
+
return { type: 'text', text: parsedItem };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (typeof parsedItem === 'object' && parsedItem !== null && parsedItem.type === 'image_url') {
|
|
37
|
+
parsedItem.image_url.url = parsedItem.url || parsedItem.image_url.url;
|
|
38
|
+
return parsedItem;
|
|
39
39
|
}
|
|
40
|
-
|
|
40
|
+
|
|
41
|
+
return parsedItem;
|
|
41
42
|
});
|
|
42
43
|
}
|
|
43
44
|
} catch (e) {
|
|
@@ -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;
|