@aj-archipelago/cortex 1.0.25 → 1.1.0-beta.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.
package/.eslintrc CHANGED
@@ -16,7 +16,7 @@
16
16
  ],
17
17
  "rules": {
18
18
  "import/no-unresolved": "error",
19
- "import/no-extraneous-dependencies": ["error", {"devDependencies": true}],
19
+ "import/no-extraneous-dependencies": ["error", {"devDependencies": true, "dependencies": true}],
20
20
  "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }]
21
21
  },
22
22
  "settings": {
package/config.js CHANGED
@@ -4,11 +4,17 @@ import HandleBars from './lib/handleBars.js';
4
4
  import fs from 'fs';
5
5
  import { fileURLToPath, pathToFileURL } from 'url';
6
6
  import GcpAuthTokenHelper from './lib/gcpAuthTokenHelper.js';
7
+ import logger from './lib/logger.js';
7
8
 
8
9
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
10
 
10
11
  // Schema for config
11
12
  var config = convict({
13
+ env: {
14
+ format: String,
15
+ default: 'development',
16
+ env: 'NODE_ENV'
17
+ },
12
18
  cortexId: {
13
19
  format: String,
14
20
  default: 'local',
@@ -195,14 +201,16 @@ const configFile = config.get('cortexConfigFile');
195
201
 
196
202
  // Load config file
197
203
  if (configFile && fs.existsSync(configFile)) {
198
- console.log('Loading config from', configFile);
204
+ logger.info(`Loading config from ${configFile}`);
199
205
  config.loadFile(configFile);
200
206
  } else {
201
207
  const openaiApiKey = config.get('openaiApiKey');
202
208
  if (!openaiApiKey) {
203
- throw console.log('No config file or api key specified. Please set the OPENAI_API_KEY to use OAI or use CORTEX_CONFIG_FILE environment variable to point at the Cortex configuration for your project.');
209
+ const errorString = 'No config file or api key specified. Please set the OPENAI_API_KEY to use OAI or use CORTEX_CONFIG_FILE environment variable to point at the Cortex configuration for your project.';
210
+ logger.error(errorString);
211
+ throw new Error(errorString);
204
212
  } else {
205
- console.log(`Using default model with OPENAI_API_KEY environment variable`)
213
+ logger.info(`Using default model with OPENAI_API_KEY environment variable`)
206
214
  }
207
215
  }
208
216
 
@@ -223,12 +231,12 @@ const buildPathways = async (config) => {
223
231
  const basePathway = await import(basePathwayURL).then(module => module.default);
224
232
 
225
233
  // Load core pathways, default from the Cortex package
226
- console.log('Loading core pathways from', corePathwaysPath)
234
+ logger.info(`Loading core pathways from ${corePathwaysPath}`)
227
235
  let loadedPathways = await import(`${corePathwaysURL}/index.js`).then(module => module);
228
236
 
229
237
  // Load custom pathways and override core pathways if same
230
238
  if (pathwaysPath && fs.existsSync(pathwaysPath)) {
231
- console.log('Loading custom pathways from', pathwaysPath)
239
+ logger.info(`Loading custom pathways from ${pathwaysPath}`)
232
240
  const customPathways = await import(`${pathwaysURL}/index.js`).then(module => module);
233
241
  loadedPathways = { ...loadedPathways, ...customPathways };
234
242
  }
@@ -263,12 +271,14 @@ const buildModels = (config) => {
263
271
 
264
272
  // Check that models are specified, Cortex cannot run without a model
265
273
  if (Object.keys(config.get('models')).length <= 0) {
266
- throw console.log('No models specified! Please set the models in your config file or via CORTEX_MODELS environment variable to point at the models for your project.');
274
+ const errorString = 'No models specified! Please set the models in your config file or via CORTEX_MODELS environment variable to point at the models for your project.';
275
+ logger.error(errorString);
276
+ throw new Error(errorString);
267
277
  }
268
278
 
269
279
  // Set default model name to the first model in the config in case no default is specified
270
280
  if (!config.get('defaultModelName')) {
271
- console.log('No default model specified, using first model as default.');
281
+ logger.warn('No default model specified, using first model as default.');
272
282
  config.load({ defaultModelName: Object.keys(config.get('models'))[0] });
273
283
  }
274
284
 
package/lib/logger.js ADDED
@@ -0,0 +1,29 @@
1
+ // logger.js
2
+ import winston from 'winston';
3
+
4
+ const format = winston.format.combine(
5
+ //winston.format.timestamp(),
6
+ winston.format.colorize({ all: true }),
7
+ winston.format.simple()
8
+ );
9
+
10
+ const transports = [
11
+ new winston.transports.Console({ format })
12
+ ];
13
+
14
+ const logger = winston.createLogger({
15
+ level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
16
+ format: format,
17
+ transports: transports
18
+ });
19
+
20
+ winston.addColors({
21
+ debug: 'green',
22
+ verbose: 'blue',
23
+ http: 'gray',
24
+ info: 'cyan',
25
+ warn: 'yellow',
26
+ error: 'red'
27
+ });
28
+
29
+ export default logger;
@@ -1,39 +1,47 @@
1
1
  import Redis from 'ioredis';
2
2
  import { config } from '../config.js';
3
3
  import pubsub from '../server/pubsub.js';
4
+ import { requestState } from '../server/requestState.js';
5
+ import logger from '../lib/logger.js';
4
6
 
5
7
  const connectionString = config.get('storageConnectionString');
6
- const channel = 'requestProgress';
8
+ const channels = ['requestProgress', 'requestProgressSubscriptions'];
7
9
  let client;
8
10
 
9
11
  if (connectionString) {
10
- console.log(`Using Redis subscription for channel ${channel}`);
12
+ logger.info(`Using Redis subscription for channel(s) ${channels.join(', ')}`);
11
13
  try {
12
14
  client = connectionString && new Redis(connectionString);
13
15
  } catch (error) {
14
- console.error('Redis connection error: ', error);
16
+ logger.error(`Redis connection error: ${JSON.stringify(error)}`);
15
17
  }
16
18
 
17
19
  if (client) {
18
- const channel = 'requestProgress';
19
20
 
20
21
  client.on('error', (error) => {
21
- console.error(`Redis client error: ${error}`);
22
+ logger.error(`Redis client error: ${JSON.stringify(error)}`);
22
23
  });
23
24
 
24
25
  client.on('connect', () => {
25
- client.subscribe(channel, (error) => {
26
+ client.subscribe('requestProgress', (error) => {
26
27
  if (error) {
27
- console.error(`Error subscribing to channel ${channel}: ${error}`);
28
+ logger.error(`Error subscribing to redis channel requestProgress: ${JSON.stringify(error)}`);
28
29
  } else {
29
- console.log(`Subscribed to channel ${channel}`);
30
+ logger.info(`Subscribed to channel requestProgress`);
31
+ }
32
+ });
33
+ client.subscribe('requestProgressSubscriptions', (error) => {
34
+ if (error) {
35
+ logger.error(`Error subscribing to redis channel requestProgressSubscriptions: ${JSON.stringify(error)}`);
36
+ } else {
37
+ logger.info(`Subscribed to channel requestProgressSubscriptions`);
30
38
  }
31
39
  });
32
40
  });
33
41
 
34
42
  client.on('message', (channel, message) => {
35
43
  if (channel === 'requestProgress') {
36
- console.log(`Received message from ${channel}: ${message}`);
44
+ logger.debug(`Received message from ${channel}: ${message}`);
37
45
  let parsedMessage;
38
46
 
39
47
  try {
@@ -42,22 +50,86 @@ if (connectionString) {
42
50
  parsedMessage = message;
43
51
  }
44
52
 
45
- handleMessage(parsedMessage);
53
+ pubsubHandleMessage(parsedMessage);
54
+ } else {
55
+ if (channel === 'requestProgressSubscriptions') {
56
+ logger.debug(`Received message from ${channel}: ${message}`);
57
+ let parsedMessage;
58
+
59
+ try {
60
+ parsedMessage = JSON.parse(message);
61
+ } catch (error) {
62
+ parsedMessage = message;
63
+ }
64
+
65
+ handleSubscription(parsedMessage);
66
+ }
46
67
  }
47
68
  });
69
+ }
70
+ }
48
71
 
49
- const handleMessage = (data) => {
50
- // Process the received data
51
- console.log('Processing data:', data);
52
- try {
53
- pubsub.publish('REQUEST_PROGRESS', { requestProgress: data });
54
- } catch (error) {
55
- console.error(`Error publishing data to pubsub: ${error}`);
56
- }
57
- };
72
+
73
+ let publisherClient;
74
+
75
+ if (connectionString) {
76
+ logger.info(`Using Redis publish for channel(s) ${channels.join(', ')}`);
77
+ publisherClient = Redis.createClient(connectionString);
78
+ } else {
79
+ logger.info(`Using pubsub publish for channel ${channels[0]}`);
80
+ }
81
+
82
+ async function publishRequestProgress(data) {
83
+ if (publisherClient) {
84
+ try {
85
+ const message = JSON.stringify(data);
86
+ logger.debug(`Publishing message ${message} to channel ${channels[0]}`);
87
+ await publisherClient.publish(channels[0], message);
88
+ } catch (error) {
89
+ logger.error(`Error publishing message: ${JSON.stringify(error)}`);
90
+ }
91
+ } else {
92
+ pubsubHandleMessage(data);
93
+ }
94
+ }
95
+
96
+ async function publishRequestProgressSubscription(data) {
97
+ if (publisherClient) {
98
+ try {
99
+ const message = JSON.stringify(data);
100
+ logger.debug(`Publishing message ${message} to channel ${channels[1]}`);
101
+ await publisherClient.publish(channels[1], message);
102
+ } catch (error) {
103
+ logger.error(`Error publishing message: ${JSON.stringify(error)}`);
104
+ }
105
+ } else {
106
+ handleSubscription(data);
58
107
  }
59
108
  }
60
109
 
110
+ function pubsubHandleMessage(data){
111
+ const message = JSON.stringify(data);
112
+ logger.debug(`Publishing message to pubsub: ${message}`);
113
+ try {
114
+ pubsub.publish('REQUEST_PROGRESS', { requestProgress: data });
115
+ } catch (error) {
116
+ logger.error(`Error publishing data to pubsub: ${JSON.stringify(error)}`);
117
+ }
118
+ }
119
+
120
+ function handleSubscription(data){
121
+ const requestIds = data;
122
+ for (const requestId of requestIds) {
123
+ if (requestState[requestId] && !requestState[requestId].started) {
124
+ requestState[requestId].started = true;
125
+ logger.info(`Subscription starting async requestProgress, requestId: ${requestId}`);
126
+ const { resolver, args } = requestState[requestId];
127
+ resolver(args);
128
+ }
129
+ }
130
+ }
131
+
132
+
61
133
  export {
62
- client as subscriptionClient,
134
+ client as subscriptionClient, publishRequestProgress, publishRequestProgressSubscription
63
135
  };
package/lib/request.js CHANGED
@@ -4,13 +4,14 @@ import { config } from '../config.js';
4
4
  import axios from 'axios';
5
5
  import { setupCache } from 'axios-cache-interceptor';
6
6
  import Redis from 'ioredis';
7
+ import logger from './logger.js';
7
8
 
8
9
  const connectionString = config.get('storageConnectionString');
9
10
 
10
11
  if (!connectionString) {
11
- console.log('No STORAGE_CONNECTION_STRING found in environment. Redis features (caching, pubsub, clustered limiters) disabled.')
12
+ logger.info('No STORAGE_CONNECTION_STRING found in environment. Redis features (caching, pubsub, clustered limiters) disabled.')
12
13
  } else {
13
- console.log('Using Redis connection specified in STORAGE_CONNECTION_STRING.');
14
+ logger.info('Using Redis connection specified in STORAGE_CONNECTION_STRING.');
14
15
  }
15
16
 
16
17
  let client;
@@ -19,7 +20,7 @@ if (connectionString) {
19
20
  try {
20
21
  client = new Redis(connectionString);
21
22
  } catch (error) {
22
- console.error('Redis connection error: ', error);
23
+ logger.error(`Redis connection error: ${error}`);
23
24
  }
24
25
  }
25
26
 
@@ -30,7 +31,7 @@ const limiters = {};
30
31
  const monitors = {};
31
32
 
32
33
  const buildLimiters = (config) => {
33
- console.log(`Building ${connection ? 'Redis clustered' : 'local'} model rate limiters for ${cortexId}...`);
34
+ logger.info(`Building ${connection ? 'Redis clustered' : 'local'} model rate limiters for ${cortexId}...`);
34
35
  for (const [name, model] of Object.entries(config.get('models'))) {
35
36
  const rps = model.requestsPerSecond ?? 100;
36
37
  let limiterOptions = {
@@ -50,7 +51,7 @@ const buildLimiters = (config) => {
50
51
 
51
52
  limiters[name] = new Bottleneck(limiterOptions);
52
53
  limiters[name].on('error', (err) => {
53
- console.error(`Limiter error for ${cortexId}-${name}:`, err);
54
+ logger.error(`Limiter error for ${cortexId}-${name}: ${err}`);
54
55
  });
55
56
  monitors[name] = new RequestMonitor();
56
57
  }
@@ -81,9 +82,9 @@ setInterval(() => {
81
82
  const callRate = monitor.getPeakCallRate();
82
83
  const error429Rate = monitor.getError429Rate();
83
84
  if (callRate > 0) {
84
- console.log('------------------------');
85
- console.log(`${monitorName} Call rate: ${callRate} calls/sec, 429 errors: ${error429Rate * 100}%`);
86
- console.log('------------------------');
85
+ logger.info('------------------------');
86
+ logger.info(`${monitorName} Call rate: ${callRate} calls/sec, 429 errors: ${error429Rate * 100}%`);
87
+ logger.info('------------------------');
87
88
  // Reset the rate monitor to start a new monitoring interval.
88
89
  monitor.reset();
89
90
  }
@@ -109,7 +110,7 @@ const postRequest = async ({ url, data, params, headers, cache }, model, request
109
110
  let duplicateRequestAfter = (pathway?.duplicateRequestAfter || DUPLICATE_REQUEST_AFTER) * 1000;
110
111
 
111
112
  if (enableDuplicateRequests) {
112
- //console.log(`>>> [${requestId}] Duplicate requests enabled after ${duplicateRequestAfter / 1000} seconds`);
113
+ //logger.info(`>>> [${requestId}] Duplicate requests enabled after ${duplicateRequestAfter / 1000} seconds`);
113
114
  }
114
115
 
115
116
  const axiosConfigObj = { params, headers, cache };
@@ -119,7 +120,7 @@ const postRequest = async ({ url, data, params, headers, cache }, model, request
119
120
  promises.push(limiters[model].schedule(() => postWithMonitor(model, url, data, axiosConfigObj)));
120
121
  } else {
121
122
  if (streamRequested) {
122
- console.log(`>>> [${requestId}] ${model} does not support streaming - sending non-streaming request`);
123
+ logger.info(`>>> [${requestId}] ${model} does not support streaming - sending non-streaming request`);
123
124
  axiosConfigObj.params.stream = false;
124
125
  data.stream = false;
125
126
  }
@@ -144,7 +145,7 @@ const postRequest = async ({ url, data, params, headers, cache }, model, request
144
145
  axiosConfigObj.headers['X-Cortex-Request-Index'] = index;
145
146
 
146
147
  if (index === 0) {
147
- //console.log(`>>> [${requestId}] sending request to ${model} API ${axiosConfigObj.responseType === 'stream' ? 'with streaming' : ''}`);
148
+ //logger.info(`>>> [${requestId}] sending request to ${model} API ${axiosConfigObj.responseType === 'stream' ? 'with streaming' : ''}`);
148
149
  } else {
149
150
  if (modelProperties.supportsStreaming) {
150
151
  axiosConfigObj.responseType = 'stream';
@@ -152,23 +153,23 @@ const postRequest = async ({ url, data, params, headers, cache }, model, request
152
153
  }
153
154
  const logMessage = `>>> [${requestId}] taking too long - sending duplicate request ${index} to ${model} API ${axiosConfigObj.responseType === 'stream' ? 'with streaming' : ''}`;
154
155
  const header = '>'.repeat(logMessage.length);
155
- console.log(`\n${header}\n${logMessage}`);
156
+ logger.info(`\n${header}\n${logMessage}`);
156
157
  }
157
158
 
158
159
  response = await limiters[model].schedule(() => postWithMonitor(model, url, data, axiosConfigObj));
159
160
 
160
161
  if (!controller.signal?.aborted) {
161
162
 
162
- //console.log(`<<< [${requestId}] received response for request ${index}`);
163
+ //logger.info(`<<< [${requestId}] received response for request ${index}`);
163
164
 
164
165
  if (axiosConfigObj.responseType === 'stream') {
165
166
  // Buffering and collecting the stream data
166
- console.log(`<<< [${requestId}] buffering streaming response for request ${index}`);
167
+ logger.info(`<<< [${requestId}] buffering streaming response for request ${index}`);
167
168
  response = await new Promise((resolve, reject) => {
168
169
  let responseData = '';
169
170
  response.data.on('data', (chunk) => {
170
171
  responseData += chunk;
171
- //console.log(`<<< [${requestId}] received chunk for request ${index}`);
172
+ //logger.info(`<<< [${requestId}] received chunk for request ${index}`);
172
173
  });
173
174
  response.data.on('end', () => {
174
175
  response.data = JSON.parse(responseData);
@@ -186,10 +187,10 @@ const postRequest = async ({ url, data, params, headers, cache }, model, request
186
187
 
187
188
  } catch (error) {
188
189
  if (error.name === 'AbortError' || error.name === 'CanceledError') {
189
- //console.log(`XXX [${requestId}] request ${index} was cancelled`);
190
+ //logger.info(`XXX [${requestId}] request ${index} was cancelled`);
190
191
  reject(error);
191
192
  } else {
192
- console.log(`!!! [${requestId}] request ${index} failed with error: ${error?.response?.data?.error?.message || error}`);
193
+ logger.info(`!!! [${requestId}] request ${index} failed with error: ${error?.response?.data?.error?.message || error}`);
193
194
  reject(error);
194
195
  }
195
196
  } finally {
@@ -210,14 +211,14 @@ const postRequest = async ({ url, data, params, headers, cache }, model, request
210
211
  throw new Error(`Received error response: ${response.status}`);
211
212
  }
212
213
  } catch (error) {
213
- //console.error(`!!! [${requestId}] failed request with data ${JSON.stringify(data)}: ${error}`);
214
+ //logger.error(`!!! [${requestId}] failed request with data ${JSON.stringify(data)}: ${error}`);
214
215
  if (error.response) {
215
216
  const status = error.response.status;
216
217
  if ((status === 429) || (status >= 500 && status < 600)) {
217
218
  if (status === 429) {
218
219
  monitors[model].incrementError429Count();
219
220
  }
220
- console.log(`>>> [${requestId}] retrying request due to ${status} response. Retry count: ${i + 1}`);
221
+ logger.info(`>>> [${requestId}] retrying request due to ${status} response. Retry count: ${i + 1}`);
221
222
  if (i < MAX_RETRY - 1) {
222
223
  const backoffTime = 200 * Math.pow(2, i);
223
224
  const jitter = backoffTime * 0.2 * Math.random();
@@ -246,10 +247,10 @@ const request = async (params, model, requestId, pathway) => {
246
247
  const lastError = error[error.length - 1];
247
248
  return { error: lastError.toJSON() ?? lastError ?? error };
248
249
  }
249
- //console.log("<<< [${requestId}] response: ", data.choices[0].delta || data.choices[0])
250
+ //logger.info(`<<< [${requestId}] response: ${data.choices[0].delta || data.choices[0]}`)
250
251
  return data;
251
252
  } catch (error) {
252
- console.error(`Error in request: ${error.message || error}`);
253
+ logger.error(`Error in request: ${error.message || error}`);
253
254
  return { error: error };
254
255
  }
255
256
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aj-archipelago/cortex",
3
- "version": "1.0.25",
3
+ "version": "1.1.0-beta.0",
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": {
@@ -54,6 +54,7 @@
54
54
  "langchain": "^0.0.47",
55
55
  "subsrt": "^1.1.1",
56
56
  "uuid": "^9.0.0",
57
+ "winston": "^3.11.0",
57
58
  "ws": "^8.12.0"
58
59
  },
59
60
  "devDependencies": {
@@ -62,7 +63,7 @@
62
63
  "eslint": "^8.38.0",
63
64
  "eslint-plugin-import": "^2.27.5",
64
65
  "got": "^13.0.0",
65
- "sinon": "^15.0.3"
66
+ "sinon": "^17.0.1"
66
67
  },
67
68
  "publishConfig": {
68
69
  "access": "restricted"
package/server/graphql.js CHANGED
@@ -22,6 +22,7 @@ import { buildPathways, buildModels } from '../config.js';
22
22
  import { requestState } from './requestState.js';
23
23
  import { buildRestEndpoints } from './rest.js';
24
24
  import { startTestServer } from '../tests/server.js'
25
+ import logger from '../lib/logger.js';
25
26
 
26
27
  // Utility functions
27
28
  // Server plugins
@@ -41,7 +42,7 @@ const getPlugins = (config) => {
41
42
  // TODO: custom cache key:
42
43
  // https://www.apollographql.com/docs/apollo-server/performance/cache-backends#implementing-your-own-cache-backend
43
44
  plugins.push(responseCachePlugin({ cache }));
44
- console.log('Using Redis for GraphQL cache');
45
+ logger.info('Using Redis for GraphQL cache');
45
46
  }
46
47
 
47
48
  return { plugins, cache };
@@ -146,12 +147,12 @@ const build = async (config) => {
146
147
  // Respects the keep alive setting in config in case you want to
147
148
  // turn it off for deployments that don't route the ping/pong frames
148
149
  const keepAlive = config.get('subscriptionKeepAlive');
149
- console.log(`Starting web socket server with subscription keep alive: ${keepAlive}`);
150
+ logger.info(`Starting web socket server with subscription keep alive: ${keepAlive}`);
150
151
  const serverCleanup = useServer({ schema }, wsServer, keepAlive);
151
152
 
152
153
  const server = new ApolloServer({
153
154
  schema,
154
- introspection: process.env.NODE_ENV === 'development',
155
+ introspection: config.get('env') === 'development',
155
156
  csrfPrevention: true,
156
157
  plugins: plugins.concat([// Proper shutdown for the HTTP server.
157
158
  ApolloServerPluginDrainHttpServer({ httpServer }),
@@ -232,7 +233,7 @@ const build = async (config) => {
232
233
 
233
234
  // Now that our HTTP server is fully set up, we can listen to it.
234
235
  httpServer.listen(config.get('PORT'), () => {
235
- console.log(`🚀 Server is now running at http://localhost:${config.get('PORT')}/graphql`);
236
+ logger.info(`🚀 Server is now running at http://localhost:${config.get('PORT')}/graphql`);
236
237
  });
237
238
  };
238
239
 
@@ -1,7 +1,6 @@
1
1
  import { PathwayPrompter } from './pathwayPrompter.js';
2
2
  // eslint-disable-next-line import/no-extraneous-dependencies
3
3
  import { v4 as uuidv4 } from 'uuid';
4
- import pubsub from './pubsub.js';
5
4
  import { encode } from 'gpt-3-encoder';
6
5
  import { getFirstNToken, getLastNToken, getSemanticChunks } from './chunker.js';
7
6
  import { PathwayResponseParser } from './pathwayResponseParser.js';
@@ -9,6 +8,8 @@ import { Prompt } from './prompt.js';
9
8
  import { getv, setv } from '../lib/keyValueStorageClient.js';
10
9
  import { requestState } from './requestState.js';
11
10
  import { callPathway } from '../lib/pathwayTools.js';
11
+ import { publishRequestProgress } from '../lib/redisSubscription.js';
12
+ import logger from '../lib/logger.js';
12
13
 
13
14
  const modelTypesExcludedFromProgressUpdates = ['OPENAI-DALLE2', 'OPENAI-DALLE3'];
14
15
 
@@ -83,12 +84,10 @@ class PathwayResolver {
83
84
 
84
85
  // if model type is OPENAI-IMAGE
85
86
  if (!modelTypesExcludedFromProgressUpdates.includes(this.model.type)) {
86
- pubsub.publish('REQUEST_PROGRESS', {
87
- requestProgress: {
87
+ await publishRequestProgress({
88
88
  requestId: this.requestId,
89
89
  progress: completedCount / totalCount,
90
90
  data: JSON.stringify(responseData),
91
- }
92
91
  });
93
92
  }
94
93
  } else {
@@ -99,7 +98,7 @@ class PathwayResolver {
99
98
 
100
99
  const processData = (data) => {
101
100
  try {
102
- //console.log(`\n\nReceived stream data for requestId ${this.requestId}`, data.toString());
101
+ //logger.info(`\n\nReceived stream data for requestId ${this.requestId}: ${data.toString()}`);
103
102
  let events = data.toString().split('\n');
104
103
 
105
104
  //events = "data: {\"id\":\"chatcmpl-20bf1895-2fa7-4ef9-abfe-4d142aba5817\",\"object\":\"chat.completion.chunk\",\"created\":1689303423723,\"model\":\"gpt-4\",\"choices\":[{\"delta\":{\"role\":\"assistant\",\"content\":{\"error\":{\"message\":\"The server had an error while processing your request. Sorry about that!\",\"type\":\"server_error\",\"param\":null,\"code\":null}}},\"finish_reason\":null}]}\n\n".split("\n");
@@ -109,7 +108,7 @@ class PathwayResolver {
109
108
 
110
109
  // skip empty events
111
110
  if (!(event.trim() === '')) {
112
- //console.log(`Processing stream event for requestId ${this.requestId}`, event);
111
+ //logger.info(`Processing stream event for requestId ${this.requestId}: ${event}`);
113
112
  messageBuffer += event.replace(/^data: /, '');
114
113
 
115
114
  const requestProgress = {
@@ -133,24 +132,22 @@ class PathwayResolver {
133
132
  const streamError = parsedMessage?.error || parsedMessage?.choices?.[0]?.delta?.content?.error || parsedMessage?.choices?.[0]?.text?.error;
134
133
  if (streamError) {
135
134
  streamErrorOccurred = true;
136
- console.error(`Stream error: ${streamError.message}`);
135
+ logger.error(`Stream error: ${streamError.message}`);
137
136
  incomingMessage.off('data', processData); // Stop listening to 'data'
138
137
  return;
139
138
  }
140
139
  }
141
140
 
142
141
  try {
143
- //console.log(`Publishing stream message to requestId ${this.requestId}`, message);
144
- pubsub.publish('REQUEST_PROGRESS', {
145
- requestProgress: requestProgress
146
- });
142
+ //logger.info(`Publishing stream message to requestId ${this.requestId}: ${message}`);
143
+ publishRequestProgress(requestProgress);
147
144
  } catch (error) {
148
- console.error('Could not publish the stream message', messageBuffer, error);
145
+ logger.error(`Could not publish the stream message: "${messageBuffer}", ${error}`);
149
146
  }
150
147
  }
151
148
  }
152
149
  } catch (error) {
153
- console.error('Could not process stream data', error);
150
+ logger.error(`Could not process stream data: ${error}`);
154
151
  }
155
152
  };
156
153
 
@@ -163,25 +160,23 @@ class PathwayResolver {
163
160
  }
164
161
 
165
162
  } catch (error) {
166
- console.error('Could not subscribe to stream', error);
163
+ logger.error(`Could not subscribe to stream: ${error}`);
167
164
  }
168
165
  }
169
166
 
170
167
  if (streamErrorOccurred) {
171
168
  attempt++;
172
- console.error(`Stream attempt ${attempt} failed. Retrying...`);
169
+ logger.error(`Stream attempt ${attempt} failed. Retrying...`);
173
170
  streamErrorOccurred = false; // Reset the flag for the next attempt
174
171
  } else {
175
172
  return;
176
173
  }
177
174
  }
178
175
  // if all retries failed, publish the stream end message
179
- pubsub.publish('REQUEST_PROGRESS', {
180
- requestProgress: {
176
+ publishRequestProgress({
181
177
  requestId: this.requestId,
182
178
  progress: 1,
183
179
  data: '[DONE]',
184
- }
185
180
  });
186
181
  }
187
182
 
@@ -401,11 +396,9 @@ class PathwayResolver {
401
396
  const { completedCount, totalCount } = requestState[this.requestId];
402
397
 
403
398
  if (completedCount < totalCount) {
404
- pubsub.publish('REQUEST_PROGRESS', {
405
- requestProgress: {
399
+ await publishRequestProgress({
406
400
  requestId: this.requestId,
407
401
  progress: completedCount / totalCount,
408
- }
409
402
  });
410
403
  }
411
404
 
@@ -5,6 +5,7 @@ import { v4 as uuidv4 } from 'uuid';
5
5
  import path from 'path';
6
6
  import { config } from '../../config.js';
7
7
  import { axios } from '../../lib/request.js';
8
+ import logger from '../../lib/logger.js';
8
9
 
9
10
  const API_URL = config.get('whisperMediaApiUrl');
10
11
 
@@ -24,7 +25,7 @@ class AzureCognitivePlugin extends ModelPlugin {
24
25
  }
25
26
  return JSON.parse(await callPathway(this.config, 'embeddings', { text }))[0];
26
27
  }catch(err){
27
- console.log(`Error in calculating input vector for text: ${text}, error: ${err}`);
28
+ logger.error(`Error in calculating input vector for text: ${text}, error: ${err}`);
28
29
  }
29
30
  }
30
31
 
@@ -134,11 +135,11 @@ class AzureCognitivePlugin extends ModelPlugin {
134
135
  if (API_URL) {
135
136
  //call helper api to mark processing as completed
136
137
  const res = await axios.delete(API_URL, { params: { requestId } });
137
- console.log(`Marked request ${requestId} as completed:`, res.data);
138
+ logger.info(`Marked request ${requestId} as completed: ${res.data}`);
138
139
  return res.data;
139
140
  }
140
141
  } catch (err) {
141
- console.log(`Error marking request ${requestId} as completed:`, err);
142
+ logger.error(`Error marking request ${requestId} as completed: ${err}`);
142
143
  }
143
144
  }
144
145
 
@@ -161,7 +162,7 @@ class AzureCognitivePlugin extends ModelPlugin {
161
162
  const { data } = await axios.get(API_URL, { params: { uri: file, requestId, save: true } });
162
163
  url = data[0];
163
164
  } catch (error) {
164
- console.error(`Error converting file ${file} to txt:`, error);
165
+ logger.error(`Error converting file ${file} to txt: ${error}`);
165
166
  await this.markCompletedForCleanUp(requestId);
166
167
  throw Error(error?.response?.data || error?.message || error);
167
168
  }
@@ -52,8 +52,8 @@ class AzureTranslatePlugin extends ModelPlugin {
52
52
 
53
53
  const modelInput = data[0].Text;
54
54
 
55
- console.log(`\x1b[36m${modelInput}\x1b[0m`);
56
- console.log(`\x1b[34m> ${this.parseResponse(responseData)}\x1b[0m`);
55
+ logger.debug(`${modelInput}`);
56
+ logger.debug(`${this.parseResponse(responseData)}`);
57
57
 
58
58
  prompt && prompt.debugInfo && (prompt.debugInfo += `\n${JSON.stringify(data)}`);
59
59
  }
@@ -54,16 +54,16 @@ class LocalModelPlugin extends ModelPlugin {
54
54
  //args.push("--temperature", requestParameters.temperature);
55
55
 
56
56
  try {
57
- console.log(`\x1b[36mRunning local model:\x1b[0m`, executablePath, args);
57
+ logger.debug(`Running local model: ${executablePath}, ${args}`);
58
58
  const result = execFileSync(executablePath, args, { encoding: 'utf8' });
59
59
  // Remove only the first occurrence of requestParameters.prompt from the result
60
60
  // Could have used regex here but then would need to escape the prompt
61
61
  const parts = result.split(requestParameters.prompt, 2);
62
62
  const modifiedResult = parts[0] + parts[1];
63
- console.log(`\x1b[36mResult:\x1b[0m`, modifiedResult);
63
+ logger.debug(`Result: ${modifiedResult}`);
64
64
  return this.filterFirstResponse(modifiedResult);
65
65
  } catch (error) {
66
- console.error(`\x1b[31mError running local model:\x1b[0m`, error);
66
+ logger.error(`Error running local model: ${error}`);
67
67
  throw error;
68
68
  }
69
69
  }
@@ -4,6 +4,7 @@ import HandleBars from '../../lib/handleBars.js';
4
4
  import { request } from '../../lib/request.js';
5
5
  import { encode } from 'gpt-3-encoder';
6
6
  import { getFirstNToken } from '../chunker.js';
7
+ import logger from '../../lib/logger.js';
7
8
 
8
9
  const DEFAULT_MAX_TOKENS = 4096;
9
10
  const DEFAULT_MAX_RETURN_TOKENS = 256;
@@ -224,8 +225,9 @@ class ModelPlugin {
224
225
  this.lastRequestStartTime = new Date();
225
226
  const logMessage = `>>> [${this.requestId}: ${this.pathwayName}.${this.requestCount}] request`;
226
227
  const header = '>'.repeat(logMessage.length);
227
- console.log(`\n${header}\n${logMessage}`);
228
- console.log(`>>> Making API request to ${url}`);
228
+ logger.info(`${header}`);
229
+ logger.info(`${logMessage}`);
230
+ logger.info(`>>> Making API request to ${url}`);
229
231
  }
230
232
 
231
233
  logAIRequestFinished() {
@@ -233,7 +235,8 @@ class ModelPlugin {
233
235
  const timeElapsed = (currentTime - this.lastRequestStartTime) / 1000;
234
236
  const logMessage = `<<< [${this.requestId}: ${this.pathwayName}] response - complete in ${timeElapsed}s - data:`;
235
237
  const header = '<'.repeat(logMessage.length);
236
- console.log(`\n${header}\n${logMessage}\n`);
238
+ logger.info(`${header}`);
239
+ logger.info(`${logMessage}`);
237
240
  }
238
241
 
239
242
  logRequestData(data, responseData, prompt) {
@@ -241,10 +244,15 @@ class ModelPlugin {
241
244
  const modelInput = data.prompt || (data.messages && data.messages[0].content) || (data.length > 0 && data[0].Text) || null;
242
245
 
243
246
  if (modelInput) {
244
- console.log(`\x1b[36m${modelInput}\x1b[0m`);
247
+ const inputTokens = encode(modelInput).length;
248
+ logger.info(`[request sent containing ${inputTokens} tokens]`);
249
+ logger.debug(`${modelInput}`);
245
250
  }
246
251
 
247
- console.log(`\x1b[34m> ${JSON.stringify(this.parseResponse(responseData))}\x1b[0m`);
252
+ const responseText = JSON.stringify(this.parseResponse(responseData));
253
+ const responseTokens = encode(responseText).length;
254
+ logger.info(`[response received containing ${responseTokens} tokens]`);
255
+ logger.debug(`${responseText}`);
248
256
 
249
257
  prompt && prompt.debugInfo && (prompt.debugInfo += `\n${JSON.stringify(data)}`);
250
258
  }
@@ -264,7 +272,8 @@ class ModelPlugin {
264
272
  return this.parseResponse(responseData);
265
273
  } catch (error) {
266
274
  // Log the error and continue
267
- console.error(error);
275
+ logger.error(error.message || error);
276
+ throw error;
268
277
  }
269
278
  }
270
279
 
@@ -1,6 +1,7 @@
1
1
  // OpenAIChatPlugin.js
2
2
  import ModelPlugin from './modelPlugin.js';
3
3
  import { encode } from 'gpt-3-encoder';
4
+ import logger from '../../lib/logger.js';
4
5
 
5
6
  class OpenAIChatPlugin extends ModelPlugin {
6
7
  constructor(config, pathway, modelName, model) {
@@ -110,6 +111,8 @@ class OpenAIChatPlugin extends ModelPlugin {
110
111
 
111
112
  const { stream, messages } = data;
112
113
  if (messages && messages.length > 1) {
114
+ logger.info(`[chat request sent containing ${messages.length} messages]`);
115
+ let totalTokens = 0;
113
116
  messages.forEach((message, index) => {
114
117
  //message.content string or array
115
118
  const content = Array.isArray(message.content) ? message.content.map(item => JSON.stringify(item)).join(', ') : message.content;
@@ -117,16 +120,25 @@ class OpenAIChatPlugin extends ModelPlugin {
117
120
  const tokenCount = encode(content).length;
118
121
  const preview = words.length < 41 ? content : words.slice(0, 20).join(" ") + " ... " + words.slice(-20).join(" ");
119
122
 
120
- console.log(`\x1b[36mMessage ${index + 1}: Role: ${message.role}, Tokens: ${tokenCount}, Content: "${preview}"\x1b[0m`);
123
+ logger.debug(`Message ${index + 1}: Role: ${message.role}, Tokens: ${tokenCount}, Content: "${preview}"`);
124
+ totalTokens += tokenCount;
121
125
  });
126
+ logger.info(`[chat request contained ${totalTokens} tokens]`);
122
127
  } else {
123
- console.log(`\x1b[36m${messages[0].content}\x1b[0m`);
128
+ const message = messages[0];
129
+ const content = Array.isArray(message.content) ? message.content.map(item => JSON.stringify(item)).join(', ') : message.content;
130
+ const tokenCount = encode(content).length;
131
+ logger.info(`[request sent containing ${tokenCount} tokens]`);
132
+ logger.debug(`${content}`);
124
133
  }
125
134
 
126
135
  if (stream) {
127
- console.log(`\x1b[34m> [response is an SSE stream]\x1b[0m`);
136
+ logger.info(`[response received as an SSE stream]`);
128
137
  } else {
129
- console.log(`\x1b[34m> ${this.parseResponse(responseData)}\x1b[0m`);
138
+ const responseText = this.parseResponse(responseData);
139
+ const responseTokens = encode(responseText).length;
140
+ logger.info(`[response received containing ${responseTokens} tokens]`);
141
+ logger.debug(`${responseText}`);
130
142
  }
131
143
 
132
144
  prompt && prompt.debugInfo && (prompt.debugInfo += `\n${JSON.stringify(data)}`);
@@ -3,6 +3,7 @@
3
3
  import { request } from 'https';
4
4
  import ModelPlugin from './modelPlugin.js';
5
5
  import { encode } from 'gpt-3-encoder';
6
+ import logger from '../../lib/logger.js';
6
7
 
7
8
  // Helper function to truncate the prompt if it is too long
8
9
  const truncatePromptIfNecessary = (text, textTokenCount, modelMaxTokenCount, targetTextTokenCount, pathwayResolver) => {
@@ -112,12 +113,17 @@ class OpenAICompletionPlugin extends ModelPlugin {
112
113
  const stream = data.stream;
113
114
  const modelInput = data.prompt;
114
115
 
115
- console.log(`\x1b[36m${modelInput}\x1b[0m`);
116
+ const modelInputTokens = encode(modelInput).length;
117
+ logger.info(`[request sent containing ${modelInputTokens} tokens]`);
118
+ logger.debug(`${modelInput}`);
116
119
 
117
120
  if (stream) {
118
- console.log(`\x1b[34m> [response is an SSE stream]\x1b[0m`);
121
+ logger.info(`[response received as an SSE stream]`);
119
122
  } else {
120
- console.log(`\x1b[34m> ${this.parseResponse(responseData)}\x1b[0m`);
123
+ const responseText = this.parseResponse(responseData);
124
+ const responseTokens = encode(responseText).length;
125
+ logger.info(`[response received containing ${responseTokens} tokens]`);
126
+ logger.debug(`${responseText}`);
121
127
  }
122
128
 
123
129
  prompt && prompt.debugInfo && (prompt.debugInfo += `\n${JSON.stringify(data)}`);
@@ -1,7 +1,8 @@
1
1
  import RequestDurationEstimator from '../../lib/requestDurationEstimator.js';
2
- import pubsub from '../pubsub.js';
3
2
  import ModelPlugin from './modelPlugin.js';
4
3
  import { request } from '../../lib/request.js';
4
+ import { publishRequestProgress } from '../../lib/redisSubscription.js';
5
+ import logger from '../../lib/logger.js';
5
6
 
6
7
  const requestDurationEstimator = new RequestDurationEstimator(10);
7
8
 
@@ -30,7 +31,7 @@ class OpenAIDallE3Plugin extends ModelPlugin {
30
31
  return this.parseResponse(responseData);
31
32
  } catch (error) {
32
33
  // Log the error and continue
33
- console.error(error);
34
+ logger.error(error.message || error);
34
35
  }
35
36
  }
36
37
 
@@ -83,7 +84,7 @@ class OpenAIDallE3Plugin extends ModelPlugin {
83
84
 
84
85
  state.status = status;
85
86
  requestDurationEstimator.endRequest();
86
- pubsub.publish("REQUEST_PROGRESS", { requestProgress });
87
+ publishRequestProgress(requestProgress);
87
88
  }
88
89
 
89
90
  // publish an update every 2 seconds, using the request duration estimator to calculate
@@ -92,14 +93,12 @@ class OpenAIDallE3Plugin extends ModelPlugin {
92
93
  let progress =
93
94
  requestDurationEstimator.calculatePercentComplete();
94
95
 
95
- pubsub.publish('REQUEST_PROGRESS', {
96
- requestProgress: {
96
+ await publishRequestProgress({
97
97
  requestId,
98
98
  status: "pending",
99
99
  progress,
100
100
  data,
101
- }
102
- });
101
+ });
103
102
 
104
103
  if (state.status !== "pending") {
105
104
  break;
@@ -1,10 +1,9 @@
1
1
  // OpenAIImagePlugin.js
2
- import FormData from 'form-data';
3
- import { config } from '../../config.js';
4
2
  import ModelPlugin from './modelPlugin.js';
5
- import pubsub from '../pubsub.js';
6
3
  import axios from 'axios';
7
4
  import RequestDurationEstimator from '../../lib/requestDurationEstimator.js';
5
+ import { publishRequestProgress } from '../../lib/redisSubscription.js';
6
+ import logger from '../../lib/logger.js';
8
7
 
9
8
  const requestDurationEstimator = new RequestDurationEstimator(10);
10
9
 
@@ -26,7 +25,7 @@ class OpenAIImagePlugin extends ModelPlugin {
26
25
  id = (await this.executeRequest(url, data, {}, { ...this.model.headers }, {}, requestId, pathway))?.id;
27
26
  } catch (error) {
28
27
  const errMsg = `Error generating image: ${error?.message || JSON.stringify(error)}`;
29
- console.error(errMsg);
28
+ logger.error(errMsg);
30
29
  return errMsg;
31
30
  }
32
31
 
@@ -59,13 +58,11 @@ class OpenAIImagePlugin extends ModelPlugin {
59
58
  data = JSON.stringify(response);
60
59
  }
61
60
 
62
- pubsub.publish('REQUEST_PROGRESS', {
63
- requestProgress: {
61
+ await publishRequestProgress({
64
62
  requestId,
65
63
  status,
66
64
  progress,
67
65
  data,
68
- }
69
66
  });
70
67
 
71
68
  if (status === "succeeded") {
@@ -2,7 +2,6 @@
2
2
  import ModelPlugin from './modelPlugin.js';
3
3
  import FormData from 'form-data';
4
4
  import fs from 'fs';
5
- import pubsub from '../pubsub.js';
6
5
  import { axios } from '../../lib/request.js';
7
6
  import stream from 'stream';
8
7
  import os from 'os';
@@ -14,8 +13,10 @@ import http from 'http';
14
13
  import https from 'https';
15
14
  import { promisify } from 'util';
16
15
  import subsrt from 'subsrt';
17
- const pipeline = promisify(stream.pipeline);
16
+ import { publishRequestProgress } from '../../lib/redisSubscription.js';
17
+ import logger from '../../lib/logger.js';
18
18
 
19
+ const pipeline = promisify(stream.pipeline);
19
20
 
20
21
  const API_URL = config.get('whisperMediaApiUrl');
21
22
  const WHISPER_TS_API_URL = config.get('whisperTSApiUrl');
@@ -47,7 +48,7 @@ function alignSubtitles(subtitles, format) {
47
48
  }
48
49
  }
49
50
  } catch (error) {
50
- console.error("An error occurred in content text parsing: ", error);
51
+ logger.error(`An error occurred in content text parsing: ${error}`);
51
52
  }
52
53
 
53
54
  return subsrt.build(result, { format: format === 'vtt' ? 'vtt' : 'srt' });
@@ -80,7 +81,7 @@ const downloadFile = async (fileUrl) => {
80
81
  });
81
82
 
82
83
  await pipeline(response, fs.createWriteStream(localFilePath));
83
- console.log(`Downloaded file to ${localFilePath}`);
84
+ logger.info(`Downloaded file to ${localFilePath}`);
84
85
  resolve(localFilePath);
85
86
  } catch (error) {
86
87
  fs.unlink(localFilePath, () => {
@@ -103,11 +104,11 @@ class OpenAIWhisperPlugin extends ModelPlugin {
103
104
  const res = await axios.get(API_URL, { params: { uri: file, requestId } });
104
105
  return res.data;
105
106
  } else {
106
- console.log(`No API_URL set, returning file as chunk`);
107
+ logger.info(`No API_URL set, returning file as chunk`);
107
108
  return [file];
108
109
  }
109
110
  } catch (err) {
110
- console.log(`Error getting media chunks list from api:`, err);
111
+ logger.error(`Error getting media chunks list from api:`, err);
111
112
  throw err;
112
113
  }
113
114
  }
@@ -117,11 +118,11 @@ class OpenAIWhisperPlugin extends ModelPlugin {
117
118
  if (API_URL) {
118
119
  //call helper api to mark processing as completed
119
120
  const res = await axios.delete(API_URL, { params: { requestId } });
120
- console.log(`Marked request ${requestId} as completed:`, res.data);
121
+ logger.info(`Marked request ${requestId} as completed: ${res.data}`);
121
122
  return res.data;
122
123
  }
123
124
  } catch (err) {
124
- console.log(`Error marking request ${requestId} as completed:`, err);
125
+ logger.error(`Error marking request ${requestId} as completed: ${err}`);
125
126
  }
126
127
  }
127
128
 
@@ -149,7 +150,7 @@ class OpenAIWhisperPlugin extends ModelPlugin {
149
150
  const res = await this.executeRequest(WHISPER_TS_API_URL, tsparams, {}, {}, {}, requestId, pathway);
150
151
  return res;
151
152
  } catch (err) {
152
- console.log(`Error getting word timestamped data from api:`, err);
153
+ logger.error(`Error getting word timestamped data from api: ${err}`);
153
154
  throw err;
154
155
  }
155
156
  }
@@ -169,7 +170,7 @@ class OpenAIWhisperPlugin extends ModelPlugin {
169
170
 
170
171
  return this.executeRequest(url, formData, params, { ...this.model.headers, ...formData.getHeaders() }, {}, requestId, pathway);
171
172
  } catch (err) {
172
- console.log(err);
173
+ logger.error(err);
173
174
  throw err;
174
175
  }
175
176
  }
@@ -183,13 +184,12 @@ class OpenAIWhisperPlugin extends ModelPlugin {
183
184
  const sendProgress = () => {
184
185
  completedCount++;
185
186
  if (completedCount >= totalCount) return;
186
- pubsub.publish('REQUEST_PROGRESS', {
187
- requestProgress: {
187
+ publishRequestProgress({
188
188
  requestId,
189
189
  progress: completedCount / totalCount,
190
190
  data: null,
191
- }
192
191
  });
192
+
193
193
  }
194
194
 
195
195
  let chunks = []; // array of local file paths
@@ -225,7 +225,7 @@ class OpenAIWhisperPlugin extends ModelPlugin {
225
225
 
226
226
  } catch (error) {
227
227
  const errMsg = `Transcribe error: ${error?.message || JSON.stringify(error)}`;
228
- console.error(errMsg);
228
+ logger.error(errMsg);
229
229
  return errMsg;
230
230
  }
231
231
  finally {
@@ -242,11 +242,11 @@ class OpenAIWhisperPlugin extends ModelPlugin {
242
242
  if (match && match[1]) {
243
243
  const extractedValue = match[1];
244
244
  await this.markCompletedForCleanUp(extractedValue);
245
- console.log(`Cleaned temp whisper file ${file} with request id ${extractedValue}`);
245
+ logger.info(`Cleaned temp whisper file ${file} with request id ${extractedValue}`);
246
246
  }
247
247
 
248
248
  } catch (error) {
249
- console.error("An error occurred while deleting:", error);
249
+ logger.error(`An error occurred while deleting: ${error}`);
250
250
  }
251
251
  }
252
252
 
@@ -2,6 +2,7 @@
2
2
  import ModelPlugin from './modelPlugin.js';
3
3
  import { encode } from 'gpt-3-encoder';
4
4
  import HandleBars from '../../lib/handleBars.js';
5
+ import logger from '../../lib/logger.js';
5
6
 
6
7
  class PalmChatPlugin extends ModelPlugin {
7
8
  constructor(config, pathway, modelName, model) {
@@ -189,33 +190,40 @@ class PalmChatPlugin extends ModelPlugin {
189
190
  const { context, examples } = instances && instances [0] || {};
190
191
 
191
192
  if (context) {
192
- console.log(`\x1b[36mContext: ${context}\x1b[0m`);
193
+ const contextLength = encode(context).length;
194
+ logger.info(`[chat request contains context information of length ${contextLength} tokens]`)
195
+ logger.debug(`Context: ${context}`);
193
196
  }
194
197
 
195
198
  if (examples && examples.length) {
199
+ logger.info(`[chat request contains ${examples.length} examples]`);
196
200
  examples.forEach((example, index) => {
197
- console.log(`\x1b[36mExample ${index + 1}: Input: "${example.input.content}", Output: "${example.output.content}"\x1b[0m`);
201
+ logger.debug(`Example ${index + 1}: Input: "${example.input.content}", Output: "${example.output.content}"`);
198
202
  });
199
203
  }
200
204
 
201
205
  if (messages && messages.length > 1) {
206
+ logger.info(`[chat request contains ${messages.length} messages]`);
202
207
  messages.forEach((message, index) => {
203
208
  const words = message.content.split(" ");
204
209
  const tokenCount = encode(message.content).length;
205
210
  const preview = words.length < 41 ? message.content : words.slice(0, 20).join(" ") + " ... " + words.slice(-20).join(" ");
206
211
 
207
- console.log(`\x1b[36mMessage ${index + 1}: Author: ${message.author}, Tokens: ${tokenCount}, Content: "${preview}"\x1b[0m`);
212
+ logger.debug(`Message ${index + 1}: Author: ${message.author}, Tokens: ${tokenCount}, Content: "${preview}"`);
208
213
  });
209
214
  } else if (messages && messages.length === 1) {
210
- console.log(`\x1b[36m${messages[0].content}\x1b[0m`);
215
+ logger.debug(`${messages[0].content}`);
211
216
  }
212
217
 
213
218
  const safetyAttributes = this.getSafetyAttributes(responseData);
214
219
 
215
- console.log(`\x1b[34m> ${this.parseResponse(responseData)}\x1b[0m`);
220
+ const responseText = this.parseResponse(responseData);
221
+ const responseTokens = encode(responseText).length;
222
+ logger.info(`[response received containing ${responseTokens} tokens]`);
223
+ logger.debug(`${responseText}`);
216
224
 
217
225
  if (safetyAttributes) {
218
- console.log(`\x1b[33mSafety Attributes: ${JSON.stringify(safetyAttributes, null, 2)}\x1b[0m`);
226
+ logger.warn(`[response contains safety attributes: ${JSON.stringify(safetyAttributes, null, 2)}]`);
219
227
  }
220
228
 
221
229
  if (prompt && prompt.debugInfo) {
@@ -1,6 +1,8 @@
1
1
  // palmCompletionPlugin.js
2
2
 
3
3
  import ModelPlugin from './modelPlugin.js';
4
+ import { encode } from 'gpt-3-encoder';
5
+ import logger from '../../lib/logger.js';
4
6
 
5
7
  // PalmCompletionPlugin class for handling requests and responses to the PaLM API Text Completion API
6
8
  class PalmCompletionPlugin extends ModelPlugin {
@@ -114,13 +116,18 @@ class PalmCompletionPlugin extends ModelPlugin {
114
116
  const modelInput = instances && instances[0] && instances[0].prompt;
115
117
 
116
118
  if (modelInput) {
117
- console.log(`\x1b[36m${modelInput}\x1b[0m`);
119
+ const inputTokens = encode(modelInput).length;
120
+ logger.info(`[request sent containing ${inputTokens} tokens]`);
121
+ logger.debug(`${modelInput}`);
118
122
  }
119
123
 
120
- console.log(`\x1b[34m> ${this.parseResponse(responseData)}\x1b[0m`);
124
+ const responseText = this.parseResponse(responseData);
125
+ const responseTokens = encode(responseText).length;
126
+ logger.info(`[response received containing ${responseTokens} tokens]`);
127
+ logger.debug(`${responseText}`);
121
128
 
122
129
  if (safetyAttributes) {
123
- console.log(`\x1b[33mSafety Attributes: ${JSON.stringify(safetyAttributes, null, 2)}\x1b[0m`);
130
+ logger.warn(`[response contains safety attributes: ${JSON.stringify(safetyAttributes, null, 2)}]`);
124
131
  }
125
132
 
126
133
  if (prompt && prompt.debugInfo) {
package/server/pubsub.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { PubSub } from 'graphql-subscriptions';
2
2
  const pubsub = new PubSub();
3
+ pubsub.ee.setMaxListeners(300);
3
4
 
4
5
  export default pubsub;
package/server/rest.js CHANGED
@@ -5,6 +5,7 @@ import { json } from 'express';
5
5
  import pubsub from './pubsub.js';
6
6
  import { requestState } from './requestState.js';
7
7
  import { v4 as uuidv4 } from 'uuid';
8
+ import logger from '../lib/logger.js';
8
9
 
9
10
 
10
11
  const processRestRequest = async (server, req, pathway, name, parameterMap = {}) => {
@@ -85,7 +86,7 @@ const processIncomingStream = (requestId, res, jsonResponse) => {
85
86
  }
86
87
 
87
88
  const sendStreamData = (data) => {
88
- //console.log(`REST SEND: data: ${JSON.stringify(data)}`);
89
+ //logger.info(`REST SEND: data: ${JSON.stringify(data)}`);
89
90
  const dataString = (data==='[DONE]') ? data : JSON.stringify(data);
90
91
 
91
92
  if (!res.writableEnded) {
@@ -116,13 +117,13 @@ const processIncomingStream = (requestId, res, jsonResponse) => {
116
117
  try {
117
118
  pubsub.unsubscribe(await subscription);
118
119
  } catch (error) {
119
- console.error(`Error unsubscribing from pubsub: ${error}`);
120
+ logger.error(`Error unsubscribing from pubsub: ${error}`);
120
121
  }
121
122
  }
122
123
  }
123
124
 
124
125
  if (data.requestProgress.requestId === requestId) {
125
- //console.log(`REQUEST_PROGRESS received progress: ${data.requestProgress.progress}, data: ${data.requestProgress.data}`);
126
+ //logger.info(`REQUEST_PROGRESS received progress: ${data.requestProgress.progress}, data: ${data.requestProgress.data}`);
126
127
 
127
128
  const progress = data.requestProgress.progress;
128
129
  const progressData = data.requestProgress.data;
@@ -130,7 +131,7 @@ const processIncomingStream = (requestId, res, jsonResponse) => {
130
131
  try {
131
132
  const messageJson = JSON.parse(progressData);
132
133
  if (messageJson.error) {
133
- console.error(`Stream error REST:`, messageJson?.error?.message);
134
+ logger.error(`Stream error REST: ${messageJson?.error?.message || 'unknown error'}`);
134
135
  safeUnsubscribe();
135
136
  finishStream(res, jsonResponse);
136
137
  return;
@@ -146,7 +147,7 @@ const processIncomingStream = (requestId, res, jsonResponse) => {
146
147
  fillJsonResponse(jsonResponse, messageJson, null);
147
148
  }
148
149
  } catch (error) {
149
- //console.log(`progressData not JSON: ${progressData}`);
150
+ //logger.info(`progressData not JSON: ${progressData}`);
150
151
  fillJsonResponse(jsonResponse, progressData, "stop");
151
152
  }
152
153
  if (progress === 1 && progressData.trim() === "[DONE]") {
@@ -165,7 +166,7 @@ const processIncomingStream = (requestId, res, jsonResponse) => {
165
166
  });
166
167
 
167
168
  // Fire the resolver for the async requestProgress
168
- console.log(`Rest Endpoint starting async requestProgress, requestId: ${requestId}`);
169
+ logger.info(`Rest Endpoint starting async requestProgress, requestId: ${requestId}`);
169
170
  const { resolver, args } = requestState[requestId];
170
171
  resolver(args);
171
172
 
@@ -1,25 +1,13 @@
1
- // TODO: Replace PubSub class with PubSub engine to support
2
- // multi-server instance
3
- // See https://www.apollographql.com/docs/apollo-server/v3/data/subscriptions/#resolving-a-subscription
4
-
5
1
  import pubsub from './pubsub.js';
6
-
2
+ import logger from '../lib/logger.js';
7
3
  import { withFilter } from 'graphql-subscriptions';
8
- import { requestState } from './requestState.js';
4
+ import { publishRequestProgressSubscription } from '../lib/redisSubscription.js';
9
5
 
10
6
  const subscriptions = {
11
7
  requestProgress: {
12
8
  subscribe: withFilter(
13
9
  (_, args, __, _info) => {
14
- const { requestIds } = args;
15
- for (const requestId of requestIds) {
16
- if (requestState[requestId] && !requestState[requestId].started) {
17
- requestState[requestId].started = true;
18
- console.log(`Subscription starting async requestProgress, requestId: ${requestId}`);
19
- const { resolver, args } = requestState[requestId];
20
- resolver(args);
21
- }
22
- }
10
+ publishRequestProgressSubscription(args.requestIds);
23
11
  return pubsub.asyncIterator(['REQUEST_PROGRESS'])
24
12
  },
25
13
  (payload, variables) => {
@@ -217,41 +217,3 @@ test('parseResponse', (t) => {
217
217
 
218
218
  t.is(palmChatPlugin.parseResponse(responseData), expectedResult);
219
219
  });
220
-
221
- test('logRequestData', (t) => {
222
- const { palmChatPlugin } = t.context;
223
- const data = {
224
- instances: [
225
- {
226
- messages: [
227
- { author: 'user', content: 'Hello' },
228
- { author: 'assistant', content: 'How can I help you?' },
229
- ],
230
- },
231
- ],
232
- };
233
- const responseData = {
234
- predictions: [
235
- {
236
- candidates: [
237
- {
238
- content: 'Hello, how can I help you today?',
239
- },
240
- ],
241
- },
242
- ],
243
- };
244
- const prompt = { debugInfo: '' };
245
-
246
- const consoleLog = console.log;
247
- let logOutput = '';
248
- console.log = (msg) => (logOutput += msg + '\n');
249
-
250
- palmChatPlugin.logRequestData(data, responseData, prompt);
251
-
252
- console.log = consoleLog;
253
-
254
- t.true(logOutput.includes('Message 1:'));
255
- t.true(logOutput.includes('Message 2:'));
256
- t.true(logOutput.includes('> Hello, how can I help you today?'));
257
- });
@@ -56,33 +56,3 @@ test('getSafetyAttributes', (t) => {
56
56
 
57
57
  t.deepEqual(palmCompletionPlugin.getSafetyAttributes(responseData), expectedResult);
58
58
  });
59
-
60
- test('logRequestData', (t) => {
61
- const { palmCompletionPlugin } = t.context;
62
- const data = {
63
- instances: [
64
- {
65
- prompt: 'Hello, how can I help you?',
66
- },
67
- ],
68
- };
69
- const responseData = {
70
- predictions: [
71
- {
72
- content: 'Hello, how can I help you today?',
73
- },
74
- ],
75
- };
76
- const prompt = { debugInfo: '' };
77
-
78
- const consoleLog = console.log;
79
- let logOutput = '';
80
- console.log = (msg) => (logOutput += msg + '\n');
81
-
82
- palmCompletionPlugin.logRequestData(data, responseData, prompt);
83
-
84
- console.log = consoleLog;
85
-
86
- t.true(logOutput.includes('Hello, how can I help you?'));
87
- t.true(logOutput.includes('> Hello, how can I help you today?'));
88
- });