@gmickel/gno 1.45.1 → 2.0.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.
- package/README.md +1 -1
- package/THIRD_PARTY_NOTICES.md +46 -0
- package/assets/skill/SKILL.md +7 -6
- package/assets/skill/cli-reference.md +14 -6
- package/assets/skill/mcp-reference.md +4 -1
- package/assets/spa-production.json.gz +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.0.0.zip +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.0.0.zip.sha256 +1 -0
- package/browser-extension/dist/chunk-4tc9v0ja.js +74 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/browser-extension/dist/preview.html +1 -1
- package/browser-extension/dist/service-worker.js +32 -33
- package/bunfig.toml +2 -0
- package/package.json +40 -26
- package/spec/cli.md +30 -11
- package/spec/db/schema.sql +146 -1
- package/spec/mcp.md +26 -0
- package/src/app/context-runtime-types.ts +3 -0
- package/src/app/context-runtime.ts +2 -0
- package/src/cli/commands/ask.ts +6 -1
- package/src/cli/commands/daemon.ts +21 -8
- package/src/cli/commands/embed.ts +77 -41
- package/src/cli/commands/mcp/install.ts +20 -0
- package/src/cli/commands/mcp/paths.ts +25 -0
- package/src/cli/commands/mcp/status.ts +6 -0
- package/src/cli/detach.ts +3 -2
- package/src/cli/program.ts +6 -0
- package/src/config/types.ts +3 -3
- package/src/converters/adapters/markitdownTs/adapter.ts +1 -2
- package/src/converters/adapters/officeparser/adapter.ts +1 -2
- package/src/converters/versions.ts +6 -8
- package/src/core/context-evidence.ts +8 -4
- package/src/core/job-manager.ts +95 -13
- package/src/core/network-boundary-inventory.ts +10 -0
- package/src/core/shutdown-budget.ts +45 -0
- package/src/embed/backlog.ts +107 -4
- package/src/embed/batch.ts +42 -2
- package/src/embed/fingerprint.ts +16 -0
- package/src/embed/retry.ts +113 -5
- package/src/embed/variant-backlog.ts +105 -0
- package/src/embed/variant-plan.ts +62 -0
- package/src/embed/variant-retry.ts +113 -0
- package/src/ingestion/graph-reconciliation.ts +327 -0
- package/src/ingestion/sync.ts +9 -272
- package/src/llm/http-inference.ts +6 -0
- package/src/llm/httpEmbedding.ts +37 -6
- package/src/llm/httpGeneration.ts +18 -3
- package/src/llm/httpRerank.ts +23 -5
- package/src/llm/inference-cancellation.ts +168 -0
- package/src/llm/inference-scope.ts +202 -0
- package/src/llm/lazy-ports.ts +115 -0
- package/src/llm/native-worker/client.ts +541 -0
- package/src/llm/native-worker/dispatcher.ts +228 -0
- package/src/llm/native-worker/embedding-identity.ts +33 -0
- package/src/llm/native-worker/entry.ts +173 -0
- package/src/llm/native-worker/errors.ts +32 -0
- package/src/llm/native-worker/evaluation.ts +16 -0
- package/src/llm/native-worker/owned-exit.ts +108 -0
- package/src/llm/native-worker/owner.ts +141 -0
- package/src/llm/native-worker/ports.ts +317 -0
- package/src/llm/native-worker/protocol.ts +442 -0
- package/src/llm/native-worker/runtime-config.ts +92 -0
- package/src/llm/nodeLlamaCpp/adapter.ts +77 -20
- package/src/llm/nodeLlamaCpp/embedding.ts +130 -46
- package/src/llm/nodeLlamaCpp/generation.ts +34 -5
- package/src/llm/nodeLlamaCpp/lifecycle-options.ts +99 -0
- package/src/llm/nodeLlamaCpp/lifecycle.ts +209 -204
- package/src/llm/nodeLlamaCpp/rerank-capacity.ts +111 -0
- package/src/llm/nodeLlamaCpp/rerank.ts +118 -27
- package/src/llm/nodeLlamaCpp/simulator-handle.ts +73 -0
- package/src/llm/nodeLlamaCpp/simulator-install.ts +124 -0
- package/src/llm/nodeLlamaCpp/simulator-session.ts +240 -0
- package/src/llm/nodeLlamaCpp/simulator-types.ts +80 -0
- package/src/llm/types.ts +35 -5
- package/src/mcp/context.ts +27 -0
- package/src/mcp/http-transport.ts +12 -10
- package/src/mcp/server.ts +3 -0
- package/src/mcp/tool-profile.ts +30 -8
- package/src/mcp/tools/context.ts +8 -11
- package/src/mcp/tools/embed.ts +1 -1
- package/src/mcp/tools/index-cmd.ts +1 -1
- package/src/mcp/tools/index.ts +10 -8
- package/src/mcp/tools/query.ts +14 -30
- package/src/mcp/tools/vsearch.ts +1 -1
- package/src/pipeline/answer.ts +23 -3
- package/src/pipeline/claim-verifier.ts +6 -0
- package/src/pipeline/expansion.ts +43 -40
- package/src/pipeline/explain.ts +6 -2
- package/src/pipeline/filters.ts +63 -0
- package/src/pipeline/fusion.ts +29 -9
- package/src/pipeline/graph-retrieval.ts +29 -9
- package/src/pipeline/hybrid.ts +198 -55
- package/src/pipeline/hydration.ts +161 -0
- package/src/pipeline/owner-fusion.ts +87 -0
- package/src/pipeline/rerank.ts +35 -11
- package/src/pipeline/search.ts +13 -2
- package/src/pipeline/types.ts +5 -3
- package/src/pipeline/vsearch.ts +87 -7
- package/src/sdk/client.ts +47 -3
- package/src/sdk/embed.ts +63 -39
- package/src/serve/background-runtime.ts +1 -1
- package/src/serve/context.ts +41 -56
- package/src/serve/embed-scheduler.ts +58 -35
- package/src/serve/public/components/IndexingProgress.tsx +46 -60
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/lib/shiki-language-ids.ts +14 -0
- package/src/serve/resident-admission.ts +36 -36
- package/src/serve/resident-background-work.ts +20 -2
- package/src/serve/resident-request.ts +11 -5
- package/src/serve/resident-runtime.ts +97 -61
- package/src/serve/resident-shutdown.ts +153 -0
- package/src/serve/routes/api.ts +3 -1
- package/src/serve/server.ts +47 -26
- package/src/store/migrations/028-vector-variants.ts +54 -0
- package/src/store/migrations/029-graph-reference-state.ts +77 -0
- package/src/store/migrations/index.ts +4 -0
- package/src/store/sqlite/adapter.ts +251 -183
- package/src/store/sqlite/eligibility.ts +174 -0
- package/src/store/sqlite/graph-edge-application.ts +66 -0
- package/src/store/sqlite/graph-reference-state.ts +194 -0
- package/src/store/sqlite/legacy-vector-ownership.ts +79 -0
- package/src/store/types.ts +80 -12
- package/src/store/vector/eligibility.ts +36 -0
- package/src/store/vector/freshness.ts +33 -6
- package/src/store/vector/lazy.ts +81 -0
- package/src/store/vector/sqlite-vec.ts +106 -54
- package/src/store/vector/stats.ts +14 -3
- package/src/store/vector/types.ts +35 -2
- package/src/store/vector/variant-search.ts +192 -0
- package/src/store/vector/variants.ts +451 -0
- package/vendor/converters/markitdown-ts/LICENSE +21 -0
- package/vendor/converters/markitdown-ts/dist/index.cjs +1180 -0
- package/vendor/converters/markitdown-ts/dist/index.d.cts +46 -0
- package/vendor/converters/markitdown-ts/dist/index.d.mts +46 -0
- package/vendor/converters/markitdown-ts/dist/index.d.ts +46 -0
- package/vendor/converters/markitdown-ts/dist/index.mjs +1152 -0
- package/vendor/converters/markitdown-ts/package.json +77 -0
- package/vendor/converters/officeparser/LICENSE +21 -0
- package/vendor/converters/officeparser/dist/OfficeConverter.d.ts +47 -0
- package/vendor/converters/officeparser/dist/OfficeConverter.js +76 -0
- package/vendor/converters/officeparser/dist/OfficeGenerator.d.ts +23 -0
- package/vendor/converters/officeparser/dist/OfficeGenerator.js +73 -0
- package/vendor/converters/officeparser/dist/OfficeParser.d.ts +106 -0
- package/vendor/converters/officeparser/dist/OfficeParser.js +332 -0
- package/vendor/converters/officeparser/dist/cli.d.ts +28 -0
- package/vendor/converters/officeparser/dist/cli.js +381 -0
- package/vendor/converters/officeparser/dist/defaults.d.ts +41 -0
- package/vendor/converters/officeparser/dist/defaults.js +218 -0
- package/vendor/converters/officeparser/dist/generators/BaseGenerator.d.ts +107 -0
- package/vendor/converters/officeparser/dist/generators/BaseGenerator.js +248 -0
- package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.d.ts +82 -0
- package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.js +797 -0
- package/vendor/converters/officeparser/dist/generators/CsvGenerator.d.ts +38 -0
- package/vendor/converters/officeparser/dist/generators/CsvGenerator.js +245 -0
- package/vendor/converters/officeparser/dist/generators/EpubGenerator.d.ts +43 -0
- package/vendor/converters/officeparser/dist/generators/EpubGenerator.js +315 -0
- package/vendor/converters/officeparser/dist/generators/HtmlGenerator.d.ts +59 -0
- package/vendor/converters/officeparser/dist/generators/HtmlGenerator.js +1942 -0
- package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.d.ts +96 -0
- package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.js +1175 -0
- package/vendor/converters/officeparser/dist/generators/PdfGenerator.d.ts +22 -0
- package/vendor/converters/officeparser/dist/generators/PdfGenerator.js +194 -0
- package/vendor/converters/officeparser/dist/generators/RtfGenerator.d.ts +29 -0
- package/vendor/converters/officeparser/dist/generators/RtfGenerator.js +316 -0
- package/vendor/converters/officeparser/dist/generators/TextGenerator.d.ts +13 -0
- package/vendor/converters/officeparser/dist/generators/TextGenerator.js +201 -0
- package/vendor/converters/officeparser/dist/index.d.ts +60 -0
- package/vendor/converters/officeparser/dist/index.js +72 -0
- package/vendor/converters/officeparser/dist/index.mjs +18 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.d.ts +2621 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.iife.js +1336 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.mjs +1335 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.slim.d.ts +2621 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.slim.iife.js +1336 -0
- package/vendor/converters/officeparser/dist/officeparser.browser.slim.mjs +1335 -0
- package/vendor/converters/officeparser/dist/parsers/CsvParser.d.ts +9 -0
- package/vendor/converters/officeparser/dist/parsers/CsvParser.js +115 -0
- package/vendor/converters/officeparser/dist/parsers/EpubParser.d.ts +8 -0
- package/vendor/converters/officeparser/dist/parsers/EpubParser.js +217 -0
- package/vendor/converters/officeparser/dist/parsers/ExcelParser.d.ts +32 -0
- package/vendor/converters/officeparser/dist/parsers/ExcelParser.js +736 -0
- package/vendor/converters/officeparser/dist/parsers/HtmlParser.d.ts +2 -0
- package/vendor/converters/officeparser/dist/parsers/HtmlParser.js +1287 -0
- package/vendor/converters/officeparser/dist/parsers/MarkdownParser.d.ts +2 -0
- package/vendor/converters/officeparser/dist/parsers/MarkdownParser.js +1272 -0
- package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.d.ts +31 -0
- package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.js +1819 -0
- package/vendor/converters/officeparser/dist/parsers/PdfParser.d.ts +67 -0
- package/vendor/converters/officeparser/dist/parsers/PdfParser.js +848 -0
- package/vendor/converters/officeparser/dist/parsers/PowerPointParser.d.ts +32 -0
- package/vendor/converters/officeparser/dist/parsers/PowerPointParser.js +950 -0
- package/vendor/converters/officeparser/dist/parsers/RtfParser.d.ts +187 -0
- package/vendor/converters/officeparser/dist/parsers/RtfParser.js +1801 -0
- package/vendor/converters/officeparser/dist/parsers/WordParser.d.ts +79 -0
- package/vendor/converters/officeparser/dist/parsers/WordParser.js +1177 -0
- package/vendor/converters/officeparser/dist/sbom.cdx.json +1763 -0
- package/vendor/converters/officeparser/dist/types.d.ts +2507 -0
- package/vendor/converters/officeparser/dist/types.js +107 -0
- package/vendor/converters/officeparser/dist/utils/astUtils.d.ts +16 -0
- package/vendor/converters/officeparser/dist/utils/astUtils.js +33 -0
- package/vendor/converters/officeparser/dist/utils/chartUtils.d.ts +6 -0
- package/vendor/converters/officeparser/dist/utils/chartUtils.js +257 -0
- package/vendor/converters/officeparser/dist/utils/configUtils.d.ts +44 -0
- package/vendor/converters/officeparser/dist/utils/configUtils.js +315 -0
- package/vendor/converters/officeparser/dist/utils/dateUtils.d.ts +17 -0
- package/vendor/converters/officeparser/dist/utils/dateUtils.js +69 -0
- package/vendor/converters/officeparser/dist/utils/envUtils.d.ts +29 -0
- package/vendor/converters/officeparser/dist/utils/envUtils.js +152 -0
- package/vendor/converters/officeparser/dist/utils/errorUtils.d.ts +72 -0
- package/vendor/converters/officeparser/dist/utils/errorUtils.js +245 -0
- package/vendor/converters/officeparser/dist/utils/imageUtils.d.ts +66 -0
- package/vendor/converters/officeparser/dist/utils/imageUtils.js +133 -0
- package/vendor/converters/officeparser/dist/utils/mathUtils.d.ts +42 -0
- package/vendor/converters/officeparser/dist/utils/mathUtils.js +385 -0
- package/vendor/converters/officeparser/dist/utils/moduleLoader.d.ts +18 -0
- package/vendor/converters/officeparser/dist/utils/moduleLoader.js +106 -0
- package/vendor/converters/officeparser/dist/utils/ocrUtils.d.ts +42 -0
- package/vendor/converters/officeparser/dist/utils/ocrUtils.js +428 -0
- package/vendor/converters/officeparser/dist/utils/sanitize.d.ts +148 -0
- package/vendor/converters/officeparser/dist/utils/sanitize.js +344 -0
- package/vendor/converters/officeparser/dist/utils/sheetUtils.d.ts +7 -0
- package/vendor/converters/officeparser/dist/utils/sheetUtils.js +35 -0
- package/vendor/converters/officeparser/dist/utils/styleMapper.d.ts +36 -0
- package/vendor/converters/officeparser/dist/utils/styleMapper.js +224 -0
- package/vendor/converters/officeparser/dist/utils/xmlUtils.d.ts +163 -0
- package/vendor/converters/officeparser/dist/utils/xmlUtils.js +461 -0
- package/vendor/converters/officeparser/dist/utils/zipUtils.d.ts +134 -0
- package/vendor/converters/officeparser/dist/utils/zipUtils.js +337 -0
- package/vendor/converters/officeparser/package.json +147 -0
- package/vendor/converters/upstream-manifest.json +124 -0
- package/vendor/dependency-fixes/README.md +77 -0
- package/vendor/dependency-fixes/vendor-converters.py +83 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.45.1.zip +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.45.1.zip.sha256 +0 -1
- package/browser-extension/dist/chunk-627emwpj.js +0 -75
- /package/browser-extension/dist/{chunk-ydfx5d7p.css → chunk-z74y8n8c.css} +0 -0
|
@@ -12,15 +12,42 @@ export function getStoredEmbeddingDimensions(
|
|
|
12
12
|
db: Database,
|
|
13
13
|
model: string
|
|
14
14
|
): number | undefined {
|
|
15
|
+
// Activated metadata supplies dimensions only, never query eligibility.
|
|
16
|
+
// The vector search still validates the actual inference identity and epoch.
|
|
17
|
+
const hasVariants = db
|
|
18
|
+
.prepare(
|
|
19
|
+
"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'vector_partitions'"
|
|
20
|
+
)
|
|
21
|
+
.get();
|
|
22
|
+
if (hasVariants) {
|
|
23
|
+
const partitions = db
|
|
24
|
+
.prepare(`SELECT dimensions FROM vector_partitions
|
|
25
|
+
WHERE model = ? AND state = 'active' AND activated_epoch IS NOT NULL`)
|
|
26
|
+
.all(model) as { dimensions: number }[];
|
|
27
|
+
if (partitions.length) {
|
|
28
|
+
const dimensions = partitions[0]?.dimensions;
|
|
29
|
+
return dimensions !== undefined &&
|
|
30
|
+
Number.isSafeInteger(dimensions) &&
|
|
31
|
+
dimensions > 0 &&
|
|
32
|
+
partitions.every((partition) => partition.dimensions === dimensions)
|
|
33
|
+
? dimensions
|
|
34
|
+
: undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Validate the entire model partition: never trust an arbitrary first blob.
|
|
15
38
|
const row = db
|
|
16
|
-
.prepare(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
39
|
+
.prepare(`SELECT MIN(length(embedding)) AS smallest,
|
|
40
|
+
MAX(length(embedding)) AS largest FROM content_vectors WHERE model = ?`)
|
|
41
|
+
.get(model) as { smallest: number | null; largest: number | null };
|
|
42
|
+
const bytes = row.smallest;
|
|
43
|
+
if (
|
|
44
|
+
!bytes ||
|
|
45
|
+
bytes !== row.largest ||
|
|
46
|
+
bytes % Float32Array.BYTES_PER_ELEMENT !== 0
|
|
47
|
+
) {
|
|
20
48
|
return undefined;
|
|
21
49
|
}
|
|
22
|
-
|
|
23
|
-
return row.embedding.byteLength / Float32Array.BYTES_PER_ELEMENT;
|
|
50
|
+
return bytes / Float32Array.BYTES_PER_ELEMENT;
|
|
24
51
|
}
|
|
25
52
|
|
|
26
53
|
export function getStoredEmbeddingFingerprint(
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/** Native-free startup; dimensions are discovered only when vector work needs them. */
|
|
2
|
+
import type { Database } from "bun:sqlite";
|
|
3
|
+
|
|
4
|
+
import type { EmbeddingPort } from "../../llm/types";
|
|
5
|
+
import type { VectorIndexPort } from "./types";
|
|
6
|
+
|
|
7
|
+
import { getStoredEmbeddingDimensions } from "./freshness";
|
|
8
|
+
import { createVectorIndexPort } from "./sqlite-vec";
|
|
9
|
+
|
|
10
|
+
export async function createLazyVectorIndex(
|
|
11
|
+
db: Database,
|
|
12
|
+
model: string,
|
|
13
|
+
embedPort: EmbeddingPort
|
|
14
|
+
): Promise<VectorIndexPort> {
|
|
15
|
+
const stored = getStoredEmbeddingDimensions(db, model);
|
|
16
|
+
let current: VectorIndexPort | undefined;
|
|
17
|
+
let loading: Promise<VectorIndexPort> | undefined;
|
|
18
|
+
let dirty = false;
|
|
19
|
+
const create = async (dimensions: number): Promise<VectorIndexPort> => {
|
|
20
|
+
const result = await createVectorIndexPort(db, { model, dimensions });
|
|
21
|
+
if (!result.ok) throw new Error(result.error.message);
|
|
22
|
+
current = result.value;
|
|
23
|
+
current.vecDirty ||= dirty;
|
|
24
|
+
return current;
|
|
25
|
+
};
|
|
26
|
+
if (stored !== undefined) return create(stored);
|
|
27
|
+
const get = (): Promise<VectorIndexPort> => {
|
|
28
|
+
if (current) return Promise.resolve(current);
|
|
29
|
+
loading ??= (async () => {
|
|
30
|
+
const result = await embedPort.init();
|
|
31
|
+
if (!result.ok) throw new Error(result.error.message);
|
|
32
|
+
return create(embedPort.dimensions());
|
|
33
|
+
})().finally(() => {
|
|
34
|
+
loading = undefined;
|
|
35
|
+
});
|
|
36
|
+
return loading;
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
model,
|
|
40
|
+
get dimensions() {
|
|
41
|
+
return current?.dimensions ?? embedPort.dimensions();
|
|
42
|
+
},
|
|
43
|
+
get searchAvailable() {
|
|
44
|
+
return current?.searchAvailable ?? true;
|
|
45
|
+
},
|
|
46
|
+
get loadError() {
|
|
47
|
+
return current?.loadError;
|
|
48
|
+
},
|
|
49
|
+
get guidance() {
|
|
50
|
+
return current?.guidance;
|
|
51
|
+
},
|
|
52
|
+
get vecDirty() {
|
|
53
|
+
return current?.vecDirty ?? dirty;
|
|
54
|
+
},
|
|
55
|
+
set vecDirty(value) {
|
|
56
|
+
dirty = value;
|
|
57
|
+
if (current) current.vecDirty = value;
|
|
58
|
+
},
|
|
59
|
+
async upsertVectors(rows) {
|
|
60
|
+
return (await get()).upsertVectors(rows);
|
|
61
|
+
},
|
|
62
|
+
async upsertVectorsChecked(rows, checkpoint) {
|
|
63
|
+
const port = await get();
|
|
64
|
+
if (!port.upsertVectorsChecked)
|
|
65
|
+
throw new Error("Atomic vector checkpoint unavailable");
|
|
66
|
+
return port.upsertVectorsChecked(rows, checkpoint);
|
|
67
|
+
},
|
|
68
|
+
async deleteVectorsForMirror(hash) {
|
|
69
|
+
return (await get()).deleteVectorsForMirror(hash);
|
|
70
|
+
},
|
|
71
|
+
async searchNearest(embedding, k, options) {
|
|
72
|
+
return (await get()).searchNearest(embedding, k, options);
|
|
73
|
+
},
|
|
74
|
+
async rebuildVecIndex() {
|
|
75
|
+
return (await get()).rebuildVecIndex();
|
|
76
|
+
},
|
|
77
|
+
async syncVecIndex() {
|
|
78
|
+
return (await get()).syncVecIndex();
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -10,9 +10,16 @@ import type { Database } from "bun:sqlite";
|
|
|
10
10
|
import { createHash } from "node:crypto";
|
|
11
11
|
|
|
12
12
|
import type { StoreResult } from "../types";
|
|
13
|
-
import type {
|
|
13
|
+
import type {
|
|
14
|
+
VectorIndexPort,
|
|
15
|
+
VectorRow,
|
|
16
|
+
VectorSearchOptions,
|
|
17
|
+
VectorSearchResult,
|
|
18
|
+
} from "./types";
|
|
14
19
|
|
|
15
20
|
import { err, ok } from "../types";
|
|
21
|
+
import { buildEligibleVectorQuery } from "./eligibility";
|
|
22
|
+
import { searchVectorVariants } from "./variant-search";
|
|
16
23
|
|
|
17
24
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
18
25
|
// BLOB Encoding Helpers (avoid Buffer.buffer footgun)
|
|
@@ -162,6 +169,61 @@ export async function createVectorIndexPort(
|
|
|
162
169
|
let vecDirty = false;
|
|
163
170
|
let vecErrorLogged = false; // Rate-limit warning to once per run
|
|
164
171
|
|
|
172
|
+
function upsert(
|
|
173
|
+
rows: VectorRow[],
|
|
174
|
+
checkpoint?: (rows: VectorRow[]) => VectorRow[]
|
|
175
|
+
): Promise<StoreResult<number>> {
|
|
176
|
+
// 1. Always store in content_vectors first (critical path)
|
|
177
|
+
try {
|
|
178
|
+
db.transaction(() => {
|
|
179
|
+
rows = checkpoint?.(rows) ?? rows;
|
|
180
|
+
for (const row of rows) {
|
|
181
|
+
upsertVectorStmt.run(
|
|
182
|
+
row.mirrorHash,
|
|
183
|
+
row.seq,
|
|
184
|
+
row.model,
|
|
185
|
+
row.embedFingerprint,
|
|
186
|
+
encodeEmbedding(row.embedding)
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
})();
|
|
190
|
+
} catch (e) {
|
|
191
|
+
return Promise.resolve(
|
|
192
|
+
err(
|
|
193
|
+
"VECTOR_WRITE_FAILED",
|
|
194
|
+
`Vector write failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
195
|
+
e
|
|
196
|
+
)
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 2. Best-effort update vec0 (graceful degradation)
|
|
201
|
+
if (deleteVecChunkStmt && insertVecStmt) {
|
|
202
|
+
try {
|
|
203
|
+
db.transaction(() => {
|
|
204
|
+
for (const row of rows) {
|
|
205
|
+
const chunkId = `${row.mirrorHash}:${row.seq}`;
|
|
206
|
+
// sqlite-vec vec0 tables do not reliably support OR REPLACE semantics.
|
|
207
|
+
// Delete first, then insert the fresh vector row.
|
|
208
|
+
deleteVecChunkStmt.run(chunkId);
|
|
209
|
+
insertVecStmt.run(chunkId, encodeEmbedding(row.embedding));
|
|
210
|
+
}
|
|
211
|
+
})();
|
|
212
|
+
} catch (e) {
|
|
213
|
+
// Vec0 write failed - storage succeeded, search needs sync
|
|
214
|
+
vecDirty = true;
|
|
215
|
+
if (!vecErrorLogged) {
|
|
216
|
+
vecErrorLogged = true;
|
|
217
|
+
console.warn(
|
|
218
|
+
`[vec] Index write failed, will sync after embed: ${e instanceof Error ? e.message : String(e)}`
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return Promise.resolve(ok(rows.length));
|
|
225
|
+
}
|
|
226
|
+
|
|
165
227
|
return ok({
|
|
166
228
|
searchAvailable,
|
|
167
229
|
model,
|
|
@@ -175,56 +237,12 @@ export async function createVectorIndexPort(
|
|
|
175
237
|
vecDirty = v;
|
|
176
238
|
},
|
|
177
239
|
|
|
178
|
-
upsertVectors(rows
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
for (const row of rows) {
|
|
183
|
-
upsertVectorStmt.run(
|
|
184
|
-
row.mirrorHash,
|
|
185
|
-
row.seq,
|
|
186
|
-
row.model,
|
|
187
|
-
row.embedFingerprint,
|
|
188
|
-
encodeEmbedding(row.embedding)
|
|
189
|
-
);
|
|
190
|
-
}
|
|
191
|
-
})();
|
|
192
|
-
} catch (e) {
|
|
193
|
-
return Promise.resolve(
|
|
194
|
-
err(
|
|
195
|
-
"VECTOR_WRITE_FAILED",
|
|
196
|
-
`Vector write failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
197
|
-
e
|
|
198
|
-
)
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// 2. Best-effort update vec0 (graceful degradation)
|
|
203
|
-
if (deleteVecChunkStmt && insertVecStmt) {
|
|
204
|
-
try {
|
|
205
|
-
db.transaction(() => {
|
|
206
|
-
for (const row of rows) {
|
|
207
|
-
const chunkId = `${row.mirrorHash}:${row.seq}`;
|
|
208
|
-
// sqlite-vec vec0 tables do not reliably support OR REPLACE semantics.
|
|
209
|
-
// Delete first, then insert the fresh vector row.
|
|
210
|
-
deleteVecChunkStmt.run(chunkId);
|
|
211
|
-
insertVecStmt.run(chunkId, encodeEmbedding(row.embedding));
|
|
212
|
-
}
|
|
213
|
-
})();
|
|
214
|
-
} catch (e) {
|
|
215
|
-
// Vec0 write failed - storage succeeded, search needs sync
|
|
216
|
-
vecDirty = true;
|
|
217
|
-
if (!vecErrorLogged) {
|
|
218
|
-
vecErrorLogged = true;
|
|
219
|
-
console.warn(
|
|
220
|
-
`[vec] Index write failed, will sync after embed: ${e instanceof Error ? e.message : String(e)}`
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
return Promise.resolve(ok(undefined));
|
|
240
|
+
upsertVectors(rows) {
|
|
241
|
+
return upsert(rows).then((result) =>
|
|
242
|
+
result.ok ? ok(undefined) : result
|
|
243
|
+
);
|
|
227
244
|
},
|
|
245
|
+
upsertVectorsChecked: upsert,
|
|
228
246
|
|
|
229
247
|
deleteVectorsForMirror(mirrorHash: string): Promise<StoreResult<void>> {
|
|
230
248
|
// 1. Always delete from content_vectors first
|
|
@@ -254,10 +272,7 @@ export async function createVectorIndexPort(
|
|
|
254
272
|
searchNearest(
|
|
255
273
|
embedding: Float32Array,
|
|
256
274
|
k: number,
|
|
257
|
-
searchOptions?:
|
|
258
|
-
minScore?: number;
|
|
259
|
-
allowedMirrorHashes?: string[];
|
|
260
|
-
}
|
|
275
|
+
searchOptions?: VectorSearchOptions
|
|
261
276
|
): Promise<StoreResult<VectorSearchResult[]>> {
|
|
262
277
|
if (!(searchAvailable && searchStmt)) {
|
|
263
278
|
return Promise.resolve(
|
|
@@ -271,6 +286,43 @@ export async function createVectorIndexPort(
|
|
|
271
286
|
}
|
|
272
287
|
|
|
273
288
|
try {
|
|
289
|
+
const variants = searchVectorVariants(
|
|
290
|
+
db,
|
|
291
|
+
model,
|
|
292
|
+
dimensions,
|
|
293
|
+
embedding,
|
|
294
|
+
k,
|
|
295
|
+
searchOptions
|
|
296
|
+
);
|
|
297
|
+
if (variants !== null) return Promise.resolve(ok(variants));
|
|
298
|
+
if (searchOptions?.eligibility) {
|
|
299
|
+
const eligible = buildEligibleVectorQuery(db, searchOptions);
|
|
300
|
+
const distanceFunction =
|
|
301
|
+
distanceMetric === "cosine"
|
|
302
|
+
? "vec_distance_cosine"
|
|
303
|
+
: "vec_distance_L2";
|
|
304
|
+
const rows = db
|
|
305
|
+
.query<
|
|
306
|
+
{ mirrorHash: string; seq: number; distance: number },
|
|
307
|
+
(Uint8Array | string | number)[]
|
|
308
|
+
>(`
|
|
309
|
+
SELECT v.mirror_hash AS mirrorHash, v.seq,
|
|
310
|
+
${distanceFunction}(v.embedding, ?) AS distance
|
|
311
|
+
FROM content_vectors v
|
|
312
|
+
WHERE v.model = ? AND EXISTS (${eligible.sql})
|
|
313
|
+
ORDER BY distance, v.mirror_hash, v.seq LIMIT ?
|
|
314
|
+
`)
|
|
315
|
+
.all(encodeEmbedding(embedding), model, ...eligible.params, k);
|
|
316
|
+
return Promise.resolve(
|
|
317
|
+
ok(
|
|
318
|
+
rows.filter(
|
|
319
|
+
(row) =>
|
|
320
|
+
searchOptions.minScore === undefined ||
|
|
321
|
+
1 - row.distance >= searchOptions.minScore
|
|
322
|
+
)
|
|
323
|
+
)
|
|
324
|
+
);
|
|
325
|
+
}
|
|
274
326
|
const allowed = searchOptions?.allowedMirrorHashes;
|
|
275
327
|
if (allowed) {
|
|
276
328
|
const hashes = [
|
|
@@ -12,6 +12,15 @@ import type { BacklogItem, VectorStatsPort } from "./types";
|
|
|
12
12
|
|
|
13
13
|
import { err, ok } from "../types";
|
|
14
14
|
|
|
15
|
+
const statsDatabases = new WeakMap<VectorStatsPort, Database>();
|
|
16
|
+
|
|
17
|
+
/** Internal bridge for exact-input backlog; mock/third-party stats have no database. */
|
|
18
|
+
export function getVectorStatsDatabase(
|
|
19
|
+
stats: VectorStatsPort
|
|
20
|
+
): Database | undefined {
|
|
21
|
+
return statsDatabases.get(stats);
|
|
22
|
+
}
|
|
23
|
+
|
|
15
24
|
/**
|
|
16
25
|
* Create a VectorStatsPort for backlog detection and vector stats.
|
|
17
26
|
* Uses EXISTS-based queries to avoid duplicates from multiple docs sharing mirror_hash.
|
|
@@ -44,7 +53,7 @@ export function createVectorStatsPort(db: Database): VectorStatsPort {
|
|
|
44
53
|
};
|
|
45
54
|
}
|
|
46
55
|
|
|
47
|
-
|
|
56
|
+
const port: VectorStatsPort = {
|
|
48
57
|
countVectors(model: string): Promise<StoreResult<number>> {
|
|
49
58
|
try {
|
|
50
59
|
const result = db
|
|
@@ -121,7 +130,7 @@ export function createVectorStatsPort(db: Database): VectorStatsPort {
|
|
|
121
130
|
const sql = after
|
|
122
131
|
? `
|
|
123
132
|
SELECT c.mirror_hash as mirrorHash, c.seq, c.text,
|
|
124
|
-
(SELECT d.title FROM documents d WHERE d.mirror_hash = c.mirror_hash AND d.active = 1 LIMIT 1) as title,
|
|
133
|
+
(SELECT d.title FROM documents d WHERE d.mirror_hash = c.mirror_hash AND d.active = 1 ORDER BY d.id LIMIT 1) as title,
|
|
125
134
|
CASE
|
|
126
135
|
WHEN NOT EXISTS (
|
|
127
136
|
SELECT 1 FROM content_vectors v
|
|
@@ -148,7 +157,7 @@ export function createVectorStatsPort(db: Database): VectorStatsPort {
|
|
|
148
157
|
`
|
|
149
158
|
: `
|
|
150
159
|
SELECT c.mirror_hash as mirrorHash, c.seq, c.text,
|
|
151
|
-
(SELECT d.title FROM documents d WHERE d.mirror_hash = c.mirror_hash AND d.active = 1 LIMIT 1) as title,
|
|
160
|
+
(SELECT d.title FROM documents d WHERE d.mirror_hash = c.mirror_hash AND d.active = 1 ORDER BY d.id LIMIT 1) as title,
|
|
152
161
|
CASE
|
|
153
162
|
WHEN NOT EXISTS (
|
|
154
163
|
SELECT 1 FROM content_vectors v
|
|
@@ -206,4 +215,6 @@ export function createVectorStatsPort(db: Database): VectorStatsPort {
|
|
|
206
215
|
}
|
|
207
216
|
},
|
|
208
217
|
};
|
|
218
|
+
statsDatabases.set(port, db);
|
|
219
|
+
return port;
|
|
209
220
|
}
|
|
@@ -5,7 +5,25 @@
|
|
|
5
5
|
* @module src/store/vector/types
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { StoreResult } from "../types";
|
|
8
|
+
import type { DocumentEligibilityOptions, StoreResult } from "../types";
|
|
9
|
+
|
|
10
|
+
/** Full effective embedding identity, including truncation/runtime policy. */
|
|
11
|
+
export interface VectorVariantIdentity {
|
|
12
|
+
model: string;
|
|
13
|
+
modelFingerprint: string;
|
|
14
|
+
contextSize: number;
|
|
15
|
+
truncationPolicy: string;
|
|
16
|
+
dimensions: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Snapshot carried through asynchronous embedding; write revalidates input. */
|
|
20
|
+
export interface VectorOwnerInput {
|
|
21
|
+
documentId: number;
|
|
22
|
+
mirrorHash: string;
|
|
23
|
+
seq: number;
|
|
24
|
+
formattedInput: string;
|
|
25
|
+
inputHash: string;
|
|
26
|
+
}
|
|
9
27
|
|
|
10
28
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
11
29
|
// Row Types
|
|
@@ -21,8 +39,18 @@ export interface VectorRow {
|
|
|
21
39
|
// embeddedAt is set by DB via datetime('now')
|
|
22
40
|
}
|
|
23
41
|
|
|
42
|
+
export interface VectorSearchOptions {
|
|
43
|
+
embeddingIdentity?: VectorVariantIdentity;
|
|
44
|
+
minScore?: number;
|
|
45
|
+
allowedMirrorHashes?: string[];
|
|
46
|
+
/** Exact active owner/chunk domain, applied before the nearest-neighbor budget. */
|
|
47
|
+
eligibility?: DocumentEligibilityOptions & { language?: string };
|
|
48
|
+
}
|
|
49
|
+
|
|
24
50
|
/** Vector search result */
|
|
25
51
|
export interface VectorSearchResult {
|
|
52
|
+
/** Exact current owners for variant hits; undefined preserves legacy semantics. */
|
|
53
|
+
documentIds?: number[];
|
|
26
54
|
mirrorHash: string;
|
|
27
55
|
seq: number;
|
|
28
56
|
distance: number;
|
|
@@ -72,6 +100,11 @@ export interface VectorIndexPort {
|
|
|
72
100
|
|
|
73
101
|
/** Upsert vectors into storage and vec index */
|
|
74
102
|
upsertVectors(rows: VectorRow[]): Promise<StoreResult<void>>;
|
|
103
|
+
/** Internal checkpoint validation inside the storage transaction; returns committed rows. */
|
|
104
|
+
upsertVectorsChecked?(
|
|
105
|
+
rows: VectorRow[],
|
|
106
|
+
checkpoint: (rows: VectorRow[]) => VectorRow[]
|
|
107
|
+
): Promise<StoreResult<number>>;
|
|
75
108
|
|
|
76
109
|
/** Delete all vectors for a mirror hash (for this model) */
|
|
77
110
|
deleteVectorsForMirror(mirrorHash: string): Promise<StoreResult<void>>;
|
|
@@ -84,7 +117,7 @@ export interface VectorIndexPort {
|
|
|
84
117
|
searchNearest(
|
|
85
118
|
embedding: Float32Array,
|
|
86
119
|
k: number,
|
|
87
|
-
options?:
|
|
120
|
+
options?: VectorSearchOptions
|
|
88
121
|
): Promise<StoreResult<VectorSearchResult[]>>;
|
|
89
122
|
|
|
90
123
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite";
|
|
2
|
+
|
|
3
|
+
import type { EmbeddingPort } from "../../llm/types";
|
|
4
|
+
import type {
|
|
5
|
+
VectorSearchOptions,
|
|
6
|
+
VectorSearchResult,
|
|
7
|
+
VectorVariantIdentity,
|
|
8
|
+
} from "./types";
|
|
9
|
+
|
|
10
|
+
import { getVariantModelFingerprint } from "../../embed/fingerprint";
|
|
11
|
+
import { formatDocForEmbedding } from "../../pipeline/contextual";
|
|
12
|
+
import { buildEligibleDocumentQuery } from "../sqlite/eligibility";
|
|
13
|
+
import { encodeEmbedding } from "./sqlite-vec";
|
|
14
|
+
import { embeddingInputHash, vectorVariantFingerprint } from "./variants";
|
|
15
|
+
|
|
16
|
+
/** Call after query embedding initialized the port; never infer runtime policy. */
|
|
17
|
+
export function resolveVectorSearchIdentity(
|
|
18
|
+
port: EmbeddingPort
|
|
19
|
+
): VectorVariantIdentity | undefined {
|
|
20
|
+
const identity = port.getIdentity?.();
|
|
21
|
+
if (!identity) return undefined;
|
|
22
|
+
const dimensions = port.dimensions();
|
|
23
|
+
return {
|
|
24
|
+
model: port.modelUri,
|
|
25
|
+
modelFingerprint: getVariantModelFingerprint(
|
|
26
|
+
{ modelUri: port.modelUri, dimensions },
|
|
27
|
+
identity
|
|
28
|
+
),
|
|
29
|
+
contextSize: identity.contextSize,
|
|
30
|
+
truncationPolicy: identity.truncationPolicy,
|
|
31
|
+
dimensions,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Null means legacy authority. Once promoted, unavailable provenance fails closed. */
|
|
36
|
+
function searchVectorVariantsInSnapshot(
|
|
37
|
+
db: Database,
|
|
38
|
+
model: string,
|
|
39
|
+
dimensions: number,
|
|
40
|
+
embedding: Float32Array,
|
|
41
|
+
k: number,
|
|
42
|
+
options: VectorSearchOptions = {}
|
|
43
|
+
): VectorSearchResult[] | null {
|
|
44
|
+
if (
|
|
45
|
+
!db
|
|
46
|
+
.query("SELECT 1 FROM sqlite_master WHERE name = 'vector_partitions'")
|
|
47
|
+
.get()
|
|
48
|
+
)
|
|
49
|
+
return null;
|
|
50
|
+
const activated = db
|
|
51
|
+
.query(
|
|
52
|
+
"SELECT 1 FROM vector_partitions WHERE model = ? AND state = 'active' AND activated_epoch IS NOT NULL LIMIT 1"
|
|
53
|
+
)
|
|
54
|
+
.get(model);
|
|
55
|
+
const identity = options.embeddingIdentity;
|
|
56
|
+
if (!identity) {
|
|
57
|
+
if (activated)
|
|
58
|
+
throw new Error(
|
|
59
|
+
"Effective embedding identity unavailable after variant activation"
|
|
60
|
+
);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
if (
|
|
64
|
+
identity.model !== model ||
|
|
65
|
+
identity.dimensions !== dimensions ||
|
|
66
|
+
embedding.length !== dimensions
|
|
67
|
+
)
|
|
68
|
+
throw new Error("Query embedding identity does not match vector index");
|
|
69
|
+
const partitionId = embeddingInputHash(
|
|
70
|
+
JSON.stringify([model, vectorVariantFingerprint(identity), dimensions])
|
|
71
|
+
);
|
|
72
|
+
const partition = db
|
|
73
|
+
.query<{ state: string; activated_epoch: number | null }, [string]>(
|
|
74
|
+
"SELECT state, activated_epoch FROM vector_partitions WHERE partition_id = ?"
|
|
75
|
+
)
|
|
76
|
+
.get(partitionId);
|
|
77
|
+
if (partition?.state !== "active" || partition.activated_epoch === null) {
|
|
78
|
+
if (activated)
|
|
79
|
+
throw new Error(
|
|
80
|
+
"Selected embedding variant partition has not activated; run gno embed"
|
|
81
|
+
);
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const tableName = `vec_v1_${partitionId}`;
|
|
85
|
+
if (!db.query("SELECT 1 FROM sqlite_master WHERE name = ?").get(tableName))
|
|
86
|
+
throw new Error("Activated variant index unavailable; run gno embed");
|
|
87
|
+
if (
|
|
88
|
+
db
|
|
89
|
+
.query(
|
|
90
|
+
`SELECT 1 FROM vector_variants v LEFT JOIN ${tableName} x ON x.variant_id = v.variant_id WHERE v.partition_id = ? AND (x.variant_id IS NULL OR x.embedding != v.embedding) LIMIT 1`
|
|
91
|
+
)
|
|
92
|
+
.get(partitionId)
|
|
93
|
+
)
|
|
94
|
+
throw new Error("Activated variant index inconsistent; run gno embed");
|
|
95
|
+
const eligible = buildEligibleDocumentQuery(
|
|
96
|
+
{ ...options.eligibility, semanticMetadata: true },
|
|
97
|
+
db
|
|
98
|
+
);
|
|
99
|
+
const conditions = ["o.partition_id = ?"];
|
|
100
|
+
const params: (string | number)[] = [partitionId];
|
|
101
|
+
if (options.eligibility?.language) {
|
|
102
|
+
conditions.push("c.language = ?");
|
|
103
|
+
params.push(options.eligibility.language);
|
|
104
|
+
}
|
|
105
|
+
if (options.allowedMirrorHashes !== undefined) {
|
|
106
|
+
conditions.push("o.mirror_hash IN (SELECT value FROM json_each(?))");
|
|
107
|
+
params.push(JSON.stringify(options.allowedMirrorHashes));
|
|
108
|
+
}
|
|
109
|
+
const bindings = db
|
|
110
|
+
.query<
|
|
111
|
+
{
|
|
112
|
+
document_id: number;
|
|
113
|
+
seq: number;
|
|
114
|
+
text: string;
|
|
115
|
+
title: string | null;
|
|
116
|
+
input_hash: string;
|
|
117
|
+
},
|
|
118
|
+
(string | number)[]
|
|
119
|
+
>(`
|
|
120
|
+
SELECT o.document_id, o.seq, c.text, d.title, v.input_hash
|
|
121
|
+
FROM vector_owners o
|
|
122
|
+
JOIN (${eligible.sql}) e ON e.id = o.document_id AND e.mirror_hash = o.mirror_hash
|
|
123
|
+
JOIN documents d ON d.id = o.document_id
|
|
124
|
+
JOIN content_chunks c ON c.mirror_hash = o.mirror_hash AND c.seq = o.seq
|
|
125
|
+
JOIN vector_variants v ON v.variant_id = o.variant_id AND v.partition_id = o.partition_id
|
|
126
|
+
WHERE ${conditions.join(" AND ")}
|
|
127
|
+
`)
|
|
128
|
+
.all(...eligible.params, ...params);
|
|
129
|
+
const proven = bindings
|
|
130
|
+
.filter(
|
|
131
|
+
(row) =>
|
|
132
|
+
row.input_hash ===
|
|
133
|
+
embeddingInputHash(
|
|
134
|
+
formatDocForEmbedding(row.text, row.title ?? undefined, model)
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
.map((row) => `${row.document_id}:${row.seq}`);
|
|
138
|
+
// Both owner eligibility and formatted-input validation precede ranking/LIMIT.
|
|
139
|
+
const rows = db
|
|
140
|
+
.query<
|
|
141
|
+
{
|
|
142
|
+
mirrorHash: string;
|
|
143
|
+
seq: number;
|
|
144
|
+
distance: number;
|
|
145
|
+
documentIds: string;
|
|
146
|
+
},
|
|
147
|
+
(Uint8Array | string | number)[]
|
|
148
|
+
>(`
|
|
149
|
+
SELECT o.mirror_hash AS mirrorHash, o.seq, vec_distance_cosine(v.embedding, ?) AS distance,
|
|
150
|
+
json_group_array(o.document_id) AS documentIds
|
|
151
|
+
FROM vector_owners o
|
|
152
|
+
JOIN (${eligible.sql}) e ON e.id = o.document_id AND e.mirror_hash = o.mirror_hash
|
|
153
|
+
JOIN documents d ON d.id = o.document_id
|
|
154
|
+
JOIN content_chunks c ON c.mirror_hash = o.mirror_hash AND c.seq = o.seq
|
|
155
|
+
JOIN vector_variants v ON v.variant_id = o.variant_id AND v.partition_id = o.partition_id
|
|
156
|
+
JOIN ${tableName} x ON x.variant_id = v.variant_id AND x.embedding = v.embedding
|
|
157
|
+
WHERE ${conditions.join(" AND ")}
|
|
158
|
+
AND (o.document_id || ':' || o.seq) IN (SELECT value FROM json_each(?))
|
|
159
|
+
GROUP BY v.variant_id, o.mirror_hash, o.seq
|
|
160
|
+
ORDER BY distance, o.mirror_hash, o.seq, v.variant_id LIMIT ?
|
|
161
|
+
`)
|
|
162
|
+
.all(
|
|
163
|
+
encodeEmbedding(embedding),
|
|
164
|
+
...eligible.params,
|
|
165
|
+
...params,
|
|
166
|
+
JSON.stringify(proven),
|
|
167
|
+
k
|
|
168
|
+
);
|
|
169
|
+
return rows
|
|
170
|
+
.filter(
|
|
171
|
+
(row) =>
|
|
172
|
+
options.minScore === undefined || 1 - row.distance >= options.minScore
|
|
173
|
+
)
|
|
174
|
+
.map((row) => ({
|
|
175
|
+
...row,
|
|
176
|
+
documentIds: JSON.parse(row.documentIds) as number[],
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Keep eligibility, provenance validation and ranking on one SQLite snapshot. */
|
|
181
|
+
export function searchVectorVariants(
|
|
182
|
+
db: Database,
|
|
183
|
+
model: string,
|
|
184
|
+
dimensions: number,
|
|
185
|
+
embedding: Float32Array,
|
|
186
|
+
k: number,
|
|
187
|
+
options: VectorSearchOptions = {}
|
|
188
|
+
): VectorSearchResult[] | null {
|
|
189
|
+
return db.transaction(() =>
|
|
190
|
+
searchVectorVariantsInSnapshot(db, model, dimensions, embedding, k, options)
|
|
191
|
+
)();
|
|
192
|
+
}
|