utilitas 1995.2.49 → 1995.2.50
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/dist/utilitas.lite.mjs +1 -1
- package/dist/utilitas.lite.mjs.map +1 -1
- package/lib/alan.mjs +51 -12
- package/lib/bot.mjs +31 -18
- package/lib/manifest.mjs +1 -1
- package/package.json +1 -1
package/lib/alan.mjs
CHANGED
|
@@ -18,7 +18,7 @@ const [
|
|
|
18
18
|
EMBEDDING_GECKO_001, EMBEDDING_GECKO_002, EMBEDDING_GECKO_ML001, MISTRAL,
|
|
19
19
|
] = [
|
|
20
20
|
'gpt-3.5-turbo', 'gpt-3.5-turbo-1106', 'gpt-4', 'gpt-4-1106',
|
|
21
|
-
'gpt-4-
|
|
21
|
+
'gpt-4-vision-preview', 'gemini-pro', 'gemini-pro-vision',
|
|
22
22
|
'text-embedding-ada-002', 'embedding-001', 'textembedding-gecko@001',
|
|
23
23
|
'textembedding-gecko@002', 'textembedding-gecko-multilingual@001',
|
|
24
24
|
'mistral',
|
|
@@ -51,6 +51,7 @@ const trimTailing = text => text.replace(/[\.\s]*$/, '');
|
|
|
51
51
|
const newSessionId = () => createUoid({ type: sessionType });
|
|
52
52
|
const renderText = (t, o) => _renderText(t, { extraCodeBlock: 0, ...o || {} });
|
|
53
53
|
const log = (cnt, opt) => _log(cnt, import.meta.url, { time: 1, ...opt || {} });
|
|
54
|
+
const buildGeminiParts = (text, atcmt) => [{ text }, ...atcmt ? [atcmt] : []];
|
|
54
55
|
const [png, jpeg, mov, mpeg, mp4, mpg, avi, wmv, mpegps, flv, gif, webp] = [
|
|
55
56
|
'image/png', 'image/jpeg', 'video/mov', 'video/mpeg', 'video/mp4',
|
|
56
57
|
'video/mpg', 'video/avi', 'video/wmv', 'video/mpegps', 'video/flv',
|
|
@@ -160,6 +161,7 @@ const MODELS = {
|
|
|
160
161
|
for (const n in MODELS) {
|
|
161
162
|
MODELS[n]['name'] = n;
|
|
162
163
|
if ([TEXT_EMBEDDING_ADA_002].includes(n)) { continue; }
|
|
164
|
+
MODELS[n].supportedMimeTypes = MODELS[n].supportedMimeTypes || [];
|
|
163
165
|
MODELS[n].maxOutputTokens = MODELS[n].maxOutputTokens
|
|
164
166
|
|| Math.ceil(MODELS[n].contextWindow * 0.4);
|
|
165
167
|
MODELS[n].maxInputTokens = MODELS[n].maxInputTokens
|
|
@@ -265,19 +267,35 @@ const countTokens = input => tokenSafe((
|
|
|
265
267
|
|
|
266
268
|
const buildGptMessage = (content, options) => {
|
|
267
269
|
assert(content, 'Content is required.');
|
|
270
|
+
const attachments = (options?.attachments || []).map(x => ({
|
|
271
|
+
type: 'image_url', image_url: x.image_url
|
|
272
|
+
}));
|
|
268
273
|
return String.isString(content) ? {
|
|
269
|
-
role: options?.role || user,
|
|
274
|
+
role: options?.role || user,
|
|
275
|
+
content: [{ type: 'text', text: content }, ...attachments],
|
|
270
276
|
} : content;
|
|
271
277
|
};
|
|
272
278
|
|
|
273
279
|
const buildVertexMessage = (text, options) => {
|
|
274
280
|
assert(text, 'Text is required.');
|
|
281
|
+
// only 1 attachment is allowed while using inline_data:
|
|
282
|
+
const attachment = (options?.attachments || []).map(x => ({
|
|
283
|
+
inline_data: { mime_type: x.mime_type, data: x.data }
|
|
284
|
+
}))?.[0];
|
|
275
285
|
return String.isString(text) ? {
|
|
276
|
-
role: options?.role || user, parts:
|
|
286
|
+
role: options?.role || user, parts: buildGeminiParts(text, attachment),
|
|
277
287
|
} : text;
|
|
278
288
|
};
|
|
279
289
|
|
|
280
|
-
const buildGeminiMessage = text
|
|
290
|
+
const buildGeminiMessage = (text, options) => {
|
|
291
|
+
assert(text, 'Text is required.');
|
|
292
|
+
// @todo: check this issue similar to Vertex AI:
|
|
293
|
+
// only 1 attachment is allowed while using inline_data?
|
|
294
|
+
const attachment = (options?.attachments || []).map(x => ({
|
|
295
|
+
inlineData: { mimeType: x.mime_type, data: x.data }
|
|
296
|
+
}))?.[0];
|
|
297
|
+
return String.isString(text) ? buildGeminiParts(text, attachment) : text;
|
|
298
|
+
};
|
|
281
299
|
|
|
282
300
|
const [getOpenAIClient, getVertexClient, getGeminiClient, getOllamaClient]
|
|
283
301
|
= [OPENAI, VERTEX, GEMINI, OLLAMA].map(
|
|
@@ -293,7 +311,7 @@ const listOpenAIModels = async (options) => {
|
|
|
293
311
|
const packGptResp = (resp, options) => {
|
|
294
312
|
if (options?.raw) { return resp; }
|
|
295
313
|
else if (options?.simple) { return resp.choices[0].message.content; }
|
|
296
|
-
return packResp(resp
|
|
314
|
+
return packResp(resp?.choices?.[0]?.message?.content || '');
|
|
297
315
|
};
|
|
298
316
|
|
|
299
317
|
const promptChatGPT = async (content, options) => {
|
|
@@ -301,7 +319,9 @@ const promptChatGPT = async (content, options) => {
|
|
|
301
319
|
// https://github.com/openai/openai-node?tab=readme-ov-file#streaming-responses
|
|
302
320
|
// https://github.com/openai/openai-node?tab=readme-ov-file#streaming-responses-1
|
|
303
321
|
let [resp, result, chunk] = [await chatGptClient.chat.completions.create({
|
|
304
|
-
...messages([
|
|
322
|
+
...messages([
|
|
323
|
+
...options?.messages || [], buildGptMessage(content, options)
|
|
324
|
+
]),
|
|
305
325
|
model: options?.model || DEFAULT_MODELS[CHATGPT],
|
|
306
326
|
stream: !!options?.stream,
|
|
307
327
|
}), '', null];
|
|
@@ -555,20 +575,29 @@ const handleGeminiResponse = async (resp, options) => {
|
|
|
555
575
|
|
|
556
576
|
const promptVertex = async (content, options) => {
|
|
557
577
|
const { generative } = await getVertexClient(options);
|
|
578
|
+
// https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/gemini?hl=en&_ga=2.265647247.-1650899985.1695692196
|
|
579
|
+
// Google's bug: history is not allowed while using inline_data.
|
|
558
580
|
return await handleGeminiResponse(generative.generateContentStream({
|
|
559
|
-
contents: [
|
|
581
|
+
contents: [
|
|
582
|
+
...options?.messages && !options?.attachments?.length
|
|
583
|
+
? options.messages : [],
|
|
584
|
+
buildVertexMessage(content, options),
|
|
585
|
+
],
|
|
560
586
|
}), options);
|
|
561
587
|
};
|
|
562
588
|
|
|
563
589
|
const promptGemini = async (content, options) => {
|
|
564
590
|
const { generative } = await getGeminiClient(options);
|
|
565
591
|
// https://github.com/google/generative-ai-js/blob/main/samples/node/advanced-chat.js
|
|
592
|
+
// @todo: check this issue similar to Vertex AI:
|
|
593
|
+
// Google's bug: history is not allowed while using inline_data?
|
|
566
594
|
const chat = generative.startChat({
|
|
567
|
-
history: options?.messages
|
|
595
|
+
history: options?.messages && !options?.attachments?.length
|
|
596
|
+
? options.messages : [],
|
|
568
597
|
generationConfig: { ...options?.generationConfig || {} },
|
|
569
598
|
});
|
|
570
599
|
return handleGeminiResponse(chat.sendMessageStream(
|
|
571
|
-
buildGeminiMessage(content)
|
|
600
|
+
buildGeminiMessage(content, options),
|
|
572
601
|
), options);
|
|
573
602
|
};
|
|
574
603
|
|
|
@@ -813,10 +842,16 @@ const talk = async (input, options) => {
|
|
|
813
842
|
}
|
|
814
843
|
}
|
|
815
844
|
const chat = { request: input };
|
|
845
|
+
const attachments = [];
|
|
846
|
+
(options?.attachments || []).filter(
|
|
847
|
+
x => _MODEL.supportedMimeTypes[x.mime_type]
|
|
848
|
+
).map(attachments.push);
|
|
816
849
|
log(`Prompt: ${JSON.stringify(input)}`);
|
|
817
850
|
switch (engine) {
|
|
818
851
|
case CHATGPT:
|
|
819
|
-
resp = await promptChatGPT(input, {
|
|
852
|
+
resp = await promptChatGPT(input, {
|
|
853
|
+
messages, attachments, model, ...options,
|
|
854
|
+
});
|
|
820
855
|
break;
|
|
821
856
|
case ASSISTANT:
|
|
822
857
|
resp = await promptAssistant(input, {
|
|
@@ -826,10 +861,14 @@ const talk = async (input, options) => {
|
|
|
826
861
|
session.threadId = resp.thread.id;
|
|
827
862
|
break;
|
|
828
863
|
case GEMINI:
|
|
829
|
-
resp = await promptGemini(input, {
|
|
864
|
+
resp = await promptGemini(input, {
|
|
865
|
+
messages, attachments, ...options,
|
|
866
|
+
});
|
|
830
867
|
break;
|
|
831
868
|
case VERTEX:
|
|
832
|
-
resp = await promptVertex(input, {
|
|
869
|
+
resp = await promptVertex(input, {
|
|
870
|
+
messages, attachments, ...options,
|
|
871
|
+
});
|
|
833
872
|
break;
|
|
834
873
|
case OLLAMA:
|
|
835
874
|
resp = await promptOllama(input, { messages, model, ...options });
|
package/lib/bot.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
prettyJson, splitArgs, timeout, trim, which,
|
|
7
7
|
} from './utilitas.mjs';
|
|
8
8
|
|
|
9
|
+
import { base64Encode } from './utilitas.mjs';
|
|
9
10
|
import { distill } from './web.mjs';
|
|
10
11
|
import { fakeUuid } from './uoid.mjs';
|
|
11
12
|
import { get } from './shot.mjs';
|
|
@@ -29,6 +30,7 @@ const oList = arr => lines(arr.map((v, k) => `${k + 1}. ${v}`));
|
|
|
29
30
|
const map = obj => uList(Object.entries(obj).map(([k, v]) => `${k}: ${v}`));
|
|
30
31
|
const isMarkdownError = e => e?.description?.includes?.("can't parse entities");
|
|
31
32
|
const sendMd = (cId, cnt, opt) => send(cId, cnt, { parse_mode, ...opt || {} });
|
|
33
|
+
const getFile = async (id, op) => (await get(await getFileUrl(id), op)).content;
|
|
32
34
|
|
|
33
35
|
const [ // https://limits.tginfo.me/en
|
|
34
36
|
BOT_SEND, provider, HELLO, GROUP, PRIVATE, CHANNEL, MENTION, CALLBACK_LIMIT,
|
|
@@ -88,13 +90,11 @@ const getExtra = (ctx, options) => {
|
|
|
88
90
|
return resp;
|
|
89
91
|
};
|
|
90
92
|
|
|
91
|
-
const
|
|
93
|
+
const getFileUrl = async (file_id) => {
|
|
92
94
|
assert(file_id, 'File ID is required.', 400);
|
|
93
95
|
const file = await (await init()).telegram.getFile(file_id);
|
|
94
96
|
assert(file.file_path, 'Error getting file info.', 500);
|
|
95
|
-
return
|
|
96
|
-
`${API_ROOT}file/bot${bot.token}/${file.file_path}`, options
|
|
97
|
-
)).content;
|
|
97
|
+
return `${API_ROOT}file/bot${bot.token}/${file.file_path}`;
|
|
98
98
|
};
|
|
99
99
|
|
|
100
100
|
const officeParser = async file => await ignoreErrFunc(
|
|
@@ -431,7 +431,7 @@ const subconscious = [{
|
|
|
431
431
|
},
|
|
432
432
|
}, {
|
|
433
433
|
run: true, priority: -8870, name: 'vision', func: async (ctx, next) => {
|
|
434
|
-
let fileId, type, file_name, mime_type, ocrFunc;
|
|
434
|
+
let fileId, type, file_name, mime_type, ocrFunc, asPrompt = false;
|
|
435
435
|
if ('application/pdf' === ctx.msg.document?.mime_type) {
|
|
436
436
|
ocrFunc = ctx._.vision?.read;
|
|
437
437
|
fileId = ctx.msg.document.file_id;
|
|
@@ -439,6 +439,7 @@ const subconscious = [{
|
|
|
439
439
|
mime_type = ctx.msg.document.mime_type;
|
|
440
440
|
type = 'DOCUMENT';
|
|
441
441
|
} else if (/^image\/.*$/ig.test(ctx.msg.document?.mime_type)) {
|
|
442
|
+
asPrompt = bot._.supportedMimeTypes.has(ctx.msg.document.mime_type);
|
|
442
443
|
ocrFunc = ctx._.vision?.see;
|
|
443
444
|
fileId = ctx.msg.document.file_id;
|
|
444
445
|
file_name = ctx.msg.document.file_name;
|
|
@@ -457,27 +458,38 @@ const subconscious = [{
|
|
|
457
458
|
mime_type = ctx.msg.document.mime_type;
|
|
458
459
|
type = 'FILE';
|
|
459
460
|
} else if (ctx.msg.photo) {
|
|
461
|
+
asPrompt = true;
|
|
460
462
|
ocrFunc = ctx._.vision?.see;
|
|
461
463
|
fileId = ctx.msg.photo[ctx.msg.photo.length - 1]?.file_id;
|
|
462
464
|
mime_type = 'image';
|
|
463
465
|
type = 'PHOTO';
|
|
464
466
|
}
|
|
465
|
-
if (fileId && ocrFunc) {
|
|
467
|
+
if (fileId && (asPrompt || ocrFunc)) {
|
|
466
468
|
await ctx.ok(EMOJI_LOOK);
|
|
467
469
|
try {
|
|
468
|
-
const
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
file, BUFFER_ENCODE
|
|
472
|
-
), logOptions)
|
|
473
|
-
).filter(x => x).join('\n'));
|
|
474
|
-
if (content) {
|
|
470
|
+
const image_url = await getFileUrl(fileId);
|
|
471
|
+
const file = (await get(image_url, BUFFER_ENCODE)).content;
|
|
472
|
+
if (asPrompt) {
|
|
475
473
|
ctx.collect(ctx.msg.caption || '');
|
|
476
|
-
|
|
477
|
-
'
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
474
|
+
ctx.collect({
|
|
475
|
+
mime_type: mime_type === 'image' ? 'image/jpeg' : mime_type,
|
|
476
|
+
image_url, data: base64Encode(file, true),
|
|
477
|
+
}, 'PROMPT');
|
|
478
|
+
}
|
|
479
|
+
if (ocrFunc) {
|
|
480
|
+
const content = trim(ensureArray(
|
|
481
|
+
await ignoreErrFunc(async () => await ocrFunc(
|
|
482
|
+
file, BUFFER_ENCODE
|
|
483
|
+
), logOptions)
|
|
484
|
+
).filter(x => x).join('\n'));
|
|
485
|
+
if (content) {
|
|
486
|
+
ctx.collect(ctx.msg.caption || '');
|
|
487
|
+
ctx.collect(lines([
|
|
488
|
+
'---', ...file_name ? [`file_name: ${file_name}`] : [],
|
|
489
|
+
`mime_type: ${mime_type}`, `type: ${type}`, '---',
|
|
490
|
+
content
|
|
491
|
+
]), 'VISION');
|
|
492
|
+
}
|
|
481
493
|
}
|
|
482
494
|
} catch (err) { return await ctx.er(err); }
|
|
483
495
|
}
|
|
@@ -705,6 +717,7 @@ const init = async (options) => {
|
|
|
705
717
|
skills: { ...options?.skills || {} },
|
|
706
718
|
speech: options?.speech,
|
|
707
719
|
vision: options?.vision,
|
|
720
|
+
supportedMimeTypes: options?.supportedMimeTypes || [],
|
|
708
721
|
};
|
|
709
722
|
(!options?.session?.get || !options?.session?.set)
|
|
710
723
|
&& log(`WARNING: Sessions persistence is not enabled.`);
|
package/lib/manifest.mjs
CHANGED