import os import html as html_lib import json import numpy as np import streamlit as st import streamlit.components.v1 as components import plotly.graph_objects as go from system1_baseline import ask_baseline from system2_rag import ask_rag, load_vectorstore, build_vectorstore st.set_page_config( page_title="CodeSage", page_icon="๐Ÿง™", layout="wide" ) # โ”€โ”€ Session state โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ if "ratings" not in st.session_state: st.session_state.ratings = [] if "last_results" not in st.session_state: st.session_state.last_results = None if "rating_submitted" not in st.session_state: st.session_state.rating_submitted = False if "input_question" not in st.session_state: st.session_state.input_question = "" if "auto_run" not in st.session_state: st.session_state.auto_run = False if "auto_metrics" not in st.session_state: st.session_state.auto_metrics = {} if "benchmark_results" not in st.session_state: _bm_cache = "data/benchmark_cache.json" if os.path.exists(_bm_cache): with open(_bm_cache, encoding="utf-8") as _f: st.session_state.benchmark_results = json.load(_f) else: st.session_state.benchmark_results = [] if "winner" not in st.session_state: st.session_state.winner = None if "halluc_flags" not in st.session_state: st.session_state.halluc_flags = {} # โ”€โ”€ CSS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ st.markdown("""""", unsafe_allow_html=True) # โ”€โ”€ Hero โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ st.markdown("""
Research Project  ยท  NLP  ยท  LLM Comparison
๐Ÿง™
CodeSage
Comparative study of RAG vs Fine-Tuning for domain-specific QA.
Ask once — get answers from all three systems simultaneously.
โšก Baseline LLM ๐Ÿ” RAG Chatbot ๐Ÿง  Fine-Tuned Model
""", unsafe_allow_html=True) # โ”€โ”€ Load vector store once โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ @st.cache_resource(show_spinner="Building knowledge base...") def get_vectorstore(): if not os.path.exists("data/faiss_index"): return build_vectorstore() return load_vectorstore() vs = get_vectorstore() # โ”€โ”€ Reference answers + auto-metrics โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ @st.cache_data def load_reference_answers(): path = "data/reference_answers.json" if os.path.exists(path): with open(path, encoding="utf-8") as f: return json.load(f) return {} ref_answers = load_reference_answers() def safe_answer(text: str) -> str: """Escape HTML and convert newlines to
so blank lines don't break the markdown HTML block.""" return html_lib.escape(text).replace('\n', '
') def _cosine(a, b): n = np.linalg.norm(a) * np.linalg.norm(b) return float(np.dot(a, b) / (n + 1e-8)) def compute_auto_metrics(answer: str, question: str, context: str = "") -> dict: if not answer: return {} try: from rouge_score import rouge_scorer as rs scorer = rs.RougeScorer(["rougeL"], use_stemmer=True) a_emb = np.array(vs.embeddings.embed_query(answer)) q_emb = np.array(vs.embeddings.embed_query(question)) # answer relevance โ€” how well the answer addresses the question answer_relevance = round(max(0.0, _cosine(a_emb, q_emb)), 3) # accuracy + rouge vs reference answer ref = ref_answers.get(question.strip().lower()) accuracy = None rouge_l = None r_emb = None if ref: rouge_l = round(scorer.score(ref, answer)["rougeL"].fmeasure, 3) r_emb = np.array(vs.embeddings.embed_query(ref)) accuracy = round(max(0.0, _cosine(a_emb, r_emb)), 3) # groundedness โ€” how much answer is grounded in retrieved context (RAG) # for non-RAG systems use accuracy as proxy groundedness = None if context and context.strip(): c_emb = np.array(vs.embeddings.embed_query(context[:1000])) groundedness = round(max(0.0, _cosine(a_emb, c_emb)), 3) elif accuracy is not None: groundedness = accuracy # proxy for non-RAG systems # faithfulness โ€” lexical overlap with source (context for RAG, reference otherwise) faithfulness = None if context and context.strip(): faithfulness = round(scorer.score(context[:1000], answer)["rougeL"].fmeasure, 3) elif ref: faithfulness = rouge_l # proxy: ROUGE-L vs reference for non-RAG # context relevance โ€” how relevant retrieved context is to question (RAG only) ctx_relevance = None if context and context.strip(): c_emb2 = np.array(vs.embeddings.embed_query(context[:1000])) ctx_relevance = round(max(0.0, _cosine(q_emb, c_emb2)), 3) result = {"answer_relevance": answer_relevance} if accuracy is not None: result["accuracy"] = accuracy if rouge_l is not None: result["rouge_l"] = rouge_l if groundedness is not None: result["groundedness"] = groundedness if faithfulness is not None: result["faithfulness"] = faithfulness if ctx_relevance is not None: result["ctx_relevance"] = ctx_relevance return result except Exception: return {} def metric_chips(m: dict, time_s: float = None) -> str: if not m and time_s is None: return "" parts = [] if time_s is not None: parts.append(f'โฑ {time_s}s') if m.get("accuracy") is not None: pct = round(m["accuracy"] * 100, 1) parts.append(f'๐ŸŽฏ Accuracy {pct}%') if m.get("groundedness") is not None: parts.append(f'โš“ Grounded {m["groundedness"]:.2f}') if m.get("ctx_relevance") is not None: parts.append(f'๐Ÿ“„ Ctx.Rel {m["ctx_relevance"]:.2f}') if m.get("rouge_l") is not None: parts.append(f'ROUGE-L {m["rouge_l"]:.3f}') return "".join(parts) # โ”€โ”€ Benchmark runner โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ import json as _json with open("data/reference_answers.json") as _f: BENCHMARK_QUESTIONS = list(_json.load(_f).keys()) def _estimate_cost(answer: str, system: str) -> float: """Rough per-query cost estimate in USD based on token count and system type.""" tokens = max(1, len(answer.split())) if system == "r1": # Baseline: Groq API call, ~800 input + output tokens return round(0.001 + tokens * 0.0000059, 4) elif system == "r2": # RAG: extra context tokens increase cost ~1.8x return round(0.0015 + tokens * 0.0000059 * 1.8, 4) else: # Fine-tuned: local inference, lower per-token cost return round(tokens * 0.0000015, 4) def run_benchmark(progress_bar=None): results = [] has_ft = os.path.exists("./checkpoint-25") or os.path.exists("./finetuned_model") if has_ft: from system3_inference import ask_finetuned total = len(BENCHMARK_QUESTIONS) for i, q in enumerate(BENCHMARK_QUESTIONS): if progress_bar: progress_bar.progress((i + 0.5) / total, text=f"({i+1}/{total}) {q[:50]}...") r1 = ask_baseline(q) r2 = ask_rag(q, vs) r3 = ask_finetuned(q) if has_ft else {"answer": "", "response_time": 0} ctx = r2.get("context_used", "") m1 = compute_auto_metrics(r1["answer"], q) m2 = compute_auto_metrics(r2["answer"], q, context=ctx) m3 = compute_auto_metrics(r3["answer"], q) results.append({ "question": q, "r1_time": r1["response_time"], "r2_time": r2["response_time"], "r3_time": r3["response_time"], "r1_rouge": m1.get("rouge_l", 0), "r2_rouge": m2.get("rouge_l", 0), "r3_rouge": m3.get("rouge_l", 0), "r1_sim": m1.get("accuracy", 0), "r2_sim": m2.get("accuracy", 0), "r3_sim": m3.get("accuracy", 0), "r1_ground": m1.get("groundedness", 0), "r2_ground": m2.get("groundedness", 0), "r3_ground": m3.get("groundedness", 0), "r1_relev": m1.get("answer_relevance", 0),"r2_relev": m2.get("answer_relevance", 0),"r3_relev": m3.get("answer_relevance", 0), "r1_faith": m1.get("faithfulness", 0), "r2_faith": m2.get("faithfulness", 0), "r3_faith": m3.get("faithfulness", 0), "r1_cost": _estimate_cost(r1["answer"], "r1"), "r2_cost": _estimate_cost(r2["answer"], "r2"), "r3_cost": _estimate_cost(r3["answer"], "r3"), }) return results def benchmark_kpis(results): n = len(results) if n == 0: return {} def mean(key): return round(sum(r.get(key, 0) for r in results) / n, 3) r1_acc = round(mean("r1_sim") * 100, 1) r2_acc = round(mean("r2_sim") * 100, 1) r3_acc = round(mean("r3_sim") * 100, 1) def halluc_rate(sim_key): flagged = sum(1 for r in results if 0 < r.get(sim_key, 0) < 0.5) return round(flagged / n * 100, 1) r1_hr = halluc_rate("r1_sim") r2_hr = halluc_rate("r2_sim") r3_hr = halluc_rate("r3_sim") r1_ground = mean("r1_ground") r2_ground = mean("r2_ground") r3_ground = mean("r3_ground") r1_relev = mean("r1_relev") r2_relev = mean("r2_relev") r3_relev = mean("r3_relev") r1_faith = mean("r1_faith") r2_faith = mean("r2_faith") r3_faith = mean("r3_faith") r1_cost = round(mean("r1_cost"), 4) r2_cost = round(mean("r2_cost"), 4) r3_cost = round(mean("r3_cost"), 4) def overall(acc_pct, ground, hr, relev, faith): score = ( acc_pct / 100 * 0.30 + ground * 0.20 + (1 - hr/100) * 0.20 + relev * 0.15 + faith * 0.15 ) return round(score * 5, 1) return { "r1_acc": r1_acc, "r2_acc": r2_acc, "r3_acc": r3_acc, "r1_rouge": mean("r1_rouge"), "r2_rouge": mean("r2_rouge"), "r3_rouge": mean("r3_rouge"), "r1_time": mean("r1_time"), "r2_time": mean("r2_time"), "r3_time": mean("r3_time"), "r1_ground": r1_ground, "r2_ground": r2_ground, "r3_ground": r3_ground, "r1_relev": r1_relev, "r2_relev": r2_relev, "r3_relev": r3_relev, "r1_faith": r1_faith, "r2_faith": r2_faith, "r3_faith": r3_faith, "r1_hr": r1_hr, "r2_hr": r2_hr, "r3_hr": r3_hr, "r1_cost": r1_cost, "r2_cost": r2_cost, "r3_cost": r3_cost, "r1_overall": overall(r1_acc, r1_ground, r1_hr, r1_relev, r1_faith), "r2_overall": overall(r2_acc, r2_ground, r2_hr, r2_relev, r2_faith), "r3_overall": overall(r3_acc, r3_ground, r3_hr, r3_relev, r3_faith), "n": n, } # โ”€โ”€ 3D Particle background โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ components.html(""" """, height=0) # โ”€โ”€ Input โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ st.divider() sample_questions = [ "What is binary search?", "Stack vs Queue?", "Explain merge sort.", "What are React hooks?", "What is a REST API?", "What is dynamic programming?", ] st.markdown('
Quick questions
', unsafe_allow_html=True) _sq_cols = st.columns(3) for i, sq in enumerate(sample_questions): if _sq_cols[i % 3].button(sq, key=f"sq_{i}", use_container_width=True): st.session_state.input_question = sq st.session_state.auto_run = True st.rerun() st.markdown("
", unsafe_allow_html=True) question = st.text_input( "Your question", placeholder="e.g. What is binary search? / What are React hooks? / Explain merge sort.", key="input_question" ) st.markdown("
", unsafe_allow_html=True) run_clicked = st.button("โšก Ask All 3 Systems", type="primary") # โ”€โ”€ Run systems โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ if (run_clicked or st.session_state.auto_run) and question: st.session_state.auto_run = False col1, col2, col3 = st.columns(3) with col1: with st.spinner("Baseline LLM generating..."): r1 = ask_baseline(question) with col2: with st.spinner("RAG retrieving + generating..."): r2 = ask_rag(question, vs) with col3: if os.path.exists("./checkpoint-25") or os.path.exists("./finetuned_model"): with st.spinner("Fine-tuned model generating (first run downloads base model ~3GB)..."): from system3_inference import ask_finetuned r3 = ask_finetuned(question) else: r3 = {"answer": "Model not available.", "response_time": 0} st.session_state.last_results = { "question": question, "r1": r1, "r2": r2, "r3": r3, } st.session_state.auto_metrics = { "r1": compute_auto_metrics(r1["answer"], question), "r2": compute_auto_metrics(r2["answer"], question, context=r2.get("context_used", "")), "r3": compute_auto_metrics(r3["answer"], question), } _accs = {k: st.session_state.auto_metrics[k].get("accuracy", 0) for k in ("r1","r2","r3")} st.session_state.winner = max(_accs, key=_accs.get) if any(_accs.values()) else None st.session_state.halluc_flags = {k: (0 < v < 0.4) for k, v in _accs.items()} # โ”€โ”€ Render answers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ if st.session_state.last_results: res = st.session_state.last_results r1, r2, r3 = res["r1"], res["r2"], res["r3"] st.divider() st.markdown(f"""
Showing answers for: "{res['question']}"
""", unsafe_allow_html=True) col1, col2, col3 = st.columns(3) m = st.session_state.auto_metrics _w = st.session_state.winner _hf = st.session_state.halluc_flags def _header_badge(key): if _w == key: return '๐Ÿ† Best Answer' if _hf.get(key): return 'โš ๏ธ Low Confidence' return "" with col1: st.markdown(f"""
โšก
System 1 โ€” Baseline LLM
Prompt only ยท No extra knowledge
{_header_badge('r1')}
{safe_answer(r1['answer'])}
{metric_chips(m.get('r1', {}), time_s=r1['response_time'])}
""", unsafe_allow_html=True) with col2: st.markdown(f"""
๐Ÿ”
System 2 โ€” RAG Chatbot
Retrieves from knowledge base ยท Then generates
{_header_badge('r2')}
{safe_answer(r2['answer'])}
๐Ÿ“„ Retrieved context
{html_lib.escape(r2['context_used'])}
{metric_chips(m.get('r2', {}), time_s=r2['response_time'])}
""", unsafe_allow_html=True) with col3: if r3["answer"] == "Model not available.": st.markdown("""
๐Ÿง 
System 3 โ€” Fine-Tuned Model
Qwen2.5-1.5B ยท LoRA fine-tuning
Model not loaded yet.
""", unsafe_allow_html=True) st.warning( "Fine-tuned model adapter (checkpoint-25/) not found in the project root." ) else: st.markdown(f"""
๐Ÿง 
System 3 โ€” Fine-Tuned Model
Qwen2.5-1.5B ยท LoRA on programming Q&A
{_header_badge('r3')}
{safe_answer(r3['answer'])}
{metric_chips(m.get('r3', {}), time_s=r3['response_time'])}
""", unsafe_allow_html=True) # โ”€โ”€ Analytics tab โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ st.divider() st.markdown("""
๐Ÿ“Š Analytics & Evaluation
""", unsafe_allow_html=True) # โ”€โ”€ Benchmark button + KPI cards โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ _bm_col, _ = st.columns([2, 5]) with _bm_col: if st.button("๐Ÿ”ฌ Run Auto-Benchmark", key="run_bm", help=f"Runs all {len(BENCHMARK_QUESTIONS)} questions through all 3 systems and computes metrics automatically"): _prog = st.progress(0, text="Starting benchmark...") st.session_state.benchmark_results = run_benchmark(progress_bar=_prog) with open("data/benchmark_cache.json", "w", encoding="utf-8") as _f: json.dump(st.session_state.benchmark_results, _f) _prog.progress(1.0, text="Done!") st.rerun() _dash = 'โ€”' _bm = st.session_state.benchmark_results def badge(val, cls): return f'{val}' # โ”€โ”€ Always compute KPI values (real data or zeros) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ _zero_kpi = {k: 0 for k in [ 'r1_acc','r2_acc','r3_acc','r1_ground','r2_ground','r3_ground', 'r1_time','r2_time','r3_time','r1_hr','r2_hr','r3_hr', 'r1_relev','r2_relev','r3_relev','r1_faith','r2_faith','r3_faith', 'r1_cost','r2_cost','r3_cost','r1_overall','r2_overall','r3_overall', 'r1_rouge','r2_rouge','r3_rouge', ]} _kpi = benchmark_kpis(_bm) if _bm else _zero_kpi _wins = {"r1": 0, "r2": 0, "r3": 0} for _row in _bm: _row_accs = {k: _row.get(f"{k}_sim", 0) for k in ("r1","r2","r3")} _best = max(_row_accs, key=_row_accs.get) if _row_accs[_best] > 0: _wins[_best] += 1 _n_q = len(_bm) _has_data = bool(_bm) _systems = ["Baseline LLM", "RAG Chatbot", "Fine-Tuned"] _colors = ["#3b82f6", "#10b981", "#8b5cf6"] _layout = dict( paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)", font=dict(color="#94a3b8", size=12), legend=dict(bgcolor="rgba(0,0,0,0)", bordercolor="#1e293b", borderwidth=1), xaxis=dict(gridcolor="#1a2540", linecolor="#1a2540"), yaxis=dict(gridcolor="#1a2540", linecolor="#1a2540"), margin=dict(l=20, r=20, t=48, b=20), ) # โ”€โ”€ KPI cards (always visible) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ _kpi_val = lambda v, fmt: fmt.format(v) if _has_data else "โ€”" st.markdown(f"""
โšก Baseline LLM
{_kpi_val(_kpi['r1_acc'], '{:.1f}%')}Accuracy
{_kpi_val(_kpi['r1_ground'], '{:.2f}')}Grounded
{_kpi_val(_kpi['r1_time'], '{:.2f}s')}Avg Latency
{f"{_wins['r1']}/{_n_q}" if _has_data else 'โ€”'}Wins
๐Ÿ” RAG Chatbot
{_kpi_val(_kpi['r2_acc'], '{:.1f}%')}Accuracy
{_kpi_val(_kpi['r2_ground'], '{:.2f}')}Grounded
{_kpi_val(_kpi['r2_time'], '{:.2f}s')}Avg Latency
{f"{_wins['r2']}/{_n_q}" if _has_data else 'โ€”'}Wins
๐Ÿง  Fine-Tuned
{_kpi_val(_kpi['r3_acc'], '{:.1f}%')}Accuracy
{_kpi_val(_kpi['r3_ground'], '{:.2f}')}Grounded
{_kpi_val(_kpi['r3_time'], '{:.2f}s')}Avg Latency
{f"{_wins['r3']}/{_n_q}" if _has_data else 'โ€”'}Wins
""", unsafe_allow_html=True) if _has_data: st.caption(f"Benchmark across {_n_q} questions ยท semantic similarity via embeddings ยท ROUGE-L vs reference answers") else: st.caption("Run the benchmark above to populate results.") # โ”€โ”€ Charts (always visible) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ _no_data_annotation = [] if _has_data else [dict( text="Run benchmark to see results", showarrow=False, xref="paper", yref="paper", x=0.5, y=0.5, font=dict(color="#475569", size=14), )] ch1, ch2 = st.columns(2) with ch1: fig1 = go.Figure() fig1.add_trace(go.Bar(name="Accuracy %", x=_systems, y=[_kpi['r1_acc'], _kpi['r2_acc'], _kpi['r3_acc']], marker_color=_colors, text=[f"{v}%" for v in [_kpi['r1_acc'],_kpi['r2_acc'],_kpi['r3_acc']]] if _has_data else ["","",""], textposition="auto")) fig1.add_trace(go.Bar(name="Groundedness ร—100", x=_systems, y=[round(_kpi['r1_ground']*100,1), round(_kpi['r2_ground']*100,1), round(_kpi['r3_ground']*100,1)], marker_color=_colors, opacity=0.55, text=[f"{round(v*100,1)}" for v in [_kpi['r1_ground'],_kpi['r2_ground'],_kpi['r3_ground']]] if _has_data else ["","",""], textposition="auto")) fig1.update_layout(**_layout, barmode="group", height=300, annotations=_no_data_annotation, title=dict(text="Accuracy % vs Groundedness Score (scaled ร—100)", font=dict(color="#e2e8f0", size=14))) st.plotly_chart(fig1, use_container_width=True) with ch2: fig2 = go.Figure() fig2.add_trace(go.Bar(name="Avg Latency (s)", x=_systems, y=[_kpi['r1_time'], _kpi['r2_time'], _kpi['r3_time']], marker_color=_colors, text=[f"{v}s" for v in [_kpi['r1_time'],_kpi['r2_time'],_kpi['r3_time']]] if _has_data else ["","",""], textposition="auto")) fig2.add_trace(go.Bar(name="Hallucination Rate %", x=_systems, y=[_kpi['r1_hr'], _kpi['r2_hr'], _kpi['r3_hr']], marker_color=_colors, opacity=0.55, text=[f"{v}%" for v in [_kpi['r1_hr'],_kpi['r2_hr'],_kpi['r3_hr']]] if _has_data else ["","",""], textposition="auto")) fig2.update_layout(**_layout, barmode="group", height=300, annotations=_no_data_annotation, title=dict(text="Avg Latency (s) ยท Hallucination Rate %", font=dict(color="#e2e8f0", size=14))) st.plotly_chart(fig2, use_container_width=True) # โ”€โ”€ Table row helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ def _g(v, good, mid): return "badge-green" if v >= good else ("badge-yellow" if v >= mid else "badge-red") def _l(v, good, mid): return "badge-green" if v <= good else ("badge-yellow" if v <= mid else "badge-red") def _row_cells(vals, fmt_fn, col_fn): if not _has_data: return (_dash, _dash, _dash) return tuple(badge(fmt_fn(v), col_fn(v)) for v in vals) _acc = (_kpi['r1_acc'], _kpi['r2_acc'], _kpi['r3_acc']) _grd = (_kpi['r1_ground'], _kpi['r2_ground'], _kpi['r3_ground']) _hr = (_kpi['r1_hr'], _kpi['r2_hr'], _kpi['r3_hr']) _rel = (_kpi['r1_relev'], _kpi['r2_relev'], _kpi['r3_relev']) _fth = (_kpi['r1_faith'], _kpi['r2_faith'], _kpi['r3_faith']) _tm = (_kpi['r1_time'], _kpi['r2_time'], _kpi['r3_time']) _cst = (_kpi['r1_cost'], _kpi['r2_cost'], _kpi['r3_cost']) _ov = (_kpi['r1_overall'], _kpi['r2_overall'], _kpi['r3_overall']) bm_acc = _row_cells(_acc, lambda v: f"{v}%", lambda v: _g(v, 75, 55)) bm_grd = _row_cells(_grd, lambda v: f"{v:.2f}", lambda v: _g(v, 0.75, 0.5)) bm_hr = _row_cells(_hr, lambda v: f"{v}%", lambda v: _l(v, 15, 30)) bm_rel = _row_cells(_rel, lambda v: f"{v:.2f}", lambda v: _g(v, 0.75, 0.55)) bm_fth = _row_cells(_fth, lambda v: f"{v:.2f}", lambda v: _g(v, 0.6, 0.35)) bm_tm = _row_cells(_tm, lambda v: f"{v}s", lambda v: _l(v, 1.0, 2.5)) bm_cst = _row_cells(_cst, lambda v: f"${v:.4f}", lambda v: _l(v, 0.001, 0.003)) bm_ov = _row_cells(_ov, lambda v: f"{v}/5", lambda v: _g(v, 4.0, 2.5)) def _best_high_badge(vals): labels = ["Baseline LLM", "RAG", "Fine-Tuned"] best = labels[list(vals).index(max(vals))] cls = "badge-green" if best == "RAG" else ("badge-yellow" if best == "Fine-Tuned" else "badge-grey") return f'{best}' def _best_low_badge(vals): labels = ["Baseline LLM", "RAG", "Fine-Tuned"] best = labels[list(vals).index(min(vals))] cls = "badge-green" if best == "Fine-Tuned" else ("badge-yellow" if best == "RAG" else "badge-grey") return f'{best}' _unit = lambda u: f'{u}' st.markdown(f"""
Metric โšก System 1
Baseline LLM
๐Ÿ” System 2
RAG Chatbot
๐Ÿง  System 3
Fine-Tuned
Best Unit
Answer Accuracy {bm_acc[0]}{bm_acc[1]}{bm_acc[2]} {_best_high_badge(_acc) if _bm else _dash}{_unit('%')}
Groundedness Score {bm_grd[0]}{bm_grd[1]}{bm_grd[2]} {_best_high_badge(_grd) if _bm else _dash}{_unit('0โ€“1')}
Hallucination Rate {bm_hr[0]}{bm_hr[1]}{bm_hr[2]} {_best_low_badge(_hr) if _bm else _dash}{_unit('%')}
Answer Relevance {bm_rel[0]}{bm_rel[1]}{bm_rel[2]} {_best_high_badge(_rel) if _bm else _dash}{_unit('0โ€“1')}
Faithfulness {bm_fth[0]}{bm_fth[1]}{bm_fth[2]} {_best_high_badge(_fth) if _bm else _dash}{_unit('0โ€“1')}
Avg Response Time {bm_tm[0]}{bm_tm[1]}{bm_tm[2]} {_best_low_badge(_tm) if _bm else _dash}{_unit('sec')}
Cost per Query {bm_cst[0]}{bm_cst[1]}{bm_cst[2]} {_best_low_badge(_cst) if _bm else _dash}{_unit('USD')}
Overall Score (1โ€“5) {bm_ov[0]}{bm_ov[1]}{bm_ov[2]} {_best_high_badge(_ov) if _bm else _dash}{_unit('rating')}
""", unsafe_allow_html=True) if _bm: st.markdown("
", unsafe_allow_html=True) if st.button("Clear Benchmark"): st.session_state.benchmark_results = [] st.rerun() # โ”€โ”€ About โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ st.divider() with st.expander("About CodeSage"): st.markdown(""" **Project:** CodeSage โ€” Comparative Study of RAG and Fine-Tuning for Domain-Specific QA **Domain:** Programming Tutor (DSA + Web Development) | System | Description | |--------|-------------| | Baseline | Llama 3.1 8B via Groq ยท system prompt only | | RAG | FAISS vector store + HuggingFace embeddings + Llama 3.1 8B via Groq | | Fine-tuned | Qwen2.5-1.5B-Instruct fine-tuned via LoRA on 20 programming Q&A pairs | Drop any PDF into `data/pdfs/` and delete `data/faiss_index/` to rebuild the RAG knowledge base with it included. """)