@fgv/ts-agent-memory 5.1.0-36
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/.rush/temp/chunked-rush-logs/ts-agent-memory.build.chunks.jsonl +9 -0
- package/.rush/temp/f6a88bfdd66517ccb98c2c7ae1be6e6fe9e15d38.tar.log +282 -0
- package/.rush/temp/operation/build/all.log +9 -0
- package/.rush/temp/operation/build/log-chunks.jsonl +9 -0
- package/.rush/temp/operation/build/state.json +3 -0
- package/.rush/temp/shrinkwrap-deps.json +688 -0
- package/LICENSE +21 -0
- package/README.md +45 -0
- package/config/api-extractor.json +343 -0
- package/config/jest.config.json +14 -0
- package/config/rig.json +4 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/packlets/converters/bodyConverterRegistry.js +51 -0
- package/dist/packlets/converters/bodyConverterRegistry.js.map +1 -0
- package/dist/packlets/converters/envelopeConverter.js +159 -0
- package/dist/packlets/converters/envelopeConverter.js.map +1 -0
- package/dist/packlets/converters/index.js +7 -0
- package/dist/packlets/converters/index.js.map +1 -0
- package/dist/packlets/index/index.js +6 -0
- package/dist/packlets/index/index.js.map +1 -0
- package/dist/packlets/index/memoryIndex.js +170 -0
- package/dist/packlets/index/memoryIndex.js.map +1 -0
- package/dist/packlets/observe/index.js +7 -0
- package/dist/packlets/observe/index.js.map +1 -0
- package/dist/packlets/observe/memoryObservationStore.js +119 -0
- package/dist/packlets/observe/memoryObservationStore.js.map +1 -0
- package/dist/packlets/observe/observer.js +6 -0
- package/dist/packlets/observe/observer.js.map +1 -0
- package/dist/packlets/retrieve/hybridRetriever.js +135 -0
- package/dist/packlets/retrieve/hybridRetriever.js.map +1 -0
- package/dist/packlets/retrieve/index.js +12 -0
- package/dist/packlets/retrieve/index.js.map +1 -0
- package/dist/packlets/retrieve/linkTraversalRetriever.js +143 -0
- package/dist/packlets/retrieve/linkTraversalRetriever.js.map +1 -0
- package/dist/packlets/retrieve/recencyRetriever.js +33 -0
- package/dist/packlets/retrieve/recencyRetriever.js.map +1 -0
- package/dist/packlets/retrieve/retriever.js +110 -0
- package/dist/packlets/retrieve/retriever.js.map +1 -0
- package/dist/packlets/retrieve/semanticRetriever.js +86 -0
- package/dist/packlets/retrieve/semanticRetriever.js.map +1 -0
- package/dist/packlets/retrieve/structuredFilterRetriever.js +37 -0
- package/dist/packlets/retrieve/structuredFilterRetriever.js.map +1 -0
- package/dist/packlets/retrieve/tagRetriever.js +37 -0
- package/dist/packlets/retrieve/tagRetriever.js.map +1 -0
- package/dist/packlets/store/fileTreeMemoryStore.js +698 -0
- package/dist/packlets/store/fileTreeMemoryStore.js.map +1 -0
- package/dist/packlets/store/index.js +7 -0
- package/dist/packlets/store/index.js.map +1 -0
- package/dist/packlets/store/scopeEncoding.js +31 -0
- package/dist/packlets/store/scopeEncoding.js.map +1 -0
- package/dist/packlets/types/envelope.js +6 -0
- package/dist/packlets/types/envelope.js.map +1 -0
- package/dist/packlets/types/filenameSafety.js +52 -0
- package/dist/packlets/types/filenameSafety.js.map +1 -0
- package/dist/packlets/types/identityCodec.js +184 -0
- package/dist/packlets/types/identityCodec.js.map +1 -0
- package/dist/packlets/types/ids.js +67 -0
- package/dist/packlets/types/ids.js.map +1 -0
- package/dist/packlets/types/index.js +10 -0
- package/dist/packlets/types/index.js.map +1 -0
- package/dist/packlets/types/writePolicy.js +263 -0
- package/dist/packlets/types/writePolicy.js.map +1 -0
- package/dist/packlets/vector/inMemoryCosineIndex.js +150 -0
- package/dist/packlets/vector/inMemoryCosineIndex.js.map +1 -0
- package/dist/packlets/vector/index.js +7 -0
- package/dist/packlets/vector/index.js.map +1 -0
- package/dist/packlets/vector/vectorIndex.js +6 -0
- package/dist/packlets/vector/vectorIndex.js.map +1 -0
- package/dist/test/unit/converters/bodyConverterRegistry.test.js +72 -0
- package/dist/test/unit/converters/bodyConverterRegistry.test.js.map +1 -0
- package/dist/test/unit/converters/envelopeConverter.test.js +196 -0
- package/dist/test/unit/converters/envelopeConverter.test.js.map +1 -0
- package/dist/test/unit/index/memoryIndex.test.js +152 -0
- package/dist/test/unit/index/memoryIndex.test.js.map +1 -0
- package/dist/test/unit/observe/memoryObservationStore.test.js +118 -0
- package/dist/test/unit/observe/memoryObservationStore.test.js.map +1 -0
- package/dist/test/unit/retrieve/linkTraversalRetriever.test.js +182 -0
- package/dist/test/unit/retrieve/linkTraversalRetriever.test.js.map +1 -0
- package/dist/test/unit/retrieve/retrievers.test.js +506 -0
- package/dist/test/unit/retrieve/retrievers.test.js.map +1 -0
- package/dist/test/unit/store/embedOnWrite.test.js +260 -0
- package/dist/test/unit/store/embedOnWrite.test.js.map +1 -0
- package/dist/test/unit/store/fileTreeMemoryStore.test.js +647 -0
- package/dist/test/unit/store/fileTreeMemoryStore.test.js.map +1 -0
- package/dist/test/unit/store/observations.test.js +239 -0
- package/dist/test/unit/store/observations.test.js.map +1 -0
- package/dist/test/unit/store/scopeEncoding.test.js +24 -0
- package/dist/test/unit/store/scopeEncoding.test.js.map +1 -0
- package/dist/test/unit/types/identityCodec.test.js +187 -0
- package/dist/test/unit/types/identityCodec.test.js.map +1 -0
- package/dist/test/unit/types/ids.test.js +84 -0
- package/dist/test/unit/types/ids.test.js.map +1 -0
- package/dist/test/unit/types/writePolicy.test.js +241 -0
- package/dist/test/unit/types/writePolicy.test.js.map +1 -0
- package/dist/test/unit/vector/inMemoryCosineIndex.test.js +192 -0
- package/dist/test/unit/vector/inMemoryCosineIndex.test.js.map +1 -0
- package/dist/test/unit/vector/vectorIndex.test.js +42 -0
- package/dist/test/unit/vector/vectorIndex.test.js.map +1 -0
- package/dist/ts-agent-memory.d.ts +1901 -0
- package/dist/tsdoc-metadata.json +11 -0
- package/eslint.config.js +15 -0
- package/etc/ts-agent-memory.api.md +525 -0
- package/lib/index.d.ts +8 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +28 -0
- package/lib/index.js.map +1 -0
- package/lib/packlets/converters/bodyConverterRegistry.d.ts +64 -0
- package/lib/packlets/converters/bodyConverterRegistry.d.ts.map +1 -0
- package/lib/packlets/converters/bodyConverterRegistry.js +55 -0
- package/lib/packlets/converters/bodyConverterRegistry.js.map +1 -0
- package/lib/packlets/converters/envelopeConverter.d.ts +70 -0
- package/lib/packlets/converters/envelopeConverter.d.ts.map +1 -0
- package/lib/packlets/converters/envelopeConverter.js +166 -0
- package/lib/packlets/converters/envelopeConverter.js.map +1 -0
- package/lib/packlets/converters/index.d.ts +3 -0
- package/lib/packlets/converters/index.d.ts.map +1 -0
- package/lib/packlets/converters/index.js +23 -0
- package/lib/packlets/converters/index.js.map +1 -0
- package/lib/packlets/index/index.d.ts +2 -0
- package/lib/packlets/index/index.d.ts.map +1 -0
- package/lib/packlets/index/index.js +22 -0
- package/lib/packlets/index/index.js.map +1 -0
- package/lib/packlets/index/memoryIndex.d.ts +127 -0
- package/lib/packlets/index/memoryIndex.d.ts.map +1 -0
- package/lib/packlets/index/memoryIndex.js +174 -0
- package/lib/packlets/index/memoryIndex.js.map +1 -0
- package/lib/packlets/observe/index.d.ts +3 -0
- package/lib/packlets/observe/index.d.ts.map +1 -0
- package/lib/packlets/observe/index.js +23 -0
- package/lib/packlets/observe/index.js.map +1 -0
- package/lib/packlets/observe/memoryObservationStore.d.ts +91 -0
- package/lib/packlets/observe/memoryObservationStore.d.ts.map +1 -0
- package/lib/packlets/observe/memoryObservationStore.js +123 -0
- package/lib/packlets/observe/memoryObservationStore.js.map +1 -0
- package/lib/packlets/observe/observer.d.ts +110 -0
- package/lib/packlets/observe/observer.d.ts.map +1 -0
- package/lib/packlets/observe/observer.js +7 -0
- package/lib/packlets/observe/observer.js.map +1 -0
- package/lib/packlets/retrieve/hybridRetriever.d.ts +79 -0
- package/lib/packlets/retrieve/hybridRetriever.d.ts.map +1 -0
- package/lib/packlets/retrieve/hybridRetriever.js +140 -0
- package/lib/packlets/retrieve/hybridRetriever.js.map +1 -0
- package/lib/packlets/retrieve/index.d.ts +8 -0
- package/lib/packlets/retrieve/index.d.ts.map +1 -0
- package/lib/packlets/retrieve/index.js +28 -0
- package/lib/packlets/retrieve/index.js.map +1 -0
- package/lib/packlets/retrieve/linkTraversalRetriever.d.ts +61 -0
- package/lib/packlets/retrieve/linkTraversalRetriever.d.ts.map +1 -0
- package/lib/packlets/retrieve/linkTraversalRetriever.js +147 -0
- package/lib/packlets/retrieve/linkTraversalRetriever.js.map +1 -0
- package/lib/packlets/retrieve/recencyRetriever.d.ts +21 -0
- package/lib/packlets/retrieve/recencyRetriever.d.ts.map +1 -0
- package/lib/packlets/retrieve/recencyRetriever.js +37 -0
- package/lib/packlets/retrieve/recencyRetriever.js.map +1 -0
- package/lib/packlets/retrieve/retriever.d.ts +133 -0
- package/lib/packlets/retrieve/retriever.d.ts.map +1 -0
- package/lib/packlets/retrieve/retriever.js +119 -0
- package/lib/packlets/retrieve/retriever.js.map +1 -0
- package/lib/packlets/retrieve/semanticRetriever.d.ts +69 -0
- package/lib/packlets/retrieve/semanticRetriever.d.ts.map +1 -0
- package/lib/packlets/retrieve/semanticRetriever.js +90 -0
- package/lib/packlets/retrieve/semanticRetriever.js.map +1 -0
- package/lib/packlets/retrieve/structuredFilterRetriever.d.ts +22 -0
- package/lib/packlets/retrieve/structuredFilterRetriever.d.ts.map +1 -0
- package/lib/packlets/retrieve/structuredFilterRetriever.js +41 -0
- package/lib/packlets/retrieve/structuredFilterRetriever.js.map +1 -0
- package/lib/packlets/retrieve/tagRetriever.d.ts +22 -0
- package/lib/packlets/retrieve/tagRetriever.d.ts.map +1 -0
- package/lib/packlets/retrieve/tagRetriever.js +41 -0
- package/lib/packlets/retrieve/tagRetriever.js.map +1 -0
- package/lib/packlets/store/fileTreeMemoryStore.d.ts +327 -0
- package/lib/packlets/store/fileTreeMemoryStore.d.ts.map +1 -0
- package/lib/packlets/store/fileTreeMemoryStore.js +702 -0
- package/lib/packlets/store/fileTreeMemoryStore.js.map +1 -0
- package/lib/packlets/store/index.d.ts +3 -0
- package/lib/packlets/store/index.d.ts.map +1 -0
- package/lib/packlets/store/index.js +23 -0
- package/lib/packlets/store/index.js.map +1 -0
- package/lib/packlets/store/scopeEncoding.d.ts +19 -0
- package/lib/packlets/store/scopeEncoding.d.ts.map +1 -0
- package/lib/packlets/store/scopeEncoding.js +34 -0
- package/lib/packlets/store/scopeEncoding.js.map +1 -0
- package/lib/packlets/types/envelope.d.ts +119 -0
- package/lib/packlets/types/envelope.d.ts.map +1 -0
- package/lib/packlets/types/envelope.js +7 -0
- package/lib/packlets/types/envelope.js.map +1 -0
- package/lib/packlets/types/filenameSafety.d.ts +16 -0
- package/lib/packlets/types/filenameSafety.d.ts.map +1 -0
- package/lib/packlets/types/filenameSafety.js +55 -0
- package/lib/packlets/types/filenameSafety.js.map +1 -0
- package/lib/packlets/types/identityCodec.d.ts +136 -0
- package/lib/packlets/types/identityCodec.d.ts.map +1 -0
- package/lib/packlets/types/identityCodec.js +190 -0
- package/lib/packlets/types/identityCodec.js.map +1 -0
- package/lib/packlets/types/ids.d.ts +55 -0
- package/lib/packlets/types/ids.d.ts.map +1 -0
- package/lib/packlets/types/ids.js +70 -0
- package/lib/packlets/types/ids.js.map +1 -0
- package/lib/packlets/types/index.d.ts +6 -0
- package/lib/packlets/types/index.d.ts.map +1 -0
- package/lib/packlets/types/index.js +26 -0
- package/lib/packlets/types/index.js.map +1 -0
- package/lib/packlets/types/writePolicy.d.ts +213 -0
- package/lib/packlets/types/writePolicy.d.ts.map +1 -0
- package/lib/packlets/types/writePolicy.js +268 -0
- package/lib/packlets/types/writePolicy.js.map +1 -0
- package/lib/packlets/vector/inMemoryCosineIndex.d.ts +69 -0
- package/lib/packlets/vector/inMemoryCosineIndex.d.ts.map +1 -0
- package/lib/packlets/vector/inMemoryCosineIndex.js +154 -0
- package/lib/packlets/vector/inMemoryCosineIndex.js.map +1 -0
- package/lib/packlets/vector/index.d.ts +3 -0
- package/lib/packlets/vector/index.d.ts.map +1 -0
- package/lib/packlets/vector/index.js +23 -0
- package/lib/packlets/vector/index.js.map +1 -0
- package/lib/packlets/vector/vectorIndex.d.ts +68 -0
- package/lib/packlets/vector/vectorIndex.d.ts.map +1 -0
- package/lib/packlets/vector/vectorIndex.js +7 -0
- package/lib/packlets/vector/vectorIndex.js.map +1 -0
- package/lib/test/unit/converters/bodyConverterRegistry.test.d.ts +2 -0
- package/lib/test/unit/converters/bodyConverterRegistry.test.d.ts.map +1 -0
- package/lib/test/unit/converters/bodyConverterRegistry.test.js +74 -0
- package/lib/test/unit/converters/bodyConverterRegistry.test.js.map +1 -0
- package/lib/test/unit/converters/envelopeConverter.test.d.ts +2 -0
- package/lib/test/unit/converters/envelopeConverter.test.d.ts.map +1 -0
- package/lib/test/unit/converters/envelopeConverter.test.js +198 -0
- package/lib/test/unit/converters/envelopeConverter.test.js.map +1 -0
- package/lib/test/unit/index/memoryIndex.test.d.ts +2 -0
- package/lib/test/unit/index/memoryIndex.test.d.ts.map +1 -0
- package/lib/test/unit/index/memoryIndex.test.js +154 -0
- package/lib/test/unit/index/memoryIndex.test.js.map +1 -0
- package/lib/test/unit/observe/memoryObservationStore.test.d.ts +2 -0
- package/lib/test/unit/observe/memoryObservationStore.test.d.ts.map +1 -0
- package/lib/test/unit/observe/memoryObservationStore.test.js +120 -0
- package/lib/test/unit/observe/memoryObservationStore.test.js.map +1 -0
- package/lib/test/unit/retrieve/linkTraversalRetriever.test.d.ts +2 -0
- package/lib/test/unit/retrieve/linkTraversalRetriever.test.d.ts.map +1 -0
- package/lib/test/unit/retrieve/linkTraversalRetriever.test.js +184 -0
- package/lib/test/unit/retrieve/linkTraversalRetriever.test.js.map +1 -0
- package/lib/test/unit/retrieve/retrievers.test.d.ts +2 -0
- package/lib/test/unit/retrieve/retrievers.test.d.ts.map +1 -0
- package/lib/test/unit/retrieve/retrievers.test.js +508 -0
- package/lib/test/unit/retrieve/retrievers.test.js.map +1 -0
- package/lib/test/unit/store/embedOnWrite.test.d.ts +2 -0
- package/lib/test/unit/store/embedOnWrite.test.d.ts.map +1 -0
- package/lib/test/unit/store/embedOnWrite.test.js +262 -0
- package/lib/test/unit/store/embedOnWrite.test.js.map +1 -0
- package/lib/test/unit/store/fileTreeMemoryStore.test.d.ts +2 -0
- package/lib/test/unit/store/fileTreeMemoryStore.test.d.ts.map +1 -0
- package/lib/test/unit/store/fileTreeMemoryStore.test.js +649 -0
- package/lib/test/unit/store/fileTreeMemoryStore.test.js.map +1 -0
- package/lib/test/unit/store/observations.test.d.ts +2 -0
- package/lib/test/unit/store/observations.test.d.ts.map +1 -0
- package/lib/test/unit/store/observations.test.js +241 -0
- package/lib/test/unit/store/observations.test.js.map +1 -0
- package/lib/test/unit/store/scopeEncoding.test.d.ts +2 -0
- package/lib/test/unit/store/scopeEncoding.test.d.ts.map +1 -0
- package/lib/test/unit/store/scopeEncoding.test.js +26 -0
- package/lib/test/unit/store/scopeEncoding.test.js.map +1 -0
- package/lib/test/unit/types/identityCodec.test.d.ts +2 -0
- package/lib/test/unit/types/identityCodec.test.d.ts.map +1 -0
- package/lib/test/unit/types/identityCodec.test.js +189 -0
- package/lib/test/unit/types/identityCodec.test.js.map +1 -0
- package/lib/test/unit/types/ids.test.d.ts +2 -0
- package/lib/test/unit/types/ids.test.d.ts.map +1 -0
- package/lib/test/unit/types/ids.test.js +86 -0
- package/lib/test/unit/types/ids.test.js.map +1 -0
- package/lib/test/unit/types/writePolicy.test.d.ts +2 -0
- package/lib/test/unit/types/writePolicy.test.d.ts.map +1 -0
- package/lib/test/unit/types/writePolicy.test.js +243 -0
- package/lib/test/unit/types/writePolicy.test.js.map +1 -0
- package/lib/test/unit/vector/inMemoryCosineIndex.test.d.ts +2 -0
- package/lib/test/unit/vector/inMemoryCosineIndex.test.d.ts.map +1 -0
- package/lib/test/unit/vector/inMemoryCosineIndex.test.js +194 -0
- package/lib/test/unit/vector/inMemoryCosineIndex.test.js.map +1 -0
- package/lib/test/unit/vector/vectorIndex.test.d.ts +2 -0
- package/lib/test/unit/vector/vectorIndex.test.d.ts.map +1 -0
- package/lib/test/unit/vector/vectorIndex.test.js +44 -0
- package/lib/test/unit/vector/vectorIndex.test.js.map +1 -0
- package/package.json +81 -0
- package/rush-logs/ts-agent-memory.build.cache.log +3 -0
- package/rush-logs/ts-agent-memory.build.log +9 -0
- package/src/index.ts +12 -0
- package/src/packlets/converters/bodyConverterRegistry.ts +105 -0
- package/src/packlets/converters/envelopeConverter.ts +210 -0
- package/src/packlets/converters/index.ts +7 -0
- package/src/packlets/index/index.ts +6 -0
- package/src/packlets/index/memoryIndex.ts +268 -0
- package/src/packlets/observe/index.ts +7 -0
- package/src/packlets/observe/memoryObservationStore.ts +153 -0
- package/src/packlets/observe/observer.ts +119 -0
- package/src/packlets/retrieve/hybridRetriever.ts +181 -0
- package/src/packlets/retrieve/index.ts +12 -0
- package/src/packlets/retrieve/linkTraversalRetriever.ts +169 -0
- package/src/packlets/retrieve/recencyRetriever.ts +54 -0
- package/src/packlets/retrieve/retriever.ts +207 -0
- package/src/packlets/retrieve/semanticRetriever.ts +147 -0
- package/src/packlets/retrieve/structuredFilterRetriever.ts +58 -0
- package/src/packlets/retrieve/tagRetriever.ts +58 -0
- package/src/packlets/store/fileTreeMemoryStore.ts +1073 -0
- package/src/packlets/store/index.ts +7 -0
- package/src/packlets/store/scopeEncoding.ts +36 -0
- package/src/packlets/types/envelope.ts +138 -0
- package/src/packlets/types/filenameSafety.ts +57 -0
- package/src/packlets/types/identityCodec.ts +263 -0
- package/src/packlets/types/ids.ts +124 -0
- package/src/packlets/types/index.ts +10 -0
- package/src/packlets/types/writePolicy.ts +447 -0
- package/src/packlets/vector/inMemoryCosineIndex.ts +173 -0
- package/src/packlets/vector/index.ts +7 -0
- package/src/packlets/vector/vectorIndex.ts +78 -0
- package/src/test/unit/converters/bodyConverterRegistry.test.ts +89 -0
- package/src/test/unit/converters/envelopeConverter.test.ts +261 -0
- package/src/test/unit/index/memoryIndex.test.ts +187 -0
- package/src/test/unit/observe/memoryObservationStore.test.ts +158 -0
- package/src/test/unit/retrieve/linkTraversalRetriever.test.ts +230 -0
- package/src/test/unit/retrieve/retrievers.test.ts +662 -0
- package/src/test/unit/store/embedOnWrite.test.ts +346 -0
- package/src/test/unit/store/fileTreeMemoryStore.test.ts +875 -0
- package/src/test/unit/store/observations.test.ts +290 -0
- package/src/test/unit/store/scopeEncoding.test.ts +37 -0
- package/src/test/unit/types/identityCodec.test.ts +266 -0
- package/src/test/unit/types/ids.test.ts +94 -0
- package/src/test/unit/types/writePolicy.test.ts +325 -0
- package/src/test/unit/vector/inMemoryCosineIndex.test.ts +242 -0
- package/src/test/unit/vector/vectorIndex.test.ts +48 -0
- package/temp/build/lint/_eslint-5eVG3S6w.json +182 -0
- package/temp/build/typescript/ts_8nwakTlr.json +1 -0
- package/temp/ts-agent-memory.api.json +12438 -0
- package/temp/ts-agent-memory.api.md +525 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Erik Fortune
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
import '@fgv/ts-utils-jest';
|
|
6
|
+
import { fail, succeed } from '@fgv/ts-utils';
|
|
7
|
+
import { HybridRetriever, MemoryIndex, RecencyRetriever, ScoreUnionMergeStrategy, SemanticRetriever, StructuredFilterRetriever, TagRetriever, envelopeConverter, guardRetrieverCapabilities, limitRecords } from '../../../index';
|
|
8
|
+
function makeEntry(spec) {
|
|
9
|
+
var _a, _b, _c, _d, _e;
|
|
10
|
+
const record = {
|
|
11
|
+
envelope: envelopeConverter
|
|
12
|
+
.convert({
|
|
13
|
+
id: spec.id,
|
|
14
|
+
entityId: spec.id,
|
|
15
|
+
kind: (_a = spec.kind) !== null && _a !== void 0 ? _a : 'knowledge',
|
|
16
|
+
tags: (_b = spec.tags) !== null && _b !== void 0 ? _b : [],
|
|
17
|
+
links: [],
|
|
18
|
+
created: 0,
|
|
19
|
+
updated: (_c = spec.updated) !== null && _c !== void 0 ? _c : 0,
|
|
20
|
+
seq: (_d = spec.seq) !== null && _d !== void 0 ? _d : 0,
|
|
21
|
+
contentHash: 'h',
|
|
22
|
+
provenance: { source: 'agent' }
|
|
23
|
+
})
|
|
24
|
+
.orThrow(),
|
|
25
|
+
body: `body-${spec.id}`
|
|
26
|
+
};
|
|
27
|
+
return { scope: ((_e = spec.scope) !== null && _e !== void 0 ? _e : 'knowledge'), record };
|
|
28
|
+
}
|
|
29
|
+
function buildIndex(specs) {
|
|
30
|
+
const index = MemoryIndex.create().orThrow();
|
|
31
|
+
index.rebuild(specs.map(makeEntry)).orThrow();
|
|
32
|
+
return index;
|
|
33
|
+
}
|
|
34
|
+
function ids(records) {
|
|
35
|
+
return records.map((r) => r.envelope.id);
|
|
36
|
+
}
|
|
37
|
+
const knowledge = 'knowledge';
|
|
38
|
+
describe('retrieve helpers', () => {
|
|
39
|
+
describe('guardRetrieverCapabilities', () => {
|
|
40
|
+
const noCaps = {
|
|
41
|
+
supportsSemanticRecall: false,
|
|
42
|
+
supportsTemporalQuery: false,
|
|
43
|
+
supportsLinkTraversal: false
|
|
44
|
+
};
|
|
45
|
+
test('passes when no unsupported capability is requested', () => {
|
|
46
|
+
expect(guardRetrieverCapabilities({}, noCaps)).toSucceed();
|
|
47
|
+
});
|
|
48
|
+
test('fails loudly when semantic recall is requested but unsupported', () => {
|
|
49
|
+
expect(guardRetrieverCapabilities({ semantic: 'q' }, noCaps)).toFailWith(/semantic recall requires a vector index; none configured/i);
|
|
50
|
+
});
|
|
51
|
+
test('fails loudly when an as-of query is requested but unsupported', () => {
|
|
52
|
+
expect(guardRetrieverCapabilities({ asOf: 5 }, noCaps)).toFailWith(/temporal query requires temporal index; none configured/i);
|
|
53
|
+
});
|
|
54
|
+
test('names the kind in the temporal failure when present', () => {
|
|
55
|
+
expect(guardRetrieverCapabilities({ asOf: 5, kind: knowledge }, noCaps)).toFailWith(/none configured for kind knowledge/i);
|
|
56
|
+
});
|
|
57
|
+
test('fails loudly when a link-traversal axis is requested but unsupported', () => {
|
|
58
|
+
expect(guardRetrieverCapabilities({ linkedTo: 'x' }, noCaps)).toFailWith(/link traversal requires a backlink index; none configured/i);
|
|
59
|
+
expect(guardRetrieverCapabilities({ linkedFrom: 'x' }, noCaps)).toFailWith(/link traversal requires/i);
|
|
60
|
+
expect(guardRetrieverCapabilities({ hops: 2 }, noCaps)).toFailWith(/link traversal requires/i);
|
|
61
|
+
});
|
|
62
|
+
test('passes a link-traversal axis when the capability is supported', () => {
|
|
63
|
+
expect(guardRetrieverCapabilities({ linkedTo: 'x' }, Object.assign(Object.assign({}, noCaps), { supportsLinkTraversal: true }))).toSucceed();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
describe('limitRecords', () => {
|
|
67
|
+
const records = buildIndex([{ id: 'a' }, { id: 'b' }, { id: 'c' }])
|
|
68
|
+
.entries()
|
|
69
|
+
.map((e) => e.record);
|
|
70
|
+
test('returns all records when no limit is supplied', () => {
|
|
71
|
+
expect(limitRecords(records)).toHaveLength(3);
|
|
72
|
+
});
|
|
73
|
+
test('truncates to the top-N for a positive limit', () => {
|
|
74
|
+
expect(limitRecords(records, 2)).toHaveLength(2);
|
|
75
|
+
});
|
|
76
|
+
test('returns an empty array for a zero or negative limit', () => {
|
|
77
|
+
expect(limitRecords(records, 0)).toEqual([]);
|
|
78
|
+
expect(limitRecords(records, -1)).toEqual([]);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
describe('RecencyRetriever', () => {
|
|
83
|
+
test('reports all-false capabilities', () => {
|
|
84
|
+
const r = RecencyRetriever.create(buildIndex([])).orThrow();
|
|
85
|
+
expect(r.capabilities).toEqual({
|
|
86
|
+
supportsSemanticRecall: false,
|
|
87
|
+
supportsTemporalQuery: false,
|
|
88
|
+
supportsLinkTraversal: false
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
test('returns every record most-recently-updated first', async () => {
|
|
92
|
+
const r = RecencyRetriever.create(buildIndex([
|
|
93
|
+
{ id: 'a', updated: 10 },
|
|
94
|
+
{ id: 'b', updated: 30 },
|
|
95
|
+
{ id: 'c', updated: 20 }
|
|
96
|
+
])).orThrow();
|
|
97
|
+
expect(await r.retrieve({})).toSucceedAndSatisfy((records) => {
|
|
98
|
+
expect(ids(records)).toEqual(['b', 'c', 'a']);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
test('breaks ties on seq descending', async () => {
|
|
102
|
+
const r = RecencyRetriever.create(buildIndex([
|
|
103
|
+
{ id: 'a', updated: 10, seq: 1 },
|
|
104
|
+
{ id: 'b', updated: 10, seq: 3 },
|
|
105
|
+
{ id: 'c', updated: 10, seq: 2 }
|
|
106
|
+
])).orThrow();
|
|
107
|
+
expect(await r.retrieve({})).toSucceedAndSatisfy((records) => {
|
|
108
|
+
expect(ids(records)).toEqual(['b', 'c', 'a']);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
test('honors scope, kind, tag, predicate, and limit filters', async () => {
|
|
112
|
+
const index = buildIndex([
|
|
113
|
+
{ id: 'a', scope: 'knowledge', kind: 'knowledge', tags: ['x'], updated: 50 },
|
|
114
|
+
{ id: 'b', scope: 'knowledge', kind: 'knowledge', tags: ['y'], updated: 40 },
|
|
115
|
+
{ id: 'c', scope: 'other', kind: 'knowledge', tags: ['x'], updated: 60 },
|
|
116
|
+
{ id: 'd', scope: 'knowledge', kind: 'note', tags: ['x'], updated: 70 }
|
|
117
|
+
]);
|
|
118
|
+
const r = RecencyRetriever.create(index).orThrow();
|
|
119
|
+
expect(await r.retrieve({
|
|
120
|
+
scope: 'knowledge',
|
|
121
|
+
kind: knowledge,
|
|
122
|
+
tag: 'x',
|
|
123
|
+
filter: (rec) => rec.envelope.id !== 'skip',
|
|
124
|
+
limit: 5
|
|
125
|
+
})).toSucceedAndSatisfy((records) => {
|
|
126
|
+
expect(ids(records)).toEqual(['a']);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
test('limit truncates to the top-N after ordering', async () => {
|
|
130
|
+
const r = RecencyRetriever.create(buildIndex([
|
|
131
|
+
{ id: 'a', updated: 10 },
|
|
132
|
+
{ id: 'b', updated: 30 },
|
|
133
|
+
{ id: 'c', updated: 20 }
|
|
134
|
+
])).orThrow();
|
|
135
|
+
expect(await r.retrieve({ limit: 2 })).toSucceedAndSatisfy((records) => {
|
|
136
|
+
expect(ids(records)).toEqual(['b', 'c']);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
test('degrades loudly on a semantic request', async () => {
|
|
140
|
+
const r = RecencyRetriever.create(buildIndex([{ id: 'a' }])).orThrow();
|
|
141
|
+
expect(await r.retrieve({ semantic: 'hello' })).toFailWith(/semantic recall requires a vector index/i);
|
|
142
|
+
});
|
|
143
|
+
test('degrades loudly on a link-traversal request', async () => {
|
|
144
|
+
const r = RecencyRetriever.create(buildIndex([{ id: 'a' }])).orThrow();
|
|
145
|
+
expect(await r.retrieve({ linkedTo: 'a' })).toFailWith(/link traversal requires/i);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
/** A retriever with configurable capabilities that records the query it received. */
|
|
149
|
+
class RecordingRetriever {
|
|
150
|
+
constructor(capabilities) {
|
|
151
|
+
this.capabilities = capabilities;
|
|
152
|
+
}
|
|
153
|
+
retrieve(query) {
|
|
154
|
+
this.lastQuery = query;
|
|
155
|
+
return Promise.resolve(succeed([]));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
describe('TagRetriever', () => {
|
|
159
|
+
test('returns records carrying the tag, recency-ordered', async () => {
|
|
160
|
+
const r = TagRetriever.create(buildIndex([
|
|
161
|
+
{ id: 'a', tags: ['x'], updated: 10 },
|
|
162
|
+
{ id: 'b', tags: ['x', 'y'], updated: 30 },
|
|
163
|
+
{ id: 'c', tags: ['y'], updated: 20 }
|
|
164
|
+
])).orThrow();
|
|
165
|
+
expect(await r.retrieve({ tag: 'x' })).toSucceedAndSatisfy((records) => {
|
|
166
|
+
expect(ids(records)).toEqual(['b', 'a']);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
test('returns empty (not failure) when no tag is supplied', async () => {
|
|
170
|
+
const r = TagRetriever.create(buildIndex([{ id: 'a', tags: ['x'] }])).orThrow();
|
|
171
|
+
expect(await r.retrieve({})).toSucceedWith([]);
|
|
172
|
+
});
|
|
173
|
+
test('degrades loudly on a semantic request', async () => {
|
|
174
|
+
const r = TagRetriever.create(buildIndex([{ id: 'a', tags: ['x'] }])).orThrow();
|
|
175
|
+
expect(await r.retrieve({ tag: 'x', semantic: 'q' })).toFailWith(/semantic recall requires/i);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
describe('StructuredFilterRetriever', () => {
|
|
179
|
+
test('applies the predicate over the scope/kind/tag pre-filter', async () => {
|
|
180
|
+
const r = StructuredFilterRetriever.create(buildIndex([
|
|
181
|
+
{ id: 'keep-1', updated: 10 },
|
|
182
|
+
{ id: 'drop-1', updated: 30 },
|
|
183
|
+
{ id: 'keep-2', updated: 20 }
|
|
184
|
+
])).orThrow();
|
|
185
|
+
expect(await r.retrieve({ filter: (rec) => rec.envelope.id.startsWith('keep') })).toSucceedAndSatisfy((records) => {
|
|
186
|
+
expect(ids(records)).toEqual(['keep-2', 'keep-1']);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
test('returns empty (not failure) when no predicate is supplied', async () => {
|
|
190
|
+
const r = StructuredFilterRetriever.create(buildIndex([{ id: 'a' }])).orThrow();
|
|
191
|
+
expect(await r.retrieve({})).toSucceedWith([]);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
/** A scripted in-memory vector index returning canned hits. */
|
|
195
|
+
class FakeVectorIndex {
|
|
196
|
+
constructor(hits, failQuery = false) {
|
|
197
|
+
this._hits = hits;
|
|
198
|
+
this._failQuery = failQuery;
|
|
199
|
+
}
|
|
200
|
+
add(id) {
|
|
201
|
+
return Promise.resolve(succeed(`ref-${id}`));
|
|
202
|
+
}
|
|
203
|
+
remove(id) {
|
|
204
|
+
return Promise.resolve(succeed(id));
|
|
205
|
+
}
|
|
206
|
+
query(__vector, topK) {
|
|
207
|
+
this.lastTopK = topK;
|
|
208
|
+
return Promise.resolve(this._failQuery ? fail('vector backend down') : succeed(this._hits));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
describe('SemanticRetriever', () => {
|
|
212
|
+
const okEmbed = () => Promise.resolve(succeed(Float32Array.from([0.1, 0.2])));
|
|
213
|
+
test('reports supportsSemanticRecall=false when no backend is wired', () => {
|
|
214
|
+
const r = SemanticRetriever.create({ index: buildIndex([]) }).orThrow();
|
|
215
|
+
expect(r.capabilities.supportsSemanticRecall).toBe(false);
|
|
216
|
+
});
|
|
217
|
+
test('reports supportsSemanticRecall=true when a backend is wired', () => {
|
|
218
|
+
const r = SemanticRetriever.create({
|
|
219
|
+
index: buildIndex([]),
|
|
220
|
+
backend: { vectorIndex: new FakeVectorIndex([]), embedQuery: okEmbed }
|
|
221
|
+
}).orThrow();
|
|
222
|
+
expect(r.capabilities.supportsSemanticRecall).toBe(true);
|
|
223
|
+
});
|
|
224
|
+
test('degrades loudly on a semantic request when no backend is wired', async () => {
|
|
225
|
+
const r = SemanticRetriever.create({ index: buildIndex([{ id: 'a' }]) }).orThrow();
|
|
226
|
+
expect(await r.retrieve({ semantic: 'hi' })).toFailWith(/semantic recall requires a vector index; none configured/i);
|
|
227
|
+
});
|
|
228
|
+
test('returns empty (not failure) when no semantic term is supplied', async () => {
|
|
229
|
+
const r = SemanticRetriever.create({ index: buildIndex([{ id: 'a' }]) }).orThrow();
|
|
230
|
+
expect(await r.retrieve({})).toSucceedWith([]);
|
|
231
|
+
});
|
|
232
|
+
test('degrades loudly on an as-of request', async () => {
|
|
233
|
+
const r = SemanticRetriever.create({ index: buildIndex([{ id: 'a' }]) }).orThrow();
|
|
234
|
+
expect(await r.retrieve({ asOf: 5 })).toFailWith(/temporal query requires temporal index/i);
|
|
235
|
+
});
|
|
236
|
+
test('resolves vector hits back to records in hit order', async () => {
|
|
237
|
+
const index = buildIndex([
|
|
238
|
+
{ id: 'a', updated: 1 },
|
|
239
|
+
{ id: 'b', updated: 99 },
|
|
240
|
+
{ id: 'c', updated: 50 }
|
|
241
|
+
]);
|
|
242
|
+
const r = SemanticRetriever.create({
|
|
243
|
+
index,
|
|
244
|
+
backend: {
|
|
245
|
+
vectorIndex: new FakeVectorIndex([
|
|
246
|
+
{ id: 'b', score: 0.9 },
|
|
247
|
+
{ id: 'a', score: 0.5 }
|
|
248
|
+
]),
|
|
249
|
+
embedQuery: okEmbed
|
|
250
|
+
}
|
|
251
|
+
}).orThrow();
|
|
252
|
+
expect(await r.retrieve({ semantic: 'q' })).toSucceedAndSatisfy((records) => {
|
|
253
|
+
// Hit order preserved (b before a), NOT recency order.
|
|
254
|
+
expect(ids(records)).toEqual(['b', 'a']);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
test('drops hits not present in the index and applies query filters', async () => {
|
|
258
|
+
const index = buildIndex([
|
|
259
|
+
{ id: 'a', tags: ['x'] },
|
|
260
|
+
{ id: 'b', tags: ['y'] }
|
|
261
|
+
]);
|
|
262
|
+
const r = SemanticRetriever.create({
|
|
263
|
+
index,
|
|
264
|
+
backend: {
|
|
265
|
+
vectorIndex: new FakeVectorIndex([
|
|
266
|
+
{ id: 'gone', score: 0.99 },
|
|
267
|
+
{ id: 'b', score: 0.8 },
|
|
268
|
+
{ id: 'a', score: 0.7 }
|
|
269
|
+
]),
|
|
270
|
+
embedQuery: okEmbed
|
|
271
|
+
}
|
|
272
|
+
}).orThrow();
|
|
273
|
+
expect(await r.retrieve({ semantic: 'q', tag: 'x' })).toSucceedAndSatisfy((records) => {
|
|
274
|
+
expect(ids(records)).toEqual(['a']);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
test('forwards topK to the vector index and applies the post-filter limit', async () => {
|
|
278
|
+
const index = buildIndex([{ id: 'a' }, { id: 'b' }, { id: 'c' }]);
|
|
279
|
+
const vectorIndex = new FakeVectorIndex([
|
|
280
|
+
{ id: 'a', score: 0.9 },
|
|
281
|
+
{ id: 'b', score: 0.8 },
|
|
282
|
+
{ id: 'c', score: 0.7 }
|
|
283
|
+
]);
|
|
284
|
+
const r = SemanticRetriever.create({ index, backend: { vectorIndex, embedQuery: okEmbed } }).orThrow();
|
|
285
|
+
expect(await r.retrieve({ semantic: 'q', topK: 2, limit: 2 })).toSucceedAndSatisfy((records) => {
|
|
286
|
+
expect(ids(records)).toEqual(['a', 'b']);
|
|
287
|
+
});
|
|
288
|
+
// The retriever must forward the caller's topK to the vector backend, not its default.
|
|
289
|
+
expect(vectorIndex.lastTopK).toBe(2);
|
|
290
|
+
});
|
|
291
|
+
test('defaults topK to 10 when the query omits it', async () => {
|
|
292
|
+
const vectorIndex = new FakeVectorIndex([{ id: 'a', score: 0.9 }]);
|
|
293
|
+
const r = SemanticRetriever.create({
|
|
294
|
+
index: buildIndex([{ id: 'a' }]),
|
|
295
|
+
backend: { vectorIndex, embedQuery: okEmbed }
|
|
296
|
+
}).orThrow();
|
|
297
|
+
expect(await r.retrieve({ semantic: 'q' })).toSucceed();
|
|
298
|
+
expect(vectorIndex.lastTopK).toBe(10);
|
|
299
|
+
});
|
|
300
|
+
test('fails loudly when the embedder fails', async () => {
|
|
301
|
+
const r = SemanticRetriever.create({
|
|
302
|
+
index: buildIndex([{ id: 'a' }]),
|
|
303
|
+
backend: {
|
|
304
|
+
vectorIndex: new FakeVectorIndex([]),
|
|
305
|
+
embedQuery: () => Promise.resolve(fail('no embed model'))
|
|
306
|
+
}
|
|
307
|
+
}).orThrow();
|
|
308
|
+
expect(await r.retrieve({ semantic: 'q' })).toFailWith(/query embedding failed: no embed model/i);
|
|
309
|
+
});
|
|
310
|
+
test('fails loudly when the vector backend fails', async () => {
|
|
311
|
+
const r = SemanticRetriever.create({
|
|
312
|
+
index: buildIndex([{ id: 'a' }]),
|
|
313
|
+
backend: { vectorIndex: new FakeVectorIndex([], true), embedQuery: okEmbed }
|
|
314
|
+
}).orThrow();
|
|
315
|
+
expect(await r.retrieve({ semantic: 'q' })).toFailWith(/vector query failed: vector backend down/i);
|
|
316
|
+
});
|
|
317
|
+
test('normalizes a rejecting embedder into a Failure (never escapes as a rejection)', async () => {
|
|
318
|
+
const r = SemanticRetriever.create({
|
|
319
|
+
index: buildIndex([{ id: 'a' }]),
|
|
320
|
+
backend: {
|
|
321
|
+
vectorIndex: new FakeVectorIndex([]),
|
|
322
|
+
embedQuery: () => Promise.reject(new Error('embedder blew up'))
|
|
323
|
+
}
|
|
324
|
+
}).orThrow();
|
|
325
|
+
expect(await r.retrieve({ semantic: 'q' })).toFailWith(/query embedding failed: .*embedder blew up/i);
|
|
326
|
+
});
|
|
327
|
+
test('normalizes a rejecting vector backend into a Failure', async () => {
|
|
328
|
+
// A vector index whose `query` rejects (throws) rather than returning a fail.
|
|
329
|
+
const rejectingIndex = {
|
|
330
|
+
add: (id) => Promise.resolve(succeed(`ref-${id}`)),
|
|
331
|
+
remove: (id) => Promise.resolve(succeed(id)),
|
|
332
|
+
query: () => Promise.reject(new Error('socket hangup'))
|
|
333
|
+
};
|
|
334
|
+
const r = SemanticRetriever.create({
|
|
335
|
+
index: buildIndex([{ id: 'a' }]),
|
|
336
|
+
backend: { vectorIndex: rejectingIndex, embedQuery: okEmbed }
|
|
337
|
+
}).orThrow();
|
|
338
|
+
expect(await r.retrieve({ semantic: 'q' })).toFailWith(/vector query failed: .*socket hangup/i);
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
describe('ScoreUnionMergeStrategy', () => {
|
|
342
|
+
test('unions, dedupes by id, and scores by retriever count', () => {
|
|
343
|
+
const strategy = ScoreUnionMergeStrategy.create().orThrow();
|
|
344
|
+
const [a, b, c] = [
|
|
345
|
+
makeEntry({ id: 'a', updated: 10 }).record,
|
|
346
|
+
makeEntry({ id: 'b', updated: 30 }).record,
|
|
347
|
+
makeEntry({ id: 'c', updated: 20 }).record
|
|
348
|
+
];
|
|
349
|
+
// 'b' appears in two sets (score 2); 'a' and 'c' in one each (score 1) — among
|
|
350
|
+
// score-1 records, recency breaks the tie (c.updated 20 > a.updated 10).
|
|
351
|
+
expect(strategy.merge([
|
|
352
|
+
[a, b],
|
|
353
|
+
[b, c]
|
|
354
|
+
])).toSucceedAndSatisfy((records) => {
|
|
355
|
+
expect(ids(records)).toEqual(['b', 'c', 'a']);
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
test('counts an id at most once per result set', () => {
|
|
359
|
+
const strategy = ScoreUnionMergeStrategy.create().orThrow();
|
|
360
|
+
const a = makeEntry({ id: 'a', updated: 10 }).record;
|
|
361
|
+
const b = makeEntry({ id: 'b', updated: 5 }).record;
|
|
362
|
+
// 'a' twice in one set still scores 1; 'b' in two sets scores 2 → b ranks first.
|
|
363
|
+
expect(strategy.merge([[a, a], [b], [b]])).toSucceedAndSatisfy((records) => {
|
|
364
|
+
expect(ids(records)).toEqual(['b', 'a']);
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
describe('HybridRetriever', () => {
|
|
369
|
+
function build() {
|
|
370
|
+
return {
|
|
371
|
+
index: buildIndex([
|
|
372
|
+
{ id: 'a', tags: ['x'], updated: 10 },
|
|
373
|
+
{ id: 'b', tags: ['y'], updated: 30 },
|
|
374
|
+
{ id: 'c', tags: ['x'], updated: 20 }
|
|
375
|
+
])
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
test('requires at least one retriever', () => {
|
|
379
|
+
const strategy = ScoreUnionMergeStrategy.create().orThrow();
|
|
380
|
+
expect(HybridRetriever.create([], strategy)).toFailWith(/at least one retriever is required/i);
|
|
381
|
+
});
|
|
382
|
+
test('capabilities are the union of composed retrievers', () => {
|
|
383
|
+
const { index } = build();
|
|
384
|
+
const recency = RecencyRetriever.create(index).orThrow();
|
|
385
|
+
const semantic = SemanticRetriever.create({
|
|
386
|
+
index,
|
|
387
|
+
backend: {
|
|
388
|
+
vectorIndex: new FakeVectorIndex([]),
|
|
389
|
+
embedQuery: () => Promise.resolve(succeed(Float32Array.from([1])))
|
|
390
|
+
}
|
|
391
|
+
}).orThrow();
|
|
392
|
+
const hybrid = HybridRetriever.create([recency, semantic], ScoreUnionMergeStrategy.create().orThrow()).orThrow();
|
|
393
|
+
expect(hybrid.capabilities).toEqual({
|
|
394
|
+
supportsSemanticRecall: true,
|
|
395
|
+
supportsTemporalQuery: false,
|
|
396
|
+
supportsLinkTraversal: false
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
test('merges the results of composed retrievers', async () => {
|
|
400
|
+
const { index } = build();
|
|
401
|
+
const recency = RecencyRetriever.create(index).orThrow();
|
|
402
|
+
const tag = TagRetriever.create(index).orThrow();
|
|
403
|
+
const hybrid = HybridRetriever.create([recency, tag], ScoreUnionMergeStrategy.create().orThrow()).orThrow();
|
|
404
|
+
// recency returns a,b,c (with tag x filter: a,c); tag x returns a,c. Both
|
|
405
|
+
// surface a & c (score 2), b absent. Use tag:x so recency also filters to x.
|
|
406
|
+
expect(await hybrid.retrieve({ tag: 'x' })).toSucceedAndSatisfy((records) => {
|
|
407
|
+
expect([...ids(records)].sort()).toEqual(['a', 'c']);
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
test('routes a semantic query only to the semantic-capable child', async () => {
|
|
411
|
+
const { index } = build();
|
|
412
|
+
const recency = RecencyRetriever.create(index).orThrow();
|
|
413
|
+
const semantic = SemanticRetriever.create({
|
|
414
|
+
index,
|
|
415
|
+
backend: {
|
|
416
|
+
vectorIndex: new FakeVectorIndex([{ id: 'b', score: 0.9 }]),
|
|
417
|
+
embedQuery: () => Promise.resolve(succeed(Float32Array.from([1])))
|
|
418
|
+
}
|
|
419
|
+
}).orThrow();
|
|
420
|
+
const hybrid = HybridRetriever.create([recency, semantic], ScoreUnionMergeStrategy.create().orThrow()).orThrow();
|
|
421
|
+
// recency (no semantic support) gets the semantic field stripped → returns all
|
|
422
|
+
// three; semantic returns just b. b therefore scores 2 and ranks first.
|
|
423
|
+
expect(await hybrid.retrieve({ semantic: 'q' })).toSucceedAndSatisfy((records) => {
|
|
424
|
+
expect(records[0].envelope.id).toBe('b');
|
|
425
|
+
expect([...ids(records)].sort()).toEqual(['a', 'b', 'c']);
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
test('routes link-traversal axes only to the link-capable child', async () => {
|
|
429
|
+
var _a, _b, _c, _d;
|
|
430
|
+
const linkChild = new RecordingRetriever({
|
|
431
|
+
supportsSemanticRecall: false,
|
|
432
|
+
supportsTemporalQuery: false,
|
|
433
|
+
supportsLinkTraversal: true
|
|
434
|
+
});
|
|
435
|
+
const plainChild = new RecordingRetriever({
|
|
436
|
+
supportsSemanticRecall: false,
|
|
437
|
+
supportsTemporalQuery: false,
|
|
438
|
+
supportsLinkTraversal: false
|
|
439
|
+
});
|
|
440
|
+
const hybrid = HybridRetriever.create([linkChild, plainChild], ScoreUnionMergeStrategy.create().orThrow()).orThrow();
|
|
441
|
+
expect(hybrid.capabilities.supportsLinkTraversal).toBe(true);
|
|
442
|
+
// Union supports link traversal, so the hybrid does NOT loud-fail.
|
|
443
|
+
expect(await hybrid.retrieve({ linkedTo: 'a', hops: 2 })).toSucceed();
|
|
444
|
+
// The link-capable child keeps the axes; the plain child has them stripped.
|
|
445
|
+
expect((_a = linkChild.lastQuery) === null || _a === void 0 ? void 0 : _a.linkedTo).toBe('a');
|
|
446
|
+
expect((_b = linkChild.lastQuery) === null || _b === void 0 ? void 0 : _b.hops).toBe(2);
|
|
447
|
+
expect((_c = plainChild.lastQuery) === null || _c === void 0 ? void 0 : _c.linkedTo).toBeUndefined();
|
|
448
|
+
expect((_d = plainChild.lastQuery) === null || _d === void 0 ? void 0 : _d.hops).toBeUndefined();
|
|
449
|
+
});
|
|
450
|
+
test('degrades loudly when its union does not support a requested capability', async () => {
|
|
451
|
+
const { index } = build();
|
|
452
|
+
const recency = RecencyRetriever.create(index).orThrow();
|
|
453
|
+
const hybrid = HybridRetriever.create([recency], ScoreUnionMergeStrategy.create().orThrow()).orThrow();
|
|
454
|
+
expect(await hybrid.retrieve({ semantic: 'q' })).toFailWith(/semantic recall requires/i);
|
|
455
|
+
});
|
|
456
|
+
test('propagates a child failure rather than dropping it silently', async () => {
|
|
457
|
+
const { index } = build();
|
|
458
|
+
const semantic = SemanticRetriever.create({
|
|
459
|
+
index,
|
|
460
|
+
backend: {
|
|
461
|
+
vectorIndex: new FakeVectorIndex([], true),
|
|
462
|
+
embedQuery: () => Promise.resolve(succeed(Float32Array.from([1])))
|
|
463
|
+
}
|
|
464
|
+
}).orThrow();
|
|
465
|
+
const hybrid = HybridRetriever.create([semantic], ScoreUnionMergeStrategy.create().orThrow()).orThrow();
|
|
466
|
+
expect(await hybrid.retrieve({ semantic: 'q' })).toFailWith(/vector query failed/i);
|
|
467
|
+
});
|
|
468
|
+
test('does not pre-truncate child result sets: limit is applied only post-merge', async () => {
|
|
469
|
+
// Recency full order is [d,c,b,a]; semantic surfaces [d,a]. With a correct
|
|
470
|
+
// (post-merge-only) limit, the merge sees BOTH full sets, so d and a each
|
|
471
|
+
// score 2 and the limit-2 result is [d,a]. If the limit leaked into the
|
|
472
|
+
// children, recency would pre-truncate to [d,c] and the result would wrongly
|
|
473
|
+
// be [d,c] — a never reaching the merge to score 2.
|
|
474
|
+
const index = buildIndex([
|
|
475
|
+
{ id: 'a', updated: 10 },
|
|
476
|
+
{ id: 'b', updated: 20 },
|
|
477
|
+
{ id: 'c', updated: 30 },
|
|
478
|
+
{ id: 'd', updated: 40 }
|
|
479
|
+
]);
|
|
480
|
+
const recency = RecencyRetriever.create(index).orThrow();
|
|
481
|
+
const semantic = SemanticRetriever.create({
|
|
482
|
+
index,
|
|
483
|
+
backend: {
|
|
484
|
+
vectorIndex: new FakeVectorIndex([
|
|
485
|
+
{ id: 'd', score: 0.9 },
|
|
486
|
+
{ id: 'a', score: 0.8 }
|
|
487
|
+
]),
|
|
488
|
+
embedQuery: () => Promise.resolve(succeed(Float32Array.from([1])))
|
|
489
|
+
}
|
|
490
|
+
}).orThrow();
|
|
491
|
+
const hybrid = HybridRetriever.create([recency, semantic], ScoreUnionMergeStrategy.create().orThrow()).orThrow();
|
|
492
|
+
expect(await hybrid.retrieve({ semantic: 'q', limit: 2 })).toSucceedAndSatisfy((records) => {
|
|
493
|
+
expect(ids(records)).toEqual(['d', 'a']);
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
test('applies the hybrid-level limit after merge', async () => {
|
|
497
|
+
const { index } = build();
|
|
498
|
+
const recency = RecencyRetriever.create(index).orThrow();
|
|
499
|
+
const hybrid = HybridRetriever.create([recency], ScoreUnionMergeStrategy.create().orThrow()).orThrow();
|
|
500
|
+
expect(await hybrid.retrieve({ limit: 1 })).toSucceedAndSatisfy((records) => {
|
|
501
|
+
expect(records).toHaveLength(1);
|
|
502
|
+
expect(records[0].envelope.id).toBe('b');
|
|
503
|
+
});
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
//# sourceMappingURL=retrievers.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retrievers.test.js","sourceRoot":"","sources":["../../../../src/test/unit/retrieve/retrievers.test.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport '@fgv/ts-utils-jest';\nimport { Result, fail, succeed } from '@fgv/ts-utils';\nimport {\n HybridRetriever,\n IIndexedMemoryRecord,\n IMemoryQuery,\n IMemoryRecord,\n IMemoryRetriever,\n IMemoryRetrieverCapabilities,\n IVectorIndex,\n IVectorQueryHit,\n Kind,\n MemoryId,\n MemoryIndex,\n MemoryScopeKey,\n RecencyRetriever,\n ScoreUnionMergeStrategy,\n SemanticRetriever,\n StructuredFilterRetriever,\n Tag,\n TagRetriever,\n envelopeConverter,\n guardRetrieverCapabilities,\n limitRecords\n} from '../../../index';\n\ninterface IRecordSpec {\n readonly id: string;\n readonly scope?: string;\n readonly kind?: string;\n readonly tags?: ReadonlyArray<string>;\n readonly updated?: number;\n readonly seq?: number;\n}\n\nfunction makeEntry(spec: IRecordSpec): IIndexedMemoryRecord {\n const record: IMemoryRecord<unknown> = {\n envelope: envelopeConverter\n .convert({\n id: spec.id,\n entityId: spec.id,\n kind: spec.kind ?? 'knowledge',\n tags: spec.tags ?? [],\n links: [],\n created: 0,\n updated: spec.updated ?? 0,\n seq: spec.seq ?? 0,\n contentHash: 'h',\n provenance: { source: 'agent' }\n })\n .orThrow(),\n body: `body-${spec.id}`\n };\n return { scope: (spec.scope ?? 'knowledge') as MemoryScopeKey, record };\n}\n\nfunction buildIndex(specs: ReadonlyArray<IRecordSpec>): MemoryIndex {\n const index = MemoryIndex.create().orThrow();\n index.rebuild(specs.map(makeEntry)).orThrow();\n return index;\n}\n\nfunction ids(records: ReadonlyArray<IMemoryRecord<unknown>>): ReadonlyArray<string> {\n return records.map((r) => r.envelope.id as string);\n}\n\nconst knowledge: Kind = 'knowledge' as Kind;\n\ndescribe('retrieve helpers', () => {\n describe('guardRetrieverCapabilities', () => {\n const noCaps = {\n supportsSemanticRecall: false,\n supportsTemporalQuery: false,\n supportsLinkTraversal: false\n };\n\n test('passes when no unsupported capability is requested', () => {\n expect(guardRetrieverCapabilities({}, noCaps)).toSucceed();\n });\n\n test('fails loudly when semantic recall is requested but unsupported', () => {\n expect(guardRetrieverCapabilities({ semantic: 'q' }, noCaps)).toFailWith(\n /semantic recall requires a vector index; none configured/i\n );\n });\n\n test('fails loudly when an as-of query is requested but unsupported', () => {\n expect(guardRetrieverCapabilities({ asOf: 5 }, noCaps)).toFailWith(\n /temporal query requires temporal index; none configured/i\n );\n });\n\n test('names the kind in the temporal failure when present', () => {\n expect(guardRetrieverCapabilities({ asOf: 5, kind: knowledge }, noCaps)).toFailWith(\n /none configured for kind knowledge/i\n );\n });\n\n test('fails loudly when a link-traversal axis is requested but unsupported', () => {\n expect(guardRetrieverCapabilities({ linkedTo: 'x' as MemoryId }, noCaps)).toFailWith(\n /link traversal requires a backlink index; none configured/i\n );\n expect(guardRetrieverCapabilities({ linkedFrom: 'x' as MemoryId }, noCaps)).toFailWith(\n /link traversal requires/i\n );\n expect(guardRetrieverCapabilities({ hops: 2 }, noCaps)).toFailWith(/link traversal requires/i);\n });\n\n test('passes a link-traversal axis when the capability is supported', () => {\n expect(\n guardRetrieverCapabilities({ linkedTo: 'x' as MemoryId }, { ...noCaps, supportsLinkTraversal: true })\n ).toSucceed();\n });\n });\n\n describe('limitRecords', () => {\n const records = buildIndex([{ id: 'a' }, { id: 'b' }, { id: 'c' }])\n .entries()\n .map((e) => e.record);\n\n test('returns all records when no limit is supplied', () => {\n expect(limitRecords(records)).toHaveLength(3);\n });\n\n test('truncates to the top-N for a positive limit', () => {\n expect(limitRecords(records, 2)).toHaveLength(2);\n });\n\n test('returns an empty array for a zero or negative limit', () => {\n expect(limitRecords(records, 0)).toEqual([]);\n expect(limitRecords(records, -1)).toEqual([]);\n });\n });\n});\n\ndescribe('RecencyRetriever', () => {\n test('reports all-false capabilities', () => {\n const r = RecencyRetriever.create(buildIndex([])).orThrow();\n expect(r.capabilities).toEqual({\n supportsSemanticRecall: false,\n supportsTemporalQuery: false,\n supportsLinkTraversal: false\n });\n });\n\n test('returns every record most-recently-updated first', async () => {\n const r = RecencyRetriever.create(\n buildIndex([\n { id: 'a', updated: 10 },\n { id: 'b', updated: 30 },\n { id: 'c', updated: 20 }\n ])\n ).orThrow();\n expect(await r.retrieve({})).toSucceedAndSatisfy((records: ReadonlyArray<IMemoryRecord<unknown>>) => {\n expect(ids(records)).toEqual(['b', 'c', 'a']);\n });\n });\n\n test('breaks ties on seq descending', async () => {\n const r = RecencyRetriever.create(\n buildIndex([\n { id: 'a', updated: 10, seq: 1 },\n { id: 'b', updated: 10, seq: 3 },\n { id: 'c', updated: 10, seq: 2 }\n ])\n ).orThrow();\n expect(await r.retrieve({})).toSucceedAndSatisfy((records: ReadonlyArray<IMemoryRecord<unknown>>) => {\n expect(ids(records)).toEqual(['b', 'c', 'a']);\n });\n });\n\n test('honors scope, kind, tag, predicate, and limit filters', async () => {\n const index = buildIndex([\n { id: 'a', scope: 'knowledge', kind: 'knowledge', tags: ['x'], updated: 50 },\n { id: 'b', scope: 'knowledge', kind: 'knowledge', tags: ['y'], updated: 40 },\n { id: 'c', scope: 'other', kind: 'knowledge', tags: ['x'], updated: 60 },\n { id: 'd', scope: 'knowledge', kind: 'note', tags: ['x'], updated: 70 }\n ]);\n const r = RecencyRetriever.create(index).orThrow();\n expect(\n await r.retrieve({\n scope: 'knowledge' as MemoryScopeKey,\n kind: knowledge,\n tag: 'x' as Tag,\n filter: (rec) => rec.envelope.id !== ('skip' as MemoryId),\n limit: 5\n })\n ).toSucceedAndSatisfy((records: ReadonlyArray<IMemoryRecord<unknown>>) => {\n expect(ids(records)).toEqual(['a']);\n });\n });\n\n test('limit truncates to the top-N after ordering', async () => {\n const r = RecencyRetriever.create(\n buildIndex([\n { id: 'a', updated: 10 },\n { id: 'b', updated: 30 },\n { id: 'c', updated: 20 }\n ])\n ).orThrow();\n expect(await r.retrieve({ limit: 2 })).toSucceedAndSatisfy(\n (records: ReadonlyArray<IMemoryRecord<unknown>>) => {\n expect(ids(records)).toEqual(['b', 'c']);\n }\n );\n });\n\n test('degrades loudly on a semantic request', async () => {\n const r = RecencyRetriever.create(buildIndex([{ id: 'a' }])).orThrow();\n expect(await r.retrieve({ semantic: 'hello' })).toFailWith(/semantic recall requires a vector index/i);\n });\n\n test('degrades loudly on a link-traversal request', async () => {\n const r = RecencyRetriever.create(buildIndex([{ id: 'a' }])).orThrow();\n expect(await r.retrieve({ linkedTo: 'a' as MemoryId })).toFailWith(/link traversal requires/i);\n });\n});\n\n/** A retriever with configurable capabilities that records the query it received. */\nclass RecordingRetriever implements IMemoryRetriever {\n public lastQuery: IMemoryQuery | undefined;\n public readonly capabilities: IMemoryRetrieverCapabilities;\n public constructor(capabilities: IMemoryRetrieverCapabilities) {\n this.capabilities = capabilities;\n }\n public retrieve(query: IMemoryQuery): Promise<Result<ReadonlyArray<IMemoryRecord<unknown>>>> {\n this.lastQuery = query;\n return Promise.resolve(succeed([]));\n }\n}\n\ndescribe('TagRetriever', () => {\n test('returns records carrying the tag, recency-ordered', async () => {\n const r = TagRetriever.create(\n buildIndex([\n { id: 'a', tags: ['x'], updated: 10 },\n { id: 'b', tags: ['x', 'y'], updated: 30 },\n { id: 'c', tags: ['y'], updated: 20 }\n ])\n ).orThrow();\n expect(await r.retrieve({ tag: 'x' as Tag })).toSucceedAndSatisfy(\n (records: ReadonlyArray<IMemoryRecord<unknown>>) => {\n expect(ids(records)).toEqual(['b', 'a']);\n }\n );\n });\n\n test('returns empty (not failure) when no tag is supplied', async () => {\n const r = TagRetriever.create(buildIndex([{ id: 'a', tags: ['x'] }])).orThrow();\n expect(await r.retrieve({})).toSucceedWith([]);\n });\n\n test('degrades loudly on a semantic request', async () => {\n const r = TagRetriever.create(buildIndex([{ id: 'a', tags: ['x'] }])).orThrow();\n expect(await r.retrieve({ tag: 'x' as Tag, semantic: 'q' })).toFailWith(/semantic recall requires/i);\n });\n});\n\ndescribe('StructuredFilterRetriever', () => {\n test('applies the predicate over the scope/kind/tag pre-filter', async () => {\n const r = StructuredFilterRetriever.create(\n buildIndex([\n { id: 'keep-1', updated: 10 },\n { id: 'drop-1', updated: 30 },\n { id: 'keep-2', updated: 20 }\n ])\n ).orThrow();\n expect(\n await r.retrieve({ filter: (rec) => (rec.envelope.id as string).startsWith('keep') })\n ).toSucceedAndSatisfy((records: ReadonlyArray<IMemoryRecord<unknown>>) => {\n expect(ids(records)).toEqual(['keep-2', 'keep-1']);\n });\n });\n\n test('returns empty (not failure) when no predicate is supplied', async () => {\n const r = StructuredFilterRetriever.create(buildIndex([{ id: 'a' }])).orThrow();\n expect(await r.retrieve({})).toSucceedWith([]);\n });\n});\n\n/** A scripted in-memory vector index returning canned hits. */\nclass FakeVectorIndex implements IVectorIndex {\n private readonly _hits: ReadonlyArray<IVectorQueryHit>;\n private readonly _failQuery: boolean;\n /** The `topK` the most recent `query` call received — asserts forwarding. */\n public lastTopK: number | undefined;\n public constructor(hits: ReadonlyArray<IVectorQueryHit>, failQuery: boolean = false) {\n this._hits = hits;\n this._failQuery = failQuery;\n }\n public add(id: MemoryId): Promise<Result<string>> {\n return Promise.resolve(succeed(`ref-${id}`));\n }\n public remove(id: MemoryId): Promise<Result<MemoryId>> {\n return Promise.resolve(succeed(id));\n }\n public query(__vector: Float32Array, topK: number): Promise<Result<ReadonlyArray<IVectorQueryHit>>> {\n this.lastTopK = topK;\n return Promise.resolve(this._failQuery ? fail('vector backend down') : succeed(this._hits));\n }\n}\n\ndescribe('SemanticRetriever', () => {\n const okEmbed = (): Promise<Result<Float32Array>> =>\n Promise.resolve(succeed(Float32Array.from([0.1, 0.2])));\n\n test('reports supportsSemanticRecall=false when no backend is wired', () => {\n const r = SemanticRetriever.create({ index: buildIndex([]) }).orThrow();\n expect(r.capabilities.supportsSemanticRecall).toBe(false);\n });\n\n test('reports supportsSemanticRecall=true when a backend is wired', () => {\n const r = SemanticRetriever.create({\n index: buildIndex([]),\n backend: { vectorIndex: new FakeVectorIndex([]), embedQuery: okEmbed }\n }).orThrow();\n expect(r.capabilities.supportsSemanticRecall).toBe(true);\n });\n\n test('degrades loudly on a semantic request when no backend is wired', async () => {\n const r = SemanticRetriever.create({ index: buildIndex([{ id: 'a' }]) }).orThrow();\n expect(await r.retrieve({ semantic: 'hi' })).toFailWith(\n /semantic recall requires a vector index; none configured/i\n );\n });\n\n test('returns empty (not failure) when no semantic term is supplied', async () => {\n const r = SemanticRetriever.create({ index: buildIndex([{ id: 'a' }]) }).orThrow();\n expect(await r.retrieve({})).toSucceedWith([]);\n });\n\n test('degrades loudly on an as-of request', async () => {\n const r = SemanticRetriever.create({ index: buildIndex([{ id: 'a' }]) }).orThrow();\n expect(await r.retrieve({ asOf: 5 })).toFailWith(/temporal query requires temporal index/i);\n });\n\n test('resolves vector hits back to records in hit order', async () => {\n const index = buildIndex([\n { id: 'a', updated: 1 },\n { id: 'b', updated: 99 },\n { id: 'c', updated: 50 }\n ]);\n const r = SemanticRetriever.create({\n index,\n backend: {\n vectorIndex: new FakeVectorIndex([\n { id: 'b' as MemoryId, score: 0.9 },\n { id: 'a' as MemoryId, score: 0.5 }\n ]),\n embedQuery: okEmbed\n }\n }).orThrow();\n expect(await r.retrieve({ semantic: 'q' })).toSucceedAndSatisfy(\n (records: ReadonlyArray<IMemoryRecord<unknown>>) => {\n // Hit order preserved (b before a), NOT recency order.\n expect(ids(records)).toEqual(['b', 'a']);\n }\n );\n });\n\n test('drops hits not present in the index and applies query filters', async () => {\n const index = buildIndex([\n { id: 'a', tags: ['x'] },\n { id: 'b', tags: ['y'] }\n ]);\n const r = SemanticRetriever.create({\n index,\n backend: {\n vectorIndex: new FakeVectorIndex([\n { id: 'gone' as MemoryId, score: 0.99 },\n { id: 'b' as MemoryId, score: 0.8 },\n { id: 'a' as MemoryId, score: 0.7 }\n ]),\n embedQuery: okEmbed\n }\n }).orThrow();\n expect(await r.retrieve({ semantic: 'q', tag: 'x' as Tag })).toSucceedAndSatisfy(\n (records: ReadonlyArray<IMemoryRecord<unknown>>) => {\n expect(ids(records)).toEqual(['a']);\n }\n );\n });\n\n test('forwards topK to the vector index and applies the post-filter limit', async () => {\n const index = buildIndex([{ id: 'a' }, { id: 'b' }, { id: 'c' }]);\n const vectorIndex = new FakeVectorIndex([\n { id: 'a' as MemoryId, score: 0.9 },\n { id: 'b' as MemoryId, score: 0.8 },\n { id: 'c' as MemoryId, score: 0.7 }\n ]);\n const r = SemanticRetriever.create({ index, backend: { vectorIndex, embedQuery: okEmbed } }).orThrow();\n expect(await r.retrieve({ semantic: 'q', topK: 2, limit: 2 })).toSucceedAndSatisfy(\n (records: ReadonlyArray<IMemoryRecord<unknown>>) => {\n expect(ids(records)).toEqual(['a', 'b']);\n }\n );\n // The retriever must forward the caller's topK to the vector backend, not its default.\n expect(vectorIndex.lastTopK).toBe(2);\n });\n\n test('defaults topK to 10 when the query omits it', async () => {\n const vectorIndex = new FakeVectorIndex([{ id: 'a' as MemoryId, score: 0.9 }]);\n const r = SemanticRetriever.create({\n index: buildIndex([{ id: 'a' }]),\n backend: { vectorIndex, embedQuery: okEmbed }\n }).orThrow();\n expect(await r.retrieve({ semantic: 'q' })).toSucceed();\n expect(vectorIndex.lastTopK).toBe(10);\n });\n\n test('fails loudly when the embedder fails', async () => {\n const r = SemanticRetriever.create({\n index: buildIndex([{ id: 'a' }]),\n backend: {\n vectorIndex: new FakeVectorIndex([]),\n embedQuery: () => Promise.resolve(fail('no embed model'))\n }\n }).orThrow();\n expect(await r.retrieve({ semantic: 'q' })).toFailWith(/query embedding failed: no embed model/i);\n });\n\n test('fails loudly when the vector backend fails', async () => {\n const r = SemanticRetriever.create({\n index: buildIndex([{ id: 'a' }]),\n backend: { vectorIndex: new FakeVectorIndex([], true), embedQuery: okEmbed }\n }).orThrow();\n expect(await r.retrieve({ semantic: 'q' })).toFailWith(/vector query failed: vector backend down/i);\n });\n\n test('normalizes a rejecting embedder into a Failure (never escapes as a rejection)', async () => {\n const r = SemanticRetriever.create({\n index: buildIndex([{ id: 'a' }]),\n backend: {\n vectorIndex: new FakeVectorIndex([]),\n embedQuery: () => Promise.reject(new Error('embedder blew up'))\n }\n }).orThrow();\n expect(await r.retrieve({ semantic: 'q' })).toFailWith(/query embedding failed: .*embedder blew up/i);\n });\n\n test('normalizes a rejecting vector backend into a Failure', async () => {\n // A vector index whose `query` rejects (throws) rather than returning a fail.\n const rejectingIndex: IVectorIndex = {\n add: (id: MemoryId) => Promise.resolve(succeed(`ref-${id}`)),\n remove: (id: MemoryId) => Promise.resolve(succeed(id)),\n query: () => Promise.reject(new Error('socket hangup'))\n };\n const r = SemanticRetriever.create({\n index: buildIndex([{ id: 'a' }]),\n backend: { vectorIndex: rejectingIndex, embedQuery: okEmbed }\n }).orThrow();\n expect(await r.retrieve({ semantic: 'q' })).toFailWith(/vector query failed: .*socket hangup/i);\n });\n});\n\ndescribe('ScoreUnionMergeStrategy', () => {\n test('unions, dedupes by id, and scores by retriever count', () => {\n const strategy = ScoreUnionMergeStrategy.create().orThrow();\n const [a, b, c] = [\n makeEntry({ id: 'a', updated: 10 }).record,\n makeEntry({ id: 'b', updated: 30 }).record,\n makeEntry({ id: 'c', updated: 20 }).record\n ];\n // 'b' appears in two sets (score 2); 'a' and 'c' in one each (score 1) — among\n // score-1 records, recency breaks the tie (c.updated 20 > a.updated 10).\n expect(\n strategy.merge([\n [a, b],\n [b, c]\n ])\n ).toSucceedAndSatisfy((records: ReadonlyArray<IMemoryRecord<unknown>>) => {\n expect(ids(records)).toEqual(['b', 'c', 'a']);\n });\n });\n\n test('counts an id at most once per result set', () => {\n const strategy = ScoreUnionMergeStrategy.create().orThrow();\n const a = makeEntry({ id: 'a', updated: 10 }).record;\n const b = makeEntry({ id: 'b', updated: 5 }).record;\n // 'a' twice in one set still scores 1; 'b' in two sets scores 2 → b ranks first.\n expect(strategy.merge([[a, a], [b], [b]])).toSucceedAndSatisfy(\n (records: ReadonlyArray<IMemoryRecord<unknown>>) => {\n expect(ids(records)).toEqual(['b', 'a']);\n }\n );\n });\n});\n\ndescribe('HybridRetriever', () => {\n function build(): { index: MemoryIndex } {\n return {\n index: buildIndex([\n { id: 'a', tags: ['x'], updated: 10 },\n { id: 'b', tags: ['y'], updated: 30 },\n { id: 'c', tags: ['x'], updated: 20 }\n ])\n };\n }\n\n test('requires at least one retriever', () => {\n const strategy = ScoreUnionMergeStrategy.create().orThrow();\n expect(HybridRetriever.create([], strategy)).toFailWith(/at least one retriever is required/i);\n });\n\n test('capabilities are the union of composed retrievers', () => {\n const { index } = build();\n const recency = RecencyRetriever.create(index).orThrow();\n const semantic = SemanticRetriever.create({\n index,\n backend: {\n vectorIndex: new FakeVectorIndex([]),\n embedQuery: () => Promise.resolve(succeed(Float32Array.from([1])))\n }\n }).orThrow();\n const hybrid = HybridRetriever.create(\n [recency, semantic],\n ScoreUnionMergeStrategy.create().orThrow()\n ).orThrow();\n expect(hybrid.capabilities).toEqual({\n supportsSemanticRecall: true,\n supportsTemporalQuery: false,\n supportsLinkTraversal: false\n });\n });\n\n test('merges the results of composed retrievers', async () => {\n const { index } = build();\n const recency = RecencyRetriever.create(index).orThrow();\n const tag = TagRetriever.create(index).orThrow();\n const hybrid = HybridRetriever.create(\n [recency, tag],\n ScoreUnionMergeStrategy.create().orThrow()\n ).orThrow();\n // recency returns a,b,c (with tag x filter: a,c); tag x returns a,c. Both\n // surface a & c (score 2), b absent. Use tag:x so recency also filters to x.\n expect(await hybrid.retrieve({ tag: 'x' as Tag })).toSucceedAndSatisfy(\n (records: ReadonlyArray<IMemoryRecord<unknown>>) => {\n expect([...ids(records)].sort()).toEqual(['a', 'c']);\n }\n );\n });\n\n test('routes a semantic query only to the semantic-capable child', async () => {\n const { index } = build();\n const recency = RecencyRetriever.create(index).orThrow();\n const semantic = SemanticRetriever.create({\n index,\n backend: {\n vectorIndex: new FakeVectorIndex([{ id: 'b' as MemoryId, score: 0.9 }]),\n embedQuery: () => Promise.resolve(succeed(Float32Array.from([1])))\n }\n }).orThrow();\n const hybrid = HybridRetriever.create(\n [recency, semantic],\n ScoreUnionMergeStrategy.create().orThrow()\n ).orThrow();\n // recency (no semantic support) gets the semantic field stripped → returns all\n // three; semantic returns just b. b therefore scores 2 and ranks first.\n expect(await hybrid.retrieve({ semantic: 'q' })).toSucceedAndSatisfy(\n (records: ReadonlyArray<IMemoryRecord<unknown>>) => {\n expect(records[0].envelope.id).toBe('b');\n expect([...ids(records)].sort()).toEqual(['a', 'b', 'c']);\n }\n );\n });\n\n test('routes link-traversal axes only to the link-capable child', async () => {\n const linkChild = new RecordingRetriever({\n supportsSemanticRecall: false,\n supportsTemporalQuery: false,\n supportsLinkTraversal: true\n });\n const plainChild = new RecordingRetriever({\n supportsSemanticRecall: false,\n supportsTemporalQuery: false,\n supportsLinkTraversal: false\n });\n const hybrid = HybridRetriever.create(\n [linkChild, plainChild],\n ScoreUnionMergeStrategy.create().orThrow()\n ).orThrow();\n expect(hybrid.capabilities.supportsLinkTraversal).toBe(true);\n // Union supports link traversal, so the hybrid does NOT loud-fail.\n expect(await hybrid.retrieve({ linkedTo: 'a' as MemoryId, hops: 2 })).toSucceed();\n // The link-capable child keeps the axes; the plain child has them stripped.\n expect(linkChild.lastQuery?.linkedTo).toBe('a');\n expect(linkChild.lastQuery?.hops).toBe(2);\n expect(plainChild.lastQuery?.linkedTo).toBeUndefined();\n expect(plainChild.lastQuery?.hops).toBeUndefined();\n });\n\n test('degrades loudly when its union does not support a requested capability', async () => {\n const { index } = build();\n const recency = RecencyRetriever.create(index).orThrow();\n const hybrid = HybridRetriever.create([recency], ScoreUnionMergeStrategy.create().orThrow()).orThrow();\n expect(await hybrid.retrieve({ semantic: 'q' })).toFailWith(/semantic recall requires/i);\n });\n\n test('propagates a child failure rather than dropping it silently', async () => {\n const { index } = build();\n const semantic = SemanticRetriever.create({\n index,\n backend: {\n vectorIndex: new FakeVectorIndex([], true),\n embedQuery: () => Promise.resolve(succeed(Float32Array.from([1])))\n }\n }).orThrow();\n const hybrid = HybridRetriever.create([semantic], ScoreUnionMergeStrategy.create().orThrow()).orThrow();\n expect(await hybrid.retrieve({ semantic: 'q' })).toFailWith(/vector query failed/i);\n });\n\n test('does not pre-truncate child result sets: limit is applied only post-merge', async () => {\n // Recency full order is [d,c,b,a]; semantic surfaces [d,a]. With a correct\n // (post-merge-only) limit, the merge sees BOTH full sets, so d and a each\n // score 2 and the limit-2 result is [d,a]. If the limit leaked into the\n // children, recency would pre-truncate to [d,c] and the result would wrongly\n // be [d,c] — a never reaching the merge to score 2.\n const index = buildIndex([\n { id: 'a', updated: 10 },\n { id: 'b', updated: 20 },\n { id: 'c', updated: 30 },\n { id: 'd', updated: 40 }\n ]);\n const recency = RecencyRetriever.create(index).orThrow();\n const semantic = SemanticRetriever.create({\n index,\n backend: {\n vectorIndex: new FakeVectorIndex([\n { id: 'd' as MemoryId, score: 0.9 },\n { id: 'a' as MemoryId, score: 0.8 }\n ]),\n embedQuery: () => Promise.resolve(succeed(Float32Array.from([1])))\n }\n }).orThrow();\n const hybrid = HybridRetriever.create(\n [recency, semantic],\n ScoreUnionMergeStrategy.create().orThrow()\n ).orThrow();\n expect(await hybrid.retrieve({ semantic: 'q', limit: 2 })).toSucceedAndSatisfy(\n (records: ReadonlyArray<IMemoryRecord<unknown>>) => {\n expect(ids(records)).toEqual(['d', 'a']);\n }\n );\n });\n\n test('applies the hybrid-level limit after merge', async () => {\n const { index } = build();\n const recency = RecencyRetriever.create(index).orThrow();\n const hybrid = HybridRetriever.create([recency], ScoreUnionMergeStrategy.create().orThrow()).orThrow();\n expect(await hybrid.retrieve({ limit: 1 })).toSucceedAndSatisfy(\n (records: ReadonlyArray<IMemoryRecord<unknown>>) => {\n expect(records).toHaveLength(1);\n expect(records[0].envelope.id).toBe('b');\n }\n );\n });\n});\n"]}
|