@o-lang/semantic-doc-search 1.0.41 → 1.0.43

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/package.json CHANGED
@@ -1,38 +1,40 @@
1
- {
2
- "name": "@o-lang/semantic-doc-search",
3
- "version": "1.0.41",
4
- "description": "O-lang Semantic Document Search Resolver with hybrid search, embeddings, rerank, and streaming.",
5
- "main": "src/index.js",
6
- "type": "commonjs",
7
- "bin": {
8
- "olang-doc-search": "bin/cli.js"
9
- },
10
- "scripts": {
11
- "start": "node bin/cli.js",
12
- "test": "echo \"No tests yet\""
13
- },
14
- "dependencies": {
15
- "@anthropic-ai/sdk": "*",
16
- "@xenova/transformers": "^2.17.2",
17
- "axios": "^1.7.2",
18
- "docx": "^7.0.0",
19
- "dotenv": "^17.2.3",
20
- "express": "^4.18.2",
21
- "groq-sdk": "^0.5.0",
22
- "jsdom": "^22.1.0",
23
- "minimist": "^1.2.8",
24
- "node-stream-zip": "*",
25
- "openai": "^4.3.1",
26
- "pdf-parse": "^1.1.1",
27
- "pg": "^8.16.3",
28
- "pgvector": "^0.2.1",
29
- "pinecone-client": "^1.0.0",
30
- "readline": "^1.3.0",
31
- "redis": "^5.2.0"
32
- },
33
- "devDependencies": {
34
- "eslint": "^8.46.0",
35
- "jest": "^29.7.0",
36
- "nodemon": "^2.0.22"
37
- }
38
- }
1
+ {
2
+ "name": "@o-lang/semantic-doc-search",
3
+ "version": "1.0.43",
4
+ "description": "O-Lang semantic document search resolver with vector embeddings",
5
+ "main": "src/index.js",
6
+ "exports": {
7
+ ".": "./src/index.js",
8
+ "./resolver": "./src/resolver.js",
9
+ "./embeddings/local": "./src/embeddings/local.js"
10
+ },
11
+ "files": [
12
+ "src/",
13
+ "package.json",
14
+ "README.md"
15
+ ],
16
+ "scripts": {
17
+ "test": "node src/test-doc-search.js",
18
+ "start": "node src/index.js"
19
+ },
20
+ "keywords": [
21
+ "o-lang",
22
+ "resolver",
23
+ "semantic-search",
24
+ "rag",
25
+ "embeddings"
26
+ ],
27
+ "author": "O-Lang Team <info@olang.cloud>",
28
+ "license": "MIT",
29
+ "dependencies": {
30
+ "@pinecone-database/pinecone": "^2.2.2",
31
+ "@xenova/transformers": "^2.14.0",
32
+ "axios": "^1.6.0",
33
+ "dotenv": "^16.6.1",
34
+ "pg": "^8.20.0",
35
+ "redis": "^4.7.1"
36
+ },
37
+ "engines": {
38
+ "node": ">=18.0.0"
39
+ }
40
+ }
@@ -149,5 +149,15 @@ class LocalEmbedding {
149
149
  }
150
150
  }
151
151
 
