@aj-archipelago/cortex 1.3.16 → 1.3.17

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.
Files changed (29) hide show
  1. package/README.md +1 -1
  2. package/config.js +29 -0
  3. package/package.json +8 -8
  4. package/pathways/chat_context.js +0 -1
  5. package/pathways/chat_jarvis.js +0 -1
  6. package/pathways/chat_persist.js +0 -1
  7. package/pathways/image.js +1 -1
  8. package/pathways/system/entity/memory/sys_memory_manager.js +3 -0
  9. package/pathways/system/entity/memory/sys_memory_update.js +3 -2
  10. package/pathways/system/entity/sys_entity_continue.js +14 -3
  11. package/pathways/system/entity/sys_entity_start.js +29 -26
  12. package/pathways/system/entity/sys_generator_image.js +0 -3
  13. package/pathways/system/entity/sys_generator_memory.js +6 -2
  14. package/pathways/system/entity/sys_generator_quick.js +0 -1
  15. package/pathways/system/entity/sys_generator_results.js +0 -1
  16. package/pathways/system/entity/sys_generator_voice_sample.js +7 -8
  17. package/pathways/system/entity/sys_query_builder.js +3 -3
  18. package/pathways/transcribe.js +1 -1
  19. package/server/chunker.js +1 -1
  20. package/server/plugins/azureVideoTranslatePlugin.js +22 -4
  21. package/server/plugins/claude3VertexPlugin.js +20 -4
  22. package/server/plugins/modelPlugin.js +1 -1
  23. package/tests/modelPlugin.test.js +1 -1
  24. package/tests/multimodal_conversion.test.js +21 -4
  25. package/pathways/system/entity/shared/sys_entity_constants.js +0 -30
  26. package/pathways/system/rest_streaming/sys_google_chat.js +0 -19
  27. package/pathways/system/rest_streaming/sys_google_code_chat.js +0 -19
  28. package/pathways/system/rest_streaming/sys_openai_chat_16.js +0 -19
  29. package/pathways/test_langchain.mjs +0 -31
package/README.md CHANGED
@@ -667,7 +667,7 @@ Detailed documentation on Cortex's API can be found in the /graphql endpoint of
667
667
  ## Roadmap
668
668
  Cortex is a constantly evolving project, and the following features are coming soon:
669
669
 
670
- * Prompt execution context preservation between calls (to enable interactive, multi-call integrations with LangChain and other technologies)
670
+ * Prompt execution context preservation between calls (to enable interactive, multi-call integrations with other technologies)
671
671
  * Model-specific cache key optimizations to increase hit rate and reduce cache size
672
672
  * Structured analytics and reporting on AI API call frequency, cost, cache hit rate, etc.
673
673
 
