@seanhogg/builderforce-memory 2026.7.14 → 2026.9.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/bridges/AnthropicBridge.d.ts +9 -1
- package/dist/bridges/AnthropicBridge.d.ts.map +1 -1
- package/dist/bridges/AnthropicBridge.js +58 -2
- package/dist/bridges/AnthropicBridge.js.map +1 -1
- package/dist/bridges/CachingBridge.d.ts +9 -1
- package/dist/bridges/CachingBridge.d.ts.map +1 -1
- package/dist/bridges/CachingBridge.js +24 -1
- package/dist/bridges/CachingBridge.js.map +1 -1
- package/dist/bridges/OpenAIBridge.d.ts +4 -1
- package/dist/bridges/OpenAIBridge.d.ts.map +1 -1
- package/dist/bridges/OpenAIBridge.js +40 -2
- package/dist/bridges/OpenAIBridge.js.map +1 -1
- package/dist/bridges/SemanticCachingBridge.d.ts +8 -1
- package/dist/bridges/SemanticCachingBridge.d.ts.map +1 -1
- package/dist/bridges/SemanticCachingBridge.js +26 -1
- package/dist/bridges/SemanticCachingBridge.js.map +1 -1
- package/dist/bridges/TransformerBridge.d.ts +13 -0
- package/dist/bridges/TransformerBridge.d.ts.map +1 -1
- package/dist/bridges/VertexAIBridge.d.ts +102 -0
- package/dist/bridges/VertexAIBridge.d.ts.map +1 -0
- package/dist/bridges/VertexAIBridge.js +224 -0
- package/dist/bridges/VertexAIBridge.js.map +1 -0
- package/dist/bridges/index.d.ts +3 -1
- package/dist/bridges/index.d.ts.map +1 -1
- package/dist/bridges/index.js +1 -0
- package/dist/bridges/index.js.map +1 -1
- package/dist/eval/EvalHarness.d.ts +37 -0
- package/dist/eval/EvalHarness.d.ts.map +1 -0
- package/dist/eval/EvalHarness.js +178 -0
- package/dist/eval/EvalHarness.js.map +1 -0
- package/dist/eval/graders.d.ts +76 -0
- package/dist/eval/graders.d.ts.map +1 -0
- package/dist/eval/graders.js +168 -0
- package/dist/eval/graders.js.map +1 -0
- package/dist/eval/index.d.ts +26 -0
- package/dist/eval/index.d.ts.map +1 -0
- package/dist/eval/index.js +26 -0
- package/dist/eval/index.js.map +1 -0
- package/dist/eval/types.d.ts +97 -0
- package/dist/eval/types.d.ts.map +1 -0
- package/dist/eval/types.js +5 -0
- package/dist/eval/types.js.map +1 -0
- package/dist/evermind/contract.d.ts +40 -0
- package/dist/evermind/contract.d.ts.map +1 -0
- package/dist/evermind/contract.js +13 -0
- package/dist/evermind/contract.js.map +1 -0
- package/dist/evermind/index.d.ts +15 -0
- package/dist/evermind/index.d.ts.map +1 -0
- package/dist/evermind/index.js +4 -0
- package/dist/evermind/index.js.map +1 -0
- package/dist/evermind/memoryBlock.d.ts +7 -0
- package/dist/evermind/memoryBlock.d.ts.map +1 -0
- package/dist/evermind/memoryBlock.js +37 -0
- package/dist/evermind/memoryBlock.js.map +1 -0
- package/dist/evermind/recall.d.ts +48 -0
- package/dist/evermind/recall.d.ts.map +1 -0
- package/dist/evermind/recall.js +105 -0
- package/dist/evermind/recall.js.map +1 -0
- package/dist/index.d.ts +18 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/ingest/IngestionPipeline.d.ts +95 -0
- package/dist/ingest/IngestionPipeline.d.ts.map +1 -0
- package/dist/ingest/IngestionPipeline.js +295 -0
- package/dist/ingest/IngestionPipeline.js.map +1 -0
- package/dist/ingest/index.d.ts +10 -0
- package/dist/ingest/index.d.ts.map +1 -0
- package/dist/ingest/index.js +8 -0
- package/dist/ingest/index.js.map +1 -0
- package/dist/ingest/parsers.d.ts +53 -0
- package/dist/ingest/parsers.d.ts.map +1 -0
- package/dist/ingest/parsers.js +306 -0
- package/dist/ingest/parsers.js.map +1 -0
- package/dist/ingest/types.d.ts +114 -0
- package/dist/ingest/types.d.ts.map +1 -0
- package/dist/ingest/types.js +19 -0
- package/dist/ingest/types.js.map +1 -0
- package/dist/memory/MemoryStore.d.ts +33 -0
- package/dist/memory/MemoryStore.d.ts.map +1 -1
- package/dist/memory/MemoryStore.js +29 -3
- package/dist/memory/MemoryStore.js.map +1 -1
- package/dist/memory/index.d.ts +1 -1
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/orchestration/AgentGraph.d.ts +104 -0
- package/dist/orchestration/AgentGraph.d.ts.map +1 -0
- package/dist/orchestration/AgentGraph.js +364 -0
- package/dist/orchestration/AgentGraph.js.map +1 -0
- package/dist/orchestration/checkpoint.d.ts +58 -0
- package/dist/orchestration/checkpoint.d.ts.map +1 -0
- package/dist/orchestration/checkpoint.js +92 -0
- package/dist/orchestration/checkpoint.js.map +1 -0
- package/dist/orchestration/index.d.ts +14 -0
- package/dist/orchestration/index.d.ts.map +1 -0
- package/dist/orchestration/index.js +10 -0
- package/dist/orchestration/index.js.map +1 -0
- package/dist/orchestration/patterns.d.ts +159 -0
- package/dist/orchestration/patterns.d.ts.map +1 -0
- package/dist/orchestration/patterns.js +371 -0
- package/dist/orchestration/patterns.js.map +1 -0
- package/dist/orchestration/types.d.ts +146 -0
- package/dist/orchestration/types.d.ts.map +1 -0
- package/dist/orchestration/types.js +20 -0
- package/dist/orchestration/types.js.map +1 -0
- package/dist/rag/EnterpriseRetriever.d.ts +114 -0
- package/dist/rag/EnterpriseRetriever.d.ts.map +1 -0
- package/dist/rag/EnterpriseRetriever.js +240 -0
- package/dist/rag/EnterpriseRetriever.js.map +1 -0
- package/dist/rag/index.d.ts +6 -0
- package/dist/rag/index.d.ts.map +1 -0
- package/dist/rag/index.js +5 -0
- package/dist/rag/index.js.map +1 -0
- package/dist/retrieval/bm25.d.ts +48 -1
- package/dist/retrieval/bm25.d.ts.map +1 -1
- package/dist/retrieval/bm25.js +59 -13
- package/dist/retrieval/bm25.js.map +1 -1
- package/dist/retrieval/index.d.ts +2 -2
- package/dist/retrieval/index.d.ts.map +1 -1
- package/dist/retrieval/index.js +1 -1
- package/dist/retrieval/index.js.map +1 -1
- package/dist/runtime/SSMRuntime.d.ts +1 -1
- package/dist/runtime/SSMRuntime.d.ts.map +1 -1
- package/dist/telemetry/InstrumentedBridge.d.ts +62 -0
- package/dist/telemetry/InstrumentedBridge.d.ts.map +1 -0
- package/dist/telemetry/InstrumentedBridge.js +120 -0
- package/dist/telemetry/InstrumentedBridge.js.map +1 -0
- package/dist/telemetry/MetricsRegistry.d.ts +88 -0
- package/dist/telemetry/MetricsRegistry.d.ts.map +1 -0
- package/dist/telemetry/MetricsRegistry.js +167 -0
- package/dist/telemetry/MetricsRegistry.js.map +1 -0
- package/dist/telemetry/Tracer.d.ts +137 -0
- package/dist/telemetry/Tracer.d.ts.map +1 -0
- package/dist/telemetry/Tracer.js +244 -0
- package/dist/telemetry/Tracer.js.map +1 -0
- package/dist/telemetry/exporters.d.ts +68 -0
- package/dist/telemetry/exporters.d.ts.map +1 -0
- package/dist/telemetry/exporters.js +145 -0
- package/dist/telemetry/exporters.js.map +1 -0
- package/dist/telemetry/index.d.ts +19 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +12 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/pricing.d.ts +74 -0
- package/dist/telemetry/pricing.d.ts.map +1 -0
- package/dist/telemetry/pricing.js +98 -0
- package/dist/telemetry/pricing.js.map +1 -0
- package/dist/telemetry/traceparent.d.ts +50 -0
- package/dist/telemetry/traceparent.d.ts.map +1 -0
- package/dist/telemetry/traceparent.js +77 -0
- package/dist/telemetry/traceparent.js.map +1 -0
- package/dist/telemetry/types.d.ts +90 -0
- package/dist/telemetry/types.d.ts.map +1 -0
- package/dist/telemetry/types.js +17 -0
- package/dist/telemetry/types.js.map +1 -0
- package/dist/vectorstore/MemoryVectorStore.d.ts +72 -0
- package/dist/vectorstore/MemoryVectorStore.d.ts.map +1 -0
- package/dist/vectorstore/MemoryVectorStore.js +205 -0
- package/dist/vectorstore/MemoryVectorStore.js.map +1 -0
- package/dist/vectorstore/RestVectorStore.d.ts +79 -0
- package/dist/vectorstore/RestVectorStore.d.ts.map +1 -0
- package/dist/vectorstore/RestVectorStore.js +170 -0
- package/dist/vectorstore/RestVectorStore.js.map +1 -0
- package/dist/vectorstore/dialects.d.ts +101 -0
- package/dist/vectorstore/dialects.d.ts.map +1 -0
- package/dist/vectorstore/dialects.js +618 -0
- package/dist/vectorstore/dialects.js.map +1 -0
- package/dist/vectorstore/filter.d.ts +42 -0
- package/dist/vectorstore/filter.d.ts.map +1 -0
- package/dist/vectorstore/filter.js +132 -0
- package/dist/vectorstore/filter.js.map +1 -0
- package/dist/vectorstore/index.d.ts +15 -0
- package/dist/vectorstore/index.d.ts.map +1 -0
- package/dist/vectorstore/index.js +11 -0
- package/dist/vectorstore/index.js.map +1 -0
- package/dist/vectorstore/types.d.ts +151 -0
- package/dist/vectorstore/types.d.ts.map +1 -0
- package/dist/vectorstore/types.js +30 -0
- package/dist/vectorstore/types.js.map +1 -0
- package/package.json +39 -5
- package/src/bridges/AnthropicBridge.ts +68 -3
- package/src/bridges/CachingBridge.ts +28 -2
- package/src/bridges/OpenAIBridge.ts +48 -3
- package/src/bridges/SemanticCachingBridge.ts +29 -2
- package/src/bridges/TransformerBridge.ts +16 -0
- package/src/bridges/VertexAIBridge.ts +297 -0
- package/src/bridges/index.ts +3 -1
- package/src/eval/EvalHarness.ts +214 -0
- package/src/eval/graders.ts +218 -0
- package/src/eval/index.ts +26 -0
- package/src/eval/types.ts +107 -0
- package/src/evermind/contract.ts +39 -0
- package/src/evermind/index.ts +18 -0
- package/src/evermind/memoryBlock.ts +38 -0
- package/src/evermind/recall.ts +122 -0
- package/src/index.ts +232 -0
- package/src/ingest/IngestionPipeline.ts +378 -0
- package/src/ingest/index.ts +46 -0
- package/src/ingest/parsers.ts +318 -0
- package/src/ingest/types.ts +116 -0
- package/src/memory/MemoryStore.ts +55 -3
- package/src/memory/index.ts +1 -1
- package/src/orchestration/AgentGraph.ts +462 -0
- package/src/orchestration/checkpoint.ts +129 -0
- package/src/orchestration/index.ts +63 -0
- package/src/orchestration/patterns.ts +532 -0
- package/src/orchestration/types.ts +128 -0
- package/src/rag/EnterpriseRetriever.ts +348 -0
- package/src/rag/index.ts +18 -0
- package/src/retrieval/bm25.ts +71 -13
- package/src/retrieval/index.ts +2 -2
- package/src/telemetry/InstrumentedBridge.ts +152 -0
- package/src/telemetry/MetricsRegistry.ts +263 -0
- package/src/telemetry/Tracer.ts +310 -0
- package/src/telemetry/exporters.ts +187 -0
- package/src/telemetry/index.ts +56 -0
- package/src/telemetry/pricing.ts +129 -0
- package/src/telemetry/traceparent.ts +89 -0
- package/src/telemetry/types.ts +105 -0
- package/src/vectorstore/MemoryVectorStore.ts +250 -0
- package/src/vectorstore/RestVectorStore.ts +236 -0
- package/src/vectorstore/dialects.ts +702 -0
- package/src/vectorstore/filter.ts +148 -0
- package/src/vectorstore/index.ts +53 -0
- package/src/vectorstore/types.ts +149 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rag/EnterpriseRetriever.ts — hybrid, tenant-scoped, cited retrieval.
|
|
3
|
+
*
|
|
4
|
+
* The gap between a RAG demo and an enterprise RAG system is not the embedding
|
|
5
|
+
* model. It is these five properties, all of which live here:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Scoped.** Every read carries an {@link AccessScope} that is compiled into
|
|
8
|
+
* the database query. There is no code path that reads unscoped, so a
|
|
9
|
+
* cross-tenant answer is not something a caller can cause by forgetting.
|
|
10
|
+
* 2. **Hybrid.** Dense retrieval alone misses exact tokens — error codes, SKUs,
|
|
11
|
+
* contract clause numbers, function names. Lexical search alone misses
|
|
12
|
+
* paraphrase. RRF over both, then MMR for diversity, then an optional
|
|
13
|
+
* reranker.
|
|
14
|
+
* 3. **Honest about degradation.** When the store has no lexical index the
|
|
15
|
+
* retrieval is dense-only and SAYS so on the span and in the result, instead
|
|
16
|
+
* of quietly returning worse answers.
|
|
17
|
+
* 4. **Cited.** Every passage carries its source id, title and URI, and the
|
|
18
|
+
* prompt builder numbers them, so an answer can be checked. An uncitable
|
|
19
|
+
* answer is an unauditable one, and enterprises audit.
|
|
20
|
+
* 5. **Traced and costed.** Retrieval and generation are spans under one trace,
|
|
21
|
+
* with token counts and cost, so "why was this answer bad" and "why did this
|
|
22
|
+
* cost that" are answerable from the same record.
|
|
23
|
+
*/
|
|
24
|
+
import type { TransformerBridge } from '../bridges/TransformerBridge.js';
|
|
25
|
+
import type { Span, Tracer } from '../telemetry/Tracer.js';
|
|
26
|
+
import type { AccessScope, MetadataFilter, VectorStore } from '../vectorstore/types.js';
|
|
27
|
+
/** Reranker port — a cross-encoder, an LLM judge, or a hosted rerank endpoint. */
|
|
28
|
+
export type Reranker = (query: string, passages: readonly RetrievedPassage[]) => Promise<readonly RetrievedPassage[]>;
|
|
29
|
+
export interface RetrievedPassage {
|
|
30
|
+
id: string;
|
|
31
|
+
text: string;
|
|
32
|
+
/** Fused relevance score. */
|
|
33
|
+
score: number;
|
|
34
|
+
sourceId: string;
|
|
35
|
+
title?: string;
|
|
36
|
+
uri?: string;
|
|
37
|
+
metadata: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
export type RetrievalMode = 'hybrid' | 'dense-only' | 'keyword-only' | 'empty';
|
|
40
|
+
export interface RetrievalResult {
|
|
41
|
+
passages: RetrievedPassage[];
|
|
42
|
+
/** What actually ran — `dense-only` means the store had no lexical index. */
|
|
43
|
+
mode: RetrievalMode;
|
|
44
|
+
/** Candidates considered before fusion and truncation. */
|
|
45
|
+
candidatesConsidered: number;
|
|
46
|
+
traceId?: string;
|
|
47
|
+
}
|
|
48
|
+
export interface RetrieveOptions {
|
|
49
|
+
/** Tenant/ACL scope. Required unless the retriever was built with a default. */
|
|
50
|
+
scope?: AccessScope;
|
|
51
|
+
/** Extra metadata predicate, AND-ed with the scope. */
|
|
52
|
+
filter?: MetadataFilter;
|
|
53
|
+
/** Passages returned. Default 5. */
|
|
54
|
+
topK?: number;
|
|
55
|
+
/** Candidates pulled from each retrieval arm before fusion. Default `topK * 4`. */
|
|
56
|
+
candidateK?: number;
|
|
57
|
+
/** Drop passages below this fused score after normalisation. Default 0. */
|
|
58
|
+
minScore?: number;
|
|
59
|
+
/** Parent span, so retrieval nests under the agent turn that asked for it. */
|
|
60
|
+
parent?: Span;
|
|
61
|
+
}
|
|
62
|
+
export interface EnterpriseRetrieverOptions {
|
|
63
|
+
store: VectorStore;
|
|
64
|
+
/** Embeds the query. Omit for keyword-only retrieval. */
|
|
65
|
+
embed?: (text: string) => Promise<Float32Array>;
|
|
66
|
+
/** Applied to the fused list before truncation. */
|
|
67
|
+
reranker?: Reranker;
|
|
68
|
+
/** Default scope, when a deployment is single-tenant or scope comes from context. */
|
|
69
|
+
defaultScope?: AccessScope;
|
|
70
|
+
/** RRF weight for the dense arm. Default 1. */
|
|
71
|
+
denseWeight?: number;
|
|
72
|
+
/** RRF weight for the lexical arm. Default 1. */
|
|
73
|
+
keywordWeight?: number;
|
|
74
|
+
/** MMR relevance/diversity trade-off; 1 is pure relevance. Default 0.7. */
|
|
75
|
+
mmrLambda?: number;
|
|
76
|
+
/** Disable the MMR diversity pass. Default false. */
|
|
77
|
+
disableDiversity?: boolean;
|
|
78
|
+
tracer?: Tracer;
|
|
79
|
+
}
|
|
80
|
+
export declare class EnterpriseRetriever {
|
|
81
|
+
private readonly _store;
|
|
82
|
+
private readonly _embed;
|
|
83
|
+
private readonly _reranker;
|
|
84
|
+
private readonly _defaultScope;
|
|
85
|
+
private readonly _denseWeight;
|
|
86
|
+
private readonly _keywordWeight;
|
|
87
|
+
private readonly _mmrLambda;
|
|
88
|
+
private readonly _diversity;
|
|
89
|
+
private readonly _tracer;
|
|
90
|
+
constructor(opts: EnterpriseRetrieverOptions);
|
|
91
|
+
retrieve(query: string, opts?: RetrieveOptions): Promise<RetrievalResult>;
|
|
92
|
+
/**
|
|
93
|
+
* Retrieve, then answer strictly from what was retrieved.
|
|
94
|
+
*
|
|
95
|
+
* The system prompt forbids answering beyond the passages and requires
|
|
96
|
+
* `[n]` citations. That is not decoration: an enterprise deployment is judged
|
|
97
|
+
* on whether a wrong answer is *detectable*, and an uncited claim is not.
|
|
98
|
+
*/
|
|
99
|
+
answer(query: string, bridge: TransformerBridge, opts?: RetrieveOptions & {
|
|
100
|
+
systemPrompt?: string;
|
|
101
|
+
maxTokens?: number;
|
|
102
|
+
model?: string;
|
|
103
|
+
}): Promise<{
|
|
104
|
+
answer: string;
|
|
105
|
+
result: RetrievalResult;
|
|
106
|
+
}>;
|
|
107
|
+
private _fuse;
|
|
108
|
+
private _diversify;
|
|
109
|
+
}
|
|
110
|
+
export declare const GROUNDED_SYSTEM_PROMPT: string;
|
|
111
|
+
export declare const NO_EVIDENCE_ANSWER: string;
|
|
112
|
+
/** Renders passages as a numbered, citable evidence block. */
|
|
113
|
+
export declare function buildGroundedPrompt(query: string, passages: readonly RetrievedPassage[]): string;
|
|
114
|
+
//# sourceMappingURL=EnterpriseRetriever.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EnterpriseRetriever.d.ts","sourceRoot":"","sources":["../../src/rag/EnterpriseRetriever.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rag/EnterpriseRetriever.ts — hybrid, tenant-scoped, cited retrieval.
|
|
3
|
+
*
|
|
4
|
+
* The gap between a RAG demo and an enterprise RAG system is not the embedding
|
|
5
|
+
* model. It is these five properties, all of which live here:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Scoped.** Every read carries an {@link AccessScope} that is compiled into
|
|
8
|
+
* the database query. There is no code path that reads unscoped, so a
|
|
9
|
+
* cross-tenant answer is not something a caller can cause by forgetting.
|
|
10
|
+
* 2. **Hybrid.** Dense retrieval alone misses exact tokens — error codes, SKUs,
|
|
11
|
+
* contract clause numbers, function names. Lexical search alone misses
|
|
12
|
+
* paraphrase. RRF over both, then MMR for diversity, then an optional
|
|
13
|
+
* reranker.
|
|
14
|
+
* 3. **Honest about degradation.** When the store has no lexical index the
|
|
15
|
+
* retrieval is dense-only and SAYS so on the span and in the result, instead
|
|
16
|
+
* of quietly returning worse answers.
|
|
17
|
+
* 4. **Cited.** Every passage carries its source id, title and URI, and the
|
|
18
|
+
* prompt builder numbers them, so an answer can be checked. An uncitable
|
|
19
|
+
* answer is an unauditable one, and enterprises audit.
|
|
20
|
+
* 5. **Traced and costed.** Retrieval and generation are spans under one trace,
|
|
21
|
+
* with token counts and cost, so "why was this answer bad" and "why did this
|
|
22
|
+
* cost that" are answerable from the same record.
|
|
23
|
+
*/
|
|
24
|
+
import { maximalMarginalRelevance, reciprocalRankFusion } from '../retrieval/fusion.js';
|
|
25
|
+
import { SOURCE_ID_FIELD, TITLE_FIELD, URI_FIELD, } from '../ingest/types.js';
|
|
26
|
+
export class EnterpriseRetriever {
|
|
27
|
+
_store;
|
|
28
|
+
_embed;
|
|
29
|
+
_reranker;
|
|
30
|
+
_defaultScope;
|
|
31
|
+
_denseWeight;
|
|
32
|
+
_keywordWeight;
|
|
33
|
+
_mmrLambda;
|
|
34
|
+
_diversity;
|
|
35
|
+
_tracer;
|
|
36
|
+
constructor(opts) {
|
|
37
|
+
this._store = opts.store;
|
|
38
|
+
this._embed = opts.embed;
|
|
39
|
+
this._reranker = opts.reranker;
|
|
40
|
+
this._defaultScope = opts.defaultScope;
|
|
41
|
+
this._denseWeight = opts.denseWeight ?? 1;
|
|
42
|
+
this._keywordWeight = opts.keywordWeight ?? 1;
|
|
43
|
+
this._mmrLambda = opts.mmrLambda ?? 0.7;
|
|
44
|
+
this._diversity = !opts.disableDiversity;
|
|
45
|
+
this._tracer = opts.tracer;
|
|
46
|
+
}
|
|
47
|
+
async retrieve(query, opts = {}) {
|
|
48
|
+
const topK = Math.max(1, opts.topK ?? 5);
|
|
49
|
+
const candidateK = Math.max(topK, opts.candidateK ?? topK * 4);
|
|
50
|
+
const scope = opts.scope ?? this._defaultScope;
|
|
51
|
+
const span = this._tracer?.startSpan('retrieval.hybrid', {
|
|
52
|
+
kind: 'retrieval',
|
|
53
|
+
...(opts.parent ? { parent: opts.parent } : {}),
|
|
54
|
+
attributes: {
|
|
55
|
+
'retrieval.store': this._store.name,
|
|
56
|
+
'retrieval.top_k': topK,
|
|
57
|
+
'retrieval.tenant': scope?.tenantId ?? 'unscoped',
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
try {
|
|
61
|
+
const queryVector = this._embed ? await this._embed(query) : undefined;
|
|
62
|
+
const base = {
|
|
63
|
+
text: query,
|
|
64
|
+
topK: candidateK,
|
|
65
|
+
...(opts.filter ? { filter: opts.filter } : {}),
|
|
66
|
+
...(scope ? { scope } : {}),
|
|
67
|
+
includeVectors: this._diversity,
|
|
68
|
+
};
|
|
69
|
+
// Both arms run concurrently: they hit different indexes and the
|
|
70
|
+
// sequential version doubles p95 latency for nothing.
|
|
71
|
+
const [dense, keyword] = await Promise.all([
|
|
72
|
+
queryVector
|
|
73
|
+
? this._store.query({ ...base, vector: queryVector })
|
|
74
|
+
: Promise.resolve([]),
|
|
75
|
+
this._store.keywordSearch
|
|
76
|
+
? this._store.keywordSearch(base)
|
|
77
|
+
: Promise.resolve([]),
|
|
78
|
+
]);
|
|
79
|
+
const mode = pickMode(dense.length > 0, keyword.length > 0, Boolean(this._store.keywordSearch));
|
|
80
|
+
const byId = new Map();
|
|
81
|
+
for (const match of [...dense, ...keyword]) {
|
|
82
|
+
if (!byId.has(match.id))
|
|
83
|
+
byId.set(match.id, match);
|
|
84
|
+
}
|
|
85
|
+
let ordered = this._fuse(dense, keyword, byId);
|
|
86
|
+
if (this._diversity && queryVector && ordered.length > topK) {
|
|
87
|
+
ordered = this._diversify(queryVector, ordered, byId, topK);
|
|
88
|
+
}
|
|
89
|
+
let passages = ordered.slice(0, Math.max(topK, this._reranker ? candidateK : topK))
|
|
90
|
+
.map((hit) => toPassage(byId.get(hit.id), hit.score));
|
|
91
|
+
if (this._reranker && passages.length > 0) {
|
|
92
|
+
passages = [...await this._reranker(query, passages)];
|
|
93
|
+
}
|
|
94
|
+
if (opts.minScore !== undefined) {
|
|
95
|
+
passages = passages.filter((p) => p.score >= opts.minScore);
|
|
96
|
+
}
|
|
97
|
+
passages = passages.slice(0, topK);
|
|
98
|
+
span?.setAttributes({
|
|
99
|
+
'retrieval.mode': mode,
|
|
100
|
+
'retrieval.candidates': byId.size,
|
|
101
|
+
'retrieval.returned': passages.length,
|
|
102
|
+
'retrieval.dense_hits': dense.length,
|
|
103
|
+
'retrieval.keyword_hits': keyword.length,
|
|
104
|
+
'retrieval.reranked': Boolean(this._reranker),
|
|
105
|
+
});
|
|
106
|
+
span?.end('ok');
|
|
107
|
+
const result = {
|
|
108
|
+
passages,
|
|
109
|
+
mode,
|
|
110
|
+
candidatesConsidered: byId.size,
|
|
111
|
+
};
|
|
112
|
+
if (span)
|
|
113
|
+
result.traceId = span.traceId;
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
span?.fail(err);
|
|
118
|
+
throw err;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Retrieve, then answer strictly from what was retrieved.
|
|
123
|
+
*
|
|
124
|
+
* The system prompt forbids answering beyond the passages and requires
|
|
125
|
+
* `[n]` citations. That is not decoration: an enterprise deployment is judged
|
|
126
|
+
* on whether a wrong answer is *detectable*, and an uncited claim is not.
|
|
127
|
+
*/
|
|
128
|
+
async answer(query, bridge, opts = {}) {
|
|
129
|
+
const span = this._tracer?.startSpan('rag.answer', {
|
|
130
|
+
kind: 'agent',
|
|
131
|
+
...(opts.parent ? { parent: opts.parent } : {}),
|
|
132
|
+
});
|
|
133
|
+
try {
|
|
134
|
+
const result = await this.retrieve(query, { ...opts, ...(span ? { parent: span } : {}) });
|
|
135
|
+
if (result.passages.length === 0) {
|
|
136
|
+
// Refusing beats hallucinating: with no evidence there is nothing to
|
|
137
|
+
// ground an answer in, and a confident answer here is the exact
|
|
138
|
+
// failure enterprises cite when a pilot is killed.
|
|
139
|
+
span?.setAttribute('rag.refused', true);
|
|
140
|
+
span?.end('ok');
|
|
141
|
+
return { answer: NO_EVIDENCE_ANSWER, result };
|
|
142
|
+
}
|
|
143
|
+
const prompt = buildGroundedPrompt(query, result.passages);
|
|
144
|
+
const answer = await bridge.generate(prompt, {
|
|
145
|
+
systemPrompt: opts.systemPrompt ?? GROUNDED_SYSTEM_PROMPT,
|
|
146
|
+
...(opts.maxTokens !== undefined ? { maxTokens: opts.maxTokens } : {}),
|
|
147
|
+
...(opts.model !== undefined ? { model: opts.model } : {}),
|
|
148
|
+
});
|
|
149
|
+
span?.setAttributes({ 'rag.passages': result.passages.length, 'rag.mode': result.mode });
|
|
150
|
+
span?.end('ok');
|
|
151
|
+
return { answer, result };
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
span?.fail(err);
|
|
155
|
+
throw err;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
_fuse(dense, keyword, byId) {
|
|
159
|
+
const lists = [];
|
|
160
|
+
if (dense.length)
|
|
161
|
+
lists.push({ ids: dense.map((m) => m.id), weight: this._denseWeight });
|
|
162
|
+
if (keyword.length)
|
|
163
|
+
lists.push({ ids: keyword.map((m) => m.id), weight: this._keywordWeight });
|
|
164
|
+
if (lists.length === 0)
|
|
165
|
+
return [];
|
|
166
|
+
if (lists.length === 1) {
|
|
167
|
+
// One arm: keep the store's own similarity rather than replacing it with
|
|
168
|
+
// an RRF score, which carries no meaning for a single list.
|
|
169
|
+
const only = dense.length ? dense : keyword;
|
|
170
|
+
return only.map((m) => ({ id: m.id, score: m.score }));
|
|
171
|
+
}
|
|
172
|
+
const fused = reciprocalRankFusion(lists);
|
|
173
|
+
const best = fused[0]?.score ?? 1;
|
|
174
|
+
// RRF scores live on a tiny arbitrary scale (~1/60); normalising to [0,1]
|
|
175
|
+
// keeps `minScore` meaning the same thing on one arm or two.
|
|
176
|
+
return fused
|
|
177
|
+
.filter((hit) => byId.has(hit.id))
|
|
178
|
+
.map((hit) => ({ id: hit.id, score: best > 0 ? hit.score / best : 0 }));
|
|
179
|
+
}
|
|
180
|
+
_diversify(queryVector, ordered, byId, topK) {
|
|
181
|
+
const candidates = ordered
|
|
182
|
+
.map((hit) => ({ id: hit.id, vector: byId.get(hit.id)?.vector }))
|
|
183
|
+
.filter((c) => Boolean(c.vector));
|
|
184
|
+
// Without stored vectors MMR cannot run; keeping the fused order is the
|
|
185
|
+
// correct degradation, not an error.
|
|
186
|
+
if (candidates.length < 2)
|
|
187
|
+
return ordered;
|
|
188
|
+
const scores = new Map(ordered.map((hit) => [hit.id, hit.score]));
|
|
189
|
+
const picked = maximalMarginalRelevance(queryVector, candidates, topK, this._mmrLambda);
|
|
190
|
+
const pickedSet = new Set(picked);
|
|
191
|
+
return [
|
|
192
|
+
...picked.map((id) => ({ id, score: scores.get(id) ?? 0 })),
|
|
193
|
+
...ordered.filter((hit) => !pickedSet.has(hit.id)),
|
|
194
|
+
];
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function pickMode(hasDense, hasKeyword, storeHasKeyword) {
|
|
198
|
+
if (hasDense && hasKeyword)
|
|
199
|
+
return 'hybrid';
|
|
200
|
+
if (hasDense)
|
|
201
|
+
return 'dense-only';
|
|
202
|
+
if (hasKeyword)
|
|
203
|
+
return 'keyword-only';
|
|
204
|
+
return storeHasKeyword ? 'empty' : 'dense-only';
|
|
205
|
+
}
|
|
206
|
+
function toPassage(match, score) {
|
|
207
|
+
const metadata = match.metadata;
|
|
208
|
+
const passage = {
|
|
209
|
+
id: match.id,
|
|
210
|
+
text: match.text,
|
|
211
|
+
score,
|
|
212
|
+
sourceId: String(metadata[SOURCE_ID_FIELD] ?? match.id),
|
|
213
|
+
metadata,
|
|
214
|
+
};
|
|
215
|
+
const title = metadata[TITLE_FIELD];
|
|
216
|
+
if (typeof title === 'string')
|
|
217
|
+
passage.title = title;
|
|
218
|
+
const uri = metadata[URI_FIELD];
|
|
219
|
+
if (typeof uri === 'string')
|
|
220
|
+
passage.uri = uri;
|
|
221
|
+
return passage;
|
|
222
|
+
}
|
|
223
|
+
export const GROUNDED_SYSTEM_PROMPT = 'You answer strictly from the numbered passages provided. ' +
|
|
224
|
+
'Cite every claim with the passage number in square brackets, e.g. [2]. ' +
|
|
225
|
+
'If the passages do not contain the answer, say so plainly and name what is missing. ' +
|
|
226
|
+
'Never rely on knowledge that is not in the passages.';
|
|
227
|
+
export const NO_EVIDENCE_ANSWER = 'No indexed passage matched this question within the caller\'s access scope, ' +
|
|
228
|
+
'so there is nothing to ground an answer in.';
|
|
229
|
+
/** Renders passages as a numbered, citable evidence block. */
|
|
230
|
+
export function buildGroundedPrompt(query, passages) {
|
|
231
|
+
const evidence = passages
|
|
232
|
+
.map((p, i) => {
|
|
233
|
+
const label = p.title ?? p.sourceId;
|
|
234
|
+
const uri = p.uri ? ` (${p.uri})` : '';
|
|
235
|
+
return `[${i + 1}] ${label}${uri}\n${p.text}`;
|
|
236
|
+
})
|
|
237
|
+
.join('\n\n');
|
|
238
|
+
return `Passages:\n\n${evidence}\n\nQuestion: ${query}\n\nAnswer, citing passages by number:`;
|
|
239
|
+
}
|
|
240
|
+
//# sourceMappingURL=EnterpriseRetriever.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EnterpriseRetriever.js","sourceRoot":"","sources":["../../src/rag/EnterpriseRetriever.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,EAAE,wBAAwB,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAExF,OAAO,EACH,eAAe,EACf,WAAW,EACX,SAAS,GACZ,MAAM,oBAAoB,CAAC;AAsE5B,MAAM,OAAO,mBAAmB;IACX,MAAM,CAAc;IACpB,MAAM,CAAwD;IAC9D,SAAS,CAAuB;IAChC,aAAa,CAA0B;IACvC,YAAY,CAAS;IACrB,cAAc,CAAS;IACvB,UAAU,CAAS;IACnB,UAAU,CAAU;IACpB,OAAO,CAAqB;IAE7C,YAAY,IAAgC;QACxC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC/B,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,YAAY,CAAC;QACvC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,WAAW,IAAI,CAAC,CAAC;QAC1C,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,aAAa,IAAI,CAAC,CAAC;QAC9C,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,SAAS,IAAI,GAAG,CAAC;QACxC,IAAI,CAAC,UAAU,GAAG,CAAC,IAAI,CAAC,gBAAgB,CAAC;QACzC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,KAAa,EAAE,OAAwB,EAAE;QACpD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC;QACzC,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC;QAC/D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,aAAa,CAAC;QAE/C,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,kBAAkB,EAAE;YACrD,IAAI,EAAE,WAAW;YACjB,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/C,UAAU,EAAE;gBACR,iBAAiB,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI;gBACnC,iBAAiB,EAAE,IAAI;gBACvB,kBAAkB,EAAE,KAAK,EAAE,QAAQ,IAAI,UAAU;aACpD;SACJ,CAAC,CAAC;QAEH,IAAI,CAAC;YACD,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YAEvE,MAAM,IAAI,GAAG;gBACT,IAAI,EAAE,KAAK;gBACX,IAAI,EAAE,UAAU;gBAChB,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC/C,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC3B,cAAc,EAAE,IAAI,CAAC,UAAU;aAClC,CAAC;YAEF,iEAAiE;YACjE,sDAAsD;YACtD,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;gBACvC,WAAW;oBACP,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;oBACrD,CAAC,CAAC,OAAO,CAAC,OAAO,CAAgB,EAAE,CAAC;gBACxC,IAAI,CAAC,MAAM,CAAC,aAAa;oBACrB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC;oBACjC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAgB,EAAE,CAAC;aAC3C,CAAC,CAAC;YAEH,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC;YAChG,MAAM,IAAI,GAAG,IAAI,GAAG,EAAuB,CAAC;YAC5C,KAAK,MAAM,KAAK,IAAI,CAAC,GAAG,KAAK,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC;gBACzC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;oBAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;YACvD,CAAC;YAED,IAAI,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;YAE/C,IAAI,IAAI,CAAC,UAAU,IAAI,WAAW,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,EAAE,CAAC;gBAC1D,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;YAChE,CAAC;YAED,IAAI,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;iBAC9E,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAgB,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;YAEzE,IAAI,IAAI,CAAC,SAAS,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACxC,QAAQ,GAAG,CAAC,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;YAC1D,CAAC;YACD,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAC9B,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAK,IAAI,CAAC,QAAmB,CAAC,CAAC;YAC5E,CAAC;YACD,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;YAEnC,IAAI,EAAE,aAAa,CAAC;gBAChB,gBAAgB,EAAE,IAAI;gBACtB,sBAAsB,EAAE,IAAI,CAAC,IAAI;gBACjC,oBAAoB,EAAE,QAAQ,CAAC,MAAM;gBACrC,sBAAsB,EAAE,KAAK,CAAC,MAAM;gBACpC,wBAAwB,EAAE,OAAO,CAAC,MAAM;gBACxC,oBAAoB,EAAE,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC;aAChD,CAAC,CAAC;YACH,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;YAEhB,MAAM,MAAM,GAAoB;gBAC5B,QAAQ;gBACR,IAAI;gBACJ,oBAAoB,EAAE,IAAI,CAAC,IAAI;aAClC,CAAC;YACF,IAAI,IAAI;gBAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;YACxC,OAAO,MAAM,CAAC;QAClB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;YAChB,MAAM,GAAG,CAAC;QACd,CAAC;IACL,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,MAAM,CACR,KAAa,EACb,MAAyB,EACzB,OAAwF,EAAE;QAE1F,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,YAAY,EAAE;YAC/C,IAAI,EAAE,OAAO;YACb,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAClD,CAAC,CAAC;QAEH,IAAI,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,GAAG,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;YAE1F,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC/B,qEAAqE;gBACrE,gEAAgE;gBAChE,mDAAmD;gBACnD,IAAI,EAAE,YAAY,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;gBACxC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;gBAChB,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,EAAE,CAAC;YAClD,CAAC;YAED,MAAM,MAAM,GAAG,mBAAmB,CAAC,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;YAC3D,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE;gBACzC,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,sBAAsB;gBACzD,GAAG,CAAC,IAAI,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACtE,GAAG,CAAC,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC7D,CAAC,CAAC;YAEH,IAAI,EAAE,aAAa,CAAC,EAAE,cAAc,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;YACzF,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;YAChB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAC9B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;YAChB,MAAM,GAAG,CAAC;QACd,CAAC;IACL,CAAC;IAEO,KAAK,CACT,KAA6B,EAC7B,OAA+B,EAC/B,IAA8B;QAE9B,MAAM,KAAK,GAAG,EAAE,CAAC;QACjB,IAAI,KAAK,CAAC,MAAM;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC;QACzF,IAAI,OAAO,CAAC,MAAM;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC;QAE/F,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAClC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrB,yEAAyE;YACzE,4DAA4D;YAC5D,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC;YAC5C,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAC3D,CAAC;QAED,MAAM,KAAK,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;QAC1C,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC;QAClC,0EAA0E;QAC1E,6DAA6D;QAC7D,OAAO,KAAK;aACP,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;aACjC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAChF,CAAC;IAEO,UAAU,CACd,WAAyB,EACzB,OAA6C,EAC7C,IAA8B,EAC9B,IAAY;QAEZ,MAAM,UAAU,GAAG,OAAO;aACrB,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;aAChE,MAAM,CAAC,CAAC,CAAC,EAA6C,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QAEjF,wEAAwE;QACxE,qCAAqC;QACrC,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,OAAO,CAAC;QAE1C,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAClE,MAAM,MAAM,GAAG,wBAAwB,CAAC,WAAW,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QACxF,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;QAElC,OAAO;YACH,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC3D,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;SACrD,CAAC;IACN,CAAC;CACJ;AAED,SAAS,QAAQ,CAAC,QAAiB,EAAE,UAAmB,EAAE,eAAwB;IAC9E,IAAI,QAAQ,IAAI,UAAU;QAAE,OAAO,QAAQ,CAAC;IAC5C,IAAI,QAAQ;QAAE,OAAO,YAAY,CAAC;IAClC,IAAI,UAAU;QAAE,OAAO,cAAc,CAAC;IACtC,OAAO,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC;AACpD,CAAC;AAED,SAAS,SAAS,CAAC,KAAkB,EAAE,KAAa;IAChD,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAmC,CAAC;IAC3D,MAAM,OAAO,GAAqB;QAC9B,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,KAAK;QACL,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACvD,QAAQ;KACX,CAAC;IACF,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;IACpC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC;IACrD,MAAM,GAAG,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;IAChC,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,CAAC,GAAG,GAAG,GAAG,CAAC;IAC/C,OAAO,OAAO,CAAC;AACnB,CAAC;AAED,MAAM,CAAC,MAAM,sBAAsB,GAC/B,2DAA2D;IAC3D,yEAAyE;IACzE,sFAAsF;IACtF,sDAAsD,CAAC;AAE3D,MAAM,CAAC,MAAM,kBAAkB,GAC3B,8EAA8E;IAC9E,6CAA6C,CAAC;AAElD,8DAA8D;AAC9D,MAAM,UAAU,mBAAmB,CAAC,KAAa,EAAE,QAAqC;IACpF,MAAM,QAAQ,GAAG,QAAQ;SACpB,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACV,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,QAAQ,CAAC;QACpC,MAAM,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QACvC,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,KAAK,GAAG,GAAG,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;IAClD,CAAC,CAAC;SACD,IAAI,CAAC,MAAM,CAAC,CAAC;IAElB,OAAO,gBAAgB,QAAQ,iBAAiB,KAAK,wCAAwC,CAAC;AAClG,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rag — enterprise retrieval: hybrid, tenant/ACL scoped, cited, traced.
|
|
3
|
+
*/
|
|
4
|
+
export { EnterpriseRetriever, buildGroundedPrompt, GROUNDED_SYSTEM_PROMPT, NO_EVIDENCE_ANSWER, } from './EnterpriseRetriever.js';
|
|
5
|
+
export type { EnterpriseRetrieverOptions, RetrieveOptions, RetrievalResult, RetrievalMode, RetrievedPassage, Reranker, } from './EnterpriseRetriever.js';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rag/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACH,mBAAmB,EACnB,mBAAmB,EACnB,sBAAsB,EACtB,kBAAkB,GACrB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EACR,0BAA0B,EAC1B,eAAe,EACf,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,QAAQ,GACX,MAAM,0BAA0B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rag/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACH,mBAAmB,EACnB,mBAAmB,EACnB,sBAAsB,EACtB,kBAAkB,GACrB,MAAM,0BAA0B,CAAC"}
|
package/dist/retrieval/bm25.d.ts
CHANGED
|
@@ -6,13 +6,35 @@
|
|
|
6
6
|
* Fusing the two (see {@link ./fusion}) is what lifts the memory layer from
|
|
7
7
|
* "cosine only" to a hybrid retriever on par with Weaviate-style search.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* Two things are separable here and are kept separate on purpose:
|
|
10
|
+
*
|
|
11
|
+
* • the SCORING CORE ({@link bm25Idf}, {@link bm25LengthNorm}, {@link bm25TermScore})
|
|
12
|
+
* — the Okapi formula itself, which is the same wherever lexical relevance is
|
|
13
|
+
* scored, whether the index is built inline from raw text or read out of a
|
|
14
|
+
* precomputed per-field term table (BM25F). Exported so a consumer with its own
|
|
15
|
+
* index shape reuses the maths instead of restating it and drifting from it;
|
|
16
|
+
* • the TOKENIZER — which is genuinely corpus-specific. Prose wants stopword
|
|
17
|
+
* removal and stemming; identifiers and error codes want neither. So the
|
|
18
|
+
* tokenizer is an injectable {@link Bm25Options.tokenize} hook defaulting to the
|
|
19
|
+
* package's plain `tokenize`, rather than a second copy of BM25 growing around
|
|
20
|
+
* each specialisation.
|
|
21
|
+
*
|
|
22
|
+
* Pure and zero-dependency.
|
|
10
23
|
*/
|
|
24
|
+
/** Splits text into the terms BM25 scores over. */
|
|
25
|
+
export type Bm25Tokenizer = (text: string) => string[];
|
|
11
26
|
export interface Bm25Options {
|
|
12
27
|
/** Term-frequency saturation. Higher = TF matters more. Default 1.5. */
|
|
13
28
|
k1?: number;
|
|
14
29
|
/** Length normalisation, 0..1. Higher = penalise long docs more. Default 0.75. */
|
|
15
30
|
b?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Tokenizer for both the query and the documents. Default is the package's plain
|
|
33
|
+
* `tokenize` (lowercase word split), which preserves identifiers verbatim. Supply
|
|
34
|
+
* a stemming/stopword tokenizer when the corpus is prose — the SAME function must
|
|
35
|
+
* be used for query and documents or nothing will match.
|
|
36
|
+
*/
|
|
37
|
+
tokenize?: Bm25Tokenizer;
|
|
16
38
|
}
|
|
17
39
|
export interface Bm25Doc {
|
|
18
40
|
id: string;
|
|
@@ -22,6 +44,31 @@ export interface Bm25Hit {
|
|
|
22
44
|
id: string;
|
|
23
45
|
score: number;
|
|
24
46
|
}
|
|
47
|
+
/** Term-frequency saturation used when {@link Bm25Options.k1} is omitted. */
|
|
48
|
+
export declare const BM25_DEFAULT_K1 = 1.5;
|
|
49
|
+
/** Length normalisation used when {@link Bm25Options.b} is omitted. */
|
|
50
|
+
export declare const BM25_DEFAULT_B = 0.75;
|
|
51
|
+
/**
|
|
52
|
+
* Inverse document frequency with the +1 smoothing variant, which is always
|
|
53
|
+
* non-negative — the unsmoothed form goes negative for a term present in more than
|
|
54
|
+
* half the corpus, which subtracts score for a MATCH.
|
|
55
|
+
*/
|
|
56
|
+
export declare function bm25Idf(documentCount: number, documentFrequency: number): number;
|
|
57
|
+
/**
|
|
58
|
+
* The document-length penalty `1 - b + b·(len/avgdl)`, factored out because it is
|
|
59
|
+
* per-DOCUMENT: computing it once and reusing it across the query's terms is what
|
|
60
|
+
* keeps scoring O(terms) rather than O(terms) divisions of the same quotient.
|
|
61
|
+
*/
|
|
62
|
+
export declare function bm25LengthNorm(length: number, averageLength: number, b?: number): number;
|
|
63
|
+
/**
|
|
64
|
+
* One term's contribution: `idf · f(k1+1) / (f + k1·lengthNorm)`.
|
|
65
|
+
*
|
|
66
|
+
* `frequency` is whatever the caller counts as the term's occurrence weight — a raw
|
|
67
|
+
* count for plain BM25, or a field-weighted sum for BM25F. Combining fields BEFORE
|
|
68
|
+
* saturation (rather than saturating each field and summing) is what makes BM25F
|
|
69
|
+
* behave like BM25 over a weighted document.
|
|
70
|
+
*/
|
|
71
|
+
export declare function bm25TermScore(frequency: number, idf: number, lengthNorm: number, k1?: number): number;
|
|
25
72
|
/**
|
|
26
73
|
* Scores every document against `query` with Okapi BM25, returning hits sorted by
|
|
27
74
|
* descending score (documents with no query-term overlap score 0 and are dropped).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bm25.d.ts","sourceRoot":"","sources":["../../src/retrieval/bm25.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"bm25.d.ts","sourceRoot":"","sources":["../../src/retrieval/bm25.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,mDAAmD;AACnD,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;AAEvD,MAAM,WAAW,WAAW;IACxB,wEAAwE;IACxE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,kFAAkF;IAClF,CAAC,CAAC,EAAE,MAAM,CAAC;IACX;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC5B;AAED,MAAM,WAAW,OAAO;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,OAAO;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,6EAA6E;AAC7E,eAAO,MAAM,eAAe,MAAM,CAAC;AACnC,uEAAuE;AACvE,eAAO,MAAM,cAAc,OAAO,CAAC;AAEnC;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,aAAa,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,MAAM,CAIhF;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC,GAAE,MAAuB,GAAG,MAAM,CAGxG;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,GAAE,MAAwB,GAAG,MAAM,CAGtH;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,IAAI,GAAE,WAAgB,GAAG,OAAO,EAAE,CA0C5F"}
|
package/dist/retrieval/bm25.js
CHANGED
|
@@ -6,9 +6,58 @@
|
|
|
6
6
|
* Fusing the two (see {@link ./fusion}) is what lifts the memory layer from
|
|
7
7
|
* "cosine only" to a hybrid retriever on par with Weaviate-style search.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* Two things are separable here and are kept separate on purpose:
|
|
10
|
+
*
|
|
11
|
+
* • the SCORING CORE ({@link bm25Idf}, {@link bm25LengthNorm}, {@link bm25TermScore})
|
|
12
|
+
* — the Okapi formula itself, which is the same wherever lexical relevance is
|
|
13
|
+
* scored, whether the index is built inline from raw text or read out of a
|
|
14
|
+
* precomputed per-field term table (BM25F). Exported so a consumer with its own
|
|
15
|
+
* index shape reuses the maths instead of restating it and drifting from it;
|
|
16
|
+
* • the TOKENIZER — which is genuinely corpus-specific. Prose wants stopword
|
|
17
|
+
* removal and stemming; identifiers and error codes want neither. So the
|
|
18
|
+
* tokenizer is an injectable {@link Bm25Options.tokenize} hook defaulting to the
|
|
19
|
+
* package's plain `tokenize`, rather than a second copy of BM25 growing around
|
|
20
|
+
* each specialisation.
|
|
21
|
+
*
|
|
22
|
+
* Pure and zero-dependency.
|
|
10
23
|
*/
|
|
11
24
|
import { tokenize } from '../similarity/index.js';
|
|
25
|
+
/** Term-frequency saturation used when {@link Bm25Options.k1} is omitted. */
|
|
26
|
+
export const BM25_DEFAULT_K1 = 1.5;
|
|
27
|
+
/** Length normalisation used when {@link Bm25Options.b} is omitted. */
|
|
28
|
+
export const BM25_DEFAULT_B = 0.75;
|
|
29
|
+
/**
|
|
30
|
+
* Inverse document frequency with the +1 smoothing variant, which is always
|
|
31
|
+
* non-negative — the unsmoothed form goes negative for a term present in more than
|
|
32
|
+
* half the corpus, which subtracts score for a MATCH.
|
|
33
|
+
*/
|
|
34
|
+
export function bm25Idf(documentCount, documentFrequency) {
|
|
35
|
+
const n = Math.max(documentCount, 1);
|
|
36
|
+
const df = Math.max(0, Math.min(documentFrequency, n));
|
|
37
|
+
return Math.log(1 + (n - df + 0.5) / (df + 0.5));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The document-length penalty `1 - b + b·(len/avgdl)`, factored out because it is
|
|
41
|
+
* per-DOCUMENT: computing it once and reusing it across the query's terms is what
|
|
42
|
+
* keeps scoring O(terms) rather than O(terms) divisions of the same quotient.
|
|
43
|
+
*/
|
|
44
|
+
export function bm25LengthNorm(length, averageLength, b = BM25_DEFAULT_B) {
|
|
45
|
+
const avg = averageLength > 0 ? averageLength : 1;
|
|
46
|
+
return 1 - b + b * (Math.max(0, length) / avg);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* One term's contribution: `idf · f(k1+1) / (f + k1·lengthNorm)`.
|
|
50
|
+
*
|
|
51
|
+
* `frequency` is whatever the caller counts as the term's occurrence weight — a raw
|
|
52
|
+
* count for plain BM25, or a field-weighted sum for BM25F. Combining fields BEFORE
|
|
53
|
+
* saturation (rather than saturating each field and summing) is what makes BM25F
|
|
54
|
+
* behave like BM25 over a weighted document.
|
|
55
|
+
*/
|
|
56
|
+
export function bm25TermScore(frequency, idf, lengthNorm, k1 = BM25_DEFAULT_K1) {
|
|
57
|
+
if (frequency <= 0)
|
|
58
|
+
return 0;
|
|
59
|
+
return (idf * (frequency * (k1 + 1))) / (frequency + k1 * lengthNorm);
|
|
60
|
+
}
|
|
12
61
|
/**
|
|
13
62
|
* Scores every document against `query` with Okapi BM25, returning hits sorted by
|
|
14
63
|
* descending score (documents with no query-term overlap score 0 and are dropped).
|
|
@@ -16,12 +65,13 @@ import { tokenize } from '../similarity/index.js';
|
|
|
16
65
|
* store / a vector pre-filter) this is O(N·terms) and needs no persistence.
|
|
17
66
|
*/
|
|
18
67
|
export function bm25Search(query, docs, opts = {}) {
|
|
19
|
-
const k1 = opts.k1 ??
|
|
20
|
-
const b = opts.b ??
|
|
68
|
+
const k1 = opts.k1 ?? BM25_DEFAULT_K1;
|
|
69
|
+
const b = opts.b ?? BM25_DEFAULT_B;
|
|
70
|
+
const split = opts.tokenize ?? tokenize;
|
|
21
71
|
const N = docs.length;
|
|
22
72
|
if (N === 0)
|
|
23
73
|
return [];
|
|
24
|
-
const queryTerms = new Set(
|
|
74
|
+
const queryTerms = new Set(split(query));
|
|
25
75
|
if (queryTerms.size === 0)
|
|
26
76
|
return [];
|
|
27
77
|
// Per-doc term frequencies + document lengths.
|
|
@@ -29,7 +79,7 @@ export function bm25Search(query, docs, opts = {}) {
|
|
|
29
79
|
const df = new Map();
|
|
30
80
|
let totalLen = 0;
|
|
31
81
|
for (const doc of docs) {
|
|
32
|
-
const tokens =
|
|
82
|
+
const tokens = split(doc.text);
|
|
33
83
|
const tf = new Map();
|
|
34
84
|
for (const t of tokens)
|
|
35
85
|
tf.set(t, (tf.get(t) ?? 0) + 1);
|
|
@@ -40,22 +90,18 @@ export function bm25Search(query, docs, opts = {}) {
|
|
|
40
90
|
totalLen += tokens.length;
|
|
41
91
|
}
|
|
42
92
|
const avgdl = totalLen / N || 1;
|
|
43
|
-
// idf with the +1 smoothing variant (always non-negative).
|
|
44
93
|
const idf = new Map();
|
|
45
|
-
for (const term of queryTerms)
|
|
46
|
-
|
|
47
|
-
idf.set(term, Math.log(1 + (N - n + 0.5) / (n + 0.5)));
|
|
48
|
-
}
|
|
94
|
+
for (const term of queryTerms)
|
|
95
|
+
idf.set(term, bm25Idf(N, df.get(term) ?? 0));
|
|
49
96
|
const hits = [];
|
|
50
97
|
for (const d of docTerms) {
|
|
98
|
+
const lengthNorm = bm25LengthNorm(d.len, avgdl, b);
|
|
51
99
|
let score = 0;
|
|
52
100
|
for (const term of queryTerms) {
|
|
53
101
|
const f = d.tf.get(term);
|
|
54
102
|
if (!f)
|
|
55
103
|
continue;
|
|
56
|
-
|
|
57
|
-
const denom = f + k1 * (1 - b + b * (d.len / avgdl));
|
|
58
|
-
score += idf.get(term) * (numer / denom);
|
|
104
|
+
score += bm25TermScore(f, idf.get(term), lengthNorm, k1);
|
|
59
105
|
}
|
|
60
106
|
if (score > 0)
|
|
61
107
|
hits.push({ id: d.id, score });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bm25.js","sourceRoot":"","sources":["../../src/retrieval/bm25.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"bm25.js","sourceRoot":"","sources":["../../src/retrieval/bm25.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export { chunkText } from './chunk.js';
|
|
9
9
|
export type { Chunk, ChunkOptions } from './chunk.js';
|
|
10
|
-
export { bm25Search } from './bm25.js';
|
|
11
|
-
export type { Bm25Doc, Bm25Hit, Bm25Options } from './bm25.js';
|
|
10
|
+
export { bm25Search, bm25Idf, bm25LengthNorm, bm25TermScore, BM25_DEFAULT_K1, BM25_DEFAULT_B } from './bm25.js';
|
|
11
|
+
export type { Bm25Doc, Bm25Hit, Bm25Options, Bm25Tokenizer } from './bm25.js';
|
|
12
12
|
export { reciprocalRankFusion, maximalMarginalRelevance } from './fusion.js';
|
|
13
13
|
export type { RankedList, FusedHit, MmrCandidate } from './fusion.js';
|
|
14
14
|
export { hybridRetrieve } from './HybridRetriever.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/retrieval/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEtD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/retrieval/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEtD,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChH,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE9E,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAC7E,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEtE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EACR,kBAAkB,EAClB,WAAW,EACX,qBAAqB,EACrB,SAAS,GACZ,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACnD,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/retrieval/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* browser, Node, and the SSM runtime alike.
|
|
7
7
|
*/
|
|
8
8
|
export { chunkText } from './chunk.js';
|
|
9
|
-
export { bm25Search } from './bm25.js';
|
|
9
|
+
export { bm25Search, bm25Idf, bm25LengthNorm, bm25TermScore, BM25_DEFAULT_K1, BM25_DEFAULT_B } from './bm25.js';
|
|
10
10
|
export { reciprocalRankFusion, maximalMarginalRelevance } from './fusion.js';
|
|
11
11
|
export { hybridRetrieve } from './HybridRetriever.js';
|
|
12
12
|
export { HnswIndex, denseSearch } from './hnsw.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/retrieval/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGvC,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/retrieval/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGvC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGhH,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAG7E,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAQtD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC"}
|