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/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-1106-preview', 'gemini-pro', 'gemini-pro-vision',
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, content
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: [{ text }]
286
+ role: options?.role || user, parts: buildGeminiParts(text, attachment),
277
287
  } : text;
278
288
  };
279
289
 
280
- const buildGeminiMessage = text => String.isString(text) ? [{ text }] : 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.choices[0].message.content);
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([...options?.messages || [], buildGptMessage(content)]),
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: [...options?.messages || [], buildVertexMessage(content)],
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, { messages, model, ...options });
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, { messages, ...options });
864
+ resp = await promptGemini(input, {
865
+ messages, attachments, ...options,
866
+ });
830
867
  break;
831
868
  case VERTEX:
832
- resp = await promptVertex(input, { messages, ...options });
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 getFile = async (file_id, options) => {
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 (await get(
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 file = await getFile(fileId, BUFFER_ENCODE);
469
- const content = trim(ensureArray(
470
- await ignoreErrFunc(async () => await ocrFunc(
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
- content && ctx.collect(lines([
477
- '---', ...file_name ? [`file_name: ${file_name}`] : [],
478
- `mime_type: ${mime_type}`, `type: ${type}`, '---',
479
- content
480
- ]), 'VISION');
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
@@ -1,7 +1,7 @@
1
1
  const manifest = {
2
2
  "name": "utilitas",
3
3
  "description": "Just another common utility for JavaScript.",
4
- "version": "1995.2.49",
4
+ "version": "1995.2.50",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/Leask/utilitas",
7
7
  "main": "index.mjs",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "utilitas",
3
3
  "description": "Just another common utility for JavaScript.",
4
- "version": "1995.2.49",
4
+ "version": "1995.2.50",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/Leask/utilitas",
7
7
  "main": "index.mjs",