Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | 1x 11x 1x 1x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 7x 6x 6x 6x 6x 6x 6x 6x 6x 2x 2x 5x 2x 5x 5x | /**
* WebEmbedder — browser ONNX Runtime code embedding.
*
* Implements IEmbedder using onnxruntime-web (WASM default, WebGPU auto-upgrade).
*/
import type { IEmbedder, BatchOptions, ModelInfo } from '@agentix-e/embed-code-core';
import {
WordPieceTokenizer,
meanPool,
l2Normalize,
int32ToBigInt64,
processBatch,
} from '@agentix-e/embed-code-core';
export { type IEmbedder, type ModelInfo, type BatchOptions };
// Type-safe wrapper interface for onnxruntime-web
// Manual types avoid the complex onnxruntime-web type hierarchy
interface OrtInstance {
InferenceSession: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
create(path: ArrayBuffer | string, options?: Record<string, any>): Promise<OrtSession>;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Tensor: new (
type: string,
data: any,
dims: number[],
) => { type: string; dims: readonly number[]; data: unknown };
}
interface OrtSession {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
run(feeds: Record<string, any>): Promise<Record<string, any>>;
release(): void;
}
// Dynamic imports: onnxruntime-web is loaded lazily in browser context
let ortWeb: OrtInstance | null = null;
async function getOrt(): Promise<OrtInstance> {
if (ortWeb) return ortWeb;
ortWeb = (await import('onnxruntime-web')) as unknown as OrtInstance;
return ortWeb;
}
export class WebEmbedder implements IEmbedder {
private session: OrtSession | null = null;
readonly dimensions = 768;
readonly maxSequenceLength = 512;
readonly modelInfo: ModelInfo;
private tokenizer: WordPieceTokenizer;
private modelBuffer: ArrayBuffer;
private constructor(tokenizer: WordPieceTokenizer, modelBuffer: ArrayBuffer) {
this.tokenizer = tokenizer;
this.modelBuffer = modelBuffer;
this.modelInfo = {
name: 'nomic-embed-code',
version: 'v1.5',
dimensions: 768,
maxSequenceLength: 512,
vocabSize: tokenizer.vocabSize,
quantization: 'int8',
};
}
static async create(modelUrl: string, tokenizerJson: Record<string, any>): Promise<WebEmbedder> {
const tokenizer = WordPieceTokenizer.fromJSON(tokenizerJson, 512);
let buffer: ArrayBuffer;
if (modelUrl.startsWith('file://')) {
// Node.js file:// protocol — dynamic import of fs (not available in browser)
// @ts-expect-error 2307 — node:fs module only exists in Node.js runtime
const { readFileSync } = await import('node:fs');
const raw = readFileSync(modelUrl.replace('file://', ''));
buffer = raw.buffer.slice(raw.byteOffset, raw.byteOffset + raw.byteLength);
} else E{
const resp = await fetch(modelUrl);
buffer = await resp.arrayBuffer();
}
const embedder = new WebEmbedder(tokenizer, buffer);
const ort = await getOrt();
// WebGPU auto-upgrade
try {
embedder.session = await ort.InferenceSession.create(buffer, {
executionProviders: ['webgpu'],
graphOptimizationLevel: 'all',
});
} catch {
embedder.session = await ort.InferenceSession.create(buffer, {
executionProviders: ['wasm'],
graphOptimizationLevel: 'all',
});
}
return embedder;
}
async embed(text: string): Promise<Float32Array> {
if (!this.session) throw new Error('Session not initialized');
const tokens = this.tokenizer.tokenize(text);
const ort = await getOrt();
const feeds = {
input_ids: new ort.Tensor('int64', int32ToBigInt64(tokens.inputIds), [1, 512]),
attention_mask: new ort.Tensor('int64', int32ToBigInt64(tokens.attentionMask), [1, 512]),
token_type_ids: new ort.Tensor('int64', int32ToBigInt64(tokens.tokenTypeIds), [1, 512]),
};
const outputs = await this.session.run(feeds);
const hidden = outputs.last_hidden_state.data as Float32Array;
const pooled = meanPool(hidden, tokens.attentionMask, 1, 512, 768);
l2Normalize(pooled, 1, 768);
return pooled;
}
async embedBatch(texts: string[], options?: BatchOptions): Promise<Float32Array[]> {
const results: Float32Array[] = new Array(texts.length);
await processBatch(
texts,
async (text, index) => {
results[index] = await this.embed(text);
},
options,
);
return results;
}
async dispose(): Promise<void> {
this.session?.release();
this.session = null;
}
}
|