Vasanth6 commited on
Commit
2290123
·
1 Parent(s): 25d4f70

Pushing the changes for judge as LLM

Browse files
backend/engines/BM25Enginer.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ from typing import Any, Dict, List
3
+
4
+ from backend.routers.retrieval_router import get_keywords
5
+
6
+
7
+ class BM25Engine:
8
+ def __init__(
9
+ self, documents: List[str], doc_ids: List[str], metadatas: List[Dict[str, Any]]
10
+ ):
11
+ self.doc_ids = doc_ids
12
+ self.documents = documents
13
+ self.metadatas = metadatas
14
+ self.corpus_size = len(documents)
15
+ self.k1 = 1.5
16
+ self.b = 0.75
17
+
18
+ self.tokenized_docs = [get_keywords(doc) for doc in documents]
19
+ self.doc_length = [len(doc) for doc in self.tokenized_docs]
20
+ self.avg_doc_len = sum(self.doc_length) / max(1, self.corpus_size)
21
+
22
+ self.doc_tfs = []
23
+ self.dfs = {}
24
+ self.build_frequencies()
25
+
26
+ def build_frequencies(self):
27
+ for doc in self.tokenized_docs:
28
+ tfs = {}
29
+ for term in doc:
30
+ tfs[term] = tfs.get(term, 0) + 1
31
+ self.doc_tfs.append(tfs)
32
+ for term in doc:
33
+ self.dfs[term] = self.dfs.get(term, 0) + 1
34
+
35
+ def idf(self, term):
36
+ df = self.dfs.get(term, 0)
37
+ return math.log(1 + (self.corpus_size - df + 0.5) / (df + 0.5))
38
+
39
+ def score(self, query: str, doc_index: int) -> float:
40
+ query_terms = get_keywords(query)
41
+
42
+ total_score = 0.0
43
+ doc_len = self.doc_length[doc_index]
44
+ tfs = self.doc_tfs[doc_index]
45
+
46
+ for term in query_terms:
47
+ tf = tfs.get(term, 0)
48
+ if tf > 0:
49
+ term_idf = self.idf(term)
50
+
51
+ # BM25 term weighting formula
52
+ numerator = tf * (self.k1 + 1)
53
+ denominator = tf + self.k1 * (
54
+ 1 - self.b + self.b * (doc_len / self.avg_doc_len)
55
+ )
56
+
57
+ total_score += term_idf * (numerator / denominator)
58
+
59
+ return total_score
60
+
61
+ def search(self, query: str, top_k: int = 3) -> list:
62
+ scores = []
63
+ # Score all docs
64
+ for idx in range(self.corpus_size):
65
+ s = self.score(query, idx)
66
+ scores.append((s, idx))
67
+
68
+ # Sort descending by score
69
+ scores.sort(key=lambda x: x[0], reverse=True)
70
+
71
+ # Return documents with structured metadata
72
+ results = []
73
+ for score, idx in scores[:top_k]:
74
+ results.append(
75
+ {
76
+ "id": self.doc_ids[idx],
77
+ "text": self.documents[idx],
78
+ "metadata": self.metadatas[idx],
79
+ "score": score,
80
+ }
81
+ )
82
+ return results
backend/models/schemas.py CHANGED
@@ -151,6 +151,12 @@ class ChunkScore(BaseModel):
151
  / 4,
152
  2,
153
  )
 
 
 
 
 
 
154
  self.overall = expected
155
  return self
156
 
 
151
  / 4,
152
  2,
153
  )
154
+
155
+ # Log the discrepancy if the LLM's math is wrong
156
+ if abs(self.overall - expected) > 0.01:
157
+ print(f"\n\033[93m[JUDGE RAW] overall: {self.overall} (dims: query_relevance={self.query_relevance}, answer_completeness={self.answer_completeness}, factual_plausibility={self.factual_plausibility}, clarity={self.clarity})\033[0m")
158
+ print(f"\033[91m[VALIDATOR] expected overall: {expected} → overriding LLM's claimed {self.overall}\033[0m\n")
159
+
160
  self.overall = expected
161
  return self
162
 
