@o-lang/semantic-doc-search 1.0.32 → 1.0.33
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 +1 -1
- package/src/resolver.js +26 -12
package/package.json
CHANGED
package/src/resolver.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const VectorRouter = require("./adapters/vectorRouter");
|
|
2
|
-
const embedder = require("./embeddings/local"); //
|
|
2
|
+
const embedder = require("./embeddings/local"); // singleton embedder
|
|
3
3
|
const { extractQuery } = require("./utils/extractQuery");
|
|
4
4
|
const { formatResults } = require("./utils/formatResults");
|
|
5
5
|
const fs = require("fs");
|
|
@@ -8,7 +8,7 @@ const crypto = require("crypto");
|
|
|
8
8
|
|
|
9
9
|
const CACHE_PATH = path.join(process.cwd(), "embeddings.json");
|
|
10
10
|
|
|
11
|
-
//
|
|
11
|
+
// Load cache for ingestion guard
|
|
12
12
|
function loadCache() {
|
|
13
13
|
try {
|
|
14
14
|
if (fs.existsSync(CACHE_PATH)) {
|
|
@@ -17,24 +17,36 @@ function loadCache() {
|
|
|
17
17
|
} catch {}
|
|
18
18
|
return {};
|
|
19
19
|
}
|
|
20
|
+
|
|
20
21
|
function saveCache(cache) {
|
|
21
22
|
try {
|
|
22
23
|
fs.writeFileSync(CACHE_PATH, JSON.stringify(cache, null, 2));
|
|
23
24
|
} catch {}
|
|
24
25
|
}
|
|
26
|
+
|
|
25
27
|
function hashContent(str) {
|
|
26
28
|
return crypto.createHash("sha256").update(str).digest("hex");
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Clean text for embedding (defensive)
|
|
33
|
+
*/
|
|
34
|
+
function sanitizeTextForEmbedding(text) {
|
|
35
|
+
if (typeof text !== "string") return "";
|
|
36
|
+
// Remove wrapping quotes and extra whitespace
|
|
37
|
+
return text.replace(/^["']|["']$/g, "").trim();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Semantic Doc Search Resolver
|
|
42
|
+
*/
|
|
30
43
|
async function resolver(action, context = {}) {
|
|
31
44
|
if (typeof action !== "string") return;
|
|
32
45
|
if (!action.toLowerCase().startsWith("ask doc-search")) return;
|
|
33
46
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (!query) throw new Error("Query is empty after sanitization");
|
|
47
|
+
let query = extractQuery(action);
|
|
48
|
+
query = sanitizeTextForEmbedding(query);
|
|
49
|
+
if (!query) return { text: "(Empty query)", meta: { matches: 0 } };
|
|
38
50
|
|
|
39
51
|
// Vector backend
|
|
40
52
|
const vectorStore = VectorRouter.create(context);
|
|
@@ -61,14 +73,14 @@ async function resolver(action, context = {}) {
|
|
|
61
73
|
for (const doc of context.documents) {
|
|
62
74
|
const chunks = doc.chunks || [doc.content];
|
|
63
75
|
for (let i = 0; i < chunks.length; i++) {
|
|
64
|
-
const text = chunks[i];
|
|
65
|
-
if (!text
|
|
76
|
+
const text = sanitizeTextForEmbedding(chunks[i]);
|
|
77
|
+
if (!text) continue;
|
|
66
78
|
|
|
67
79
|
const hash = hashContent(text);
|
|
68
80
|
if (cache[hash]) continue; // Skip already ingested
|
|
69
81
|
|
|
70
82
|
const vector = await embedder.embed(text);
|
|
71
|
-
if (!
|
|
83
|
+
if (!vector || vector.every(v => v === 0)) continue;
|
|
72
84
|
|
|
73
85
|
await vectorStore.upsert({
|
|
74
86
|
id: `${doc.id}:${i}`,
|
|
@@ -85,10 +97,12 @@ async function resolver(action, context = {}) {
|
|
|
85
97
|
|
|
86
98
|
// Embed query & search
|
|
87
99
|
const queryVector = await embedder.embed(query);
|
|
88
|
-
if (!
|
|
89
|
-
|
|
100
|
+
if (!queryVector || queryVector.every(v => v === 0)) {
|
|
101
|
+
console.warn("⚠️ Query embedding invalid");
|
|
102
|
+
return { text: "(Query could not be embedded)", meta: { matches: 0 } };
|
|
90
103
|
}
|
|
91
104
|
|
|
105
|
+
// Top-K + similarity threshold
|
|
92
106
|
const results = await vectorStore.query({
|
|
93
107
|
vector: queryVector,
|
|
94
108
|
topK: context.topK || 5,
|