@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/resolver.js +26 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@o-lang/semantic-doc-search",
3
- "version": "1.0.32",
3
+ "version": "1.0.33",
4
4
  "description": "O-lang Semantic Document Search Resolver with hybrid search, embeddings, rerank, and streaming.",
5
5
  "main": "src/index.js",
6
6
  "type": "commonjs",
package/src/resolver.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const VectorRouter = require("./adapters/vectorRouter");
2
- const embedder = require("./embeddings/local"); // singleton embedder
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
- // ---------------- Cache utils ----------------
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
- // ---------------- Resolver ----------------
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
- // Extract & sanitize query
35
- const queryRaw = extractQuery(action);
36
- const query = typeof queryRaw === "string" ? queryRaw.replace(/^["']|["']$/g, "").trim() : "";
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 || typeof text !== "string") continue;
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 (!Array.isArray(vector) || vector.every(v => v === 0)) continue;
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 (!Array.isArray(queryVector) || queryVector.length !== embedder.getDimension()) {
89
- throw new Error("Query embedding invalid or not a proper array");
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,