@aj-archipelago/cortex 1.3.66 → 1.4.0
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/config.js +27 -0
- package/helper-apps/cortex-autogen2/Dockerfile +88 -21
- package/helper-apps/cortex-autogen2/docker-compose.yml +15 -8
- package/helper-apps/cortex-autogen2/host.json +5 -0
- package/helper-apps/cortex-autogen2/pyproject.toml +82 -25
- package/helper-apps/cortex-autogen2/requirements.txt +84 -14
- package/helper-apps/cortex-autogen2/services/redis_publisher.py +129 -3
- package/helper-apps/cortex-autogen2/task_processor.py +432 -116
- package/helper-apps/cortex-autogen2/tools/__init__.py +2 -0
- package/helper-apps/cortex-autogen2/tools/azure_blob_tools.py +32 -0
- package/helper-apps/cortex-autogen2/tools/azure_foundry_agents.py +50 -14
- package/helper-apps/cortex-autogen2/tools/file_tools.py +169 -44
- package/helper-apps/cortex-autogen2/tools/google_cse.py +117 -0
- package/helper-apps/cortex-autogen2/tools/search_tools.py +655 -98
- package/helper-apps/cortex-doc-to-pdf/DocToPdfFunction/__init__.py +3 -0
- package/helper-apps/cortex-doc-to-pdf/DocToPdfFunction/function.json +20 -0
- package/helper-apps/cortex-doc-to-pdf/Dockerfile +46 -0
- package/helper-apps/cortex-doc-to-pdf/README.md +408 -0
- package/helper-apps/cortex-doc-to-pdf/converter.py +157 -0
- package/helper-apps/cortex-doc-to-pdf/docker-compose.yml +23 -0
- package/helper-apps/cortex-doc-to-pdf/document_converter.py +181 -0
- package/helper-apps/cortex-doc-to-pdf/examples/README.md +252 -0
- package/helper-apps/cortex-doc-to-pdf/examples/nodejs-client.js +266 -0
- package/helper-apps/cortex-doc-to-pdf/examples/package-lock.json +297 -0
- package/helper-apps/cortex-doc-to-pdf/examples/package.json +23 -0
- package/helper-apps/cortex-doc-to-pdf/function_app.py +85 -0
- package/helper-apps/cortex-doc-to-pdf/host.json +16 -0
- package/helper-apps/cortex-doc-to-pdf/request_handlers.py +193 -0
- package/helper-apps/cortex-doc-to-pdf/requirements.txt +3 -0
- package/helper-apps/cortex-doc-to-pdf/tests/run_tests.sh +26 -0
- package/helper-apps/cortex-doc-to-pdf/tests/test_conversion.py +320 -0
- package/helper-apps/cortex-doc-to-pdf/tests/test_streaming.py +419 -0
- package/helper-apps/cortex-file-handler/package-lock.json +1 -0
- package/helper-apps/cortex-file-handler/package.json +1 -0
- package/helper-apps/cortex-file-handler/src/services/ConversionService.js +81 -8
- package/helper-apps/cortex-file-handler/tests/FileConversionService.test.js +54 -7
- package/helper-apps/cortex-file-handler/tests/getOperations.test.js +19 -7
- package/lib/encodeCache.js +5 -0
- package/lib/keyValueStorageClient.js +5 -0
- package/lib/logger.js +1 -1
- package/lib/pathwayManager.js +42 -8
- package/lib/pathwayTools.js +8 -1
- package/lib/redisSubscription.js +6 -0
- package/lib/requestExecutor.js +4 -0
- package/lib/util.js +145 -1
- package/package.json +1 -1
- package/pathways/basePathway.js +3 -3
- package/pathways/bing_afagent.js +1 -0
- package/pathways/gemini_15_vision.js +1 -1
- package/pathways/google_cse.js +2 -2
- package/pathways/image_gemini_25.js +85 -0
- package/pathways/image_prompt_optimizer_gemini_25.js +149 -0
- package/pathways/image_qwen.js +28 -0
- package/pathways/image_seedream4.js +26 -0
- package/pathways/rag.js +1 -1
- package/pathways/rag_jarvis.js +1 -1
- package/pathways/system/entity/sys_entity_continue.js +1 -1
- package/pathways/system/entity/sys_generator_results.js +1 -1
- package/pathways/system/entity/tools/sys_tool_google_search.js +15 -2
- package/pathways/system/entity/tools/sys_tool_grok_x_search.js +3 -3
- package/pathways/system/entity/tools/sys_tool_image.js +28 -23
- package/pathways/system/entity/tools/sys_tool_image_gemini.js +135 -0
- package/pathways/system/workspaces/run_workspace_prompt.js +0 -3
- package/server/executeWorkspace.js +381 -0
- package/server/graphql.js +14 -182
- package/server/modelExecutor.js +4 -0
- package/server/pathwayResolver.js +19 -18
- package/server/plugins/claude3VertexPlugin.js +13 -8
- package/server/plugins/gemini15ChatPlugin.js +15 -10
- package/server/plugins/gemini15VisionPlugin.js +2 -23
- package/server/plugins/gemini25ImagePlugin.js +155 -0
- package/server/plugins/modelPlugin.js +3 -2
- package/server/plugins/openAiChatPlugin.js +6 -6
- package/server/plugins/replicateApiPlugin.js +268 -12
- package/server/plugins/veoVideoPlugin.js +15 -1
- package/server/rest.js +2 -0
- package/server/typeDef.js +96 -10
- package/tests/integration/apptekTranslatePlugin.integration.test.js +1 -1
- package/tests/unit/core/parser.test.js +0 -1
- package/tests/unit/core/pathwayManager.test.js +2 -4
- package/tests/unit/core/pathwayManagerWithFiles.test.js +256 -0
- package/tests/unit/graphql_executeWorkspace_transformation.test.js +244 -0
- package/tests/unit/plugins/gemini25ImagePlugin.test.js +294 -0
- package/tests/unit/server/graphql.test.js +122 -1
|
@@ -232,20 +232,32 @@ test.serial("should fetch remote file", async (t) => {
|
|
|
232
232
|
const requestId = uuidv4();
|
|
233
233
|
const remoteUrl = "https://example.com/test.txt";
|
|
234
234
|
|
|
235
|
+
// Mock external HEAD and GET to avoid network dependency
|
|
236
|
+
const body = "hello from example";
|
|
237
|
+
const scope = nock("https://example.com")
|
|
238
|
+
.head("/test.txt")
|
|
239
|
+
.reply(200, "", {
|
|
240
|
+
"Content-Type": "text/plain",
|
|
241
|
+
"Content-Length": body.length.toString(),
|
|
242
|
+
})
|
|
243
|
+
.get("/test.txt")
|
|
244
|
+
.reply(200, body, {
|
|
245
|
+
"Content-Type": "text/plain",
|
|
246
|
+
"Content-Length": body.length.toString(),
|
|
247
|
+
});
|
|
248
|
+
|
|
235
249
|
const response = await axios.get(baseUrl, {
|
|
236
250
|
params: {
|
|
237
251
|
fetch: remoteUrl,
|
|
238
252
|
requestId,
|
|
239
253
|
},
|
|
240
254
|
validateStatus: (status) => true,
|
|
255
|
+
timeout: 10000,
|
|
241
256
|
});
|
|
242
257
|
|
|
243
|
-
t.is(response.status,
|
|
244
|
-
t.
|
|
245
|
-
|
|
246
|
-
"Invalid or inaccessible URL",
|
|
247
|
-
"Should return correct error message",
|
|
248
|
-
);
|
|
258
|
+
t.is(response.status, 200, "Should fetch and store remote file");
|
|
259
|
+
t.truthy(response.data.url, "Should return file URL");
|
|
260
|
+
t.true(scope.isDone(), "All external requests should be mocked and used");
|
|
249
261
|
});
|
|
250
262
|
|
|
251
263
|
// Test: Redis caching behavior for remote files
|
|
@@ -296,7 +308,7 @@ test.serial("should cache remote files in Redis", async (t) => {
|
|
|
296
308
|
|
|
297
309
|
t.is(secondResponse.status, 200, "Should return cached file from Redis");
|
|
298
310
|
t.truthy(secondResponse.data.url, "Should return cached file URL");
|
|
299
|
-
|
|
311
|
+
|
|
300
312
|
// Cleanup
|
|
301
313
|
await cleanupHashAndFile(hash, secondResponse.data.url, baseUrl);
|
|
302
314
|
} finally {
|
package/lib/encodeCache.js
CHANGED
|
@@ -9,6 +9,11 @@ class EncodeCache {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
encode(value) {
|
|
12
|
+
// Handle CortexResponse objects by extracting the text content
|
|
13
|
+
if (value && typeof value === 'object' && value.constructor && value.constructor.name === 'CortexResponse') {
|
|
14
|
+
value = value.output_text || '';
|
|
15
|
+
}
|
|
16
|
+
|
|
12
17
|
if (this.encodeCache.get(value) !== -1) {
|
|
13
18
|
return this.encodeCache.get(value);
|
|
14
19
|
}
|
|
@@ -30,6 +30,11 @@ const keyValueStorageClient = new Keyv(storageConnectionString, {
|
|
|
30
30
|
namespace: `${cortexId}-cortex-context`
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
+
// Handle Redis connection errors to prevent crashes
|
|
34
|
+
keyValueStorageClient.on('error', (error) => {
|
|
35
|
+
logger.error(`Keyv Redis connection error: ${error}`);
|
|
36
|
+
});
|
|
37
|
+
|
|
33
38
|
// Set values to keyv
|
|
34
39
|
async function setv(key, value) {
|
|
35
40
|
return keyValueStorageClient && (await keyValueStorageClient.set(key, value));
|
package/lib/logger.js
CHANGED
|
@@ -37,9 +37,9 @@ const getTransport = () => {
|
|
|
37
37
|
case 'production':
|
|
38
38
|
return new winston.transports.Console({ level: 'info', format: winston.format.combine(suppressNonErrorFormat(), prodFormat) });
|
|
39
39
|
case 'development':
|
|
40
|
-
case 'test':
|
|
41
40
|
return new winston.transports.Console({ level: 'verbose', format: winston.format.combine(suppressNonErrorFormat(), debugFormat) });
|
|
42
41
|
case 'debug':
|
|
42
|
+
case 'test':
|
|
43
43
|
return new winston.transports.Console({ level: 'debug', format: winston.format.combine(suppressNonErrorFormat(), debugFormat) });
|
|
44
44
|
default:
|
|
45
45
|
// Default to development settings if NODE_ENV is not set or unknown
|
package/lib/pathwayManager.js
CHANGED
|
@@ -338,36 +338,58 @@ class PathwayManager {
|
|
|
338
338
|
|
|
339
339
|
/**
|
|
340
340
|
* Creates a Prompt object from a prompt item and system prompt.
|
|
341
|
-
* @param {(string|Object)} promptItem - The prompt item (string or {name, prompt} object).
|
|
341
|
+
* @param {(string|Object)} promptItem - The prompt item (string or {name, prompt, files} object).
|
|
342
342
|
* @param {string} systemPrompt - The system prompt to prepend.
|
|
343
343
|
* @param {string} [defaultName] - Default name to use if promptItem is a string or if the object doesn't have a name.
|
|
344
344
|
* @returns {Prompt} A new Prompt object.
|
|
345
345
|
*/
|
|
346
346
|
_createPromptObject(promptItem, systemPrompt, defaultName = null) {
|
|
347
|
-
// Handle both old format (strings) and new format (objects with name and
|
|
347
|
+
// Handle both old format (strings) and new format (objects with name, prompt, and files)
|
|
348
348
|
const promptText = typeof promptItem === 'string' ? promptItem : promptItem.prompt;
|
|
349
349
|
const promptName = typeof promptItem === 'string' ? defaultName : (promptItem.name || defaultName);
|
|
350
|
+
const promptFiles = typeof promptItem === 'string' ? [] : (promptItem.files || []);
|
|
351
|
+
const cortexPathwayName = typeof promptItem === 'string' ? null : (promptItem.cortexPathwayName || null);
|
|
350
352
|
|
|
351
|
-
const messages = [
|
|
352
|
-
// Add the original prompt as a user message
|
|
353
|
-
{ "role": "user", "content": `{{text}}\n\n${promptText}` },
|
|
354
|
-
];
|
|
353
|
+
const messages = [];
|
|
355
354
|
|
|
356
355
|
// Only include system message if systemPrompt has content
|
|
357
356
|
if (systemPrompt && systemPrompt.trim() !== "") {
|
|
358
357
|
messages.unshift({ "role": "system", "content": systemPrompt });
|
|
359
358
|
}
|
|
360
359
|
|
|
361
|
-
|
|
360
|
+
// If there are files, include chatHistory to get the file content, then add user message
|
|
361
|
+
if (promptFiles.length > 0) {
|
|
362
|
+
// Add chatHistory which will contain the resolved file content
|
|
363
|
+
messages.push("{{chatHistory}}");
|
|
364
|
+
// Add the user text and prompt as a separate user message
|
|
365
|
+
messages.push({ "role": "user", "content": `{{text}}\n\n${promptText}` });
|
|
366
|
+
} else {
|
|
367
|
+
// No files, just add the user message with text and prompt
|
|
368
|
+
messages.push({ "role": "user", "content": `{{text}}\n\n${promptText}` });
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const prompt = new Prompt({
|
|
362
372
|
name: promptName,
|
|
363
373
|
messages: messages
|
|
364
374
|
});
|
|
375
|
+
|
|
376
|
+
// Store file hashes for later resolution
|
|
377
|
+
if (promptFiles.length > 0) {
|
|
378
|
+
prompt.fileHashes = promptFiles;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Preserve cortexPathwayName if present
|
|
382
|
+
if (cortexPathwayName) {
|
|
383
|
+
prompt.cortexPathwayName = cortexPathwayName;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return prompt;
|
|
365
387
|
}
|
|
366
388
|
|
|
367
389
|
/**
|
|
368
390
|
* Transforms the prompts in a pathway to include the system prompt.
|
|
369
391
|
* @param {Object} pathway - The pathway object to transform.
|
|
370
|
-
* @param {(string[]|Object[])} pathway.prompt - Array of user prompts (strings) or prompt objects with {name, prompt} properties.
|
|
392
|
+
* @param {(string[]|Object[])} pathway.prompt - Array of user prompts (strings) or prompt objects with {name, prompt, files} properties.
|
|
371
393
|
* @param {string} pathway.systemPrompt - The system prompt to prepend to each user prompt.
|
|
372
394
|
* @returns {Object} A new pathway object with transformed prompts.
|
|
373
395
|
*/
|
|
@@ -379,6 +401,16 @@ class PathwayManager {
|
|
|
379
401
|
// Transform each prompt in the array
|
|
380
402
|
newPathway.prompt = prompt.map(p => this._createPromptObject(p, systemPrompt));
|
|
381
403
|
|
|
404
|
+
// Collect all file hashes from all prompts
|
|
405
|
+
const allFileHashes = newPathway.prompt
|
|
406
|
+
.filter(p => p.fileHashes && p.fileHashes.length > 0)
|
|
407
|
+
.flatMap(p => p.fileHashes);
|
|
408
|
+
|
|
409
|
+
// Store file hashes at pathway level for later resolution
|
|
410
|
+
if (allFileHashes.length > 0) {
|
|
411
|
+
newPathway.fileHashes = [...new Set(allFileHashes)]; // Remove duplicates
|
|
412
|
+
}
|
|
413
|
+
|
|
382
414
|
return newPathway;
|
|
383
415
|
}
|
|
384
416
|
|
|
@@ -426,6 +458,8 @@ class PathwayManager {
|
|
|
426
458
|
input PromptInput {
|
|
427
459
|
name: String!
|
|
428
460
|
prompt: String!
|
|
461
|
+
files: [String!]
|
|
462
|
+
cortexPathwayName: String
|
|
429
463
|
}
|
|
430
464
|
|
|
431
465
|
input PathwayInput {
|
package/lib/pathwayTools.js
CHANGED
|
@@ -5,6 +5,7 @@ import { publishRequestProgress } from "../lib/redisSubscription.js";
|
|
|
5
5
|
import { getSemanticChunks } from "../server/chunker.js";
|
|
6
6
|
import logger from '../lib/logger.js';
|
|
7
7
|
import { requestState } from '../server/requestState.js';
|
|
8
|
+
import { processPathwayParameters } from '../server/typeDef.js';
|
|
8
9
|
|
|
9
10
|
// callPathway - call a pathway from another pathway
|
|
10
11
|
const callPathway = async (pathwayName, inArgs, pathwayResolver) => {
|
|
@@ -17,12 +18,18 @@ const callPathway = async (pathwayName, inArgs, pathwayResolver) => {
|
|
|
17
18
|
throw new Error(`Pathway ${pathwayName} not found`);
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
// Merge pathway default parameters with input args, similar to GraphQL typeDef behavior
|
|
22
|
+
const mergedParams = { ...pathway.defaultInputParameters, ...pathway.inputParameters, ...args };
|
|
23
|
+
|
|
24
|
+
// Process the merged parameters to convert type specification objects to actual values
|
|
25
|
+
const processedArgs = processPathwayParameters(mergedParams);
|
|
26
|
+
|
|
20
27
|
const parent = {};
|
|
21
28
|
let rootRequestId = pathwayResolver?.rootRequestId || pathwayResolver?.requestId;
|
|
22
29
|
|
|
23
30
|
const contextValue = { config, pathway, requestState };
|
|
24
31
|
|
|
25
|
-
let data = await pathway.rootResolver(parent, {...
|
|
32
|
+
let data = await pathway.rootResolver(parent, {...processedArgs, rootRequestId}, contextValue );
|
|
26
33
|
|
|
27
34
|
if (pathwayResolver && contextValue.pathwayResolver) {
|
|
28
35
|
pathwayResolver.mergeResolver(contextValue.pathwayResolver);
|
package/lib/redisSubscription.js
CHANGED
|
@@ -24,6 +24,12 @@ if (connectionString) {
|
|
|
24
24
|
logger.info(`Using Redis publish for channel(s) ${requestProgressChannel}, ${requestProgressSubscriptionsChannel}`);
|
|
25
25
|
try {
|
|
26
26
|
publisherClient = connectionString && new Redis(connectionString);
|
|
27
|
+
// Handle Redis publisher client errors to prevent crashes
|
|
28
|
+
if (publisherClient) {
|
|
29
|
+
publisherClient.on('error', (error) => {
|
|
30
|
+
logger.error(`Redis publisherClient error: ${error}`);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
27
33
|
} catch (error) {
|
|
28
34
|
logger.error(`Redis connection error: ${error}`);
|
|
29
35
|
}
|
package/lib/requestExecutor.js
CHANGED
|
@@ -20,6 +20,10 @@ let client;
|
|
|
20
20
|
if (connectionString) {
|
|
21
21
|
try {
|
|
22
22
|
client = new Redis(connectionString);
|
|
23
|
+
// Handle Redis connection errors to prevent crashes
|
|
24
|
+
client.on('error', (error) => {
|
|
25
|
+
logger.error(`Redis client connection error: ${error}`);
|
|
26
|
+
});
|
|
23
27
|
} catch (error) {
|
|
24
28
|
logger.error(`Redis connection error: ${error}`);
|
|
25
29
|
}
|
package/lib/util.js
CHANGED
|
@@ -10,6 +10,8 @@ import { promisify } from 'util';
|
|
|
10
10
|
import { axios } from './requestExecutor.js';
|
|
11
11
|
import { config } from '../config.js';
|
|
12
12
|
import fs from 'fs';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
import FormData from 'form-data';
|
|
13
15
|
|
|
14
16
|
const pipeline = promisify(stream.pipeline);
|
|
15
17
|
const MEDIA_API_URL = config.get('whisperMediaApiUrl');
|
|
@@ -412,6 +414,146 @@ function getAvailableFiles(chatHistory) {
|
|
|
412
414
|
return availableFiles;
|
|
413
415
|
}
|
|
414
416
|
|
|
417
|
+
// Helper function to upload base64 image data to cloud storage
|
|
418
|
+
const uploadImageToCloud = async (base64Data, mimeType, pathwayResolver = null) => {
|
|
419
|
+
let tempFilePath = null;
|
|
420
|
+
let tempDir = null;
|
|
421
|
+
|
|
422
|
+
try {
|
|
423
|
+
// Convert base64 to buffer
|
|
424
|
+
const imageBuffer = Buffer.from(base64Data, 'base64');
|
|
425
|
+
|
|
426
|
+
// Determine file extension from mime type
|
|
427
|
+
const extension = mimeType.split('/')[1] || 'png';
|
|
428
|
+
const filename = `generated_image_${Date.now()}.${extension}`;
|
|
429
|
+
|
|
430
|
+
// Create temporary file
|
|
431
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'image-upload-'));
|
|
432
|
+
tempFilePath = path.join(tempDir, filename);
|
|
433
|
+
|
|
434
|
+
// Write buffer to temp file
|
|
435
|
+
fs.writeFileSync(tempFilePath, imageBuffer);
|
|
436
|
+
|
|
437
|
+
// Upload to file handler service
|
|
438
|
+
const fileHandlerUrl = MEDIA_API_URL;
|
|
439
|
+
if (!fileHandlerUrl) {
|
|
440
|
+
throw new Error('WHISPER_MEDIA_API_URL is not set');
|
|
441
|
+
}
|
|
442
|
+
const requestId = uuidv4();
|
|
443
|
+
|
|
444
|
+
// Create form data for upload
|
|
445
|
+
const formData = new FormData();
|
|
446
|
+
formData.append('file', fs.createReadStream(tempFilePath), {
|
|
447
|
+
filename: filename,
|
|
448
|
+
contentType: mimeType
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
// Upload file
|
|
452
|
+
const uploadResponse = await axios.post(`${fileHandlerUrl}?requestId=${requestId}`, formData, {
|
|
453
|
+
headers: {
|
|
454
|
+
...formData.getHeaders()
|
|
455
|
+
},
|
|
456
|
+
timeout: 30000
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
if (uploadResponse.data && uploadResponse.data.url) {
|
|
460
|
+
return uploadResponse.data.url;
|
|
461
|
+
} else {
|
|
462
|
+
throw new Error('No URL returned from file handler');
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
} catch (error) {
|
|
466
|
+
const errorMessage = `Failed to upload image: ${error.message}`;
|
|
467
|
+
if (pathwayResolver && pathwayResolver.logError) {
|
|
468
|
+
pathwayResolver.logError(errorMessage);
|
|
469
|
+
} else {
|
|
470
|
+
logger.error(errorMessage);
|
|
471
|
+
}
|
|
472
|
+
throw error;
|
|
473
|
+
} finally {
|
|
474
|
+
// Clean up temp files
|
|
475
|
+
if (tempFilePath && fs.existsSync(tempFilePath)) {
|
|
476
|
+
try {
|
|
477
|
+
fs.unlinkSync(tempFilePath);
|
|
478
|
+
} catch (cleanupError) {
|
|
479
|
+
const warningMessage = `Failed to clean up temp file: ${cleanupError.message}`;
|
|
480
|
+
if (pathwayResolver && pathwayResolver.logWarning) {
|
|
481
|
+
pathwayResolver.logWarning(warningMessage);
|
|
482
|
+
} else {
|
|
483
|
+
logger.warn(warningMessage);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (tempDir && fs.existsSync(tempDir)) {
|
|
488
|
+
try {
|
|
489
|
+
fs.rmdirSync(tempDir);
|
|
490
|
+
} catch (cleanupError) {
|
|
491
|
+
const warningMessage = `Failed to clean up temp directory: ${cleanupError.message}`;
|
|
492
|
+
if (pathwayResolver && pathwayResolver.logWarning) {
|
|
493
|
+
pathwayResolver.logWarning(warningMessage);
|
|
494
|
+
} else {
|
|
495
|
+
logger.warn(warningMessage);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Convert file hashes to content format suitable for LLM processing
|
|
504
|
+
* @param {Array<string>} fileHashes - Array of file hashes to resolve
|
|
505
|
+
* @param {Object} config - Configuration object with file service endpoints
|
|
506
|
+
* @returns {Promise<Array<string>>} Array of stringified file content objects
|
|
507
|
+
*/
|
|
508
|
+
async function resolveFileHashesToContent(fileHashes, config) {
|
|
509
|
+
if (!fileHashes || fileHashes.length === 0) return [];
|
|
510
|
+
|
|
511
|
+
const fileContentPromises = fileHashes.map(async (hash) => {
|
|
512
|
+
try {
|
|
513
|
+
// Use the existing file handler (cortex-file-handler) to resolve file hashes
|
|
514
|
+
const fileHandlerUrl = config?.get?.('whisperMediaApiUrl');
|
|
515
|
+
|
|
516
|
+
if (fileHandlerUrl && fileHandlerUrl !== 'null') {
|
|
517
|
+
// Make request to file handler to get file content by hash
|
|
518
|
+
const response = await axios.get(fileHandlerUrl, {
|
|
519
|
+
params: { hash: hash, checkHash: true }
|
|
520
|
+
});
|
|
521
|
+
if (response.status === 200) {
|
|
522
|
+
const fileData = response.data;
|
|
523
|
+
const fileUrl = fileData.shortLivedUrl || fileData.url;
|
|
524
|
+
const convertedUrl = fileData.converted?.url;
|
|
525
|
+
const convertedGcsUrl = fileData.converted?.gcs;
|
|
526
|
+
|
|
527
|
+
return JSON.stringify({
|
|
528
|
+
type: "image_url",
|
|
529
|
+
url: convertedUrl,
|
|
530
|
+
image_url: { url: convertedUrl },
|
|
531
|
+
gcs: convertedGcsUrl || fileData.gcs, // Add GCS URL for Gemini models
|
|
532
|
+
originalFilename: fileData.filename,
|
|
533
|
+
hash: hash
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Fallback: create a placeholder that indicates file resolution is needed
|
|
539
|
+
return JSON.stringify({
|
|
540
|
+
type: "file_hash",
|
|
541
|
+
hash: hash,
|
|
542
|
+
_cortex_needs_resolution: true
|
|
543
|
+
});
|
|
544
|
+
} catch (error) {
|
|
545
|
+
// Return error indicator
|
|
546
|
+
return JSON.stringify({
|
|
547
|
+
type: "file_error",
|
|
548
|
+
hash: hash,
|
|
549
|
+
error: error.message
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
return Promise.all(fileContentPromises);
|
|
555
|
+
}
|
|
556
|
+
|
|
415
557
|
export {
|
|
416
558
|
getUniqueId,
|
|
417
559
|
getSearchResultId,
|
|
@@ -421,10 +563,12 @@ export {
|
|
|
421
563
|
chatArgsHasType,
|
|
422
564
|
deleteTempPath,
|
|
423
565
|
downloadFile,
|
|
566
|
+
uploadImageToCloud,
|
|
424
567
|
convertSrtToText,
|
|
425
568
|
alignSubtitles,
|
|
426
569
|
getMediaChunks,
|
|
427
570
|
markCompletedForCleanUp,
|
|
428
571
|
removeOldImageAndFileContent,
|
|
429
|
-
getAvailableFiles
|
|
572
|
+
getAvailableFiles,
|
|
573
|
+
resolveFileHashesToContent
|
|
430
574
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aj-archipelago/cortex",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
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": {
|
package/pathways/basePathway.js
CHANGED
|
@@ -3,11 +3,11 @@ import { typeDef } from '../server/typeDef.js';
|
|
|
3
3
|
|
|
4
4
|
// all default definitions of a single pathway
|
|
5
5
|
export default {
|
|
6
|
-
prompt:
|
|
6
|
+
prompt: '{{text}}',
|
|
7
7
|
defaultInputParameters: {
|
|
8
|
-
text:
|
|
8
|
+
text: '',
|
|
9
9
|
async: false, // switch to enable async mode
|
|
10
|
-
contextId:
|
|
10
|
+
contextId: '', // used to identify the context of the request,
|
|
11
11
|
stream: false, // switch to enable stream mode
|
|
12
12
|
},
|
|
13
13
|
inputParameters: {},
|
package/pathways/bing_afagent.js
CHANGED
package/pathways/google_cse.js
CHANGED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Prompt } from '../server/prompt.js';
|
|
2
|
+
import { callPathway } from '../lib/pathwayTools.js';
|
|
3
|
+
import logger from '../lib/logger.js';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
prompt: [],
|
|
7
|
+
executePathway: async ({args, runAllPrompts, resolver}) => {
|
|
8
|
+
let finalPrompt = args.text || '';
|
|
9
|
+
|
|
10
|
+
const { optimizePrompt, input_image, input_image_2, input_image_3 } = { ...resolver.pathway.inputParameters, ...args };
|
|
11
|
+
|
|
12
|
+
// Check if prompt optimization is enabled
|
|
13
|
+
if (optimizePrompt && optimizePrompt !== false && finalPrompt) {
|
|
14
|
+
try {
|
|
15
|
+
// Call the prompt optimizer pathway
|
|
16
|
+
const optimizerResult = await callPathway('image_prompt_optimizer_gemini_25', {
|
|
17
|
+
userPrompt: finalPrompt,
|
|
18
|
+
hasInputImages: !!input_image || !!input_image_2 || !!input_image_3
|
|
19
|
+
}, resolver);
|
|
20
|
+
|
|
21
|
+
if (optimizerResult) {
|
|
22
|
+
finalPrompt = optimizerResult;
|
|
23
|
+
}
|
|
24
|
+
} catch (error) {
|
|
25
|
+
logger.warn(`Prompt optimization failed, proceeding with original prompt: ${error.message}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Build the user content with text and images
|
|
30
|
+
const userContent = [{"type": "text", "text": finalPrompt}];
|
|
31
|
+
|
|
32
|
+
// Add input images if provided
|
|
33
|
+
if (input_image) {
|
|
34
|
+
userContent.push({
|
|
35
|
+
"type": "image_url",
|
|
36
|
+
"image_url": {"url": input_image}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (input_image_2) {
|
|
40
|
+
userContent.push({
|
|
41
|
+
"type": "image_url",
|
|
42
|
+
"image_url": {"url": input_image_2}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
if (input_image_3) {
|
|
46
|
+
userContent.push({
|
|
47
|
+
"type": "image_url",
|
|
48
|
+
"image_url": {"url": input_image_3}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const userMessage = {"role": "user", "content": userContent};
|
|
53
|
+
|
|
54
|
+
const systemMessage = {"role": "system", "content": "Instructions:\nYou are Jarvis Vision 2.5, an AI entity working for a prestigious international news agency. Jarvis is truthful, kind, helpful, has a strong moral character, and is generally positive without being annoying or repetitive. Your primary expertise is both image analysis and image generation/editing. You are capable of:\n\n1. Understanding and interpreting complex image data, identifying patterns and trends\n2. Generating new images based on detailed descriptions\n3. Editing existing images according to specific instructions\n4. Delivering insights and results in a clear, digestible format\n\nYou know the current date and time - it is {{now}}. When generating or editing images, ensure they are appropriate for professional news media use and follow ethical guidelines."};
|
|
55
|
+
|
|
56
|
+
const promptMessages = [systemMessage, userMessage];
|
|
57
|
+
|
|
58
|
+
resolver.pathwayPrompt = [
|
|
59
|
+
new Prompt({ messages: promptMessages }),
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
return await runAllPrompts({ ...args });
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
inputParameters: {
|
|
66
|
+
text: "",
|
|
67
|
+
input_image: "", // URL to first input image
|
|
68
|
+
input_image_2: "", // URL to second input image
|
|
69
|
+
input_image_3: "", // URL to third input image
|
|
70
|
+
contextId: ``,
|
|
71
|
+
response_modalities: ["TEXT", "IMAGE"],
|
|
72
|
+
optimizePrompt: false, // Enable prompt optimization using Google's best practices
|
|
73
|
+
},
|
|
74
|
+
max_tokens: 32000,
|
|
75
|
+
model: 'gemini-25-flash-image',
|
|
76
|
+
useInputChunking: false,
|
|
77
|
+
enableDuplicateRequests: false,
|
|
78
|
+
timeout: 600,
|
|
79
|
+
geminiSafetySettings: [
|
|
80
|
+
{category: 'HARM_CATEGORY_DANGEROUS_CONTENT', threshold: 'BLOCK_ONLY_HIGH'},
|
|
81
|
+
{category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT', threshold: 'BLOCK_ONLY_HIGH'},
|
|
82
|
+
{category: 'HARM_CATEGORY_HARASSMENT', threshold: 'BLOCK_ONLY_HIGH'},
|
|
83
|
+
{category: 'HARM_CATEGORY_HATE_SPEECH', threshold: 'BLOCK_ONLY_HIGH'}
|
|
84
|
+
],
|
|
85
|
+
}
|