@aj-archipelago/cortex 1.3.28 → 1.3.30

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aj-archipelago/cortex",
3
- "version": "1.3.28",
3
+ "version": "1.3.30",
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": {
@@ -59,12 +59,17 @@ export default {
59
59
 
60
60
  logger.debug(`Using generator pathway: ${generatorPathway}`);
61
61
 
62
- const result = await callPathway(generatorPathway, newArgs, resolver);
62
+ let result = await callPathway(generatorPathway, newArgs, resolver);
63
63
 
64
64
  if (!result && !args.stream) {
65
65
  result = await callPathway('sys_generator_error', { ...args, text: `Tried to use a tool (${generatorPathway}), but no result was returned`, stream: false }, resolver);
66
66
  }
67
67
 
68
+ if (resolver.errors.length > 0) {
69
+ result = await callPathway('sys_generator_error', { ...args, text: resolver.errors.join('\n'), stream: false }, resolver);
70
+ resolver.errors = [];
71
+ }
72
+
68
73
  return result;
69
74
 
70
75
  } catch (e) {
@@ -4,8 +4,11 @@ export default {
4
4
  prompt:
5
5
  [
6
6
  new Prompt({ messages: [
7
- {"role": "system", "content": `{{renderTemplate AI_DIRECTIVES}}\n\n{{renderTemplate AI_COMMON_INSTRUCTIONS}}\n\n{{renderTemplate AI_EXPERTISE}}\n\nThe user has requested information that you have already determined can be found in the indexes that you can search, and you were trying to search for it, but encountered the following error: {{{text}}}. Your response should be concise, fit the rest of the conversation, include detail appropriate for the technical level of the user if you can determine it, and be appropriate for the context. You cannot resolve this error.\n{{renderTemplate AI_DATETIME}}`},
8
- "{{chatHistory}}",
7
+ {"role": "system", "content": `{{renderTemplate AI_MEMORY}}\n\n{{renderTemplate AI_DIRECTIVES}}\n\n{{renderTemplate AI_COMMON_INSTRUCTIONS}}\n\n{{renderTemplate AI_EXPERTISE}}\n\n{{renderTemplate AI_CONVERSATION_HISTORY}}\n\nYou were trying to fulfill the user's last request in the above conversation, but ran into an error. You cannot resolve this error.\n{{renderTemplate AI_DATETIME}}`},
8
+ {
9
+ "role": "user",
10
+ "content": `The model that you were trying to use to fulfill the user's request returned the following error(s): {{{text}}}. Please let them know what happened. Your response should be concise, fit the rest of the conversation, include detail appropriate for the technical level of the user if you can determine it, and be appropriate for the context. You cannot resolve this error.`
11
+ },
9
12
  ]}),
10
13
  ],
11
14
  inputParameters: {
@@ -2,7 +2,7 @@ import { parse, build } from "@aj-archipelago/subvibe";
2
2
  import logger from "../lib/logger.js";
3
3
  import { callPathway } from "../lib/pathwayTools.js";
4
4
 
5
- function splitIntoOverlappingChunks(captions, chunkSize = 20, overlap = 3) {
5
+ export function splitIntoOverlappingChunks(captions, chunkSize = 20, overlap = 3) {
6
6
  const chunks = [];
7
7
  for (let i = 0; i < captions.length; i += (chunkSize - overlap)) {
8
8
  const end = Math.min(i + chunkSize, captions.length);
@@ -17,26 +17,51 @@ function splitIntoOverlappingChunks(captions, chunkSize = 20, overlap = 3) {
17
17
  return chunks;
18
18
  }
19
19
 
20
- function selectBestTranslation(translations, startIndex, endIndex) {
21
- // If we only have one translation for this caption, use it
22
- if (translations.length === 1) return translations[0];
20
+ export function selectBestTranslation(translations, startIndex, endIndex) {
21
+ try {
22
+ if (!translations || !Array.isArray(translations)) {
23
+ logger.warn(`Invalid translations input: ${JSON.stringify(translations)}`);
24
+ return null;
25
+ }
26
+
27
+ if (translations.length === 0) {
28
+ logger.warn(`No translations available for selection`);
29
+ return null;
30
+ }
31
+
32
+ // If we only have one translation for this caption, use it
33
+ if (translations.length === 1) return translations[0];
23
34
 
24
- // For multiple translations, prefer the one from the middle of its chunk
25
- // This helps avoid edge effects in translation
26
- return translations.reduce((best, current) => {
27
- const currentDistance = Math.min(
28
- Math.abs(current.chunkStart - startIndex),
29
- Math.abs(current.chunkEnd - endIndex)
30
- );
31
- const bestDistance = Math.min(
32
- Math.abs(best.chunkStart - startIndex),
33
- Math.abs(best.chunkEnd - endIndex)
34
- );
35
- return currentDistance < bestDistance ? current : best;
36
- });
35
+ // Use the first translation as a starting point
36
+ const first = translations[0];
37
+
38
+ // For multiple translations, prefer the one whose identifier is closest to the middle
39
+ // of the requested range
40
+ const targetValue = (Number(startIndex) + Number(endIndex)) / 2;
41
+
42
+ return translations.reduce((best, current) => {
43
+ try {
44
+ // Use identifier for comparison if available, otherwise use index
45
+ const currentValue = Number(current.identifier !== undefined ? current.identifier : current.index || 0);
46
+ const bestValue = Number(best.identifier !== undefined ? best.identifier : best.index || 0);
47
+
48
+ const currentDistance = Math.abs(currentValue - targetValue);
49
+ const bestDistance = Math.abs(bestValue - targetValue);
50
+
51
+ return currentDistance < bestDistance ? current : best;
52
+ } catch (err) {
53
+ logger.warn(`Error comparing translations: ${err.message}`);
54
+ return best; // Fallback to existing best on error
55
+ }
56
+ }, first);
57
+ } catch (err) {
58
+ logger.error(`Error in selectBestTranslation: ${err.message}`);
59
+ // Return the first translation if available, otherwise null
60
+ return translations && translations.length ? translations[0] : null;
61
+ }
37
62
  }
38
63
 
39
- async function translateChunk(chunk, args, maxRetries = 3) {
64
+ export async function translateChunk(chunk, args, maxRetries = 3) {
40
65
  const chunkText = build(chunk.captions, { format: args.format, preserveIndexes: true });
41
66
 
42
67
  for (let attempt = 0; attempt < maxRetries; attempt++) {
@@ -82,7 +107,7 @@ export default {
82
107
  timeout: 3600,
83
108
  executePathway: async ({args}) => {
84
109
  try {
85
- const { text, format = 'srt' } = args;
110
+ const { text, format = 'vtt' } = args;
86
111
  const parsed = parse(text, { format, preserveIndexes: true });
87
112
  const captions = parsed.cues;
88
113
 
@@ -101,16 +126,18 @@ export default {
101
126
  // Create a map of caption index to all its translations
102
127
  const translationMap = new Map();
103
128
  translatedChunks.flat().forEach(caption => {
104
- if (!translationMap.has(caption.index)) {
105
- translationMap.set(caption.index, []);
129
+ const identifier = caption.identifier || caption.index;
130
+ if (!translationMap.has(identifier)) {
131
+ translationMap.set(identifier, []);
106
132
  }
107
- translationMap.get(caption.index).push(caption);
133
+ translationMap.get(identifier).push(caption);
108
134
  });
109
135
 
110
136
  // Select best translation for each caption
111
137
  const finalCaptions = captions.map(caption => {
112
- const translations = translationMap.get(caption.index) || [caption];
113
- const bestTranslation = selectBestTranslation(translations, caption.index, caption.index);
138
+ const identifier = caption.identifier || caption.index;
139
+ const translations = translationMap.get(identifier) || [caption];
140
+ const bestTranslation = selectBestTranslation(translations, identifier, identifier);
114
141
  const text = bestTranslation?.text || caption?.text;
115
142
  return { ...caption, text };
116
143
  });
@@ -140,7 +140,7 @@ class Gemini15ChatPlugin extends ModelPlugin {
140
140
  dataToMerge = data.contents;
141
141
  } else if (data && data.candidates && Array.isArray(data.candidates)) {
142
142
  const { content, finishReason, safetyRatings } = data.candidates[0];
143
- if (finishReason === 'STOP') {
143
+ if (finishReason === 'STOP' || finishReason === 'MAX_TOKENS') {
144
144
  return content?.parts?.[0]?.text ?? '';
145
145
  } else {
146
146
  const returnString = `Response was not completed. Finish reason: ${finishReason}, Safety ratings: ${JSON.stringify(safetyRatings, null, 2)}`;
@@ -143,8 +143,8 @@ class Gemini15VisionPlugin extends Gemini15ChatPlugin {
143
143
  if (data.error.code === 400 && data.error.message === 'Precondition check failed.') {
144
144
  throw new Error('One or more of the included files is too large to process. Please try again with a smaller file.');
145
145
  }
146
- throw e;
147
146
  }
147
+ throw e;
148
148
  }
149
149
  return result;
150
150
  }
@@ -72,13 +72,7 @@ class OpenAIWhisperPlugin extends ModelPlugin {
72
72
  if(maxLineWidth) tsparams.max_line_width = maxLineWidth;
73
73
  if(maxLineCount) tsparams.max_line_count = maxLineCount;
74
74
  if(maxWordsPerLine) tsparams.max_words_per_line = maxWordsPerLine;
75
- if(wordTimestamped!=null) {
76
- if(!wordTimestamped) {
77
- tsparams.word_timestamps = "False";
78
- }else{
79
- tsparams.word_timestamps = wordTimestamped;
80
- }
81
- }
75
+ tsparams.word_timestamps = !wordTimestamped ? "False" : wordTimestamped;
82
76
 
83
77
  const cortexRequest = new CortexRequest({ pathwayResolver });
84
78
  cortexRequest.url = WHISPER_TS_API_URL;
@@ -157,7 +151,8 @@ async function processURI(uri) {
157
151
 
158
152
  const intervalId = setInterval(() => sendProgress(true), 3000);
159
153
 
160
- const useTS = WHISPER_TS_API_URL && (wordTimestamped || highlightWords);
154
+ //const useTS = WHISPER_TS_API_URL && (wordTimestamped || highlightWords); // use TS API only for word timestamped
155
+ const useTS = !!WHISPER_TS_API_URL; // use TS API always if URL is set
161
156
 
162
157
  if (useTS) {
163
158
  _promise = processTS;