@voidwire/lore 1.3.0 → 1.4.0

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/lib/semantic.ts +62 -3
  2. package/package.json +1 -1
package/lib/semantic.ts CHANGED
@@ -13,6 +13,44 @@ import { getDatabasePath, openDatabase } from "./db.js";
13
13
  import { search as keywordSearch, type SearchResult } from "./search.js";
14
14
  import { getConfig } from "./config.js";
15
15
 
16
+ // ─── Embedding Server (server-first, in-process fallback) ────────────────────
17
+
18
+ const EMBED_SERVER = process.env.EMBED_SERVER_URL || "http://localhost:8090";
19
+
20
+ /**
21
+ * Try the persistent embedding server first (warm: ~9ms vs 244ms in-process).
22
+ * Returns null on any failure — caller falls back to in-process.
23
+ */
24
+ async function serverEmbed(
25
+ text: string,
26
+ prefix: string,
27
+ ): Promise<number[] | null> {
28
+ try {
29
+ const resp = await fetch(`${EMBED_SERVER}/embed`, {
30
+ method: "POST",
31
+ headers: { "Content-Type": "application/json" },
32
+ body: JSON.stringify({ text, prefix }),
33
+ signal: AbortSignal.timeout(500),
34
+ });
35
+ if (!resp.ok) return null;
36
+ const data = (await resp.json()) as {
37
+ embedding?: number[];
38
+ dims?: number;
39
+ };
40
+ if (
41
+ !Array.isArray(data.embedding) ||
42
+ data.embedding.length !== EMBEDDING_DIM
43
+ ) {
44
+ return null;
45
+ }
46
+ return data.embedding;
47
+ } catch {
48
+ return null; // Server not running or timed out — fall back silently
49
+ }
50
+ }
51
+
52
+ // ─── Types ───────────────────────────────────────────────────────────────────
53
+
16
54
  export interface SemanticResult {
17
55
  rowid: number;
18
56
  source: string;
@@ -76,6 +114,11 @@ async function getEmbeddingPipeline(): Promise<EmbeddingPipeline> {
76
114
  * @returns 768-dimensional embedding vector
77
115
  */
78
116
  export async function embedQuery(query: string): Promise<number[]> {
117
+ // Try persistent server first (~9ms warm vs 244ms in-process)
118
+ const serverResult = await serverEmbed(query, "search_query");
119
+ if (serverResult) return serverResult;
120
+
121
+ // Fall back to in-process model loading
79
122
  const embedder = await getEmbeddingPipeline();
80
123
 
81
124
  // nomic model requires "search_query: " prefix for queries
@@ -104,6 +147,11 @@ export async function embedQuery(query: string): Promise<number[]> {
104
147
  * @returns 768-dimensional embedding vector
105
148
  */
106
149
  export async function embedDocument(text: string): Promise<number[]> {
150
+ // Try persistent server first (~9ms warm vs 244ms in-process)
151
+ const serverResult = await serverEmbed(text, "search_document");
152
+ if (serverResult) return serverResult;
153
+
154
+ // Fall back to in-process model loading
107
155
  const embedder = await getEmbeddingPipeline();
108
156
 
109
157
  const prefixedText = `search_document: ${text}`;
@@ -131,12 +179,23 @@ export async function embedDocument(text: string): Promise<number[]> {
131
179
  export async function embedDocuments(texts: string[]): Promise<number[][]> {
132
180
  if (texts.length === 0) return [];
133
181
 
134
- const embedder = await getEmbeddingPipeline();
135
182
  const results: number[][] = [];
136
183
 
137
- // Process one at a time (transformers.js doesn't batch well)
138
- // But we benefit from cached pipeline
184
+ // Try persistent server first for each document
185
+ let serverAvailable = true;
139
186
  for (const text of texts) {
187
+ if (serverAvailable) {
188
+ const serverResult = await serverEmbed(text, "search_document");
189
+ if (serverResult) {
190
+ results.push(serverResult);
191
+ continue;
192
+ }
193
+ // Server failed — stop trying and fall back for remaining
194
+ serverAvailable = false;
195
+ }
196
+
197
+ // Fall back to in-process
198
+ const embedder = await getEmbeddingPipeline();
140
199
  const prefixedText = `search_document: ${text}`;
141
200
  const output = await embedder(prefixedText, {
142
201
  pooling: "mean",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voidwire/lore",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Unified knowledge CLI - Search, list, and capture your indexed knowledge",
5
5
  "type": "module",
6
6
  "main": "./index.ts",