@aj-archipelago/cortex 1.3.32 → 1.3.34

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 (34) hide show
  1. package/helper-apps/cortex-autogen/OAI_CONFIG_LIST +1 -1
  2. package/lib/encodeCache.js +22 -10
  3. package/lib/pathwayTools.js +10 -3
  4. package/lib/requestExecutor.js +1 -1
  5. package/lib/util.js +136 -1
  6. package/package.json +2 -2
  7. package/pathways/system/entity/memory/sys_memory_manager.js +2 -1
  8. package/pathways/system/entity/sys_entity_continue.js +10 -2
  9. package/pathways/system/entity/sys_entity_start.js +12 -10
  10. package/pathways/system/entity/sys_router_tool.js +2 -2
  11. package/server/chunker.js +23 -3
  12. package/server/pathwayResolver.js +2 -5
  13. package/server/plugins/claude3VertexPlugin.js +2 -3
  14. package/server/plugins/cohereGeneratePlugin.js +1 -1
  15. package/server/plugins/gemini15ChatPlugin.js +1 -1
  16. package/server/plugins/geminiChatPlugin.js +1 -1
  17. package/server/plugins/localModelPlugin.js +1 -1
  18. package/server/plugins/modelPlugin.js +332 -77
  19. package/server/plugins/openAiChatPlugin.js +1 -1
  20. package/server/plugins/openAiCompletionPlugin.js +1 -1
  21. package/server/plugins/palmChatPlugin.js +1 -1
  22. package/server/plugins/palmCodeCompletionPlugin.js +1 -1
  23. package/server/plugins/palmCompletionPlugin.js +1 -1
  24. package/tests/chunkfunction.test.js +9 -6
  25. package/tests/claude3VertexPlugin.test.js +81 -3
  26. package/tests/data/largecontent.txt +1 -0
  27. package/tests/data/mixedcontent.txt +1 -0
  28. package/tests/encodeCache.test.js +47 -14
  29. package/tests/modelPlugin.test.js +21 -0
  30. package/tests/multimodal_conversion.test.js +1 -1
  31. package/tests/subscription.test.js +7 -1
  32. package/tests/tokenHandlingTests.test.js +587 -0
  33. package/tests/truncateMessages.test.js +404 -46
  34. package/tests/util.test.js +146 -0
@@ -2,7 +2,21 @@ import test from 'ava';
2
2
  import { faker } from '@faker-js/faker';
3
3
  import { performance } from 'perf_hooks';
4
4
  import { encode, decode } from '../lib/encodeCache.js';
5
- import { encode as gpt3Encode, decode as gpt3Decode } from 'gpt-3-encoder';
5
+ import { encoding_for_model } from '@dqbd/tiktoken';
6
+
7
+ // Create reference encoder with same model as used in encodeCache
8
+ const encoder = encoding_for_model("gpt-4o");
9
+
10
+ // Helper to create a stable representation of token arrays for comparisons
11
+ const tokenArrayToString = arr => Array.from(arr).toString();
12
+
13
+ // Helper to normalize decoded content to string
14
+ const normalizeDecoded = decoded => {
15
+ if (decoded instanceof Uint8Array) {
16
+ return new TextDecoder().decode(decoded);
17
+ }
18
+ return decoded;
19
+ };
6
20
 
7
21
  // Test the accuracy of the cached encoding and decoding
