@eucoder/rag 0.2.0

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 (103) hide show
  1. package/README.md +384 -0
  2. package/dist/ab-testing.d.ts +52 -0
  3. package/dist/ab-testing.d.ts.map +1 -0
  4. package/dist/ab-testing.js +144 -0
  5. package/dist/ab-testing.js.map +1 -0
  6. package/dist/ab-testing.test.d.ts +2 -0
  7. package/dist/ab-testing.test.d.ts.map +1 -0
  8. package/dist/ab-testing.test.js +147 -0
  9. package/dist/ab-testing.test.js.map +1 -0
  10. package/dist/agentic-rag.d.ts +23 -0
  11. package/dist/agentic-rag.d.ts.map +1 -0
  12. package/dist/agentic-rag.js +170 -0
  13. package/dist/agentic-rag.js.map +1 -0
  14. package/dist/agentic-rag.test.d.ts +2 -0
  15. package/dist/agentic-rag.test.d.ts.map +1 -0
  16. package/dist/agentic-rag.test.js +174 -0
  17. package/dist/agentic-rag.test.js.map +1 -0
  18. package/dist/corrective-rag.d.ts +16 -0
  19. package/dist/corrective-rag.d.ts.map +1 -0
  20. package/dist/corrective-rag.js +85 -0
  21. package/dist/corrective-rag.js.map +1 -0
  22. package/dist/corrective-rag.test.d.ts +2 -0
  23. package/dist/corrective-rag.test.d.ts.map +1 -0
  24. package/dist/corrective-rag.test.js +140 -0
  25. package/dist/corrective-rag.test.js.map +1 -0
  26. package/dist/feedback.d.ts +77 -0
  27. package/dist/feedback.d.ts.map +1 -0
  28. package/dist/feedback.js +44 -0
  29. package/dist/feedback.js.map +1 -0
  30. package/dist/feedback.test.d.ts +2 -0
  31. package/dist/feedback.test.d.ts.map +1 -0
  32. package/dist/feedback.test.js +202 -0
  33. package/dist/feedback.test.js.map +1 -0
  34. package/dist/hybrid-search.d.ts +14 -0
  35. package/dist/hybrid-search.d.ts.map +1 -0
  36. package/dist/hybrid-search.js +70 -0
  37. package/dist/hybrid-search.js.map +1 -0
  38. package/dist/hybrid-search.test.d.ts +2 -0
  39. package/dist/hybrid-search.test.d.ts.map +1 -0
  40. package/dist/hybrid-search.test.js +93 -0
  41. package/dist/hybrid-search.test.js.map +1 -0
  42. package/dist/index.d.ts +17 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +12 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/knowledge-graph.d.ts +24 -0
  47. package/dist/knowledge-graph.d.ts.map +1 -0
  48. package/dist/knowledge-graph.js +131 -0
  49. package/dist/knowledge-graph.js.map +1 -0
  50. package/dist/knowledge-graph.test.d.ts +2 -0
  51. package/dist/knowledge-graph.test.d.ts.map +1 -0
  52. package/dist/knowledge-graph.test.js +140 -0
  53. package/dist/knowledge-graph.test.js.map +1 -0
  54. package/dist/llm-grader.d.ts +19 -0
  55. package/dist/llm-grader.d.ts.map +1 -0
  56. package/dist/llm-grader.js +63 -0
  57. package/dist/llm-grader.js.map +1 -0
  58. package/dist/metrics.d.ts +26 -0
  59. package/dist/metrics.d.ts.map +1 -0
  60. package/dist/metrics.js +100 -0
  61. package/dist/metrics.js.map +1 -0
  62. package/dist/optimizer.d.ts +52 -0
  63. package/dist/optimizer.d.ts.map +1 -0
  64. package/dist/optimizer.js +228 -0
  65. package/dist/optimizer.js.map +1 -0
  66. package/dist/optimizer.test.d.ts +2 -0
  67. package/dist/optimizer.test.d.ts.map +1 -0
  68. package/dist/optimizer.test.js +201 -0
  69. package/dist/optimizer.test.js.map +1 -0
  70. package/dist/self-improving.d.ts +85 -0
  71. package/dist/self-improving.d.ts.map +1 -0
  72. package/dist/self-improving.js +163 -0
  73. package/dist/self-improving.js.map +1 -0
  74. package/dist/self-improving.test.d.ts +2 -0
  75. package/dist/self-improving.test.d.ts.map +1 -0
  76. package/dist/self-improving.test.js +234 -0
  77. package/dist/self-improving.test.js.map +1 -0
  78. package/dist/types.d.ts +117 -0
  79. package/dist/types.d.ts.map +1 -0
  80. package/dist/types.js +2 -0
  81. package/dist/types.js.map +1 -0
  82. package/package.json +42 -0
  83. package/src/ab-testing.test.ts +239 -0
  84. package/src/ab-testing.ts +214 -0
  85. package/src/agentic-rag.test.ts +201 -0
  86. package/src/agentic-rag.ts +220 -0
  87. package/src/corrective-rag.test.ts +166 -0
  88. package/src/corrective-rag.ts +115 -0
  89. package/src/feedback.test.ts +227 -0
  90. package/src/feedback.ts +118 -0
  91. package/src/hybrid-search.test.ts +107 -0
  92. package/src/hybrid-search.ts +86 -0
  93. package/src/index.ts +57 -0
  94. package/src/knowledge-graph.test.ts +170 -0
  95. package/src/knowledge-graph.ts +182 -0
  96. package/src/llm-grader.ts +69 -0
  97. package/src/metrics.ts +121 -0
  98. package/src/optimizer.test.ts +232 -0
  99. package/src/optimizer.ts +307 -0
  100. package/src/self-improving.test.ts +341 -0
  101. package/src/self-improving.ts +239 -0
  102. package/src/types.ts +139 -0
  103. package/tsconfig.json +9 -0
