@aj-archipelago/cortex 1.1.2 → 1.1.3

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.
@@ -46,7 +46,7 @@ if (connectionString) {
46
46
  channels.forEach(channel => {
47
47
  subscriptionClient.subscribe(channel, (error) => {
48
48
  if (error) {
49
- logger.error(`Error subscribing to redis channel ${channel}: ${error}`);
49
+ logger.error(`Error subscribing to Redis channel ${channel}: ${error}`);
50
50
  } else {
51
51
  logger.info(`Subscribed to channel ${channel}`);
52
52
  }
@@ -55,26 +55,22 @@ if (connectionString) {
55
55
  });
56
56
 
57
57
  subscriptionClient.on('message', (channel, message) => {
58
- logger.debug(`Received message from ${channel}: ${message}`);
58
+ logger.debug(`Received message from Redis channel ${channel}: ${message}`);
59
59
 
60
- let decryptedMessage;
61
-
62
- if (channel === requestProgressChannel && redisEncryptionKey) {
63
- try {
64
- decryptedMessage = decrypt(message, redisEncryptionKey);
65
- } catch (error) {
66
- logger.error(`Error decrypting message: ${error}`);
67
- }
68
- }
69
-
70
- decryptedMessage = decryptedMessage || message;
71
-
72
60
  let parsedMessage;
73
61
 
74
62
  try {
75
- parsedMessage = JSON.parse(decryptedMessage);
63
+ parsedMessage = JSON.parse(message);
76
64
  } catch (error) {
77
- logger.error(`Error parsing message: ${error}`);
65
+ if (channel === requestProgressChannel && redisEncryptionKey) {
66
+ try {
67
+ parsedMessage = JSON.parse(decrypt(message, redisEncryptionKey));
68
+ } catch (error) {
69
+ logger.error(`Error parsing or decrypting message: ${error}`);
70
+ }
71
+ } else {
72
+ logger.error(`Error parsing message: ${error}`);
73
+ }
78
74
  }
79
75
 
80
76
  switch(channel) {
@@ -95,8 +91,8 @@ if (connectionString) {
95
91
  logger.info(`Using pubsub publish for channel ${requestProgressChannel}`);
96
92
  }
97
93
 
98
- async function publishRequestProgress(data, useRedis = true) {
99
- if (publisherClient && useRedis) {
94
+ async function publishRequestProgress(data) {
95
+ if (publisherClient && requestState?.[data?.requestId]?.useRedis) {
100
96
  try {
101
97
  let message = JSON.stringify(data);
102
98
  if (redisEncryptionKey) {
@@ -106,10 +102,10 @@ async function publishRequestProgress(data, useRedis = true) {
106
102
  logger.error(`Error encrypting message: ${error}`);
107
103
  }
108
104
  }
109
- logger.debug(`Publishing message ${message} to channel ${requestProgressChannel}`);
105
+ logger.debug(`Publishing request progress ${message} to Redis channel ${requestProgressChannel}`);
110
106
  await publisherClient.publish(requestProgressChannel, message);
111
107
  } catch (error) {
112
- logger.error(`Error publishing message: ${error}`);
108
+ logger.error(`Error publishing request progress to Redis: ${error}`);
113
109
  }
114
110
  } else {
115
111
  pubsubHandleMessage(data);
@@ -119,11 +115,30 @@ async function publishRequestProgress(data, useRedis = true) {
119
115
  async function publishRequestProgressSubscription(data) {
120
116
  if (publisherClient) {
121
117
  try {
122
- const message = JSON.stringify(data);
123
- logger.debug(`Publishing message ${message} to channel ${requestProgressSubscriptionsChannel}`);
124
- await publisherClient.publish(requestProgressSubscriptionsChannel, message);
118
+ const requestIds = data;
119
+ const idsToForward = [];
120
+ // If any of these requests belong to this instance, we can just start and handle them locally
121
+ for (const requestId of requestIds) {
122
+ if (requestState[requestId]) {
123
+ if (!requestState[requestId].started) {
124
+ requestState[requestId].started = true;
125
+ requestState[requestId].useRedis = false;
126
+ logger.info(`Starting local execution for registered async request: ${requestId}`);
127
+ const { resolver, args } = requestState[requestId];
128
+ resolver(args, false);
129
+ }
130
+ } else {
131
+ idsToForward.push(requestId);
132
+ }
133
+ }
134
+
135
+ if (idsToForward.length > 0) {
136
+ const message = JSON.stringify(idsToForward);
137
+ logger.debug(`Sending subscription request(s) to channel ${requestProgressSubscriptionsChannel} for remote execution: ${message}`);
138
+ await publisherClient.publish(requestProgressSubscriptionsChannel, message);
139
+ }
125
140
  } catch (error) {
126
- logger.error(`Error publishing message: ${error}`);
141
+ logger.error(`Error handling subscription: ${error}`);
127
142
  }
128
143
  } else {
129
144
  handleSubscription(data);
@@ -132,11 +147,11 @@ async function publishRequestProgressSubscription(data) {
132
147
 
133
148
  function pubsubHandleMessage(data){
134
149
  const message = JSON.stringify(data);
135
- logger.debug(`Publishing message to pubsub: ${message}`);
150
+ logger.debug(`Publishing request progress to local subscribers: ${message}`);
136
151
  try {
137
152
  pubsub.publish('REQUEST_PROGRESS', { requestProgress: data });
138
153
  } catch (error) {
139
- logger.error(`Error publishing data to pubsub: ${error}`);
154
+ logger.error(`Error publishing request progress to local subscribers: ${error}`);
140
155
  }
141
156
  }
142
157
 
@@ -145,7 +160,8 @@ function handleSubscription(data){
145
160
  for (const requestId of requestIds) {
146
161
  if (requestState[requestId] && !requestState[requestId].started) {
147
162
  requestState[requestId].started = true;
148
- logger.info(`Subscription starting async requestProgress, requestId: ${requestId}`);
163
+ requestState[requestId].useRedis = true;
164
+ logger.info(`Starting execution for registered async request: ${requestId}`);
149
165
  const { resolver, args } = requestState[requestId];
150
166
  resolver(args);
151
167
  }
package/lib/request.js CHANGED
@@ -46,7 +46,6 @@ const buildLimiters = (config) => {
46
46
  if (connection) {
47
47
  limiterOptions.id = `${cortexId}-${name}-limiter`; // Unique id for each limiter
48
48
  limiterOptions.connection = connection; // Shared Redis connection
49
- limiterOptions.clearDatastore = true; // Clear Redis datastore on startup
50
49
  }
51
50
 
52
51
  limiters[name] = new Bottleneck(limiterOptions);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aj-archipelago/cortex",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
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": {
@@ -70,7 +70,7 @@ class PathwayResolver {
70
70
  // the graphql subscription to send progress updates to the client. Most of
71
71
  // the time the client will be an external client, but it could also be the
72
72
  // Cortex REST api code.
73
- async asyncResolve(args, useRedis = true) {
73
+ async asyncResolve(args) {
74
74
  const MAX_RETRY_COUNT = 3;
75
75
  let attempt = 0;
76
76
  let streamErrorOccurred = false;
@@ -88,7 +88,7 @@ class PathwayResolver {
88
88
  requestId: this.requestId,
89
89
  progress: completedCount / totalCount,
90
90
  data: JSON.stringify(responseData),
91
- }, useRedis);
91
+ });
92
92
  }
93
93
  } else {
94
94
  try {
@@ -140,7 +140,7 @@ class PathwayResolver {
140
140
 
141
141
  try {
142
142
  //logger.info(`Publishing stream message to requestId ${this.requestId}: ${message}`);
143
- publishRequestProgress(requestProgress, useRedis);
143
+ publishRequestProgress(requestProgress);
144
144
  } catch (error) {
145
145
  logger.error(`Could not publish the stream message: "${messageBuffer}", ${error}`);
146
146
  }
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(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
  },