@fgv/ts-agent-memory 5.1.0-50 → 5.1.0-51
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/dist/packlets/retrieve/fragmentSemanticRetriever.js +94 -5
- package/dist/packlets/retrieve/fragmentSemanticRetriever.js.map +1 -1
- package/dist/packlets/store/fileTreeMemoryStore.js +13 -24
- package/dist/packlets/store/fileTreeMemoryStore.js.map +1 -1
- package/dist/packlets/store/memoryStore.js.map +1 -1
- package/dist/packlets/store/storeIdentity.js +62 -0
- package/dist/packlets/store/storeIdentity.js.map +1 -0
- package/dist/packlets/types/identityResolver.js +6 -0
- package/dist/packlets/types/identityResolver.js.map +1 -0
- package/dist/packlets/types/index.js +1 -0
- package/dist/packlets/types/index.js.map +1 -1
- package/dist/packlets/vector/inMemoryFragmentCosineIndex.js +42 -2
- package/dist/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -1
- package/dist/packlets/vector/vectorIndex.js.map +1 -1
- package/dist/ts-agent-memory.d.ts +192 -9
- package/lib/packlets/retrieve/fragmentSemanticRetriever.d.ts +79 -1
- package/lib/packlets/retrieve/fragmentSemanticRetriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/fragmentSemanticRetriever.js +94 -5
- package/lib/packlets/retrieve/fragmentSemanticRetriever.js.map +1 -1
- package/lib/packlets/store/fileTreeMemoryStore.d.ts +4 -2
- package/lib/packlets/store/fileTreeMemoryStore.d.ts.map +1 -1
- package/lib/packlets/store/fileTreeMemoryStore.js +13 -24
- package/lib/packlets/store/fileTreeMemoryStore.js.map +1 -1
- package/lib/packlets/store/memoryStore.d.ts +2 -2
- package/lib/packlets/store/memoryStore.d.ts.map +1 -1
- package/lib/packlets/store/memoryStore.js.map +1 -1
- package/lib/packlets/store/storeIdentity.d.ts +38 -0
- package/lib/packlets/store/storeIdentity.d.ts.map +1 -0
- package/lib/packlets/store/storeIdentity.js +67 -0
- package/lib/packlets/store/storeIdentity.js.map +1 -0
- package/lib/packlets/types/identityResolver.d.ts +42 -0
- package/lib/packlets/types/identityResolver.d.ts.map +1 -0
- package/lib/packlets/types/identityResolver.js +7 -0
- package/lib/packlets/types/identityResolver.js.map +1 -0
- package/lib/packlets/types/index.d.ts +1 -0
- package/lib/packlets/types/index.d.ts.map +1 -1
- package/lib/packlets/types/index.js +1 -0
- package/lib/packlets/types/index.js.map +1 -1
- package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts +13 -2
- package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts.map +1 -1
- package/lib/packlets/vector/inMemoryFragmentCosineIndex.js +42 -2
- package/lib/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -1
- package/lib/packlets/vector/vectorIndex.d.ts +57 -6
- package/lib/packlets/vector/vectorIndex.d.ts.map +1 -1
- package/lib/packlets/vector/vectorIndex.js.map +1 -1
- package/package.json +7 -7
|
@@ -445,6 +445,9 @@ export declare class FileTreeMemoryStore implements IMemoryStore {
|
|
|
445
445
|
listScoped(): Promise<Result<ReadonlyArray<IScopedMemoryRecord>>>;
|
|
446
446
|
/** {@inheritDoc IMemoryRecordResolver.resolveRecord} */
|
|
447
447
|
resolveRecord(scope: MemoryScopeKey, id: MemoryId): Result<IMemoryRecord<unknown> | undefined>;
|
|
448
|
+
private _codec;
|
|
449
|
+
/** {@inheritDoc IIdentityResolver.resolveIdentity} */
|
|
450
|
+
resolveIdentity(kind: Kind, entityId: EntityId): Result<IIdentityCodecResult>;
|
|
448
451
|
/**
|
|
449
452
|
* Materialize a selected set of entries into records, dropping any that have
|
|
450
453
|
* vanished since selection.
|
|
@@ -717,7 +720,6 @@ export declare class FileTreeMemoryStore implements IMemoryStore {
|
|
|
717
720
|
* than preserving it), so a ranking bug never loses an authoritative write.
|
|
718
721
|
*/
|
|
719
722
|
private _stampRank;
|
|
720
|
-
private _codecFor;
|
|
721
723
|
private _policyFor;
|
|
722
724
|
/** {@inheritDoc IMemoryStore.dedupScopeFor} */
|
|
723
725
|
dedupScopeFor(kind: Kind): DedupScope;
|
|
@@ -777,6 +779,28 @@ export declare class FileTreeMemoryStore implements IMemoryStore {
|
|
|
777
779
|
private _loadRecordFile;
|
|
778
780
|
}
|
|
779
781
|
|
|
782
|
+
/**
|
|
783
|
+
* The message returned when exactly one of `entityId` / `kind` is supplied.
|
|
784
|
+
*
|
|
785
|
+
* @remarks
|
|
786
|
+
* They travel together because `kind` is what selects the identity codec, and the
|
|
787
|
+
* codec is what makes the resolution unambiguous. One without the other is not a
|
|
788
|
+
* partial narrowing that could be honored best-effort — it is not a narrowing at all.
|
|
789
|
+
* @public
|
|
790
|
+
*/
|
|
791
|
+
export declare const FRAGMENT_NARROWING_INCOMPLETE_MESSAGE: string;
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* The loud-degradation message returned when a query carries a record narrowing but
|
|
795
|
+
* no {@link IIdentityResolver} is wired to resolve it.
|
|
796
|
+
*
|
|
797
|
+
* @remarks
|
|
798
|
+
* Deliberately a `Failure` rather than a silently-global search: answering a scoped
|
|
799
|
+
* question with an unscoped result is the failure this narrowing exists to remove.
|
|
800
|
+
* @public
|
|
801
|
+
*/
|
|
802
|
+
export declare const FRAGMENT_NARROWING_UNRESOLVABLE_MESSAGE: string;
|
|
803
|
+
|
|
780
804
|
/**
|
|
781
805
|
* The loud-degradation message a {@link FragmentSemanticRetriever} returns when a
|
|
782
806
|
* fragment query is issued but no {@link IFragmentSemanticBackend | backend} is
|
|
@@ -829,18 +853,44 @@ export declare const fragmentLocatorConverter: Converter<IFragmentLocator>;
|
|
|
829
853
|
*/
|
|
830
854
|
export declare class FragmentSemanticRetriever {
|
|
831
855
|
private readonly _backend;
|
|
856
|
+
private readonly _identityResolver;
|
|
832
857
|
private constructor();
|
|
833
858
|
/** What this retriever can do given its wiring. */
|
|
834
859
|
get capabilities(): IFragmentRetrieverCapabilities;
|
|
835
|
-
/**
|
|
860
|
+
/**
|
|
861
|
+
* Family-convention factory.
|
|
862
|
+
*
|
|
863
|
+
* @param params - `backend` wires fragment recall itself. `identityResolver`
|
|
864
|
+
* resolves a query's `(kind, entityId)` narrowing to a storage address;
|
|
865
|
+
* `IMemoryStore` implements it, so the usual wiring is
|
|
866
|
+
* `{ backend, identityResolver: store }`. It is optional because an unscoped
|
|
867
|
+
* fragment search needs nothing to resolve — but a query that *does* carry a
|
|
868
|
+
* narrowing fails loudly without it rather than quietly searching everything.
|
|
869
|
+
*/
|
|
836
870
|
static create(params: {
|
|
837
871
|
readonly backend?: IFragmentSemanticBackend;
|
|
872
|
+
readonly identityResolver?: IIdentityResolver;
|
|
838
873
|
}): Result<FragmentSemanticRetriever>;
|
|
839
874
|
/**
|
|
840
875
|
* Embed `query.semantic`, query the fragment index, and return the per-fragment
|
|
841
876
|
* hits in descending score order. Fails loudly when no backend is wired.
|
|
842
877
|
*/
|
|
843
878
|
retrieve(query: IFragmentQuery): Promise<Result<ReadonlyArray<IVectorQueryHit>>>;
|
|
879
|
+
/**
|
|
880
|
+
* Turn the query's consumer-facing narrowing into the storage-address narrowing
|
|
881
|
+
* the index understands.
|
|
882
|
+
*
|
|
883
|
+
* @remarks
|
|
884
|
+
* `kind` selects the identity codec and the codec computes the record's storage
|
|
885
|
+
* address, so this is a deterministic resolution rather than a search —
|
|
886
|
+
* which is what makes a colliding `entityId` across kinds a non-issue.
|
|
887
|
+
*
|
|
888
|
+
* A **versioned** kind resolves to the entity's own subtree scope and deliberately
|
|
889
|
+
* carries no `id`, so the narrowing covers every version of the entity — including
|
|
890
|
+
* superseded ones, which are invalidated but never pruned from the index. A
|
|
891
|
+
* non-versioned kind resolves to exactly one record.
|
|
892
|
+
*/
|
|
893
|
+
private _resolveOptions;
|
|
844
894
|
/**
|
|
845
895
|
* Invoke a consumer-supplied backend hook, normalizing both a returned `fail`
|
|
846
896
|
* and a thrown/rejected promise into a single `fragment recall: <label> failed`
|
|
@@ -1558,6 +1608,85 @@ export declare interface IFragmentQuery {
|
|
|
1558
1608
|
* Applied during selection (before the `topK` cut). Omit for uncapped.
|
|
1559
1609
|
*/
|
|
1560
1610
|
readonly maxPerRecord?: number;
|
|
1611
|
+
/**
|
|
1612
|
+
* Narrow the search to one record's fragments: the consumer-supplied domain key
|
|
1613
|
+
* of the record to search within. **Must be supplied with
|
|
1614
|
+
* {@link IFragmentQuery.kind}.**
|
|
1615
|
+
*
|
|
1616
|
+
* @remarks
|
|
1617
|
+
* The narrowing is applied **during selection, before the `topK` cut**, so the
|
|
1618
|
+
* `topK` you ask for is the `topK` you get. Filtering a global result afterwards
|
|
1619
|
+
* is not equivalent: it truncates to `topK` across every record first, so a scoped
|
|
1620
|
+
* search would come back short whenever other records outscored this one's
|
|
1621
|
+
* fragments.
|
|
1622
|
+
*
|
|
1623
|
+
* For a versioned kind this narrows to **every version of the entity** — literally
|
|
1624
|
+
* every version, superseded ones included, because invalidation stamps `invalid_at`
|
|
1625
|
+
* without pruning that version's fragments. Nothing on a hit distinguishes a
|
|
1626
|
+
* current fragment from a historical one. That matches the record-granular vector
|
|
1627
|
+
* lane; it is not currency filtering.
|
|
1628
|
+
*/
|
|
1629
|
+
readonly entityId?: EntityId;
|
|
1630
|
+
/**
|
|
1631
|
+
* The kind of the record named by {@link IFragmentQuery.entityId}. **Must be
|
|
1632
|
+
* supplied with it.**
|
|
1633
|
+
*
|
|
1634
|
+
* @remarks
|
|
1635
|
+
* This is not decoration and not a filter: `kind` **selects the identity codec**,
|
|
1636
|
+
* and the codec computes the storage address. An `EntityId` promises no uniqueness
|
|
1637
|
+
* beyond a scope — the same id under two kinds is the ordinary case, not a
|
|
1638
|
+
* pathological one — so without `kind` the resolution is ambiguous, and with it
|
|
1639
|
+
* ambiguity is structurally impossible.
|
|
1640
|
+
*/
|
|
1641
|
+
readonly kind?: Kind;
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
/**
|
|
1645
|
+
* Selection-time narrowing for {@link IFragmentVectorIndex.query}. Every member is
|
|
1646
|
+
* applied **before** the `topK` cut.
|
|
1647
|
+
*
|
|
1648
|
+
* @remarks
|
|
1649
|
+
* `scope` (optionally with `id`) is the record narrowing. It is expressed as a
|
|
1650
|
+
* storage address rather than as a consumer-facing `(kind, entityId)` because that
|
|
1651
|
+
* is what the index is keyed by; resolving one to the other is
|
|
1652
|
+
* `IIdentityResolver.resolveIdentity`'s job, and doing it above the index keeps the
|
|
1653
|
+
* index dealing only in the keys it actually holds.
|
|
1654
|
+
*
|
|
1655
|
+
* **`scope` alone is not a coarser accident — it is what a versioned kind needs.**
|
|
1656
|
+
* `TemporalIdentityCodec` files every version of an entity in its own per-entity
|
|
1657
|
+
* subtree, so for a versioned kind the entity's subtree *is* the narrowing and
|
|
1658
|
+
* `{ scope }` means "every version of this entity". A non-versioned kind resolves to
|
|
1659
|
+
* a single record and supplies `{ scope, id }`.
|
|
1660
|
+
*
|
|
1661
|
+
* **"Every version" is literal, and includes superseded ones.** Invalidation stamps
|
|
1662
|
+
* `invalid_at` on an envelope; it does not prune that version's fragments from the
|
|
1663
|
+
* index. So a scope-narrowed query returns current and historical fragments alike,
|
|
1664
|
+
* and a hit carries nothing that distinguishes them — `IVectorQueryHit` has no
|
|
1665
|
+
* temporal fields. This is the record-granular vector lane's existing behaviour
|
|
1666
|
+
* rather than something this narrowing introduces, but do not read the subtree
|
|
1667
|
+
* narrowing as currency filtering: it is not, and there is no `asOf` axis here to
|
|
1668
|
+
* make it so.
|
|
1669
|
+
* @public
|
|
1670
|
+
*/
|
|
1671
|
+
export declare interface IFragmentQueryOptions {
|
|
1672
|
+
/**
|
|
1673
|
+
* Maximum number of fragments any single record may contribute. Omit for
|
|
1674
|
+
* uncapped. With a single-record narrowing (`scope` + `id`) this caps the one
|
|
1675
|
+
* record's contribution, which is a second cap on the same axis rather than a
|
|
1676
|
+
* cross-record fairness knob — usually you want one or the other, not both.
|
|
1677
|
+
*/
|
|
1678
|
+
readonly maxPerRecord?: number;
|
|
1679
|
+
/**
|
|
1680
|
+
* Restrict the search to fragments of records in this scope. Omit to search every
|
|
1681
|
+
* record.
|
|
1682
|
+
*/
|
|
1683
|
+
readonly scope?: MemoryScopeKey;
|
|
1684
|
+
/**
|
|
1685
|
+
* With {@link IFragmentQueryOptions.scope}, restrict further to the single record
|
|
1686
|
+
* at `(scope, id)`. Ignored — and meaningless — without `scope`, since a bare `id`
|
|
1687
|
+
* does not address a record.
|
|
1688
|
+
*/
|
|
1689
|
+
readonly id?: MemoryId;
|
|
1561
1690
|
}
|
|
1562
1691
|
|
|
1563
1692
|
/**
|
|
@@ -1637,12 +1766,16 @@ export declare interface IFragmentVectorIndex {
|
|
|
1637
1766
|
/**
|
|
1638
1767
|
* Return the `topK` nearest fragments to `vector`, in descending score order,
|
|
1639
1768
|
* each hit carrying its record `target` plus whichever of `locator` /
|
|
1640
|
-
* `fragmentId` the stored fragment was added with.
|
|
1641
|
-
*
|
|
1642
|
-
*
|
|
1643
|
-
* `
|
|
1769
|
+
* `fragmentId` the stored fragment was added with.
|
|
1770
|
+
*
|
|
1771
|
+
* @remarks
|
|
1772
|
+
* **Every member of `options` is applied during selection, before the `topK`
|
|
1773
|
+
* cut.** That ordering is the contract, not an implementation detail: a narrowing
|
|
1774
|
+
* applied afterwards would mean the caller's `topK` is not the `topK` that reached
|
|
1775
|
+
* the index, so a scoped search would be exact only when a global over-fetch
|
|
1776
|
+
* happened to be generous enough. See {@link IFragmentQueryOptions}.
|
|
1644
1777
|
*/
|
|
1645
|
-
query(vector: Float32Array, topK: number,
|
|
1778
|
+
query(vector: Float32Array, topK: number, options?: IFragmentQueryOptions): Promise<Result<ReadonlyArray<IVectorQueryHit>>>;
|
|
1646
1779
|
/**
|
|
1647
1780
|
* Whether this index holds **any** fragment for the scope-qualified `target`.
|
|
1648
1781
|
*
|
|
@@ -1794,6 +1927,45 @@ export declare interface IIdentityCodecResult {
|
|
|
1794
1927
|
readonly isVersioned: boolean;
|
|
1795
1928
|
}
|
|
1796
1929
|
|
|
1930
|
+
/**
|
|
1931
|
+
* Resolves a consumer-facing `(kind, entityId)` address to the storage address the
|
|
1932
|
+
* vault files it under, **without reading the record**.
|
|
1933
|
+
*
|
|
1934
|
+
* @remarks
|
|
1935
|
+
* This is the resolution `IMemoryStore.get(kind, entityId)` already performs before
|
|
1936
|
+
* it reads anything: `kind` selects the kind's `IIdentityCodec`, and the codec's
|
|
1937
|
+
* `encode` computes `{ scope, idStem, isVersioned }`. Exposing it separately lets a
|
|
1938
|
+
* caller that needs only the *address* — a fragment query narrowing to one record,
|
|
1939
|
+
* say — avoid paying for a read it does not want.
|
|
1940
|
+
*
|
|
1941
|
+
* **The resolution is a function, not a search, and that is the load-bearing
|
|
1942
|
+
* property.** A consumer holds an `EntityId`; a vector hit is addressed by a
|
|
1943
|
+
* `(scope, id)` pair; and `EntityId` promises no uniqueness beyond a scope, so the
|
|
1944
|
+
* same id may legitimately appear in several scopes (a document `acme-corp` under
|
|
1945
|
+
* one kind and the entity `acme-corp` under another is the ordinary case, not a
|
|
1946
|
+
* pathological one). Supplying `kind` selects one codec, and a codec cannot return
|
|
1947
|
+
* two answers — so ambiguity is structurally impossible rather than merely unlikely,
|
|
1948
|
+
* and no disambiguation pass is needed anywhere downstream.
|
|
1949
|
+
*
|
|
1950
|
+
* Narrow by design, mirroring `IMemoryRecordResolver`: a component that needs to
|
|
1951
|
+
* turn an entity address into a storage address should depend on this rather than on
|
|
1952
|
+
* the whole store.
|
|
1953
|
+
* @public
|
|
1954
|
+
*/
|
|
1955
|
+
export declare interface IIdentityResolver {
|
|
1956
|
+
/**
|
|
1957
|
+
* The storage address `(kind, entityId)` maps to, without reading the record.
|
|
1958
|
+
*
|
|
1959
|
+
* @param kind - Selects the identity codec. Required: it is what makes the
|
|
1960
|
+
* resolution unambiguous.
|
|
1961
|
+
* @param entityId - The consumer-supplied domain key.
|
|
1962
|
+
* @returns `Success` with the codec's `{ scope, idStem, isVersioned }`, or
|
|
1963
|
+
* `Failure` if no codec is registered for `kind` (and no default is wired) or the
|
|
1964
|
+
* codec rejects the id. Both are caller errors and both are loud.
|
|
1965
|
+
*/
|
|
1966
|
+
resolveIdentity(kind: Kind, entityId: EntityId): Result<IIdentityCodecResult>;
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1797
1969
|
/**
|
|
1798
1970
|
* Coverage of the record-granular vector index.
|
|
1799
1971
|
* @public
|
|
@@ -2608,7 +2780,7 @@ export declare interface IMemoryRetrieverCapabilities {
|
|
|
2608
2780
|
* a consumer actually holds, not only for the concrete `FileTreeMemoryStore`.
|
|
2609
2781
|
* @public
|
|
2610
2782
|
*/
|
|
2611
|
-
export declare interface IMemoryStore extends IMemoryRecordResolver {
|
|
2783
|
+
export declare interface IMemoryStore extends IMemoryRecordResolver, IIdentityResolver {
|
|
2612
2784
|
/**
|
|
2613
2785
|
* Keyed read by entity id. Resolves `entityId` to a storage address via the
|
|
2614
2786
|
* registered {@link IIdentityCodec} for `kind`. Returns `undefined` when no
|
|
@@ -3086,7 +3258,18 @@ export declare class InMemoryFragmentCosineIndex implements IFragmentVectorIndex
|
|
|
3086
3258
|
/** {@inheritDoc IFragmentVectorIndex.remove} */
|
|
3087
3259
|
remove(target: IEdgeTarget): Promise<Result<IEdgeTarget>>;
|
|
3088
3260
|
/** {@inheritDoc IFragmentVectorIndex.query} */
|
|
3089
|
-
query(vector: Float32Array, topK: number,
|
|
3261
|
+
query(vector: Float32Array, topK: number, options?: IFragmentQueryOptions): Promise<Result<ReadonlyArray<IVectorQueryHit>>>;
|
|
3262
|
+
/**
|
|
3263
|
+
* The records a query is allowed to score, honoring the `scope` / `id` narrowing.
|
|
3264
|
+
*
|
|
3265
|
+
* @remarks
|
|
3266
|
+
* The single-record case is an O(1) map lookup rather than a scan, because the
|
|
3267
|
+
* record map is keyed by `edgeTargetKey`. The scope-only case (a versioned kind's
|
|
3268
|
+
* per-entity subtree) is a filtered walk — still bounded by the vault, but it
|
|
3269
|
+
* scores only the entity's own fragments, which is what makes the caller's `topK`
|
|
3270
|
+
* meaningful.
|
|
3271
|
+
*/
|
|
3272
|
+
private _selectRecords;
|
|
3090
3273
|
/**
|
|
3091
3274
|
* Re-embed every record from `source` and rebuild the fragment index from
|
|
3092
3275
|
* scratch. Clears the current contents (and the established dimension) first, so
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Result } from '@fgv/ts-utils';
|
|
2
|
+
import { EntityId, IIdentityResolver, Kind } from '../types';
|
|
2
3
|
import { IFragmentVectorIndex, IVectorQueryHit } from '../vector';
|
|
3
4
|
import { QueryEmbedder } from './semanticRetriever';
|
|
4
5
|
/**
|
|
@@ -8,6 +9,26 @@ import { QueryEmbedder } from './semanticRetriever';
|
|
|
8
9
|
* @public
|
|
9
10
|
*/
|
|
10
11
|
export declare const FRAGMENT_SEMANTIC_UNWIRED_MESSAGE: string;
|
|
12
|
+
/**
|
|
13
|
+
* The loud-degradation message returned when a query carries a record narrowing but
|
|
14
|
+
* no {@link IIdentityResolver} is wired to resolve it.
|
|
15
|
+
*
|
|
16
|
+
* @remarks
|
|
17
|
+
* Deliberately a `Failure` rather than a silently-global search: answering a scoped
|
|
18
|
+
* question with an unscoped result is the failure this narrowing exists to remove.
|
|
19
|
+
* @public
|
|
20
|
+
*/
|
|
21
|
+
export declare const FRAGMENT_NARROWING_UNRESOLVABLE_MESSAGE: string;
|
|
22
|
+
/**
|
|
23
|
+
* The message returned when exactly one of `entityId` / `kind` is supplied.
|
|
24
|
+
*
|
|
25
|
+
* @remarks
|
|
26
|
+
* They travel together because `kind` is what selects the identity codec, and the
|
|
27
|
+
* codec is what makes the resolution unambiguous. One without the other is not a
|
|
28
|
+
* partial narrowing that could be honored best-effort — it is not a narrowing at all.
|
|
29
|
+
* @public
|
|
30
|
+
*/
|
|
31
|
+
export declare const FRAGMENT_NARROWING_INCOMPLETE_MESSAGE: string;
|
|
11
32
|
/**
|
|
12
33
|
* The fragment backend wired into a {@link FragmentSemanticRetriever}: the fragment
|
|
13
34
|
* index to query and the embedder that turns the query text into a vector. Both are
|
|
@@ -36,6 +57,37 @@ export interface IFragmentQuery {
|
|
|
36
57
|
* Applied during selection (before the `topK` cut). Omit for uncapped.
|
|
37
58
|
*/
|
|
38
59
|
readonly maxPerRecord?: number;
|
|
60
|
+
/**
|
|
61
|
+
* Narrow the search to one record's fragments: the consumer-supplied domain key
|
|
62
|
+
* of the record to search within. **Must be supplied with
|
|
63
|
+
* {@link IFragmentQuery.kind}.**
|
|
64
|
+
*
|
|
65
|
+
* @remarks
|
|
66
|
+
* The narrowing is applied **during selection, before the `topK` cut**, so the
|
|
67
|
+
* `topK` you ask for is the `topK` you get. Filtering a global result afterwards
|
|
68
|
+
* is not equivalent: it truncates to `topK` across every record first, so a scoped
|
|
69
|
+
* search would come back short whenever other records outscored this one's
|
|
70
|
+
* fragments.
|
|
71
|
+
*
|
|
72
|
+
* For a versioned kind this narrows to **every version of the entity** — literally
|
|
73
|
+
* every version, superseded ones included, because invalidation stamps `invalid_at`
|
|
74
|
+
* without pruning that version's fragments. Nothing on a hit distinguishes a
|
|
75
|
+
* current fragment from a historical one. That matches the record-granular vector
|
|
76
|
+
* lane; it is not currency filtering.
|
|
77
|
+
*/
|
|
78
|
+
readonly entityId?: EntityId;
|
|
79
|
+
/**
|
|
80
|
+
* The kind of the record named by {@link IFragmentQuery.entityId}. **Must be
|
|
81
|
+
* supplied with it.**
|
|
82
|
+
*
|
|
83
|
+
* @remarks
|
|
84
|
+
* This is not decoration and not a filter: `kind` **selects the identity codec**,
|
|
85
|
+
* and the codec computes the storage address. An `EntityId` promises no uniqueness
|
|
86
|
+
* beyond a scope — the same id under two kinds is the ordinary case, not a
|
|
87
|
+
* pathological one — so without `kind` the resolution is ambiguous, and with it
|
|
88
|
+
* ambiguity is structurally impossible.
|
|
89
|
+
*/
|
|
90
|
+
readonly kind?: Kind;
|
|
39
91
|
}
|
|
40
92
|
/**
|
|
41
93
|
* What a {@link FragmentSemanticRetriever} can do given its wiring.
|
|
@@ -71,18 +123,44 @@ export interface IFragmentRetrieverCapabilities {
|
|
|
71
123
|
*/
|
|
72
124
|
export declare class FragmentSemanticRetriever {
|
|
73
125
|
private readonly _backend;
|
|
126
|
+
private readonly _identityResolver;
|
|
74
127
|
private constructor();
|
|
75
128
|
/** What this retriever can do given its wiring. */
|
|
76
129
|
get capabilities(): IFragmentRetrieverCapabilities;
|
|
77
|
-
/**
|
|
130
|
+
/**
|
|
131
|
+
* Family-convention factory.
|
|
132
|
+
*
|
|
133
|
+
* @param params - `backend` wires fragment recall itself. `identityResolver`
|
|
134
|
+
* resolves a query's `(kind, entityId)` narrowing to a storage address;
|
|
135
|
+
* `IMemoryStore` implements it, so the usual wiring is
|
|
136
|
+
* `{ backend, identityResolver: store }`. It is optional because an unscoped
|
|
137
|
+
* fragment search needs nothing to resolve — but a query that *does* carry a
|
|
138
|
+
* narrowing fails loudly without it rather than quietly searching everything.
|
|
139
|
+
*/
|
|
78
140
|
static create(params: {
|
|
79
141
|
readonly backend?: IFragmentSemanticBackend;
|
|
142
|
+
readonly identityResolver?: IIdentityResolver;
|
|
80
143
|
}): Result<FragmentSemanticRetriever>;
|
|
81
144
|
/**
|
|
82
145
|
* Embed `query.semantic`, query the fragment index, and return the per-fragment
|
|
83
146
|
* hits in descending score order. Fails loudly when no backend is wired.
|
|
84
147
|
*/
|
|
85
148
|
retrieve(query: IFragmentQuery): Promise<Result<ReadonlyArray<IVectorQueryHit>>>;
|
|
149
|
+
/**
|
|
150
|
+
* Turn the query's consumer-facing narrowing into the storage-address narrowing
|
|
151
|
+
* the index understands.
|
|
152
|
+
*
|
|
153
|
+
* @remarks
|
|
154
|
+
* `kind` selects the identity codec and the codec computes the record's storage
|
|
155
|
+
* address, so this is a deterministic resolution rather than a search —
|
|
156
|
+
* which is what makes a colliding `entityId` across kinds a non-issue.
|
|
157
|
+
*
|
|
158
|
+
* A **versioned** kind resolves to the entity's own subtree scope and deliberately
|
|
159
|
+
* carries no `id`, so the narrowing covers every version of the entity — including
|
|
160
|
+
* superseded ones, which are invalidated but never pruned from the index. A
|
|
161
|
+
* non-versioned kind resolves to exactly one record.
|
|
162
|
+
*/
|
|
163
|
+
private _resolveOptions;
|
|
86
164
|
/**
|
|
87
165
|
* Invoke a consumer-supplied backend hook, normalizing both a returned `fail`
|
|
88
166
|
* and a thrown/rejected promise into a single `fragment recall: <label> failed`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fragmentSemanticRetriever.d.ts","sourceRoot":"","sources":["../../../src/packlets/retrieve/fragmentSemanticRetriever.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"fragmentSemanticRetriever.d.ts","sourceRoot":"","sources":["../../../src/packlets/retrieve/fragmentSemanticRetriever.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,MAAM,EAAgC,MAAM,eAAe,CAAC;AACrE,OAAO,EAAW,QAAQ,EAAwB,iBAAiB,EAAE,IAAI,EAAY,MAAM,UAAU,CAAC;AACtG,OAAO,EAAyB,oBAAoB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACzF,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD;;;;;GAKG;AACH,eAAO,MAAM,iCAAiC,EAAE,MAC+D,CAAC;AAEhH;;;;;;;;GAQG;AACH,eAAO,MAAM,uCAAuC,EAAE,MAC8E,CAAC;AAErI;;;;;;;;GAQG;AACH,eAAO,MAAM,qCAAqC,EAAE,MACgF,CAAC;AAErI;;;;;GAKG;AACH,MAAM,WAAW,wBAAwB;IACvC,mDAAmD;IACnD,QAAQ,CAAC,aAAa,EAAE,oBAAoB,CAAC;IAC7C,0CAA0C;IAC1C,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC;CACpC;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,iEAAiE;IACjE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAE/B;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAE7B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC;CACtB;AAED;;;GAGG;AACH,MAAM,WAAW,8BAA8B;IAC7C,kFAAkF;IAClF,QAAQ,CAAC,sBAAsB,EAAE,OAAO,CAAC;CAC1C;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,yBAAyB;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuC;IAChE,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAgC;IAElE,OAAO;IAQP,mDAAmD;IACnD,IAAW,YAAY,IAAI,8BAA8B,CAExD;IAED;;;;;;;;;OASG;WACW,MAAM,CAAC,MAAM,EAAE;QAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,wBAAwB,CAAC;QAC5C,QAAQ,CAAC,gBAAgB,CAAC,EAAE,iBAAiB,CAAC;KAC/C,GAAG,MAAM,CAAC,yBAAyB,CAAC;IAIrC;;;OAGG;IACU,QAAQ,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,eAAe,CAAC,CAAC,CAAC;IA2B7F;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,eAAe;IAwCvB;;;;OAIG;mBACkB,YAAY;CAOlC"}
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* SPDX-License-Identifier: MIT
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.FragmentSemanticRetriever = exports.FRAGMENT_SEMANTIC_UNWIRED_MESSAGE = void 0;
|
|
7
|
+
exports.FragmentSemanticRetriever = exports.FRAGMENT_NARROWING_INCOMPLETE_MESSAGE = exports.FRAGMENT_NARROWING_UNRESOLVABLE_MESSAGE = exports.FRAGMENT_SEMANTIC_UNWIRED_MESSAGE = void 0;
|
|
8
8
|
const ts_utils_1 = require("@fgv/ts-utils");
|
|
9
|
+
const types_1 = require("../types");
|
|
9
10
|
/**
|
|
10
11
|
* The loud-degradation message a {@link FragmentSemanticRetriever} returns when a
|
|
11
12
|
* fragment query is issued but no {@link IFragmentSemanticBackend | backend} is
|
|
@@ -13,6 +14,26 @@ const ts_utils_1 = require("@fgv/ts-utils");
|
|
|
13
14
|
* @public
|
|
14
15
|
*/
|
|
15
16
|
exports.FRAGMENT_SEMANTIC_UNWIRED_MESSAGE = 'fragment recall: no fragment index is wired; wire an IFragmentSemanticBackend to enable sub-document search';
|
|
17
|
+
/**
|
|
18
|
+
* The loud-degradation message returned when a query carries a record narrowing but
|
|
19
|
+
* no {@link IIdentityResolver} is wired to resolve it.
|
|
20
|
+
*
|
|
21
|
+
* @remarks
|
|
22
|
+
* Deliberately a `Failure` rather than a silently-global search: answering a scoped
|
|
23
|
+
* question with an unscoped result is the failure this narrowing exists to remove.
|
|
24
|
+
* @public
|
|
25
|
+
*/
|
|
26
|
+
exports.FRAGMENT_NARROWING_UNRESOLVABLE_MESSAGE = 'fragment recall: a record narrowing was supplied but no identity resolver is wired; pass one to FragmentSemanticRetriever.create';
|
|
27
|
+
/**
|
|
28
|
+
* The message returned when exactly one of `entityId` / `kind` is supplied.
|
|
29
|
+
*
|
|
30
|
+
* @remarks
|
|
31
|
+
* They travel together because `kind` is what selects the identity codec, and the
|
|
32
|
+
* codec is what makes the resolution unambiguous. One without the other is not a
|
|
33
|
+
* partial narrowing that could be honored best-effort — it is not a narrowing at all.
|
|
34
|
+
* @public
|
|
35
|
+
*/
|
|
36
|
+
exports.FRAGMENT_NARROWING_INCOMPLETE_MESSAGE = 'fragment recall: `entityId` and `kind` must be supplied together — `kind` selects the identity codec that resolves the narrowing';
|
|
16
37
|
/**
|
|
17
38
|
* The sub-document semantic-search retriever — the "discovery" half of a
|
|
18
39
|
* search-then-read contract. It embeds a fragment query, queries the
|
|
@@ -38,16 +59,26 @@ exports.FRAGMENT_SEMANTIC_UNWIRED_MESSAGE = 'fragment recall: no fragment index
|
|
|
38
59
|
* @public
|
|
39
60
|
*/
|
|
40
61
|
class FragmentSemanticRetriever {
|
|
41
|
-
constructor(backend) {
|
|
62
|
+
constructor(backend, identityResolver) {
|
|
42
63
|
this._backend = backend;
|
|
64
|
+
this._identityResolver = identityResolver;
|
|
43
65
|
}
|
|
44
66
|
/** What this retriever can do given its wiring. */
|
|
45
67
|
get capabilities() {
|
|
46
68
|
return { supportsFragmentRecall: this._backend !== undefined };
|
|
47
69
|
}
|
|
48
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* Family-convention factory.
|
|
72
|
+
*
|
|
73
|
+
* @param params - `backend` wires fragment recall itself. `identityResolver`
|
|
74
|
+
* resolves a query's `(kind, entityId)` narrowing to a storage address;
|
|
75
|
+
* `IMemoryStore` implements it, so the usual wiring is
|
|
76
|
+
* `{ backend, identityResolver: store }`. It is optional because an unscoped
|
|
77
|
+
* fragment search needs nothing to resolve — but a query that *does* carry a
|
|
78
|
+
* narrowing fails loudly without it rather than quietly searching everything.
|
|
79
|
+
*/
|
|
49
80
|
static create(params) {
|
|
50
|
-
return (0, ts_utils_1.succeed)(new FragmentSemanticRetriever(params.backend));
|
|
81
|
+
return (0, ts_utils_1.succeed)(new FragmentSemanticRetriever(params.backend, params.identityResolver));
|
|
51
82
|
}
|
|
52
83
|
/**
|
|
53
84
|
* Embed `query.semantic`, query the fragment index, and return the per-fragment
|
|
@@ -58,6 +89,13 @@ class FragmentSemanticRetriever {
|
|
|
58
89
|
return (0, ts_utils_1.fail)(exports.FRAGMENT_SEMANTIC_UNWIRED_MESSAGE);
|
|
59
90
|
}
|
|
60
91
|
const backend = this._backend;
|
|
92
|
+
// Resolve the narrowing FIRST. It is synchronous, local, and cheap, while
|
|
93
|
+
// `embedQuery` is typically a paid network round trip — so a typo'd `kind`, a
|
|
94
|
+
// missing resolver, or a half-supplied narrowing should cost nothing.
|
|
95
|
+
const options = this._resolveOptions(query);
|
|
96
|
+
if (options.isFailure()) {
|
|
97
|
+
return (0, ts_utils_1.fail)(options.message);
|
|
98
|
+
}
|
|
61
99
|
// Consumer-supplied hooks may throw; normalize both a returned `fail` and a
|
|
62
100
|
// rejection into a single `fragment recall: <label> failed` Failure so
|
|
63
101
|
// `retrieve` always honors its `Promise<Result<...>>` contract.
|
|
@@ -65,7 +103,58 @@ class FragmentSemanticRetriever {
|
|
|
65
103
|
if (embedded.isFailure()) {
|
|
66
104
|
return (0, ts_utils_1.fail)(embedded.message);
|
|
67
105
|
}
|
|
68
|
-
return FragmentSemanticRetriever._callBackend('fragment query', () => { var _a; return backend.fragmentIndex.query(embedded.value, (_a = query.topK) !== null && _a !== void 0 ? _a : 10,
|
|
106
|
+
return FragmentSemanticRetriever._callBackend('fragment query', () => { var _a; return backend.fragmentIndex.query(embedded.value, (_a = query.topK) !== null && _a !== void 0 ? _a : 10, options.value); });
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Turn the query's consumer-facing narrowing into the storage-address narrowing
|
|
110
|
+
* the index understands.
|
|
111
|
+
*
|
|
112
|
+
* @remarks
|
|
113
|
+
* `kind` selects the identity codec and the codec computes the record's storage
|
|
114
|
+
* address, so this is a deterministic resolution rather than a search —
|
|
115
|
+
* which is what makes a colliding `entityId` across kinds a non-issue.
|
|
116
|
+
*
|
|
117
|
+
* A **versioned** kind resolves to the entity's own subtree scope and deliberately
|
|
118
|
+
* carries no `id`, so the narrowing covers every version of the entity — including
|
|
119
|
+
* superseded ones, which are invalidated but never pruned from the index. A
|
|
120
|
+
* non-versioned kind resolves to exactly one record.
|
|
121
|
+
*/
|
|
122
|
+
_resolveOptions(query) {
|
|
123
|
+
const { entityId, kind, maxPerRecord } = query;
|
|
124
|
+
if (entityId === undefined && kind === undefined) {
|
|
125
|
+
return (0, ts_utils_1.succeed)({ maxPerRecord });
|
|
126
|
+
}
|
|
127
|
+
if (entityId === undefined || kind === undefined) {
|
|
128
|
+
return (0, ts_utils_1.fail)(exports.FRAGMENT_NARROWING_INCOMPLETE_MESSAGE);
|
|
129
|
+
}
|
|
130
|
+
if (this._identityResolver === undefined) {
|
|
131
|
+
return (0, ts_utils_1.fail)(exports.FRAGMENT_NARROWING_UNRESOLVABLE_MESSAGE);
|
|
132
|
+
}
|
|
133
|
+
const resolver = this._identityResolver;
|
|
134
|
+
// `identityResolver` is a consumer-injectable seam like the two backend hooks,
|
|
135
|
+
// so a throw has to become a `Failure` here rather than escaping `retrieve()`
|
|
136
|
+
// and breaking its `Promise<Result<...>>` contract. `captureResult` yields a
|
|
137
|
+
// nested `Result`, which the identity `onSuccess` flattens.
|
|
138
|
+
return (0, ts_utils_1.captureResult)(() => resolver.resolveIdentity(kind, entityId))
|
|
139
|
+
.onSuccess((resolved) => resolved)
|
|
140
|
+
.withErrorFormat((msg) => `fragment recall: cannot resolve '${kind}'/'${entityId}': ${msg}`)
|
|
141
|
+
.onSuccess((address) => {
|
|
142
|
+
// A versioned kind's every version lives under the entity subtree the
|
|
143
|
+
// codec returned, so omitting `id` is what makes the narrowing mean
|
|
144
|
+
// "this entity" rather than "one of its versions".
|
|
145
|
+
if (address.isVersioned) {
|
|
146
|
+
return (0, ts_utils_1.succeed)({ maxPerRecord, scope: address.scope });
|
|
147
|
+
}
|
|
148
|
+
// `idStem` is a plain `string` on the codec result, but a `MemoryId` IS the
|
|
149
|
+
// filename stem by contract and `Convert.memoryId` is what enforces that —
|
|
150
|
+
// so validate rather than assert. A resolver that returned a path-unsafe
|
|
151
|
+
// stem could otherwise smuggle it into the index query, where it would
|
|
152
|
+
// match nothing and look like an empty result rather than a caller bug.
|
|
153
|
+
return types_1.Convert.memoryId
|
|
154
|
+
.convert(address.idStem)
|
|
155
|
+
.withErrorFormat((msg) => `fragment recall: '${kind}'/'${entityId}' resolved to an unusable record id: ${msg}`)
|
|
156
|
+
.onSuccess((id) => (0, ts_utils_1.succeed)({ maxPerRecord, scope: address.scope, id }));
|
|
157
|
+
});
|
|
69
158
|
}
|
|
70
159
|
/**
|
|
71
160
|
* Invoke a consumer-supplied backend hook, normalizing both a returned `fail`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fragmentSemanticRetriever.js","sourceRoot":"","sources":["../../../src/packlets/retrieve/fragmentSemanticRetriever.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,4CAAsD;AAItD;;;;;GAKG;AACU,QAAA,iCAAiC,GAC5C,6GAA6G,CAAC;AA0ChH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAa,yBAAyB;IAGpC,YAAoB,OAA6C;QAC/D,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;IAC1B,CAAC;IAED,mDAAmD;IACnD,IAAW,YAAY;QACrB,OAAO,EAAE,sBAAsB,EAAE,IAAI,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;IACjE,CAAC;IAED,iCAAiC;IAC1B,MAAM,CAAC,MAAM,CAAC,MAEpB;QACC,OAAO,IAAA,kBAAO,EAAC,IAAI,yBAAyB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IAChE,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,QAAQ,CAAC,KAAqB;QACzC,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YAChC,OAAO,IAAA,eAAI,EAAC,yCAAiC,CAAC,CAAC;QACjD,CAAC;QACD,MAAM,OAAO,GAA6B,IAAI,CAAC,QAAQ,CAAC;QACxD,4EAA4E;QAC5E,uEAAuE;QACvE,gEAAgE;QAChE,MAAM,QAAQ,GAAyB,MAAM,yBAAyB,CAAC,YAAY,CACjF,iBAAiB,EACjB,GAAG,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,CACzC,CAAC;QACF,IAAI,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC;YACzB,OAAO,IAAA,eAAI,EAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAChC,CAAC;QACD,OAAO,yBAAyB,CAAC,YAAY,CAAC,gBAAgB,EAAE,GAAG,EAAE,WACnE,OAAA,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAA,KAAK,CAAC,IAAI,mCAAI,EAAE,EAAE,KAAK,CAAC,YAAY,CAAC,CAAA,EAAA,CAClF,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACK,MAAM,CAAC,KAAK,CAAC,YAAY,CAAI,KAAa,EAAE,EAA4B;QAC9E,IAAI,CAAC;YACH,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,eAAe,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,oBAAoB,KAAK,YAAY,GAAG,EAAE,CAAC,CAAC;QAC3F,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,IAAA,eAAI,EAAC,oBAAoB,KAAK,YAAY,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAClE,CAAC;IACH,CAAC;CACF;AAvDD,8DAuDC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result, fail, succeed } from '@fgv/ts-utils';\nimport { IFragmentVectorIndex, IVectorQueryHit } from '../vector';\nimport { QueryEmbedder } from './semanticRetriever';\n\n/**\n * The loud-degradation message a {@link FragmentSemanticRetriever} returns when a\n * fragment query is issued but no {@link IFragmentSemanticBackend | backend} is\n * wired — the discovery surface NEVER answers a fragment query with a silent empty.\n * @public\n */\nexport const FRAGMENT_SEMANTIC_UNWIRED_MESSAGE: string =\n 'fragment recall: no fragment index is wired; wire an IFragmentSemanticBackend to enable sub-document search';\n\n/**\n * The fragment backend wired into a {@link FragmentSemanticRetriever}: the fragment\n * index to query and the embedder that turns the query text into a vector. Both are\n * required together — a fragment index is useless without a way to embed the query.\n * @public\n */\nexport interface IFragmentSemanticBackend {\n /** The fragment-granular vector index to query. */\n readonly fragmentIndex: IFragmentVectorIndex;\n /** Turns the query text into a vector. */\n readonly embedQuery: QueryEmbedder;\n}\n\n/**\n * A sub-document semantic-search request: the natural-language `semantic` text to\n * match, an optional `topK` result cap (default 10), and an optional\n * `maxPerRecord` cap that keeps one long document from monopolizing the result.\n * @public\n */\nexport interface IFragmentQuery {\n /** The natural-language text to embed and match against stored fragments. */\n readonly semantic: string;\n /** Maximum number of fragment hits to return. Defaults to 10. */\n readonly topK?: number;\n /**\n * Maximum number of fragments any single record may contribute to the result.\n * Applied during selection (before the `topK` cut). Omit for uncapped.\n */\n readonly maxPerRecord?: number;\n}\n\n/**\n * What a {@link FragmentSemanticRetriever} can do given its wiring.\n * @public\n */\nexport interface IFragmentRetrieverCapabilities {\n /** `true` when a fragment backend is wired and fragment recall is operational. */\n readonly supportsFragmentRecall: boolean;\n}\n\n/**\n * The sub-document semantic-search retriever — the \"discovery\" half of a\n * search-then-read contract. It embeds a fragment query, queries the\n * {@link IFragmentVectorIndex}, and returns the raw per-fragment\n * {@link IVectorQueryHit | hits} (each carrying a record `target` AND whichever of\n * `locator` / `fragmentId` the stored fragment was indexed with), NOT resolved\n * records: the consumer re-reads each record and resolves the fragment on its own\n * read side. Note the `locator` span is advisory — see {@link IFragmentLocator}; it\n * is not a slice guaranteed to reproduce the fragment's text.\n *\n * @remarks\n * Deliberately NOT an {@link IMemoryRetriever}: memory recall is record-granular and\n * returns records; fragment discovery is fragment-granular and returns fragment\n * identities. Keeping it a distinct surface matches the consumer contract (memory\n * stays record-granular; sub-document knowledge uses a separate fragment index) and\n * avoids overloading the record retriever's return type with identity fields that\n * only make sense here.\n *\n * When no backend is wired, `supportsFragmentRecall` is `false` and any fragment\n * query degrades loudly ({@link FRAGMENT_SEMANTIC_UNWIRED_MESSAGE}) — it NEVER\n * returns a silent empty. A consumer-supplied backend that rejects (throws) is\n * normalized into a `Failure`.\n * @public\n */\nexport class FragmentSemanticRetriever {\n private readonly _backend: IFragmentSemanticBackend | undefined;\n\n private constructor(backend: IFragmentSemanticBackend | undefined) {\n this._backend = backend;\n }\n\n /** What this retriever can do given its wiring. */\n public get capabilities(): IFragmentRetrieverCapabilities {\n return { supportsFragmentRecall: this._backend !== undefined };\n }\n\n /** Family-convention factory. */\n public static create(params: {\n readonly backend?: IFragmentSemanticBackend;\n }): Result<FragmentSemanticRetriever> {\n return succeed(new FragmentSemanticRetriever(params.backend));\n }\n\n /**\n * Embed `query.semantic`, query the fragment index, and return the per-fragment\n * hits in descending score order. Fails loudly when no backend is wired.\n */\n public async retrieve(query: IFragmentQuery): Promise<Result<ReadonlyArray<IVectorQueryHit>>> {\n if (this._backend === undefined) {\n return fail(FRAGMENT_SEMANTIC_UNWIRED_MESSAGE);\n }\n const backend: IFragmentSemanticBackend = this._backend;\n // Consumer-supplied hooks may throw; normalize both a returned `fail` and a\n // rejection into a single `fragment recall: <label> failed` Failure so\n // `retrieve` always honors its `Promise<Result<...>>` contract.\n const embedded: Result<Float32Array> = await FragmentSemanticRetriever._callBackend(\n 'query embedding',\n () => backend.embedQuery(query.semantic)\n );\n if (embedded.isFailure()) {\n return fail(embedded.message);\n }\n return FragmentSemanticRetriever._callBackend('fragment query', () =>\n backend.fragmentIndex.query(embedded.value, query.topK ?? 10, query.maxPerRecord)\n );\n }\n\n /**\n * Invoke a consumer-supplied backend hook, normalizing both a returned `fail`\n * and a thrown/rejected promise into a single `fragment recall: <label> failed`\n * `Failure`.\n */\n private static async _callBackend<T>(label: string, op: () => Promise<Result<T>>): Promise<Result<T>> {\n try {\n return (await op()).withErrorFormat((msg) => `fragment recall: ${label} failed: ${msg}`);\n } catch (err) {\n return fail(`fragment recall: ${label} failed: ${String(err)}`);\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"fragmentSemanticRetriever.js","sourceRoot":"","sources":["../../../src/packlets/retrieve/fragmentSemanticRetriever.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result, captureResult, fail, succeed } from '@fgv/ts-utils';\nimport { Convert, EntityId, IIdentityCodecResult, IIdentityResolver, Kind, MemoryId } from '../types';\nimport { IFragmentQueryOptions, IFragmentVectorIndex, IVectorQueryHit } from '../vector';\nimport { QueryEmbedder } from './semanticRetriever';\n\n/**\n * The loud-degradation message a {@link FragmentSemanticRetriever} returns when a\n * fragment query is issued but no {@link IFragmentSemanticBackend | backend} is\n * wired — the discovery surface NEVER answers a fragment query with a silent empty.\n * @public\n */\nexport const FRAGMENT_SEMANTIC_UNWIRED_MESSAGE: string =\n 'fragment recall: no fragment index is wired; wire an IFragmentSemanticBackend to enable sub-document search';\n\n/**\n * The loud-degradation message returned when a query carries a record narrowing but\n * no {@link IIdentityResolver} is wired to resolve it.\n *\n * @remarks\n * Deliberately a `Failure` rather than a silently-global search: answering a scoped\n * question with an unscoped result is the failure this narrowing exists to remove.\n * @public\n */\nexport const FRAGMENT_NARROWING_UNRESOLVABLE_MESSAGE: string =\n 'fragment recall: a record narrowing was supplied but no identity resolver is wired; pass one to FragmentSemanticRetriever.create';\n\n/**\n * The message returned when exactly one of `entityId` / `kind` is supplied.\n *\n * @remarks\n * They travel together because `kind` is what selects the identity codec, and the\n * codec is what makes the resolution unambiguous. One without the other is not a\n * partial narrowing that could be honored best-effort — it is not a narrowing at all.\n * @public\n */\nexport const FRAGMENT_NARROWING_INCOMPLETE_MESSAGE: string =\n 'fragment recall: `entityId` and `kind` must be supplied together — `kind` selects the identity codec that resolves the narrowing';\n\n/**\n * The fragment backend wired into a {@link FragmentSemanticRetriever}: the fragment\n * index to query and the embedder that turns the query text into a vector. Both are\n * required together — a fragment index is useless without a way to embed the query.\n * @public\n */\nexport interface IFragmentSemanticBackend {\n /** The fragment-granular vector index to query. */\n readonly fragmentIndex: IFragmentVectorIndex;\n /** Turns the query text into a vector. */\n readonly embedQuery: QueryEmbedder;\n}\n\n/**\n * A sub-document semantic-search request: the natural-language `semantic` text to\n * match, an optional `topK` result cap (default 10), and an optional\n * `maxPerRecord` cap that keeps one long document from monopolizing the result.\n * @public\n */\nexport interface IFragmentQuery {\n /** The natural-language text to embed and match against stored fragments. */\n readonly semantic: string;\n /** Maximum number of fragment hits to return. Defaults to 10. */\n readonly topK?: number;\n /**\n * Maximum number of fragments any single record may contribute to the result.\n * Applied during selection (before the `topK` cut). Omit for uncapped.\n */\n readonly maxPerRecord?: number;\n\n /**\n * Narrow the search to one record's fragments: the consumer-supplied domain key\n * of the record to search within. **Must be supplied with\n * {@link IFragmentQuery.kind}.**\n *\n * @remarks\n * The narrowing is applied **during selection, before the `topK` cut**, so the\n * `topK` you ask for is the `topK` you get. Filtering a global result afterwards\n * is not equivalent: it truncates to `topK` across every record first, so a scoped\n * search would come back short whenever other records outscored this one's\n * fragments.\n *\n * For a versioned kind this narrows to **every version of the entity** — literally\n * every version, superseded ones included, because invalidation stamps `invalid_at`\n * without pruning that version's fragments. Nothing on a hit distinguishes a\n * current fragment from a historical one. That matches the record-granular vector\n * lane; it is not currency filtering.\n */\n readonly entityId?: EntityId;\n\n /**\n * The kind of the record named by {@link IFragmentQuery.entityId}. **Must be\n * supplied with it.**\n *\n * @remarks\n * This is not decoration and not a filter: `kind` **selects the identity codec**,\n * and the codec computes the storage address. An `EntityId` promises no uniqueness\n * beyond a scope — the same id under two kinds is the ordinary case, not a\n * pathological one — so without `kind` the resolution is ambiguous, and with it\n * ambiguity is structurally impossible.\n */\n readonly kind?: Kind;\n}\n\n/**\n * What a {@link FragmentSemanticRetriever} can do given its wiring.\n * @public\n */\nexport interface IFragmentRetrieverCapabilities {\n /** `true` when a fragment backend is wired and fragment recall is operational. */\n readonly supportsFragmentRecall: boolean;\n}\n\n/**\n * The sub-document semantic-search retriever — the \"discovery\" half of a\n * search-then-read contract. It embeds a fragment query, queries the\n * {@link IFragmentVectorIndex}, and returns the raw per-fragment\n * {@link IVectorQueryHit | hits} (each carrying a record `target` AND whichever of\n * `locator` / `fragmentId` the stored fragment was indexed with), NOT resolved\n * records: the consumer re-reads each record and resolves the fragment on its own\n * read side. Note the `locator` span is advisory — see {@link IFragmentLocator}; it\n * is not a slice guaranteed to reproduce the fragment's text.\n *\n * @remarks\n * Deliberately NOT an {@link IMemoryRetriever}: memory recall is record-granular and\n * returns records; fragment discovery is fragment-granular and returns fragment\n * identities. Keeping it a distinct surface matches the consumer contract (memory\n * stays record-granular; sub-document knowledge uses a separate fragment index) and\n * avoids overloading the record retriever's return type with identity fields that\n * only make sense here.\n *\n * When no backend is wired, `supportsFragmentRecall` is `false` and any fragment\n * query degrades loudly ({@link FRAGMENT_SEMANTIC_UNWIRED_MESSAGE}) — it NEVER\n * returns a silent empty. A consumer-supplied backend that rejects (throws) is\n * normalized into a `Failure`.\n * @public\n */\nexport class FragmentSemanticRetriever {\n private readonly _backend: IFragmentSemanticBackend | undefined;\n private readonly _identityResolver: IIdentityResolver | undefined;\n\n private constructor(\n backend: IFragmentSemanticBackend | undefined,\n identityResolver: IIdentityResolver | undefined\n ) {\n this._backend = backend;\n this._identityResolver = identityResolver;\n }\n\n /** What this retriever can do given its wiring. */\n public get capabilities(): IFragmentRetrieverCapabilities {\n return { supportsFragmentRecall: this._backend !== undefined };\n }\n\n /**\n * Family-convention factory.\n *\n * @param params - `backend` wires fragment recall itself. `identityResolver`\n * resolves a query's `(kind, entityId)` narrowing to a storage address;\n * `IMemoryStore` implements it, so the usual wiring is\n * `{ backend, identityResolver: store }`. It is optional because an unscoped\n * fragment search needs nothing to resolve — but a query that *does* carry a\n * narrowing fails loudly without it rather than quietly searching everything.\n */\n public static create(params: {\n readonly backend?: IFragmentSemanticBackend;\n readonly identityResolver?: IIdentityResolver;\n }): Result<FragmentSemanticRetriever> {\n return succeed(new FragmentSemanticRetriever(params.backend, params.identityResolver));\n }\n\n /**\n * Embed `query.semantic`, query the fragment index, and return the per-fragment\n * hits in descending score order. Fails loudly when no backend is wired.\n */\n public async retrieve(query: IFragmentQuery): Promise<Result<ReadonlyArray<IVectorQueryHit>>> {\n if (this._backend === undefined) {\n return fail(FRAGMENT_SEMANTIC_UNWIRED_MESSAGE);\n }\n const backend: IFragmentSemanticBackend = this._backend;\n // Resolve the narrowing FIRST. It is synchronous, local, and cheap, while\n // `embedQuery` is typically a paid network round trip — so a typo'd `kind`, a\n // missing resolver, or a half-supplied narrowing should cost nothing.\n const options: Result<IFragmentQueryOptions> = this._resolveOptions(query);\n if (options.isFailure()) {\n return fail(options.message);\n }\n // Consumer-supplied hooks may throw; normalize both a returned `fail` and a\n // rejection into a single `fragment recall: <label> failed` Failure so\n // `retrieve` always honors its `Promise<Result<...>>` contract.\n const embedded: Result<Float32Array> = await FragmentSemanticRetriever._callBackend(\n 'query embedding',\n () => backend.embedQuery(query.semantic)\n );\n if (embedded.isFailure()) {\n return fail(embedded.message);\n }\n return FragmentSemanticRetriever._callBackend('fragment query', () =>\n backend.fragmentIndex.query(embedded.value, query.topK ?? 10, options.value)\n );\n }\n\n /**\n * Turn the query's consumer-facing narrowing into the storage-address narrowing\n * the index understands.\n *\n * @remarks\n * `kind` selects the identity codec and the codec computes the record's storage\n * address, so this is a deterministic resolution rather than a search —\n * which is what makes a colliding `entityId` across kinds a non-issue.\n *\n * A **versioned** kind resolves to the entity's own subtree scope and deliberately\n * carries no `id`, so the narrowing covers every version of the entity — including\n * superseded ones, which are invalidated but never pruned from the index. A\n * non-versioned kind resolves to exactly one record.\n */\n private _resolveOptions(query: IFragmentQuery): Result<IFragmentQueryOptions> {\n const { entityId, kind, maxPerRecord } = query;\n if (entityId === undefined && kind === undefined) {\n return succeed({ maxPerRecord });\n }\n if (entityId === undefined || kind === undefined) {\n return fail(FRAGMENT_NARROWING_INCOMPLETE_MESSAGE);\n }\n if (this._identityResolver === undefined) {\n return fail(FRAGMENT_NARROWING_UNRESOLVABLE_MESSAGE);\n }\n const resolver: IIdentityResolver = this._identityResolver;\n // `identityResolver` is a consumer-injectable seam like the two backend hooks,\n // so a throw has to become a `Failure` here rather than escaping `retrieve()`\n // and breaking its `Promise<Result<...>>` contract. `captureResult` yields a\n // nested `Result`, which the identity `onSuccess` flattens.\n return captureResult(() => resolver.resolveIdentity(kind, entityId))\n .onSuccess((resolved: Result<IIdentityCodecResult>) => resolved)\n .withErrorFormat((msg) => `fragment recall: cannot resolve '${kind}'/'${entityId}': ${msg}`)\n .onSuccess((address: IIdentityCodecResult) => {\n // A versioned kind's every version lives under the entity subtree the\n // codec returned, so omitting `id` is what makes the narrowing mean\n // \"this entity\" rather than \"one of its versions\".\n if (address.isVersioned) {\n return succeed({ maxPerRecord, scope: address.scope });\n }\n // `idStem` is a plain `string` on the codec result, but a `MemoryId` IS the\n // filename stem by contract and `Convert.memoryId` is what enforces that —\n // so validate rather than assert. A resolver that returned a path-unsafe\n // stem could otherwise smuggle it into the index query, where it would\n // match nothing and look like an empty result rather than a caller bug.\n return Convert.memoryId\n .convert(address.idStem)\n .withErrorFormat(\n (msg) => `fragment recall: '${kind}'/'${entityId}' resolved to an unusable record id: ${msg}`\n )\n .onSuccess((id: MemoryId) => succeed({ maxPerRecord, scope: address.scope, id }));\n });\n }\n\n /**\n * Invoke a consumer-supplied backend hook, normalizing both a returned `fail`\n * and a thrown/rejected promise into a single `fragment recall: <label> failed`\n * `Failure`.\n */\n private static async _callBackend<T>(label: string, op: () => Promise<Result<T>>): Promise<Result<T>> {\n try {\n return (await op()).withErrorFormat((msg) => `fragment recall: ${label} failed: ${msg}`);\n } catch (err) {\n return fail(`fragment recall: ${label} failed: ${String(err)}`);\n }\n }\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Logging, Result } from '@fgv/ts-utils';
|
|
2
2
|
import { FileTree } from '@fgv/ts-json-base';
|
|
3
|
-
import { DedupScope, EntityId, IIdentityCodec, IMemoryRecord, IWritePolicy, Kind, MemoryId, MemoryScopeKey, RankProjector } from '../types';
|
|
3
|
+
import { DedupScope, EntityId, IIdentityCodecResult, IIdentityCodec, IMemoryRecord, IWritePolicy, Kind, MemoryId, MemoryScopeKey, RankProjector } from '../types';
|
|
4
4
|
import { IBodyConverterRegistry as IRegistry } from '../converters';
|
|
5
5
|
import { IDerivedStateCoverage } from './coverage';
|
|
6
6
|
import { DerivedArtifact, ReconcileReport } from './reconcile';
|
|
@@ -325,6 +325,9 @@ export declare class FileTreeMemoryStore implements IMemoryStore {
|
|
|
325
325
|
listScoped(): Promise<Result<ReadonlyArray<IScopedMemoryRecord>>>;
|
|
326
326
|
/** {@inheritDoc IMemoryRecordResolver.resolveRecord} */
|
|
327
327
|
resolveRecord(scope: MemoryScopeKey, id: MemoryId): Result<IMemoryRecord<unknown> | undefined>;
|
|
328
|
+
private _codec;
|
|
329
|
+
/** {@inheritDoc IIdentityResolver.resolveIdentity} */
|
|
330
|
+
resolveIdentity(kind: Kind, entityId: EntityId): Result<IIdentityCodecResult>;
|
|
328
331
|
/**
|
|
329
332
|
* Materialize a selected set of entries into records, dropping any that have
|
|
330
333
|
* vanished since selection.
|
|
@@ -597,7 +600,6 @@ export declare class FileTreeMemoryStore implements IMemoryStore {
|
|
|
597
600
|
* than preserving it), so a ranking bug never loses an authoritative write.
|
|
598
601
|
*/
|
|
599
602
|
private _stampRank;
|
|
600
|
-
private _codecFor;
|
|
601
603
|
private _policyFor;
|
|
602
604
|
/** {@inheritDoc IMemoryStore.dedupScopeFor} */
|
|
603
605
|
dedupScopeFor(kind: Kind): DedupScope;
|