@aj-archipelago/cortex 1.0.2 → 1.0.4

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.
@@ -0,0 +1,99 @@
1
+ // ModelPlugin.test.js
2
+ import test from 'ava';
3
+ import ModelPlugin from '../graphql/plugins/modelPlugin.js';
4
+ import { encode } from 'gpt-3-encoder';
5
+ import { mockConfig, mockPathwayString } from './mocks.js';
6
+
7
+ const config = mockConfig;
8
+ const pathway = mockPathwayString;
9
+
10
+ const modelPlugin = new ModelPlugin(config, pathway);
11
+
12
+ const generateMessage = (role, content) => ({ role, content });
13
+
14
+ test('truncateMessagesToTargetLength: should not modify messages if already within target length', (t) => {
15
+ const messages = [
16
+ generateMessage('user', 'Hello, how are you?'),
17
+ generateMessage('assistant', 'I am doing well, thank you!'),
18
+ ];
19
+ const targetTokenLength = encode(modelPlugin.messagesToChatML(messages, false)).length;
20
+
21
+ const result = modelPlugin.truncateMessagesToTargetLength(messages, targetTokenLength);
22
+ t.deepEqual(result, messages);
23
+ });
24
+
25
+ test('truncateMessagesToTargetLength: should remove messages from the front until target length is reached', (t) => {
26
+ const messages = [
27
+ generateMessage('user', 'Hello, how are you?'),
28
+ generateMessage('assistant', 'I am doing well, thank you!'),
29
+ generateMessage('user', 'What is your favorite color?'),
30
+ ];
31
+ const targetTokenLength = encode(modelPlugin.messagesToChatML(messages.slice(1), false)).length;
32
+
33
+ const result = modelPlugin.truncateMessagesToTargetLength(messages, targetTokenLength);
34
+ t.deepEqual(result, messages.slice(1));
35
+ });
36
+
37
+ test('truncateMessagesToTargetLength: should skip system messages', (t) => {
38
+ const messages = [
39
+ generateMessage('system', 'System message 1'),
40
+ generateMessage('user', 'Hello, how are you?'),
41
+ generateMessage('assistant', 'I am doing well, thank you!'),
42
+ ];
43
+ const targetTokenLength = encode(modelPlugin.messagesToChatML([messages[0], ...messages.slice(2)], false)).length;
44
+
45
+ const result = modelPlugin.truncateMessagesToTargetLength(messages, targetTokenLength);
46
+ t.deepEqual(result, [messages[0], ...messages.slice(2)]);
47
+ });
48
+
49
+ test('truncateMessagesToTargetLength: should truncate messages to fit target length', (t) => {
50
+ const messages = [
51
+ generateMessage('user', 'Hello, how are you?'),
52
+ generateMessage('assistant', 'I am doing well, thank you!'),
53
+ ];
54
+ const targetTokenLength = encode(modelPlugin.messagesToChatML(messages, false)).length - 4;
55
+
56
+ const result = modelPlugin.truncateMessagesToTargetLength(messages, targetTokenLength);
57
+ t.true(result.every((message, index) => message.content.length <= messages[index].content.length));
58
+ t.true(encode(modelPlugin.messagesToChatML(result, false)).length <= targetTokenLength);
59
+ });
60
+
61
+ test('truncateMessagesToTargetLength: should remove messages entirely if they need to be empty to fit target length', (t) => {
62
+ const messages = [
63
+ generateMessage('user', 'Hello, how are you?'),
64
+ generateMessage('assistant', 'I am doing well, thank you!'),
65
+ ];
66
+ const targetTokenLength = encode(modelPlugin.messagesToChatML(messages.slice(1), false)).length;
67
+
68
+ const result = modelPlugin.truncateMessagesToTargetLength(messages, targetTokenLength);
69
+ t.deepEqual(result, messages.slice(1));
70
+ });
71
+
72
+ test('truncateMessagesToTargetLength: should return an empty array if target length is 0', (t) => {
73
+ const messages = [
74
+ generateMessage('user', 'Hello, how are you?'),
75
+ generateMessage('assistant', 'I am doing well, thank you!'),
76
+ ];
77
+
78
+ const result = modelPlugin.truncateMessagesToTargetLength(messages, 0);
79
+ t.deepEqual(result, []);
80
+ });
81
+
82
+ test('truncateMessagesToTargetLength: should not remove system messages even if they are too long', (t) => {
83
+ const messages = [
84
+ generateMessage('user', 'Hello, how are you?'),
85
+ generateMessage('system', 'System message content that is very long and exceeds the target token length'),
86
+ generateMessage('assistant', 'I am fine, thank you.'),
87
+ ];
88
+
89
+ const targetTokenLength = 20;
90
+ const result = modelPlugin.truncateMessagesToTargetLength(messages, targetTokenLength);
91
+
92
+ const systemMessage = result.find((message) => message.role === 'system');
93
+ t.truthy(systemMessage, 'System message should not be removed');
94
+ t.is(
95
+ systemMessage.content,
96
+ 'System message content that is very long and exceeds the target token length',
97
+ 'System message content should not be altered'
98
+ );
99
+ });
@@ -1,147 +0,0 @@
1
- import fs from 'fs';
2
- import { path as ffmpegPath } from '@ffmpeg-installer/ffmpeg';
3
- import ffmpeg from 'fluent-ffmpeg';
4
- ffmpeg.setFfmpegPath(ffmpegPath);
5
- import path from 'path';
6
- import { v4 as uuidv4 } from 'uuid';
7
- import os from 'os';
8
- import util from 'util';
9
- import { pipeline } from 'stream';
10
-
11
- const ffmpegProbe = util.promisify(ffmpeg.ffprobe);
12
-
13
- const cPipeline = util.promisify(pipeline);
14
-
15
- import ytdl from 'ytdl-core';
16
-
17
-
18
- async function processChunk(inputPath, outputFileName, start, duration) {
19
- return new Promise((resolve, reject) => {
20
- ffmpeg(inputPath)
21
- .seekInput(start)
22
- .duration(duration)
23
- .on('start', (cmd) => {
24
- console.log(`Started FFmpeg with command: ${cmd}`);
25
- })
26
- .on('error', (err) => {
27
- console.error(`Error occurred while processing chunk:`, err);
28
- reject(err);
29
- })
30
- .on('end', () => {
31
- console.log(`Finished processing chunk`);
32
- resolve(outputFileName);
33
- })
34
- .save(outputFileName);
35
- });
36
- }
37
-
38
- const generateUniqueFolderName = () => {
39
- const uniqueFolderName = uuidv4();
40
- const tempFolderPath = os.tmpdir(); // Get the system's temporary folder
41
- const uniqueOutputPath = path.join(tempFolderPath, uniqueFolderName);
42
- return uniqueOutputPath;
43
- }
44
-
45
- async function splitMediaFile(inputPath, chunkDurationInSeconds = 600) {
46
- try {
47
- const metadata = await ffmpegProbe(inputPath);
48
- const duration = metadata.format.duration;
49
- const numChunks = Math.ceil((duration - 1) / chunkDurationInSeconds);
50
-
51
- const chunkPromises = [];
52
-
53
- const uniqueOutputPath = generateUniqueFolderName();
54
-
55
- // Create unique folder
56
- fs.mkdirSync(uniqueOutputPath, { recursive: true });
57
-
58
-
59
- for (let i = 0; i < numChunks; i++) {
60
- const outputFileName = path.join(
61
- uniqueOutputPath,
62
- `chunk-${i + 1}-${path.basename(inputPath)}`
63
- );
64
-
65
- const chunkPromise = processChunk(
66
- inputPath,
67
- outputFileName,
68
- i * chunkDurationInSeconds,
69
- chunkDurationInSeconds
70
- );
71
-
72
- chunkPromises.push(chunkPromise);
73
- }
74
-
75
- // const chunkedFiles = await Promise.all(chunkPromises);
76
- // console.log('All chunks processed. Chunked file names:', chunkedFiles);
77
- // return { chunks: chunkedFiles, folder: uniqueOutputPath }
78
- return { chunkPromises, uniqueOutputPath }
79
- } catch (err) {
80
- console.error('Error occurred during the splitting process:', err);
81
- }
82
- }
83
-
84
- async function deleteTempPath(path) {
85
- try {
86
- if (!path) return;
87
- const stats = fs.statSync(path);
88
- if (stats.isFile()) {
89
- fs.unlinkSync(path);
90
- console.log(`Temporary file ${path} deleted successfully.`);
91
- } else if (stats.isDirectory()) {
92
- fs.rmdirSync(path, { recursive: true });
93
- console.log(`Temporary folder ${path} and its contents deleted successfully.`);
94
- }
95
- } catch (err) {
96
- console.error('Error occurred while deleting the temporary path:', err);
97
- }
98
- }
99
-
100
-
101
- function isValidYoutubeUrl(url) {
102
- const regex = /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.?be)\/.+$/;
103
- return regex.test(url);
104
- }
105
-
106
- function convertYoutubeToMp3Stream(video) {
107
- // Configure ffmpeg to convert the video to mp3
108
- const mp3Stream = ffmpeg(video)
109
- .withAudioCodec('libmp3lame')
110
- .toFormat('mp3')
111
- .on('error', (err) => {
112
- console.error(`An error occurred during conversion: ${err.message}`);
113
- });
114
-
115
- return mp3Stream;
116
- }
117
-
118
- async function pipeStreamToFile(stream, filePath) {
119
- try {
120
- await cPipeline(stream, fs.createWriteStream(filePath));
121
- console.log('Stream piped to file successfully.');
122
- } catch (error) {
123
- console.error(`Error piping stream to file: ${error.message}`);
124
- }
125
- }
126
-
127
-
128
- const processYoutubeUrl = async (url) => {
129
- const info = await ytdl.getInfo(url);
130
- const audioFormat = ytdl.chooseFormat(info.formats, { quality: 'highestaudio' });
131
-
132
- if (!audioFormat) {
133
- throw new Error('No suitable audio format found');
134
- }
135
-
136
- const stream = ytdl.downloadFromInfo(info, { format: audioFormat });
137
- // const stream = ytdl(url, { filter: 'audioonly' })
138
-
139
- const mp3Stream = convertYoutubeToMp3Stream(stream);
140
- const outputFileName = path.join(os.tmpdir(), `${uuidv4()}.mp3`);
141
- await pipeStreamToFile(mp3Stream, outputFileName); // You can also pipe the stream to a file
142
- return outputFileName;
143
- }
144
-
145
- export {
146
- splitMediaFile, deleteTempPath, processYoutubeUrl, isValidYoutubeUrl
147
- };