backend/routers/retrieval_router.py CHANGED
@@ -3,7 +3,7 @@ from backend.engines import llm_client
3
  from backend.engines.llm_client import OllamaClient
4
  from backend.constants import system_instructions
5
  import asyncio
6
- from typing import Any, Optional
7
  from fastapi import APIRouter
8
  import json
9
  import re
@@ -16,6 +16,7 @@ from backend.models.schemas import (
16
  JudgeRequest,
17
  QueryRequest,
18
  QueryResponse,
 
19
  RetrievedChunk,
20
  )
21
  from backend.storage.vector_store import VectorStore
@@ -48,6 +49,7 @@ async def retrieve(request: QueryRequest):
48
  request.strategy,
49
  request.top_k,
50
  request.search_text,
 
51
  )
52
 
53
  query_coords = [0.0, 0.0]
@@ -116,21 +118,33 @@ async def judge(request: JudgeRequest):
116
  llm_client = OllamaClient()
117
  result = await llm_client.generate(prompt=prompt)
118
 
119
- print(f"res:res: {result}")
120
-
121
  result_dict = json.loads(result)
122
 
123
  return JudgeResponse(**result_dict)
124
 
125
 
126
  async def process_retrieval(
127
- model, search_text, strategy, top_k, original_query: Optional[str] = None
 
 
 
 
 
128
  ):
129
  vector_store = VectorStore()
130
  embedding_engine = EmbeddingEngine(model.value)
131
  embeddings = await embedding_engine.generate_embeddings([search_text])
 
 
 
 
 
 
 
 
 
132
  result: Any = await vector_store.retrieve(
133
- collection_name=f"{model.value}_{strategy.value}".replace(":", "-"),
134
  embeddings=embeddings,
135
  n_results=top_k,
136
  )
@@ -189,3 +203,15 @@ async def get_hyde_text(search_text):
189
  llm_client = OllamaClient()
190
  hyde_prompt.format(search_text=search_text)
191
  return await llm_client.generate(hyde_prompt, response_format=None)
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  from backend.engines.llm_client import OllamaClient
4
  from backend.constants import system_instructions
5
  import asyncio
6
+ from typing import Any, List, Optional
7
  from fastapi import APIRouter
8
  import json
9
  import re
 
16
  JudgeRequest,
17
  QueryRequest,
18
  QueryResponse,
19
+ RetrievalMode,
20
  RetrievedChunk,
21
  )
22
  from backend.storage.vector_store import VectorStore
 
49
  request.strategy,
50
  request.top_k,
51
  request.search_text,
52
+ request.retrieval_mode,
53
  )
54
 
55
  query_coords = [0.0, 0.0]
 
118
  llm_client = OllamaClient()
119
  result = await llm_client.generate(prompt=prompt)
120
 
 
 
121
  result_dict = json.loads(result)
122
 
123
  return JudgeResponse(**result_dict)
124
 
125
 
126
  async def process_retrieval(
127
+ model,
128
+ search_text,
129
+ strategy,
130
+ top_k,
131
+ retrieval_mode,
132
+ original_query: Optional[str] = None,
133
  ):
134
  vector_store = VectorStore()
135
  embedding_engine = EmbeddingEngine(model.value)
136
  embeddings = await embedding_engine.generate_embeddings([search_text])
137
+ collection_name = f"{model.value}_{strategy.value}".replace(":", "-")
138
+ retrieval_response: Any
139
+ if retrieval_mode == RetrievalMode.DENSE:
140
+ """"""
141
+ elif retrieval_mode == RetrievalMode.SPARSE:
142
+ """"""
143
+ elif retrieval_mode == RetrievalMode.HYBRID:
144
+ """"""
145
+
146
  result: Any = await vector_store.retrieve(
147
+ collection_name=collection_name,
148
  embeddings=embeddings,
149
  n_results=top_k,
150
  )
 
203
  llm_client = OllamaClient()
204
  hyde_prompt.format(search_text=search_text)
205
  return await llm_client.generate(hyde_prompt, response_format=None)
