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