@aj-archipelago/cortex 1.3.53 → 1.3.55

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 CHANGED
@@ -326,6 +326,33 @@ var config = convict({
326
326
  "Content-Type": "application/json"
327
327
  },
328
328
  },
329
+ "replicate-flux-kontext-pro": {
330
+ "type": "REPLICATE-API",
331
+ "url": "https://api.replicate.com/v1/models/black-forest-labs/flux-kontext-pro/predictions",
332
+ "headers": {
333
+ "Prefer": "wait",
334
+ "Authorization": "Token {{REPLICATE_API_KEY}}",
335
+ "Content-Type": "application/json"
336
+ },
337
+ },
338
+ "replicate-flux-kontext-max": {
339
+ "type": "REPLICATE-API",
340
+ "url": "https://api.replicate.com/v1/models/black-forest-labs/flux-kontext-max/predictions",
341
+ "headers": {
342
+ "Prefer": "wait",
343
+ "Authorization": "Token {{REPLICATE_API_KEY}}",
344
+ "Content-Type": "application/json"
345
+ },
346
+ },
347
+ "replicate-multi-image-kontext-max": {
348
+ "type": "REPLICATE-API",
349
+ "url": "https://api.replicate.com/v1/models/flux-kontext-apps/multi-image-kontext-max/predictions",
350
+ "headers": {
351
+ "Prefer": "wait",
352
+ "Authorization": "Token {{REPLICATE_API_KEY}}",
353
+ "Content-Type": "application/json"
354
+ },
355
+ },
329
356
  "azure-video-translate": {
330
357
  "type": "AZURE-VIDEO-TRANSLATE",
331
358
  "url": "https://eastus.api.cognitive.microsoft.com/videotranslation",
@@ -120,6 +120,8 @@ term~N (Match terms similar to "term", edit distance N)
120
120
 
121
121
  AI_GROUNDING_INSTRUCTIONS: "# Grounding Responses\n\nIf you base part or all of your response on one or more search results, you MUST cite the source using a custom markdown directive of the form :cd_source[searchResultId]. There is NO other valid way to cite a source and a good UX depends on you using this directive correctly. Do not include other clickable links to the sourcewhen using the :cd_source[searchResultId] directive. Every search result has a unique searchResultId. You must include it verbatim, copied directly from the search results. Place the directives at the end of the phrase, sentence or paragraph that is grounded in that particular search result. If you are citing multiple search results, use multiple individual :cd_source[searchResultId] directives (e.g. :cd_source[searchResultId1] :cd_source[searchResultId2] :cd_source[searchResultId3] etc.)",
122
122
 
123
+ AI_AVAILABLE_FILES: "# Available Files\n\nThe following files are available for you to use in your tool calls or responses:\n{{{availableFiles}}}\n",
124
+
123
125
  AI_MEMORY_INSTRUCTIONS: `# Memory Instructions
124
126
 
125
127
  You have a memory system that contains important details, instructions, and context. Consult your memories when formulating a response to ensure your answers reflect previous learnings and context.
package/lib/util.js CHANGED
@@ -310,6 +310,60 @@ function removeImageAndFileFromMessage(message) {
310
310
  return modifiedMessage;
311
311
  }
312
312
 
313
+ // Helper function to extract file URLs from a content object
314
+ function extractFileUrlsFromContent(contentObj) {
315
+ const urls = [];
316
+ if (contentObj.type === 'image_url' && contentObj.image_url?.url) {
317
+ urls.push(contentObj.image_url.url);
318
+ } else if (contentObj.type === 'file' && contentObj.file) {
319
+ urls.push(contentObj.file);
320
+ }
321
+ return urls;
322
+ }
323
+
324
+ function getAvailableFiles(chatHistory) {
325
+ const availableFiles = [];
326
+
327
+ if (!chatHistory || !Array.isArray(chatHistory)) {
328
+ return availableFiles;
329
+ }
330
+
331
+ for (const message of chatHistory) {
332
+ if (!message || !message.content) {
333
+ continue;
334
+ }
335
+
336
+ // Handle array content
337
+ if (Array.isArray(message.content)) {
338
+ for (const content of message.content) {
339
+ try {
340
+ const contentObj = typeof content === 'string' ? JSON.parse(content) : content;
341
+ availableFiles.push(...extractFileUrlsFromContent(contentObj));
342
+ } catch (e) {
343
+ // Not JSON or couldn't be parsed, continue
344
+ continue;
345
+ }
346
+ }
347
+ }
348
+ // Handle string content
349
+ else if (typeof message.content === 'string') {
350
+ try {
351
+ const contentObj = JSON.parse(message.content);
352
+ availableFiles.push(...extractFileUrlsFromContent(contentObj));
353
+ } catch (e) {
354
+ // Not JSON or couldn't be parsed, continue
355
+ continue;
356
+ }
357
+ }
358
+ // Handle object content
359
+ else if (typeof message.content === 'object') {
360
+ availableFiles.push(...extractFileUrlsFromContent(message.content));
361
+ }
362
+ }
363
+
364
+ return availableFiles;
365
+ }
366
+
313
367
  export {
314
368
  getUniqueId,
315
369
  getSearchResultId,
@@ -322,5 +376,6 @@ export {
322
376
  alignSubtitles,
323
377
  getMediaChunks,
324
378
  markCompletedForCleanUp,
325
- removeOldImageAndFileContent
379
+ removeOldImageAndFileContent,
380
+ getAvailableFiles
326
381
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aj-archipelago/cortex",
3
- "version": "1.3.53",
3
+ "version": "1.3.55",
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": {
@@ -33,7 +33,7 @@
33
33
  "type": "module",
34
34
  "homepage": "https://github.com/aj-archipelago/cortex#readme",
35
35
  "dependencies": {
36
- "@aj-archipelago/subvibe": "^1.0.10",
36
+ "@aj-archipelago/subvibe": "^1.0.12",
37
37
  "@apollo/server": "^4.7.3",
38
38
  "@apollo/server-plugin-response-cache": "^4.1.2",
39
39
  "@apollo/utils.keyvadapter": "^3.0.0",
@@ -13,5 +13,7 @@ export default {
13
13
  output_format: "webp",
14
14
  output_quality: 80,
15
15
  steps: 4,
16
+ input_image: "", // URL to input image for models that support it
17
+ input_image_2: "", // URL to second input image for models that support it
16
18
  },
17
19
  };
@@ -5,7 +5,7 @@ const MAX_TOOL_CALLS = 50;
5
5
  import { callPathway, callTool, say } from '../../../lib/pathwayTools.js';
6
6
  import logger from '../../../lib/logger.js';
7
7
  import { config } from '../../../config.js';
8
- import { chatArgsHasImageUrl, removeOldImageAndFileContent } from '../../../lib/util.js';
8
+ import { chatArgsHasImageUrl, removeOldImageAndFileContent, getAvailableFiles } from '../../../lib/util.js';
9
9
  import { Prompt } from '../../../server/prompt.js';
10
10
  import { getToolsForEntity, loadEntityConfig } from './tools/shared/sys_entity_tools.js';
11
11
 
@@ -275,7 +275,7 @@ export default {
275
275
  const instructionTemplates = entityInstructions ? (entityInstructions + '\n\n') : `{{renderTemplate AI_COMMON_INSTRUCTIONS}}\n\n{{renderTemplate AI_EXPERTISE}}\n\n`;
276
276
 
277
277
  const promptMessages = [
278
- {"role": "system", "content": `${promptPrefix}${instructionTemplates}{{renderTemplate AI_TOOLS}}\n\n{{renderTemplate AI_SEARCH_RULES}}\n\n{{renderTemplate AI_SEARCH_SYNTAX}}\n\n{{renderTemplate AI_GROUNDING_INSTRUCTIONS}}\n\n${memoryTemplates}{{renderTemplate AI_DATETIME}}`},
278
+ {"role": "system", "content": `${promptPrefix}${instructionTemplates}{{renderTemplate AI_TOOLS}}\n\n{{renderTemplate AI_SEARCH_RULES}}\n\n{{renderTemplate AI_SEARCH_SYNTAX}}\n\n{{renderTemplate AI_GROUNDING_INSTRUCTIONS}}\n\n${memoryTemplates}{{renderTemplate AI_AVAILABLE_FILES}}\n\n{{renderTemplate AI_DATETIME}}`},
279
279
  "{{chatHistory}}",
280
280
  ];
281
281
 
@@ -294,6 +294,8 @@ export default {
294
294
  args.chatHistory = args.chatHistory.slice(-20);
295
295
  }
296
296
 
297
+ const availableFiles = getAvailableFiles(args.chatHistory);
298
+
297
299
  // remove old image and file content
298
300
  const visionContentPresent = chatArgsHasImageUrl(args);
299
301
  visionContentPresent && (args.chatHistory = removeOldImageAndFileContent(args.chatHistory));
@@ -327,6 +329,7 @@ export default {
327
329
  let response = await runAllPrompts({
328
330
  ...args,
329
331
  chatHistory: currentMessages,
332
+ availableFiles,
330
333
  tools: entityToolsOpenAiFormat,
331
334
  tool_choice: memoryLookupRequired ? "required" : "auto"
332
335
  });
@@ -1,5 +1,5 @@
1
1
  // sys_tool_image.js
2
- // Entity tool that creates images for the entity to show to the user
2
+ // Entity tool that creates and modifies images for the entity to show to the user
3
3
  import { callPathway } from '../../../../lib/pathwayTools.js';
4
4
 
5
5
  export default {
@@ -10,7 +10,7 @@ export default {
10
10
  model: 'oai-gpt4o',
11
11
  },
12
12
  timeout: 300,
13
- toolDefinition: {
13
+ toolDefinition: [{
14
14
  type: "function",
15
15
  icon: "🎨",
16
16
  function: {
@@ -36,6 +36,36 @@ export default {
36
36
  }
37
37
  }
38
38
  },
39
+ {
40
+ type: "function",
41
+ icon: "🔄",
42
+ function: {
43
+ name: "ModifyImage",
44
+ description: "Use when asked to modify, transform, or edit an existing image. This tool can apply various transformations like style changes, artistic effects, or specific modifications to an image that has been previously uploaded or generated. It takes up to two input images as a reference and outputs a new image based on the instructions.",
45
+ parameters: {
46
+ type: "object",
47
+ properties: {
48
+ inputImage: {
49
+ type: "string",
50
+ description: "The first image URL copied exactly from an image_url field in your chat context."
51
+ },
52
+ inputImage2: {
53
+ type: "string",
54
+ description: "The second input image URL copied exactly from an image_url field in your chat context if there is one."
55
+ },
56
+ detailedInstructions: {
57
+ type: "string",
58
+ description: "A very detailed prompt describing how you want to modify the image. Be specific about the changes you want to make, including style changes, artistic effects, or specific modifications. The more detailed and descriptive the prompt, the better the result."
59
+ },
60
+ userMessage: {
61
+ type: "string",
62
+ description: "A user-friendly message that describes what you're doing with this tool"
63
+ }
64
+ },
65
+ required: ["inputImage", "detailedInstructions", "userMessage"]
66
+ }
67
+ }
68
+ }],
39
69
 
40
70
  executePathway: async ({args, runAllPrompts, resolver}) => {
41
71
  const pathwayResolver = resolver;
@@ -46,8 +76,27 @@ export default {
46
76
  let numberResults = args.numberResults || 1;
47
77
  let negativePrompt = args.negativePrompt || "";
48
78
 
79
+ // If we have an input image, use the flux-kontext-max model
80
+ if (args.inputImage) {
81
+ model = "replicate-flux-kontext-max";
82
+ }
83
+
84
+ // If we have two input images, use the multi-image-kontext-max model
85
+ if (args.inputImage2) {
86
+ model = "replicate-multi-image-kontext-max";
87
+ }
88
+
49
89
  pathwayResolver.tool = JSON.stringify({ toolUsed: "image" });
50
- return await callPathway('image_flux', {...args, text: prompt, negativePrompt, numberResults, model, stream: false });
90
+ return await callPathway('image_flux', {
91
+ ...args,
92
+ text: prompt,
93
+ negativePrompt,
94
+ numberResults,
95
+ model,
96
+ stream: false,
97
+ input_image: args.inputImage,
98
+ input_image_2: args.inputImage2,
99
+ });
51
100
 
52
101
  } catch (e) {
53
102
  pathwayResolver.logError(e.message ?? e);
@@ -91,6 +91,50 @@ class ReplicateApiPlugin extends ModelPlugin {
91
91
  };
92
92
  break;
93
93
  }
94
+ case "replicate-flux-kontext-pro":
95
+ case "replicate-flux-kontext-max": {
96
+ const validRatios = [
97
+ '1:1', '16:9', '21:9', '3:2', '2:3', '4:5',
98
+ '5:4', '3:4', '4:3', '9:16', '9:21', 'match_input_image'
99
+ ];
100
+
101
+ let safetyTolerance = combinedParameters.safety_tolerance || 3;
102
+ if(combinedParameters.input_image){
103
+ safetyTolerance = Math.min(safetyTolerance, 2);
104
+ }
105
+
106
+ requestParameters = {
107
+ input: {
108
+ prompt: modelPromptText,
109
+ input_image: combinedParameters.input_image,
110
+ aspect_ratio: validRatios.includes(combinedParameters.aspectRatio) ? combinedParameters.aspectRatio : "1:1",
111
+ safety_tolerance: safetyTolerance,
112
+ },
113
+ };
114
+ break;
115
+ }
116
+ case "replicate-multi-image-kontext-max": {
117
+ const validRatios = [
118
+ '1:1', '16:9', '21:9', '3:2', '2:3', '4:5',
119
+ '5:4', '3:4', '4:3', '9:16', '9:21', 'match_input_image'
120
+ ];
121
+
122
+ let safetyTolerance = combinedParameters.safety_tolerance || 3;
123
+ if(combinedParameters.input_image_1 || combinedParameters.input_image) {
124
+ safetyTolerance = Math.min(safetyTolerance, 2);
125
+ }
126
+
127
+ requestParameters = {
128
+ input: {
129
+ prompt: modelPromptText,
130
+ input_image_1: combinedParameters.input_image_1 || combinedParameters.input_image,
131
+ input_image_2: combinedParameters.input_image_2,
132
+ aspect_ratio: validRatios.includes(combinedParameters.aspectRatio) ? combinedParameters.aspectRatio : "1:1",
133
+ safety_tolerance: safetyTolerance,
134
+ },
135
+ };
136
+ break;
137
+ }
94
138
  }
95
139
 
96
140
  return requestParameters;