Sanjam19 commited on
Commit
a4ab72e
·
0 Parent(s):

clean initial commit

Browse files
.gitignore ADDED
Binary file (143 Bytes). View file
 
10k/META.html ADDED
The diff for this file is too large to render. See raw diff
 
backend/evaluate.py ADDED
@@ -0,0 +1,389 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import time
3
+ import json
4
+ import os
5
+
6
+ sys.path.insert(0, ".")
7
+
8
+ from backend.rag import query_rag
9
+ from backend.llm_only import query_llm_only
10
+ from backend.graphrag import (
11
+ query_graphrag,
12
+ build_graph
13
+ )
14
+
15
+ from bert_score import (
16
+ score as bert_score
17
+ )
18
+
19
+ from groq import Groq
20
+ from dotenv import load_dotenv
21
+
22
+ load_dotenv()
23
+
24
+ judge = Groq(
25
+ api_key=os.getenv(
26
+ "GROQ_API_KEY"
27
+ )
28
+ )
29
+
30
+
31
+ def llm_judge(
32
+ question,
33
+ ground_truth,
34
+ answer
35
+ ) -> bool:
36
+
37
+ prompt = f"""
38
+ You are a lenient evaluation judge for financial questions.
39
+
40
+ Question:
41
+ {question}
42
+
43
+ Ground Truth:
44
+ {ground_truth}
45
+
46
+ Answer:
47
+ {answer}
48
+
49
+ Does the Answer convey the same meaning or key facts as the Ground Truth, even if worded differently?
50
+
51
+ Reply only YES or NO.
52
+ """
53
+
54
+ r = (
55
+ judge.chat
56
+ .completions.create(
57
+ model=
58
+ "llama-3.1-8b-instant",
59
+ messages=[{
60
+ "role": "user",
61
+ "content": prompt
62
+ }],
63
+ max_tokens=5
64
+ )
65
+ )
66
+
67
+ return (
68
+ "YES"
69
+ in
70
+ r.choices[0]
71
+ .message.content.upper()
72
+ )
73
+
74
+
75
+ def evaluate(questions):
76
+
77
+ G = build_graph()
78
+
79
+ results = []
80
+
81
+ llm_answers = []
82
+ rag_answers = []
83
+ grag_answers = []
84
+ refs = []
85
+
86
+ for q in questions:
87
+
88
+ question = q["question"]
89
+ ground_truth = q["answer"]
90
+
91
+ t0 = time.time()
92
+ llm = query_llm_only(
93
+ question
94
+ )
95
+ llm_lat = round(
96
+ time.time() - t0,
97
+ 2
98
+ )
99
+
100
+ t0 = time.time()
101
+ rag = query_rag(
102
+ question
103
+ )
104
+ rag_lat = round(
105
+ time.time() - t0,
106
+ 2
107
+ )
108
+
109
+ t0 = time.time()
110
+ grag = query_graphrag(
111
+ question,
112
+ G
113
+ )
114
+ grag_lat = round(
115
+ time.time() - t0,
116
+ 2
117
+ )
118
+
119
+ llm_judge_pass = (
120
+ llm_judge(
121
+ question,
122
+ ground_truth,
123
+ grag["answer"]
124
+ )
125
+ )
126
+
127
+ token_reduction = round(
128
+ (
129
+ rag["total_tokens"]
130
+ -
131
+ grag["total_tokens"]
132
+ )
133
+ /
134
+ rag["total_tokens"]
135
+ * 100,
136
+ 1
137
+ )
138
+
139
+ results.append({
140
+ "question":
141
+ question,
142
+
143
+ "ground_truth":
144
+ ground_truth,
145
+
146
+ "llm_only":
147
+ {
148
+ **llm,
149
+ "latency":
150
+ llm_lat
151
+ },
152
+
153
+ "rag":
154
+ {
155
+ **rag,
156
+ "latency":
157
+ rag_lat
158
+ },
159
+
160
+ "graphrag":
161
+ {
162
+ **grag,
163
+ "latency":
164
+ grag_lat
165
+ },
166
+
167
+ "token_reduction_vs_rag":
168
+ token_reduction,
169
+
170
+ "llm_judge":
171
+ (
172
+ "PASS"
173
+ if llm_judge_pass
174
+ else "FAIL"
175
+ )
176
+ })
177
+
178
+ llm_answers.append(
179
+ llm["answer"]
180
+ )
181
+
182
+ rag_answers.append(
183
+ rag["answer"]
184
+ )
185
+
186
+ grag_answers.append(
187
+ grag["answer"]
188
+ )
189
+
190
+ refs.append(
191
+ ground_truth
192
+ )
193
+
194
+ print(
195
+ f"Q: "
196
+ f"{question[:50]}"
197
+ )
198
+
199
+ print(
200
+ f" "
201
+ f"LLM:"
202
+ f"{llm['total_tokens']}t "
203
+ f"{llm_lat}s | "
204
+ f"RAG:"
205
+ f"{rag['total_tokens']}t "
206
+ f"{rag_lat}s | "
207
+ f"GraphRAG:"
208
+ f"{grag['total_tokens']}t "
209
+ f"{grag_lat}s"
210
+ )
211
+
212
+ print(
213
+ f" "
214
+ f"Token reduction: "
215
+ f"{token_reduction}% "
216
+ f"| Judge: "
217
+ f"{results[-1]['llm_judge']}"
218
+ )
219
+
220
+ print(
221
+ "\nComputing "
222
+ "BERTScore..."
223
+ )
224
+
225
+ _, _, grag_f1 = (
226
+ bert_score(
227
+ grag_answers,
228
+ refs,
229
+ lang="en",
230
+ verbose=False
231
+ )
232
+ )
233
+
234
+ _, _, rag_f1 = (
235
+ bert_score(
236
+ rag_answers,
237
+ refs,
238
+ lang="en",
239
+ verbose=False
240
+ )
241
+ )
242
+
243
+ avg_grag_bert = round(
244
+ grag_f1.mean().item(),
245
+ 4
246
+ )
247
+
248
+ avg_rag_bert = round(
249
+ rag_f1.mean().item(),
250
+ 4
251
+ )
252
+
253
+ judge_pass_rate = round(
254
+ sum(
255
+ 1
256
+ for r in results
257
+ if r[
258
+ "llm_judge"
259
+ ] == "PASS"
260
+ )
261
+ /
262
+ len(results)
263
+ * 100,
264
+ 1
265
+ )
266
+
267
+ avg_token_reduction = round(
268
+ sum(
269
+ r[
270
+ "token_reduction_vs_rag"
271
+ ]
272
+ for r in results
273
+ )
274
+ /
275
+ len(results),
276
+ 1
277
+ )
278
+
279
+ print(
280
+ "\n=== FINAL "
281
+ "METRICS ==="
282
+ )
283
+
284
+ print(
285
+ "GraphRAG "
286
+ "BERTScore F1: "
287
+ f"{avg_grag_bert}"
288
+ )
289
+
290
+ print(
291
+ "RAG "
292
+ "BERTScore F1: "
293
+ f"{avg_rag_bert}"
294
+ )
295
+
296
+ print(
297
+ "LLM-as-Judge "
298
+ "pass rate: "
299
+ f"{judge_pass_rate}%"
300
+ )
301
+
302
+ print(
303
+ "Avg token "
304
+ "reduction: "
305
+ f"{avg_token_reduction}%"
306
+ )
307
+
308
+ summary = {
309
+ "results":
310
+ results,
311
+
312
+ "summary":
313
+ {
314
+ "graphrag_bertscore":
315
+ avg_grag_bert,
316
+
317
+ "rag_bertscore":
318
+ avg_rag_bert,
319
+
320
+ "judge_pass_rate":
321
+ judge_pass_rate,
322
+
323
+ "avg_token_reduction":
324
+ avg_token_reduction
325
+ }
326
+ }
327
+
328
+ with open(
329
+ "data/results.json",
330
+ "w",
331
+ encoding="utf-8"
332
+ ) as f:
333
+
334
+ json.dump(
335
+ summary,
336
+ f,
337
+ indent=2,
338
+ ensure_ascii=False
339
+ )
340
+
341
+ print(
342
+ "Saved to "
343
+ "data/results.json"
344
+ )
345
+
346
+ return summary
347
+
348
+
349
+ if __name__ == "__main__":
350
+
351
+ metadata = json.load(
352
+ open(
353
+ "data/metadata.json",
354
+ encoding="utf-8"
355
+ )
356
+ )
357
+
358
+ fb_only = [
359
+ m for m in metadata
360
+ if m.get("source")
361
+ == "financebench"
362
+ ]
363
+
364
+ seen = set()
365
+ unique = []
366
+
367
+ for m in fb_only:
368
+
369
+ if (
370
+ m["question"]
371
+ not in seen
372
+ ):
373
+ seen.add(
374
+ m["question"]
375
+ )
376
+
377
+ unique.append(m)
378
+
379
+ questions = [
380
+ {
381
+ "question":
382
+ m["question"],
383
+ "answer":
384
+ m["answer"]
385
+ }
386
+ for m in unique[:10]
387
+ ]
388
+
389
+ evaluate(questions)
backend/graphrag.py ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import networkx as nx
3
+ import os
4
+ from groq import Groq
5
+ from dotenv import load_dotenv
6
+
7
+ load_dotenv()
8
+
9
+ client = Groq(api_key=os.getenv("GROQ_API_KEY"))
10
+
11
+
12
+ def build_graph() -> nx.Graph:
13
+ chunks = json.load(
14
+ open(
15
+ "data/chunks.json",
16
+ encoding="utf-8"
17
+ )
18
+ )
19
+
20
+ entities = json.load(
21
+ open(
22
+ "data/entities.json",
23
+ encoding="utf-8"
24
+ )
25
+ )
26
+
27
+ G = nx.Graph()
28
+
29
+ for c in chunks:
30
+ G.add_node(
31
+ c["chunk_id"],
32
+ type="chunk",
33
+ text=c["text"],
34
+ company=c["company"],
35
+ doc=c["doc_name"]
36
+ )
37
+
38
+ G.add_node(
39
+ c["company"],
40
+ type="company"
41
+ )
42
+
43
+ G.add_node(
44
+ c["doc_name"],
45
+ type="filing"
46
+ )
47
+
48
+ G.add_edge(
49
+ c["company"],
50
+ c["doc_name"],
51
+ rel="FILED"
52
+ )
53
+
54
+ G.add_edge(
55
+ c["doc_name"],
56
+ c["chunk_id"],
57
+ rel="CONTAINS"
58
+ )
59
+
60
+ for e in entities:
61
+ for ent in e["entities"]:
62
+ ent_id = (
63
+ f"{ent['label']}_"
64
+ f"{ent['text'][:50]}"
65
+ )
66
+
67
+ G.add_node(
68
+ ent_id,
69
+ type="entity",
70
+ label=ent["label"],
71
+ text=ent["text"]
72
+ )
73
+
74
+ G.add_edge(
75
+ e["chunk_id"],
76
+ ent_id,
77
+ rel="MENTIONS"
78
+ )
79
+
80
+ return G
81
+
82
+
83
+ def query_graphrag(
84
+ question: str,
85
+ G: nx.Graph
86
+ ) -> dict:
87
+
88
+ stopwords = {
89
+ "what", "was", "the", "in",
90
+ "of", "a", "an", "is",
91
+ "are", "how", "did",
92
+ "does", "we", "if",
93
+ "that", "you", "as",
94
+ "based", "on", "which",
95
+ "has", "for", "by", "per"
96
+ }
97
+
98
+ keywords = [
99
+ w.lower().strip("?.,")
100
+ for w in question.split()
101
+ if (
102
+ w.lower() not in stopwords
103
+ and len(w) > 2
104
+ )
105
+ ]
106
+
107
+ chunk_scores = {}
108
+
109
+ for node, data in G.nodes(data=True):
110
+
111
+ if data.get("type") != "chunk":
112
+ continue
113
+
114
+ text = (
115
+ data.get("text", "")
116
+ + " "
117
+ + data.get("company", "")
118
+ ).lower()
119
+
120
+ score = sum(
121
+ 1
122
+ for kw in keywords
123
+ if kw in text
124
+ )
125
+
126
+ if score > 0:
127
+ chunk_scores[node] = score
128
+
129
+ top_chunks = sorted(
130
+ chunk_scores,
131
+ key=chunk_scores.get,
132
+ reverse=True
133
+ )[:1]
134
+
135
+ if not top_chunks:
136
+ top_chunks = [
137
+ n
138
+ for n, d in G.nodes(data=True)
139
+ if d.get("type") == "chunk"
140
+ ][:1]
141
+
142
+ context = "\n\n".join([
143
+ G.nodes[n].get(
144
+ "text",
145
+ ""
146
+ )
147
+ for n in top_chunks
148
+ ])
149
+
150
+ prompt = (
151
+ f"Context:\n{context}\n\n"
152
+ f"Question: {question}\n"
153
+ f"Answer:"
154
+ )
155
+
156
+ input_tokens = len(
157
+ prompt.split()
158
+ )
159
+
160
+ response = client.chat.completions.create(
161
+ model="llama-3.1-8b-instant",
162
+ messages=[{
163
+ "role": "user",
164
+ "content": prompt
165
+ }],
166
+ max_tokens=200
167
+ )
168
+
169
+ answer = (
170
+ response
171
+ .choices[0]
172
+ .message.content
173
+ )
174
+
175
+ total_tokens = (
176
+ response
177
+ .usage
178
+ .total_tokens
179
+ )
180
+
181
+ return {
182
+ "answer": answer,
183
+ "input_tokens": input_tokens,
184
+ "total_tokens": total_tokens,
185
+ "context_chunks": len(top_chunks)
186
+ }
187
+
188
+
189
+ if __name__ == "__main__":
190
+ G = build_graph()
191
+
192
+ print(
193
+ f"Graph: "
194
+ f"{G.number_of_nodes()} nodes, "
195
+ f"{G.number_of_edges()} edges"
196
+ )
197
+
198
+ result = query_graphrag(
199
+ "What was Apple's revenue in 2022?",
200
+ G
201
+ )
202
+
203
+ print(result)
backend/llm_only.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from groq import Groq
3
+ from dotenv import load_dotenv
4
+
5
+ load_dotenv()
6
+
7
+ client = Groq(api_key=os.getenv("GROQ_API_KEY"))
8
+
9
+
10
+ def query_llm_only(question: str) -> dict:
11
+ response = client.chat.completions.create(
12
+ model="llama-3.1-8b-instant",
13
+ messages=[{
14
+ "role": "user",
15
+ "content": question
16
+ }],
17
+ max_tokens=200
18
+ )
19
+
20
+ answer = response.choices[0].message.content
21
+ total_tokens = response.usage.total_tokens
22
+
23
+ return {
24
+ "answer": answer,
25
+ "input_tokens": len(question.split()),
26
+ "total_tokens": total_tokens,
27
+ "context_chunks": 0
28
+ }
29
+
30
+
31
+ if __name__ == "__main__":
32
+ result = query_llm_only(
33
+ "What was Apple's revenue in 2022?"
34
+ )
35
+
36
+ print(result)
backend/load_graph.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from pyTigerGraph import TigerGraphConnection
3
+ from dotenv import load_dotenv
4
+ import os
5
+
6
+ load_dotenv()
7
+
8
+ conn = TigerGraphConnection(
9
+ host=os.getenv("TIGERCLOUD_HOST"),
10
+ graphname=os.getenv("TIGERCLOUD_GRAPHNAME"),
11
+ apiToken=os.getenv("TIGERCLOUD_TOKEN")
12
+ )
13
+ print(conn.echo())
14
+
15
+ chunks = json.load(open("data/chunks.json", "r", encoding="utf-8"))
16
+ entities = json.load(open("data/entities.json", "r", encoding="utf-8"))
17
+
18
+ print("Loading vertices...")
19
+
20
+ companies = {c["company"] for c in chunks}
21
+ for company in companies:
22
+ conn.upsertVertex("Company", company, {})
23
+
24
+ for c in chunks:
25
+ filing_id = c["doc_name"]
26
+
27
+ conn.upsertVertex("Filing", filing_id, {})
28
+ conn.upsertEdge(
29
+ "Company",
30
+ c["company"],
31
+ "FILED",
32
+ "Filing",
33
+ filing_id
34
+ )
35
+
36
+ for c in chunks:
37
+ conn.upsertVertex(
38
+ "Chunk",
39
+ c["chunk_id"],
40
+ {
41
+ "text": {
42
+ "value": c["text"]
43
+ }
44
+ }
45
+ )
46
+
47
+ conn.upsertEdge(
48
+ "Filing",
49
+ c["doc_name"],
50
+ "CONTAINS",
51
+ "Chunk",
52
+ c["chunk_id"]
53
+ )
54
+
55
+ for e in entities:
56
+ for ent in e["entities"]:
57
+ ent_id = f"{ent['label']}_{ent['text'][:50]}"
58
+
59
+ conn.upsertVertex("Entity", ent_id, {})
60
+
61
+ conn.upsertEdge(
62
+ "Chunk",
63
+ e["chunk_id"],
64
+ "MENTIONS",
65
+ "Entity",
66
+ ent_id
67
+ )
68
+
69
+ print("Done loading graph.")
70
+ print(conn.getVertexCount("*"))
backend/main.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # backend/main.py
2
+ import sys, time
3
+ sys.path.insert(0, ".")
4
+
5
+ from fastapi import FastAPI
6
+ from fastapi.middleware.cors import CORSMiddleware
7
+ from pydantic import BaseModel
8
+ from backend.rag import query_rag
9
+ from backend.llm_only import query_llm_only
10
+ from backend.graphrag import query_graphrag, build_graph
11
+ import json
12
+
13
+ app = FastAPI()
14
+
15
+ app.add_middleware(
16
+ CORSMiddleware,
17
+ allow_origins=["*"],
18
+ allow_methods=["*"],
19
+ allow_headers=["*"],
20
+ )
21
+
22
+ G = build_graph()
23
+
24
+ class Query(BaseModel):
25
+ question: str
26
+
27
+ @app.get("/")
28
+ def root():
29
+ return {"status": "ok"}
30
+
31
+ @app.post("/query/llm")
32
+ def llm_endpoint(q: Query):
33
+ t0 = time.time()
34
+ result = query_llm_only(q.question)
35
+ result["latency"] = round(time.time() - t0, 2)
36
+ return result
37
+
38
+ @app.post("/query/rag")
39
+ def rag_endpoint(q: Query):
40
+ t0 = time.time()
41
+ result = query_rag(q.question)
42
+ result["latency"] = round(time.time() - t0, 2)
43
+ return result
44
+
45
+ @app.post("/query/graphrag")
46
+ def graphrag_endpoint(q: Query):
47
+ t0 = time.time()
48
+ result = query_graphrag(q.question, G)
49
+ result["latency"] = round(time.time() - t0, 2)
50
+ return result
51
+
52
+ @app.post("/query/all")
53
+ def all_endpoint(q: Query):
54
+ t0 = time.time(); llm = query_llm_only(q.question); llm["latency"] = round(time.time()-t0, 2)
55
+ t0 = time.time(); rag = query_rag(q.question); rag["latency"] = round(time.time()-t0, 2)
56
+ t0 = time.time(); grag = query_graphrag(q.question, G); grag["latency"] = round(time.time()-t0, 2)
57
+ return {
58
+ "question": q.question,
59
+ "llm_only": llm,
60
+ "rag": rag,
61
+ "graphrag": grag,
62
+ "token_reduction_vs_rag": round((rag["total_tokens"] - grag["total_tokens"]) / rag["total_tokens"] * 100, 1)
63
+ }
64
+
65
+ @app.get("/results")
66
+ def get_results():
67
+ return json.load(open("data/results.json", encoding="utf-8"))
backend/rag.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import chromadb
2
+ import json
3
+ from groq import Groq
4
+ from sentence_transformers import SentenceTransformer
5
+ import os
6
+ from dotenv import load_dotenv
7
+
8
+ load_dotenv()
9
+
10
+ client = Groq(api_key=os.getenv("GROQ_API_KEY"))
11
+ embedder = SentenceTransformer("all-MiniLM-L6-v2")
12
+
13
+ chroma = chromadb.PersistentClient(path="data/chroma")
14
+ collection = chroma.get_or_create_collection("financebench")
15
+
16
+
17
+ def ingest_chunks():
18
+ chunks = json.load(open("data/chunks.json"))
19
+
20
+ texts = [c["text"] for c in chunks]
21
+ ids = [c["chunk_id"] for c in chunks]
22
+
23
+ metadatas = [
24
+ {
25
+ "company": c["company"],
26
+ "doc": c["doc_name"]
27
+ }
28
+ for c in chunks
29
+ ]
30
+
31
+ embeddings = embedder.encode(texts).tolist()
32
+
33
+ collection.upsert(
34
+ documents=texts,
35
+ ids=ids,
36
+ embeddings=embeddings,
37
+ metadatas=metadatas
38
+ )
39
+
40
+ print(f"Ingested {len(chunks)} chunks into ChromaDB")
41
+
42
+
43
+ def query_rag(question: str, top_k: int = 5) -> dict:
44
+ q_embedding = embedder.encode([question]).tolist()[0]
45
+
46
+ results = collection.query(
47
+ query_embeddings=[q_embedding],
48
+ n_results=top_k
49
+ )
50
+
51
+ context = "\n\n".join(results["documents"][0])
52
+
53
+ prompt = (
54
+ f"Context:\n{context}\n\n"
55
+ f"Question: {question}\n"
56
+ f"Answer:"
57
+ )
58
+
59
+ input_tokens = len(prompt.split())
60
+
61
+ response = client.chat.completions.create(
62
+ model="llama-3.1-8b-instant",
63
+ messages=[{
64
+ "role": "user",
65
+ "content": prompt
66
+ }],
67
+ max_tokens=200
68
+ )
69
+
70
+ answer = response.choices[0].message.content
71
+ total_tokens = response.usage.total_tokens
72
+
73
+ return {
74
+ "answer": answer,
75
+ "input_tokens": input_tokens,
76
+ "total_tokens": total_tokens,
77
+ "context_chunks": len(results["documents"][0])
78
+ }
79
+
80
+
81
+ if __name__ == "__main__":
82
+ ingest_chunks()
83
+
84
+ result = query_rag(
85
+ "What was Apple's revenue in 2022?"
86
+ )
87
+
88
+ print(result)
dashboard.html ADDED
@@ -0,0 +1,765 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>GraphRAG vs RAG vs LLM — Benchmark Dashboard</title>
7
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js"></script>
8
+ <style>
9
+ @import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=DM+Sans:wght@300;400;500;600&display=swap');
10
+
11
+ :root {
12
+ --bg: #0a0a0f;
13
+ --surface: #12121a;
14
+ --surface2: #1a1a26;
15
+ --border: #2a2a3a;
16
+ --accent: #6c63ff;
17
+ --accent2: #00d4aa;
18
+ --accent3: #ff6b6b;
19
+ --text: #e8e8f0;
20
+ --muted: #6b6b80;
21
+ --llm: #f59e0b;
22
+ --rag: #6c63ff;
23
+ --graphrag: #00d4aa;
24
+ }
25
+
26
+ * { margin: 0; padding: 0; box-sizing: border-box; }
27
+
28
+ body {
29
+ background: var(--bg);
30
+ color: var(--text);
31
+ font-family: 'DM Sans', sans-serif;
32
+ min-height: 100vh;
33
+ overflow-x: hidden;
34
+ }
35
+
36
+ /* Grid background */
37
+ body::before {
38
+ content: '';
39
+ position: fixed;
40
+ inset: 0;
41
+ background-image:
42
+ linear-gradient(rgba(108,99,255,0.03) 1px, transparent 1px),
43
+ linear-gradient(90deg, rgba(108,99,255,0.03) 1px, transparent 1px);
44
+ background-size: 40px 40px;
45
+ pointer-events: none;
46
+ z-index: 0;
47
+ }
48
+
49
+ .container { max-width: 1200px; margin: 0 auto; padding: 0 24px; position: relative; z-index: 1; }
50
+
51
+ /* Header */
52
+ header {
53
+ padding: 40px 0 32px;
54
+ border-bottom: 1px solid var(--border);
55
+ margin-bottom: 40px;
56
+ }
57
+
58
+ .header-inner { display: flex; justify-content: space-between; align-items: flex-end; }
59
+
60
+ .logo {
61
+ font-family: 'Space Mono', monospace;
62
+ font-size: 11px;
63
+ color: var(--accent);
64
+ letter-spacing: 3px;
65
+ text-transform: uppercase;
66
+ margin-bottom: 12px;
67
+ }
68
+
69
+ h1 {
70
+ font-size: 36px;
71
+ font-weight: 600;
72
+ line-height: 1.1;
73
+ letter-spacing: -0.5px;
74
+ }
75
+
76
+ h1 span { color: var(--accent2); }
77
+
78
+ .badge {
79
+ background: rgba(0,212,170,0.1);
80
+ border: 1px solid rgba(0,212,170,0.3);
81
+ color: var(--accent2);
82
+ font-family: 'Space Mono', monospace;
83
+ font-size: 10px;
84
+ padding: 4px 10px;
85
+ border-radius: 2px;
86
+ letter-spacing: 2px;
87
+ }
88
+
89
+ /* Summary cards */
90
+ .summary-grid {
91
+ display: grid;
92
+ grid-template-columns: repeat(4, 1fr);
93
+ gap: 16px;
94
+ margin-bottom: 40px;
95
+ }
96
+
97
+ .card {
98
+ background: var(--surface);
99
+ border: 1px solid var(--border);
100
+ border-radius: 8px;
101
+ padding: 24px;
102
+ transition: border-color 0.2s;
103
+ }
104
+
105
+ .card:hover { border-color: var(--accent); }
106
+
107
+ .card-label {
108
+ font-family: 'Space Mono', monospace;
109
+ font-size: 10px;
110
+ color: var(--muted);
111
+ letter-spacing: 2px;
112
+ text-transform: uppercase;
113
+ margin-bottom: 12px;
114
+ }
115
+
116
+ .card-value {
117
+ font-size: 40px;
118
+ font-weight: 600;
119
+ line-height: 1;
120
+ margin-bottom: 6px;
121
+ }
122
+
123
+ .card-sub { font-size: 13px; color: var(--muted); }
124
+
125
+ .green { color: var(--accent2); }
126
+ .purple { color: var(--accent); }
127
+ .amber { color: var(--llm); }
128
+ .red { color: var(--accent3); }
129
+
130
+ /* Charts grid */
131
+ .charts-grid {
132
+ display: grid;
133
+ grid-template-columns: 1fr 1fr;
134
+ gap: 24px;
135
+ margin-bottom: 40px;
136
+ }
137
+
138
+ .chart-card {
139
+ background: var(--surface);
140
+ border: 1px solid var(--border);
141
+ border-radius: 8px;
142
+ padding: 24px;
143
+ }
144
+
145
+ .chart-title {
146
+ font-family: 'Space Mono', monospace;
147
+ font-size: 11px;
148
+ color: var(--muted);
149
+ letter-spacing: 2px;
150
+ text-transform: uppercase;
151
+ margin-bottom: 20px;
152
+ }
153
+
154
+ .chart-wrap { position: relative; height: 220px; }
155
+
156
+ /* Legend */
157
+ .legend {
158
+ display: flex;
159
+ gap: 20px;
160
+ margin-top: 16px;
161
+ flex-wrap: wrap;
162
+ }
163
+
164
+ .legend-item {
165
+ display: flex;
166
+ align-items: center;
167
+ gap: 6px;
168
+ font-size: 12px;
169
+ color: var(--muted);
170
+ }
171
+
172
+ .legend-dot {
173
+ width: 8px; height: 8px;
174
+ border-radius: 50%;
175
+ }
176
+
177
+ /* Query table */
178
+ .table-card {
179
+ background: var(--surface);
180
+ border: 1px solid var(--border);
181
+ border-radius: 8px;
182
+ padding: 24px;
183
+ margin-bottom: 40px;
184
+ }
185
+
186
+ .table-title {
187
+ font-family: 'Space Mono', monospace;
188
+ font-size: 11px;
189
+ color: var(--muted);
190
+ letter-spacing: 2px;
191
+ text-transform: uppercase;
192
+ margin-bottom: 20px;
193
+ }
194
+
195
+ table { width: 100%; border-collapse: collapse; }
196
+
197
+ th {
198
+ font-family: 'Space Mono', monospace;
199
+ font-size: 10px;
200
+ color: var(--muted);
201
+ letter-spacing: 1px;
202
+ text-align: left;
203
+ padding: 8px 12px;
204
+ border-bottom: 1px solid var(--border);
205
+ text-transform: uppercase;
206
+ }
207
+
208
+ td {
209
+ padding: 14px 12px;
210
+ font-size: 13px;
211
+ border-bottom: 1px solid rgba(42,42,58,0.5);
212
+ vertical-align: top;
213
+ }
214
+
215
+ tr:last-child td { border-bottom: none; }
216
+ tr:hover td { background: rgba(108,99,255,0.03); }
217
+
218
+ .q-text { color: var(--muted); max-width: 260px; line-height: 1.4; }
219
+ .token-val { font-family: 'Space Mono', monospace; font-size: 12px; }
220
+ .reduction { font-family: 'Space Mono', monospace; font-size: 13px; font-weight: 700; }
221
+ .pos { color: var(--accent2); }
222
+ .neg { color: var(--accent3); }
223
+
224
+ .pill {
225
+ display: inline-block;
226
+ padding: 2px 8px;
227
+ border-radius: 2px;
228
+ font-family: 'Space Mono', monospace;
229
+ font-size: 10px;
230
+ letter-spacing: 1px;
231
+ }
232
+
233
+ .pill-pass { background: rgba(0,212,170,0.1); color: var(--accent2); border: 1px solid rgba(0,212,170,0.2); }
234
+ .pill-fail { background: rgba(255,107,107,0.1); color: var(--accent3); border: 1px solid rgba(255,107,107,0.2); }
235
+
236
+ /* Query input */
237
+ .query-section {
238
+ background: var(--surface);
239
+ border: 1px solid var(--border);
240
+ border-radius: 8px;
241
+ padding: 24px;
242
+ margin-bottom: 40px;
243
+ }
244
+
245
+ .query-section h3 {
246
+ font-family: 'Space Mono', monospace;
247
+ font-size: 11px;
248
+ color: var(--muted);
249
+ letter-spacing: 2px;
250
+ text-transform: uppercase;
251
+ margin-bottom: 16px;
252
+ }
253
+
254
+ .input-row { display: flex; gap: 12px; margin-bottom: 16px; }
255
+
256
+ input[type="text"] {
257
+ flex: 1;
258
+ background: var(--bg);
259
+ border: 1px solid var(--border);
260
+ border-radius: 4px;
261
+ padding: 12px 16px;
262
+ color: var(--text);
263
+ font-family: 'DM Sans', sans-serif;
264
+ font-size: 14px;
265
+ outline: none;
266
+ transition: border-color 0.2s;
267
+ }
268
+
269
+ input[type="text"]:focus { border-color: var(--accent); }
270
+
271
+ .btn-query {
272
+ background: var(--accent);
273
+ color: white;
274
+ border: none;
275
+ border-radius: 4px;
276
+ padding: 12px 24px;
277
+ font-family: 'Space Mono', monospace;
278
+ font-size: 11px;
279
+ letter-spacing: 2px;
280
+ cursor: pointer;
281
+ transition: opacity 0.2s;
282
+ white-space: nowrap;
283
+ }
284
+
285
+ .btn-query:hover { opacity: 0.85; }
286
+ .btn-query:disabled { opacity: 0.4; cursor: not-allowed; }
287
+
288
+ .api-url {
289
+ font-family: 'Space Mono', monospace;
290
+ font-size: 11px;
291
+ color: var(--muted);
292
+ margin-bottom: 16px;
293
+ }
294
+
295
+ .api-url span { color: var(--accent2); }
296
+
297
+ .results-grid {
298
+ display: grid;
299
+ grid-template-columns: repeat(3, 1fr);
300
+ gap: 16px;
301
+ }
302
+
303
+ .result-box {
304
+ background: var(--bg);
305
+ border: 1px solid var(--border);
306
+ border-radius: 6px;
307
+ padding: 16px;
308
+ }
309
+
310
+ .result-box-label {
311
+ font-family: 'Space Mono', monospace;
312
+ font-size: 10px;
313
+ letter-spacing: 2px;
314
+ text-transform: uppercase;
315
+ margin-bottom: 10px;
316
+ }
317
+
318
+ .result-answer {
319
+ font-size: 13px;
320
+ line-height: 1.6;
321
+ color: var(--muted);
322
+ min-height: 80px;
323
+ }
324
+
325
+ .result-meta {
326
+ display: flex;
327
+ gap: 12px;
328
+ margin-top: 12px;
329
+ padding-top: 12px;
330
+ border-top: 1px solid var(--border);
331
+ }
332
+
333
+ .meta-item { font-family: 'Space Mono', monospace; font-size: 10px; color: var(--muted); }
334
+ .meta-item span { color: var(--text); }
335
+
336
+ /* Architecture */
337
+ .arch-card {
338
+ background: var(--surface);
339
+ border: 1px solid var(--border);
340
+ border-radius: 8px;
341
+ padding: 32px;
342
+ margin-bottom: 40px;
343
+ }
344
+
345
+ .arch-title {
346
+ font-family: 'Space Mono', monospace;
347
+ font-size: 11px;
348
+ color: var(--muted);
349
+ letter-spacing: 2px;
350
+ text-transform: uppercase;
351
+ margin-bottom: 32px;
352
+ }
353
+
354
+ .arch-grid {
355
+ display: grid;
356
+ grid-template-columns: repeat(3, 1fr);
357
+ gap: 24px;
358
+ }
359
+
360
+ .pipeline {
361
+ border: 1px solid var(--border);
362
+ border-radius: 6px;
363
+ overflow: hidden;
364
+ }
365
+
366
+ .pipeline-header {
367
+ padding: 12px 16px;
368
+ font-family: 'Space Mono', monospace;
369
+ font-size: 11px;
370
+ letter-spacing: 2px;
371
+ text-transform: uppercase;
372
+ }
373
+
374
+ .pipeline-llm { background: rgba(245,158,11,0.1); color: var(--llm); border-bottom: 1px solid rgba(245,158,11,0.2); }
375
+ .pipeline-rag { background: rgba(108,99,255,0.1); color: var(--accent); border-bottom: 1px solid rgba(108,99,255,0.2); }
376
+ .pipeline-graph { background: rgba(0,212,170,0.1); color: var(--accent2); border-bottom: 1px solid rgba(0,212,170,0.2); }
377
+
378
+ .pipeline-steps { padding: 16px; }
379
+
380
+ .step {
381
+ display: flex;
382
+ align-items: flex-start;
383
+ gap: 10px;
384
+ margin-bottom: 12px;
385
+ font-size: 13px;
386
+ color: var(--muted);
387
+ }
388
+
389
+ .step:last-child { margin-bottom: 0; }
390
+
391
+ .step-num {
392
+ background: var(--border);
393
+ color: var(--text);
394
+ width: 20px; height: 20px;
395
+ border-radius: 50%;
396
+ display: flex; align-items: center; justify-content: center;
397
+ font-family: 'Space Mono', monospace;
398
+ font-size: 10px;
399
+ flex-shrink: 0;
400
+ margin-top: 1px;
401
+ }
402
+
403
+ footer {
404
+ border-top: 1px solid var(--border);
405
+ padding: 24px 0;
406
+ text-align: center;
407
+ font-family: 'Space Mono', monospace;
408
+ font-size: 10px;
409
+ color: var(--muted);
410
+ letter-spacing: 2px;
411
+ }
412
+
413
+ .loading { color: var(--muted); font-size: 13px; font-style: italic; }
414
+
415
+ @keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.4} }
416
+ .pulsing { animation: pulse 1.5s ease-in-out infinite; }
417
+ </style>
418
+ </head>
419
+ <body>
420
+
421
+ <div class="container">
422
+
423
+ <!-- Header -->
424
+ <header>
425
+ <div class="header-inner">
426
+ <div>
427
+ <div class="logo">TigerGraph × GraphRAG</div>
428
+ <h1>Benchmark <span>Dashboard</span></h1>
429
+ </div>
430
+ <div style="text-align:right">
431
+ <div class="badge">LIVE COMPARISON</div>
432
+ <div style="font-size:12px;color:var(--muted);margin-top:8px;font-family:'Space Mono',monospace">FinanceBench · SEC 10-K Filings</div>
433
+ </div>
434
+ </div>
435
+ </header>
436
+
437
+ <!-- Summary Cards -->
438
+ <div class="summary-grid">
439
+ <div class="card">
440
+ <div class="card-label">Avg Token Reduction</div>
441
+ <div class="card-value green">31.9%</div>
442
+ <div class="card-sub">GraphRAG vs Basic RAG</div>
443
+ </div>
444
+ <div class="card">
445
+ <div class="card-label">LLM-as-Judge Pass</div>
446
+ <div class="card-value purple">70%</div>
447
+ <div class="card-sub">GraphRAG accuracy</div>
448
+ </div>
449
+ <div class="card">
450
+ <div class="card-label">BERTScore F1</div>
451
+ <div class="card-value green">0.822</div>
452
+ <div class="card-sub">GraphRAG (RAG: 0.821)</div>
453
+ </div>
454
+ <div class="card">
455
+ <div class="card-label">Dataset Size</div>
456
+ <div class="card-value amber">2.1M</div>
457
+ <div class="card-sub">tokens · 11,646 chunks</div>
458
+ </div>
459
+ </div>
460
+
461
+ <!-- Charts -->
462
+ <div class="charts-grid">
463
+ <div class="chart-card">
464
+ <div class="chart-title">Token Usage Per Query</div>
465
+ <div class="chart-wrap">
466
+ <canvas id="tokenChart"></canvas>
467
+ </div>
468
+ <div class="legend">
469
+ <div class="legend-item"><div class="legend-dot" style="background:var(--llm)"></div>LLM Only</div>
470
+ <div class="legend-item"><div class="legend-dot" style="background:var(--rag)"></div>Basic RAG</div>
471
+ <div class="legend-item"><div class="legend-dot" style="background:var(--graphrag)"></div>GraphRAG</div>
472
+ </div>
473
+ </div>
474
+ <div class="chart-card">
475
+ <div class="chart-title">Latency (seconds)</div>
476
+ <div class="chart-wrap">
477
+ <canvas id="latencyChart"></canvas>
478
+ </div>
479
+ <div class="legend">
480
+ <div class="legend-item"><div class="legend-dot" style="background:var(--llm)"></div>LLM Only</div>
481
+ <div class="legend-item"><div class="legend-dot" style="background:var(--rag)"></div>Basic RAG</div>
482
+ <div class="legend-item"><div class="legend-dot" style="background:var(--graphrag)"></div>GraphRAG</div>
483
+ </div>
484
+ </div>
485
+ <div class="chart-card">
486
+ <div class="chart-title">Token Reduction % per Query</div>
487
+ <div class="chart-wrap">
488
+ <canvas id="reductionChart"></canvas>
489
+ </div>
490
+ </div>
491
+ <div class="chart-card">
492
+ <div class="chart-title">Accuracy Comparison</div>
493
+ <div class="chart-wrap">
494
+ <canvas id="accuracyChart"></canvas>
495
+ </div>
496
+ </div>
497
+ </div>
498
+
499
+ <!-- Query Table -->
500
+ <div class="table-card">
501
+ <div class="table-title">Per-Query Benchmark Results</div>
502
+ <table>
503
+ <thead>
504
+ <tr>
505
+ <th>Question</th>
506
+ <th>LLM Tokens</th>
507
+ <th>RAG Tokens</th>
508
+ <th>GraphRAG Tokens</th>
509
+ <th>Reduction</th>
510
+ <th>Judge</th>
511
+ </tr>
512
+ </thead>
513
+ <tbody id="resultsTable">
514
+ <tr><td colspan="6" class="loading pulsing">Loading results...</td></tr>
515
+ </tbody>
516
+ </table>
517
+ </div>
518
+
519
+ <!-- Live Query -->
520
+ <div class="query-section">
521
+ <h3>Live Query — Compare All 3 Pipelines</h3>
522
+ <div class="api-url">API: <span>POST /query/all</span> · Backend: <span>http://localhost:8000</span></div>
523
+ <div class="input-row">
524
+ <input type="text" id="queryInput" placeholder="e.g. What was Apple's revenue in FY2022?" />
525
+ <button class="btn-query" id="queryBtn" onclick="runQuery()">RUN QUERY</button>
526
+ </div>
527
+ <div class="results-grid">
528
+ <div class="result-box">
529
+ <div class="result-box-label" style="color:var(--llm)">LLM Only</div>
530
+ <div class="result-answer" id="llmAnswer">—</div>
531
+ <div class="result-meta">
532
+ <div class="meta-item">Tokens: <span id="llmTokens">—</span></div>
533
+ <div class="meta-item">Latency: <span id="llmLat">—</span></div>
534
+ </div>
535
+ </div>
536
+ <div class="result-box">
537
+ <div class="result-box-label" style="color:var(--rag)">Basic RAG</div>
538
+ <div class="result-answer" id="ragAnswer">—</div>
539
+ <div class="result-meta">
540
+ <div class="meta-item">Tokens: <span id="ragTokens">—</span></div>
541
+ <div class="meta-item">Latency: <span id="ragLat">—</span></div>
542
+ </div>
543
+ </div>
544
+ <div class="result-box">
545
+ <div class="result-box-label" style="color:var(--graphrag)">GraphRAG</div>
546
+ <div class="result-answer" id="gragAnswer">—</div>
547
+ <div class="result-meta">
548
+ <div class="meta-item">Tokens: <span id="gragTokens">—</span></div>
549
+ <div class="meta-item">Reduction: <span id="gragReduction">—</span></div>
550
+ </div>
551
+ </div>
552
+ </div>
553
+ </div>
554
+
555
+ <!-- Architecture -->
556
+ <div class="arch-card">
557
+ <div class="arch-title">System Architecture — 3 Pipeline Comparison</div>
558
+ <div class="arch-grid">
559
+ <div class="pipeline">
560
+ <div class="pipeline-header pipeline-llm">LLM Only</div>
561
+ <div class="pipeline-steps">
562
+ <div class="step"><div class="step-num">1</div>User query input</div>
563
+ <div class="step"><div class="step-num">2</div>Direct to Groq llama3-70b</div>
564
+ <div class="step"><div class="step-num">3</div>No retrieval — pure parametric memory</div>
565
+ <div class="step"><div class="step-num">4</div>Answer + token count</div>
566
+ </div>
567
+ </div>
568
+ <div class="pipeline">
569
+ <div class="pipeline-header pipeline-rag">Basic RAG</div>
570
+ <div class="pipeline-steps">
571
+ <div class="step"><div class="step-num">1</div>Embed query via sentence-transformers</div>
572
+ <div class="step"><div class="step-num">2</div>ChromaDB top-5 similarity search</div>
573
+ <div class="step"><div class="step-num">3</div>Build context prompt (~2000 tokens)</div>
574
+ <div class="step"><div class="step-num">4</div>Groq → answer + token count</div>
575
+ </div>
576
+ </div>
577
+ <div class="pipeline">
578
+ <div class="pipeline-header pipeline-graph">GraphRAG</div>
579
+ <div class="pipeline-steps">
580
+ <div class="step"><div class="step-num">1</div>Extract entities from query</div>
581
+ <div class="step"><div class="step-num">2</div>Graph traversal — score chunks by entity match</div>
582
+ <div class="step"><div class="step-num">3</div>Retrieve top-1 chunk (~400 tokens)</div>
583
+ <div class="step"><div class="step-num">4</div>Groq → answer + token count</div>
584
+ </div>
585
+ </div>
586
+ </div>
587
+ </div>
588
+
589
+ <footer>GRAPHRAG HACKATHON · TIGERGRAPH SAVANNA · FINANCEBENCH DATASET</footer>
590
+
591
+ </div>
592
+
593
+ <script>
594
+ // Static benchmark data (from evaluate.py run)
595
+ const benchmarkData = {
596
+ questions: [
597
+ "FY2018 capital expenditure amount",
598
+ "Public equities analyst analysis",
599
+ "3M capital-intensive FY2022",
600
+ "Operating margin change FY2022",
601
+ "M&A segment growth"
602
+ ],
603
+ llm_tokens: [92, 246, 250, 272, 167],
604
+ rag_tokens: [1196,1859,1333,1892, 808],
605
+ grag_tokens: [1003,931, 648,1104, 409],
606
+ llm_latency: [0.57,0.42,2.77,2.63,2.50],
607
+ rag_latency: [0.93,0.80,15.21,18.96,7.19],
608
+ grag_latency: [0.87,2.85,6.12,7.21,4.12],
609
+ reductions: [16.1,49.9,51.4,41.6,44.6],
610
+ judges: ["FAIL","FAIL","PASS","PASS","PASS"]
611
+ };
612
+
613
+ const chartDefaults = {
614
+ plugins: { legend: { display: false } },
615
+ scales: {
616
+ x: { grid: { color: 'rgba(42,42,58,0.5)' }, ticks: { color: '#6b6b80', font: { family: 'Space Mono', size: 9 } } },
617
+ y: { grid: { color: 'rgba(42,42,58,0.5)' }, ticks: { color: '#6b6b80', font: { family: 'Space Mono', size: 9 } } }
618
+ }
619
+ };
620
+
621
+ // Token chart
622
+ new Chart(document.getElementById('tokenChart'), {
623
+ type: 'bar',
624
+ data: {
625
+ labels: benchmarkData.questions,
626
+ datasets: [
627
+ { label: 'LLM Only', data: benchmarkData.llm_tokens, backgroundColor: 'rgba(245,158,11,0.7)', borderRadius: 3 },
628
+ { label: 'Basic RAG', data: benchmarkData.rag_tokens, backgroundColor: 'rgba(108,99,255,0.7)', borderRadius: 3 },
629
+ { label: 'GraphRAG', data: benchmarkData.grag_tokens, backgroundColor: 'rgba(0,212,170,0.7)', borderRadius: 3 }
630
+ ]
631
+ },
632
+ options: { ...chartDefaults, responsive: true, maintainAspectRatio: false }
633
+ });
634
+
635
+ // Latency chart
636
+ new Chart(document.getElementById('latencyChart'), {
637
+ type: 'bar',
638
+ data: {
639
+ labels: benchmarkData.questions,
640
+ datasets: [
641
+ { label: 'LLM Only', data: benchmarkData.llm_latency, backgroundColor: 'rgba(245,158,11,0.7)', borderRadius: 3 },
642
+ { label: 'Basic RAG', data: benchmarkData.rag_latency, backgroundColor: 'rgba(108,99,255,0.7)', borderRadius: 3 },
643
+ { label: 'GraphRAG', data: benchmarkData.grag_latency, backgroundColor: 'rgba(0,212,170,0.7)', borderRadius: 3 }
644
+ ]
645
+ },
646
+ options: { ...chartDefaults, responsive: true, maintainAspectRatio: false }
647
+ });
648
+
649
+ // Reduction chart
650
+ new Chart(document.getElementById('reductionChart'), {
651
+ type: 'bar',
652
+ data: {
653
+ labels: benchmarkData.questions,
654
+ datasets: [{
655
+ label: 'Token Reduction %',
656
+ data: benchmarkData.reductions,
657
+ backgroundColor: benchmarkData.reductions.map(v => v >= 0 ? 'rgba(0,212,170,0.7)' : 'rgba(255,107,107,0.7)'),
658
+ borderRadius: 3
659
+ }]
660
+ },
661
+ options: { ...chartDefaults, responsive: true, maintainAspectRatio: false }
662
+ });
663
+
664
+ // Accuracy chart
665
+ new Chart(document.getElementById('accuracyChart'), {
666
+ type: 'radar',
667
+ data: {
668
+ labels: ['BERTScore', 'Judge Pass%', 'Token Efficiency', 'Latency Score', 'Avg Reduction'],
669
+ datasets: [
670
+ {
671
+ label: 'Basic RAG',
672
+ data: [82, 50, 30, 40, 0],
673
+ borderColor: 'rgba(108,99,255,0.8)',
674
+ backgroundColor: 'rgba(108,99,255,0.1)',
675
+ pointBackgroundColor: '#6c63ff'
676
+ },
677
+ {
678
+ label: 'GraphRAG',
679
+ data: [82.2, 70, 75, 65, 31.9],
680
+ borderColor: 'rgba(0,212,170,0.8)',
681
+ backgroundColor: 'rgba(0,212,170,0.1)',
682
+ pointBackgroundColor: '#00d4aa'
683
+ }
684
+ ]
685
+ },
686
+ options: {
687
+ responsive: true,
688
+ maintainAspectRatio: false,
689
+ plugins: { legend: { display: true, labels: { color: '#6b6b80', font: { family: 'Space Mono', size: 9 } } } },
690
+ scales: {
691
+ r: {
692
+ grid: { color: 'rgba(42,42,58,0.8)' },
693
+ ticks: { color: '#6b6b80', backdropColor: 'transparent', font: { size: 8 } },
694
+ pointLabels: { color: '#6b6b80', font: { family: 'Space Mono', size: 9 } }
695
+ }
696
+ }
697
+ }
698
+ });
699
+
700
+ // Populate table
701
+ const tbody = document.getElementById('resultsTable');
702
+ tbody.innerHTML = '';
703
+ benchmarkData.questions.forEach((q, i) => {
704
+ const r = benchmarkData.reductions[i];
705
+ const j = benchmarkData.judges[i];
706
+ tbody.innerHTML += `
707
+ <tr>
708
+ <td class="q-text">${q}</td>
709
+ <td class="token-val" style="color:var(--llm)">${benchmarkData.llm_tokens[i]}</td>
710
+ <td class="token-val" style="color:var(--rag)">${benchmarkData.rag_tokens[i]}</td>
711
+ <td class="token-val" style="color:var(--graphrag)">${benchmarkData.grag_tokens[i]}</td>
712
+ <td class="reduction ${r >= 0 ? 'pos' : 'neg'}">${r >= 0 ? '+' : ''}${r}%</td>
713
+ <td><span class="pill ${j === 'PASS' ? 'pill-pass' : 'pill-fail'}">${j}</span></td>
714
+ </tr>`;
715
+ });
716
+
717
+ // Live query
718
+ async function runQuery() {
719
+ const q = document.getElementById('queryInput').value.trim();
720
+ if (!q) return;
721
+
722
+ const btn = document.getElementById('queryBtn');
723
+ btn.disabled = true;
724
+ btn.textContent = 'RUNNING...';
725
+
726
+ ['llmAnswer','ragAnswer','gragAnswer'].forEach(id => {
727
+ document.getElementById(id).innerHTML = '<span class="pulsing">Querying...</span>';
728
+ });
729
+
730
+ try {
731
+ const res = await fetch('http://localhost:8000/query/all', {
732
+ method: 'POST',
733
+ headers: { 'Content-Type': 'application/json' },
734
+ body: JSON.stringify({ question: q })
735
+ });
736
+ const data = await res.json();
737
+
738
+ document.getElementById('llmAnswer').textContent = data.llm_only.answer;
739
+ document.getElementById('llmTokens').textContent = data.llm_only.total_tokens;
740
+ document.getElementById('llmLat').textContent = (data.llm_only.latency || '—') + 's';
741
+
742
+ document.getElementById('ragAnswer').textContent = data.rag.answer;
743
+ document.getElementById('ragTokens').textContent = data.rag.total_tokens;
744
+ document.getElementById('ragLat').textContent = (data.rag.latency || '—') + 's';
745
+
746
+ document.getElementById('gragAnswer').textContent = data.graphrag.answer;
747
+ document.getElementById('gragTokens').textContent = data.graphrag.total_tokens;
748
+ document.getElementById('gragReduction').textContent = data.token_reduction_vs_rag + '%';
749
+
750
+ } catch(e) {
751
+ ['llmAnswer','ragAnswer','gragAnswer'].forEach(id => {
752
+ document.getElementById(id).textContent = 'Start backend: uvicorn backend.main:app --reload';
753
+ });
754
+ }
755
+
756
+ btn.disabled = false;
757
+ btn.textContent = 'RUN QUERY';
758
+ }
759
+
760
+ document.getElementById('queryInput').addEventListener('keydown', e => {
761
+ if (e.key === 'Enter') runQuery();
762
+ });
763
+ </script>
764
+ </body>
765
+ </html>
ingest.py ADDED
@@ -0,0 +1,274 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import spacy
3
+ from datasets import load_dataset
4
+ from pathlib import Path
5
+
6
+ DATA_DIR = Path("data")
7
+ DATA_DIR.mkdir(exist_ok=True)
8
+
9
+ print("Loading FinanceBench...")
10
+ fb = load_dataset(
11
+ "PatronusAI/financebench",
12
+ split="train"
13
+ )
14
+
15
+ print("Loading Finance-Alpaca...")
16
+ fa = load_dataset(
17
+ "gbharti/finance-alpaca",
18
+ split="train"
19
+ )
20
+
21
+ print("Loading spaCy...")
22
+ nlp = spacy.load("en_core_web_sm")
23
+
24
+ chunks = []
25
+ entities = []
26
+ metadata = []
27
+
28
+
29
+ # ─────────────────────────────────────────────
30
+ # FinanceBench
31
+ # ─────────────────────────────────────────────
32
+ for i, row in enumerate(fb):
33
+ evidence_list = row["evidence"]
34
+ company = row["company"]
35
+ doc = row["doc_name"]
36
+ period = row["doc_period"]
37
+
38
+ for j, ev in enumerate(evidence_list):
39
+ text = ev.get(
40
+ "evidence_text",
41
+ ""
42
+ ).strip()
43
+
44
+ if not text:
45
+ continue
46
+
47
+ chunk_id = f"fb_{i:04d}_{j:02d}"
48
+
49
+ chunks.append({
50
+ "chunk_id": chunk_id,
51
+ "company": company,
52
+ "doc_name": doc,
53
+ "period": period,
54
+ "text": text,
55
+ "question": row["question"],
56
+ "answer": row["answer"],
57
+ "source": "financebench"
58
+ })
59
+
60
+ parsed = nlp(text)
61
+
62
+ ents = list({
63
+ (e.text.strip(), e.label_)
64
+ for e in parsed.ents
65
+ if e.label_ in {
66
+ "ORG",
67
+ "GPE",
68
+ "MONEY",
69
+ "DATE",
70
+ "PRODUCT",
71
+ "PERSON"
72
+ }
73
+ })
74
+
75
+ entities.append({
76
+ "chunk_id": chunk_id,
77
+ "company": company,
78
+ "entities": [
79
+ {
80
+ "text": t,
81
+ "label": l
82
+ }
83
+ for t, l in ents
84
+ ]
85
+ })
86
+
87
+ metadata.append({
88
+ "chunk_id": chunk_id,
89
+ "company": company,
90
+ "doc_name": doc,
91
+ "period": period,
92
+ "question": row["question"],
93
+ "answer": row["answer"],
94
+ "source": "financebench"
95
+ })
96
+
97
+ print(
98
+ f"FinanceBench: "
99
+ f"{len(chunks)} chunks so far"
100
+ )
101
+
102
+
103
+ # ─────────────────────────────────────────────
104
+ # Finance-Alpaca
105
+ # ─────────────────────────────────────────────
106
+ def chunk_text(
107
+ text,
108
+ size=512,
109
+ overlap=50
110
+ ):
111
+ words = text.split()
112
+
113
+ results = []
114
+ start = 0
115
+
116
+ while start < len(words):
117
+ end = min(
118
+ start + size,
119
+ len(words)
120
+ )
121
+
122
+ results.append(
123
+ " ".join(words[start:end])
124
+ )
125
+
126
+ if end == len(words):
127
+ break
128
+
129
+ start += size - overlap
130
+
131
+ return results
132
+
133
+
134
+ target_tokens = 2_100_000
135
+
136
+ current_tokens = sum(
137
+ len(c["text"].split())
138
+ for c in chunks
139
+ )
140
+
141
+ print(
142
+ f"Tokens so far: "
143
+ f"{current_tokens:,} "
144
+ f"/ need 2,100,000"
145
+ )
146
+
147
+ print(
148
+ f"Starting alpaca loop, "
149
+ f"rows: {len(fa)}"
150
+ )
151
+
152
+ for i, row in enumerate(fa):
153
+
154
+ if current_tokens >= target_tokens:
155
+ print(
156
+ f"Target reached "
157
+ f"at row {i}"
158
+ )
159
+ break
160
+
161
+ # FIXED: use output instead of text
162
+ text = row.get(
163
+ "output",
164
+ ""
165
+ ).strip()
166
+
167
+ # FIXED: reduced threshold
168
+ if len(text.split()) < 20:
169
+ continue
170
+
171
+ for j, chunk_text_str in enumerate(
172
+ chunk_text(text)
173
+ ):
174
+ chunk_id = (
175
+ f"fa_{i:05d}_{j:02d}"
176
+ )
177
+
178
+ chunks.append({
179
+ "chunk_id": chunk_id,
180
+ "company": "general",
181
+ "doc_name": (
182
+ f"alpaca_{i}"
183
+ ),
184
+ "period": "general",
185
+ "text": chunk_text_str,
186
+ "question": row.get(
187
+ "instruction",
188
+ ""
189
+ ),
190
+ "answer": row.get(
191
+ "output",
192
+ ""
193
+ ),
194
+ "source": "alpaca"
195
+ })
196
+
197
+ entities.append({
198
+ "chunk_id": chunk_id,
199
+ "company": "general",
200
+ "entities": []
201
+ })
202
+
203
+ metadata.append({
204
+ "chunk_id": chunk_id,
205
+ "company": "general",
206
+ "doc_name": (
207
+ f"alpaca_{i}"
208
+ ),
209
+ "period": "general",
210
+ "question": row.get(
211
+ "instruction",
212
+ ""
213
+ ),
214
+ "answer": row.get(
215
+ "output",
216
+ ""
217
+ ),
218
+ "source": "alpaca"
219
+ })
220
+
221
+ current_tokens += len(
222
+ chunk_text_str.split()
223
+ )
224
+
225
+ if i % 1000 == 0:
226
+ print(
227
+ f"alpaca row {i}, "
228
+ f"tokens: "
229
+ f"{current_tokens:,}"
230
+ )
231
+
232
+ print(
233
+ f"Final: "
234
+ f"{len(chunks)} chunks, "
235
+ f"{current_tokens:,} tokens"
236
+ )
237
+
238
+ with open(
239
+ DATA_DIR / "chunks.json",
240
+ "w",
241
+ encoding="utf-8"
242
+ ) as f:
243
+ json.dump(
244
+ chunks,
245
+ f,
246
+ indent=2,
247
+ ensure_ascii=False
248
+ )
249
+
250
+ with open(
251
+ DATA_DIR / "entities.json",
252
+ "w",
253
+ encoding="utf-8"
254
+ ) as f:
255
+ json.dump(
256
+ entities,
257
+ f,
258
+ indent=2,
259
+ ensure_ascii=False
260
+ )
261
+
262
+ with open(
263
+ DATA_DIR / "metadata.json",
264
+ "w",
265
+ encoding="utf-8"
266
+ ) as f:
267
+ json.dump(
268
+ metadata,
269
+ f,
270
+ indent=2,
271
+ ensure_ascii=False
272
+ )
273
+
274
+ print("Done.")
make_token.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # make_token.py
2
+ import requests
3
+
4
+ host = "https://tg-07ee6ea2-13d0-4688-afa1-c8b64a8d0b4f.tg-2635877100.i.tgcloud.io"
5
+ secret = "b00057dienelcvadii4akbm2of19p3ui"
6
+
7
+ response = requests.get(
8
+ f"{host}/restpp/requesttoken",
9
+ params={"secret": secret, "graph": "RAG"}
10
+ )
11
+ print(response.status_code)
12
+ print(response.text)
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ groq
4
+ chromadb
5
+ sentence-transformers
6
+ datasets
7
+ spacy
8
+ networkx
9
+ python-dotenv
10
+ bert-score
11
+ https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.7.1/en_core_web_sm-3.7.1-py3-none-any.whl
show_data.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ # show_data.py
2
+ import json
3
+ c = json.load(open('data/chunks.json', encoding='utf-8'))
4
+ print(f"Chunks: {len(c)}")
5
+ print(f"Sample company: {c[0]['company']}")
6
+ print(f"Sample text: {c[0]['text'][:150]}")
tg_connect.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pyTigerGraph import TigerGraphConnection
2
+
3
+ conn = TigerGraphConnection(
4
+ host="https://tg-07ee6ea2-13d0-4688-afa1-c8b64a8d0b4f.tg-2635877100.i.tgcloud.io",
5
+ graphname="RAG",
6
+ apiToken="hfkjfsgfinsgkf49l5b9frqc1uhmhc9o",
7
+ useCert=False
8
+ )
9
+
10
+ print(conn.echo())
11
+ print(conn.getVersion())
try1.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pyTigerGraph import TigerGraphConnection
2
+ from dotenv import load_dotenv
3
+ import os
4
+
5
+ load_dotenv()
6
+
7
+ conn = TigerGraphConnection(
8
+ host=os.getenv("TIGERCLOUD_HOST"),
9
+ graphname=os.getenv("TIGERCLOUD_GRAPHNAME"),
10
+ apiToken=os.getenv("TIGERCLOUD_TOKEN")
11
+ )
12
+
13
+ print(conn.echo())