Spaces:
Sleeping
Sleeping
File size: 7,341 Bytes
81726c9 529b177 81726c9 8d8e564 81726c9 8d8e564 b063f48 529b177 81726c9 8d8e564 529b177 8d8e564 81726c9 8d8e564 81726c9 529b177 81726c9 529b177 81726c9 8d8e564 81726c9 8d8e564 81726c9 8d8e564 529b177 8d8e564 529b177 8d8e564 529b177 81726c9 529b177 8d8e564 529b177 8d8e564 529b177 81726c9 8d8e564 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | # chain/qa_chain.py
import os
import re
from dotenv import load_dotenv
from openai import OpenAI
from rag.retriever import retrieve, format_context
load_dotenv()
# ββ Config βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
MODEL = "openai/gpt-oss-120b"
MAX_TOKENS = 2048
MAX_HISTORY = 10 # keep last 10 exchanges to avoid token overflow
# ββ OpenRouter Client βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
client = OpenAI(
api_key = OPENROUTER_API_KEY,
base_url = "https://openrouter.ai/api/v1"
)
# ββ System Prompt βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
SYSTEM_PROMPT = """You are Study Saathi β a friendly and smart study assistant.
You help students understand their Operating Systems notes.
Rules you must follow:
- Answer ONLY from the provided context or conversation history. Never use outside knowledge.
- If the answer is not in the context or history, say: "Yeh topic notes mein nahi mila."
_ Whenever you are asked to explain the topic it means you have to explain the topic in a simple way. You can use examples, analogies, and simple language to make it easy to understand. DONOT USE BULLET POINTS WHENEVER EXPLIANING THE TOPIC. USE PLAIN TEXT TO EXPLAIN THE TOPIC.
- You have access to the full conversation history. Use it to answer follow-up
questions like "translate the above", "explain more", "give examples of that", etc.
- Explain in simple Roman Urdu, Urdu, or English β based on what the user uses.
- Use markdown formatting: headings, bullet points, bold, tables where helpful.
- Keep explanations clear and student-friendly.
- For MCQs: generate exactly the number asked, only from the provided context.
- For MCQ answer keys: always return them in a markdown table.
"""
# ββ Follow-up detection βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
FOLLOWUP_PATTERNS = [
r'\babove\b', r'\bupar\b', r'\bwoh\b', r'\bise\b', r'\busse\b',
r'\btranslate\b', r'\btarjuma\b', r'\bsummari[sz]e\b',
r'\bexplain more\b', r'\baur explain\b', r'\baur batao\b',
r'\bexpand\b', r'\brepeat\b', r'\bdobara\b', r'\bphir se\b',
r'\bsimplify\b', r'\brewrite\b', r'\bconvert\b',
r'\bthis\b', r'\bthat\b', r'\byeh\b', r'\bwahi\b',
r'\bprevious\b', r'\blast\b',
]
def is_followup(query: str) -> bool:
"""Returns True if the query is a follow-up that doesn't need RAG."""
q = query.lower()
return any(re.search(p, q) for p in FOLLOWUP_PATTERNS)
# ββ Build Context Prompt ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def build_context_prompt(query: str, context: str) -> str:
return f"""Use the following context from the student's notes to help answer.
--- CONTEXT START ---
{context}
--- CONTEXT END ---
Student's Question: {query}
"""
def build_followup_prompt(query: str) -> str:
return f"""This is a follow-up question. Use the conversation history above to answer.
Do NOT search for new context β work only from what was already discussed.
Student's Follow-up: {query}
"""
# ββ Detect MCQ Request ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def extract_mcq_count(query: str):
match = re.search(r'(\d+)\s*(mcq|question|mcqs|questions)', query.lower())
return int(match.group(1)) if match else None
# ββ Trim history to avoid token overflow βββββββββββββββββββββββββββββββββββββ
def trim_history(history: list) -> list:
max_msgs = MAX_HISTORY * 2
if len(history) > max_msgs:
return history[-max_msgs:]
return history
# ββ Main Chain ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def run_chain(query: str, topic: str = None, history: list = []) -> str:
"""
Full RAG chain with conversation memory.
- Follow-up questions skip RAG and use history only.
- New questions retrieve chunks from Pinecone.
"""
mcq_count = extract_mcq_count(query)
followup = is_followup(query) and len(history) > 0
if followup:
# ββ Follow-up: skip RAG, use history only ββββββββββββββββββββββββββ
user_message = build_followup_prompt(query)
else:
# ββ New question: retrieve from Pinecone βββββββββββββββββββββββββββ
top_k = 10 if mcq_count else 5
chunks = retrieve(query, topic=topic, top_k=top_k)
if not chunks:
context = "No relevant context found in the notes."
else:
context = format_context(chunks)
user_message = build_context_prompt(query, context)
# inject MCQ instructions if needed
if mcq_count:
user_message += (
f"\n\nIMPORTANT: Generate exactly {mcq_count} MCQs from the context above. "
"Format each MCQ as:\n**Q1.** Question\n- A) option\n- B) option\n"
"- C) option\n- D) option\n\n"
"After all MCQs, provide the answer key in a markdown table "
"with columns: | Q# | Answer | Explanation |"
)
# ββ Build messages: system + trimmed history + current user msg ββββββββ
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
messages += trim_history(history[:-1]) # history minus the last user msg
messages.append({"role": "user", "content": user_message})
response = client.chat.completions.create(
model = MODEL,
messages = messages,
max_tokens = MAX_TOKENS,
)
return response.choices[0].message.content
# ββ Quick Test ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
if __name__ == "__main__":
q1 = "Explain Process Registers in simple words"
r1 = run_chain(q1, topic="ch-01-updated", history=[])
print("=== Response 1 ===\n", r1)
fake_history = [
{"role": "user", "content": q1},
{"role": "assistant", "content": r1},
{"role": "user", "content": "Translate the above into Roman Urdu"},
]
q2 = "Translate the above into Roman Urdu"
r2 = run_chain(q2, topic="ch-01-updated", history=fake_history)
print("\n=== Response 2 (follow-up) ===\n", r2) |