206
+
207
+
208
+ def rrf(dense_ranks: List[str], sparse_ranks: List[str], k: int = 60) -> List[tuple]:
209
+ rrf_scores = {}
210
+
211
+ for rank, doc_id in enumerate(dense_ranks):
212
+ rrf_scores[doc_id] = rrf_scores.get(doc_id, 0.0) + 1.0 / (k + rank + 1)
213
+
214
+ for rank, doc_id in enumerate(sparse_ranks):
215
+ rrf_scores[doc_id] = rrf_scores.get(doc_id, 0.0) + 1.0 / (k + rank + 1)
216
+
217
+ return sorted(rrf_scores.items(), key=lambda x: x[1], reverse=True)
backend/storage/vector_store.py CHANGED
@@ -29,5 +29,11 @@ class VectorStore:
29
  collection = self.get_collection(collection_name)
30
 
31
  return await asyncio.to_thread(
32
- collection.query, query_embeddings=embeddings, n_results=n_results
 
 
33
  )
 
 
 
 
 
29
  collection = self.get_collection(collection_name)
30
 
31
  return await asyncio.to_thread(
32
+ collection.query,
33
+ query_embeddings=embeddings,
34
+ n_results=n_results,
35
  )
36
+
37
+ async def get_all_documents(self, collection_name: str):
38
+ collection = self.get_collection(collection_name)
39
+ return await asyncio.to_thread(collection.get)
dev.bat CHANGED
@@ -1 +1 @@
1
- uv run uvicorn backend.main:app --reload --port 8080
 
1
+ uv run uvicorn backend.main:app --reload --port 8000
frontend/app.js CHANGED
@@ -72,6 +72,7 @@ const dom = {
72
  // Query Simulator elements
73
  queryInput: $("#query-input"),
74
  btnQuery: $("#btn-query"),
 
75
  queryResultsDrawer: $("#query-results-drawer"),
76
  queryResultsList: $("#query-results-list"),
77
  closeDrawer: $("#close-drawer"),
@@ -1201,6 +1202,7 @@ async function runQuerySimulator() {
1201
  embedding_model: state.config.embedding_model,
1202
  strategy: state.strategy,
1203
  top_k: 3,
 
1204
  }),
1205
  });
1206
 
@@ -1340,6 +1342,7 @@ const domArena = {
1340
  modelB: document.getElementById("arena-model-b"),
1341
  strategyB: document.getElementById("arena-strategy-b"),
1342
  resultsB: document.getElementById("arena-results-b"),
 
1343
  };
1344
 
1345
  if (domArena.btnOpen) {
@@ -1389,6 +1392,7 @@ if (domArena.btnFight) {
1389
  strategy_a: domArena.strategyA.value,
1390
  model_b: domArena.modelB.value,
1391
  strategy_b: domArena.strategyB.value,
 
1392
  }),
1393
  });
1394
 
 
72
  // Query Simulator elements
73
  queryInput: $("#query-input"),
74
  btnQuery: $("#btn-query"),
75
+ queryRetrievalMode: $("#query-retrieval-mode"),
76
  queryResultsDrawer: $("#query-results-drawer"),
77
  queryResultsList: $("#query-results-list"),
78
  closeDrawer: $("#close-drawer"),
 
1202
  embedding_model: state.config.embedding_model,
1203
  strategy: state.strategy,
1204
  top_k: 3,
1205
+ retrieval_mode: dom.queryRetrievalMode ? dom.queryRetrievalMode.value : "dense",
1206
  }),
1207
  });
1208
 
 
1342
  modelB: document.getElementById("arena-model-b"),
1343
  strategyB: document.getElementById("arena-strategy-b"),
1344
  resultsB: document.getElementById("arena-results-b"),
1345
+ retrievalMode: document.getElementById("arena-retrieval-mode"),
1346
  };
1347
 
1348
  if (domArena.btnOpen) {
 
1392
  strategy_a: domArena.strategyA.value,
1393
  model_b: domArena.modelB.value,
1394
  strategy_b: domArena.strategyB.value,
1395
+ retrieval_mode: domArena.retrievalMode ? domArena.retrievalMode.value : "dense",
1396
  }),
1397
  });
1398
 
