@lobu/embeddings 6.0.1
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/dist/embedding-utils.d.ts +3 -0
- package/dist/embedding-utils.d.ts.map +1 -0
- package/dist/embedding-utils.js +16 -0
- package/dist/embedding-utils.js.map +1 -0
- package/dist/embeddings.d.ts +10 -0
- package/dist/embeddings.d.ts.map +1 -0
- package/dist/embeddings.js +57 -0
- package/dist/embeddings.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/openai.d.ts +10 -0
- package/dist/openai.d.ts.map +1 -0
- package/dist/openai.js +39 -0
- package/dist/openai.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +124 -0
- package/dist/server.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedding-utils.d.ts","sourceRoot":"","sources":["../src/embedding-utils.ts"],"names":[],"mappings":"AAQA,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,GAAG,MAAM,EAAE,EAAE,CAEtE;AAED,wBAAgB,2BAA2B,CACzC,SAAS,EAAE,MAAM,EAAE,EACnB,kBAAkB,EAAE,MAAM,EAC1B,OAAO,EAAE,MAAM,GACd,IAAI,CAMN"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
function l2Normalize(vec) {
|
|
2
|
+
const norm = Math.sqrt(vec.reduce((sum, x) => sum + x * x, 0));
|
|
3
|
+
if (norm === 0) {
|
|
4
|
+
return vec;
|
|
5
|
+
}
|
|
6
|
+
return vec.map((x) => x / norm);
|
|
7
|
+
}
|
|
8
|
+
export function normalizeEmbeddings(embeddings) {
|
|
9
|
+
return embeddings.map((embedding) => l2Normalize(embedding));
|
|
10
|
+
}
|
|
11
|
+
export function validateEmbeddingDimensions(embedding, expectedDimensions, context) {
|
|
12
|
+
if (embedding.length !== expectedDimensions) {
|
|
13
|
+
throw new Error(`${context}: unexpected embedding dimensions ${embedding.length} (expected ${expectedDimensions})`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=embedding-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedding-utils.js","sourceRoot":"","sources":["../src/embedding-utils.ts"],"names":[],"mappings":"AAAA,SAAS,WAAW,CAAC,GAAa;IAChC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC/D,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;QACf,OAAO,GAAG,CAAC;IACb,CAAC;IACD,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,UAAsB;IACxD,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,2BAA2B,CACzC,SAAmB,EACnB,kBAA0B,EAC1B,OAAe;IAEf,IAAI,SAAS,CAAC,MAAM,KAAK,kBAAkB,EAAE,CAAC;QAC5C,MAAM,IAAI,KAAK,CACb,GAAG,OAAO,qCAAqC,SAAS,CAAC,MAAM,cAAc,kBAAkB,GAAG,CACnG,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local embedding generation with @xenova/transformers.
|
|
3
|
+
*/
|
|
4
|
+
export declare function getLocalModelInfo(): {
|
|
5
|
+
model: string;
|
|
6
|
+
dimensions: number;
|
|
7
|
+
};
|
|
8
|
+
export declare function generateLocalEmbedding(text: string): Promise<number[]>;
|
|
9
|
+
export declare function batchGenerateLocalEmbeddings(texts: string[], batchSize?: number): Promise<number[][]>;
|
|
10
|
+
//# sourceMappingURL=embeddings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embeddings.d.ts","sourceRoot":"","sources":["../src/embeddings.ts"],"names":[],"mappings":"AAAA;;GAEG;AAqBH,wBAAgB,iBAAiB,IAAI;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAEzE;AAsBD,wBAAsB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAQ5E;AAED,wBAAsB,4BAA4B,CAChD,KAAK,EAAE,MAAM,EAAE,EACf,SAAS,GAAE,MAA2B,GACrC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAwBrB"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local embedding generation with @xenova/transformers.
|
|
3
|
+
*/
|
|
4
|
+
import { pipeline, env as transformersEnv, } from '@xenova/transformers';
|
|
5
|
+
const DEFAULT_MODEL_NAME = 'Xenova/bge-base-en-v1.5';
|
|
6
|
+
const DEFAULT_BATCH_SIZE = 32;
|
|
7
|
+
const DEFAULT_DIMENSIONS = 768;
|
|
8
|
+
transformersEnv.cacheDir = process.env.TRANSFORMERS_CACHE || '~/.cache/huggingface/transformers/';
|
|
9
|
+
transformersEnv.backends.onnx.wasm.numThreads = 1;
|
|
10
|
+
let extractorPromise = null;
|
|
11
|
+
function getModelName() {
|
|
12
|
+
return process.env.EMBEDDINGS_MODEL || DEFAULT_MODEL_NAME;
|
|
13
|
+
}
|
|
14
|
+
export function getLocalModelInfo() {
|
|
15
|
+
return { model: getModelName(), dimensions: DEFAULT_DIMENSIONS };
|
|
16
|
+
}
|
|
17
|
+
async function getExtractor() {
|
|
18
|
+
if (!extractorPromise) {
|
|
19
|
+
const modelName = getModelName();
|
|
20
|
+
console.log(`[EmbeddingsService] Loading model: ${modelName}...`);
|
|
21
|
+
const startTime = Date.now();
|
|
22
|
+
extractorPromise = pipeline('feature-extraction', modelName, {
|
|
23
|
+
quantized: true,
|
|
24
|
+
});
|
|
25
|
+
const extractor = await extractorPromise;
|
|
26
|
+
const loadTime = Date.now() - startTime;
|
|
27
|
+
console.log(`[EmbeddingsService] Model loaded in ${loadTime}ms`);
|
|
28
|
+
return extractor;
|
|
29
|
+
}
|
|
30
|
+
return extractorPromise;
|
|
31
|
+
}
|
|
32
|
+
export async function generateLocalEmbedding(text) {
|
|
33
|
+
const extractor = await getExtractor();
|
|
34
|
+
const output = await extractor(text, {
|
|
35
|
+
pooling: 'cls',
|
|
36
|
+
normalize: true,
|
|
37
|
+
});
|
|
38
|
+
return Array.from(output.data);
|
|
39
|
+
}
|
|
40
|
+
export async function batchGenerateLocalEmbeddings(texts, batchSize = DEFAULT_BATCH_SIZE) {
|
|
41
|
+
if (texts.length === 0) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
const extractor = await getExtractor();
|
|
45
|
+
const results = [];
|
|
46
|
+
for (let i = 0; i < texts.length; i += batchSize) {
|
|
47
|
+
const batch = texts.slice(i, i + batchSize);
|
|
48
|
+
const batchOutputs = await Promise.all(batch.map((text) => extractor(text, {
|
|
49
|
+
pooling: 'cls',
|
|
50
|
+
normalize: true,
|
|
51
|
+
})));
|
|
52
|
+
const batchEmbeddings = batchOutputs.map((output) => Array.from(output.data));
|
|
53
|
+
results.push(...batchEmbeddings);
|
|
54
|
+
}
|
|
55
|
+
return results;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=embeddings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embeddings.js","sourceRoot":"","sources":["../src/embeddings.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAEL,QAAQ,EACR,GAAG,IAAI,eAAe,GACvB,MAAM,sBAAsB,CAAC;AAE9B,MAAM,kBAAkB,GAAG,yBAAyB,CAAC;AACrD,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAC9B,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAE/B,eAAe,CAAC,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,oCAAoC,CAAC;AAClG,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;AAElD,IAAI,gBAAgB,GAA8C,IAAI,CAAC;AAEvE,SAAS,YAAY;IACnB,OAAO,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,kBAAkB,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,UAAU,EAAE,kBAAkB,EAAE,CAAC;AACnE,CAAC;AAED,KAAK,UAAU,YAAY;IACzB,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACtB,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;QACjC,OAAO,CAAC,GAAG,CAAC,sCAAsC,SAAS,KAAK,CAAC,CAAC;QAClE,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAE7B,gBAAgB,GAAG,QAAQ,CAAC,oBAAoB,EAAE,SAAS,EAAE;YAC3D,SAAS,EAAE,IAAI;SAChB,CAAC,CAAC;QAEH,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC;QACzC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;QACxC,OAAO,CAAC,GAAG,CAAC,uCAAuC,QAAQ,IAAI,CAAC,CAAC;QAEjE,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,IAAY;IACvD,MAAM,SAAS,GAAG,MAAM,YAAY,EAAE,CAAC;IACvC,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,IAAI,EAAE;QACnC,OAAO,EAAE,KAAK;QACd,SAAS,EAAE,IAAI;KAChB,CAAC,CAAC;IAEH,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAa,CAAC;AAC7C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,KAAe,EACf,YAAoB,kBAAkB;IAEtC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,YAAY,EAAE,CAAC;IACvC,MAAM,OAAO,GAAe,EAAE,CAAC;IAE/B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,SAAS,EAAE,CAAC;QACjD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC;QAC5C,MAAM,YAAY,GAAG,MAAM,OAAO,CAAC,GAAG,CACpC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CACjB,SAAS,CAAC,IAAI,EAAE;YACd,OAAO,EAAE,KAAK;YACd,SAAS,EAAE,IAAI;SAChB,CAAC,CACH,CACF,CAAC;QAEF,MAAM,eAAe,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAa,CAAC,CAAC;QAC1F,OAAO,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,CAAC;IACnC,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC"}
|
package/dist/openai.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function generateOpenAIEmbeddings(config: {
|
|
2
|
+
texts: string[];
|
|
3
|
+
apiUrl: string;
|
|
4
|
+
apiKey: string;
|
|
5
|
+
model: string;
|
|
6
|
+
expectedDimensions: number;
|
|
7
|
+
normalize: boolean;
|
|
8
|
+
timeoutMs: number;
|
|
9
|
+
}): Promise<number[][]>;
|
|
10
|
+
//# sourceMappingURL=openai.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../src/openai.ts"],"names":[],"mappings":"AAOA,wBAAsB,wBAAwB,CAAC,MAAM,EAAE;IACrD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,kBAAkB,EAAE,MAAM,CAAC;IAC3B,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CA8CtB"}
|
package/dist/openai.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { normalizeEmbeddings, validateEmbeddingDimensions } from './embedding-utils';
|
|
2
|
+
export async function generateOpenAIEmbeddings(config) {
|
|
3
|
+
const controller = new AbortController();
|
|
4
|
+
const timeout = setTimeout(() => controller.abort(), config.timeoutMs);
|
|
5
|
+
try {
|
|
6
|
+
const response = await fetch(config.apiUrl, {
|
|
7
|
+
method: 'POST',
|
|
8
|
+
headers: {
|
|
9
|
+
'Content-Type': 'application/json',
|
|
10
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
11
|
+
},
|
|
12
|
+
body: JSON.stringify({
|
|
13
|
+
model: config.model,
|
|
14
|
+
input: config.texts,
|
|
15
|
+
}),
|
|
16
|
+
signal: controller.signal,
|
|
17
|
+
});
|
|
18
|
+
if (!response.ok) {
|
|
19
|
+
const errorText = await response.text();
|
|
20
|
+
throw new Error(`OpenAI embeddings error (${response.status}): ${errorText.slice(0, 300)}`);
|
|
21
|
+
}
|
|
22
|
+
const payload = (await response.json());
|
|
23
|
+
if (!Array.isArray(payload.data)) {
|
|
24
|
+
throw new Error('OpenAI embeddings response missing data array');
|
|
25
|
+
}
|
|
26
|
+
const embeddings = payload.data.map((item) => item.embedding);
|
|
27
|
+
if (embeddings.length !== config.texts.length) {
|
|
28
|
+
throw new Error(`OpenAI embeddings response returned ${embeddings.length} embeddings for ${config.texts.length} texts`);
|
|
29
|
+
}
|
|
30
|
+
for (const embedding of embeddings) {
|
|
31
|
+
validateEmbeddingDimensions(embedding, config.expectedDimensions, 'OpenAI embeddings response');
|
|
32
|
+
}
|
|
33
|
+
return config.normalize ? normalizeEmbeddings(embeddings) : embeddings;
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
clearTimeout(timeout);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=openai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../src/openai.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,MAAM,mBAAmB,CAAC;AAOrF,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAC,MAQ9C;IACC,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;IAEvE,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE;YAC1C,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,aAAa,EAAE,UAAU,MAAM,CAAC,MAAM,EAAE;aACzC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,KAAK,EAAE,MAAM,CAAC,KAAK;aACpB,CAAC;YACF,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACxC,MAAM,IAAI,KAAK,CAAC,4BAA4B,QAAQ,CAAC,MAAM,MAAM,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QAC9F,CAAC;QAED,MAAM,OAAO,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA4B,CAAC;QACnE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACjC,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;QACnE,CAAC;QAED,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC9D,IAAI,UAAU,CAAC,MAAM,KAAK,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YAC9C,MAAM,IAAI,KAAK,CACb,uCAAuC,UAAU,CAAC,MAAM,mBAAmB,MAAM,CAAC,KAAK,CAAC,MAAM,QAAQ,CACvG,CAAC;QACJ,CAAC;QACD,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;YACnC,2BAA2B,CACzB,SAAS,EACT,MAAM,CAAC,kBAAkB,EACzB,4BAA4B,CAC7B,CAAC;QACJ,CAAC;QAED,OAAO,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;IACzE,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,OAAO,CAAC,CAAC;IACxB,CAAC;AACH,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":""}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { serve } from '@hono/node-server';
|
|
2
|
+
import { Hono } from 'hono';
|
|
3
|
+
import { validateEmbeddingDimensions } from './embedding-utils';
|
|
4
|
+
import { batchGenerateLocalEmbeddings, generateLocalEmbedding, getLocalModelInfo, } from './embeddings';
|
|
5
|
+
import { generateOpenAIEmbeddings } from './openai';
|
|
6
|
+
const DEFAULT_PORT = 8790;
|
|
7
|
+
const DEFAULT_TIMEOUT_MS = 30000;
|
|
8
|
+
const DEFAULT_DIMENSIONS = 768;
|
|
9
|
+
const DEFAULT_BATCH_SIZE = 32;
|
|
10
|
+
const app = new Hono();
|
|
11
|
+
function resolveNumber(value, fallback) {
|
|
12
|
+
const parsed = Number.parseInt(value || '', 10);
|
|
13
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
14
|
+
}
|
|
15
|
+
function parseTexts(payload) {
|
|
16
|
+
if (!Array.isArray(payload.texts)) {
|
|
17
|
+
return { error: 'texts must be an array of strings' };
|
|
18
|
+
}
|
|
19
|
+
const texts = [];
|
|
20
|
+
for (const value of payload.texts) {
|
|
21
|
+
if (typeof value !== 'string') {
|
|
22
|
+
return { error: 'texts must be an array of strings' };
|
|
23
|
+
}
|
|
24
|
+
const trimmed = value.trim();
|
|
25
|
+
if (!trimmed) {
|
|
26
|
+
return { error: 'texts cannot contain empty strings' };
|
|
27
|
+
}
|
|
28
|
+
texts.push(trimmed);
|
|
29
|
+
}
|
|
30
|
+
if (texts.length === 0) {
|
|
31
|
+
return { error: 'texts cannot be empty' };
|
|
32
|
+
}
|
|
33
|
+
return { texts };
|
|
34
|
+
}
|
|
35
|
+
function requireAuth(request) {
|
|
36
|
+
const token = process.env.EMBEDDINGS_SERVICE_TOKEN;
|
|
37
|
+
if (!token) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const header = request.headers.get('authorization') || '';
|
|
41
|
+
const match = header.match(/^Bearer\s+(.+)$/i);
|
|
42
|
+
if (!match || match[1] !== token) {
|
|
43
|
+
return 'Unauthorized';
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
app.get('/health', (c) => {
|
|
48
|
+
const backend = (process.env.EMBEDDINGS_BACKEND || 'local').toLowerCase();
|
|
49
|
+
const model = backend === 'openai' ? process.env.EMBEDDINGS_MODEL : getLocalModelInfo().model;
|
|
50
|
+
return c.json({ ok: true, backend, model });
|
|
51
|
+
});
|
|
52
|
+
app.post('/api/embeddings', async (c) => {
|
|
53
|
+
const authError = requireAuth(c.req.raw);
|
|
54
|
+
if (authError) {
|
|
55
|
+
return c.json({ error: authError }, 401);
|
|
56
|
+
}
|
|
57
|
+
let payload;
|
|
58
|
+
try {
|
|
59
|
+
payload = (await c.req.json());
|
|
60
|
+
}
|
|
61
|
+
catch (_error) {
|
|
62
|
+
return c.json({ error: 'Invalid JSON payload' }, 400);
|
|
63
|
+
}
|
|
64
|
+
const parsed = parseTexts(payload);
|
|
65
|
+
if ('error' in parsed) {
|
|
66
|
+
return c.json({ error: parsed.error }, 400);
|
|
67
|
+
}
|
|
68
|
+
const backend = (process.env.EMBEDDINGS_BACKEND || 'local').toLowerCase();
|
|
69
|
+
const expectedDimensions = resolveNumber(process.env.EMBEDDINGS_DIMENSIONS, DEFAULT_DIMENSIONS);
|
|
70
|
+
const timeoutMs = resolveNumber(process.env.EMBEDDINGS_TIMEOUT_MS, DEFAULT_TIMEOUT_MS);
|
|
71
|
+
const normalize = process.env.EMBEDDINGS_NORMALIZE !== 'false';
|
|
72
|
+
try {
|
|
73
|
+
if (backend === 'openai') {
|
|
74
|
+
const apiKey = process.env.EMBEDDINGS_API_KEY;
|
|
75
|
+
if (!apiKey) {
|
|
76
|
+
return c.json({ error: 'EMBEDDINGS_API_KEY is required for openai backend' }, 500);
|
|
77
|
+
}
|
|
78
|
+
const apiUrl = process.env.EMBEDDINGS_API_URL || 'https://api.openai.com/v1/embeddings';
|
|
79
|
+
const model = payload.model || process.env.EMBEDDINGS_MODEL;
|
|
80
|
+
if (!model) {
|
|
81
|
+
return c.json({ error: 'EMBEDDINGS_MODEL is required for openai backend' }, 500);
|
|
82
|
+
}
|
|
83
|
+
const embeddings = await generateOpenAIEmbeddings({
|
|
84
|
+
texts: parsed.texts,
|
|
85
|
+
apiUrl,
|
|
86
|
+
apiKey,
|
|
87
|
+
model,
|
|
88
|
+
expectedDimensions,
|
|
89
|
+
normalize,
|
|
90
|
+
timeoutMs,
|
|
91
|
+
});
|
|
92
|
+
return c.json({
|
|
93
|
+
model,
|
|
94
|
+
dimensions: expectedDimensions,
|
|
95
|
+
embeddings,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
const batchSize = resolveNumber(process.env.EMBEDDINGS_BATCH_SIZE, DEFAULT_BATCH_SIZE);
|
|
99
|
+
const embeddings = parsed.texts.length === 1
|
|
100
|
+
? [await generateLocalEmbedding(parsed.texts[0])]
|
|
101
|
+
: await batchGenerateLocalEmbeddings(parsed.texts, batchSize);
|
|
102
|
+
for (const embedding of embeddings) {
|
|
103
|
+
validateEmbeddingDimensions(embedding, expectedDimensions, 'Local embeddings response');
|
|
104
|
+
}
|
|
105
|
+
const model = getLocalModelInfo().model;
|
|
106
|
+
return c.json({
|
|
107
|
+
model,
|
|
108
|
+
dimensions: expectedDimensions,
|
|
109
|
+
embeddings,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
const message = error instanceof Error ? error.message : 'Embedding generation failed';
|
|
114
|
+
console.error('[EmbeddingsService] Error:', message);
|
|
115
|
+
return c.json({ error: message }, 500);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
const port = Number.parseInt(process.env.PORT || String(DEFAULT_PORT), 10);
|
|
119
|
+
serve({
|
|
120
|
+
fetch: app.fetch,
|
|
121
|
+
port,
|
|
122
|
+
});
|
|
123
|
+
console.log(`[EmbeddingsService] Listening on port ${port}`);
|
|
124
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lobu/embeddings",
|
|
3
|
+
"version": "6.0.1",
|
|
4
|
+
"license": "BUSL-1.1",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Embeddings HTTP service for Lobu memory",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"dev": "tsx watch --tsconfig tsconfig.json src/server.ts",
|
|
10
|
+
"start": "tsx --tsconfig tsconfig.json src/server.ts",
|
|
11
|
+
"typecheck": "tsc --noEmit",
|
|
12
|
+
"build": "tsc"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@hono/node-server": "^1.13.7",
|
|
16
|
+
"@xenova/transformers": "^2.17.2",
|
|
17
|
+
"hono": "^4.10.4"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "^22.10.2",
|
|
21
|
+
"tsx": "^4.19.2",
|
|
22
|
+
"typescript": "^5.7.2"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=20"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/lobu-ai/lobu.git",
|
|
33
|
+
"directory": "packages/embeddings"
|
|
34
|
+
},
|
|
35
|
+
"types": "dist/index.d.ts",
|
|
36
|
+
"files": [
|
|
37
|
+
"dist"
|
|
38
|
+
],
|
|
39
|
+
"exports": {
|
|
40
|
+
".": {
|
|
41
|
+
"import": {
|
|
42
|
+
"types": "./dist/index.d.ts",
|
|
43
|
+
"default": "./dist/index.js"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"./server": {
|
|
47
|
+
"import": {
|
|
48
|
+
"types": "./dist/server.d.ts",
|
|
49
|
+
"default": "./dist/server.js"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|