YousifCreates commited on
Commit
8d8e564
Β·
1 Parent(s): 529b177

updated chain

Browse files
Files changed (1) hide show
  1. 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 (20 messages) to avoid token overflow
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 the question.
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
- 1. Retrieve chunks from Pinecone
70
- 2. Format context
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 = extract_mcq_count(query)
 
76
 
77
- # fetch more chunks for MCQ generation
78
- top_k = 10 if mcq_count else 5
79
- chunks = retrieve(query, topic=topic, top_k=top_k)
80
-
81
- if not chunks:
82
- context = "No relevant context found."
83
  else:
84
- context = format_context(chunks)
85
-
86
- # build the new user message with context injected
87
- user_message = build_context_prompt(query, context)
88
-
89
- # inject MCQ instructions if needed
90
- if mcq_count:
91
- user_message += f"\n\nIMPORTANT: Generate exactly {mcq_count} MCQs from the context above. \
92
- Format each MCQ as:\n**Q1.** Question\n- A) option\n- B) option\n- C) option\n- D) option\
93
- \n\nAfter all MCQs, provide the answer key in a markdown table with columns: | Q# | Answer | Explanation |"
94
-
95
- # build full messages list: system + history + current user message
 
 
 
 
 
 
 
 
 
 
96
  messages = [{"role": "system", "content": SYSTEM_PROMPT}]
97
- messages += trim_history(history[:-1]) # history WITHOUT the last user msg (we already have it)
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 = "Ab upar wali cheez ko Roman Urdu mein translate karo"
123
- r2 = run_chain(q2, topic="ch-01-updated", history=fake_history + [{"role": "user", "content": q2}])
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)