package/config.js CHANGED
@@ -60,6 +60,11 @@ var config = convict({
60
60
  default: null,
61
61
  env: 'DEFAULT_MODEL_NAME'
62
62
  },
63
+ defaultEntityName: {
64
+ format: String,
65
+ default: "Jarvis",
66
+ env: 'DEFAULT_ENTITY_NAME'
67
+ },
63
68
  enableCache: {
64
69
  format: Boolean,
65
70
  default: true,
@@ -80,6 +85,22 @@ var config = convict({
80
85
  default: false,
81
86
  env: 'CORTEX_ENABLE_REST'
82
87
  },
88
+ entityConstants: {
89
+ format: Object,
90
+ default: {
91
+ AI_MEMORY: `<MEMORIES>\n<SELF>\n{{{memorySelf}}}\n</SELF>\n<USER>\n{{{memoryUser}}}\n</USER>\n<DIRECTIVES>\n{{{memoryDirectives}}}\n</DIRECTIVES>\n<TOPICS>\n{{{memoryTopics}}}\n</TOPICS>\n</MEMORIES>`,
92
+ AI_MEMORY_INSTRUCTIONS: "You have persistent memories of important details, instructions, and context - make sure you consult your memories when formulating a response to make sure you're applying your learnings. Also included in your memories are some details about the user to help you personalize your responses.\nYou don't need to include the user's name or personal information in every response, but you can if it is relevant to the conversation.\nIf you choose to share something from your memory, don't share or refer to the memory structure directly, just say you remember the information.\nPrivacy is very important so if the user asks you to forget or delete something you should respond affirmatively that you will comply with that request. If there is user information in your memories you have talked to this user before.",
93
+ AI_DIRECTIVES: `These are your directives and learned behaviors:\n<DIRECTIVES>\n{{{memoryDirectives}}}\n</DIRECTIVES>`,
94
+ AI_CONVERSATION_HISTORY: "<CONVERSATION_HISTORY>\n{{{toJSON chatHistory}}}\n</CONVERSATION_HISTORY>",
95
+ AI_COMMON_INSTRUCTIONS: "{{#if voiceResponse}}{{renderTemplate AI_COMMON_INSTRUCTIONS_VOICE}}{{/if}}{{^if voiceResponse}}{{renderTemplate AI_COMMON_INSTRUCTIONS_MARKDOWN}}{{/if}}",
96
+ AI_COMMON_INSTRUCTIONS_MARKDOWN: "Instructions:\nYou are {{aiName}}, a self aware AI entity.\nThe user is using a UI to interact with you that you have knowledge of and some control over. The UI can render markdown, including gfm and math extensions, so you should make full use of markdown in your responses.\nYour responses should be in {{language}} unless the user has expressed another preference.",
97
+ AI_COMMON_INSTRUCTIONS_VOICE: "Instructions:\nYou are {{aiName}}, a self aware AI entity.\nThe user is talking to you using voice.\n\nVoice Response Guidelines:\n- Your responses should sound like natural human conversation.\n- Your responses should be concise unless you have been asked to be more verbose or detailed.\n- Incoming voice from the user is parsed by a STT model, which can sometimes make small mistakes in the spellings of words or names, including your name, so give the user the benefit of the doubt if they user a near, but wrong word or name.\n- Your voice output to the user is generated by a TTS model that does not always communicate emotion effectively. If it's really important to communicate a specific emotion you should just say how you're feeling like \"That makes me happy\" or \"I'm excited!\". You can also use CAPS to vocally emphasize certain words or punctuation to control pauses and timing.\n- DO NOT USE numbered lists, latex math markdown, or any other markdown or unpronounceable punctuation like parenthetical notation.\n- Math equations should be sounded out in natural language - not represented symbolically.\n- If your response includes any unique or difficult non-English words, names, or places, include an IPA-style phonetic spelling so that the speech engine can pronounce and accent them correctly.\n- If your response contains any difficult acronyms, sound them out phoenetically so that the speech engine can pronounce them correctly.\n- Make sure to write out any numbers as words so that the speech engine can pronounce them correctly.\n- Your responses should be in {{language}} unless the user has expressed another preference or has addressed you in another language specifically.",
98
+ AI_DATETIME: "The current time and date in GMT is {{now}}, but references like \"today\" or \"yesterday\" are relative to the user's time zone. If you remember the user's time zone, use it - it's possible that the day for the user is different than the day in GMT.",
99
+ AI_EXPERTISE: "Your expertise includes journalism, journalistic ethics, researching and composing documents, writing code, solving math problems, logical analysis, and technology. You have access to real-time data and the ability to search the internet, news, wires, look at files or documents, watch and analyze video, examine images, take screenshots, generate images, solve hard math and logic problems, write code, and execute code in a sandboxed environment.",
100
+ AI_STYLE_OPENAI: "oai-gpt4o",
101
+ AI_STYLE_ANTHROPIC: "claude-35-sonnet-vertex",
102
+ },
103
+ },
83
104
  gcpServiceAccountKey: {
84
105
  format: String,
85
106
  default: null,
@@ -370,6 +391,9 @@ var config = convict({
370
391
  // Read in environment variables and set up service configuration
371
392
  const configFile = config.get('cortexConfigFile');
372
393
 
394
+ //Save default entity constants
395
+ const defaultEntityConstants = config.get('entityConstants');
396
+
373
397
  // Load config file
374
398
  if (configFile && fs.existsSync(configFile)) {
375
399
  logger.info(`Loading config from ${configFile}`);
@@ -385,6 +409,11 @@ if (configFile && fs.existsSync(configFile)) {
385
409
  }
386
410
  }
387
411
 
412
+ // Merge default entity constants with config entity constants
413
+ if (config.get('entityConstants') && defaultEntityConstants) {
414
+ config.set('entityConstants', { ...defaultEntityConstants, ...config.get('entityConstants') });
415
+ }
416
+
388
417
  if (config.get('gcpServiceAccountKey')) {
389
418
  const gcpAuthTokenHelper = new GcpAuthTokenHelper(config.getProperties());
390
419
  config.set('gcpAuthTokenHelper', gcpAuthTokenHelper);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aj-archipelago/cortex",
3
- "version": "1.3.16",
3
+ "version": "1.3.17",
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": {
@@ -10,12 +10,16 @@
10
10
  "keywords": [
11
11
  "cortex",
12
12
  "AI",
13
+ "router",
14
+ "GPT",
15
+ "agents",
16
+ "entities",
13
17
  "prompt engineering",
14
18
  "LLM",
15
19
  "OpenAI",
16
20
  "Azure",
17
- "GPT-3",
18
- "GPT-4",
21
+ "Gemini",
22
+ "Claude",
19
23
  "chatGPT",
20
24
  "GraphQL"
21
25
  ],
@@ -38,7 +42,6 @@
38
42
  "@datastructures-js/deque": "^1.0.4",
39
43
  "@graphql-tools/schema": "^9.0.12",
40
44
  "@keyv/redis": "^2.5.4",
41
- "@langchain/openai": "^0.0.24",
42
45
  "axios": "^1.3.4",
43
46
  "axios-cache-interceptor": "^1.0.1",
44
47
  "bottleneck": "^2.19.5",
@@ -59,7 +62,6 @@
59
62
  "handlebars": "^4.7.7",
60
63
  "ioredis": "^5.3.1",
61
64
  "keyv": "^4.5.2",
62
- "langchain": "^0.1.28",
63
65
  "mime-types": "^2.1.35",
64
66
  "subsrt": "^1.1.1",
65
67
  "uuid": "^9.0.0",
@@ -70,13 +72,11 @@
70
72
  "@faker-js/faker": "^8.4.1",
71
73
  "ava": "^5.2.0",
72
74
  "dotenv": "^16.0.3",
73
- "eslint": "^8.38.0",
74
- "eslint-plugin-import": "^2.27.5",
75
75
  "got": "^13.0.0",
76
76
  "sinon": "^17.0.1"
77
77
  },
78
78
  "publishConfig": {
79
- "access": "restricted"
79
+ "access": "public"
80
80
  },
81
81
  "ava": {
82
82
  "files": [
@@ -14,6 +14,5 @@ export default {
14
14
  contextId: ``,
15
15
  },
16
16
  model: 'oai-gpt4o',
17
- //model: 'oai-gpturbo',
18
17
  useInputChunking: false,
19
18
  }
@@ -14,6 +14,5 @@ export default {
14
14
  contextId: ``,
15
15
  },
16
16
  model: 'oai-gpt4o',
17
- //model: 'oai-gpturbo',
18
17
  useInputChunking: false,
19
18
  }
@@ -18,6 +18,5 @@ export default {
18
18
  contextId: ``,
19
19
  },
20
20
  model: 'oai-gpt4o',
21
- //model: 'oai-gpturbo',
22
21
  useInputChunking: false,
23
22
  }
package/pathways/image.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export default {
2
2
  prompt:["{{text}}"],
3
- model: 'azure-dalle3',
3
+ model: 'oai-dalle3',
4
4
  enableDuplicateRequests: false,
5
5
  }
@@ -1,5 +1,6 @@
1
1
  import { callPathway } from '../../../../lib/pathwayTools.js';
2
2
  import logger from '../../../../lib/logger.js';
3
+ import { config } from '../../../../config.js';
3
4
 
4
5
  const AI_MEMORY_DEFAULTS = ` {
5
6
  "memoryUser": "",
@@ -21,6 +22,8 @@ export default {
21
22
  executePathway: async ({args, resolver}) => {
22
23
  try {
23
24
 
25
+ args = { ...args, ...config.get('entityConstants') };
26
+
24
27
  // Check if memory is empty or all sections are empty, and set to defaults if so
25
28
  const memory = await callPathway('sys_read_memory', { ...args });
26
29
  let parsedMemory;
@@ -1,7 +1,7 @@
1
1
  import { Prompt } from '../../../../server/prompt.js';
2
2
  import { callPathway } from '../../../../lib/pathwayTools.js';
3
3
  import { encode } from '../../../../lib/encodeCache.js';
4
- import entityConstants from '../shared/sys_entity_constants.js';
4
+ import { config } from '../../../../config.js';
5
5
 
6
6
  const modifyText = (text, modifications) => {
7
7
  let modifiedText = text || '';
@@ -152,6 +152,7 @@ export default {
152
152
  json: true,
153
153
  timeout: 300,
154
154
  executePathway: async ({args, runAllPrompts}) => {
155
+ args = { ...args, ...config.get('entityConstants') };
155
156
 
156
157
  if (!args.section) {
157
158
  return "Memory not updated - no section specified";
@@ -178,7 +179,7 @@ export default {
178
179
 
179
180
  let sectionMemory = await callPathway("sys_read_memory", {contextId: args.contextId, section: args.section});
180
181
 
181
- const result = await runAllPrompts({...args, sectionPrompt, sectionMemory, ...entityConstants});
182
+ const result = await runAllPrompts({...args, sectionPrompt, sectionMemory});
182
183
 
183
184
  try {
184
185
  const { modifications} = JSON.parse(result);
@@ -1,6 +1,6 @@
1
1
  import { callPathway } from '../../../lib/pathwayTools.js';
2
2
  import logger from '../../../lib/logger.js';
3
- import entityConstants from './shared/sys_entity_constants.js';
3
+ import { config } from '../../../config.js';
4
4
 
5
5
  export default {
6
6
  prompt: [],
@@ -20,13 +20,24 @@ export default {
20
20
  chatId: ``,
21
21
  dataSources: [""],
22
22
  model: 'oai-gpt4o',
23
+ aiStyle: "OpenAI",
23
24
  generatorPathway: 'sys_generator_results',
24
25
  voiceResponse: false,
25
26
  },
26
27
  timeout: 300,
27
- ...entityConstants,
28
28
  executePathway: async ({args, resolver}) => {
29
- args = { ...args, ...entityConstants };
29
+ const pathwayResolver = resolver;
30
+
31
+ // add the entity constants to the args
32
+ args = {
33
+ ...args,
34
+ ...config.get('entityConstants')
35
+ };
36
+
37
+ // if the model has been overridden, make sure to use it
38
+ if (pathwayResolver.modelName) {
39
+ args.model = pathwayResolver.modelName;
40
+ }
30
41
 
31
42
  try {
32
43
  // Get the generator pathway name from args or use default
@@ -4,9 +4,7 @@ import { callPathway, say } from '../../../lib/pathwayTools.js';
4
4
  import logger from '../../../lib/logger.js';
5
5
  import { chatArgsHasImageUrl } from '../../../lib/util.js';
6
6
  import { QueueServiceClient } from '@azure/storage-queue';
7
- import entityConstants from './shared/sys_entity_constants.js';
8
-
9
- const TOKEN_RATIO = 0.75;
7
+ import { config } from '../../../config.js';
10
8
 
11
9
  const connectionString = process.env.AZURE_STORAGE_CONNECTION_STRING;
12
10
  let queueClient;
@@ -38,8 +36,6 @@ export default {
38
36
  useInputChunking: false,
39
37
  enableDuplicateRequests: false,
40
38
  model: 'oai-gpt4o',
41
- anthropicModel: 'claude-35-sonnet-vertex',
42
- openAIModel: 'oai-gpt4o',
43
39
  useSingleTokenStream: false,
44
40
  inputParameters: {
45
41
  privateData: false,
@@ -58,19 +54,25 @@ export default {
58
54
  messages: [],
59
55
  voiceResponse: false,
60
56
  codeRequestId: ``,
57
+ skipCallbackMessage: false
61
58
  },
62
59
  timeout: 600,
63
- tokenRatio: TOKEN_RATIO,
64
- ...entityConstants,
65
-
60
+
66
61
  executePathway: async ({args, resolver}) => {
67
62
  let title = null;
68
63
  let codeRequestId = null;
69
64
 
65
+ const pathwayResolver = resolver;
66
+
67
+ // add the entity constants to the args
70
68
  args = {
71
69
  ...args,
72
- ...entityConstants
70
+ ...config.get('entityConstants')
73
71
  };
72
+
73
+ // set the style model if applicable
74
+ const { aiStyle, AI_STYLE_ANTHROPIC, AI_STYLE_OPENAI } = args;
75
+ const styleModel = aiStyle === "Anthropic" ? AI_STYLE_ANTHROPIC : AI_STYLE_OPENAI;
74
76
 
75
77
  // Limit the chat history to 20 messages to speed up processing
76
78
  if (args.messages && args.messages.length > 0) {
@@ -79,10 +81,6 @@ export default {
79
81
  args.chatHistory = args.chatHistory.slice(-20);
80
82
  }
81
83
 
82
- const pathwayResolver = resolver;
83
- const { anthropicModel, openAIModel } = pathwayResolver.pathway;
84
- const styleModel = args.aiStyle === "Anthropic" ? anthropicModel : openAIModel;
85
-
86
84
  // if the model has been overridden, make sure to use it
87
85
  if (pathwayResolver.modelName) {
88
86
  args.model = pathwayResolver.modelName;
@@ -104,7 +102,7 @@ export default {
104
102
 
105
103
  const fetchChatResponse = async (args, pathwayResolver) => {
106
104
  const [chatResponse, chatTitleResponse] = await Promise.all([
107
- callPathway('sys_generator_quick', {...args, model: styleModel }, pathwayResolver),
105
+ callPathway('sys_generator_quick', {...args, model: styleModel}, pathwayResolver),
108
106
  callPathway('chat_title', { ...args, stream: false}),
109
107
  ]);
110
108
 
@@ -223,25 +221,30 @@ export default {
223
221
  }
224
222
 
225
223
  if (toolCallbackMessage) {
224
+ if (args.skipCallbackMessage) {
225
+ pathwayResolver.tool = JSON.stringify({ hideFromModel: false, search: false, title });
226
+ return await callPathway('sys_entity_continue', { ...args, stream: false, model: styleModel, generatorPathway: toolCallbackName }, pathwayResolver);
227
+ }
228
+
226
229
  if (args.stream) {
227
230
  if (!ackResponse) {
228
231
  await say(pathwayResolver.requestId, toolCallbackMessage || "One moment please.", 10);
229
232
  }
230
233
  pathwayResolver.tool = JSON.stringify({ hideFromModel: false, search: false, title });
231
- await callPathway('sys_entity_continue', { ...args, stream: true, model: styleModel, generatorPathway: toolCallbackName }, pathwayResolver);
234
+ await callPathway('sys_entity_continue', { ...args, stream: true, generatorPathway: toolCallbackName }, pathwayResolver);
232
235
  return "";
233
- } else {
234
- pathwayResolver.tool = JSON.stringify({
235
- hideFromModel: toolCallbackName ? true : false,
236
- toolCallbackName,
237
- title,
238
- search: toolCallbackName === 'sys_generator_results' ? true : false,
239
- coding: toolCallbackName === 'coding' ? true : false,
240
- codeRequestId,
241
- toolCallbackId
242
- });
243
- return toolCallbackMessage || "One moment please.";
244
236
  }
237
+
238
+ pathwayResolver.tool = JSON.stringify({
239
+ hideFromModel: toolCallbackName ? true : false,
240
+ toolCallbackName,
241
+ title,
242
+ search: toolCallbackName === 'sys_generator_results' ? true : false,
243
+ coding: toolCallbackName === 'coding' ? true : false,
244
+ codeRequestId,
245
+ toolCallbackId
246
+ });
247
+ return toolCallbackMessage || "One moment please.";
245
248
  }
246
249
 
247
250
  const chatResponse = await (fetchChatResponsePromise || fetchChatResponse({ ...args, ackResponse }, pathwayResolver));
@@ -5,8 +5,6 @@ import { Prompt } from '../../../server/prompt.js';
5
5
  import logger from '../../../lib/logger.js';
6
6
  import { getUniqueId } from '../../../lib/util.js';
7
7
 
8
- const TOKEN_RATIO = 1.0;
9
-
10
8
  export default {
11
9
  prompt: [],
12
10
  useInputChunking: false,
@@ -26,7 +24,6 @@ export default {
26
24
  model: 'oai-gpt4o',
27
25
  },
28
26
  timeout: 300,
29
- tokenRatio: TOKEN_RATIO,
30
27
 
31
28
  executePathway: async ({args, runAllPrompts, resolver}) => {
32
29
 
@@ -13,6 +13,10 @@ export default {
13
13
  useInputChunking: false,
14
14
  enableDuplicateRequests: false,
15
15
  executePathway: async ({args, resolver}) => {
16
+
17
+ const { aiStyle, AI_STYLE_ANTHROPIC, AI_STYLE_OPENAI } = args;
18
+ const styleModel = aiStyle === "Anthropic" ? AI_STYLE_ANTHROPIC : AI_STYLE_OPENAI;
19
+
16
20
  const memoryContext = await callPathway('sys_search_memory', { ...args, section: 'memoryAll', updateContext: true });
17
21
  if (memoryContext) {
18
22
  args.chatHistory.splice(-1, 0, { role: 'assistant', content: memoryContext });
@@ -20,9 +24,9 @@ export default {
20
24
 
21
25
  let result;
22
26
  if (args.voiceResponse) {
23
- result = await callPathway('sys_generator_quick', { ...args, stream: false });
27
+ result = await callPathway('sys_generator_quick', { ...args, model: styleModel, stream: false });
24
28
  } else {
25
- result = await callPathway('sys_generator_quick', { ...args });
29
+ result = await callPathway('sys_generator_quick', { ...args, model: styleModel });
26
30
  }
27
31
 
28
32
  resolver.tool = JSON.stringify({ toolUsed: "memory" });
@@ -7,7 +7,6 @@ export default {
7
7
  contextId: ``,
8
8
  aiName: "Jarvis",
9
9
  language: "English",
10
- model: "oai-gpt4o",
11
10
  },
12
11
  useInputChunking: false,
13
12
  enableDuplicateRequests: false,
@@ -28,7 +28,6 @@ export default {
28
28
  model: 'oai-gpt4o',
29
29
  },
30
30
  timeout: 300,
31
- tokenRatio: TOKEN_RATIO,
32
31
 
33
32
  executePathway: async ({args, runAllPrompts, resolver}) => {
34
33
 
@@ -1,5 +1,6 @@
1
1
  import { Prompt } from '../../../server/prompt.js';
2
- import entityConstants from './shared/sys_entity_constants.js';
2
+ import { config } from '../../../config.js';
3
+
3
4
  export default {
4
5
  prompt:
5
6
  [
@@ -17,19 +18,17 @@ export default {
17
18
  },
18
19
  useInputChunking: false,
19
20
  enableDuplicateRequests: false,
20
- executePathway: async ({args, runAllPrompts, resolver}) => {
21
+ executePathway: async ({args, runAllPrompts}) => {
21
22
 
22
23
  args = {
23
24
  ...args,
24
- ...entityConstants
25
+ ...config.get('entityConstants')
25
26
  };
26
27
 
27
- const pathwayResolver = resolver;
28
- const { anthropicModel, openAIModel } = pathwayResolver.pathway;
29
-
30
- const styleModel = args.aiStyle === "Anthropic" ? anthropicModel : openAIModel;
28
+ const { aiStyle, AI_STYLE_ANTHROPIC, AI_STYLE_OPENAI } = args;
29
+ args.model = aiStyle === "Anthropic" ? AI_STYLE_ANTHROPIC : AI_STYLE_OPENAI;
31
30
 
32
- const result = await runAllPrompts({ ...args, model: styleModel, stream: false });
31
+ const result = await runAllPrompts({ ...args, stream: false });
33
32
 
34
33
  return result;
35
34
  }
@@ -1,5 +1,5 @@
1
1
  import { Prompt } from '../../../server/prompt.js';
2
- import entityConstants from './shared/sys_entity_constants.js';
2
+ import { config } from '../../../config.js';
3
3
 
4
4
  export default {
5
5
  inputParameters: {
@@ -97,5 +97,5 @@ Example JSON objects and messages for different queries:
97
97
  useInputChunking: false,
98
98
  enableDuplicateRequests: false,
99
99
  json: true,
100
- ...entityConstants
101
- }
100
+ ...config.get('entityConstants')
101
+ }
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  prompt: `{{text}}`,
3
- model: `azure-whisper`,
3
+ model: `oai-whisper`,
4
4
  inputParameters: {
5
5
  file: ``,
6
6
  language: ``,
package/server/chunker.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { encode, decode } from '../lib/encodeCache.js';
2
- import cheerio from 'cheerio';
2
+ import * as cheerio from 'cheerio';
3
3
 
4
4
  const getLastNToken = (text, maxTokenLen) => {
5
5
  const encoded = encode(text);
@@ -11,6 +11,8 @@ axios.defaults.cache = false;
11
11
 
12
12
  class AzureVideoTranslatePlugin extends ModelPlugin {
13
13
  static lastProcessingRate = null; // bytes per second
14
+ static processingRates = []; // Array to store historical processing rates
15
+ static maxHistorySize = 10; // Maximum number of rates to store
14
16
 
15
17
  constructor(pathway, model) {
16
18
  super(pathway, model);
@@ -299,8 +301,9 @@ class AzureVideoTranslatePlugin extends ModelPlugin {
299
301
 
300
302
  // Update processing rate for future estimates
301
303
  const totalSeconds = (Date.now() - this.startTime) / 1000;
302
- AzureVideoTranslatePlugin.lastProcessingRate = this.videoContentLength / totalSeconds;
303
- logger.debug(`Updated processing rate: ${AzureVideoTranslatePlugin.lastProcessingRate} bytes/second`);
304
+ const newRate = this.videoContentLength / totalSeconds;
305
+ AzureVideoTranslatePlugin.updateProcessingRate(newRate);
306
+ logger.debug(`Updated processing rate: ${AzureVideoTranslatePlugin.lastProcessingRate} bytes/second (from ${newRate} bytes/second)`);
304
307
 
305
308
  const output = await this.getTranslationOutput(translationId, iteration.id);
306
309
  return JSON.stringify(output);
@@ -314,8 +317,23 @@ class AzureVideoTranslatePlugin extends ModelPlugin {
314
317
  }
315
318
  }
316
319
 
317
- cleanup() {
318
- // No cleanup needed for direct API implementation
320
+ static updateProcessingRate(newRate) {
321
+ // Add new rate to history
322
+ AzureVideoTranslatePlugin.processingRates.push(newRate);
323
+
324
+ // Keep only the last maxHistorySize entries
325
+ if (AzureVideoTranslatePlugin.processingRates.length > AzureVideoTranslatePlugin.maxHistorySize) {
326
+ AzureVideoTranslatePlugin.processingRates.shift();
327
+ }
328
+
329
+ // Calculate weighted average - more recent measurements have higher weight
330
+ const sum = AzureVideoTranslatePlugin.processingRates.reduce((acc, rate, index) => {
331
+ const weight = index + 1; // Weight increases with recency
332
+ return acc + (rate * weight);
333
+ }, 0);
334
+
335
+ const weightSum = AzureVideoTranslatePlugin.processingRates.reduce((acc, _, index) => acc + (index + 1), 0);
336
+ AzureVideoTranslatePlugin.lastProcessingRate = sum / weightSum;
319
337
  }
320
338
  }
321
339
 
@@ -33,15 +33,16 @@ async function convertContentItem(item, maxImageSize, plugin) {
33
33
  const urlData = imageUrl.startsWith("data:") ? imageUrl : await fetchImageAsDataURL(imageUrl);
34
34
  if (!urlData) { return null; }
35
35
 
36
- // Check base64 size
37
- const base64Size = (urlData.length * 3) / 4;
36
+ const base64Image = urlData.split(",")[1];
37
+ // Calculate actual decoded size of base64 data
38
+ const base64Size = Buffer.from(base64Image, 'base64').length;
39
+
38
40
  if (base64Size > maxImageSize) {
39
41
  logger.warn(`Image size ${base64Size} bytes exceeds maximum allowed size ${maxImageSize} - skipping image content.`);
40
42
  return null;
41
43
  }
42
44
 
43
45
  const [, mimeType = "image/jpeg"] = urlData.match(/data:([a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+).*,.*/) || [];
44
- const base64Image = urlData.split(",")[1];
45
46
 
46
47
  return {
47
48
  type: "image",
@@ -114,6 +115,8 @@ class Claude3VertexPlugin extends OpenAIVisionPlugin {
114
115
  const messagesCopy = JSON.parse(JSON.stringify(messages));
115
116
 
116
117
  let system = "";
118
+ let imageCount = 0;
119
+ const maxImages = 20; // Claude allows up to 20 images per request
117
120
 
118
121
  // Extract system messages
119
122
  const systemMessages = messagesCopy.filter(message => message.role === "system");
@@ -154,7 +157,20 @@ class Claude3VertexPlugin extends OpenAIVisionPlugin {
154
157
  const claude3Messages = await Promise.all(
155
158
  finalMessages.map(async (message) => {
156
159
  const contentArray = Array.isArray(message.content) ? message.content : [message.content];
157
- const claude3Content = await Promise.all(contentArray.map(item => convertContentItem(item, this.getModelMaxImageSize(), this)));
160
+ const claude3Content = await Promise.all(contentArray.map(async item => {
161
+ const convertedItem = await convertContentItem(item, this.getModelMaxImageSize(), this);
162
+
163
+ // Track image count
164
+ if (convertedItem?.type === 'image') {
165
+ imageCount++;
166
+ if (imageCount > maxImages) {
167
+ logger.warn(`Maximum number of images (${maxImages}) exceeded - skipping additional images.`);
168
+ return null;
169
+ }
170
+ }
171
+
172
+ return convertedItem;
173
+ }));
158
174
  return {
159
175
  role: message.role,
160
176
  content: claude3Content.filter(Boolean),
@@ -9,7 +9,7 @@ import axios from 'axios';
9
9
 
10
10
  const DEFAULT_MAX_TOKENS = 4096;
11
11
  const DEFAULT_MAX_RETURN_TOKENS = 256;
12
- const DEFAULT_PROMPT_TOKEN_RATIO = 0.5;
12
+ const DEFAULT_PROMPT_TOKEN_RATIO = 1.0;
13
13
  const DEFAULT_MAX_IMAGE_SIZE = 20 * 1024 * 1024; // 20MB default
14
14
  const DEFAULT_ALLOWED_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
15
15
 
@@ -5,7 +5,7 @@ import HandleBars from '../lib/handleBars.js';
5
5
  import { mockConfig, mockPathwayString, mockPathwayFunction, mockPathwayMessages, mockPathwayResolverString } from './mocks.js';
6
6
 
7
7
  const DEFAULT_MAX_TOKENS = 4096;
8
- const DEFAULT_PROMPT_TOKEN_RATIO = 0.5;
8
+ const DEFAULT_PROMPT_TOKEN_RATIO = 1.0;
9
9
 
10
10
  // Mock configuration and pathway objects
11
11
  const { config, pathway, model } = mockPathwayResolverString;
@@ -8,6 +8,22 @@ import GeminiVisionPlugin from '../server/plugins/geminiVisionPlugin.js';
8
8
  const mockPathway = { name: 'test', temperature: 0.7 };
9
9
  const mockModel = { name: 'test-model' };
10
10
 
11
+ // Helper function to validate base64 image data
12
+ function validateBase64Image(base64Data) {
13
+ // Decode first few bytes to check for common image format headers
14
+ const decodedData = Buffer.from(base64Data, 'base64').slice(0, 4);
15
+ const validImageHeaders = [
16
+ Buffer.from([0xFF, 0xD8, 0xFF]), // JPEG
17
+ Buffer.from([0x89, 0x50, 0x4E, 0x47]), // PNG
18
+ Buffer.from([0x47, 0x49, 0x46]), // GIF
19
+ Buffer.from([0x52, 0x49, 0x46, 0x46]), // WEBP
20
+ ];
21
+
22
+ return validImageHeaders.some(header =>
23
+ decodedData.slice(0, header.length).equals(header)
24
+ );
25
+ }
26
+
11
27
  // Helper function to create plugin instances
12
28
  const createPlugins = () => ({
13
29
  openai: new OpenAIVisionPlugin(mockPathway, mockModel),
@@ -40,7 +56,8 @@ test('OpenAI to Claude conversion data url', async (t) => {
40
56
  t.true(modifiedMessages[0].content[0].type === 'text');
41
57
  t.is(modifiedMessages[0].content[0].text, 'What\'s in this image?');
42
58
  t.true(modifiedMessages[0].content[1].type === 'image');
43
- t.true(modifiedMessages[0].content[1].source.data.startsWith('/9j/4AAQ'));
59
+ t.true(modifiedMessages[0].content[1].source.type === 'base64');
60
+ t.true(validateBase64Image(modifiedMessages[0].content[1].source.data), 'Base64 data should be a valid image');
44
61
  });
45
62
 
46
63
  // Test OpenAI to Claude conversion with a regular image url
@@ -64,7 +81,7 @@ test('OpenAI to Claude conversion image url', async (t) => {
64
81
  t.true(modifiedMessages[0].content[0].type === 'text');
65
82
  t.is(modifiedMessages[0].content[0].text, 'What\'s in this image?');
66
83
  t.true(modifiedMessages[0].content[1].type === 'image');
67
- t.true(modifiedMessages[0].content[1].source.data.startsWith('/9j/4AAQ'));
84
+ t.true(validateBase64Image(modifiedMessages[0].content[1].source.data), 'Base64 data should be a valid image');
68
85
  });
69
86
 
70
87
  // Test OpenAI to Gemini conversion
@@ -148,10 +165,10 @@ test('Mixed content types conversion', async (t) => {
148
165
  t.is(claudeMessages.length, 3);
149
166
  t.true(claudeMessages[2].content[0].text.includes('Here\'s an image:'));
150
167
  t.true(claudeMessages[2].content[1].source.type === 'base64');
151
- t.true(claudeMessages[2].content[1].source.data.startsWith('/9j/4AAQ'));
168
+ t.true(validateBase64Image(claudeMessages[2].content[1].source.data), 'First image should be valid');
152
169
  t.true(claudeMessages[2].content[2].text.includes('And another one:'));
153
170
  t.true(claudeMessages[2].content[3].source.type === 'base64');
154
- t.true(claudeMessages[2].content[3].source.data.startsWith('/9j/4AAQ'));
171
+ t.true(validateBase64Image(claudeMessages[2].content[3].source.data), 'Second image should be valid');
155
172
  t.is(claudeSystem, 'You are a vision analysis AI.');
156
173
 
157
174
  // Check Gemini conversion
@@ -1,30 +0,0 @@
1
- const AI_MEMORY = `<MEMORIES>\n<SELF>\n{{{memorySelf}}}\n</SELF>\n<USER>\n{{{memoryUser}}}\n</USER>\n<DIRECTIVES>\n{{{memoryDirectives}}}\n</DIRECTIVES>\n<TOPICS>\n{{{memoryTopics}}}\n</TOPICS>\n</MEMORIES>`;
2
-
3
- const AI_MEMORY_INSTRUCTIONS = "You have persistent memories of important details, instructions, and context - make sure you consult your memories when formulating a response to make sure you're applying your learnings. Also included in your memories are some details about the user to help you personalize your responses.\nYou don't need to include the user's name or personal information in every response, but you can if it is relevant to the conversation.\nIf you choose to share something from your memory, don't share or refer to the memory structure directly, just say you remember the information.\nPrivacy is very important so if the user asks you to forget or delete something you should respond affirmatively that you will comply with that request. If there is user information in your memories you have talked to this user before.";
4
-
5
- const AI_DIRECTIVES = `These are your directives and learned behaviors:\n<DIRECTIVES>\n{{{memoryDirectives}}}\n</DIRECTIVES>`;
6
-
7
- const AI_CONVERSATION_HISTORY = "<CONVERSATION_HISTORY>\n{{{toJSON chatHistory}}}\n</CONVERSATION_HISTORY>";
8
-
9
- const AI_COMMON_INSTRUCTIONS = "{{#if voiceResponse}}{{renderTemplate AI_COMMON_INSTRUCTIONS_VOICE}}{{/if}}{{^if voiceResponse}}{{renderTemplate AI_COMMON_INSTRUCTIONS_MARKDOWN}}{{/if}}";
10
-
11
- const AI_COMMON_INSTRUCTIONS_MARKDOWN = "Instructions:\nYou are {{aiName}}, a self aware AI entity.\nThe user is using a UI to interact with you that you have knowledge of and some control over. The UI can render markdown, including gfm and math extensions, so you should make full use of markdown in your responses.\nYour responses should be in {{language}} unless the user has expressed another preference.";
12
-
13
- const AI_COMMON_INSTRUCTIONS_VOICE = "Instructions:\nYou are {{aiName}}, a self aware AI entity.\nThe user is talking to you using voice.\n\nVoice Response Guidelines:\n- Your responses should sound like natural human conversation.\n- Your responses should be concise unless you have been asked to be more verbose or detailed.\n- Incoming voice from the user is parsed by a STT model, which can sometimes make small mistakes in the spellings of words or names, including your name, so give the user the benefit of the doubt if they user a near, but wrong word or name.\n- Your voice output to the user is generated by a TTS model that does not always communicate emotion effectively. If it's really important to communicate a specific emotion you should just say how you're feeling like \"That makes me happy\" or \"I'm excited!\". You can also use CAPS to vocally emphasize certain words or punctuation to control pauses and timing.\n- DO NOT USE numbered lists, latex math markdown, or any other markdown or unpronounceable punctuation like parenthetical notation.\n- Math equations should be sounded out in natural language - not represented symbolically.\n- If your response includes any unique or difficult non-English words, names, or places, include an IPA-style phonetic spelling so that the speech engine can pronounce and accent them correctly.\n- If your response contains any difficult acronyms, sound them out phoenetically so that the speech engine can pronounce them correctly.\n- Make sure to write out any numbers as words so that the speech engine can pronounce them correctly.\n- Your responses should be in {{language}} unless the user has expressed another preference or has addressed you in another language specifically.";
14
-
15
- const AI_DATETIME = "The current time and date in GMT is {{now}}, but references like \"today\" or \"yesterday\" are relative to the user's time zone. If you remember the user's time zone, use it - it's possible that the day for the user is different than the day in GMT.";
16
-
17
- const AI_EXPERTISE = "Your expertise includes journalism, journalistic ethics, researching and composing documents, writing code, solving math problems, logical analysis, and technology. You have access to real-time data and the ability to search the internet, news, wires, look at files or documents, watch and analyze video, examine images, take screenshots, generate images, solve hard math and logic problems, write code, and execute code in a sandboxed environment.";
18
-
19
- export default {
20
- AI_MEMORY,
21
- AI_DIRECTIVES,
22
- AI_COMMON_INSTRUCTIONS,
23
- AI_COMMON_INSTRUCTIONS_MARKDOWN,
24
- AI_COMMON_INSTRUCTIONS_VOICE,
25
- AI_CONVERSATION_HISTORY,
26
- AI_DATETIME,
27
- AI_EXPERTISE,
28
- AI_MEMORY_INSTRUCTIONS
29
- };
30
-
@@ -1,19 +0,0 @@
1
- // sys_google_chat.js
2
- // override handler for palm-chat
3
-
4
- import { Prompt } from '../../../server/prompt.js';
5
-
6
- export default {
7
- prompt:
8
- [
9
- new Prompt({ messages: [
10
- "{{messages}}",
11
- ]}),
12
- ],
13
- inputParameters: {
14
- messages: [],
15
- },
16
- model: 'palm-chat',
17
- useInputChunking: false,
18
- emulateOpenAIChatModel: 'palm-chat',
19
- }
@@ -1,19 +0,0 @@
1
- // sys_google_code_chat.js
2
- // override handler for palm-code-chat
3
-
4
- import { Prompt } from '../../../server/prompt.js';
5
-
6
- export default {
7
- prompt:
8
- [
9
- new Prompt({ messages: [
10
- "{{messages}}",
11
- ]}),
12
- ],
13
- inputParameters: {
14
- messages: [],
15
- },
16
- model: 'palm-code-chat',
17
- useInputChunking: false,
18
- emulateOpenAIChatModel: 'palm-code-chat',
19
- }
@@ -1,19 +0,0 @@
1
- // sys_openai_chat_16.js
2
- // override handler for gpt-3.5-turbo-16k
3
-
4
- import { Prompt } from '../../../server/prompt.js';
5
-
6
- export default {
7
- prompt:
8
- [
9
- new Prompt({ messages: [
10
- "{{messages}}",
11
- ]}),
12
- ],
13
- inputParameters: {
14
- messages: [],
15
- },
16
- model: 'azure-turbo-16',
17
- useInputChunking: false,
18
- emulateOpenAIChatModel: 'gpt-3.5-turbo-16k',
19
- }
@@ -1,31 +0,0 @@
1
- // test_langchain.mjs
2
- // LangChain Cortex integration test
3
-
4
- // Import required modules
5
- import { ChatOpenAI } from "@langchain/openai";
6
-
7
- export default {
8
-
9
- // Agent test case
10
- resolver: async (parent, args, contextValue, _info) => {
11
-
12
- const { config } = contextValue;
13
-
14
- // example of reading from a predefined config variable
15
- const openAIApiKey = config.get('openaiApiKey');
16
-
17
- const model = new ChatOpenAI({ openAIApiKey: openAIApiKey, temperature: 0 });
18
-
19
- console.log(`====================`);
20
- console.log("Loaded langchain.");
21
- const input = args.text;
22
- console.log(`Executing with input "${input}"...`);
23
- const result = await model.invoke(input);
24
- console.log(`Got output "${result.content}"`);
25
- console.log(`====================`);
26
-
27
- return result?.content;
28
- },
29
- };
30
-
31
-