@aj-archipelago/cortex 1.0.5 → 1.0.7
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/README.md +2 -2
- package/config/default.example.json +4 -2
- package/config.js +14 -8
- package/helper_apps/WhisperX/.dockerignore +27 -0
- package/helper_apps/WhisperX/Dockerfile +31 -0
- package/helper_apps/WhisperX/app-ts.py +76 -0
- package/helper_apps/WhisperX/app.py +115 -0
- package/helper_apps/WhisperX/docker-compose.debug.yml +12 -0
- package/helper_apps/WhisperX/docker-compose.yml +10 -0
- package/helper_apps/WhisperX/requirements.txt +6 -0
- package/index.js +1 -1
- package/lib/redisSubscription.js +1 -1
- package/package.json +8 -7
- package/pathways/basePathway.js +3 -2
- package/pathways/index.js +4 -0
- package/pathways/summary.js +2 -2
- package/pathways/sys_openai_chat.js +19 -0
- package/pathways/sys_openai_completion.js +11 -0
- package/pathways/test_palm_chat.js +1 -1
- package/pathways/transcribe.js +2 -1
- package/{graphql → server}/chunker.js +48 -3
- package/{graphql → server}/graphql.js +70 -62
- package/{graphql → server}/pathwayPrompter.js +14 -17
- package/{graphql → server}/pathwayResolver.js +59 -42
- package/{graphql → server}/plugins/azureTranslatePlugin.js +2 -2
- package/{graphql → server}/plugins/localModelPlugin.js +2 -2
- package/{graphql → server}/plugins/modelPlugin.js +8 -10
- package/{graphql → server}/plugins/openAiChatPlugin.js +13 -8
- package/{graphql → server}/plugins/openAiCompletionPlugin.js +9 -3
- package/{graphql → server}/plugins/openAiWhisperPlugin.js +30 -7
- package/{graphql → server}/plugins/palmChatPlugin.js +4 -6
- package/server/plugins/palmCodeCompletionPlugin.js +46 -0
- package/{graphql → server}/plugins/palmCompletionPlugin.js +13 -15
- package/server/rest.js +321 -0
- package/{graphql → server}/typeDef.js +30 -13
- package/tests/chunkfunction.test.js +112 -26
- package/tests/config.test.js +1 -1
- package/tests/main.test.js +282 -43
- package/tests/mocks.js +43 -2
- package/tests/modelPlugin.test.js +4 -4
- package/tests/openAiChatPlugin.test.js +21 -14
- package/tests/openai_api.test.js +147 -0
- package/tests/palmChatPlugin.test.js +10 -11
- package/tests/palmCompletionPlugin.test.js +3 -4
- package/tests/pathwayResolver.test.js +1 -1
- package/tests/truncateMessages.test.js +4 -5
- package/pathways/completions.js +0 -17
- package/pathways/test_oai_chat.js +0 -18
- package/pathways/test_oai_cmpl.js +0 -13
- package/tests/chunking.test.js +0 -157
- package/tests/translate.test.js +0 -126
- /package/{graphql → server}/parser.js +0 -0
- /package/{graphql → server}/pathwayResponseParser.js +0 -0
- /package/{graphql → server}/prompt.js +0 -0
- /package/{graphql → server}/pubsub.js +0 -0
- /package/{graphql → server}/requestState.js +0 -0
- /package/{graphql → server}/resolver.js +0 -0
- /package/{graphql → server}/subscriptions.js +0 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// palmCodeCompletionPlugin.js
|
|
2
|
+
|
|
3
|
+
import PalmCompletionPlugin from './palmCompletionPlugin.js';
|
|
4
|
+
|
|
5
|
+
// PalmCodeCompletionPlugin class for handling requests and responses to the PaLM API Code Completion API
|
|
6
|
+
class PalmCodeCompletionPlugin extends PalmCompletionPlugin {
|
|
7
|
+
constructor(config, pathway, modelName, model) {
|
|
8
|
+
super(config, pathway, modelName, model);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Set up parameters specific to the PaLM API Code Completion API
|
|
12
|
+
getRequestParameters(text, parameters, prompt, pathwayResolver) {
|
|
13
|
+
const { modelPromptText, tokenLength } = this.getCompiledPrompt(text, parameters, prompt);
|
|
14
|
+
const { stream } = parameters;
|
|
15
|
+
// Define the model's max token length
|
|
16
|
+
const modelTargetTokenLength = this.getModelMaxTokenLength() * this.getPromptTokenRatio();
|
|
17
|
+
|
|
18
|
+
const truncatedPrompt = this.truncatePromptIfNecessary(modelPromptText, tokenLength, this.getModelMaxTokenLength(), modelTargetTokenLength, pathwayResolver);
|
|
19
|
+
|
|
20
|
+
const max_tokens = this.getModelMaxReturnTokens();
|
|
21
|
+
|
|
22
|
+
if (max_tokens < 0) {
|
|
23
|
+
throw new Error(`Prompt is too long to successfully call the model at ${tokenLength} tokens. The model will not be called.`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!truncatedPrompt) {
|
|
27
|
+
throw new Error(`Prompt is empty. The model will not be called.`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const requestParameters = {
|
|
31
|
+
instances: [
|
|
32
|
+
{ prefix: truncatedPrompt }
|
|
33
|
+
],
|
|
34
|
+
parameters: {
|
|
35
|
+
temperature: this.temperature ?? 0.7,
|
|
36
|
+
maxOutputTokens: max_tokens,
|
|
37
|
+
topP: parameters.topP ?? 0.95,
|
|
38
|
+
topK: parameters.topK ?? 40,
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return requestParameters;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default PalmCodeCompletionPlugin;
|
|
@@ -2,23 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
import ModelPlugin from './modelPlugin.js';
|
|
4
4
|
|
|
5
|
-
// Helper function to truncate the prompt if it is too long
|
|
6
|
-
const truncatePromptIfNecessary = (text, textTokenCount, modelMaxTokenCount, targetTextTokenCount, pathwayResolver) => {
|
|
7
|
-
const maxAllowedTokens = textTokenCount + ((modelMaxTokenCount - targetTextTokenCount) * 0.5);
|
|
8
|
-
|
|
9
|
-
if (textTokenCount > maxAllowedTokens) {
|
|
10
|
-
pathwayResolver.logWarning(`Prompt is too long at ${textTokenCount} tokens (this target token length for this pathway is ${targetTextTokenCount} tokens because the response is expected to take up the rest of the model's max tokens (${modelMaxTokenCount}). Prompt will be truncated.`);
|
|
11
|
-
return pathwayResolver.truncate(text, maxAllowedTokens);
|
|
12
|
-
}
|
|
13
|
-
return text;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
5
|
// PalmCompletionPlugin class for handling requests and responses to the PaLM API Text Completion API
|
|
17
6
|
class PalmCompletionPlugin extends ModelPlugin {
|
|
18
|
-
constructor(config, pathway) {
|
|
19
|
-
super(config, pathway);
|
|
7
|
+
constructor(config, pathway, modelName, model) {
|
|
8
|
+
super(config, pathway, modelName, model);
|
|
20
9
|
}
|
|
21
10
|
|
|
11
|
+
truncatePromptIfNecessary (text, textTokenCount, modelMaxTokenCount, targetTextTokenCount, pathwayResolver) {
|
|
12
|
+
const maxAllowedTokens = textTokenCount + ((modelMaxTokenCount - targetTextTokenCount) * 0.5);
|
|
13
|
+
|
|
14
|
+
if (textTokenCount > maxAllowedTokens) {
|
|
15
|
+
pathwayResolver.logWarning(`Prompt is too long at ${textTokenCount} tokens (this target token length for this pathway is ${targetTextTokenCount} tokens because the response is expected to take up the rest of the model's max tokens (${modelMaxTokenCount}). Prompt will be truncated.`);
|
|
16
|
+
return pathwayResolver.truncate(text, maxAllowedTokens);
|
|
17
|
+
}
|
|
18
|
+
return text;
|
|
19
|
+
}
|
|
22
20
|
// Set up parameters specific to the PaLM API Text Completion API
|
|
23
21
|
getRequestParameters(text, parameters, prompt, pathwayResolver) {
|
|
24
22
|
const { modelPromptText, tokenLength } = this.getCompiledPrompt(text, parameters, prompt);
|
|
@@ -26,9 +24,9 @@ class PalmCompletionPlugin extends ModelPlugin {
|
|
|
26
24
|
// Define the model's max token length
|
|
27
25
|
const modelTargetTokenLength = this.getModelMaxTokenLength() * this.getPromptTokenRatio();
|
|
28
26
|
|
|
29
|
-
const truncatedPrompt = truncatePromptIfNecessary(modelPromptText, tokenLength, this.getModelMaxTokenLength(), modelTargetTokenLength, pathwayResolver);
|
|
27
|
+
const truncatedPrompt = this.truncatePromptIfNecessary(modelPromptText, tokenLength, this.getModelMaxTokenLength(), modelTargetTokenLength, pathwayResolver);
|
|
30
28
|
|
|
31
|
-
const max_tokens =
|
|
29
|
+
const max_tokens = this.getModelMaxReturnTokens();
|
|
32
30
|
|
|
33
31
|
if (max_tokens < 0) {
|
|
34
32
|
throw new Error(`Prompt is too long to successfully call the model at ${tokenLength} tokens. The model will not be called.`);
|
package/server/rest.js
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
// rest.js
|
|
2
|
+
// Implement the REST endpoints for the pathways
|
|
3
|
+
|
|
4
|
+
import { json } from 'express';
|
|
5
|
+
import pubsub from './pubsub.js';
|
|
6
|
+
import { requestState } from './requestState.js';
|
|
7
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const processRestRequest = async (server, req, pathway, name, parameterMap = {}) => {
|
|
11
|
+
const fieldVariableDefs = pathway.typeDef(pathway).restDefinition || [];
|
|
12
|
+
|
|
13
|
+
const convertType = (value, type) => {
|
|
14
|
+
if (type === 'Boolean') {
|
|
15
|
+
return Boolean(value);
|
|
16
|
+
} else if (type === 'Int') {
|
|
17
|
+
return parseInt(value, 10);
|
|
18
|
+
} else {
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const variables = fieldVariableDefs.reduce((acc, variableDef) => {
|
|
24
|
+
const requestBodyParamName = Object.keys(parameterMap).includes(variableDef.name)
|
|
25
|
+
? parameterMap[variableDef.name]
|
|
26
|
+
: variableDef.name;
|
|
27
|
+
|
|
28
|
+
if (Object.prototype.hasOwnProperty.call(req.body, requestBodyParamName)) {
|
|
29
|
+
acc[variableDef.name] = convertType(req.body[requestBodyParamName], variableDef.type);
|
|
30
|
+
}
|
|
31
|
+
return acc;
|
|
32
|
+
}, {});
|
|
33
|
+
|
|
34
|
+
const variableParams = fieldVariableDefs.map(({ name, type }) => `$${name}: ${type}`).join(', ');
|
|
35
|
+
const queryArgs = fieldVariableDefs.map(({ name }) => `${name}: $${name}`).join(', ');
|
|
36
|
+
|
|
37
|
+
const query = `
|
|
38
|
+
query ${name}(${variableParams}) {
|
|
39
|
+
${name}(${queryArgs}) {
|
|
40
|
+
contextId
|
|
41
|
+
previousResult
|
|
42
|
+
result
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
const result = await server.executeOperation({ query, variables });
|
|
48
|
+
const resultText = result?.body?.singleResult?.data?.[name]?.result || result?.body?.singleResult?.errors?.[0]?.message || "";
|
|
49
|
+
|
|
50
|
+
return resultText;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const processIncomingStream = (requestId, res, jsonResponse) => {
|
|
54
|
+
|
|
55
|
+
const startStream = (res) => {
|
|
56
|
+
// Set the headers for streaming
|
|
57
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
58
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
59
|
+
res.setHeader('Connection', 'keep-alive');
|
|
60
|
+
res.flushHeaders();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const finishStream = (res, jsonResponse) => {
|
|
64
|
+
|
|
65
|
+
// If we haven't sent the stop message yet, do it now
|
|
66
|
+
if (jsonResponse.choices?.[0]?.finish_reason !== "stop") {
|
|
67
|
+
|
|
68
|
+
let jsonEndStream = JSON.parse(JSON.stringify(jsonResponse));
|
|
69
|
+
|
|
70
|
+
if (jsonEndStream.object === 'text_completion') {
|
|
71
|
+
jsonEndStream.choices[0].index = 0;
|
|
72
|
+
jsonEndStream.choices[0].finish_reason = "stop";
|
|
73
|
+
jsonEndStream.choices[0].text = "";
|
|
74
|
+
} else {
|
|
75
|
+
jsonEndStream.choices[0].finish_reason = "stop";
|
|
76
|
+
jsonEndStream.choices[0].index = 0;
|
|
77
|
+
jsonEndStream.choices[0].delta = {};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
//console.log(`REST SEND: data: ${JSON.stringify(jsonEndStream)}`);
|
|
81
|
+
res.write(`data: ${JSON.stringify(jsonEndStream)}\n\n`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
//console.log(`REST SEND: data: [DONE]\n\n`);
|
|
85
|
+
res.write(`data: [DONE]\n\n`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const sendStreamData = (data) => {
|
|
89
|
+
//console.log(`REST SEND: data: ${JSON.stringify(data)}`);
|
|
90
|
+
res.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const fillJsonResponse = (jsonResponse, inputText, finishReason) => {
|
|
94
|
+
|
|
95
|
+
jsonResponse.choices[0].finish_reason = finishReason;
|
|
96
|
+
if (jsonResponse.object === 'text_completion') {
|
|
97
|
+
jsonResponse.choices[0].text = inputText;
|
|
98
|
+
} else {
|
|
99
|
+
jsonResponse.choices[0].delta.content = inputText;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return jsonResponse;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
startStream(res);
|
|
106
|
+
|
|
107
|
+
let subscription;
|
|
108
|
+
|
|
109
|
+
const unsubscribe = async () => {
|
|
110
|
+
if (subscription) {
|
|
111
|
+
pubsub.unsubscribe(await subscription);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
subscription = pubsub.subscribe('REQUEST_PROGRESS', (data) => {
|
|
116
|
+
if (data.requestProgress.requestId === requestId) {
|
|
117
|
+
//console.log(`REQUEST_PROGRESS received progress: ${data.requestProgress.progress}, data: ${data.requestProgress.data}`);
|
|
118
|
+
|
|
119
|
+
const progress = data.requestProgress.progress;
|
|
120
|
+
const progressData = data.requestProgress.data;
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const messageJson = JSON.parse(progressData);
|
|
124
|
+
if (messageJson.choices) {
|
|
125
|
+
const { text, delta, finish_reason } = messageJson.choices[0];
|
|
126
|
+
|
|
127
|
+
if (messageJson.object === 'text_completion') {
|
|
128
|
+
fillJsonResponse(jsonResponse, text, finish_reason);
|
|
129
|
+
} else {
|
|
130
|
+
fillJsonResponse(jsonResponse, delta.content, finish_reason);
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
fillJsonResponse(jsonResponse, messageJson, null);
|
|
134
|
+
}
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.log(`progressData not JSON: ${progressData}`);
|
|
137
|
+
fillJsonResponse(jsonResponse, progressData, "stop");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (progress === 1 && progressData.trim() === "[DONE]") {
|
|
141
|
+
finishStream(res, jsonResponse);
|
|
142
|
+
unsubscribe();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
sendStreamData(jsonResponse);
|
|
146
|
+
|
|
147
|
+
if (progress === 1) {
|
|
148
|
+
finishStream(res, jsonResponse);
|
|
149
|
+
unsubscribe();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Fire the resolver for the async requestProgress
|
|
155
|
+
console.log(`Rest Endpoint starting async requestProgress, requestId: ${requestId}`);
|
|
156
|
+
const { resolver, args } = requestState[requestId];
|
|
157
|
+
resolver(args);
|
|
158
|
+
|
|
159
|
+
return subscription;
|
|
160
|
+
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function buildRestEndpoints(pathways, app, server, config) {
|
|
164
|
+
|
|
165
|
+
if (config.get('enableRestEndpoints')) {
|
|
166
|
+
const openAIChatModels = {};
|
|
167
|
+
const openAICompletionModels = {};
|
|
168
|
+
|
|
169
|
+
// Create normal REST endpoints or emulate OpenAI API per pathway
|
|
170
|
+
for (const [name, pathway] of Object.entries(pathways)) {
|
|
171
|
+
// Only expose endpoints for enabled pathways that explicitly want to expose a REST endpoint
|
|
172
|
+
if (pathway.disabled) continue;
|
|
173
|
+
|
|
174
|
+
// The pathway can either emulate an OpenAI endpoint or be a normal REST endpoint
|
|
175
|
+
if (pathway.emulateOpenAIChatModel || pathway.emulateOpenAICompletionModel) {
|
|
176
|
+
if (pathway.emulateOpenAIChatModel) {
|
|
177
|
+
openAIChatModels[pathway.emulateOpenAIChatModel] = name;
|
|
178
|
+
}
|
|
179
|
+
if (pathway.emulateOpenAICompletionModel) {
|
|
180
|
+
openAICompletionModels[pathway.emulateOpenAICompletionModel] = name;
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
app.post(`/rest/${name}`, async (req, res) => {
|
|
184
|
+
const resultText = await processRestRequest(server, req, pathway, name);
|
|
185
|
+
res.send(resultText);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Create OpenAI compatible endpoints
|
|
191
|
+
app.post('/v1/completions', async (req, res) => {
|
|
192
|
+
const modelName = req.body.model || 'gpt-3.5-turbo';
|
|
193
|
+
const pathwayName = openAICompletionModels[modelName] || openAICompletionModels['*'];
|
|
194
|
+
|
|
195
|
+
if (!pathwayName) {
|
|
196
|
+
res.status(404).json({
|
|
197
|
+
error: `Model ${modelName} not found.`,
|
|
198
|
+
});
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const pathway = pathways[pathwayName];
|
|
203
|
+
|
|
204
|
+
const parameterMap = {
|
|
205
|
+
text: 'prompt'
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const resultText = await processRestRequest(server, req, pathway, pathwayName, parameterMap);
|
|
209
|
+
|
|
210
|
+
const jsonResponse = {
|
|
211
|
+
id: `cmpl`,
|
|
212
|
+
object: "text_completion",
|
|
213
|
+
created: Date.now(),
|
|
214
|
+
model: req.body.model,
|
|
215
|
+
choices: [
|
|
216
|
+
{
|
|
217
|
+
text: resultText,
|
|
218
|
+
index: 0,
|
|
219
|
+
logprobs: null,
|
|
220
|
+
finish_reason: "stop"
|
|
221
|
+
}
|
|
222
|
+
],
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
if (Boolean(req.body.stream)) {
|
|
226
|
+
jsonResponse.id = `cmpl-${resultText}`;
|
|
227
|
+
jsonResponse.choices[0].finish_reason = null;
|
|
228
|
+
//jsonResponse.object = "text_completion.chunk";
|
|
229
|
+
|
|
230
|
+
const subscription = processIncomingStream(resultText, res, jsonResponse);
|
|
231
|
+
} else {
|
|
232
|
+
const requestId = uuidv4();
|
|
233
|
+
jsonResponse.id = `cmpl-${requestId}`;
|
|
234
|
+
res.json(jsonResponse);
|
|
235
|
+
};
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
app.post('/v1/chat/completions', async (req, res) => {
|
|
239
|
+
const modelName = req.body.model || 'gpt-3.5-turbo';
|
|
240
|
+
const pathwayName = openAIChatModels[modelName] || openAIChatModels['*'];
|
|
241
|
+
|
|
242
|
+
if (!pathwayName) {
|
|
243
|
+
res.status(404).json({
|
|
244
|
+
error: `Model ${modelName} not found.`,
|
|
245
|
+
});
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const pathway = pathways[pathwayName];
|
|
250
|
+
|
|
251
|
+
const resultText = await processRestRequest(server, req, pathway, pathwayName);
|
|
252
|
+
|
|
253
|
+
const jsonResponse = {
|
|
254
|
+
id: `chatcmpl`,
|
|
255
|
+
object: "chat.completion",
|
|
256
|
+
created: Date.now(),
|
|
257
|
+
model: req.body.model,
|
|
258
|
+
choices: [
|
|
259
|
+
{
|
|
260
|
+
message: {
|
|
261
|
+
role: "assistant",
|
|
262
|
+
content: resultText
|
|
263
|
+
},
|
|
264
|
+
index: 0,
|
|
265
|
+
finish_reason: "stop"
|
|
266
|
+
}
|
|
267
|
+
],
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
if (Boolean(req.body.stream)) {
|
|
271
|
+
jsonResponse.id = `chatcmpl-${resultText}`;
|
|
272
|
+
jsonResponse.choices[0] = {
|
|
273
|
+
delta: {
|
|
274
|
+
role: "assistant",
|
|
275
|
+
content: resultText
|
|
276
|
+
},
|
|
277
|
+
finish_reason: null
|
|
278
|
+
}
|
|
279
|
+
jsonResponse.object = "chat.completion.chunk";
|
|
280
|
+
|
|
281
|
+
const subscription = processIncomingStream(resultText, res, jsonResponse);
|
|
282
|
+
} else {
|
|
283
|
+
const requestId = uuidv4();
|
|
284
|
+
jsonResponse.id = `chatcmpl-${requestId}`;
|
|
285
|
+
|
|
286
|
+
res.json(jsonResponse);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
app.get('/v1/models', async (req, res) => {
|
|
292
|
+
const openAIModels = { ...openAIChatModels, ...openAICompletionModels };
|
|
293
|
+
const defaultModelId = 'gpt-3.5-turbo';
|
|
294
|
+
|
|
295
|
+
const models = Object.entries(openAIModels)
|
|
296
|
+
.map(([modelId]) => {
|
|
297
|
+
if (modelId.includes('*')) {
|
|
298
|
+
modelId = defaultModelId;
|
|
299
|
+
}
|
|
300
|
+
return {
|
|
301
|
+
id: modelId,
|
|
302
|
+
object: 'model',
|
|
303
|
+
owned_by: 'openai',
|
|
304
|
+
permission: '',
|
|
305
|
+
};
|
|
306
|
+
})
|
|
307
|
+
.filter((model, index, self) => {
|
|
308
|
+
return index === self.findIndex((m) => m.id === model.id);
|
|
309
|
+
})
|
|
310
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
311
|
+
|
|
312
|
+
res.json({
|
|
313
|
+
data: models,
|
|
314
|
+
object: 'list',
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
export { buildRestEndpoints };
|
|
@@ -1,8 +1,30 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
const getGraphQlType = (value) => {
|
|
2
|
+
switch (typeof value) {
|
|
3
|
+
case 'boolean':
|
|
4
|
+
return {type: 'Boolean', defaultValue: 'false'};
|
|
5
|
+
break;
|
|
6
|
+
case 'string':
|
|
7
|
+
return {type: 'String', defaultValue: `""`};
|
|
8
|
+
break;
|
|
9
|
+
case 'number':
|
|
10
|
+
return {type: 'Int', defaultValue: '0'};
|
|
11
|
+
break;
|
|
12
|
+
case 'object':
|
|
13
|
+
if (Array.isArray(value)) {
|
|
14
|
+
if (value.length > 0 && typeof(value[0]) === 'string') {
|
|
15
|
+
return {type: '[String]', defaultValue: '[]'};
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
return {type: '[Message]', defaultValue: '[]'};
|
|
19
|
+
}
|
|
20
|
+
} else {
|
|
21
|
+
return {type: `[${value.objName}]`, defaultValue: 'null'};
|
|
22
|
+
}
|
|
23
|
+
break;
|
|
24
|
+
default:
|
|
25
|
+
return {type: 'String', defaultValue: `""`};
|
|
26
|
+
}
|
|
27
|
+
};
|
|
6
28
|
|
|
7
29
|
const typeDef = (pathway) => {
|
|
8
30
|
const { name, objName, defaultInputParameters, inputParameters, format } = pathway;
|
|
@@ -31,20 +53,15 @@ const typeDef = (pathway) => {
|
|
|
31
53
|
|
|
32
54
|
const paramsStr = Object.entries(params)
|
|
33
55
|
.map(([key, value]) => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
} else {
|
|
37
|
-
return `${key}: ${GRAPHQL_TYPE_MAP[typeof value]} = ${
|
|
38
|
-
typeof value === 'string' ? `"${value}"` : value
|
|
39
|
-
}`;
|
|
40
|
-
}
|
|
56
|
+
const { type, defaultValue } = getGraphQlType(value);
|
|
57
|
+
return `${key}: ${type} = ${defaultValue}`;
|
|
41
58
|
})
|
|
42
59
|
.join('\n');
|
|
43
60
|
|
|
44
61
|
const restDefinition = Object.entries(params).map(([key, value]) => {
|
|
45
62
|
return {
|
|
46
63
|
name: key,
|
|
47
|
-
type: `${
|
|
64
|
+
type: `${getGraphQlType(value).type}`,
|
|
48
65
|
};
|
|
49
66
|
});
|
|
50
67
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import test from 'ava';
|
|
2
|
-
import { getSemanticChunks } from '../
|
|
2
|
+
import { getSemanticChunks, determineTextFormat } from '../server/chunker.js';
|
|
3
|
+
|
|
3
4
|
import { encode } from 'gpt-3-encoder';
|
|
4
5
|
|
|
5
6
|
const testText = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. In id erat sem. Phasellus ac dapibus purus, in fermentum nunc. Mauris quis rutrum magna. Quisque rutrum, augue vel blandit posuere, augue magna convallis turpis, nec elementum augue mauris sit amet nunc. Aenean sit amet leo est. Nunc ante ex, blandit et felis ut, iaculis lacinia est. Phasellus dictum orci id libero ullamcorper tempor.
|
|
@@ -69,34 +70,119 @@ test('should return identical text that chunker was passed, given tiny chunk siz
|
|
|
69
70
|
t.is(recomposedText, testText); //check recomposition
|
|
70
71
|
});
|
|
71
72
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
73
|
+
const htmlChunkOne = `<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. <a href="https://www.google.com">Google</a></p> Vivamus id pharetra odio. Sed consectetur leo sed tortor dictum venenatis.Donec gravida libero non accumsan suscipit.Donec lectus turpis, ullamcorper eu pulvinar iaculis, ornare ut risus.Phasellus aliquam, turpis quis viverra condimentum, risus est pretium metus, in porta ipsum tortor vitae elit.Pellentesque id finibus erat. In suscipit, sapien non posuere dignissim, augue nisl ultrices tortor, sit amet eleifend nibh elit at risus.`
|
|
74
|
+
const htmlVoidElement = `<br>`
|
|
75
|
+
const htmlChunkTwo = `<p><img src="https://www.google.com/googlelogo_color_272x92dp.png"></p>`
|
|
76
|
+
const htmlSelfClosingElement = `<img src="https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png" />`
|
|
77
|
+
const plainTextChunk = 'Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Fusce at dignissim quam.'
|
|
78
|
+
|
|
79
|
+
test('should throw an error if html cannot be accommodated within the chunk size', async t => {
|
|
80
|
+
const chunkSize = encode(htmlChunkTwo).length;
|
|
81
|
+
const error = t.throws(() => getSemanticChunks(htmlChunkTwo, chunkSize - 1, 'html'));
|
|
82
|
+
t.is(error.message, 'The HTML contains elements that are larger than the chunk size. Please try again with HTML that has smaller elements.');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('should chunk text between html elements if needed', async t => {
|
|
86
|
+
const chunkSize = encode(htmlChunkTwo).length;
|
|
87
|
+
const chunks = getSemanticChunks(htmlChunkTwo + plainTextChunk + htmlChunkTwo, chunkSize, 'html');
|
|
88
|
+
|
|
89
|
+
t.is(chunks.length, 4);
|
|
90
|
+
t.is(chunks[0], htmlChunkTwo);
|
|
91
|
+
t.is(chunks[1], 'Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae');
|
|
92
|
+
t.is(encode(chunks[1]).length, chunkSize);
|
|
93
|
+
t.is(chunks[2], '; Fusce at dignissim quam.');
|
|
94
|
+
t.is(chunks[3], htmlChunkTwo);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('should chunk html element correctly when chunk size is exactly the same as the element length', async t => {
|
|
98
|
+
const chunkSize = encode(htmlChunkTwo).length;
|
|
99
|
+
const chunks = getSemanticChunks(htmlChunkTwo, chunkSize, 'html');
|
|
100
|
+
|
|
101
|
+
t.is(chunks.length, 1);
|
|
102
|
+
t.is(chunks[0], htmlChunkTwo);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('should chunk html element correctly when chunk size is greater than the element length', async t => {
|
|
106
|
+
const chunkSize = encode(htmlChunkTwo).length;
|
|
107
|
+
const chunks = getSemanticChunks(htmlChunkTwo, chunkSize + 1, 'html');
|
|
108
|
+
|
|
109
|
+
t.is(chunks.length, 1);
|
|
110
|
+
t.is(chunks[0], htmlChunkTwo);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('should not break up second html element correctly when chunk size is greater than the first element length', async t => {
|
|
114
|
+
const chunkSize = encode(htmlChunkTwo).length;
|
|
115
|
+
const chunks = getSemanticChunks(htmlChunkTwo + htmlChunkTwo, chunkSize + 10, 'html');
|
|
116
|
+
|
|
117
|
+
t.is(chunks.length, 2);
|
|
118
|
+
t.is(chunks[0], htmlChunkTwo);
|
|
119
|
+
t.is(chunks[1], htmlChunkTwo);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('should treat text chunks as also unbreakable chunks', async t => {
|
|
123
|
+
const chunkSize = encode(htmlChunkTwo).length;
|
|
124
|
+
const chunks = getSemanticChunks(htmlChunkTwo + plainTextChunk + htmlChunkTwo, chunkSize + 20, 'html');
|
|
125
|
+
|
|
126
|
+
t.is(chunks.length, 3);
|
|
127
|
+
t.is(chunks[0], htmlChunkTwo);
|
|
128
|
+
t.is(chunks[1], plainTextChunk);
|
|
129
|
+
t.is(chunks[2], htmlChunkTwo);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
test('should determine format correctly for text only', async t => {
|
|
134
|
+
const format = determineTextFormat(plainTextChunk);
|
|
135
|
+
t.is(format, 'text');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('should determine format correctly for simple html element', async t => {
|
|
139
|
+
const format = determineTextFormat(htmlChunkTwo);
|
|
140
|
+
t.is(format, 'html');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('should determine format correctly for simple html element embedded in text', async t => {
|
|
144
|
+
const format = determineTextFormat(plainTextChunk + htmlChunkTwo + plainTextChunk);
|
|
145
|
+
t.is(format, 'html');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('should determine format correctly for self-closing html element', async t => {
|
|
149
|
+
const format = determineTextFormat(htmlSelfClosingElement);
|
|
150
|
+
t.is(format, 'html');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('should determine format correctly for self-closing html element embedded in text', async t => {
|
|
154
|
+
const format = determineTextFormat(plainTextChunk + htmlSelfClosingElement + plainTextChunk);
|
|
155
|
+
t.is(format, 'html');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('should determine format correctly for void element', async t => {
|
|
159
|
+
const format = determineTextFormat(htmlVoidElement);
|
|
160
|
+
t.is(format, 'html');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('should determine format correctly for void element embedded in text', async t => {
|
|
164
|
+
const format = determineTextFormat(plainTextChunk + htmlVoidElement + plainTextChunk);
|
|
165
|
+
t.is(format, 'html');
|
|
80
166
|
});
|
|
81
167
|
|
|
82
|
-
|
|
168
|
+
test('should return identical text that chunker was passed, given huge chunk size (32000)', t => {
|
|
83
169
|
const maxChunkToken = 32000;
|
|
84
170
|
const chunks = getSemanticChunks(testText, maxChunkToken);
|
|
85
|
-
|
|
86
|
-
|
|
171
|
+
t.assert(chunks.length === 1); //check chunking
|
|
172
|
+
t.assert(chunks.every(chunk => encode(chunk).length <= maxChunkToken)); //check chunk size
|
|
87
173
|
const recomposedText = chunks.reduce((acc, chunk) => acc + chunk, '');
|
|
88
|
-
|
|
174
|
+
t.assert(recomposedText === testText); //check recomposition
|
|
89
175
|
});
|
|
90
176
|
|
|
91
177
|
const testTextNoSpaces = `Loremipsumdolorsitamet,consecteturadipiscingelit.Inideratsem.Phasellusacdapibuspurus,infermentumnunc.Maurisquisrutrummagna.Quisquerutrum,auguevelblanditposuere,auguemagnacon vallisturpis,necelementumauguemaurissitametnunc.Aeneansitametleoest.Nuncanteex,blanditetfelisut,iaculislaciniaest.Phasellusdictumorciidliberoullamcorpertempor.Vivamusidpharetraodioq.Sedconsecteturleosedtortordictumvenenatis.Donecgravidaliberononaccumsansuscipit.Doneclectusturpis,ullamcorpereupulvinariaculis,ornareutrisus.Phasellusaliquam,turpisquisviverracondimentum,risusestpretiummetus,inportaips umtortorvita elit.Pellentesqueidfinibuserat.Insuscipit,sapiennonposueredignissim,auguenisl ultricestortor,sitameteleifendnibhelitatrisus.`;
|
|
92
178
|
|
|
93
|
-
|
|
179
|
+
test('should return identical text that chunker was passed, given no spaces and small chunks(5)', t => {
|
|
94
180
|
const maxChunkToken = 5;
|
|
95
181
|
const chunks = getSemanticChunks(testTextNoSpaces, maxChunkToken);
|
|
96
|
-
|
|
97
|
-
|
|
182
|
+
t.assert(chunks.length > 0); //check chunking
|
|
183
|
+
t.assert(chunks.every(chunk => encode(chunk).length <= maxChunkToken)); //check chunk size
|
|
98
184
|
const recomposedText = chunks.reduce((acc, chunk) => acc + chunk, '');
|
|
99
|
-
|
|
185
|
+
t.assert(recomposedText === testTextNoSpaces); //check recomposition
|
|
100
186
|
});
|
|
101
187
|
|
|
102
188
|
const testTextShortWeirdSpaces=`Lorem ipsum dolor sit amet, consectetur adipiscing elit. In id erat sem. Phasellus ac dapibus purus, in fermentum nunc.............................. Mauris quis rutrum magna. Quisque rutrum, augue vel blandit posuere, augue magna convallis turpis, nec elementum augue mauris sit amet nunc. Aenean sit a;lksjdf 098098- -23 eln ;lkn l;kn09 oij[0u ,,,,,,,,,,,,,,,,,,,,, amet leo est. Nunc ante ex, blandit et felis ut, iaculis lacinia est. Phasellus dictum orci id libero ullamcorper tempor.
|
|
@@ -106,20 +192,20 @@ const testTextShortWeirdSpaces=`Lorem ipsum dolor sit amet, consectetur adipisci
|
|
|
106
192
|
|
|
107
193
|
Vivamus id pharetra odio. Sed consectetur leo sed tortor dictum venenatis.Donec gravida libero non accumsan suscipit.Donec lectus turpis, ullamcorper eu pulvinar iaculis, ornare ut risus.Phasellus aliquam, turpis quis viverra condimentum, risus est pretium metus, in porta ipsum tortor vitae elit.Pellentesque id finibus erat. In suscipit, sapien non posuere dignissim, augue nisl ultrices tortor, sit amet eleifend nibh elit at risus.`;
|
|
108
194
|
|
|
109
|
-
|
|
195
|
+
test('should return identical text that chunker was passed, given weird spaces and tiny chunks(1)', t => {
|
|
110
196
|
const maxChunkToken = 1;
|
|
111
197
|
const chunks = getSemanticChunks(testTextShortWeirdSpaces, maxChunkToken);
|
|
112
|
-
|
|
113
|
-
|
|
198
|
+
t.assert(chunks.length > 0); //check chunking
|
|
199
|
+
t.assert(chunks.every(chunk => encode(chunk).length <= maxChunkToken)); //check chunk size
|
|
114
200
|
const recomposedText = chunks.reduce((acc, chunk) => acc + chunk, '');
|
|
115
|
-
|
|
201
|
+
t.assert(recomposedText === testTextShortWeirdSpaces); //check recomposition
|
|
116
202
|
});
|
|
117
203
|
|
|
118
|
-
|
|
204
|
+
test('should return identical text that chunker was passed, given weird spaces and small chunks(10)', t => {
|
|
119
205
|
const maxChunkToken = 1;
|
|
120
206
|
const chunks = getSemanticChunks(testTextShortWeirdSpaces, maxChunkToken);
|
|
121
|
-
|
|
122
|
-
|
|
207
|
+
t.assert(chunks.length > 0); //check chunking
|
|
208
|
+
t.assert(chunks.every(chunk => encode(chunk).length <= maxChunkToken)); //check chunk size
|
|
123
209
|
const recomposedText = chunks.reduce((acc, chunk) => acc + chunk, '');
|
|
124
|
-
|
|
125
|
-
})
|
|
210
|
+
t.assert(recomposedText === testTextShortWeirdSpaces); //check recomposition
|
|
211
|
+
});
|
package/tests/config.test.js
CHANGED
|
@@ -45,7 +45,7 @@ test('config enableRestEndpoints', (t) => {
|
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
test('config openaiDefaultModel', (t) => {
|
|
48
|
-
const expectedDefault = '
|
|
48
|
+
const expectedDefault = 'gpt-3.5-turbo';
|
|
49
49
|
t.is(config.get('openaiDefaultModel'), expectedDefault);
|
|
50
50
|
});
|
|
51
51
|
|