| |
| |
| |
| |
| |
| |
|
|
|
|
| import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts";
|
| import type { EmbeddingResult, EmbeddingError } from "./types";
|
|
|
| const TRANSFORMERS_MODEL =
|
| process.env.MEMORY_TRANSFORMERS_MODEL || "Xenova/all-MiniLM-L6-v2";
|
|
|
|
|
| type PipelineFn = (text: string | string[], options?: Record<string, unknown>) => Promise<unknown>;
|
| let _pipeline: PipelineFn | null = null;
|
| let _pipelineLoading: Promise<PipelineFn> | null = null;
|
|
|
|
|
| export function _injectPipeline(fn: PipelineFn | null): void {
|
| _pipeline = fn;
|
| _pipelineLoading = null;
|
| }
|
|
|
| async function getOrLoadPipeline(): Promise<PipelineFn> {
|
| if (_pipeline) return _pipeline;
|
| if (_pipelineLoading) return _pipelineLoading;
|
|
|
| _pipelineLoading = (async (): Promise<PipelineFn> => {
|
|
|
| const transformers = await import("@huggingface/transformers");
|
| const { pipeline } = transformers as { pipeline: (task: string, model: string, opts?: Record<string, unknown>) => Promise<PipelineFn> };
|
| const pipe = await pipeline("feature-extraction", TRANSFORMERS_MODEL, { dtype: "q8" });
|
| _pipeline = pipe;
|
| _pipelineLoading = null;
|
| return pipe;
|
| })();
|
|
|
| return _pipelineLoading;
|
| }
|
|
|
| |
| |
| |
| |
|
|
| function tensorToFloat32Array(output: unknown): Float32Array {
|
|
|
| const tensor = output as {
|
| data?: Float32Array | number[];
|
| dims?: number[];
|
| tolist?: () => number[][][];
|
| };
|
|
|
| if (tensor && tensor.data && tensor.dims) {
|
| const data = tensor.data instanceof Float32Array ? tensor.data : new Float32Array(tensor.data);
|
| const dims = tensor.dims;
|
|
|
|
|
| let seqLen: number;
|
| let hiddenSize: number;
|
|
|
| if (dims.length === 3) {
|
|
|
| seqLen = dims[1];
|
| hiddenSize = dims[2];
|
| } else if (dims.length === 2) {
|
|
|
| seqLen = dims[0];
|
| hiddenSize = dims[1];
|
| } else {
|
|
|
| return data instanceof Float32Array ? data : new Float32Array(data);
|
| }
|
|
|
|
|
| const result = new Float32Array(hiddenSize);
|
| for (let s = 0; s < seqLen; s++) {
|
| for (let h = 0; h < hiddenSize; h++) {
|
| result[h] += data[s * hiddenSize + h];
|
| }
|
| }
|
| for (let h = 0; h < hiddenSize; h++) {
|
| result[h] /= seqLen;
|
| }
|
| return result;
|
| }
|
|
|
|
|
| if (tensor && typeof tensor.tolist === "function") {
|
| const list = tensor.tolist();
|
| if (Array.isArray(list) && Array.isArray(list[0])) {
|
|
|
| const inner = list[0];
|
| const hiddenSize2 = (inner[0] as number[]).length;
|
| const result2 = new Float32Array(hiddenSize2);
|
| for (const row of inner) {
|
| for (let h = 0; h < hiddenSize2; h++) {
|
| result2[h] += (row as number[])[h];
|
| }
|
| }
|
| for (let h = 0; h < hiddenSize2; h++) {
|
| result2[h] /= inner.length;
|
| }
|
| return result2;
|
| }
|
| }
|
|
|
| throw new Error("Cannot convert transformers output to Float32Array");
|
| }
|
|
|
| export async function embedTransformers(text: string): Promise<EmbeddingResult | EmbeddingError> {
|
| const t0 = Date.now();
|
| let pipe: PipelineFn;
|
|
|
| try {
|
| pipe = await getOrLoadPipeline();
|
| } catch (err: unknown) {
|
| const isTimeout =
|
| err instanceof Error &&
|
| (err.name === "AbortError" || err.message.toLowerCase().includes("timeout"));
|
| return {
|
| source: "transformers",
|
| model: TRANSFORMERS_MODEL,
|
| reason: isTimeout ? "timeout" : "model_load_failed",
|
| message: sanitizeErrorMessage(err instanceof Error ? err.message : String(err)),
|
| };
|
| }
|
|
|
| try {
|
| const output = await pipe(text, { pooling: "mean", normalize: true });
|
| const vector = tensorToFloat32Array(output);
|
| return {
|
| vector,
|
| source: "transformers",
|
| model: TRANSFORMERS_MODEL,
|
| dimensions: vector.length,
|
| latencyMs: Date.now() - t0,
|
| cached: false,
|
| };
|
| } catch (err: unknown) {
|
| const isTimeout =
|
| err instanceof Error &&
|
| (err.name === "AbortError" || err.message.toLowerCase().includes("timeout"));
|
| return {
|
| source: "transformers",
|
| model: TRANSFORMERS_MODEL,
|
| reason: isTimeout ? "timeout" : "request_failed",
|
| message: sanitizeErrorMessage(err instanceof Error ? err.message : String(err)),
|
| };
|
| }
|
| }
|
|
|