@aj-archipelago/cortex 1.1.2 → 1.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.
Files changed (64) hide show
  1. package/.eslintignore +3 -3
  2. package/README.md +16 -3
  3. package/config.js +32 -8
  4. package/{helper_apps/CortexFileHandler → helper-apps/cortex-file-handler}/Dockerfile +1 -1
  5. package/{helper_apps/CortexFileHandler → helper-apps/cortex-file-handler}/fileChunker.js +1 -0
  6. package/{helper_apps/CortexFileHandler → helper-apps/cortex-file-handler}/package-lock.json +25 -216
  7. package/{helper_apps/CortexFileHandler → helper-apps/cortex-file-handler}/package.json +2 -2
  8. package/helper-apps/cortex-whisper-wrapper/.dockerignore +27 -0
  9. package/helper-apps/cortex-whisper-wrapper/Dockerfile +32 -0
  10. package/helper-apps/cortex-whisper-wrapper/app.py +104 -0
  11. package/helper-apps/cortex-whisper-wrapper/docker-compose.debug.yml +12 -0
  12. package/helper-apps/cortex-whisper-wrapper/docker-compose.yml +10 -0
  13. package/helper-apps/cortex-whisper-wrapper/models/.gitkeep +0 -0
  14. package/helper-apps/cortex-whisper-wrapper/requirements.txt +5 -0
  15. package/lib/cortexRequest.js +117 -0
  16. package/lib/pathwayTools.js +2 -1
  17. package/lib/redisSubscription.js +44 -28
  18. package/lib/requestExecutor.js +360 -0
  19. package/lib/requestMonitor.js +131 -28
  20. package/package.json +2 -1
  21. package/pathways/summary.js +3 -3
  22. package/server/graphql.js +4 -4
  23. package/server/{pathwayPrompter.js → modelExecutor.js} +24 -21
  24. package/server/pathwayResolver.js +25 -20
  25. package/server/plugins/azureCognitivePlugin.js +25 -20
  26. package/server/plugins/azureTranslatePlugin.js +6 -10
  27. package/server/plugins/cohereGeneratePlugin.js +5 -12
  28. package/server/plugins/cohereSummarizePlugin.js +5 -12
  29. package/server/plugins/localModelPlugin.js +3 -3
  30. package/server/plugins/modelPlugin.js +18 -12
  31. package/server/plugins/openAiChatExtensionPlugin.js +5 -5
  32. package/server/plugins/openAiChatPlugin.js +8 -10
  33. package/server/plugins/openAiCompletionPlugin.js +9 -12
  34. package/server/plugins/openAiDallE3Plugin.js +14 -31
  35. package/server/plugins/openAiEmbeddingsPlugin.js +6 -9
  36. package/server/plugins/openAiImagePlugin.js +19 -15
  37. package/server/plugins/openAiWhisperPlugin.js +167 -99
  38. package/server/plugins/palmChatPlugin.js +9 -10
  39. package/server/plugins/palmCodeCompletionPlugin.js +2 -2
  40. package/server/plugins/palmCompletionPlugin.js +11 -12
  41. package/server/resolver.js +2 -2
  42. package/server/rest.js +4 -5
  43. package/server/subscriptions.js +2 -0
  44. package/tests/config.test.js +1 -1
  45. package/tests/mocks.js +5 -0
  46. package/tests/modelPlugin.test.js +3 -10
  47. package/tests/openAiChatPlugin.test.js +9 -8
  48. package/tests/openai_api.test.js +3 -3
  49. package/tests/palmChatPlugin.test.js +1 -1
  50. package/tests/palmCompletionPlugin.test.js +1 -1
  51. package/tests/pathwayResolver.test.js +2 -1
  52. package/tests/requestMonitor.test.js +94 -0
  53. package/tests/{requestDurationEstimator.test.js → requestMonitorDurationEstimator.test.js} +21 -17
  54. package/tests/truncateMessages.test.js +1 -1
  55. package/lib/request.js +0 -260
  56. package/lib/requestDurationEstimator.js +0 -90
  57. /package/{helper_apps/CortexFileHandler → helper-apps/cortex-file-handler}/blobHandler.js +0 -0
  58. /package/{helper_apps/CortexFileHandler → helper-apps/cortex-file-handler}/docHelper.js +0 -0
  59. /package/{helper_apps/CortexFileHandler → helper-apps/cortex-file-handler}/function.json +0 -0
  60. /package/{helper_apps/CortexFileHandler → helper-apps/cortex-file-handler}/helper.js +0 -0
  61. /package/{helper_apps/CortexFileHandler → helper-apps/cortex-file-handler}/index.js +0 -0
  62. /package/{helper_apps/CortexFileHandler → helper-apps/cortex-file-handler}/localFileHandler.js +0 -0
  63. /package/{helper_apps/CortexFileHandler → helper-apps/cortex-file-handler}/redis.js +0 -0
  64. /package/{helper_apps/CortexFileHandler → helper-apps/cortex-file-handler}/start.js +0 -0
