@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.
Files changed (225) hide show
  1. package/README.md +10 -0
  2. package/dist/bridges/AnthropicBridge.d.ts +9 -1
  3. package/dist/bridges/AnthropicBridge.d.ts.map +1 -1
  4. package/dist/bridges/AnthropicBridge.js +58 -2
  5. package/dist/bridges/AnthropicBridge.js.map +1 -1
  6. package/dist/bridges/CachingBridge.d.ts +9 -1
  7. package/dist/bridges/CachingBridge.d.ts.map +1 -1
  8. package/dist/bridges/CachingBridge.js +24 -1
  9. package/dist/bridges/CachingBridge.js.map +1 -1
  10. package/dist/bridges/OpenAIBridge.d.ts +4 -1
  11. package/dist/bridges/OpenAIBridge.d.ts.map +1 -1
  12. package/dist/bridges/OpenAIBridge.js +40 -2
  13. package/dist/bridges/OpenAIBridge.js.map +1 -1
  14. package/dist/bridges/SemanticCachingBridge.d.ts +8 -1
  15. package/dist/bridges/SemanticCachingBridge.d.ts.map +1 -1
  16. package/dist/bridges/SemanticCachingBridge.js +26 -1
  17. package/dist/bridges/SemanticCachingBridge.js.map +1 -1
  18. package/dist/bridges/TransformerBridge.d.ts +13 -0
  19. package/dist/bridges/TransformerBridge.d.ts.map +1 -1
  20. package/dist/bridges/VertexAIBridge.d.ts +102 -0
  21. package/dist/bridges/VertexAIBridge.d.ts.map +1 -0
  22. package/dist/bridges/VertexAIBridge.js +224 -0
  23. package/dist/bridges/VertexAIBridge.js.map +1 -0
  24. package/dist/bridges/index.d.ts +3 -1
  25. package/dist/bridges/index.d.ts.map +1 -1
  26. package/dist/bridges/index.js +1 -0
  27. package/dist/bridges/index.js.map +1 -1
  28. package/dist/eval/EvalHarness.d.ts +37 -0
  29. package/dist/eval/EvalHarness.d.ts.map +1 -0
  30. package/dist/eval/EvalHarness.js +178 -0
  31. package/dist/eval/EvalHarness.js.map +1 -0
  32. package/dist/eval/graders.d.ts +76 -0
  33. package/dist/eval/graders.d.ts.map +1 -0
  34. package/dist/eval/graders.js +168 -0
  35. package/dist/eval/graders.js.map +1 -0
  36. package/dist/eval/index.d.ts +26 -0
  37. package/dist/eval/index.d.ts.map +1 -0
  38. package/dist/eval/index.js +26 -0
  39. package/dist/eval/index.js.map +1 -0
  40. package/dist/eval/types.d.ts +97 -0
  41. package/dist/eval/types.d.ts.map +1 -0
  42. package/dist/eval/types.js +5 -0
  43. package/dist/eval/types.js.map +1 -0
  44. package/dist/evermind/contract.d.ts +40 -0
  45. package/dist/evermind/contract.d.ts.map +1 -0
  46. package/dist/evermind/contract.js +13 -0
  47. package/dist/evermind/contract.js.map +1 -0
  48. package/dist/evermind/index.d.ts +15 -0
  49. package/dist/evermind/index.d.ts.map +1 -0
  50. package/dist/evermind/index.js +4 -0
  51. package/dist/evermind/index.js.map +1 -0
  52. package/dist/evermind/memoryBlock.d.ts +7 -0
  53. package/dist/evermind/memoryBlock.d.ts.map +1 -0
  54. package/dist/evermind/memoryBlock.js +37 -0
  55. package/dist/evermind/memoryBlock.js.map +1 -0
  56. package/dist/evermind/recall.d.ts +48 -0
  57. package/dist/evermind/recall.d.ts.map +1 -0
  58. package/dist/evermind/recall.js +105 -0
  59. package/dist/evermind/recall.js.map +1 -0
  60. package/dist/index.d.ts +18 -3
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +14 -1
  63. package/dist/index.js.map +1 -1
  64. package/dist/ingest/IngestionPipeline.d.ts +95 -0
  65. package/dist/ingest/IngestionPipeline.d.ts.map +1 -0
  66. package/dist/ingest/IngestionPipeline.js +295 -0
  67. package/dist/ingest/IngestionPipeline.js.map +1 -0
  68. package/dist/ingest/index.d.ts +10 -0
  69. package/dist/ingest/index.d.ts.map +1 -0
  70. package/dist/ingest/index.js +8 -0
  71. package/dist/ingest/index.js.map +1 -0
  72. package/dist/ingest/parsers.d.ts +53 -0
  73. package/dist/ingest/parsers.d.ts.map +1 -0
  74. package/dist/ingest/parsers.js +306 -0
  75. package/dist/ingest/parsers.js.map +1 -0
  76. package/dist/ingest/types.d.ts +114 -0
  77. package/dist/ingest/types.d.ts.map +1 -0
  78. package/dist/ingest/types.js +19 -0
  79. package/dist/ingest/types.js.map +1 -0
  80. package/dist/memory/MemoryStore.d.ts +33 -0
  81. package/dist/memory/MemoryStore.d.ts.map +1 -1
  82. package/dist/memory/MemoryStore.js +29 -3
  83. package/dist/memory/MemoryStore.js.map +1 -1
  84. package/dist/memory/index.d.ts +1 -1
  85. package/dist/memory/index.d.ts.map +1 -1
  86. package/dist/orchestration/AgentGraph.d.ts +104 -0
  87. package/dist/orchestration/AgentGraph.d.ts.map +1 -0
  88. package/dist/orchestration/AgentGraph.js +364 -0
  89. package/dist/orchestration/AgentGraph.js.map +1 -0
  90. package/dist/orchestration/checkpoint.d.ts +58 -0
  91. package/dist/orchestration/checkpoint.d.ts.map +1 -0
  92. package/dist/orchestration/checkpoint.js +92 -0
  93. package/dist/orchestration/checkpoint.js.map +1 -0
  94. package/dist/orchestration/index.d.ts +14 -0
  95. package/dist/orchestration/index.d.ts.map +1 -0
  96. package/dist/orchestration/index.js +10 -0
  97. package/dist/orchestration/index.js.map +1 -0
  98. package/dist/orchestration/patterns.d.ts +159 -0
  99. package/dist/orchestration/patterns.d.ts.map +1 -0
  100. package/dist/orchestration/patterns.js +371 -0
  101. package/dist/orchestration/patterns.js.map +1 -0
  102. package/dist/orchestration/types.d.ts +146 -0
  103. package/dist/orchestration/types.d.ts.map +1 -0
  104. package/dist/orchestration/types.js +20 -0
  105. package/dist/orchestration/types.js.map +1 -0
  106. package/dist/rag/EnterpriseRetriever.d.ts +114 -0
  107. package/dist/rag/EnterpriseRetriever.d.ts.map +1 -0
  108. package/dist/rag/EnterpriseRetriever.js +240 -0
  109. package/dist/rag/EnterpriseRetriever.js.map +1 -0
  110. package/dist/rag/index.d.ts +6 -0
  111. package/dist/rag/index.d.ts.map +1 -0
  112. package/dist/rag/index.js +5 -0
  113. package/dist/rag/index.js.map +1 -0
  114. package/dist/retrieval/bm25.d.ts +48 -1
  115. package/dist/retrieval/bm25.d.ts.map +1 -1
  116. package/dist/retrieval/bm25.js +59 -13
  117. package/dist/retrieval/bm25.js.map +1 -1
  118. package/dist/retrieval/index.d.ts +2 -2
  119. package/dist/retrieval/index.d.ts.map +1 -1
  120. package/dist/retrieval/index.js +1 -1
  121. package/dist/retrieval/index.js.map +1 -1
  122. package/dist/runtime/SSMRuntime.d.ts +1 -1
  123. package/dist/runtime/SSMRuntime.d.ts.map +1 -1
  124. package/dist/telemetry/InstrumentedBridge.d.ts +62 -0
  125. package/dist/telemetry/InstrumentedBridge.d.ts.map +1 -0
  126. package/dist/telemetry/InstrumentedBridge.js +120 -0
  127. package/dist/telemetry/InstrumentedBridge.js.map +1 -0
  128. package/dist/telemetry/MetricsRegistry.d.ts +88 -0
  129. package/dist/telemetry/MetricsRegistry.d.ts.map +1 -0
  130. package/dist/telemetry/MetricsRegistry.js +167 -0
  131. package/dist/telemetry/MetricsRegistry.js.map +1 -0
  132. package/dist/telemetry/Tracer.d.ts +137 -0
  133. package/dist/telemetry/Tracer.d.ts.map +1 -0
  134. package/dist/telemetry/Tracer.js +244 -0
  135. package/dist/telemetry/Tracer.js.map +1 -0
  136. package/dist/telemetry/exporters.d.ts +68 -0
  137. package/dist/telemetry/exporters.d.ts.map +1 -0
  138. package/dist/telemetry/exporters.js +145 -0
  139. package/dist/telemetry/exporters.js.map +1 -0
  140. package/dist/telemetry/index.d.ts +19 -0
  141. package/dist/telemetry/index.d.ts.map +1 -0
  142. package/dist/telemetry/index.js +12 -0
  143. package/dist/telemetry/index.js.map +1 -0
  144. package/dist/telemetry/pricing.d.ts +74 -0
  145. package/dist/telemetry/pricing.d.ts.map +1 -0
  146. package/dist/telemetry/pricing.js +98 -0
  147. package/dist/telemetry/pricing.js.map +1 -0
  148. package/dist/telemetry/traceparent.d.ts +50 -0
  149. package/dist/telemetry/traceparent.d.ts.map +1 -0
  150. package/dist/telemetry/traceparent.js +77 -0
  151. package/dist/telemetry/traceparent.js.map +1 -0
  152. package/dist/telemetry/types.d.ts +90 -0
  153. package/dist/telemetry/types.d.ts.map +1 -0
  154. package/dist/telemetry/types.js +17 -0
  155. package/dist/telemetry/types.js.map +1 -0
  156. package/dist/vectorstore/MemoryVectorStore.d.ts +72 -0
  157. package/dist/vectorstore/MemoryVectorStore.d.ts.map +1 -0
  158. package/dist/vectorstore/MemoryVectorStore.js +205 -0
  159. package/dist/vectorstore/MemoryVectorStore.js.map +1 -0
  160. package/dist/vectorstore/RestVectorStore.d.ts +79 -0
  161. package/dist/vectorstore/RestVectorStore.d.ts.map +1 -0
  162. package/dist/vectorstore/RestVectorStore.js +170 -0
  163. package/dist/vectorstore/RestVectorStore.js.map +1 -0
  164. package/dist/vectorstore/dialects.d.ts +101 -0
  165. package/dist/vectorstore/dialects.d.ts.map +1 -0
  166. package/dist/vectorstore/dialects.js +618 -0
  167. package/dist/vectorstore/dialects.js.map +1 -0
  168. package/dist/vectorstore/filter.d.ts +42 -0
  169. package/dist/vectorstore/filter.d.ts.map +1 -0
  170. package/dist/vectorstore/filter.js +132 -0
  171. package/dist/vectorstore/filter.js.map +1 -0
  172. package/dist/vectorstore/index.d.ts +15 -0
  173. package/dist/vectorstore/index.d.ts.map +1 -0
  174. package/dist/vectorstore/index.js +11 -0
  175. package/dist/vectorstore/index.js.map +1 -0
  176. package/dist/vectorstore/types.d.ts +151 -0
  177. package/dist/vectorstore/types.d.ts.map +1 -0
  178. package/dist/vectorstore/types.js +30 -0
  179. package/dist/vectorstore/types.js.map +1 -0
  180. package/package.json +39 -5
  181. package/src/bridges/AnthropicBridge.ts +68 -3
  182. package/src/bridges/CachingBridge.ts +28 -2
  183. package/src/bridges/OpenAIBridge.ts +48 -3
  184. package/src/bridges/SemanticCachingBridge.ts +29 -2
  185. package/src/bridges/TransformerBridge.ts +16 -0
  186. package/src/bridges/VertexAIBridge.ts +297 -0
  187. package/src/bridges/index.ts +3 -1
  188. package/src/eval/EvalHarness.ts +214 -0
  189. package/src/eval/graders.ts +218 -0
  190. package/src/eval/index.ts +26 -0
  191. package/src/eval/types.ts +107 -0
  192. package/src/evermind/contract.ts +39 -0
  193. package/src/evermind/index.ts +18 -0
  194. package/src/evermind/memoryBlock.ts +38 -0
  195. package/src/evermind/recall.ts +122 -0
  196. package/src/index.ts +232 -0
  197. package/src/ingest/IngestionPipeline.ts +378 -0
  198. package/src/ingest/index.ts +46 -0
  199. package/src/ingest/parsers.ts +318 -0
  200. package/src/ingest/types.ts +116 -0
  201. package/src/memory/MemoryStore.ts +55 -3
  202. package/src/memory/index.ts +1 -1
  203. package/src/orchestration/AgentGraph.ts +462 -0
  204. package/src/orchestration/checkpoint.ts +129 -0
  205. package/src/orchestration/index.ts +63 -0
  206. package/src/orchestration/patterns.ts +532 -0
  207. package/src/orchestration/types.ts +128 -0
  208. package/src/rag/EnterpriseRetriever.ts +348 -0
  209. package/src/rag/index.ts +18 -0
  210. package/src/retrieval/bm25.ts +71 -13
  211. package/src/retrieval/index.ts +2 -2
  212. package/src/telemetry/InstrumentedBridge.ts +152 -0
  213. package/src/telemetry/MetricsRegistry.ts +263 -0
  214. package/src/telemetry/Tracer.ts +310 -0
  215. package/src/telemetry/exporters.ts +187 -0
  216. package/src/telemetry/index.ts +56 -0
  217. package/src/telemetry/pricing.ts +129 -0
  218. package/src/telemetry/traceparent.ts +89 -0
  219. package/src/telemetry/types.ts +105 -0
  220. package/src/vectorstore/MemoryVectorStore.ts +250 -0
  221. package/src/vectorstore/RestVectorStore.ts +236 -0
  222. package/src/vectorstore/dialects.ts +702 -0
  223. package/src/vectorstore/filter.ts +148 -0
  224. package/src/vectorstore/index.ts +53 -0
  225. 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":"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"}
@@ -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,5 @@
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
+ //# sourceMappingURL=index.js.map
@@ -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"}
@@ -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
- * Pure and zero-dependency — reuses the shared `tokenize` from ../similarity.
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;;;;;;;;;GASG;AAIH,MAAM,WAAW,WAAW;IACxB,wEAAwE;IACxE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,kFAAkF;IAClF,CAAC,CAAC,EAAE,MAAM,CAAC;CACd;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;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,IAAI,GAAE,WAAgB,GAAG,OAAO,EAAE,CA8C5F"}
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"}
@@ -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
- * Pure and zero-dependency — reuses the shared `tokenize` from ../similarity.
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 ?? 1.5;
20
- const b = opts.b ?? 0.75;
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(tokenize(query));
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 = tokenize(doc.text);
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
- const n = df.get(term) ?? 0;
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
- const numer = f * (k1 + 1);
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":"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"}
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;AACvC,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE/D,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"}
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"}
@@ -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;AAGvC,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"}
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"}