8
22
  test('cached encode and decode are reversible', t => {
@@ -12,23 +26,29 @@ test('cached encode and decode are reversible', t => {
12
26
  t.is(decoded, original);
13
27
  })
14
28
 
15
- // Test whether the cached encoding and decoding is identical to the gpt3-encoder
29
+ // Test whether the cached encoding and decoding is identical to tiktoken
16
30
  test('cached encode and decode are identical to noncached', t => {
17
31
  const original = faker.lorem.paragraph(50);
18
32
  const encoded = encode(original);
19
- const gpt3Encoded = gpt3Encode(original);
20
- t.deepEqual(encoded, gpt3Encoded);
33
+ const tiktokenEncoded = encoder.encode(original);
34
+
35
+ // Compare arrays by converting to strings
36
+ t.is(tokenArrayToString(encoded), tokenArrayToString(tiktokenEncoded));
21
37
 
22
38
  const decoded = decode(encoded);
23
- const gpt3Decoded = gpt3Decode(encoded);
24
- t.is(decoded, gpt3Decoded);
39
+ const tiktokenDecoded = encoder.decode(tiktokenEncoded);
40
+
41
+ // Normalize tiktoken decoded output to string for comparison
42
+ const normalizedTiktokenDecoded = normalizeDecoded(tiktokenDecoded);
43
+
44
+ t.is(decoded, normalizedTiktokenDecoded);
25
45
  })
26
46
 
27
47
  // Test whether decoding adds the encoded value to the encode cache
28
48
  // the only way to tell is if the encode is faster after the cached decode
29
49
  test('decode operation adds to encode cache', t => {
30
50
  const original = faker.lorem.paragraph(50);
31
- const encodedOriginal = gpt3Encode(original);
51
+ const encodedOriginal = encoder.encode(original);
32
52
 
33
53
  const startEncode = performance.now();
34
54
  const encoded = encode(original);
@@ -36,39 +56,51 @@ test('decode operation adds to encode cache', t => {
36
56
  const encodeTime = endEncode - startEncode;
37
57
  console.log("pre-decode encode time", encodeTime);
38
58
 
39
- t.deepEqual(encoded, encodedOriginal);
59
+ // Compare arrays using our helper
60
+ t.is(tokenArrayToString(encoded), tokenArrayToString(encodedOriginal));
40
61
 
41
62
  const original2 = faker.lorem.paragraph(50);
42
- const encodedOriginal2 = gpt3Encode(original2);
63
+ const encodedOriginal2 = encoder.encode(original2);
64
+
65
+ // Decode should add to cache
43
66
  const decodedOriginal2 = decode(encodedOriginal2);
67
+
44
68
  const startEncode2 = performance.now();
45
69
  const encoded2 = encode(original2);
46
70
  const endEncode2 = performance.now();
47
71
  const encodeTime2 = endEncode2 - startEncode2;
48
72
  console.log("post-decode encode time", encodeTime2);
49
73
 
50
- t.deepEqual(encoded2, encodedOriginal2);
74
+ t.is(tokenArrayToString(encoded2), tokenArrayToString(encodedOriginal2));
75
+
76
+ // Allow some buffer for timing variations
51
77
  t.true(encodeTime2 <= encodeTime);
52
78
  })
53
79
 
54
-
55
80
  // Test encode and decode caching
56
81
  test('caching', t => {
57
82
  const original = faker.lorem.paragraph(50);
83
+
84
+ // First encode should be uncached
58
85
  const startEncode1 = performance.now();
59
86
  const encoded1 = encode(original);
60
87
  const endEncode1 = performance.now();
61
88
  const encodeTime1 = endEncode1 - startEncode1;
62
89
 
63
90
  const original2 = faker.lorem.paragraph(50);
64
- const encodedOriginal2 = gpt3Encode(original2);
91
+ const encodedOriginal2 = encoder.encode(original2);
92
+
93
+ // First decode should be uncached
65
94
  const startDecode1 = performance.now();
66
95
  const decoded1 = decode(encodedOriginal2);
67
96
  const endDecode1 = performance.now();
68
97
  const decodeTime1 = endDecode1 - startDecode1;
69
98
 
70
- t.deepEqual(encoded1, gpt3Encode(original));
71
- t.is(decoded1, original2);
99
+ t.is(tokenArrayToString(encoded1), tokenArrayToString(encoder.encode(original)));
100
+
101
+ // Compare with normalized tiktoken output
102
+ const normalizedOriginal2 = normalizeDecoded(encoder.decode(encodedOriginal2));
103
+ t.is(decoded1, normalizedOriginal2);
72
104
 
73
105
  console.log('uncached encode time', encodeTime1);
74
106
  console.log('uncached decode time', decodeTime1);
@@ -87,6 +119,7 @@ test('caching', t => {
87
119
  console.log('cached encode time', encodeTime2);
88
120
  console.log('cached decode time', decodeTime2);
89
121
 
122
+ // Allow some buffer for timing variations
90
123
  t.true(encodeTime2 <= encodeTime1);
91
124
  t.true(decodeTime2 <= decodeTime1);
92
125
  });
@@ -71,6 +71,27 @@ test('getPromptTokenRatio', (t) => {
71
71
  t.is(modelPlugin.getPromptTokenRatio(), DEFAULT_PROMPT_TOKEN_RATIO, 'getPromptTokenRatio should return default prompt token ratio');
72
72
  });
73
73
 
74
+ test('getModelMaxPromptTokens', (t) => {
75
+ const { modelPlugin } = t.context;
76
+
77
+ // Default case - should use token ratio
78
+ t.is(
79
+ modelPlugin.getModelMaxPromptTokens(),
80
+ Math.floor(DEFAULT_MAX_TOKENS * DEFAULT_PROMPT_TOKEN_RATIO),
81
+ 'Should return maxTokenLength * tokenRatio when maxReturnTokens is not defined'
82
+ );
83
+
84
+ // When maxReturnTokens is defined
85
+ const returnTokens = 256;
86
+ modelPlugin.promptParameters.maxReturnTokens = returnTokens;
87
+
88
+ t.is(
89
+ modelPlugin.getModelMaxPromptTokens(),
90
+ DEFAULT_MAX_TOKENS - returnTokens,
91
+ 'Should return maxTokenLength - maxReturnTokens when maxReturnTokens is defined'
92
+ );
93
+ });
94
+
74
95
  test('default parseResponse', (t) => {
75
96
  const { modelPlugin } = t.context;
76
97
  const multipleChoicesResponse = {
@@ -200,7 +200,7 @@ test('Unsupported mime type conversion', async (t) => {
200
200
  const pdfMessage = [
201
201
  { role: 'user', content: [
202
202
  { type: 'text', text: 'Can you analyze this PDF?' },
203
- { type: 'image_url', image_url: { url: 'https://unec.edu.az/application/uploads/2014/12/pdf-sample.pdf' } }
203
+ { type: 'image_url', image_url: { url: 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf' } }
204
204
  ]}
205
205
  ];
206
206
 
@@ -162,13 +162,19 @@ function validateProgressMessage(t, progress, requestId = null) {
162
162
  }
163
163
 
164
164
  // Validate info field if present and not an error
165
- if (progress.info && !progress.info.startsWith('ERROR:')) {
165
+ if (progress.info) {
166
166
  t.true(typeof progress.info === 'string', 'Info field should be a string');
167
167
  t.notThrows(() => {
168
168
  const parsedInfo = JSON.parse(progress.info);
169
169
  t.true(typeof parsedInfo === 'object', 'Info should be valid JSON object');
170
170
  }, 'Info should be valid JSON');
171
171
  }
172
+
173
+ // Validate error field if present
174
+ if (progress.error) {
175
+ t.true(typeof progress.error === 'string', 'Error field should be a string');
176
+ t.notThrows(() => JSON.parse(progress.error), 'Error should be valid JSON');
177
+ }
172
178
  }
173
179
 
174
180
  test.serial('Request progress messages have string data and info fields', async (t) => {