package/server/rest.js CHANGED
@@ -167,11 +167,10 @@ const processIncomingStream = (requestId, res, jsonResponse) => {
167
167
  // Fire the resolver for the async requestProgress
168
168
  logger.info(`Rest Endpoint starting async requestProgress, requestId: ${requestId}`);
169
169
  const { resolver, args } = requestState[requestId];
170
- // The false here means never use a Redis subscription channel
171
- // to handle these streaming messages. This is because we are
172
- // guaranteed in this case that the stream is going to the same
173
- // client.
174
- resolver(args, false);
170
+ requestState[requestId].useRedis = false;
171
+ requestState[requestId].started = true;
172
+
173
+ resolver && resolver(args);
175
174
 
176
175
  return subscription;
177
176
 
@@ -1,11 +1,13 @@
1
1
  import pubsub from './pubsub.js';
2
2
  import { withFilter } from 'graphql-subscriptions';
3
3
  import { publishRequestProgressSubscription } from '../lib/redisSubscription.js';
4
+ import logger from '../lib/logger.js';
4
5
 
5
6
  const subscriptions = {
6
7
  requestProgress: {
7
8
  subscribe: withFilter(
8
9
  (_, args, __, _info) => {
10
+ logger.debug(`Client requested subscription for request ids: ${args.requestIds}`);
9
11
  publishRequestProgressSubscription(args.requestIds);
10
12
  return pubsub.asyncIterator(['REQUEST_PROGRESS'])
11
13
  },
@@ -25,7 +25,7 @@ test('config basePathwayPath', (t) => {
25
25
  });
26
26
 
27
27
  test('config PORT', (t) => {
28
- const expectedDefault = 4000;
28
+ const expectedDefault = parseInt(process.env.CORTEX_PORT) || 4000;
29
29
  t.is(config.get('PORT'), expectedDefault);
30
30
  });
31
31
 
package/tests/mocks.js CHANGED
@@ -6,6 +6,7 @@ export const mockConfig = {
6
6
  defaultModelName: 'testModel',
7
7
  models: {
8
8
  testModel: {
9
+ name: 'testModel',
9
10
  url: 'https://api.example.com/testModel',
10
11
  type: 'OPENAI-COMPLETION',
11
12
  },
@@ -40,6 +41,7 @@ export const mockConfig = {
40
41
 
41
42
  export const mockPathwayResolverString = {
42
43
  model: {
44
+ name: 'testModel',
43
45
  url: 'https://api.example.com/testModel',
44
46
  type: 'OPENAI-COMPLETION',
45
47
  },
@@ -51,6 +53,7 @@ export const mockConfig = {
51
53
 
52
54
  export const mockPathwayResolverFunction = {
53
55
  model: {
56
+ name: 'testModel',
54
57
  url: 'https://api.example.com/testModel',
55
58
  type: 'OPENAI-COMPLETION',
56
59
  },
@@ -64,6 +67,7 @@ export const mockConfig = {
64
67
 
65
68
  export const mockPathwayResolverMessages = {
66
69
  model: {
70
+ name: 'testModel',
67
71
  url: 'https://api.example.com/testModel',
68
72
  type: 'OPENAI-COMPLETION',
69
73
  },
@@ -78,3 +82,4 @@ export const mockConfig = {
78
82
  }),
79
83
  };
80
84
 
85
+ export const mockModelEndpoints = { testModel: { name: 'testModel', url: 'https://api.example.com/testModel', type: 'OPENAI-COMPLETION' }};
@@ -8,10 +8,10 @@ const DEFAULT_MAX_TOKENS = 4096;
8
8
  const DEFAULT_PROMPT_TOKEN_RATIO = 0.5;
9
9
 
10
10
  // Mock configuration and pathway objects
11
- const { config, pathway, modelName, model } = mockPathwayResolverString;
11
+ const { config, pathway, model } = mockPathwayResolverString;
12
12
 
13
13
  test('ModelPlugin constructor', (t) => {
14
- const modelPlugin = new ModelPlugin(config, pathway, modelName, model);
14
+ const modelPlugin = new ModelPlugin(pathway, model);
15
15
 
16
16
  t.is(modelPlugin.modelName, pathway.model, 'modelName should be set from pathway');
17
17
  t.deepEqual(modelPlugin.model, config.get('models')[pathway.model], 'model should be set from config');
@@ -20,7 +20,7 @@ test('ModelPlugin constructor', (t) => {
20
20
  });
21
21
 
22
22
  test.beforeEach((t) => {
23
- t.context.modelPlugin = new ModelPlugin(config, pathway, modelName, model);
23
+ t.context.modelPlugin = new ModelPlugin(pathway, model);
24
24
  });
25
25
 
26
26
  test('getCompiledPrompt - text and parameters', (t) => {
@@ -71,13 +71,6 @@ 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('requestUrl', (t) => {
75
- const { modelPlugin } = t.context;
76
-
77
- const expectedUrl = HandleBars.compile(modelPlugin.model.url)({ ...modelPlugin.model, ...config.getEnv(), ...config });
78
- t.is(modelPlugin.requestUrl(), expectedUrl, 'requestUrl should return the correct URL');
79
- });
80
-
81
74
  test('default parseResponse', (t) => {
82
75
  const { modelPlugin } = t.context;
83
76
  const multipleChoicesResponse = {
@@ -1,19 +1,20 @@
1
1
  import test from 'ava';
2
2
  import OpenAIChatPlugin from '../server/plugins/openAiChatPlugin.js';
3
3
  import { mockPathwayResolverMessages } from './mocks.js';
4
+ import { config } from '../config.js';
4
5
 
5
- const { config, pathway, modelName, model } = mockPathwayResolverMessages;
6
+ const { pathway, modelName, model } = mockPathwayResolverMessages;
6
7
 
7
8
  // Test the constructor
8
9
  test('constructor', (t) => {
9
- const plugin = new OpenAIChatPlugin(config, pathway, modelName, model);
10
- t.is(plugin.config, mockPathwayResolverMessages.config);
10
+ const plugin = new OpenAIChatPlugin(pathway, model);
11
+ t.is(plugin.config, config);
11
12
  t.is(plugin.pathwayPrompt, mockPathwayResolverMessages.pathway.prompt);
12
13
  });
13
14
 
14
15
  // Test the convertPalmToOpenAIMessages function
15
16
  test('convertPalmToOpenAIMessages', (t) => {
16
- const plugin = new OpenAIChatPlugin(config, pathway, modelName, model);
17
+ const plugin = new OpenAIChatPlugin(pathway, model);
17
18
  const context = 'This is a test context.';
18
19
  const examples = [
19
20
  {
@@ -37,7 +38,7 @@ test('convertPalmToOpenAIMessages', (t) => {
37
38
 
38
39
  // Test the getRequestParameters function
39
40
  test('getRequestParameters', async (t) => {
40
- const plugin = new OpenAIChatPlugin(config, pathway, modelName, model);
41
+ const plugin = new OpenAIChatPlugin(pathway, model);
41
42
  const text = 'Help me';
42
43
  const parameters = { name: 'John', age: 30 };
43
44
  const prompt = mockPathwayResolverMessages.pathway.prompt;
@@ -59,7 +60,7 @@ test('getRequestParameters', async (t) => {
59
60
 
60
61
  // Test the execute function
61
62
  test('execute', async (t) => {
62
- const plugin = new OpenAIChatPlugin(config, pathway, modelName, model);
63
+ const plugin = new OpenAIChatPlugin(pathway, model);
63
64
  const text = 'Help me';
64
65
  const parameters = { name: 'John', age: 30 };
65
66
  const prompt = mockPathwayResolverMessages.pathway.prompt;
@@ -91,7 +92,7 @@ test('execute', async (t) => {
91
92
 
92
93
  // Test the parseResponse function
93
94
  test('parseResponse', (t) => {
94
- const plugin = new OpenAIChatPlugin(config, pathway, modelName, model);
95
+ const plugin = new OpenAIChatPlugin(pathway, model);
95
96
  const data = {
96
97
  choices: [
97
98
  {
@@ -107,7 +108,7 @@ test('parseResponse', (t) => {
107
108
 
108
109
  // Test the logRequestData function
109
110
  test('logRequestData', (t) => {
110
- const plugin = new OpenAIChatPlugin(config, pathway, modelName, model);
111
+ const plugin = new OpenAIChatPlugin(pathway, model);
111
112
  const data = {
112
113
  messages: [
113
114
  { role: 'user', content: 'User: Help me\nAssistant: Please help John who is 30 years old.' },
@@ -5,7 +5,7 @@ import got from 'got';
5
5
  import axios from 'axios';
6
6
  import serverFactory from '../index.js';
7
7
 
8
- const API_BASE = 'http://localhost:4000/v1';
8
+ const API_BASE = `http://localhost:${process.env.CORTEX_PORT}/v1`;
9
9
 
10
10
  let testServer;
11
11
 
@@ -110,7 +110,7 @@ test('POST SSE: /v1/completions should send a series of events and a [DONE] even
110
110
  stream: true,
111
111
  };
112
112
 
113
- const url = 'http://localhost:4000/v1';
113
+ const url = `http://localhost:${process.env.CORTEX_PORT}/v1`;
114
114
 
115
115
  const completionsAssertions = (t, messageJson) => {
116
116
  t.truthy(messageJson.id);
@@ -133,7 +133,7 @@ test('POST SSE: /v1/chat/completions should send a series of events and a [DONE]
133
133
  stream: true,
134
134
  };
135
135
 
136
- const url = 'http://localhost:4000/v1';
136
+ const url = `http://localhost:${process.env.CORTEX_PORT}/v1`;
137
137
 
138
138
  const chatCompletionsAssertions = (t, messageJson) => {
139
139
  t.truthy(messageJson.id);
@@ -6,7 +6,7 @@ import { mockPathwayResolverMessages } from './mocks.js';
6
6
  const { config, pathway, modelName, model } = mockPathwayResolverMessages;
7
7
 
8
8
  test.beforeEach((t) => {
9
- const palmChatPlugin = new PalmChatPlugin(config, pathway, modelName, model);
9
+ const palmChatPlugin = new PalmChatPlugin(pathway, model);
10
10
  t.context = { palmChatPlugin };
11
11
  });
12
12
 
@@ -7,7 +7,7 @@ import { mockPathwayResolverString } from './mocks.js';
7
7
  const { config, pathway, modelName, model } = mockPathwayResolverString;
8
8
 
9
9
  test.beforeEach((t) => {
10
- const palmCompletionPlugin = new PalmCompletionPlugin(config, pathway, modelName, model);
10
+ const palmCompletionPlugin = new PalmCompletionPlugin(pathway, model);
11
11
  t.context = { palmCompletionPlugin };
12
12
  });
13
13
 
@@ -1,7 +1,7 @@
1
1
  import test from 'ava';
2
2
  import { PathwayResolver } from '../server/pathwayResolver.js';
3
3
  import sinon from 'sinon';
4
- import { mockConfig, mockPathwayString } from './mocks.js';
4
+ import { mockConfig, mockPathwayString, mockModelEndpoints } from './mocks.js';
5
5
 
6
6
  const mockPathway = mockPathwayString;
7
7
  mockPathway.useInputChunking = false;
@@ -16,6 +16,7 @@ test.beforeEach((t) => {
16
16
  config: mockConfig,
17
17
  pathway: mockPathway,
18
18
  args: mockArgs,
19
+ endpoints: mockModelEndpoints,
19
20
  });
20
21
  });
21
22
 
@@ -0,0 +1,94 @@
1
+ import test from 'ava';
2
+ import RequestMonitor from '../lib/requestMonitor.js'; // replace with actual path
3
+
4
+ test('RequestMonitor: startCall', t => {
5
+ const rm = new RequestMonitor();
6
+
7
+ const callId = rm.startCall();
8
+
9
+ t.is(rm.callStartTimes.has(callId), true);
10
+ });
11
+
12
+ test('RequestMonitor: endCall', t => {
13
+ const rm = new RequestMonitor();
14
+
15
+ const callId = rm.startCall();
16
+ rm.endCall(callId);
17
+
18
+ t.is(rm.callStartTimes.has(callId), false);
19
+ t.is(rm.callCount.size(), 1);
20
+ });
21
+
22
+ test('RequestMonitor: getAverageCallDuration', async t => {
23
+ const rm = new RequestMonitor();
24
+
25
+ const callId1 = rm.startCall();
26
+ await new Promise(resolve => setTimeout(resolve, 1000));
27
+ rm.endCall(callId1);
28
+
29
+ const callId2 = rm.startCall();
30
+ await new Promise(resolve => setTimeout(resolve, 2000));
31
+ rm.endCall(callId2);
32
+
33
+ const average = rm.getAverageCallDuration();
34
+ t.truthy(average > 1400 && average < 1600);
35
+ });
36
+
37
+ test('RequestMonitor: incrementError429Count', t => {
38
+ const rm = new RequestMonitor();
39
+
40
+ rm.incrementError429Count();
41
+
42
+ t.is(rm.error429Count.size(), 1);
43
+ });
44
+
45
+ test('RequestMonitor: getCallRate', async t => {
46
+ const rm = new RequestMonitor();
47
+
48
+ rm.startCall();
49
+ rm.endCall();
50
+
51
+ await new Promise(resolve => setTimeout(resolve, 1000));
52
+
53
+ const callRate = rm.getCallRate();
54
+ t.truthy(callRate > 0.9 && callRate < 1.1);
55
+ });
56
+
57
+ test('RequestMonitor: getPeakCallRate', async t => {
58
+ const rm = new RequestMonitor();
59
+
60
+ rm.startCall();
61
+ rm.endCall();
62
+
63
+ await new Promise(resolve => setTimeout(resolve, 1000));
64
+
65
+ rm.startCall();
66
+ rm.endCall();
67
+
68
+ const peakCallRate = rm.getPeakCallRate();
69
+ t.truthy(peakCallRate > 1.9 && peakCallRate < 2.1);
70
+ });
71
+
72
+ test('RequestMonitor: getError429Rate', t => {
73
+ const rm = new RequestMonitor();
74
+
75
+ rm.startCall();
76
+ rm.endCall();
77
+ rm.incrementError429Count();
78
+
79
+ t.is(rm.getError429Rate(), 1);
80
+ });
81
+
82
+ test('RequestMonitor: reset', t => {
83
+ const rm = new RequestMonitor();
84
+
85
+ rm.startCall();
86
+ rm.endCall();
87
+ rm.incrementError429Count();
88
+
89
+ rm.reset();
90
+
91
+ t.is(rm.callCount.size(), 0);
92
+ t.is(rm.error429Count.size(), 0);
93
+ t.is(rm.peakCallRate, 0);
94
+ });
@@ -1,14 +1,14 @@
1
1
  import test from 'ava';
2
- import RequestDurationEstimator from '../lib/requestDurationEstimator.js';
2
+ import RequestMonitor from '../lib/requestMonitor.js';
3
3
 
4
4
  test('add and get average request duration', async (t) => {
5
- const estimator = new RequestDurationEstimator(5);
5
+ const estimator = new RequestMonitor(5);
6
6
 
7
- estimator.startRequest('req1');
7
+ const callid = estimator.startCall();
8
8
  await new Promise(resolve => setTimeout(() => {
9
- estimator.endRequest();
9
+ estimator.endCall(callid);
10
10
 
11
- const average = estimator.calculatePercentComplete();
11
+ const average = estimator.calculatePercentComplete(callid);
12
12
 
13
13
  // An average should be calculated after the first completed request
14
14
  t.not(average, 0);
@@ -17,31 +17,31 @@ test('add and get average request duration', async (t) => {
17
17
  });
18
18
 
19
19
  test('add more requests than size of durations array', (t) => {
20
- const estimator = new RequestDurationEstimator(5);
20
+ const estimator = new RequestMonitor(5);
21
21
 
22
22
  for (let i = 0; i < 10; i++) {
23
- estimator.startRequest(`req${i}`);
24
- estimator.endRequest();
23
+ const callid = estimator.startCall();
24
+ estimator.endCall(callid);
25
25
  }
26
26
 
27
27
  // Array size should not exceed maximum length (5 in this case)
28
- t.is(estimator.durations.length, 5);
28
+ t.is(estimator.callDurations.size(), 5);
29
29
  });
30
30
 
31
31
  test('calculate percent complete of current request based on average of past durations', async (t) => {
32
- const estimator = new RequestDurationEstimator(5);
32
+ const estimator = new RequestMonitor(5);
33
33
 
34
34
  for (let i = 0; i < 4; i++) {
35
- estimator.startRequest(`req${i}`);
35
+ const callid = estimator.startCall();
36
36
  // wait 1 second
37
37
  await new Promise(resolve => setTimeout(resolve, 1000));
38
- estimator.endRequest();
38
+ estimator.endCall(callid);
39
39
  }
40
40
 
41
- estimator.startRequest('req5');
41
+ const callid = estimator.startCall();
42
42
 
43
43
  await new Promise(resolve => setTimeout(() => {
44
- const percentComplete = estimator.calculatePercentComplete();
44
+ const percentComplete = estimator.calculatePercentComplete(callid);
45
45
 
46
46
  // Depending on how fast the operations are,
47
47
  // the percentage may not be exactly 50%, but
@@ -52,8 +52,12 @@ test('calculate percent complete of current request based on average of past dur
52
52
  });
53
53
 
54
54
  test('calculate percent complete based on average of past durations', async (t) => {
55
- const estimator = new RequestDurationEstimator(5);
56
- estimator.durations = [1000, 2000, 3000];
57
- const average = estimator.getAverage();
55
+ const estimator = new RequestMonitor(5);
56
+ estimator.callDurations.clear;
57
+ estimator.callDurations.pushBack({endTime: new Date(), callDuration: 1000});
58
+ estimator.callDurations.pushBack({endTime: new Date(), callDuration: 2000});
59
+ estimator.callDurations.pushBack({endTime: new Date(), callDuration: 3000});
60
+
61
+ const average = estimator.getAverageCallDuration();
58
62
  t.is(average, 2000);
59
63
  });
@@ -6,7 +6,7 @@ import { mockPathwayResolverString } from './mocks.js';
6
6
 
7
7
  const { config, pathway, modelName, model } = mockPathwayResolverString;
8
8
 
9
- const modelPlugin = new ModelPlugin(config, pathway, modelName, model);
9
+ const modelPlugin = new ModelPlugin(pathway, model);
10
10
 
11
11
  const generateMessage = (role, content) => ({ role, content });
12
12