enigma-memory 0.1.17 → 0.1.22
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 +85 -27
- package/apps/cli/bin/enigma-desktop.mjs +140 -0
- package/apps/cli/bin/enigma-terminal.mjs +78 -0
- package/apps/cli/bin/enigma.mjs +4979 -3263
- package/apps/desktop/electron-main.cjs +217 -0
- package/apps/desktop/package.json +12 -0
- package/apps/desktop/src/app.js +264 -7
- package/apps/desktop/src/index.html +3514 -1373
- package/apps/desktop/src/launch-electron.mjs +51 -0
- package/apps/desktop/src/server.mjs +2914 -0
- package/apps/desktop/src/styles.css +2972 -260
- package/apps/desktop/src/zk-browser-prove.mjs +53 -0
- package/apps/desktop/src/zk-state.mjs +1789 -0
- package/apps/gateway/bin/enigma-gateway.mjs +102 -5
- package/apps/gateway/src/server.mjs +271 -8
- package/apps/ios/EnigmaCore/Package.swift +12 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/EnigmaAPIClient.swift +227 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/Models.swift +278 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/PKCE.swift +96 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/PrivacyMinimizer.swift +187 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/ToolModels.swift +129 -0
- package/apps/ios/EnigmaCore/Tests/EnigmaCoreTests/EnigmaCoreTests.swift +42 -0
- package/apps/ios/EnigmaIOS/Enigma/AppModel.swift +346 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AccentColor.colorset/Contents.json +12 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AppIcon.appiconset/Contents.json +11 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AppIcon.appiconset/EnigmaAppIcon.png +0 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/Contents.json +3 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/LaunchBackground.colorset/Contents.json +12 -0
- package/apps/ios/EnigmaIOS/Enigma/ChatView.swift +181 -0
- package/apps/ios/EnigmaIOS/Enigma/CouncilView.swift +78 -0
- package/apps/ios/EnigmaIOS/Enigma/CreateView.swift +152 -0
- package/apps/ios/EnigmaIOS/Enigma/EnigmaApp.swift +52 -0
- package/apps/ios/EnigmaIOS/Enigma/Info.plist +52 -0
- package/apps/ios/EnigmaIOS/Enigma/NaturalLanguagePrivacyTagger.swift +26 -0
- package/apps/ios/EnigmaIOS/Enigma/OAuthClient.swift +321 -0
- package/apps/ios/EnigmaIOS/Enigma/OnboardingView.swift +105 -0
- package/apps/ios/EnigmaIOS/Enigma/PrivateVaultView.swift +275 -0
- package/apps/ios/EnigmaIOS/Enigma/SecureStore.swift +76 -0
- package/apps/ios/EnigmaIOS/Enigma/SettingsView.swift +60 -0
- package/apps/ios/EnigmaIOS/Enigma/Theme.swift +80 -0
- package/apps/ios/EnigmaIOS/EnigmaIOS.xcodeproj/project.pbxproj +211 -0
- package/apps/ios/EnigmaIOS/EnigmaIOS.xcodeproj/xcshareddata/xcschemes/Enigma.xcscheme +23 -0
- package/apps/native-host/README.md +19 -8
- package/apps/native-host/bin/enigma-native-host.mjs +229 -13
- package/apps/relay/bin/enigma-relay.mjs +103 -5
- package/apps/relay/src/federation-runtime.mjs +618 -0
- package/apps/relay/src/server.mjs +310 -9
- package/apps/verifier/bin/enigma-verify.mjs +327 -11
- package/cortex-v3/circuits/build/intent_vk_bytes.json +35 -0
- package/cortex-v3/circuits/build/sale_vk_bytes.json +35 -0
- package/cortex-v3/circuits/build/vk_bytes.json +32 -0
- package/cortex-v3/proving-assets.json +64 -0
- package/cortex-v3/zk/BUILD-CONTRACT.md +87 -0
- package/cortex-v3/zk/action-transition-vk.json +119 -0
- package/cortex-v3/zk/alias-adversarial.test.mjs +220 -0
- package/cortex-v3/zk/groth16-verify-child.mjs +17 -0
- package/cortex-v3/zk/intent-witness.mjs +365 -0
- package/cortex-v3/zk/intent-witness.test.mjs +485 -0
- package/cortex-v3/zk/proving-assets.mjs +203 -0
- package/cortex-v3/zk/sale-witness.mjs +783 -0
- package/cortex-v3/zk/sale-witness.test.mjs +784 -0
- package/cortex-v3/zk/sealed-sale-release-vk.json +119 -0
- package/cortex-v3/zk/settlement-evidence.mjs +722 -0
- package/cortex-v3/zk/setup-intent.mjs +688 -0
- package/cortex-v3/zk/setup-sale.mjs +666 -0
- package/cortex-v3/zk/setup.mjs +594 -0
- package/cortex-v3/zk/witness.mjs +184 -0
- package/cortex-v3/zk/zk-codec.mjs +232 -0
- package/cortex-v3/zk/zk-codec.test.mjs +293 -0
- package/cortex-v3/zk/zk-settle.mjs +370 -0
- package/cortex-v3/zk/zk-tree.mjs +256 -0
- package/cortex-v3/zk/zk-tree.test.mjs +419 -0
- package/deploy/SIMULATION.md +14 -9
- package/deploy/docker-compose.local-production-simulation.yml +54 -12
- package/docs/benchmark-attestation-network.md +487 -487
- package/docs/benchmark-reproducibility.md +289 -289
- package/docs/blockchain-only-mechanisms.md +400 -400
- package/docs/browser-extension-install.md +8 -6
- package/docs/client-connectors.md +16 -12
- package/docs/demo-proof-network.md +275 -275
- package/docs/developer-ecosystem.md +15 -13
- package/docs/developer-proof-quickstart.md +325 -325
- package/docs/enigma-memory-ready-conformance.md +378 -376
- package/docs/install-anywhere.md +64 -30
- package/docs/installers-and-desktop.md +8 -7
- package/docs/memory-benchmarks.md +1 -1
- package/docs/memory-drive-health-model.md +690 -690
- package/docs/novelty-invention-candidates.md +161 -161
- package/docs/proof-network-build-notes.md +240 -240
- package/docs/proof-network-claim-boundaries.md +320 -318
- package/docs/proof-network.md +339 -339
- package/docs/sdk-api.md +324 -324
- package/docs/solana-proof-rail.md +453 -453
- package/examples/01-quickstart-agent/index.mjs +49 -0
- package/examples/01_agent_memory_quickstart.mjs +57 -0
- package/examples/02-multi-agent-swarm/index.mjs +57 -0
- package/examples/02_cross_model_passport.mjs +64 -0
- package/examples/03-langchain-memory/index.mjs +41 -0
- package/examples/03_poseidon_commitment_verification.mjs +71 -0
- package/examples/04-python-trading-agent/trader.py +49 -0
- package/examples/README.md +27 -0
- package/examples/ci/github-actions.yml +7 -2
- package/package.json +410 -278
- package/packages/adapters/PACKAGE_CONTRACT.md +1 -1
- package/packages/connectors/src/index.js +196 -4
- package/packages/connectors/swarm-router.mjs +168 -0
- package/packages/core/src/index.js +249 -2
- package/packages/core/src/version.mjs +7 -0
- package/packages/dev-tools/package.json +19 -0
- package/packages/dev-tools/src/index.js +4 -0
- package/packages/dev-tools/src/memory-benchmark-suite.js +112 -0
- package/packages/dev-tools/src/swarm-simulator.js +101 -0
- package/packages/dev-tools/src/vault-inspector.js +114 -0
- package/packages/dev-tools/src/vector-benchmark.js +100 -0
- package/packages/developer-platform/src/access-credentials.js +341 -0
- package/packages/developer-platform/src/http.js +132 -0
- package/packages/developer-platform/src/index.js +4 -0
- package/packages/developer-platform/src/usage-http.js +60 -0
- package/packages/developer-platform/src/usage.js +295 -0
- package/packages/enclave-runtime/attestation.mjs +159 -0
- package/packages/enclave-runtime/index.mjs +47 -0
- package/packages/enclave-runtime/session-manager.mjs +253 -0
- package/packages/enclave-runtime/zeroization-proof.mjs +227 -0
- package/packages/enigma-reflex/package.json +14 -0
- package/packages/enigma-reflex/src/index.js +204 -0
- package/packages/enigma-reflex/training/generate-dataset.mjs +40 -0
- package/packages/enigma-reflex/training/requirements.txt +8 -0
- package/packages/enigma-reflex/training/train.py +314 -0
- package/packages/enigma-weave/LICENSE +22 -0
- package/packages/enigma-weave/UPSTREAM.json +21 -0
- package/packages/enigma-weave/package.json +14 -0
- package/packages/enigma-weave/src/index.js +286 -0
- package/packages/hosted-cloud/src/index.js +80 -5
- package/packages/importers/src/index.js +432 -0
- package/packages/inference-runtime/src/browser.js +401 -0
- package/packages/inference-runtime/src/chat.js +265 -0
- package/packages/inference-runtime/src/code.js +407 -0
- package/packages/inference-runtime/src/contracts.js +162 -0
- package/packages/inference-runtime/src/http.js +232 -0
- package/packages/inference-runtime/src/image.js +186 -0
- package/packages/inference-runtime/src/index.js +10 -0
- package/packages/inference-runtime/src/model-router.js +320 -0
- package/packages/inference-runtime/src/platform.js +125 -0
- package/packages/inference-runtime/src/privacy.js +400 -0
- package/packages/inference-runtime/src/video.js +253 -0
- package/packages/mcp-server/README.md +22 -6
- package/packages/mcp-server/bin/enigma-mcp.mjs +2 -1
- package/packages/mcp-server/src/index.js +2498 -1185
- package/packages/mcp-server/src/oauth.js +561 -0
- package/packages/mcp-server/src/private-handoff.js +84 -0
- package/packages/mcp-server/src/remote-http.js +273 -0
- package/packages/mcp-server/src/remote-policy.js +72 -0
- package/packages/mcp-server/swarm-bridge.mjs +361 -0
- package/packages/mesh/index.d.ts +283 -0
- package/packages/mesh/package.json +23 -0
- package/packages/mesh/src/crypto.js +189 -0
- package/packages/mesh/src/federation-packets.js +353 -0
- package/packages/mesh/src/gossip.js +311 -0
- package/packages/mesh/src/index.js +6 -0
- package/packages/mesh/src/protocol.js +255 -0
- package/packages/mesh/src/router.js +279 -0
- package/packages/mesh/src/transport.js +306 -0
- package/packages/passport/src/index.js +436 -7
- package/packages/private-economy/src/credits-http.js +100 -0
- package/packages/private-economy/src/credits.js +447 -0
- package/packages/private-economy/src/index.js +5 -0
- package/packages/private-economy/src/payments-http.js +120 -0
- package/packages/private-economy/src/payments.js +509 -0
- package/packages/private-economy/src/x402.js +346 -0
- package/packages/proof-network/PACKAGE_CONTRACT.md +21 -0
- package/packages/rag/index.d.ts +182 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/THIRD_PARTY_LICENSES.txt +207 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/config.json +25 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/onnx/model_quantized.onnx +0 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/sha256-manifest.json +28 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/tokenizer.json +30686 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/tokenizer_config.json +15 -0
- package/packages/rag/package.json +27 -0
- package/packages/rag/src/blinded-search.js +109 -0
- package/packages/rag/src/bm25.js +169 -0
- package/packages/rag/src/embeddings.js +459 -0
- package/packages/rag/src/hybrid.js +76 -0
- package/packages/rag/src/index.js +38 -0
- package/packages/rag/src/reranker.js +61 -0
- package/packages/rag/src/research.js +107 -0
- package/packages/rag/src/vector-store.js +430 -0
- package/packages/rag/src/verify-model-artifacts.mjs +4 -0
- package/packages/sdk/index.d.ts +760 -0
- package/packages/sdk/package.json +33 -0
- package/packages/sdk/python/README.md +24 -0
- package/packages/sdk/python/enigma_sdk.py +250 -0
- package/packages/sdk/python/pyproject.toml +34 -0
- package/packages/sdk/python/requirements.txt +1 -0
- package/packages/sdk/python/setup.py +20 -0
- package/packages/sdk/src/federation/capability-grant.js +389 -0
- package/packages/sdk/src/federation/federation-router.js +360 -0
- package/packages/sdk/src/federation/ghostmesh-bridge.js +497 -0
- package/packages/sdk/src/federation/index.js +3 -0
- package/packages/sdk/src/index.js +1796 -0
- package/packages/sdk/src/intelligence/contradiction.js +337 -0
- package/packages/sdk/src/intelligence/decision-engine.js +155 -0
- package/packages/sdk/src/intelligence/index.js +4 -0
- package/packages/sdk/src/intelligence/ontology.js +122 -0
- package/packages/sdk/src/intelligence/temporal.js +123 -0
- package/packages/sdk/src/market-client.js +142 -0
- package/packages/sdk/src/mesh-client.js +110 -0
- package/packages/sdk/src/middleware/index.js +3 -0
- package/packages/sdk/src/middleware/langchain.js +159 -0
- package/packages/sdk/src/middleware/llamaindex.js +101 -0
- package/packages/sdk/src/middleware/vercel-ai.js +112 -0
- package/packages/sdk/src/rag-client.js +85 -0
- package/packages/sdk/src/swarm-orchestrator.js +260 -0
- package/packages/settlement/PACKAGE_CONTRACT.md +1 -1
- package/packages/snapcompact/THIRD_PARTY_LICENSES.txt +40 -0
- package/packages/snapcompact/assets/8x13-latin1.bdf +3837 -0
- package/packages/snapcompact/index.d.ts +284 -0
- package/packages/snapcompact/package.json +25 -0
- package/packages/snapcompact/src/index.js +716 -0
- package/packages/storage/PACKAGE_CONTRACT.md +1 -1
- package/packages/terminal-console/animations.mjs +240 -0
- package/packages/terminal-console/auto-anchor.mjs +220 -0
- package/packages/terminal-console/banner.mjs +91 -0
- package/packages/terminal-console/commands.mjs +459 -0
- package/packages/terminal-console/delegation.mjs +152 -0
- package/packages/terminal-console/index.mjs +5 -0
- package/packages/terminal-console/outbox.mjs +143 -0
- package/packages/terminal-console/phantom-bridge.mjs +637 -0
- package/packages/terminal-console/repl.mjs +136 -0
- package/packages/terminal-console/signer-store.mjs +130 -0
- package/packages/terminal-console/solana-rpc.mjs +214 -0
- package/packages/terminal-console/solana-transport.mjs +189 -0
- package/packages/terminal-tui/dashboard.mjs +214 -0
- package/packages/terminal-tui/index.mjs +28 -0
- package/packages/terminal-tui/merkle-tree-renderer.mjs +268 -0
- package/packages/terminal-tui/telemetry-hud.mjs +137 -0
- package/packages/vault/index.d.ts +449 -0
- package/packages/vault/package.json +27 -0
- package/packages/vault/src/e2ee.mjs +393 -0
- package/packages/vault/src/enclave.js +481 -0
- package/packages/vault/src/erasure.js +207 -0
- package/packages/vault/src/index.js +1150 -125
- package/packages/vault/src/persistence.js +307 -0
- package/packages/vault/src/poseidon.js +354 -0
- package/packages/vault/src/receipt.js +459 -0
- package/scripts/benchmark-optical-context.mjs +166 -0
- package/scripts/bootstrap-enigma.mjs +502 -0
- package/scripts/build-edge-backend-workers.mjs +20 -5
- package/scripts/build-goal-completion-audit.mjs +72 -25
- package/scripts/build-hosted-api-key-lifecycle.mjs +292 -274
- package/scripts/build-hosted-customer-lifecycle.mjs +493 -476
- package/scripts/build-hosted-probe-worker.mjs +19 -4
- package/scripts/build-installer-assets.mjs +409 -389
- package/scripts/build-operator-evidence-starter.mjs +59 -1
- package/scripts/build-production-backend-env-kit.mjs +2 -0
- package/scripts/build-production-unblocker.mjs +4 -1
- package/scripts/build-proof-network-packet.mjs +213 -213
- package/scripts/check.mjs +25 -4
- package/scripts/collect-hosted-backend-live-evidence.mjs +49 -12
- package/scripts/install-enigma-local.mjs +18 -5
- package/scripts/release-audit.mjs +74 -115
- package/scripts/release-provenance.mjs +12 -2
- package/scripts/run-backend-readiness-smoke.mjs +112 -10
- package/scripts/run-standard-memory-benchmarks.mjs +1354 -1352
- package/scripts/scan-secrets.mjs +178 -0
- package/scripts/simulate-production-env.mjs +71 -10
- package/scripts/validate-hosted-backend-live.mjs +112 -1
- package/specs/antibody-pack-v1.schema.json +95 -0
- package/specs/antigen-envelope-v1.schema.json +81 -0
- package/specs/boundary-manifest-v1.schema.json +35 -35
- package/specs/capsule-v1.schema.json +55 -55
- package/specs/claim-boundary-manifest-v1.schema.json +22 -22
- package/specs/claim-ledger-v1.schema.json +291 -0
- package/specs/context-passport-v1.schema.json +59 -0
- package/specs/deletion-tombstone-v1.schema.json +26 -26
- package/specs/evidence-packet-v1.schema.json +177 -0
- package/specs/hosted-backend-live-evidence-v1.schema.json +72 -3
- package/specs/immune-scan-report-v1.schema.json +112 -0
- package/specs/lifecycle-receipt-log-v1.schema.json +67 -0
- package/specs/memory-atom-v1.schema.json +59 -0
- package/specs/memory-event-v1.schema.json +42 -42
- package/specs/passport-v1.schema.json +50 -50
- package/specs/proof-of-non-use-v1.schema.json +65 -0
- package/specs/quarantine-record-v1.schema.json +126 -0
- package/specs/receipt-v1.schema.json +61 -61
- package/specs/state-checkpoint-v1.schema.json +37 -37
- package/specs/trust-bundle-v1.schema.json +56 -56
- package/specs/trust-card-v1.schema.json +119 -0
- package/docs/proof-network-launch-plan.md +0 -421
- package/packages/metering/PACKAGE_CONTRACT.md +0 -20
- package/scripts/build-ai-orchestration-plan.mjs +0 -248
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
// Enigma Sovereign Embedding & Lexical-Vector Provider.
|
|
2
|
+
// Neural inference is local-only and pinned to the bundled, hash-verified model.
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { readFile } from 'node:fs/promises';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
export const EMBEDDING_DIM = 384;
|
|
8
|
+
|
|
9
|
+
export const LEXICAL_HASH_DESCRIPTOR = Object.freeze({
|
|
10
|
+
provider: 'enigma_lexical_hash',
|
|
11
|
+
version: '1.0.0',
|
|
12
|
+
dimension: EMBEDDING_DIM,
|
|
13
|
+
architecture: 'DeterministicLexicalHash/NgramProjection',
|
|
14
|
+
description: 'Explicit deterministic subword n-gram fallback with position-weighted pooling and L2 unit-sphere normalization.',
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export const LOCAL_MINILM_DESCRIPTOR = Object.freeze({
|
|
18
|
+
provider: 'huggingface_transformers_local',
|
|
19
|
+
package: '@huggingface/transformers',
|
|
20
|
+
packageVersion: '4.2.0',
|
|
21
|
+
modelId: 'Xenova/all-MiniLM-L6-v2',
|
|
22
|
+
immutableCommit: '751bff37182d3f1213fa05d7196b954e230abad9',
|
|
23
|
+
dimension: EMBEDDING_DIM,
|
|
24
|
+
maxSequenceLength: 512,
|
|
25
|
+
dtype: 'q8',
|
|
26
|
+
device: 'cpu',
|
|
27
|
+
pooling: 'mean',
|
|
28
|
+
normalization: 'l2_unit_sphere',
|
|
29
|
+
manifest: 'sha256-manifest.json',
|
|
30
|
+
manifestSha256: 'c1d03ad7b6e33e74fdbb862bda766cffb6c2bc6674f9628172d98c8f58b8b103',
|
|
31
|
+
artifacts: Object.freeze([
|
|
32
|
+
Object.freeze({ path: 'config.json', size: 650, sha256: '7135149f7cffa1a573466c6e4d8423ed73b62fd2332c575bf738a0d033f70df7' }),
|
|
33
|
+
Object.freeze({ path: 'tokenizer.json', size: 711661, sha256: 'da0e79933b9ed51798a3ae27893d3c5fa4a201126cef75586296df9b4d2c62a0' }),
|
|
34
|
+
Object.freeze({ path: 'tokenizer_config.json', size: 366, sha256: '9261e7d79b44c8195c1cada2b453e55b00aeb81e907a6664974b4d7776172ab3' }),
|
|
35
|
+
Object.freeze({ path: 'onnx/model_quantized.onnx', size: 22972370, sha256: 'afdb6f1a0e45b715d0bb9b11772f032c399babd23bfc31fed1c170afc848bdb1' }),
|
|
36
|
+
]),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Retained name for callers that inspected the earlier integration descriptor.
|
|
40
|
+
export const ONNX_INTEGRATION_SPEC = LOCAL_MINILM_DESCRIPTOR;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Word and subword tokenizer.
|
|
44
|
+
* Segments strings into lowercase words and subword fragments.
|
|
45
|
+
* @param {string} text
|
|
46
|
+
* @returns {Array<{ token: string, isSubword: boolean, pos: number }>}
|
|
47
|
+
*/
|
|
48
|
+
export function tokenizeSubwords(text) {
|
|
49
|
+
if (!text || typeof text !== 'string') return [];
|
|
50
|
+
const normalized = text.toLowerCase().replace(/[^\w\s]/g, ' ');
|
|
51
|
+
const words = normalized.split(/\s+/).filter(w => w.length > 0);
|
|
52
|
+
const tokens = [];
|
|
53
|
+
|
|
54
|
+
let pos = 0;
|
|
55
|
+
for (const word of words) {
|
|
56
|
+
tokens.push({ token: word, isSubword: false, pos: pos++ });
|
|
57
|
+
if (word.length > 4) {
|
|
58
|
+
const mid = Math.floor(word.length / 2);
|
|
59
|
+
tokens.push({ token: word.slice(0, mid), isSubword: false, pos });
|
|
60
|
+
tokens.push({ token: `##${word.slice(mid)}`, isSubword: true, pos });
|
|
61
|
+
pos++;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return tokens;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Deterministic Murmur-style hash for feature projection.
|
|
69
|
+
*/
|
|
70
|
+
function hashFeature(str, seed = 0) {
|
|
71
|
+
let h = seed ^ str.length;
|
|
72
|
+
for (let i = 0; i < str.length; i++) {
|
|
73
|
+
const c = str.charCodeAt(i);
|
|
74
|
+
h = Math.imul(h ^ c, 0x5bd1e995);
|
|
75
|
+
h ^= h >>> 15;
|
|
76
|
+
}
|
|
77
|
+
return (h >>> 0);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Computes 384-dimensional dense vector via deterministic subword feature hashing,
|
|
82
|
+
* position-weighted pooling, and L2 unit-sphere normalization.
|
|
83
|
+
* Note: This is an offline lexical-hash fallback, not neural transformer inference.
|
|
84
|
+
* @param {string} text
|
|
85
|
+
* @param {number} [dim=384]
|
|
86
|
+
* @returns {Float32Array} L2-normalized 384-D vector
|
|
87
|
+
*/
|
|
88
|
+
export function computeLexicalEmbedding(text, dim = EMBEDDING_DIM) {
|
|
89
|
+
const tokens = tokenizeSubwords(text);
|
|
90
|
+
const vector = new Float32Array(dim);
|
|
91
|
+
|
|
92
|
+
if (tokens.length === 0) {
|
|
93
|
+
return vector;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const HEAD_SEEDS = [0x9747b28c, 0xcafebabe, 0x85ebca6b, 0x5bd1e995, 0x12b9b0a1, 0x43f119e2];
|
|
97
|
+
let totalWeight = 0;
|
|
98
|
+
|
|
99
|
+
for (const item of tokens) {
|
|
100
|
+
const weight = (item.isSubword ? 0.65 : 1.25) / Math.sqrt(1 + item.pos * 0.05);
|
|
101
|
+
totalWeight += weight;
|
|
102
|
+
|
|
103
|
+
for (let h = 0; h < HEAD_SEEDS.length; h++) {
|
|
104
|
+
const h1 = hashFeature(item.token, HEAD_SEEDS[h]);
|
|
105
|
+
const idx = h1 % dim;
|
|
106
|
+
const sign = (hashFeature(item.token, h1) & 1) === 0 ? 1.0 : -1.0;
|
|
107
|
+
vector[idx] += sign * weight;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (totalWeight > 1e-12) {
|
|
112
|
+
const invWeight = 1.0 / totalWeight;
|
|
113
|
+
for (let i = 0; i < dim; i++) {
|
|
114
|
+
vector[i] *= invWeight;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const mixed = new Float32Array(dim);
|
|
119
|
+
for (let i = 0; i < dim; i++) {
|
|
120
|
+
const prev = vector[(i - 1 + dim) % dim];
|
|
121
|
+
const curr = vector[i];
|
|
122
|
+
const next = vector[(i + 1) % dim];
|
|
123
|
+
mixed[i] = curr * 0.70 + (prev + next) * 0.15;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let normSq = 0;
|
|
127
|
+
for (let i = 0; i < dim; i++) {
|
|
128
|
+
normSq += mixed[i] * mixed[i];
|
|
129
|
+
}
|
|
130
|
+
const norm = Math.sqrt(normSq);
|
|
131
|
+
if (norm > 1e-12) {
|
|
132
|
+
const invNorm = 1.0 / norm;
|
|
133
|
+
for (let i = 0; i < dim; i++) {
|
|
134
|
+
mixed[i] *= invNorm;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return mixed;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const BUNDLED_MODELS_URL = new URL('../models/', import.meta.url);
|
|
142
|
+
const BUNDLED_MODEL_URL = new URL(`${LOCAL_MINILM_DESCRIPTOR.modelId}/`, BUNDLED_MODELS_URL);
|
|
143
|
+
|
|
144
|
+
let sharedExtractorPromise = null;
|
|
145
|
+
let sharedExtractorLeases = 0;
|
|
146
|
+
let sharedExtractorDisposal = null;
|
|
147
|
+
|
|
148
|
+
export async function verifyBundledModel() {
|
|
149
|
+
const manifestUrl = new URL(LOCAL_MINILM_DESCRIPTOR.manifest, BUNDLED_MODEL_URL);
|
|
150
|
+
let manifestBytes;
|
|
151
|
+
try {
|
|
152
|
+
manifestBytes = await readFile(manifestUrl);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
throw new Error('Bundled embedding model SHA-256 manifest is unavailable', { cause: error });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const manifestHash = createHash('sha256').update(manifestBytes).digest('hex');
|
|
158
|
+
if (manifestHash !== LOCAL_MINILM_DESCRIPTOR.manifestSha256) {
|
|
159
|
+
throw new Error(
|
|
160
|
+
`Bundled embedding model SHA-256 manifest hash mismatch: expected ${LOCAL_MINILM_DESCRIPTOR.manifestSha256}, received ${manifestHash}`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let manifest;
|
|
165
|
+
try {
|
|
166
|
+
manifest = JSON.parse(manifestBytes.toString('utf8'));
|
|
167
|
+
} catch (error) {
|
|
168
|
+
throw new Error('Bundled embedding model SHA-256 manifest is invalid JSON', { cause: error });
|
|
169
|
+
}
|
|
170
|
+
if (
|
|
171
|
+
manifest.schemaVersion !== 1
|
|
172
|
+
|| manifest.algorithm !== 'sha256'
|
|
173
|
+
|| manifest.modelId !== LOCAL_MINILM_DESCRIPTOR.modelId
|
|
174
|
+
|| manifest.revision !== LOCAL_MINILM_DESCRIPTOR.immutableCommit
|
|
175
|
+
|| !Array.isArray(manifest.artifacts)
|
|
176
|
+
|| manifest.artifacts.length !== LOCAL_MINILM_DESCRIPTOR.artifacts.length
|
|
177
|
+
) {
|
|
178
|
+
throw new Error('Bundled embedding model SHA-256 manifest does not match the pinned runtime descriptor');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
for (let index = 0; index < LOCAL_MINILM_DESCRIPTOR.artifacts.length; index += 1) {
|
|
182
|
+
const artifact = LOCAL_MINILM_DESCRIPTOR.artifacts[index];
|
|
183
|
+
const manifestArtifact = manifest.artifacts[index];
|
|
184
|
+
if (
|
|
185
|
+
manifestArtifact?.path !== artifact.path
|
|
186
|
+
|| manifestArtifact?.size !== artifact.size
|
|
187
|
+
|| manifestArtifact?.sha256 !== artifact.sha256
|
|
188
|
+
) {
|
|
189
|
+
throw new Error(`Bundled embedding model SHA-256 manifest entry mismatch for ${artifact.path}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const artifactUrl = new URL(artifact.path, BUNDLED_MODEL_URL);
|
|
193
|
+
let bytes;
|
|
194
|
+
try {
|
|
195
|
+
bytes = await readFile(artifactUrl);
|
|
196
|
+
} catch (error) {
|
|
197
|
+
throw new Error(`Bundled embedding model artifact is unavailable: ${artifact.path}`, { cause: error });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (bytes.byteLength !== artifact.size) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
`Bundled embedding model artifact size mismatch for ${artifact.path}: expected ${artifact.size}, received ${bytes.byteLength}`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
const actualHash = createHash('sha256').update(bytes).digest('hex');
|
|
206
|
+
if (actualHash !== artifact.sha256) {
|
|
207
|
+
throw new Error(
|
|
208
|
+
`Bundled embedding model artifact hash mismatch for ${artifact.path}: expected ${artifact.sha256}, received ${actualHash}`
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function loadSharedExtractor() {
|
|
215
|
+
if (sharedExtractorDisposal) await sharedExtractorDisposal;
|
|
216
|
+
if (!sharedExtractorPromise) {
|
|
217
|
+
sharedExtractorPromise = (async () => {
|
|
218
|
+
await verifyBundledModel();
|
|
219
|
+
const { env, pipeline } = await import('@huggingface/transformers');
|
|
220
|
+
env.localModelPath = fileURLToPath(BUNDLED_MODELS_URL);
|
|
221
|
+
env.allowLocalModels = true;
|
|
222
|
+
env.allowRemoteModels = false;
|
|
223
|
+
env.useFS = true;
|
|
224
|
+
env.useBrowserCache = false;
|
|
225
|
+
env.useFSCache = false;
|
|
226
|
+
env.cacheDir = null;
|
|
227
|
+
env.useCustomCache = false;
|
|
228
|
+
env.customCache = null;
|
|
229
|
+
env.useWasmCache = false;
|
|
230
|
+
const extractor = await pipeline(
|
|
231
|
+
'feature-extraction',
|
|
232
|
+
LOCAL_MINILM_DESCRIPTOR.modelId,
|
|
233
|
+
{
|
|
234
|
+
revision: LOCAL_MINILM_DESCRIPTOR.immutableCommit,
|
|
235
|
+
local_files_only: true,
|
|
236
|
+
device: LOCAL_MINILM_DESCRIPTOR.device,
|
|
237
|
+
dtype: LOCAL_MINILM_DESCRIPTOR.dtype,
|
|
238
|
+
}
|
|
239
|
+
);
|
|
240
|
+
return extractor;
|
|
241
|
+
})().catch((error) => {
|
|
242
|
+
sharedExtractorPromise = null;
|
|
243
|
+
throw error;
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
return await sharedExtractorPromise;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function releaseSharedExtractor() {
|
|
250
|
+
sharedExtractorLeases -= 1;
|
|
251
|
+
if (sharedExtractorLeases !== 0 || !sharedExtractorPromise) return;
|
|
252
|
+
|
|
253
|
+
const extractorPromise = sharedExtractorPromise;
|
|
254
|
+
sharedExtractorPromise = null;
|
|
255
|
+
sharedExtractorDisposal = (async () => {
|
|
256
|
+
const extractor = await extractorPromise;
|
|
257
|
+
await extractor.dispose();
|
|
258
|
+
})().finally(() => {
|
|
259
|
+
sharedExtractorDisposal = null;
|
|
260
|
+
});
|
|
261
|
+
await sharedExtractorDisposal;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function coerceAdapterVector(output, dimension) {
|
|
265
|
+
const candidate = Array.isArray(output) && output.length === 1
|
|
266
|
+
&& (Array.isArray(output[0]) || ArrayBuffer.isView(output[0]))
|
|
267
|
+
? output[0]
|
|
268
|
+
: output;
|
|
269
|
+
const vector = candidate instanceof Float32Array
|
|
270
|
+
? new Float32Array(candidate)
|
|
271
|
+
: Array.isArray(candidate)
|
|
272
|
+
? new Float32Array(candidate)
|
|
273
|
+
: null;
|
|
274
|
+
if (!vector) {
|
|
275
|
+
throw new TypeError('ONNX runner must return a Float32Array, number array, or one-element batch');
|
|
276
|
+
}
|
|
277
|
+
if (vector.length !== dimension) {
|
|
278
|
+
throw new Error(`ONNX runner returned ${vector.length} values; expected ${dimension}`);
|
|
279
|
+
}
|
|
280
|
+
return vector;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function createAdapterProvider(onnxRunner, dimension) {
|
|
284
|
+
const invoke = typeof onnxRunner === 'function'
|
|
285
|
+
? (text) => onnxRunner(text)
|
|
286
|
+
: onnxRunner && typeof onnxRunner.run === 'function'
|
|
287
|
+
? async (text) => {
|
|
288
|
+
const result = await onnxRunner.run([text]);
|
|
289
|
+
return result;
|
|
290
|
+
}
|
|
291
|
+
: null;
|
|
292
|
+
if (!invoke) throw new TypeError('onnxRunner must be a function or an object with run(texts)');
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
providerType: 'onnx_runtime_adapter',
|
|
296
|
+
descriptor: LOCAL_MINILM_DESCRIPTOR,
|
|
297
|
+
dimension,
|
|
298
|
+
async embed(text) {
|
|
299
|
+
return coerceAdapterVector(await invoke(text), dimension);
|
|
300
|
+
},
|
|
301
|
+
async embedBatch(texts) {
|
|
302
|
+
return await Promise.all(texts.map((text) => this.embed(text)));
|
|
303
|
+
},
|
|
304
|
+
embedSync() {
|
|
305
|
+
throw new Error('embedSync() is unsupported by an async ONNX runner; use await embed() instead');
|
|
306
|
+
},
|
|
307
|
+
async dispose() {
|
|
308
|
+
await onnxRunner.dispose?.();
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function createLocalNeuralProvider(dimension) {
|
|
314
|
+
if (dimension !== EMBEDDING_DIM) {
|
|
315
|
+
throw new Error(`The bundled MiniLM model requires dimension ${EMBEDDING_DIM}, received ${dimension}`);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
let extractorLeasePromise = null;
|
|
319
|
+
let disposed = false;
|
|
320
|
+
const acquire = async () => {
|
|
321
|
+
if (disposed) throw new Error('Embedding provider has been disposed');
|
|
322
|
+
if (!extractorLeasePromise) {
|
|
323
|
+
extractorLeasePromise = loadSharedExtractor().then((extractor) => {
|
|
324
|
+
sharedExtractorLeases += 1;
|
|
325
|
+
return extractor;
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
return await extractorLeasePromise;
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
providerType: 'local_minilm',
|
|
333
|
+
descriptor: LOCAL_MINILM_DESCRIPTOR,
|
|
334
|
+
dimension,
|
|
335
|
+
async embed(text) {
|
|
336
|
+
if (typeof text !== 'string') throw new TypeError('Embedding text must be a string');
|
|
337
|
+
const extractor = await acquire();
|
|
338
|
+
let output;
|
|
339
|
+
try {
|
|
340
|
+
output = await extractor(text, { pooling: 'mean', normalize: true });
|
|
341
|
+
if (
|
|
342
|
+
!(output.data instanceof Float32Array)
|
|
343
|
+
|| output.dims?.length !== 2
|
|
344
|
+
|| output.dims[0] !== 1
|
|
345
|
+
|| output.dims[1] !== EMBEDDING_DIM
|
|
346
|
+
) {
|
|
347
|
+
throw new Error(
|
|
348
|
+
`Local MiniLM returned invalid tensor shape or dtype: expected Float32[1,${EMBEDDING_DIM}]`
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
return new Float32Array(output.data);
|
|
352
|
+
} finally {
|
|
353
|
+
output?.dispose();
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
async embedBatch(texts) {
|
|
357
|
+
const results = [];
|
|
358
|
+
for (const text of texts) results.push(await this.embed(text));
|
|
359
|
+
return results;
|
|
360
|
+
},
|
|
361
|
+
embedSync() {
|
|
362
|
+
throw new Error('embedSync() is unsupported by the local neural provider; use await embed() instead');
|
|
363
|
+
},
|
|
364
|
+
async dispose() {
|
|
365
|
+
if (disposed) return;
|
|
366
|
+
disposed = true;
|
|
367
|
+
if (extractorLeasePromise) {
|
|
368
|
+
await extractorLeasePromise;
|
|
369
|
+
extractorLeasePromise = null;
|
|
370
|
+
await releaseSharedExtractor();
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Creates an embedding provider. Local MiniLM is the fail-closed default; the
|
|
378
|
+
* deterministic lexical provider is available only through modelType: 'lexical'.
|
|
379
|
+
* @param {Object} [options]
|
|
380
|
+
* @param {'neural'|'lexical'|'onnx'} [options.modelType='neural']
|
|
381
|
+
* @param {Function|{run: Function, dispose?: Function}} [options.onnxRunner]
|
|
382
|
+
* @param {number} [options.dimension=384]
|
|
383
|
+
* @returns {Object}
|
|
384
|
+
*/
|
|
385
|
+
export function createEmbeddingProvider(options = {}) {
|
|
386
|
+
const dimension = options.dimension || EMBEDDING_DIM;
|
|
387
|
+
if (options.onnxRunner) return createAdapterProvider(options.onnxRunner, dimension);
|
|
388
|
+
|
|
389
|
+
const modelType = options.modelType || 'neural';
|
|
390
|
+
if (modelType === 'lexical') {
|
|
391
|
+
return {
|
|
392
|
+
providerType: 'lexical_hash',
|
|
393
|
+
descriptor: LEXICAL_HASH_DESCRIPTOR,
|
|
394
|
+
dimension,
|
|
395
|
+
async embed(text) {
|
|
396
|
+
return computeLexicalEmbedding(text, dimension);
|
|
397
|
+
},
|
|
398
|
+
async embedBatch(texts) {
|
|
399
|
+
return texts.map((text) => computeLexicalEmbedding(text, dimension));
|
|
400
|
+
},
|
|
401
|
+
embedSync(text) {
|
|
402
|
+
return computeLexicalEmbedding(text, dimension);
|
|
403
|
+
},
|
|
404
|
+
async dispose() {},
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
if (modelType === 'onnx') {
|
|
408
|
+
throw new Error('modelType "onnx" requires an explicit onnxRunner');
|
|
409
|
+
}
|
|
410
|
+
if (modelType !== 'neural') throw new Error(`Unsupported embedding modelType: ${modelType}`);
|
|
411
|
+
return createLocalNeuralProvider(dimension);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Cosine similarity between two float vectors.
|
|
416
|
+
* @param {Float32Array} a
|
|
417
|
+
* @param {Float32Array} b
|
|
418
|
+
* @returns {number}
|
|
419
|
+
*/
|
|
420
|
+
export function cosineSimilarity(a, b) {
|
|
421
|
+
if (a.length !== b.length) throw new Error(`Vector length mismatch: ${a.length} !== ${b.length}`);
|
|
422
|
+
let dot = 0;
|
|
423
|
+
for (let i = 0; i < a.length; i++) dot += a[i] * b[i];
|
|
424
|
+
return Math.max(-1.0, Math.min(1.0, dot));
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Quantizes Float32Array to Int8Array [-127, 127] for 75% memory reduction.
|
|
429
|
+
* @param {Float32Array} floatVec
|
|
430
|
+
* @returns {Int8Array}
|
|
431
|
+
*/
|
|
432
|
+
export function quantizeVector(floatVec) {
|
|
433
|
+
const int8 = new Int8Array(floatVec.length);
|
|
434
|
+
for (let i = 0; i < floatVec.length; i++) {
|
|
435
|
+
int8[i] = Math.round(Math.max(-1.0, Math.min(1.0, floatVec[i])) * 127);
|
|
436
|
+
}
|
|
437
|
+
return int8;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Dequantizes Int8Array back to L2-normalized Float32Array.
|
|
442
|
+
* @param {Int8Array} int8Vec
|
|
443
|
+
* @returns {Float32Array}
|
|
444
|
+
*/
|
|
445
|
+
export function dequantizeVector(int8Vec) {
|
|
446
|
+
const floatVec = new Float32Array(int8Vec.length);
|
|
447
|
+
let sumSq = 0;
|
|
448
|
+
for (let i = 0; i < int8Vec.length; i++) {
|
|
449
|
+
const v = int8Vec[i] / 127.0;
|
|
450
|
+
floatVec[i] = v;
|
|
451
|
+
sumSq += v * v;
|
|
452
|
+
}
|
|
453
|
+
const norm = Math.sqrt(sumSq);
|
|
454
|
+
if (norm > 1e-12) {
|
|
455
|
+
const inv = 1.0 / norm;
|
|
456
|
+
for (let i = 0; i < floatVec.length; i++) floatVec[i] *= inv;
|
|
457
|
+
}
|
|
458
|
+
return floatVec;
|
|
459
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Enigma Hybrid RAG: Reciprocal Rank Fusion (RRF).
|
|
2
|
+
// Blends dense vector semantic retrieval with BM25 exact lexical term matching.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Combines dense vector search results and BM25 lexical results using Reciprocal Rank Fusion.
|
|
6
|
+
* @param {Array<{ id: string, text: string, score: number, scope: string, metadata?: Object }>} denseResults
|
|
7
|
+
* @param {Array<{ id: string, text: string, score: number, scope: string }>} bm25Results
|
|
8
|
+
* @param {Object} [options]
|
|
9
|
+
* @param {number} [options.k=60] - RRF smoothing parameter
|
|
10
|
+
* @param {number} [options.denseWeight=1.0] - Weight multiplier for dense semantic search
|
|
11
|
+
* @param {number} [options.bm25Weight=1.0] - Weight multiplier for BM25 keyword search
|
|
12
|
+
* @param {number} [options.topK=10]
|
|
13
|
+
* @returns {Array<{ id: string, text: string, score: number, denseScore: number, bm25Score: number, scope: string, metadata?: Object }>}
|
|
14
|
+
*/
|
|
15
|
+
export function reciprocalRankFusion(denseResults = [], bm25Results = [], options = {}) {
|
|
16
|
+
const k = options.k || 60;
|
|
17
|
+
const denseWeight = typeof options.denseWeight === 'number' ? options.denseWeight : 1.0;
|
|
18
|
+
const bm25Weight = typeof options.bm25Weight === 'number' ? options.bm25Weight : 1.0;
|
|
19
|
+
const topK = options.topK || 10;
|
|
20
|
+
|
|
21
|
+
const fusedMap = new Map();
|
|
22
|
+
|
|
23
|
+
// 1. Process Dense Vector Ranks
|
|
24
|
+
for (let rank = 0; rank < denseResults.length; rank++) {
|
|
25
|
+
const item = denseResults[rank];
|
|
26
|
+
const rrfScore = denseWeight / (k + rank + 1);
|
|
27
|
+
|
|
28
|
+
fusedMap.set(item.id, {
|
|
29
|
+
id: item.id,
|
|
30
|
+
text: item.text,
|
|
31
|
+
scope: item.scope,
|
|
32
|
+
metadata: item.metadata,
|
|
33
|
+
denseScore: item.score,
|
|
34
|
+
bm25Score: 0,
|
|
35
|
+
rrfScore,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 2. Process BM25 Ranks
|
|
40
|
+
for (let rank = 0; rank < bm25Results.length; rank++) {
|
|
41
|
+
const item = bm25Results[rank];
|
|
42
|
+
const rrfScore = bm25Weight / (k + rank + 1);
|
|
43
|
+
|
|
44
|
+
if (fusedMap.has(item.id)) {
|
|
45
|
+
const entry = fusedMap.get(item.id);
|
|
46
|
+
entry.bm25Score = item.score;
|
|
47
|
+
entry.rrfScore += rrfScore;
|
|
48
|
+
} else {
|
|
49
|
+
fusedMap.set(item.id, {
|
|
50
|
+
id: item.id,
|
|
51
|
+
text: item.text,
|
|
52
|
+
scope: item.scope,
|
|
53
|
+
metadata: item.metadata,
|
|
54
|
+
denseScore: 0,
|
|
55
|
+
bm25Score: item.score,
|
|
56
|
+
rrfScore,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const fusedList = Array.from(fusedMap.values());
|
|
62
|
+
fusedList.sort((a, b) => b.rrfScore - a.rrfScore);
|
|
63
|
+
|
|
64
|
+
// Normalize final score relative to top match
|
|
65
|
+
const maxRrf = fusedList.length > 0 && fusedList[0].rrfScore > 0 ? fusedList[0].rrfScore : 1.0;
|
|
66
|
+
|
|
67
|
+
return fusedList.slice(0, topK).map((item) => ({
|
|
68
|
+
id: item.id,
|
|
69
|
+
text: item.text,
|
|
70
|
+
score: Math.round((item.rrfScore / maxRrf) * 1000) / 1000,
|
|
71
|
+
denseScore: Math.round(item.denseScore * 1000) / 1000,
|
|
72
|
+
bm25Score: Math.round(item.bm25Score * 1000) / 1000,
|
|
73
|
+
scope: item.scope,
|
|
74
|
+
metadata: item.metadata,
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Enigma Semantic Vector Search & Hybrid RAG Engine.
|
|
2
|
+
export {
|
|
3
|
+
LEXICAL_HASH_DESCRIPTOR,
|
|
4
|
+
LOCAL_MINILM_DESCRIPTOR,
|
|
5
|
+
ONNX_INTEGRATION_SPEC,
|
|
6
|
+
EMBEDDING_DIM,
|
|
7
|
+
tokenizeSubwords,
|
|
8
|
+
computeLexicalEmbedding,
|
|
9
|
+
createEmbeddingProvider,
|
|
10
|
+
verifyBundledModel,
|
|
11
|
+
cosineSimilarity,
|
|
12
|
+
quantizeVector,
|
|
13
|
+
dequantizeVector,
|
|
14
|
+
} from './embeddings.js';
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
BM25Index,
|
|
18
|
+
} from './bm25.js';
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
reciprocalRankFusion,
|
|
22
|
+
} from './hybrid.js';
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
semanticReranker,
|
|
26
|
+
} from './reranker.js';
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
EncryptedVectorStore,
|
|
30
|
+
} from './vector-store.js';
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
RESEARCH_DISCLAIMER,
|
|
34
|
+
generateResearchOrthogonalBasis,
|
|
35
|
+
applyResearchBasisTransform,
|
|
36
|
+
createResearchBlindedQuery,
|
|
37
|
+
evaluateResearchBlindedRanking,
|
|
38
|
+
} from './research.js';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Enigma Hybrid RAG: Semantic Candidate Reranker.
|
|
2
|
+
// Calibrates retrieval scores by evaluating exact phrase matches,
|
|
3
|
+
// token coverage density, and metadata importance boosts.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Reranks candidate memories against the query for precision.
|
|
7
|
+
* @param {string} queryText
|
|
8
|
+
* @param {Array<{ id: string, text: string, score: number, scope: string, metadata?: Object }>} candidates
|
|
9
|
+
* @param {Object} [options]
|
|
10
|
+
* @param {number} [options.topK=5]
|
|
11
|
+
* @param {number} [options.phraseBonus=0.3]
|
|
12
|
+
* @param {number} [options.coverageWeight=0.25]
|
|
13
|
+
* @returns {Array<{ id: string, text: string, score: number, originalScore: number, scope: string, metadata?: Object }>}
|
|
14
|
+
*/
|
|
15
|
+
export function semanticReranker(queryText, candidates = [], options = {}) {
|
|
16
|
+
if (!queryText || typeof queryText !== 'string' || candidates.length === 0) {
|
|
17
|
+
return candidates.slice(0, options.topK || 5);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const topK = options.topK || 5;
|
|
21
|
+
const phraseBonus = typeof options.phraseBonus === 'number' ? options.phraseBonus : 0.3;
|
|
22
|
+
const coverageWeight = typeof options.coverageWeight === 'number' ? options.coverageWeight : 0.25;
|
|
23
|
+
|
|
24
|
+
const cleanQuery = queryText.toLowerCase().trim();
|
|
25
|
+
const queryTerms = cleanQuery.split(/[^a-zA-Z0-9_\-]/).filter(t => t.length >= 2);
|
|
26
|
+
const queryTermSet = new Set(queryTerms);
|
|
27
|
+
|
|
28
|
+
const reranked = candidates.map((cand) => {
|
|
29
|
+
const textLower = cand.text.toLowerCase();
|
|
30
|
+
let score = cand.score;
|
|
31
|
+
|
|
32
|
+
// 1. Exact phrase match boost
|
|
33
|
+
if (cleanQuery.length > 3 && textLower.includes(cleanQuery)) {
|
|
34
|
+
score += phraseBonus;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 2. Token coverage calculation
|
|
38
|
+
if (queryTermSet.size > 0) {
|
|
39
|
+
const docTerms = new Set(textLower.split(/[^a-zA-Z0-9_\-]/).filter(t => t.length >= 2));
|
|
40
|
+
let matches = 0;
|
|
41
|
+
for (const t of queryTermSet) {
|
|
42
|
+
if (docTerms.has(t)) matches++;
|
|
43
|
+
}
|
|
44
|
+
const coverage = matches / queryTermSet.size;
|
|
45
|
+
score += coverage * coverageWeight;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 3. Metadata importance multiplier
|
|
49
|
+
const importance = cand.metadata?.importance || cand.importance || 1.0;
|
|
50
|
+
score = score * Math.min(2.0, Math.max(0.5, importance));
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
...cand,
|
|
54
|
+
originalScore: cand.score,
|
|
55
|
+
score: Math.round(score * 1000) / 1000,
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
reranked.sort((a, b) => b.score - a.score);
|
|
60
|
+
return reranked.slice(0, topK);
|
|
61
|
+
}
|