@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,220 @@
1
+ import type { SearchHit } from "@eucode/indexer";
2
+ import type {
3
+ AgenticRagOptions,
4
+ AgenticRagResult,
5
+ ReasoningStep,
6
+ RagCitation,
7
+ } from "./types.js";
8
+
9
+ /**
10
+ * Agentic RAG con Reasoning e Self-Reflection
11
+ *
12
+ * Implementa un ciclo di ragionamento:
13
+ * 1. Thought: Analizza la query e pianifica l'approccio
14
+ * 2. Action: Esegue retrieval e grading
15
+ * 3. Observation: Valuta i risultati
16
+ * 4. Self-Reflection: Valuta la qualità della risposta
17
+ * 5. Itera se necessario
18
+ */
19
+ export class AgenticRag {
20
+ private options: Required<AgenticRagOptions>;
21
+
22
+ constructor(options: AgenticRagOptions) {
23
+ this.options = {
24
+ maxIterations: options.maxIterations ?? 3,
25
+ enableSelfReflection: options.enableSelfReflection ?? true,
26
+ enableChainOfThought: options.enableChainOfThought ?? true,
27
+ ...options,
28
+ };
29
+ }
30
+
31
+ async search(query: string): Promise<AgenticRagResult> {
32
+ const reasoningSteps: ReasoningStep[] = [];
33
+ let currentQuery = query;
34
+ let bestHits: SearchHit[] = [];
35
+ let bestScore = 0;
36
+ let iterations = 0;
37
+
38
+ // Ciclo di ragionamento
39
+ for (let i = 0; i < this.options.maxIterations; i++) {
40
+ iterations++;
41
+
42
+ // Step 1: Thought
43
+ const thought = this.generateThought(currentQuery, i);
44
+
45
+ // Step 2: Action - Retrieval
46
+ const hits = await this.options.retriever.search(currentQuery, 10);
47
+
48
+ // Step 3: Action - Grading
49
+ const gradedHits = await this.gradeHits(currentQuery, hits);
50
+ const avgScore = gradedHits.length > 0
51
+ ? gradedHits.reduce((sum, h) => sum + h.score, 0) / gradedHits.length
52
+ : 0;
53
+
54
+ // Step 4: Observation
55
+ const observation = this.generateObservation(gradedHits, avgScore);
56
+
57
+ reasoningSteps.push({
58
+ thought,
59
+ action: `Retrieved ${hits.length} hits, graded ${gradedHits.length} as relevant`,
60
+ observation,
61
+ });
62
+
63
+ // Aggiorna best hits se migliori
64
+ if (avgScore > bestScore) {
65
+ bestScore = avgScore;
66
+ bestHits = gradedHits;
67
+ }
68
+
69
+ // Step 5: Self-Reflection (se abilitato)
70
+ if (this.options.enableSelfReflection && i < this.options.maxIterations - 1) {
71
+ const reflection = this.selfReflect(currentQuery, gradedHits, avgScore);
72
+
73
+ if (reflection.shouldRewrite) {
74
+ // Riscrivi la query
75
+ currentQuery = await this.options.rewriter.rewrite(
76
+ currentQuery,
77
+ gradedHits.map((h) => h.text).join("\n")
78
+ );
79
+ continue;
80
+ } else if (reflection.isSatisfied) {
81
+ break; // Risultati soddisfacenti
82
+ }
83
+ } else if (avgScore > 0.7) {
84
+ break; // Score足够高, termina
85
+ }
86
+ }
87
+
88
+ // Genera risposta finale
89
+ const answer = this.buildAnswer(query, bestHits, reasoningSteps);
90
+ const citations: RagCitation[] = bestHits.map((hit) => ({
91
+ path: hit.path,
92
+ startLine: hit.startLine,
93
+ endLine: hit.endLine,
94
+ text: hit.text,
95
+ }));
96
+
97
+ // Self-reflection finale
98
+ const selfReflection = this.options.enableSelfReflection
99
+ ? this.finalReflection(query, bestHits, bestScore, iterations)
100
+ : undefined;
101
+
102
+ return {
103
+ answer,
104
+ citations,
105
+ reasoningSteps,
106
+ selfReflection,
107
+ iterations,
108
+ confidence: bestScore,
109
+ };
110
+ }
111
+
112
+ private generateThought(query: string, iteration: number): string {
113
+ if (iteration === 0) {
114
+ return `Analizzo la query: "${query}". Cerco documenti rilevanti e valuto la loro pertinenza.`;
115
+ } else {
116
+ return `Iterazione ${iteration + 1}. I risultati precedenti non erano sufficientemente pertinenti. Riscrivo la query per migliorare i risultati.`;
117
+ }
118
+ }
119
+
120
+ private async gradeHits(query: string, hits: SearchHit[]): Promise<SearchHit[]> {
121
+ const graded = await Promise.all(
122
+ hits.map(async (hit) => {
123
+ const result = await this.options.grader.grade(query, hit);
124
+ return { ...hit, score: result.score };
125
+ })
126
+ );
127
+
128
+ return graded
129
+ .filter((h) => h.score > 0.3)
130
+ .sort((a, b) => b.score - a.score);
131
+ }
132
+
133
+ private generateObservation(hits: SearchHit[], avgScore: number): string {
134
+ if (hits.length === 0) {
135
+ return "Nessun documento rilevante trovato. Score medio: 0";
136
+ }
137
+ return `Trovati ${hits.length} documenti rilevanti. Score medio: ${avgScore.toFixed(2)}. Miglior documento: ${hits[0]?.path ?? "N/A"}`;
138
+ }
139
+
140
+ private selfReflect(
141
+ _query: string,
142
+ hits: SearchHit[],
143
+ avgScore: number
144
+ ): { shouldRewrite: boolean; isSatisfied: boolean } {
145
+ // Se score è alto, siamo soddisfatti
146
+ if (avgScore > 0.7 && hits.length >= 3) {
147
+ return { shouldRewrite: false, isSatisfied: true };
148
+ }
149
+
150
+ // Se score è molto basso, riscrivi
151
+ if (avgScore < 0.4 || hits.length < 2) {
152
+ return { shouldRewrite: true, isSatisfied: false };
153
+ }
154
+
155
+ // Score medio, continua ma non riscrivere
156
+ return { shouldRewrite: false, isSatisfied: false };
157
+ }
158
+
159
+ private buildAnswer(
160
+ _query: string,
161
+ hits: SearchHit[],
162
+ reasoningSteps: ReasoningStep[]
163
+ ): string {
164
+ const parts: string[] = [];
165
+
166
+ // Chain of Thought (se abilitato)
167
+ if (this.options.enableChainOfThought && reasoningSteps.length > 0) {
168
+ parts.push(`## Processo di Ragionamento\n`);
169
+ reasoningSteps.forEach((step, i) => {
170
+ parts.push(`### Step ${i + 1}`);
171
+ parts.push(`**Thought:** ${step.thought}`);
172
+ parts.push(`**Action:** ${step.action}`);
173
+ parts.push(`**Observation:** ${step.observation}\n`);
174
+ });
175
+ }
176
+
177
+ // Risposta finale
178
+ parts.push(`## Risposta\n`);
179
+
180
+ if (hits.length === 0) {
181
+ parts.push(`Non sono stati trovati documenti rilevanti per la query fornita.`);
182
+ } else {
183
+ parts.push(`Basato su ${hits.length} documenti pertinenti:\n`);
184
+
185
+ hits.slice(0, 5).forEach((hit, i) => {
186
+ parts.push(`### Fonte ${i + 1}: ${hit.path}`);
187
+ parts.push(`Righe ${hit.startLine}-${hit.endLine} (score: ${hit.score.toFixed(2)})\n`);
188
+ parts.push(`\`\`\`\n${hit.text}\n\`\`\`\n`);
189
+ });
190
+ }
191
+
192
+ return parts.join("\n");
193
+ }
194
+
195
+ private finalReflection(
196
+ query: string,
197
+ hits: SearchHit[],
198
+ avgScore: number,
199
+ iterations: number
200
+ ): string {
201
+ const parts: string[] = [];
202
+
203
+ parts.push(`**Valutazione Finale:**`);
204
+ parts.push(`- Query: "${query}"`);
205
+ parts.push(`- Iterazioni: ${iterations}`);
206
+ parts.push(`- Documenti trovati: ${hits.length}`);
207
+ parts.push(`- Score medio: ${avgScore.toFixed(2)}`);
208
+ parts.push(`- Confidenza: ${avgScore > 0.7 ? "Alta" : avgScore > 0.4 ? "Media" : "Bassa"}`);
209
+
210
+ if (avgScore > 0.7) {
211
+ parts.push(`\n✅ I risultati sono altamente pertinenti alla query.`);
212
+ } else if (avgScore > 0.4) {
213
+ parts.push(`\n⚠️ I risultati sono parzialmente pertinenti. Potrebbe essere necessario raffinare la query.`);
214
+ } else {
215
+ parts.push(`\n❌ I risultati non sono sufficientemente pertinenti. Considera di riformulare la query.`);
216
+ }
217
+
218
+ return parts.join("\n");
219
+ }
220
+ }
@@ -0,0 +1,166 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { CorrectiveRag } from "./corrective-rag.js";
3
+ import type { SearchHit } from "@eucode/indexer";
4
+ import type { Retriever, Grader, QueryRewriter } from "./types.js";
5
+
6
+ class MockRetriever implements Retriever {
7
+ constructor(private hits: SearchHit[]) {}
8
+
9
+ async search(_query: string, _topK: number): Promise<SearchHit[]> {
10
+ return this.hits;
11
+ }
12
+ }
13
+
14
+ class MockGrader implements Grader {
15
+ constructor(private relevantIds: Set<string>) {}
16
+
17
+ async grade(_query: string, hit: SearchHit) {
18
+ const relevant = this.relevantIds.has(hit.id);
19
+ return {
20
+ relevant,
21
+ score: relevant ? 0.9 : 0.1,
22
+ };
23
+ }
24
+ }
25
+
26
+ class MockRewriter implements QueryRewriter {
27
+ async rewrite(query: string, _context: string): Promise<string> {
28
+ return `${query} (rewritten)`;
29
+ }
30
+ }
31
+
32
+ function createHit(id: string, path: string, text: string): SearchHit {
33
+ return {
34
+ id,
35
+ path,
36
+ startLine: 1,
37
+ endLine: 10,
38
+ text,
39
+ score: 0.5,
40
+ };
41
+ }
42
+
43
+ describe("CorrectiveRag", () => {
44
+ it("returns relevant hits without rewriting when enough good hits", async () => {
45
+ const hits = [
46
+ createHit("1", "src/auth.ts", "function login() { ... }"),
47
+ createHit("2", "src/auth.ts", "function logout() { ... }"),
48
+ createHit("3", "src/billing.ts", "function charge() { ... }"),
49
+ ];
50
+
51
+ const rag = new CorrectiveRag({
52
+ retriever: new MockRetriever(hits),
53
+ grader: new MockGrader(new Set(["1", "2"])),
54
+ rewriter: new MockRewriter(),
55
+ minGoodHits: 2,
56
+ });
57
+
58
+ const result = await rag.answer("authentication");
59
+
60
+ expect(result.citations).toHaveLength(2);
61
+ expect(result.rewrites).toBe(0);
62
+ expect(result.steps).toContain("Initial query: \"authentication\"");
63
+ });
64
+
65
+ it("rewrites query when not enough relevant hits", async () => {
66
+ const hits = [
67
+ createHit("1", "src/auth.ts", "function login() { ... }"),
68
+ createHit("2", "src/billing.ts", "function charge() { ... }"),
69
+ ];
70
+
71
+ const rag = new CorrectiveRag({
72
+ retriever: new MockRetriever(hits),
73
+ grader: new MockGrader(new Set(["1"])),
74
+ rewriter: new MockRewriter(),
75
+ minGoodHits: 2,
76
+ maxRewrites: 1,
77
+ });
78
+
79
+ const result = await rag.answer("authentication");
80
+
81
+ expect(result.rewrites).toBe(1);
82
+ expect(result.steps.some((s) => s.includes("Rewriting query"))).toBe(true);
83
+ });
84
+
85
+ it("respects maxRewrites limit", async () => {
86
+ const hits = [
87
+ createHit("1", "src/billing.ts", "function charge() { ... }"),
88
+ ];
89
+
90
+ const rag = new CorrectiveRag({
91
+ retriever: new MockRetriever(hits),
92
+ grader: new MockGrader(new Set()),
93
+ rewriter: new MockRewriter(),
94
+ minGoodHits: 2,
95
+ maxRewrites: 2,
96
+ });
97
+
98
+ const result = await rag.answer("authentication");
99
+
100
+ expect(result.rewrites).toBe(2);
101
+ });
102
+
103
+ it("formats answer with citations", async () => {
104
+ const hits = [
105
+ createHit("1", "src/auth.ts", "function login() { return true; }"),
106
+ createHit("2", "src/auth.ts", "function logout() { return false; }"),
107
+ ];
108
+
109
+ const rag = new CorrectiveRag({
110
+ retriever: new MockRetriever(hits),
111
+ grader: new MockGrader(new Set(["1", "2"])),
112
+ rewriter: new MockRewriter(),
113
+ minGoodHits: 2,
114
+ });
115
+
116
+ const result = await rag.answer("authentication");
117
+
118
+ expect(result.text).toContain("src/auth.ts:1-10");
119
+ expect(result.citations).toHaveLength(2);
120
+ expect(result.citations[0].path).toBe("src/auth.ts");
121
+ });
122
+
123
+ it("returns empty answer when no relevant hits found", async () => {
124
+ const hits = [
125
+ createHit("1", "src/billing.ts", "function charge() { ... }"),
126
+ ];
127
+
128
+ const rag = new CorrectiveRag({
129
+ retriever: new MockRetriever(hits),
130
+ grader: new MockGrader(new Set()),
131
+ rewriter: new MockRewriter(),
132
+ minGoodHits: 1,
133
+ maxRewrites: 0,
134
+ });
135
+
136
+ const result = await rag.answer("authentication");
137
+
138
+ expect(result.citations).toHaveLength(0);
139
+ expect(result.text).toContain("No relevant results found");
140
+ });
141
+
142
+ it("uses web fallback when configured", async () => {
143
+ const hits = [
144
+ createHit("1", "src/billing.ts", "function charge() { ... }"),
145
+ ];
146
+
147
+ const webHits = [
148
+ createHit("web1", "https://example.com", "Web result about auth"),
149
+ ];
150
+
151
+ const rag = new CorrectiveRag({
152
+ retriever: new MockRetriever(hits),
153
+ grader: new MockGrader(new Set()),
154
+ rewriter: new MockRewriter(),
155
+ minGoodHits: 1,
156
+ maxRewrites: 0,
157
+ webFallback: async (_query: string) => webHits,
158
+ });
159
+
160
+ const result = await rag.answer("authentication");
161
+
162
+ expect(result.citations).toHaveLength(1);
163
+ expect(result.citations[0].path).toBe("https://example.com");
164
+ expect(result.steps.some((s) => s.includes("Falling back to web search"))).toBe(true);
165
+ });
166
+ });
@@ -0,0 +1,115 @@
1
+ import type {
2
+ CorrectiveRagOptions,
3
+ RagAnswer,
4
+ RagCitation,
5
+ Retriever,
6
+ Grader,
7
+ QueryRewriter,
8
+ } from "./types.js";
9
+
10
+ const DEFAULT_MAX_REWRITES = 1;
11
+ const DEFAULT_MIN_GOOD_HITS = 2;
12
+ const DEFAULT_MIN_SCORE = 0.3;
13
+ const DEFAULT_TOP_K = 8;
14
+
15
+ export class CorrectiveRag {
16
+ private retriever: Retriever;
17
+ private grader: Grader;
18
+ private rewriter: QueryRewriter;
19
+ private maxRewrites: number;
20
+ private minGoodHits: number;
21
+ private minScore: number;
22
+ private webFallback?: (query: string) => Promise<any[]>;
23
+
24
+ constructor(options: CorrectiveRagOptions) {
25
+ this.retriever = options.retriever;
26
+ this.grader = options.grader;
27
+ this.rewriter = options.rewriter;
28
+ this.maxRewrites = options.maxRewrites ?? DEFAULT_MAX_REWRITES;
29
+ this.minGoodHits = options.minGoodHits ?? DEFAULT_MIN_GOOD_HITS;
30
+ this.minScore = options.minScore ?? DEFAULT_MIN_SCORE;
31
+ this.webFallback = options.webFallback;
32
+ }
33
+
34
+ async answer(query: string): Promise<RagAnswer> {
35
+ const steps: string[] = [];
36
+ let currentQuery = query;
37
+ let rewrites = 0;
38
+
39
+ steps.push(`Initial query: "${query}"`);
40
+
41
+ let hits = await this.retriever.search(currentQuery, DEFAULT_TOP_K);
42
+ steps.push(`Retrieved ${hits.length} candidates`);
43
+
44
+ let goodHits = await this.gradeHits(currentQuery, hits);
45
+ steps.push(`Graded: ${goodHits.length} relevant hits (min score: ${this.minScore})`);
46
+
47
+ while (goodHits.length < this.minGoodHits && rewrites < this.maxRewrites) {
48
+ rewrites++;
49
+ steps.push(`Rewriting query (attempt ${rewrites}/${this.maxRewrites})`);
50
+
51
+ const context = goodHits.map((h) => h.text).join("\n");
52
+ currentQuery = await this.rewriter.rewrite(currentQuery, context);
53
+ steps.push(`Rewritten query: "${currentQuery}"`);
54
+
55
+ hits = await this.retriever.search(currentQuery, DEFAULT_TOP_K);
56
+ steps.push(`Retrieved ${hits.length} candidates after rewrite`);
57
+
58
+ goodHits = await this.gradeHits(currentQuery, hits);
59
+ steps.push(`Graded: ${goodHits.length} relevant hits`);
60
+ }
61
+
62
+ if (goodHits.length < this.minGoodHits && this.webFallback) {
63
+ steps.push(`Falling back to web search`);
64
+ const webHits = await this.webFallback(currentQuery);
65
+ goodHits = [...goodHits, ...webHits];
66
+ steps.push(`Added ${webHits.length} web results`);
67
+ }
68
+
69
+ const citations: RagCitation[] = goodHits.map((hit) => ({
70
+ path: hit.path,
71
+ startLine: hit.startLine,
72
+ endLine: hit.endLine,
73
+ text: hit.text,
74
+ }));
75
+
76
+ const text = this.formatAnswer(currentQuery, goodHits);
77
+
78
+ return {
79
+ text,
80
+ citations,
81
+ steps,
82
+ rewrites,
83
+ };
84
+ }
85
+
86
+ private async gradeHits(query: string, hits: any[]): Promise<any[]> {
87
+ const graded = await Promise.all(
88
+ hits.map(async (hit) => {
89
+ const result = await this.grader.grade(query, hit);
90
+ return { hit, result };
91
+ })
92
+ );
93
+
94
+ return graded
95
+ .filter(({ result }) => result.relevant && result.score >= this.minScore)
96
+ .map(({ hit }) => hit);
97
+ }
98
+
99
+ private formatAnswer(query: string, hits: any[]): string {
100
+ if (hits.length === 0) {
101
+ return `No relevant results found for: "${query}"`;
102
+ }
103
+
104
+ const sections = hits.map((hit, i) => {
105
+ const location = `${hit.path}:${hit.startLine}-${hit.endLine}`;
106
+ return `## Result ${i + 1} (${location})\n\n${hit.text}`;
107
+ });
108
+
109
+ return sections.join("\n\n---\n\n");
110
+ }
111
+ }
112
+
113
+ export function createCorrectiveRag(options: CorrectiveRagOptions): CorrectiveRag {
114
+ return new CorrectiveRag(options);
115
+ }