utilitas 2001.1.136 → 2001.1.139
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 +10 -8
- package/dist/utilitas.lite.mjs +1 -1
- package/dist/utilitas.lite.mjs.map +1 -1
- package/lib/alan.mjs +169 -301
- package/lib/dbio.mjs +355 -34
- package/lib/manifest.mjs +20 -20
- package/lib/rag.mjs +85 -43
- package/lib/storage.mjs +4 -2
- package/package.json +20 -20
package/lib/rag.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BASE64, convert } from './storage.mjs';
|
|
1
|
+
import { BASE64, convert, formatDataURL } from './storage.mjs';
|
|
2
2
|
import { countTokens, trimText } from './alan.mjs';
|
|
3
3
|
import { ensureArray, ensureString, need } from './utilitas.mjs';
|
|
4
4
|
|
|
@@ -9,20 +9,16 @@ const rerankerClients = {};
|
|
|
9
9
|
const [
|
|
10
10
|
OPENAI, GOOGLE, OPENROUTER, JINA,
|
|
11
11
|
GOOGLE_DEFAULT_LOCATION, GOOGLE_RERANK_CONFIG_ID,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
GOOGLE_MODEL_GEMINI_EMBED,
|
|
15
|
-
JINA_MODEL_V_4,
|
|
12
|
+
OPENAI_EMBED,
|
|
13
|
+
GEMINI_EMBED,
|
|
16
14
|
GOOGLE_MODEL_SEMANTIC_RANKER,
|
|
17
15
|
JINA_MODEL_RERANKER_M0,
|
|
18
16
|
] = [
|
|
19
17
|
'OPENAI', 'GOOGLE', 'OPENROUTER', 'JINA',
|
|
20
18
|
'global', 'default_ranking_config',
|
|
21
19
|
'text-embedding-3-small', // dim: 1536
|
|
22
|
-
'text-embedding-3-large', // dim: 3072
|
|
23
20
|
// https://blog.google/innovation-and-ai/models-and-research/gemini-models/gemini-embedding-2/
|
|
24
|
-
'gemini-embedding-
|
|
25
|
-
'jina-embeddings-v4', // dim: 256‑2048
|
|
21
|
+
'gemini-embedding-2-preview', // dim: from 128 to 3072, default 768.
|
|
26
22
|
'semantic-ranker-default@latest',
|
|
27
23
|
'jina-reranker-m0',
|
|
28
24
|
];
|
|
@@ -33,9 +29,8 @@ const PROVIDER_BASE_URL = {
|
|
|
33
29
|
};
|
|
34
30
|
|
|
35
31
|
const DEFAULT_EMBEDDING_MODELS = {
|
|
36
|
-
[OPENAI]:
|
|
37
|
-
[OPENROUTER]:
|
|
38
|
-
[JINA]: JINA_MODEL_V_4,
|
|
32
|
+
[OPENAI]: OPENAI_EMBED,
|
|
33
|
+
[OPENROUTER]: GEMINI_EMBED,
|
|
39
34
|
};
|
|
40
35
|
|
|
41
36
|
const DEFAULT_RERANKER_MODELS = {
|
|
@@ -44,27 +39,12 @@ const DEFAULT_RERANKER_MODELS = {
|
|
|
44
39
|
};
|
|
45
40
|
|
|
46
41
|
const MODEL_CONFIG = {
|
|
47
|
-
[
|
|
42
|
+
[OPENAI_EMBED]: {
|
|
48
43
|
source: 'openai', image: false, maxTokens: 8192,
|
|
49
44
|
},
|
|
50
|
-
[
|
|
51
|
-
source: '
|
|
52
|
-
|
|
53
|
-
[GOOGLE_MODEL_GEMINI_EMBED]: {
|
|
54
|
-
source: 'google', image: false, maxTokens: 2048,
|
|
55
|
-
options: { dimensions: 768 },
|
|
56
|
-
},
|
|
57
|
-
// Token calculation may be incorrect because its limitation applies to the
|
|
58
|
-
// entire request rather than individual entries.
|
|
59
|
-
// https://jina.ai/embeddings
|
|
60
|
-
[JINA_MODEL_V_4]: {
|
|
61
|
-
source: 'jina', image: true, maxTokens: 8192, recordsLimit: 512,
|
|
62
|
-
options: {
|
|
63
|
-
task: 'text-matching', // 'retrieval.query', 'retrieval.passage'
|
|
64
|
-
dimensions: 768, // normalized: true, by default DONT submit
|
|
65
|
-
truncate: true, // late_chunking: true, by default DONT submit
|
|
66
|
-
embedding_type: 'float',
|
|
67
|
-
},
|
|
45
|
+
[GEMINI_EMBED]: {
|
|
46
|
+
source: 'google', image: true, maxTokens: 8192,
|
|
47
|
+
options: { dimensions: 768, encoding_format: 'float' },
|
|
68
48
|
},
|
|
69
49
|
[GOOGLE_MODEL_SEMANTIC_RANKER]: {
|
|
70
50
|
source: 'google', image: false, maxTokens: 1024, recordsLimit: 200,
|
|
@@ -136,6 +116,70 @@ const initEmbedding = async (options = {}) => {
|
|
|
136
116
|
return getEmbeddingClient(provider);
|
|
137
117
|
};
|
|
138
118
|
|
|
119
|
+
const parseDataUrl = (value) => {
|
|
120
|
+
const match = ensureString(value).match(/^data:([^;]+);base64,(.*)$/i);
|
|
121
|
+
return match ? { mime: match[1], data: match[2], url: value } : null;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const normalizeEmbedImage = async (value, options) => {
|
|
125
|
+
const dataUrl = parseDataUrl(value);
|
|
126
|
+
if (dataUrl) { return dataUrl; }
|
|
127
|
+
if (!options?.input && /^https?:\/\//i.test(ensureString(value))) {
|
|
128
|
+
return { url: value, mime: options?.mime };
|
|
129
|
+
}
|
|
130
|
+
if (!options?.input && String.isString(value)) {
|
|
131
|
+
assert(options?.mime, 'Image MIME type is required.', 400);
|
|
132
|
+
return { data: value, mime: options.mime };
|
|
133
|
+
}
|
|
134
|
+
const { content, mime } = await convert(value, {
|
|
135
|
+
...options, expected: BASE64, meta: true,
|
|
136
|
+
});
|
|
137
|
+
assert(
|
|
138
|
+
(options?.mime || mime)?.startsWith('image/'),
|
|
139
|
+
`Invalid image MIME type: ${options?.mime || mime}.`, 400
|
|
140
|
+
);
|
|
141
|
+
return { data: content, mime: options?.mime || mime };
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const formatOpenRouterInput = (items) => items.map((x) => {
|
|
145
|
+
if (x.text !== undefined) {
|
|
146
|
+
return { content: [{ type: 'text', text: x.text }] };
|
|
147
|
+
}
|
|
148
|
+
if (x.image) {
|
|
149
|
+
return {
|
|
150
|
+
content: [{
|
|
151
|
+
type: 'image_url',
|
|
152
|
+
image_url: {
|
|
153
|
+
url: x.image.url || formatDataURL(
|
|
154
|
+
x.image.mime, x.image.data
|
|
155
|
+
),
|
|
156
|
+
},
|
|
157
|
+
}],
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
assert(false, 'Unsupported embedding input type.', 400);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Generate embeddings for text or image input.
|
|
165
|
+
*
|
|
166
|
+
* Usage:
|
|
167
|
+
* await initEmbedding({ provider: 'OPENROUTER', apiKey });
|
|
168
|
+
* await embed('hello world', { provider: 'OPENROUTER' });
|
|
169
|
+
* await embed({ image: '/path/to/image.jpg' }, {
|
|
170
|
+
* provider: 'OPENROUTER',
|
|
171
|
+
* input: 'FILE',
|
|
172
|
+
* });
|
|
173
|
+
* await embed([
|
|
174
|
+
* 'hello world',
|
|
175
|
+
* { image: 'https://example.com/image.jpg' },
|
|
176
|
+
* ], { provider: 'OPENROUTER' });
|
|
177
|
+
*
|
|
178
|
+
* Input accepts a string, { text }, { image }, or an array of those values.
|
|
179
|
+
* Local image files should pass { input: 'FILE' }. Raw base64 image strings
|
|
180
|
+
* need { mime: 'image/...' }. OpenRouter image embeddings are sent through
|
|
181
|
+
* its multimodal `image_url` embeddings format.
|
|
182
|
+
*/
|
|
139
183
|
const embed = async (input, options = {}) => {
|
|
140
184
|
let [{ client, model: selectedModel, provider, source }, resp]
|
|
141
185
|
= [getEmbeddingClient(options?.provider), null];
|
|
@@ -147,35 +191,33 @@ const embed = async (input, options = {}) => {
|
|
|
147
191
|
Object.keys(x).length == 1,
|
|
148
192
|
'Only one type of input is allowed at a time.', 400
|
|
149
193
|
);
|
|
150
|
-
if (x.text) {
|
|
151
|
-
x.text =
|
|
152
|
-
} else if (x.image) {
|
|
194
|
+
if (x.text !== undefined) {
|
|
195
|
+
x.text = trimText(x.text, MODEL_CONFIG[model]?.maxTokens);
|
|
196
|
+
} else if (x.image !== undefined) {
|
|
153
197
|
assert(
|
|
154
198
|
MODEL_CONFIG[model]?.image,
|
|
155
199
|
`Model ${model} does not support image embeddings.`, 400
|
|
156
200
|
);
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
);
|
|
161
|
-
}
|
|
201
|
+
x.image = await normalizeEmbedImage(x.image, options);
|
|
202
|
+
} else {
|
|
203
|
+
assert(false, 'Unsupported embedding input type.', 400);
|
|
162
204
|
}
|
|
163
205
|
return x;
|
|
164
206
|
}));
|
|
165
|
-
MODEL_CONFIG[model]?.image || (input = input.map(x => x.text));
|
|
166
207
|
assert(input.length, 'Input is required.', 400);
|
|
167
208
|
const body = {
|
|
168
|
-
model,
|
|
209
|
+
model,
|
|
210
|
+
input: input.map(x => x.text),
|
|
211
|
+
...MODEL_CONFIG[model]?.options || {},
|
|
169
212
|
...options?.requestOptions || {},
|
|
170
213
|
};
|
|
171
214
|
switch (provider) {
|
|
172
|
-
case JINA:
|
|
173
|
-
resp = await client.post('/embeddings', { body });
|
|
174
|
-
break;
|
|
175
215
|
case OPENROUTER:
|
|
176
216
|
source = options?.source || source
|
|
177
217
|
|| MODEL_CONFIG[body.model]?.source;
|
|
178
218
|
body.model = `${source ? `${source}/` : ''}${body.model}`;
|
|
219
|
+
input.some(x => x.image)
|
|
220
|
+
&& (body.input = formatOpenRouterInput(input));
|
|
179
221
|
case OPENAI:
|
|
180
222
|
resp = await client.embeddings.create(body);
|
|
181
223
|
break;
|
package/lib/storage.mjs
CHANGED
|
@@ -31,7 +31,7 @@ const [
|
|
|
31
31
|
MIME_JPEG, MIME_MOV, MIME_MPEG, MIME_MP4, MIME_MPG, MIME_AVI, MIME_WMV,
|
|
32
32
|
MIME_MPEGPS, MIME_FLV, MIME_GIF, MIME_WEBP, MIME_PDF, MIME_AAC, MIME_FLAC,
|
|
33
33
|
MIME_MP3, MIME_MPEGA, MIME_M4A, MIME_MPGA, MIME_OPUS, MIME_PCM, MIME_WAV,
|
|
34
|
-
MIME_WEBM, MIME_TGPP, MIME_PCM16, MIME_OGG,
|
|
34
|
+
MIME_WAVE, MIME_WEBM, MIME_TGPP, MIME_PCM16, MIME_OGG,
|
|
35
35
|
] = [
|
|
36
36
|
'NULL', 'BASE64', 'BUFFER', 'FILE', 'STREAM', 'TEXT', 'JSON', 'utf8',
|
|
37
37
|
'binary', 'BLOB', 'DATAURL', '0644', '0755', 'text/plain',
|
|
@@ -40,7 +40,8 @@ const [
|
|
|
40
40
|
'video/avi', 'video/wmv', 'video/mpegps', 'video/x-flv', 'image/gif',
|
|
41
41
|
'image/webp', 'application/pdf', 'audio/aac', 'audio/flac', 'audio/mp3',
|
|
42
42
|
'audio/mpeg', 'audio/m4a', 'audio/mpga', 'audio/opus', 'audio/pcm',
|
|
43
|
-
'audio/wav', 'audio/webm', 'video/3gpp', 'audio/x-wav',
|
|
43
|
+
'audio/wav', 'audio/wave', 'audio/webm', 'video/3gpp', 'audio/x-wav',
|
|
44
|
+
'audio/ogg',
|
|
44
45
|
];
|
|
45
46
|
|
|
46
47
|
const [encodeBase64, encodeBinary, encodeNull]
|
|
@@ -551,6 +552,7 @@ export {
|
|
|
551
552
|
MIME_TEXT,
|
|
552
553
|
MIME_TGPP,
|
|
553
554
|
MIME_WAV,
|
|
555
|
+
MIME_WAVE,
|
|
554
556
|
MIME_WEBM,
|
|
555
557
|
MIME_WEBP,
|
|
556
558
|
MIME_WMV,
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "utilitas",
|
|
3
3
|
"description": "Just another common utility for JavaScript.",
|
|
4
|
-
"version": "2001.1.
|
|
4
|
+
"version": "2001.1.139",
|
|
5
5
|
"private": false,
|
|
6
6
|
"homepage": "https://github.com/Leask/utilitas",
|
|
7
7
|
"main": "index.mjs",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"engines": {
|
|
10
|
-
"node": ">=
|
|
10
|
+
"node": ">=22.5.0"
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
13
|
"start": "node index.mjs",
|
|
@@ -30,19 +30,19 @@
|
|
|
30
30
|
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.1/xlsx-0.20.1.tgz"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"file-type": "^
|
|
34
|
-
"mathjs": "^15.
|
|
35
|
-
"uuid": "^
|
|
33
|
+
"file-type": "^22.0.1",
|
|
34
|
+
"mathjs": "^15.2.0",
|
|
35
|
+
"uuid": "^14.0.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
|
39
39
|
"@ffprobe-installer/ffprobe": "^2.1.2",
|
|
40
|
-
"@google-cloud/discoveryengine": "^2.
|
|
40
|
+
"@google-cloud/discoveryengine": "^2.6.0",
|
|
41
41
|
"@google-cloud/storage": "^7.19.0",
|
|
42
|
-
"@google/genai": "^1.
|
|
42
|
+
"@google/genai": "^1.51.0",
|
|
43
43
|
"@mozilla/readability": "github:mozilla/readability",
|
|
44
|
-
"@sentry/node": "^10.
|
|
45
|
-
"@sentry/profiling-node": "^10.
|
|
44
|
+
"@sentry/node": "^10.51.0",
|
|
45
|
+
"@sentry/profiling-node": "^10.51.0",
|
|
46
46
|
"acme-client": "^5.4.0",
|
|
47
47
|
"browserify-fs": "^1.0.0",
|
|
48
48
|
"buffer": "^6.0.3",
|
|
@@ -51,34 +51,34 @@
|
|
|
51
51
|
"fluent-ffmpeg": "^2.1.3",
|
|
52
52
|
"form-data": "^4.0.5",
|
|
53
53
|
"google-gax": "^5.0.6",
|
|
54
|
-
"ioredis": "^5.10.
|
|
55
|
-
"jsdom": "^29.
|
|
54
|
+
"ioredis": "^5.10.1",
|
|
55
|
+
"jsdom": "^29.1.0",
|
|
56
56
|
"lorem-ipsum": "^2.0.8",
|
|
57
|
-
"mailgun.js": "^
|
|
58
|
-
"mailparser": "^3.9.
|
|
57
|
+
"mailgun.js": "^13.0.0",
|
|
58
|
+
"mailparser": "^3.9.8",
|
|
59
59
|
"mime": "^4.1.0",
|
|
60
|
-
"mysql2": "^3.
|
|
60
|
+
"mysql2": "^3.22.3",
|
|
61
61
|
"node-mailjet": "^6.0.11",
|
|
62
62
|
"node-polyfill-webpack-plugin": "^4.1.0",
|
|
63
63
|
"office-text-extractor": "^4.0.0",
|
|
64
|
-
"openai": "^6.
|
|
64
|
+
"openai": "^6.35.0",
|
|
65
65
|
"pdf-lib": "^1.17.1",
|
|
66
|
-
"pdfjs-dist": "^5.
|
|
66
|
+
"pdfjs-dist": "^5.7.284",
|
|
67
67
|
"pg": "^8.20.0",
|
|
68
68
|
"pgvector": "^0.2.1",
|
|
69
69
|
"ping": "^1.0.0",
|
|
70
70
|
"process": "^0.11.10",
|
|
71
|
-
"puppeteer": "^24.
|
|
71
|
+
"puppeteer": "^24.42.0",
|
|
72
72
|
"say": "^0.16.0",
|
|
73
73
|
"telegraf": "^4.16.3",
|
|
74
74
|
"telesignsdk": "^5.0.0",
|
|
75
75
|
"tesseract.js": "^7.0.0",
|
|
76
|
-
"twilio": "^
|
|
76
|
+
"twilio": "^6.0.0",
|
|
77
77
|
"url": "github:Leask/node-url",
|
|
78
78
|
"webpack-cli": "^7.0.2",
|
|
79
79
|
"whisper-node": "^1.1.1",
|
|
80
|
-
"wrangler": "^4.
|
|
80
|
+
"wrangler": "^4.86.0",
|
|
81
81
|
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.1/xlsx-0.20.1.tgz",
|
|
82
|
-
"youtube-transcript": "^1.3.
|
|
82
|
+
"youtube-transcript": "^1.3.1"
|
|
83
83
|
}
|
|
84
84
|
}
|