@@ -0,0 +1,227 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import { InMemoryFeedbackStorage } from "./feedback.js";
3
+ import { MetricsCalculator } from "./metrics.js";
4
+ import type { RagFeedback } from "./feedback.js";
5
+
6
+ describe("InMemoryFeedbackStorage", () => {
7
+ let storage: InMemoryFeedbackStorage;
8
+
9
+ beforeEach(() => {
10
+ storage = new InMemoryFeedbackStorage();
11
+ });
12
+
13
+ it("should save and retrieve feedback", async () => {
14
+ const feedback: RagFeedback = {
15
+ id: "test-1",
16
+ query: "test query",
17
+ answer: {
18
+ text: "test answer",
19
+ citations: [],
20
+ steps: [],
21
+ rewrites: 0,
22
+ },
23
+ rating: 4,
24
+ relevance: 0.8,
25
+ completeness: 0.7,
26
+ citationsQuality: 0.9,
27
+ timestamp: new Date(),
28
+ strategy: "strategy-a",
29
+ };
30
+
31
+ await storage.saveFeedback(feedback);
32
+ const retrieved = await storage.getFeedback("strategy-a");
33
+
34
+ expect(retrieved).toHaveLength(1);
35
+ expect(retrieved[0]?.id).toBe("test-1");
36
+ });
37
+
38
+ it("should filter feedback by strategy", async () => {
39
+ const feedback1: RagFeedback = {
40
+ id: "test-1",
41
+ query: "query 1",
42
+ answer: { text: "answer 1", citations: [], steps: [], rewrites: 0 },
43
+ rating: 4,
44
+ relevance: 0.8,
45
+ completeness: 0.7,
46
+ citationsQuality: 0.9,
47
+ timestamp: new Date(),
48
+ strategy: "strategy-a",
49
+ };
50
+
51
+ const feedback2: RagFeedback = {
52
+ id: "test-2",
53
+ query: "query 2",
54
+ answer: { text: "answer 2", citations: [], steps: [], rewrites: 0 },
55
+ rating: 5,
56
+ relevance: 0.9,
57
+ completeness: 0.8,
58
+ citationsQuality: 0.95,
59
+ timestamp: new Date(),
60
+ strategy: "strategy-b",
61
+ };
62
+
63
+ await storage.saveFeedback(feedback1);
64
+ await storage.saveFeedback(feedback2);
65
+
66
+ const retrievedA = await storage.getFeedback("strategy-a");
67
+ const retrievedB = await storage.getFeedback("strategy-b");
68
+
69
+ expect(retrievedA).toHaveLength(1);
70
+ expect(retrievedB).toHaveLength(1);
71
+ expect(retrievedA[0]?.strategy).toBe("strategy-a");
72
+ expect(retrievedB[0]?.strategy).toBe("strategy-b");
73
+ });
74
+
75
+ it("should limit feedback results", async () => {
76
+ for (let i = 0; i < 10; i++) {
77
+ await storage.saveFeedback({
78
+ id: `test-${i}`,
79
+ query: `query ${i}`,
80
+ answer: { text: `answer ${i}`, citations: [], steps: [], rewrites: 0 },
81
+ rating: 4,
82
+ relevance: 0.8,
83
+ completeness: 0.7,
84
+ citationsQuality: 0.9,
85
+ timestamp: new Date(),
86
+ strategy: "strategy-a",
87
+ });
88
+ }
89
+
90
+ const retrieved = await storage.getFeedback("strategy-a", 5);
91
+ expect(retrieved).toHaveLength(5);
92
+ });
93
+
94
+ it("should clear feedback", async () => {
95
+ await storage.saveFeedback({
96
+ id: "test-1",
97
+ query: "query 1",
98
+ answer: { text: "answer 1", citations: [], steps: [], rewrites: 0 },
99
+ rating: 4,
100
+ relevance: 0.8,
101
+ completeness: 0.7,
102
+ citationsQuality: 0.9,
103
+ timestamp: new Date(),
104
+ strategy: "strategy-a",
105
+ });
106
+
107
+ await storage.clearFeedback("strategy-a");
108
+ const retrieved = await storage.getFeedback("strategy-a");
109
+ expect(retrieved).toHaveLength(0);
110
+ });
111
+ });
112
+
113
+ describe("MetricsCalculator", () => {
114
+ it("should calculate metrics from feedback", () => {
115
+ const feedbacks: RagFeedback[] = [
116
+ {
117
+ id: "test-1",
118
+ query: "query 1",
119
+ answer: { text: "answer 1", citations: [], steps: [], rewrites: 0 },
120
+ rating: 4,
121
+ relevance: 0.8,
122
+ completeness: 0.7,
123
+ citationsQuality: 0.9,
124
+ timestamp: new Date(),
125
+ strategy: "strategy-a",
126
+ },
127
+ {
128
+ id: "test-2",
129
+ query: "query 2",
130
+ answer: { text: "answer 2", citations: [], steps: [], rewrites: 0 },
131
+ rating: 5,
132
+ relevance: 0.9,
133
+ completeness: 0.8,
134
+ citationsQuality: 0.95,
135
+ timestamp: new Date(),
136
+ strategy: "strategy-a",
137
+ },
138
+ ];
139
+
140
+ const metrics = MetricsCalculator.calculateMetrics(feedbacks, "strategy-a");
141
+
142
+ expect(metrics.strategy).toBe("strategy-a");
143
+ expect(metrics.totalQueries).toBe(2);
144
+ expect(metrics.averageRating).toBe(4.5);
145
+ expect(metrics.averageRelevance).toBeCloseTo(0.85, 10);
146
+ expect(metrics.averageCompleteness).toBeCloseTo(0.75, 10);
147
+ expect(metrics.averageCitationsQuality).toBeCloseTo(0.925, 10);
148
+ expect(metrics.overallScore).toBeGreaterThan(0);
149
+ expect(metrics.confidence).toBeGreaterThan(0);
150
+ });
151
+
152
+ it("should return zero metrics for empty feedback", () => {
153
+ const metrics = MetricsCalculator.calculateMetrics([], "strategy-a");
154
+
155
+ expect(metrics.totalQueries).toBe(0);
156
+ expect(metrics.averageRating).toBe(0);
157
+ expect(metrics.overallScore).toBe(0);
158
+ expect(metrics.confidence).toBe(0);
159
+ });
160
+
161
+ it("should calculate standard deviation", () => {
162
+ const feedbacks: RagFeedback[] = [
163
+ {
164
+ id: "test-1",
165
+ query: "query 1",
166
+ answer: { text: "answer 1", citations: [], steps: [], rewrites: 0 },
167
+ rating: 3,
168
+ relevance: 0.6,
169
+ completeness: 0.5,
170
+ citationsQuality: 0.7,
171
+ timestamp: new Date(),
172
+ strategy: "strategy-a",
173
+ },
174
+ {
175
+ id: "test-2",
176
+ query: "query 2",
177
+ answer: { text: "answer 2", citations: [], steps: [], rewrites: 0 },
178
+ rating: 5,
179
+ relevance: 0.9,
180
+ completeness: 0.8,
181
+ citationsQuality: 0.95,
182
+ timestamp: new Date(),
183
+ strategy: "strategy-a",
184
+ },
185
+ ];
186
+
187
+ const stdDev = MetricsCalculator.calculateStandardDeviation(feedbacks, "relevance");
188
+ expect(stdDev).toBeGreaterThan(0);
189
+ });
190
+
191
+ it("should identify trend", () => {
192
+ const feedbacks: RagFeedback[] = [];
193
+
194
+ // Older feedback with lower relevance
195
+ for (let i = 0; i < 10; i++) {
196
+ feedbacks.push({
197
+ id: `old-${i}`,
198
+ query: `query ${i}`,
199
+ answer: { text: `answer ${i}`, citations: [], steps: [], rewrites: 0 },
200
+ rating: 3,
201
+ relevance: 0.5,
202
+ completeness: 0.5,
203
+ citationsQuality: 0.5,
204
+ timestamp: new Date(Date.now() - 1000000),
205
+ strategy: "strategy-a",
206
+ });
207
+ }
208
+
209
+ // Recent feedback with higher relevance
210
+ for (let i = 0; i < 10; i++) {
211
+ feedbacks.push({
212
+ id: `new-${i}`,
213
+ query: `query ${i}`,
214
+ answer: { text: `answer ${i}`, citations: [], steps: [], rewrites: 0 },
215
+ rating: 5,
216
+ relevance: 0.9,
217
+ completeness: 0.9,
218
+ citationsQuality: 0.9,
219
+ timestamp: new Date(),
220
+ strategy: "strategy-a",
221
+ });
222
+ }
223
+
224
+ const trend = MetricsCalculator.identifyTrend(feedbacks, 10);
225
+ expect(trend).toBe("improving");
226
+ });
227
+ });
@@ -0,0 +1,118 @@
1
+ import type { RagAnswer } from "./types.js";
2
+
3
+ /**
4
+ * Feedback per una risposta RAG
5
+ */
6
+ export interface RagFeedback {
7
+ id: string;
8
+ query: string;
9
+ answer: RagAnswer;
10
+ rating: number; // 1-5
11
+ relevance: number; // 0-1
12
+ completeness: number; // 0-1
13
+ citationsQuality: number; // 0-1
14
+ comments?: string;
15
+ timestamp: Date;
16
+ strategy?: string; // Nome della strategia usata (per A/B testing)
17
+ }
18
+
19
+ /**
20
+ * Metriche aggregate per una strategia RAG
21
+ */
22
+ export interface RagMetrics {
23
+ strategy: string;
24
+ totalQueries: number;
25
+ averageRating: number;
26
+ averageRelevance: number;
27
+ averageCompleteness: number;
28
+ averageCitationsQuality: number;
29
+ overallScore: number; // Media pesata delle metriche
30
+ confidence: number; // 0-1, basato sul numero di sample
31
+ }
32
+
33
+ /**
34
+ * Risultato A/B test
35
+ */
36
+ export interface ABTestResult {
37
+ strategyA: string;
38
+ strategyB: string;
39
+ winner: "A" | "B" | "tie";
40
+ metricsA: RagMetrics;
41
+ metricsB: RagMetrics;
42
+ improvement: number; // Percentuale di miglioramento
43
+ statisticalSignificance: number; // 0-1
44
+ recommendation: string;
45
+ }
46
+
47
+ /**
48
+ * Suggerimento di ottimizzazione
49
+ */
50
+ export interface OptimizationSuggestion {
51
+ type: "parameter" | "strategy" | "prompt";
52
+ target: string;
53
+ current: unknown;
54
+ suggested: unknown;
55
+ expectedImprovement: number; // 0-1
56
+ confidence: number; // 0-1
57
+ reasoning: string;
58
+ }
59
+
60
+ /**
61
+ * Storage per feedback e metriche
62
+ */
63
+ export interface FeedbackStorage {
64
+ saveFeedback(feedback: RagFeedback): Promise<void>;
65
+ getFeedback(strategy?: string, limit?: number): Promise<RagFeedback[]>;
66
+ getMetrics(strategy: string): Promise<RagMetrics | null>;
67
+ saveMetrics(metrics: RagMetrics): Promise<void>;
68
+ clearFeedback(strategy?: string): Promise<void>;
69
+ }
70
+
71
+ /**
72
+ * In-memory implementation di FeedbackStorage
73
+ */
74
+ export class InMemoryFeedbackStorage implements FeedbackStorage {
75
+ private feedbacks: Map<string, RagFeedback[]> = new Map();
76
+ private metrics: Map<string, RagMetrics> = new Map();
77
+
78
+ async saveFeedback(feedback: RagFeedback): Promise<void> {
79
+ const strategy = feedback.strategy || "default";
80
+ if (!this.feedbacks.has(strategy)) {
81
+ this.feedbacks.set(strategy, []);
82
+ }
83
+ this.feedbacks.get(strategy)!.push(feedback);
84
+ }
85
+
86
+ async getFeedback(strategy?: string, limit?: number): Promise<RagFeedback[]> {
87
+ if (strategy) {
88
+ const feedbacks = this.feedbacks.get(strategy) || [];
89
+ return limit ? feedbacks.slice(-limit) : feedbacks;
90
+ }
91
+
92
+ // Tutti i feedback
93
+ const all: RagFeedback[] = [];
94
+ for (const feedbacks of this.feedbacks.values()) {
95
+ all.push(...feedbacks);
96
+ }
97
+ all.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
98
+ return limit ? all.slice(-limit) : all;
99
+ }
100
+
101
+ async getMetrics(strategy: string): Promise<RagMetrics | null> {
102
+ return this.metrics.get(strategy) || null;
103
+ }
104
+
105
+ async saveMetrics(metrics: RagMetrics): Promise<void> {
106
+ this.metrics.set(metrics.strategy, metrics);
107
+ }
108
+
109
+ async clearFeedback(strategy?: string): Promise<void> {
110
+ if (strategy) {
111
+ this.feedbacks.delete(strategy);
112
+ this.metrics.delete(strategy);
113
+ } else {
114
+ this.feedbacks.clear();
115
+ this.metrics.clear();
116
+ }
117
+ }
118
+ }
@@ -0,0 +1,107 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { HybridSearchRag } from "./hybrid-search.js";
3
+ import type { SearchHit } from "@eucode/indexer";
4
+ import type { Retriever, KeywordRetriever } from "./types.js";
5
+
6
+ class MockSemanticRetriever implements Retriever {
7
+ async search(_query: string, topK: number): Promise<SearchHit[]> {
8
+ return [
9
+ {
10
+ id: "hit-1",
11
+ path: "src/auth.ts",
12
+ startLine: 10,
13
+ endLine: 20,
14
+ text: "Authentication logic",
15
+ score: 0.9,
16
+ },
17
+ {
18
+ id: "hit-2",
19
+ path: "src/login.ts",
20
+ startLine: 5,
21
+ endLine: 15,
22
+ text: "Login implementation",
23
+ score: 0.8,
24
+ },
25
+ ].slice(0, topK);
26
+ }
27
+ }
28
+
29
+ class MockKeywordRetriever implements KeywordRetriever {
30
+ async search(_query: string, topK: number): Promise<SearchHit[]> {
31
+ return [
32
+ {
33
+ id: "hit-2",
34
+ path: "src/login.ts",
35
+ startLine: 5,
36
+ endLine: 15,
37
+ text: "Login implementation",
38
+ score: 0.85,
39
+ },
40
+ {
41
+ id: "hit-3",
42
+ path: "src/session.ts",
43
+ startLine: 1,
44
+ endLine: 10,
45
+ text: "Session management",
46
+ score: 0.7,
47
+ },
48
+ ].slice(0, topK);
49
+ }
50
+ }
51
+
52
+ describe("HybridSearchRag", () => {
53
+ it("should combine semantic and keyword results using RRF", async () => {
54
+ const hybrid = new HybridSearchRag({
55
+ semanticRetriever: new MockSemanticRetriever(),
56
+ keywordRetriever: new MockKeywordRetriever(),
57
+ topK: 5,
58
+ });
59
+
60
+ const result = await hybrid.search("authentication");
61
+
62
+ expect(result.hits.length).toBeGreaterThan(0);
63
+ expect(result.fusionMethod).toBe("rrf");
64
+ expect(result.semanticCount).toBe(2);
65
+ expect(result.keywordCount).toBe(2);
66
+ });
67
+
68
+ it("should rank overlapping results higher", async () => {
69
+ const hybrid = new HybridSearchRag({
70
+ semanticRetriever: new MockSemanticRetriever(),
71
+ keywordRetriever: new MockKeywordRetriever(),
72
+ topK: 5,
73
+ });
74
+
75
+ const result = await hybrid.search("login");
76
+
77
+ // src/login.ts appears in both, should be ranked higher
78
+ const loginHit = result.hits.find((h) => h.path === "src/login.ts");
79
+ expect(loginHit).toBeDefined();
80
+ expect(result.hits[0]?.path).toBe("src/login.ts");
81
+ });
82
+
83
+ it("should respect custom weights", async () => {
84
+ const hybridSemantic = new HybridSearchRag({
85
+ semanticRetriever: new MockSemanticRetriever(),
86
+ keywordRetriever: new MockKeywordRetriever(),
87
+ semanticWeight: 0.9,
88
+ keywordWeight: 0.1,
89
+ topK: 5,
90
+ });
91
+
92
+ const resultSemantic = await hybridSemantic.search("auth");
93
+
94
+ const hybridKeyword = new HybridSearchRag({
95
+ semanticRetriever: new MockSemanticRetriever(),
96
+ keywordRetriever: new MockKeywordRetriever(),
97
+ semanticWeight: 0.1,
98
+ keywordWeight: 0.9,
99
+ topK: 5,
100
+ });
101
+
102
+ const resultKeyword = await hybridKeyword.search("auth");
103
+
104
+ // Results should differ based on weights
105
+ expect(resultSemantic.hits[0]?.score).not.toBe(resultKeyword.hits[0]?.score);
106
+ });
107
+ });
@@ -0,0 +1,86 @@
1
+ import type { SearchHit } from "@eucode/indexer";
2
+ import type { HybridSearchOptions, HybridSearchResult } from "./types.js";
3
+
4
+ /**
5
+ * Hybrid Search RAG - Combina semantic search e keyword search usando RRF (Reciprocal Rank Fusion)
6
+ *
7
+ * RRF Formula: score(d) = Σ 1/(k + rank_i(d))
8
+ * dove k è una costante (tipicamente 60) e rank_i è la posizione del documento nella lista i
9
+ */
10
+ export class HybridSearchRag {
11
+ private options: Required<HybridSearchOptions>;
12
+
13
+ constructor(options: HybridSearchOptions) {
14
+ this.options = {
15
+ semanticWeight: options.semanticWeight ?? 0.5,
16
+ keywordWeight: options.keywordWeight ?? 0.5,
17
+ rrfK: options.rrfK ?? 60,
18
+ topK: options.topK ?? 10,
19
+ ...options,
20
+ };
21
+ }
22
+
23
+ async search(query: string, topK?: number): Promise<HybridSearchResult> {
24
+ const limit = topK ?? this.options.topK;
25
+
26
+ // Esegui entrambe le ricerche in parallelo
27
+ const [semanticHits, keywordHits] = await Promise.all([
28
+ this.options.semanticRetriever.search(query, limit * 2),
29
+ this.options.keywordRetriever.search(query, limit * 2),
30
+ ]);
31
+
32
+ // Applica RRF fusion
33
+ const fusedHits = this.reciprocalRankFusion(semanticHits, keywordHits);
34
+
35
+ // Prendi i top K risultati
36
+ const finalHits = fusedHits.slice(0, limit);
37
+
38
+ return {
39
+ hits: finalHits,
40
+ fusionMethod: "rrf",
41
+ semanticCount: semanticHits.length,
42
+ keywordCount: keywordHits.length,
43
+ };
44
+ }
45
+
46
+ private reciprocalRankFusion(
47
+ semanticHits: SearchHit[],
48
+ keywordHits: SearchHit[]
49
+ ): SearchHit[] {
50
+ const scoreMap = new Map<string, { hit: SearchHit; score: number }>();
51
+ const k = this.options.rrfK;
52
+
53
+ // Calcola RRF score per semantic hits
54
+ semanticHits.forEach((hit, index) => {
55
+ const key = `${hit.path}:${hit.startLine}`;
56
+ const rrfScore = 1 / (k + index + 1);
57
+
58
+ const existing = scoreMap.get(key);
59
+ if (existing) {
60
+ existing.score += rrfScore * this.options.semanticWeight;
61
+ } else {
62
+ scoreMap.set(key, { hit, score: rrfScore * this.options.semanticWeight });
63
+ }
64
+ });
65
+
66
+ // Calcola RRF score per keyword hits
67
+ keywordHits.forEach((hit, index) => {
68
+ const key = `${hit.path}:${hit.startLine}`;
69
+ const rrfScore = 1 / (k + index + 1);
70
+
71
+ const existing = scoreMap.get(key);
72
+ if (existing) {
73
+ existing.score += rrfScore * this.options.keywordWeight;
74
+ } else {
75
+ scoreMap.set(key, { hit, score: rrfScore * this.options.keywordWeight });
76
+ }
77
+ });
78
+
79
+ // Ordina per score decrescente
80
+ const sorted = Array.from(scoreMap.values())
81
+ .sort((a, b) => b.score - a.score)
82
+ .map(({ hit, score }) => ({ ...hit, score }));
83
+
84
+ return sorted;
85
+ }
86
+ }
package/src/index.ts ADDED
@@ -0,0 +1,57 @@
1
+ export { CorrectiveRag, createCorrectiveRag } from "./corrective-rag.js";
2
+ export { LlmGrader, LlmQueryRewriter } from "./llm-grader.js";
3
+ export { HybridSearchRag } from "./hybrid-search.js";
4
+ export { KnowledgeGraphRag, InMemoryKnowledgeGraph } from "./knowledge-graph.js";
5
+ export { AgenticRag } from "./agentic-rag.js";
6
+
7
+ // Fase 4: Self-Improving
8
+ export { InMemoryFeedbackStorage } from "./feedback.js";
9
+ export { MetricsCalculator } from "./metrics.js";
10
+ export { ABTestFramework } from "./ab-testing.js";
11
+ export { ParameterOptimizer } from "./optimizer.js";
12
+ export { SelfImprovingRag } from "./self-improving.js";
13
+
14
+ export type { LlmProvider } from "./llm-grader.js";
15
+ export type {
16
+ Retriever,
17
+ KeywordRetriever,
18
+ Grader,
19
+ GraderResult,
20
+ QueryRewriter,
21
+ CorrectiveRagOptions,
22
+ RagAnswer,
23
+ RagCitation,
24
+ HybridSearchOptions,
25
+ HybridSearchResult,
26
+ Entity,
27
+ Relation,
28
+ KnowledgeGraph,
29
+ EntityExtractor,
30
+ RelationExtractor,
31
+ KnowledgeGraphRagOptions,
32
+ KnowledgeGraphRagResult,
33
+ ReasoningStep,
34
+ AgenticRagOptions,
35
+ AgenticRagResult,
36
+ } from "./types.js";
37
+
38
+ // Fase 4: Self-Improving Types
39
+ export type {
40
+ RagFeedback,
41
+ RagMetrics,
42
+ ABTestResult,
43
+ OptimizationSuggestion,
44
+ FeedbackStorage,
45
+ } from "./feedback.js";
46
+
47
+ export type {
48
+ RagStrategyConfig,
49
+ } from "./ab-testing.js";
50
+
51
+ export type {
52
+ OptimizableParams,
53
+ } from "./optimizer.js";
54
+
55
+ export type {
56
+ SelfImprovingConfig,
57
+ } from "./self-improving.js";