@aj-archipelago/cortex 1.0.4 → 1.0.5
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 +1 -1
- package/config/default.example.json +18 -0
- package/config.js +15 -1
- package/graphql/pathwayPrompter.js +8 -0
- package/graphql/pathwayResolver.js +9 -1
- package/graphql/plugins/azureTranslatePlugin.js +22 -0
- package/graphql/plugins/modelPlugin.js +15 -42
- package/graphql/plugins/openAiChatPlugin.js +85 -2
- package/graphql/plugins/openAiCompletionPlugin.js +32 -2
- package/graphql/plugins/openAiWhisperPlugin.js +34 -5
- package/graphql/plugins/palmChatPlugin.js +229 -0
- package/graphql/plugins/palmCompletionPlugin.js +134 -0
- package/graphql/prompt.js +11 -4
- package/helper_apps/MediaFileChunker/Dockerfile +20 -0
- package/helper_apps/MediaFileChunker/package-lock.json +18 -18
- package/helper_apps/MediaFileChunker/package.json +1 -1
- package/lib/gcpAuthTokenHelper.js +37 -0
- package/package.json +3 -1
- package/pathways/completions.js +17 -0
- package/pathways/index.js +4 -2
- package/pathways/{lc_test.mjs → test_langchain.mjs} +1 -1
- package/pathways/test_oai_chat.js +18 -0
- package/pathways/test_oai_cmpl.js +13 -0
- package/pathways/test_palm_chat.js +31 -0
- package/pathways/transcribe.js +1 -0
- package/pathways/translate.js +2 -1
- package/tests/chunking.test.js +8 -6
- package/tests/modelPlugin.test.js +2 -14
- package/tests/openAiChatPlugin.test.js +125 -0
- package/tests/palmChatPlugin.test.js +256 -0
- package/tests/palmCompletionPlugin.test.js +87 -0
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ Just about anything! It's kind of an LLM swiss army knife. Here are some ideas:
|
|
|
17
17
|
## Features
|
|
18
18
|
|
|
19
19
|
* Simple architecture to build custom functional endpoints (called `pathways`), that implement common NL AI tasks. Default pathways include chat, summarization, translation, paraphrasing, completion, spelling and grammar correction, entity extraction, sentiment analysis, and bias analysis.
|
|
20
|
-
* Allows for building multi-model, multi-tool, multi-vendor, and model-agnostic pathways (choose the right model or combination of models and tools for the job, implement redundancy) with built-in support for OpenAI GPT-3, GPT-3.5 (chatGPT), and GPT-4 models - both from OpenAI directly and through Azure OpenAI, OpenAI Whisper, Azure Translator, LangChain.js and more.
|
|
20
|
+
* Allows for building multi-model, multi-tool, multi-vendor, and model-agnostic pathways (choose the right model or combination of models and tools for the job, implement redundancy) with built-in support for OpenAI GPT-3, GPT-3.5 (chatGPT), and GPT-4 models - both from OpenAI directly and through Azure OpenAI, PaLM Text and PaLM Chat from Google, OpenAI Whisper, Azure Translator, LangChain.js and more.
|
|
21
21
|
* Easy, templatized prompt definition with flexible support for most prompt engineering techniques and strategies ranging from simple single prompts to complex custom prompt chains with context continuity.
|
|
22
22
|
* Built in support for long-running, asynchronous operations with progress updates or streaming responses
|
|
23
23
|
* Integrated context persistence: have your pathways "remember" whatever you want and use it on the next request to the model
|
|
@@ -51,6 +51,24 @@
|
|
|
51
51
|
"requestsPerSecond": 10,
|
|
52
52
|
"maxTokenLength": 8192
|
|
53
53
|
},
|
|
54
|
+
"palm-text": {
|
|
55
|
+
"type": "PALM-COMPLETION",
|
|
56
|
+
"url": "https://us-central1-aiplatform.googleapis.com/v1/projects/project-id/locations/us-central1/publishers/google/models/text-bison@001:predict",
|
|
57
|
+
"headers": {
|
|
58
|
+
"Content-Type": "application/json"
|
|
59
|
+
},
|
|
60
|
+
"requestsPerSecond": 10,
|
|
61
|
+
"maxTokenLength": 2048
|
|
62
|
+
},
|
|
63
|
+
"palm-chat": {
|
|
64
|
+
"type": "PALM-CHAT",
|
|
65
|
+
"url": "https://us-central1-aiplatform.googleapis.com/v1/projects/project-id/locations/us-central1/publishers/google/models/chat-bison@001:predict",
|
|
66
|
+
"headers": {
|
|
67
|
+
"Content-Type": "application/json"
|
|
68
|
+
},
|
|
69
|
+
"requestsPerSecond": 10,
|
|
70
|
+
"maxTokenLength": 2048
|
|
71
|
+
},
|
|
54
72
|
"local-llama13B": {
|
|
55
73
|
"type": "LOCAL-CPP-MODEL",
|
|
56
74
|
"executablePath": "../llm/llama.cpp/main",
|
package/config.js
CHANGED
|
@@ -3,6 +3,7 @@ import convict from 'convict';
|
|
|
3
3
|
import HandleBars from './lib/handleBars.js';
|
|
4
4
|
import fs from 'fs';
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
6
|
+
import GcpAuthTokenHelper from './lib/gcpAuthTokenHelper.js';
|
|
6
7
|
|
|
7
8
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
9
|
|
|
@@ -57,7 +58,8 @@ var config = convict({
|
|
|
57
58
|
cortexApiKey: {
|
|
58
59
|
format: String,
|
|
59
60
|
default: null,
|
|
60
|
-
env: 'CORTEX_API_KEY'
|
|
61
|
+
env: 'CORTEX_API_KEY',
|
|
62
|
+
sensitive: true
|
|
61
63
|
},
|
|
62
64
|
defaultModelName: {
|
|
63
65
|
format: String,
|
|
@@ -77,6 +79,7 @@ var config = convict({
|
|
|
77
79
|
"params": {
|
|
78
80
|
"model": "{{openaiDefaultModel}}"
|
|
79
81
|
},
|
|
82
|
+
"requestsPerSecond": 2,
|
|
80
83
|
},
|
|
81
84
|
"oai-whisper": {
|
|
82
85
|
"type": "OPENAI_WHISPER",
|
|
@@ -117,6 +120,12 @@ var config = convict({
|
|
|
117
120
|
default: 'null',
|
|
118
121
|
env: 'WHISPER_MEDIA_API_URL'
|
|
119
122
|
},
|
|
123
|
+
gcpServiceAccountKey: {
|
|
124
|
+
format: String,
|
|
125
|
+
default: null,
|
|
126
|
+
env: 'GCP_SERVICE_ACCOUNT_KEY',
|
|
127
|
+
sensitive: true
|
|
128
|
+
},
|
|
120
129
|
});
|
|
121
130
|
|
|
122
131
|
// Read in environment variables and set up service configuration
|
|
@@ -135,6 +144,11 @@ if (configFile && fs.existsSync(configFile)) {
|
|
|
135
144
|
}
|
|
136
145
|
}
|
|
137
146
|
|
|
147
|
+
if (config.get('gcpServiceAccountKey')) {
|
|
148
|
+
const gcpAuthTokenHelper = new GcpAuthTokenHelper(config.getProperties());
|
|
149
|
+
config.set('gcpAuthTokenHelper', gcpAuthTokenHelper);
|
|
150
|
+
}
|
|
151
|
+
|
|
138
152
|
// Build and load pathways to config
|
|
139
153
|
const buildPathways = async (config) => {
|
|
140
154
|
const { pathwaysPath, corePathwaysPath, basePathwayPath } = config.getProperties();
|
|
@@ -4,6 +4,8 @@ import OpenAICompletionPlugin from './plugins/openAiCompletionPlugin.js';
|
|
|
4
4
|
import AzureTranslatePlugin from './plugins/azureTranslatePlugin.js';
|
|
5
5
|
import OpenAIWhisperPlugin from './plugins/openAiWhisperPlugin.js';
|
|
6
6
|
import LocalModelPlugin from './plugins/localModelPlugin.js';
|
|
7
|
+
import PalmChatPlugin from './plugins/palmChatPlugin.js';
|
|
8
|
+
import PalmCompletionPlugin from './plugins/palmCompletionPlugin.js';
|
|
7
9
|
|
|
8
10
|
class PathwayPrompter {
|
|
9
11
|
constructor({ config, pathway }) {
|
|
@@ -33,6 +35,12 @@ class PathwayPrompter {
|
|
|
33
35
|
case 'LOCAL-CPP-MODEL':
|
|
34
36
|
plugin = new LocalModelPlugin(config, pathway);
|
|
35
37
|
break;
|
|
38
|
+
case 'PALM-CHAT':
|
|
39
|
+
plugin = new PalmChatPlugin(config, pathway);
|
|
40
|
+
break;
|
|
41
|
+
case 'PALM-COMPLETION':
|
|
42
|
+
plugin = new PalmCompletionPlugin(config, pathway);
|
|
43
|
+
break;
|
|
36
44
|
default:
|
|
37
45
|
throw new Error(`Unsupported model type: ${model.type}`);
|
|
38
46
|
}
|
|
@@ -289,7 +289,15 @@ class PathwayResolver {
|
|
|
289
289
|
if (requestState[this.requestId].canceled) {
|
|
290
290
|
return;
|
|
291
291
|
}
|
|
292
|
-
|
|
292
|
+
let result = '';
|
|
293
|
+
|
|
294
|
+
// If this text is empty, skip applying the prompt as it will likely be a nonsensical result
|
|
295
|
+
if (!/^\s*$/.test(text)) {
|
|
296
|
+
result = await this.pathwayPrompter.execute(text, { ...parameters, ...this.savedContext }, prompt, this);
|
|
297
|
+
} else {
|
|
298
|
+
result = text;
|
|
299
|
+
}
|
|
300
|
+
|
|
293
301
|
requestState[this.requestId].completedCount++;
|
|
294
302
|
|
|
295
303
|
const { completedCount, totalCount } = requestState[this.requestId];
|
|
@@ -35,6 +35,28 @@ class AzureTranslatePlugin extends ModelPlugin {
|
|
|
35
35
|
|
|
36
36
|
return this.executeRequest(url, data, params, headers, prompt);
|
|
37
37
|
}
|
|
38
|
+
|
|
39
|
+
// Parse the response from the Azure Translate API
|
|
40
|
+
parseResponse(data) {
|
|
41
|
+
if (Array.isArray(data) && data.length > 0 && data[0].translations) {
|
|
42
|
+
return data[0].translations[0].text.trim();
|
|
43
|
+
} else {
|
|
44
|
+
return data;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Override the logging function to display the request and response
|
|
49
|
+
logRequestData(data, responseData, prompt) {
|
|
50
|
+
const separator = `\n=== ${this.pathwayName}.${this.requestCount++} ===\n`;
|
|
51
|
+
console.log(separator);
|
|
52
|
+
|
|
53
|
+
const modelInput = data[0].Text;
|
|
54
|
+
|
|
55
|
+
console.log(`\x1b[36m${modelInput}\x1b[0m`);
|
|
56
|
+
console.log(`\x1b[34m> ${this.parseResponse(responseData)}\x1b[0m`);
|
|
57
|
+
|
|
58
|
+
prompt && prompt.debugInfo && (prompt.debugInfo += `${separator}${JSON.stringify(data)}`);
|
|
59
|
+
}
|
|
38
60
|
}
|
|
39
61
|
|
|
40
62
|
export default AzureTranslatePlugin;
|
|
@@ -62,7 +62,7 @@ class ModelPlugin {
|
|
|
62
62
|
const message = tokenLengths[index].message;
|
|
63
63
|
|
|
64
64
|
// Skip system messages
|
|
65
|
-
if (message
|
|
65
|
+
if (message?.role === 'system') {
|
|
66
66
|
index++;
|
|
67
67
|
continue;
|
|
68
68
|
}
|
|
@@ -113,7 +113,7 @@ class ModelPlugin {
|
|
|
113
113
|
let output = "";
|
|
114
114
|
if (messages && messages.length) {
|
|
115
115
|
for (let message of messages) {
|
|
116
|
-
output += (message.role && (message.content || message.content === '')) ? `<|im_start|>${message.role}\n${message.content}\n<|im_end|>\n` : `${message}\n`;
|
|
116
|
+
output += ((message.author || message.role) && (message.content || message.content === '')) ? `<|im_start|>${(message.author || message.role)}\n${message.content}\n<|im_end|>\n` : `${message}\n`;
|
|
117
117
|
}
|
|
118
118
|
// you always want the assistant to respond next so add a
|
|
119
119
|
// directive for that
|
|
@@ -124,6 +124,7 @@ class ModelPlugin {
|
|
|
124
124
|
return output;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
// compile the Prompt
|
|
127
128
|
getCompiledPrompt(text, parameters, prompt) {
|
|
128
129
|
const combinedParameters = { ...this.promptParameters, ...parameters };
|
|
129
130
|
const modelPrompt = this.getModelPrompt(prompt, parameters);
|
|
@@ -132,9 +133,9 @@ class ModelPlugin {
|
|
|
132
133
|
const modelPromptMessagesML = this.messagesToChatML(modelPromptMessages);
|
|
133
134
|
|
|
134
135
|
if (modelPromptMessagesML) {
|
|
135
|
-
return { modelPromptMessages, tokenLength: encode(modelPromptMessagesML).length };
|
|
136
|
+
return { modelPromptMessages, tokenLength: encode(modelPromptMessagesML).length, modelPrompt };
|
|
136
137
|
} else {
|
|
137
|
-
return { modelPromptText, tokenLength: encode(modelPromptText).length };
|
|
138
|
+
return { modelPromptText, tokenLength: encode(modelPromptText).length, modelPrompt };
|
|
138
139
|
}
|
|
139
140
|
}
|
|
140
141
|
|
|
@@ -147,12 +148,11 @@ class ModelPlugin {
|
|
|
147
148
|
return this.promptParameters.inputParameters?.tokenRatio ?? this.promptParameters.tokenRatio ?? DEFAULT_PROMPT_TOKEN_RATIO;
|
|
148
149
|
}
|
|
149
150
|
|
|
150
|
-
|
|
151
151
|
getModelPrompt(prompt, parameters) {
|
|
152
152
|
if (typeof(prompt) === 'function') {
|
|
153
|
-
|
|
153
|
+
return prompt(parameters);
|
|
154
154
|
} else {
|
|
155
|
-
|
|
155
|
+
return prompt;
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
|
|
@@ -160,20 +160,20 @@ class ModelPlugin {
|
|
|
160
160
|
if (!modelPrompt.messages) {
|
|
161
161
|
return null;
|
|
162
162
|
}
|
|
163
|
-
|
|
163
|
+
|
|
164
164
|
// First run handlebars compile on the pathway messages
|
|
165
165
|
const compiledMessages = modelPrompt.messages.map((message) => {
|
|
166
166
|
if (message.content) {
|
|
167
167
|
const compileText = HandleBars.compile(message.content);
|
|
168
168
|
return {
|
|
169
|
-
|
|
169
|
+
...message,
|
|
170
170
|
content: compileText({ ...combinedParameters, text }),
|
|
171
171
|
};
|
|
172
172
|
} else {
|
|
173
173
|
return message;
|
|
174
174
|
}
|
|
175
175
|
});
|
|
176
|
-
|
|
176
|
+
|
|
177
177
|
// Next add in any parameters that are referenced by name in the array
|
|
178
178
|
const expandedMessages = compiledMessages.flatMap((message) => {
|
|
179
179
|
if (typeof message === 'string') {
|
|
@@ -188,7 +188,7 @@ class ModelPlugin {
|
|
|
188
188
|
return [message];
|
|
189
189
|
}
|
|
190
190
|
});
|
|
191
|
-
|
|
191
|
+
|
|
192
192
|
return expandedMessages;
|
|
193
193
|
}
|
|
194
194
|
|
|
@@ -197,44 +197,17 @@ class ModelPlugin {
|
|
|
197
197
|
return generateUrl({ ...this.model, ...this.environmentVariables, ...this.config });
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
//
|
|
201
|
-
parseResponse(data) {
|
|
202
|
-
const { choices } = data;
|
|
203
|
-
if (!choices || !choices.length) {
|
|
204
|
-
if (Array.isArray(data) && data.length > 0 && data[0].translations) {
|
|
205
|
-
return data[0].translations[0].text.trim();
|
|
206
|
-
} else {
|
|
207
|
-
return data;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// if we got a choices array back with more than one choice, return the whole array
|
|
212
|
-
if (choices.length > 1) {
|
|
213
|
-
return choices;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// otherwise, return the first choice
|
|
217
|
-
const textResult = choices[0].text && choices[0].text.trim();
|
|
218
|
-
const messageResult = choices[0].message && choices[0].message.content && choices[0].message.content.trim();
|
|
219
|
-
|
|
220
|
-
return messageResult ?? textResult ?? null;
|
|
221
|
-
}
|
|
200
|
+
// Default response parsing
|
|
201
|
+
parseResponse(data) { return data; };
|
|
222
202
|
|
|
203
|
+
// Default simple logging
|
|
223
204
|
logRequestData(data, responseData, prompt) {
|
|
224
205
|
const separator = `\n=== ${this.pathwayName}.${this.requestCount++} ===\n`;
|
|
225
206
|
console.log(separator);
|
|
226
207
|
|
|
227
208
|
const modelInput = data.prompt || (data.messages && data.messages[0].content) || (data.length > 0 && data[0].Text) || null;
|
|
228
209
|
|
|
229
|
-
if (
|
|
230
|
-
data.messages.forEach((message, index) => {
|
|
231
|
-
const words = message.content.split(" ");
|
|
232
|
-
const tokenCount = encode(message.content).length;
|
|
233
|
-
const preview = words.length < 41 ? message.content : words.slice(0, 20).join(" ") + " ... " + words.slice(-20).join(" ");
|
|
234
|
-
|
|
235
|
-
console.log(`\x1b[36mMessage ${index + 1}: Role: ${message.role}, Tokens: ${tokenCount}, Content: "${preview}"\x1b[0m`);
|
|
236
|
-
});
|
|
237
|
-
} else {
|
|
210
|
+
if (modelInput) {
|
|
238
211
|
console.log(`\x1b[36m${modelInput}\x1b[0m`);
|
|
239
212
|
}
|
|
240
213
|
|
|
@@ -1,20 +1,64 @@
|
|
|
1
1
|
// OpenAIChatPlugin.js
|
|
2
2
|
import ModelPlugin from './modelPlugin.js';
|
|
3
|
+
import { encode } from 'gpt-3-encoder';
|
|
3
4
|
|
|
4
5
|
class OpenAIChatPlugin extends ModelPlugin {
|
|
5
6
|
constructor(config, pathway) {
|
|
6
7
|
super(config, pathway);
|
|
7
8
|
}
|
|
8
9
|
|
|
10
|
+
// convert to OpenAI messages array format if necessary
|
|
11
|
+
convertPalmToOpenAIMessages(context, examples, messages) {
|
|
12
|
+
let openAIMessages = [];
|
|
13
|
+
|
|
14
|
+
// Add context as a system message
|
|
15
|
+
if (context) {
|
|
16
|
+
openAIMessages.push({
|
|
17
|
+
role: 'system',
|
|
18
|
+
content: context,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Add examples to the messages array
|
|
23
|
+
examples.forEach(example => {
|
|
24
|
+
openAIMessages.push({
|
|
25
|
+
role: example.input.author || 'user',
|
|
26
|
+
content: example.input.content,
|
|
27
|
+
});
|
|
28
|
+
openAIMessages.push({
|
|
29
|
+
role: example.output.author || 'assistant',
|
|
30
|
+
content: example.output.content,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Add remaining messages to the messages array
|
|
35
|
+
messages.forEach(message => {
|
|
36
|
+
openAIMessages.push({
|
|
37
|
+
role: message.author,
|
|
38
|
+
content: message.content,
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return openAIMessages;
|
|
43
|
+
}
|
|
44
|
+
|
|
9
45
|
// Set up parameters specific to the OpenAI Chat API
|
|
10
46
|
getRequestParameters(text, parameters, prompt) {
|
|
11
|
-
const { modelPromptText, modelPromptMessages, tokenLength } = this.getCompiledPrompt(text, parameters, prompt);
|
|
47
|
+
const { modelPromptText, modelPromptMessages, tokenLength, modelPrompt } = this.getCompiledPrompt(text, parameters, prompt);
|
|
12
48
|
const { stream } = parameters;
|
|
13
49
|
|
|
14
50
|
// Define the model's max token length
|
|
15
51
|
const modelTargetTokenLength = this.getModelMaxTokenLength() * this.getPromptTokenRatio();
|
|
16
52
|
|
|
17
53
|
let requestMessages = modelPromptMessages || [{ "role": "user", "content": modelPromptText }];
|
|
54
|
+
|
|
55
|
+
// Check if the messages are in Palm format and convert them to OpenAI format if necessary
|
|
56
|
+
const isPalmFormat = requestMessages.some(message => 'author' in message);
|
|
57
|
+
if (isPalmFormat) {
|
|
58
|
+
const context = modelPrompt.context || '';
|
|
59
|
+
const examples = modelPrompt.examples || [];
|
|
60
|
+
requestMessages = this.convertPalmToOpenAIMessages(context, examples, expandedMessages);
|
|
61
|
+
}
|
|
18
62
|
|
|
19
63
|
// Check if the token length exceeds the model's max token length
|
|
20
64
|
if (tokenLength > modelTargetTokenLength) {
|
|
@@ -25,7 +69,7 @@ class OpenAIChatPlugin extends ModelPlugin {
|
|
|
25
69
|
const requestParameters = {
|
|
26
70
|
messages: requestMessages,
|
|
27
71
|
temperature: this.temperature ?? 0.7,
|
|
28
|
-
stream
|
|
72
|
+
...(stream !== undefined ? { stream } : {}),
|
|
29
73
|
};
|
|
30
74
|
|
|
31
75
|
return requestParameters;
|
|
@@ -41,6 +85,45 @@ class OpenAIChatPlugin extends ModelPlugin {
|
|
|
41
85
|
const headers = this.model.headers || {};
|
|
42
86
|
return this.executeRequest(url, data, params, headers, prompt);
|
|
43
87
|
}
|
|
88
|
+
|
|
89
|
+
// Parse the response from the OpenAI Chat API
|
|
90
|
+
parseResponse(data) {
|
|
91
|
+
const { choices } = data;
|
|
92
|
+
if (!choices || !choices.length) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// if we got a choices array back with more than one choice, return the whole array
|
|
97
|
+
if (choices.length > 1) {
|
|
98
|
+
return choices;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// otherwise, return the first choice
|
|
102
|
+
const messageResult = choices[0].message && choices[0].message.content && choices[0].message.content.trim();
|
|
103
|
+
return messageResult ?? null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Override the logging function to display the messages and responses
|
|
107
|
+
logRequestData(data, responseData, prompt) {
|
|
108
|
+
const separator = `\n=== ${this.pathwayName}.${this.requestCount++} ===\n`;
|
|
109
|
+
console.log(separator);
|
|
110
|
+
|
|
111
|
+
if (data && data.messages && data.messages.length > 1) {
|
|
112
|
+
data.messages.forEach((message, index) => {
|
|
113
|
+
const words = message.content.split(" ");
|
|
114
|
+
const tokenCount = encode(message.content).length;
|
|
115
|
+
const preview = words.length < 41 ? message.content : words.slice(0, 20).join(" ") + " ... " + words.slice(-20).join(" ");
|
|
116
|
+
|
|
117
|
+
console.log(`\x1b[36mMessage ${index + 1}: Role: ${message.role}, Tokens: ${tokenCount}, Content: "${preview}"\x1b[0m`);
|
|
118
|
+
});
|
|
119
|
+
} else {
|
|
120
|
+
console.log(`\x1b[36m${data.messages[0].content}\x1b[0m`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
console.log(`\x1b[34m> ${this.parseResponse(responseData)}\x1b[0m`);
|
|
124
|
+
|
|
125
|
+
prompt && prompt.debugInfo && (prompt.debugInfo += `${separator}${JSON.stringify(data)}`);
|
|
126
|
+
}
|
|
44
127
|
}
|
|
45
128
|
|
|
46
129
|
export default OpenAIChatPlugin;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// OpenAICompletionPlugin.js
|
|
2
2
|
|
|
3
3
|
import ModelPlugin from './modelPlugin.js';
|
|
4
|
-
|
|
5
4
|
import { encode } from 'gpt-3-encoder';
|
|
6
5
|
|
|
7
6
|
// Helper function to truncate the prompt if it is too long
|
|
@@ -52,7 +51,7 @@ class OpenAICompletionPlugin extends ModelPlugin {
|
|
|
52
51
|
frequency_penalty: 0,
|
|
53
52
|
presence_penalty: 0,
|
|
54
53
|
stop: ["<|im_end|>"],
|
|
55
|
-
stream
|
|
54
|
+
...(stream !== undefined ? { stream } : {}),
|
|
56
55
|
};
|
|
57
56
|
} else {
|
|
58
57
|
|
|
@@ -83,8 +82,39 @@ class OpenAICompletionPlugin extends ModelPlugin {
|
|
|
83
82
|
const data = { ...(this.model.params || {}), ...requestParameters };
|
|
84
83
|
const params = {};
|
|
85
84
|
const headers = this.model.headers || {};
|
|
85
|
+
|
|
86
86
|
return this.executeRequest(url, data, params, headers, prompt);
|
|
87
87
|
}
|
|
88
|
+
|
|
89
|
+
// Parse the response from the OpenAI Completion API
|
|
90
|
+
parseResponse(data) {
|
|
91
|
+
const { choices } = data;
|
|
92
|
+
if (!choices || !choices.length) {
|
|
93
|
+
return data;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// if we got a choices array back with more than one choice, return the whole array
|
|
97
|
+
if (choices.length > 1) {
|
|
98
|
+
return choices;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// otherwise, return the first choice
|
|
102
|
+
const textResult = choices[0].text && choices[0].text.trim();
|
|
103
|
+
return textResult ?? null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Override the logging function to log the prompt and response
|
|
107
|
+
logRequestData(data, responseData, prompt) {
|
|
108
|
+
const separator = `\n=== ${this.pathwayName}.${this.requestCount++} ===\n`;
|
|
109
|
+
console.log(separator);
|
|
110
|
+
|
|
111
|
+
const modelInput = data.prompt;
|
|
112
|
+
|
|
113
|
+
console.log(`\x1b[36m${modelInput}\x1b[0m`);
|
|
114
|
+
console.log(`\x1b[34m> ${this.parseResponse(responseData)}\x1b[0m`);
|
|
115
|
+
|
|
116
|
+
prompt && prompt.debugInfo && (prompt.debugInfo += `${separator}${JSON.stringify(data)}`);
|
|
117
|
+
}
|
|
88
118
|
}
|
|
89
119
|
|
|
90
120
|
export default OpenAICompletionPlugin;
|
|
@@ -14,11 +14,33 @@ import http from 'http';
|
|
|
14
14
|
import https from 'https';
|
|
15
15
|
import url from 'url';
|
|
16
16
|
import { promisify } from 'util';
|
|
17
|
+
import subsrt from 'subsrt';
|
|
17
18
|
const pipeline = promisify(stream.pipeline);
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
const API_URL = config.get('whisperMediaApiUrl');
|
|
21
22
|
|
|
23
|
+
function alignSubtitles(subtitles) {
|
|
24
|
+
const result = [];
|
|
25
|
+
const offset = 1000 * 60 * 10; // 10 minutes for each chunk
|
|
26
|
+
|
|
27
|
+
function preprocessStr(str) {
|
|
28
|
+
return str.trim().replace(/(\n\n)(?!\n)/g, '\n\n\n');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function shiftSubtitles(subtitle, shiftOffset) {
|
|
32
|
+
const captions = subsrt.parse(preprocessStr(subtitle));
|
|
33
|
+
const resynced = subsrt.resync(captions, { offset: shiftOffset });
|
|
34
|
+
return resynced;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (let i = 0; i < subtitles.length; i++) {
|
|
38
|
+
const subtitle = subtitles[i];
|
|
39
|
+
result.push(...shiftSubtitles(subtitle, i * offset));
|
|
40
|
+
}
|
|
41
|
+
return subsrt.build(result);
|
|
42
|
+
}
|
|
43
|
+
|
|
22
44
|
function generateUniqueFilename(extension) {
|
|
23
45
|
return `${uuidv4()}.${extension}`;
|
|
24
46
|
}
|
|
@@ -93,17 +115,20 @@ class OpenAIWhisperPlugin extends ModelPlugin {
|
|
|
93
115
|
|
|
94
116
|
// Execute the request to the OpenAI Whisper API
|
|
95
117
|
async execute(text, parameters, prompt, pathwayResolver) {
|
|
118
|
+
const { responseFormat } = parameters;
|
|
96
119
|
const url = this.requestUrl(text);
|
|
97
120
|
const params = {};
|
|
98
121
|
const { modelPromptText } = this.getCompiledPrompt(text, parameters, prompt);
|
|
99
122
|
|
|
100
123
|
const processChunk = async (chunk) => {
|
|
101
124
|
try {
|
|
102
|
-
const { language } = parameters;
|
|
125
|
+
const { language, responseFormat } = parameters;
|
|
126
|
+
const response_format = responseFormat || 'text';
|
|
127
|
+
|
|
103
128
|
const formData = new FormData();
|
|
104
129
|
formData.append('file', fs.createReadStream(chunk));
|
|
105
130
|
formData.append('model', this.model.params.model);
|
|
106
|
-
formData.append('response_format',
|
|
131
|
+
formData.append('response_format', response_format);
|
|
107
132
|
language && formData.append('language', language);
|
|
108
133
|
modelPromptText && formData.append('prompt', modelPromptText);
|
|
109
134
|
|
|
@@ -114,7 +139,7 @@ class OpenAIWhisperPlugin extends ModelPlugin {
|
|
|
114
139
|
}
|
|
115
140
|
}
|
|
116
141
|
|
|
117
|
-
let result =
|
|
142
|
+
let result = [];
|
|
118
143
|
let { file } = parameters;
|
|
119
144
|
let totalCount = 0;
|
|
120
145
|
let completedCount = 0;
|
|
@@ -151,7 +176,7 @@ class OpenAIWhisperPlugin extends ModelPlugin {
|
|
|
151
176
|
|
|
152
177
|
// sequential processing of chunks
|
|
153
178
|
for (const chunk of chunks) {
|
|
154
|
-
result
|
|
179
|
+
result.push(await processChunk(chunk));
|
|
155
180
|
sendProgress();
|
|
156
181
|
}
|
|
157
182
|
|
|
@@ -184,7 +209,11 @@ class OpenAIWhisperPlugin extends ModelPlugin {
|
|
|
184
209
|
console.error("An error occurred while deleting:", error);
|
|
185
210
|
}
|
|
186
211
|
}
|
|
187
|
-
|
|
212
|
+
|
|
213
|
+
if (['srt','vtt'].includes(responseFormat)) { // align subtitles for formats
|
|
214
|
+
return alignSubtitles(result);
|
|
215
|
+
}
|
|
216
|
+
return result.join(` `);
|
|
188
217
|
}
|
|
189
218
|
}
|
|
190
219
|
|