frontend/index.html CHANGED
@@ -243,7 +243,12 @@
243
  <button id="btn-open-arena" class="btn-sm" style="background: var(--superman-red); color: white; border: none; padding: 4px 12px; border-radius: 4px; cursor: pointer; font-weight: bold; font-family: var(--font-sans);">⚔️ Arena Comparison</button>
244
  </div>
245
  <div class="query-input-wrapper">
246
- <input type="text" id="query-input" placeholder="Type a concept query to fetch semantically similar chunks (e.g. 'linear regression')..." />
 
 
 
 
 
247
  <button id="btn-query" class="btn-query">🔍 Query</button>
248
  </div>
249
  <div class="query-results-drawer" id="query-results-drawer" style="display: none;">
@@ -305,6 +310,11 @@
305
  </div>
306
 
307
  <div class="arena-query-bar">
 
 
 
 
 
308
  <input type="text" id="arena-query-input" placeholder="Type a query to compare configurations (e.g., 'What is Clark Kent's weakness?')..." />
309
  <button class="btn-run" id="btn-arena-fight" style="width: 150px; flex-shrink: 0;">🔥 FIGHT!</button>
310
  </div>
 
243
  <button id="btn-open-arena" class="btn-sm" style="background: var(--superman-red); color: white; border: none; padding: 4px 12px; border-radius: 4px; cursor: pointer; font-weight: bold; font-family: var(--font-sans);">⚔️ Arena Comparison</button>
244
  </div>
245
  <div class="query-input-wrapper">
246
+ <select id="query-retrieval-mode" class="select-input" style="width: 110px; flex-shrink: 0; display: none;">
247
+ <option value="dense" selected>Dense</option>
248
+ <option value="sparse">Sparse</option>
249
+ <option value="hybrid">Hybrid</option>
250
+ </select>
251
+ <input type="text" id="query-input" placeholder="Type a concept query to fetch chunks (e.g. 'linear regression')..." />
252
  <button id="btn-query" class="btn-query">🔍 Query</button>
253
  </div>
254
  <div class="query-results-drawer" id="query-results-drawer" style="display: none;">
 
310
  </div>
311
 
312
  <div class="arena-query-bar">
313
+ <select id="arena-retrieval-mode" class="select-input" style="width: 110px; flex-shrink: 0; display: none;">
314
+ <option value="dense" selected>Dense</option>
315
+ <option value="sparse">Sparse</option>
316
+ <option value="hybrid">Hybrid</option>
317
+ </select>
318
  <input type="text" id="arena-query-input" placeholder="Type a query to compare configurations (e.g., 'What is Clark Kent's weakness?')..." />
319
  <button class="btn-run" id="btn-arena-fight" style="width: 150px; flex-shrink: 0;">🔥 FIGHT!</button>
320
  </div>
frontend/styles.css CHANGED
@@ -698,6 +698,25 @@ input[type="range"]::-moz-range-thumb {
698
  /* ============================================================
699
  RETRIEVAL X-RAY HIGHLIGHTS (TASK 3.4)
700
  ============================================================ */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
701
  .xray-content.search-active .chunk-highlight {
702
  opacity: 0.3;
703
  transition: all 0.3s ease;
 
698
  /* ============================================================
699
  RETRIEVAL X-RAY HIGHLIGHTS (TASK 3.4)
700
  ============================================================ */
701
+ /* --- Select Dropdown Styling --- */
702
+ .select-input {
703
+ padding: 8px 12px;
704
+ background-color: var(--bg-input);
705
+ border: 1px solid var(--border-default);
706
+ border-radius: var(--radius-md);
707
+ color: var(--text-primary);
708
+ font-family: var(--font-sans);
709
+ font-size: 0.8rem;
710
+ outline: none;
711
+ transition: all var(--transition-base);
712
+ cursor: pointer;
713
+ }
714
+
715
+ .select-input:focus {
716
+ border-color: var(--superman-blue);
717
+ box-shadow: 0 0 0 2px var(--superman-blue-muted);
718
+ }
719
+
720
  .xray-content.search-active .chunk-highlight {
721
  opacity: 0.3;
722
  transition: all 0.3s ease;