152
- const embedder = new LocalEmbedding();
153
- module.exports = embedder;
152
+ // EXPORT AS FACTORY FUNCTION (what resolver expects)
153
+ // Usage: const embed = await embedder({ dimension: 384 })
154
+ // Returns: async (text) => vector
155
+ const embedderInstance = new LocalEmbedding();
156
+
157
+ module.exports = async ({ dimension = 384 } = {}) => {
158
+ if (dimension && typeof dimension === 'number') {
159
+ embedderInstance.dim = dimension;
160
+ }
161
+ // Return bound embed method that resolver can call: await embed(text)
162
+ return embedderInstance.embed.bind(embedderInstance);
163
+ };
@@ -0,0 +1,153 @@
1
+ /**
2
+ * LocalEmbedding
3
+ * ----------------
4
+ * Real semantic embeddings using all-MiniLM-L6-v2
5
+ * - Singleton model load
6
+ * - No silent failures
7
+ * - No zero vectors
8
+ * - Deterministic behavior
9
+ * - DEFENSIVE against method detaching & invalid vectors
10
+ * - WINDOWS-SAFE (disables SIMD, threads, proxy)
11
+ * - TENSOR-SAFE (handles Float32Array, Array, and all ONNX tensor types)
12
+ */
13
+
14
+ class LocalEmbedding {
15
+ constructor() {
16
+ this.dim = 384;
17
+ this.model = null;
18
+ this.loading = null;
19
+
20
+ // 🔒 Bind methods to prevent resolver breakage
21
+ this.loadModel = this.loadModel.bind(this);
22
+ this.embed = this.embed.bind(this);
23
+ this.embedBatch = this.embedBatch.bind(this);
24
+ this.getDimension = this.getDimension.bind(this);
25
+ }
26
+
27
+ /* ---------------- INTERNAL ---------------- */
28
+
29
+ async loadModel() {
30
+ if (this.model) return this.model;
31
+
32
+ if (!this.loading) {
33
+ this.loading = (async () => {
34
+ // ⚠️ CRITICAL: Configure environment BEFORE loading model
35
+ const { env } = await import("@xenova/transformers");
36
+
37
+ // Safe settings for all platforms (harmless on macOS/Linux, essential on Windows)
38
+ env.backends.onnx.wasm.simd = false; // Avoids AVX/SIMD crashes on older CPUs
39
+ env.backends.onnx.wasm.threads = false; // Prevents threading issues in Node
40
+ env.backends.onnx.wasm.proxy = false; // Avoids proxy complications
41
+ env.allowLocalModels = true;
42
+ env.backends.onnx.warmup = false;
43
+ env.cacheDir = "./.cache/embeddings"; // Explicit, project-local cache
44
+
45
+ console.log("🔄 Loading local embedding model (first run only)...");
46
+ console.log("⚙️ Using WASM (SIMD disabled) for cross-platform compatibility");
47
+
48
+ const { pipeline } = await import("@xenova/transformers");
49
+
50
+ const model = await pipeline(
51
+ "feature-extraction",
52
+ "Xenova/all-MiniLM-L6-v2",
53
+ {
54
+ revision: "main",
55
+ cache_dir: "./.cache/embeddings",
56
+ }
57
+ );
58
+
59
+ console.log("✅ Local embedding model ready");
60
+ return model;
61
+ })();
62
+ }
63
+
64
+ this.model = await this.loading;
65
+ return this.model;
66
+ }
67
+
68
+ /* ---------------- PUBLIC API ---------------- */
69
+
70
+ async embed(text) {
71
+ if (typeof text !== "string" || !text.trim()) {
72
+ throw new Error("Embedding input must be a non-empty string");
73
+ }
74
+
75
+ const model = await this.loadModel();
76
+
77
+ try {
78
+ const output = await model(text, {
79
+ pooling: "mean",
80
+ normalize: true,
81
+ });
82
+
83
+ // 🔍 DEBUG: Inspect output structure
84
+ console.log("🔍 Model output type:", typeof output);
85
+ if (output && typeof output === 'object') {
86
+ console.log("🔍 Output keys:", Object.keys(output));
87
+ console.log("🔍 Output dims:", output.dims);
88
+ console.log("🔍 output.data type:", Object.prototype.toString.call(output.data));
89
+ console.log("🔍 Is TypedArray?", ArrayBuffer.isView(output.data));
90
+ }
91
+
92
+ // ✅ UNIVERSAL EXTRACTION: handles Float32Array, Array, and all tensor forms
93
+ let vector = null;
94
+
95
+ if (output && output.data !== undefined) {
96
+ // Handle Float32Array, Uint8Array, etc. (standard in ONNX/WASM)
97
+ if (ArrayBuffer.isView(output.data)) {
98
+ vector = Array.from(output.data);
99
+ }
100
+ // Handle plain JS array (older backends or CPU mode)
101
+ else if (Array.isArray(output.data)) {
102
+ vector = Array.from(output.data);
103
+ }
104
+ }
105
+ // Handle batch output: [tensor]
106
+ else if (Array.isArray(output) && output[0]?.data !== undefined) {
107
+ if (ArrayBuffer.isView(output[0].data)) {
108
+ vector = Array.from(output[0].data);
109
+ } else if (Array.isArray(output[0].data)) {
110
+ vector = Array.from(output[0].data);
111
+ }
112
+ }
113
+ // Fallback: raw array (rare)
114
+ else if (Array.isArray(output)) {
115
+ vector = output;
116
+ }
117
+
118
+ // Final validation
119
+ if (!Array.isArray(vector) || vector.length !== this.dim) {
120
+ console.error("❌ Invalid embedding vector length:", vector?.length);
121
+ console.error("❌ First few values:", vector?.slice?.(0, 5));
122
+ throw new Error(`Invalid embedding dimension: ${vector?.length || 0} (expected ${this.dim})`);
123
+ }
124
+
125
+ return vector;
126
+ } catch (err) {
127
+ console.error(
128
+ `❌ Embedding failed for text: "${text.slice(0, 60)}..."`,
129
+ err.message
130
+ );
131
+ throw err;
132
+ }
133
+ }
134
+
135
+ async embedBatch(texts = []) {
136
+ if (!Array.isArray(texts)) {
137
+ throw new Error("embedBatch expects an array of strings");
138
+ }
139
+
140
+ const results = [];
141
+ for (const text of texts) {
142
+ results.push(await this.embed(text));
143
+ }
144
+ return results;
145
+ }
146
+
147
+ getDimension() {
148
+ return this.dim;
149
+ }
150
+ }
151
+
152
+ const embedder = new LocalEmbedding();
153
+ module.exports = embedder;
package/src/index.js CHANGED
@@ -1,4 +1,3 @@
1
- // index.js (6 lines)
2
1
  const semanticResolver = require("./resolver");
3
2
 
4
3
  async function docSearchResolver(action, context) {
@@ -6,4 +5,5 @@ async function docSearchResolver(action, context) {
6
5
  }
7
6
 
8
7
  docSearchResolver.resolverName = "doc-search";
8
+ docSearchResolver.version = "1.0.41";
9
9
  module.exports = docSearchResolver;
@@ -1,25 +0,0 @@
1
- {
2
- "_name_or_path": "sentence-transformers/all-MiniLM-L6-v2",
3
- "architectures": [
4
- "BertModel"
5
- ],
6
- "attention_probs_dropout_prob": 0.1,
7
- "classifier_dropout": null,
8
- "gradient_checkpointing": false,
9
- "hidden_act": "gelu",
10
- "hidden_dropout_prob": 0.1,
11
- "hidden_size": 384,
12
- "initializer_range": 0.02,
13
- "intermediate_size": 1536,
14
- "layer_norm_eps": 1e-12,
15
- "max_position_embeddings": 512,
16
- "model_type": "bert",
17
- "num_attention_heads": 12,
18
- "num_hidden_layers": 6,
19
- "pad_token_id": 0,
20
- "position_embedding_type": "absolute",
21
- "transformers_version": "4.29.2",
22
- "type_vocab_size": 2,
23
- "use_cache": true,
24
- "vocab_size": 30522
25
- }