resulmamiyev commited on
Commit
95f6d5d
·
verified ·
1 Parent(s): 06f3435

Upload folder using huggingface_hub

Browse files
Files changed (7) hide show
  1. .DS_Store +0 -0
  2. .gitignore +6 -0
  3. .gradio/certificate.pem +31 -0
  4. README.md +3 -11
  5. app.py +474 -0
  6. gradio_app.py +189 -0
  7. prompts.py +108 -0
.DS_Store ADDED
Binary file (6.15 kB). View file
 
.gitignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ data/
2
+ env/
3
+ app.ipynb
4
+ test.md
5
+ __pycache__/
6
+ .env
.gradio/certificate.pem ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -----BEGIN CERTIFICATE-----
2
+ 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
31
+ -----END CERTIFICATE-----
README.md CHANGED
@@ -1,14 +1,6 @@
1
  ---
2
- title: Dahee AI
3
- emoji: 👁
4
- colorFrom: red
5
- colorTo: pink
6
  sdk: gradio
7
- sdk_version: 5.23.0
8
- app_file: app.py
9
- pinned: false
10
- license: apache-2.0
11
- short_description: Math Question Preparer
12
  ---
13
-
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: Dahee_AI
3
+ app_file: gradio_app.py
 
 
4
  sdk: gradio
5
+ sdk_version: 5.22.0
 
 
 
 
6
  ---
 
 
app.py ADDED
@@ -0,0 +1,474 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langgraph.graph import StateGraph, MessagesState, END, START
2
+ from langchain_openai import ChatOpenAI, OpenAIEmbeddings
3
+ from langchain_core.messages import SystemMessage
4
+ from langgraph.checkpoint.memory import MemorySaver
5
+ from langchain_community.document_loaders import WikipediaLoader
6
+ from langchain_experimental.utilities.python import PythonREPL
7
+
8
+ from pinecone import Pinecone
9
+
10
+ from typing import List, Annotated
11
+ from pydantic import BaseModel, Field
12
+ from IPython.display import Image, display
13
+ import operator
14
+ import prompts
15
+
16
+ # set environment variables
17
+ import os
18
+ from dotenv import load_dotenv
19
+
20
+ load_dotenv()
21
+
22
+ llm = ChatOpenAI(model="gpt-4o", temperature=0)
23
+ weak_llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.5)
24
+
25
+
26
+ class QuestionState(MessagesState):
27
+ topic: str # topic of the question
28
+ subtopic: str # subtopic of the question
29
+ difficulty: str # difficulty of the question
30
+ description: str # description of the subtopic
31
+ context: Annotated[list, operator.add] # knowledge base of the subtopic
32
+ relevant_questions: List[dict] # relevant questions
33
+ num_questions: int # number of relevant questions to extract
34
+ human_feedback: str # feedback from the human
35
+ question: str # question to ask
36
+ steps: List[str] # steps to solve the question
37
+ tool_requests: List[dict] # tool requests to solve the question
38
+ tool_results: List[dict] # tool results to solve the question
39
+ verified: bool # if the solution is verified
40
+ solution: str # solution to the question
41
+ answer: str # answer to the question
42
+
43
+
44
+ # -------------------------------
45
+ # Node 1: Generate Description Node
46
+ # -------------------------------
47
+ def generate_description(state: QuestionState):
48
+ """
49
+ Generate a description for the subtopic
50
+ """
51
+ topic = state["topic"]
52
+ subtopic = state["subtopic"]
53
+
54
+ # generate description
55
+ system_message = prompts.DESCRIPTION_INSTRUCTION.format(
56
+ topic=topic, subtopic=subtopic
57
+ )
58
+ description = weak_llm.invoke(
59
+ [SystemMessage(content=system_message)], max_tokens=30
60
+ ).content
61
+
62
+ # write description to state
63
+ return {"description": description}
64
+
65
+
66
+ # -------------------------------
67
+ # Node 2: Search Wikipedia Node
68
+ # -------------------------------
69
+ def search_wikipedia(state: QuestionState):
70
+ """
71
+ Search wikipedia for the topic and subtopic
72
+ """
73
+
74
+ subtopic = state["subtopic"]
75
+
76
+ search_query = f"What is {subtopic}"
77
+
78
+ # search wikipedia
79
+ search_docs = WikipediaLoader(
80
+ query=search_query, load_max_docs=1, doc_content_chars_max=1500
81
+ ).load()
82
+
83
+ # Format
84
+ formatted_search_docs = "\n\n---\n\n".join(
85
+ [
86
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
87
+ for doc in search_docs
88
+ ]
89
+ )
90
+
91
+ return {"context": [formatted_search_docs]}
92
+
93
+
94
+ # -------------------------------
95
+ # Node 3: Search Document Node
96
+ # -------------------------------
97
+ def search_document(state: QuestionState):
98
+ """
99
+ Search the document for relevant context
100
+ """
101
+
102
+ topic = state["topic"]
103
+ subtopic = state["subtopic"]
104
+
105
+ # Initialize OpenAI Embeddings client
106
+ client = OpenAIEmbeddings(model="text-embedding-3-large")
107
+
108
+ query = f"Search about {topic} in area of {subtopic}"
109
+ embedded_query = client.embed_query(query)
110
+
111
+ # Initialize Pinecone client
112
+ api_key = os.environ.get("PINECONE_API_KEY")
113
+ pc = Pinecone(api_key=api_key)
114
+
115
+ # 2. Vector DB query with metadata filter
116
+ index_name = os.environ.get("PINECONE_INDEX_NAME")
117
+ index = pc.Index(index_name)
118
+
119
+ filters = {
120
+ "topic": {"$eq": topic},
121
+ "subtopic": {"$eq": subtopic},
122
+ "type": {"$eq": "description"},
123
+ }
124
+
125
+ # Execute similarity search
126
+ try:
127
+ results = index.query(
128
+ vector=embedded_query,
129
+ filter=filters,
130
+ top_k=1, # Get top 5 similar questions
131
+ include_metadata=True,
132
+ )
133
+ except Exception as e:
134
+ raise ConnectionError(f"Vector DB query failed: {str(e)}")
135
+
136
+ # Get the context
137
+ if results and hasattr(results, "matches") and len(results.matches) > 0:
138
+ context = results.matches[0].metadata.get("context", "")
139
+ return {"context": [context]}
140
+ else:
141
+ return {"context": []}
142
+
143
+
144
+ # -------------------------------
145
+ # Node 4: Search Questions Node
146
+ # -------------------------------
147
+ def search_questions(state: QuestionState):
148
+ """
149
+ Search the document for relevant questions
150
+ """
151
+
152
+ topic = state["topic"]
153
+ subtopic = state["subtopic"]
154
+ num_questions = state["num_questions"]
155
+ difficulty = state["difficulty"]
156
+
157
+ # Initialize OpenAI Embeddings client
158
+ client = OpenAIEmbeddings(model="text-embedding-3-large")
159
+
160
+ query = f"Questions related to {topic} in area of {subtopic}"
161
+ embedded_query = client.embed_query(query)
162
+
163
+ # Initialize Pinecone client
164
+ api_key = os.environ.get("PINECONE_API_KEY")
165
+ pc = Pinecone(api_key=api_key)
166
+
167
+ # 2. Vector DB query with metadata filter
168
+ index_name = os.environ.get("PINECONE_INDEX_NAME")
169
+ index = pc.Index(index_name)
170
+
171
+ filters = {
172
+ "topic": {"$eq": topic},
173
+ "subtopic": {"$eq": subtopic},
174
+ "type": {"$eq": "question"},
175
+ "difficulty": {"$eq": difficulty},
176
+ }
177
+
178
+ # Execute similarity search
179
+ try:
180
+ results = index.query(
181
+ vector=embedded_query,
182
+ filter=filters,
183
+ top_k=num_questions,
184
+ include_metadata=True,
185
+ )
186
+ except Exception as e:
187
+ raise ConnectionError(f"Vector DB query failed: {str(e)}")
188
+
189
+ references = []
190
+ for match in results.matches:
191
+ metadata = match.metadata
192
+ references.append(
193
+ {
194
+ "question": metadata["question"],
195
+ "answer": metadata["answer"],
196
+ "difficulty": metadata["difficulty"],
197
+ }
198
+ )
199
+
200
+ return {"relevant_questions": references}
201
+
202
+
203
+ # -------------------------------
204
+ # Node 5: Generate Question Node
205
+ # -------------------------------
206
+ def generate_question(state: QuestionState):
207
+ """
208
+ Generate a question for the subtopic
209
+ """
210
+ topic = state["topic"]
211
+ subtopic = state["subtopic"]
212
+ difficulty = state["difficulty"]
213
+ context = state["context"]
214
+ relevant_questions = state["relevant_questions"]
215
+ human_feedback = state.get("human_feedback", "")
216
+
217
+ # generate question
218
+ query = prompts.QUESTION_INSTRUCTION.format(
219
+ topic=topic,
220
+ subtopic=subtopic,
221
+ difficulty=difficulty,
222
+ context=context,
223
+ relevant_questions=relevant_questions,
224
+ feedback=human_feedback,
225
+ )
226
+ question = llm.invoke([SystemMessage(content=query)], temperature=0.3).content
227
+
228
+ # Clean residual markdown formatting
229
+ question = question.strip().strip("`").replace("**Question:**", "").strip()
230
+
231
+ print("Generated Question: ", question)
232
+ # write question to state
233
+ return {"question": question}
234
+
235
+
236
+ # -------------------------------
237
+ # Node 6: Feedback Node
238
+ # -------------------------------
239
+ def human_feedback(state: QuestionState):
240
+ """No-op node that shoulds be interrupted on"""
241
+ print("Human Feedback Node: ", state)
242
+ pass
243
+
244
+
245
+ def should_continue(state: QuestionState):
246
+ """Return the next node to execute"""
247
+ print("Should Continue: ", state)
248
+ # Check if human feedback
249
+ human_feedback = state.get("human_feedback", None)
250
+ if human_feedback:
251
+ return "generate_question"
252
+
253
+ # Otherwise end
254
+ return "llm_step_planner"
255
+
256
+
257
+ # -------------------------------
258
+ # Node 7: LLM Step Planner
259
+ # -------------------------------
260
+ class SolutionPlan(BaseModel):
261
+ solution_steps: List[str] = Field(description="List of steps to solve the problem")
262
+
263
+
264
+ def llm_step_planner(state: QuestionState):
265
+ question = state["question"]
266
+ try:
267
+ prompt = prompts.STEP_INSTRUCTION.format(question=question)
268
+ structured_llm = llm.with_structured_output(SolutionPlan)
269
+ steps = structured_llm.invoke([SystemMessage(content=prompt)])
270
+ print("Steps", steps)
271
+
272
+ return {"steps": steps.solution_steps}
273
+
274
+ except Exception as e:
275
+ return {"error": f"LLM Parsing Error: {str(e)}"}
276
+
277
+
278
+ # -------------------------------
279
+ # Node 8: LLM Tool Decider
280
+ # -------------------------------
281
+ class ToolRequest(BaseModel):
282
+ code: str = Field(description="Python code to execute")
283
+ description: str = Field(description="Description of the code")
284
+
285
+
286
+ class ToolRequestList(BaseModel):
287
+ tool_requests: List[ToolRequest] = Field(description="List of tool requests")
288
+
289
+
290
+ def llm_tool_decider(state: QuestionState):
291
+ if "error" in state and state["error"]:
292
+ return state # Pass through error
293
+
294
+ try:
295
+ question = state["question"]
296
+ steps = state.get("steps", [])
297
+
298
+ prompt = prompts.TOOL_INSTRUCTION.format(question=question, steps=steps)
299
+
300
+ structured_llm = llm.with_structured_output(ToolRequestList)
301
+ tool_requests = structured_llm.invoke(
302
+ [SystemMessage(content=prompt)], max_tokens=500, temperature=0.2
303
+ )
304
+ print("Tool Requests", tool_requests)
305
+ return {
306
+ "tool_requests": [req.model_dump() for req in tool_requests.tool_requests]
307
+ }
308
+ except Exception as e:
309
+ return {"error": f"LLM Tool Decider Error: {str(e)}"}
310
+
311
+
312
+ # -------------------------------
313
+ # Node 9: LLM Tool Executor
314
+ # -------------------------------
315
+ code_executor = PythonREPL()
316
+
317
+
318
+ def tool_executor(state: QuestionState):
319
+ if "error" in state and state["error"]:
320
+ return state
321
+
322
+ try:
323
+ tool_results = []
324
+ for req in state.get("tool_requests", []):
325
+ print("Req", req)
326
+ if req.get("type", "sympy") == "sympy": # default to sympy
327
+ try:
328
+ output = code_executor.run(req["code"]) # Executes full code
329
+ tool_results.append(
330
+ {
331
+ "description": req.get("description", ""),
332
+ "result": output.strip(),
333
+ }
334
+ )
335
+ except Exception as e:
336
+ tool_results.append(
337
+ {
338
+ "description": req.get("description", ""),
339
+ "result": f"Execution Error: {str(e)}",
340
+ }
341
+ )
342
+ else:
343
+ tool_results.append(
344
+ {
345
+ "description": f"Unknown tool type: {req.get('type')}",
346
+ "result": None,
347
+ }
348
+ )
349
+ print("Tool Results", tool_results)
350
+ return {"tool_results": tool_results}
351
+ except Exception as e:
352
+ return {"error": f"Tool Execution Error: {str(e)}"}
353
+
354
+
355
+ # -------------------------------
356
+ # Node 10: LLM Verifier
357
+ # -------------------------------
358
+ class VerifierResponse(BaseModel):
359
+ verified: bool = Field(description="Whether the solution is verified")
360
+ explanation: str = Field(description="Explanation for verification decision")
361
+
362
+
363
+ def llm_verifier(state: QuestionState):
364
+ if "error" in state and state["error"]:
365
+ return state
366
+
367
+ try:
368
+ question = state["question"]
369
+ steps = state.get("steps", [])
370
+ tool_results = state.get("tool_results", [])
371
+
372
+ prompt = prompts.VERIFICATION_INSTRUCTION.format(
373
+ question=question, steps=steps, tool_results=tool_results
374
+ )
375
+
376
+ structured_llm = weak_llm.with_structured_output(VerifierResponse)
377
+ verification_results = structured_llm.invoke(
378
+ [SystemMessage(content=prompt)], max_tokens=500
379
+ ).model_dump()
380
+ result = False
381
+ if verification_results.get("verified", False):
382
+ result = True
383
+ else:
384
+ result = False
385
+ return {
386
+ "verified": result,
387
+ "error": (
388
+ None
389
+ if result
390
+ else f"Verification Failed: {verification_results.get('explanation', 'No explanation')}"
391
+ ),
392
+ }
393
+ except Exception as e:
394
+ return {"error": f"LLM Verifier Error: {str(e)}"}
395
+
396
+
397
+ # -------------------------------
398
+ # Node 11: LLM Finalizer
399
+ # -------------------------------
400
+ class FinalizerResponse(BaseModel):
401
+ solution: str = Field(description="Markdown solution")
402
+ answer: str = Field(description="Final answer")
403
+
404
+
405
+ def llm_finalizer(state: QuestionState):
406
+ if "error" in state and state["error"]:
407
+ state["solution"] = f"### Error\n{state['error']}"
408
+ state["answer"] = "N/A"
409
+ return state
410
+
411
+ try:
412
+ question = state["question"]
413
+ steps = state.get("steps", [])
414
+ tool_results = state.get("tool_results", [])
415
+ verified = state.get("verified", False)
416
+
417
+ prompt = prompts.FINALIZE_INSTRUCTION.format(
418
+ question=question,
419
+ steps=steps,
420
+ tool_results=tool_results,
421
+ verified=verified,
422
+ )
423
+ structured_llm = llm.with_structured_output(FinalizerResponse)
424
+ final_response = structured_llm.invoke(
425
+ [SystemMessage(content=prompt)], max_tokens=1000, temperature=0.2
426
+ )
427
+
428
+ return {"solution": final_response.solution, "answer": final_response.answer}
429
+ except Exception as e:
430
+ return {"solution": f"### Finalization Error\n{str(e)}", "answer": "N/A"}
431
+
432
+
433
+ # -------------------------------
434
+ # Graph Construction
435
+ # -------------------------------
436
+ builder = StateGraph(QuestionState)
437
+ builder.add_node("generate_description", generate_description)
438
+ # builder.add_node("search_wikipedia", search_wikipedia)
439
+ builder.add_node("search_document", search_document)
440
+ builder.add_node("search_questions", search_questions)
441
+ builder.add_node("generate_question", generate_question)
442
+ builder.add_node("feedback", human_feedback)
443
+ builder.add_node("llm_step_planner", llm_step_planner)
444
+ builder.add_node("llm_tool_decider", llm_tool_decider)
445
+ builder.add_node("tool_executor", tool_executor)
446
+ builder.add_node("llm_verifier", llm_verifier)
447
+ builder.add_node("llm_finalizer", llm_finalizer)
448
+
449
+ # Add edges
450
+ builder.add_edge(START, "generate_description")
451
+ # builder.add_edge("generate_description", "search_wikipedia")
452
+ builder.add_edge("generate_description", "search_document")
453
+ builder.add_edge("generate_description", "search_questions")
454
+ # builder.add_edge("search_wikipedia", "generate_question")
455
+ builder.add_edge("search_document", "generate_question")
456
+ builder.add_edge("search_questions", "generate_question")
457
+ builder.add_edge("generate_question", "feedback")
458
+ builder.add_conditional_edges(
459
+ "feedback", should_continue, ["generate_question", "llm_step_planner"]
460
+ )
461
+ # builder.add_edge("generate_question", "llm_step_planner")
462
+ builder.add_edge("llm_step_planner", "llm_tool_decider")
463
+ builder.add_edge("llm_tool_decider", "tool_executor")
464
+ builder.add_edge("tool_executor", "llm_verifier")
465
+ builder.add_edge("llm_verifier", "llm_finalizer")
466
+ builder.add_edge("llm_finalizer", END)
467
+
468
+ # Compile
469
+ memory = MemorySaver()
470
+ question_graph = builder.compile(interrupt_before=["feedback"], checkpointer=memory)
471
+ question_graph.name = "QuestionGenerationGraph"
472
+ # question_graph = builder.compile(checkpointer=memory)
473
+
474
+ # display(Image(question_graph.get_graph(xray=1).draw_mermaid_png()))
gradio_app.py ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from app import question_graph, MemorySaver
3
+ import uuid
4
+
5
+
6
+ # -----------------------------
7
+ # In-Memory Storage for Feedback Flow
8
+ # -----------------------------
9
+ session_data = {}
10
+
11
+
12
+ # -----------------------------
13
+ # Graph Invocation Function
14
+ # -----------------------------
15
+ def run_graph(topic, subtopic, difficulty):
16
+ session_id = str(uuid.uuid4()) # Unique session per question
17
+ memory = MemorySaver()
18
+
19
+ inputs = {
20
+ "topic": topic,
21
+ "subtopic": subtopic,
22
+ "difficulty": difficulty,
23
+ "description": "",
24
+ "num_questions": 3,
25
+ "messages": [],
26
+ "context": [],
27
+ "relevant_questions": [],
28
+ "question": "",
29
+ "steps": [],
30
+ "tool_requests": [],
31
+ "tool_results": [],
32
+ "verified": False,
33
+ "solution": "",
34
+ "answer": "",
35
+ "error": "",
36
+ "human_feedback": "",
37
+ }
38
+
39
+ # Run up to feedback node (interrupt point)
40
+ partial_result = question_graph.invoke(
41
+ inputs, config={"configurable": {"thread_id": session_id}}
42
+ )
43
+ session_data[session_id] = {
44
+ "memory": memory,
45
+ "inputs": partial_result, # Store intermediate state
46
+ }
47
+
48
+ generated_question = partial_result.get("question", "No question generated.")
49
+ return (
50
+ session_id,
51
+ generated_question,
52
+ "Enter feedback if needed, or click 'Solve Question'.",
53
+ )
54
+
55
+
56
+ # -----------------------------
57
+ # Handle Feedback and Rerun Graph
58
+ # -----------------------------
59
+ def handle_feedback(session_id, feedback_text):
60
+ data = session_data.get(session_id)
61
+ if not data:
62
+ return session_id, "Session expired or invalid.", ""
63
+
64
+ memory = data["memory"]
65
+ feedback_text = feedback_text.strip()
66
+ thread = {"configurable": {"thread_id": session_id}}
67
+
68
+ if feedback_text:
69
+ # Update state with feedback at node 'feedback'
70
+ question_graph.update_state(
71
+ thread, {"human_feedback": feedback_text}, as_node="feedback"
72
+ )
73
+
74
+ # Continue from feedback
75
+ final_result = question_graph.invoke(
76
+ None, config={"configurable": {"thread_id": session_id}}
77
+ )
78
+
79
+ new_question = final_result.get("question", "No question generated.")
80
+ session_data[session_id]["inputs"] = final_result
81
+
82
+ if final_result.get("steps"):
83
+ return session_id, new_question, "Proceeding to solve question..."
84
+ else:
85
+ return (
86
+ session_id,
87
+ new_question,
88
+ "Feedback applied. Refine again or click 'Solve Question'.",
89
+ )
90
+ else:
91
+ # No feedback provided, proceed immediately
92
+ question_graph.update_state(
93
+ thread,
94
+ {"human_feedback": None},
95
+ as_node="feedback",
96
+ )
97
+
98
+ final_result = question_graph.invoke(
99
+ None, config={"configurable": {"thread_id": session_id}}
100
+ )
101
+
102
+ solution_md = final_result.get("solution", "No solution generated.")
103
+ answer = final_result.get("answer", "No answer.")
104
+ return session_id, solution_md, answer
105
+
106
+
107
+ # -----------------------------
108
+ # Solve Question – Full Graph Run
109
+ # -----------------------------
110
+ def solve_question(session_id):
111
+ data = session_data.get(session_id)
112
+ thread = {"configurable": {"thread_id": session_id}}
113
+ if not data:
114
+ return "Session expired or invalid.", "", ""
115
+
116
+ state = data["inputs"]
117
+ memory = data["memory"]
118
+
119
+ question_graph.update_state(
120
+ thread,
121
+ {"human_feedback": None},
122
+ as_node="feedback",
123
+ )
124
+
125
+ final_result = question_graph.invoke(
126
+ None, config={"configurable": {"thread_id": session_id}}
127
+ )
128
+
129
+ print("Final Result", final_result)
130
+ solution_md = final_result.get("solution", "No solution generated.")
131
+ answer = final_result.get("answer", "No answer.")
132
+ return solution_md, answer
133
+
134
+
135
+ # -----------------------------
136
+ # Gradio UI Layout
137
+ # -----------------------------
138
+ with gr.Blocks(title="LangGraph Math Solver") as demo:
139
+ gr.Markdown(
140
+ "## 🧠 Dahee AI\nInput your topic and generate math questions, provide feedback, and get step-by-step solutions."
141
+ )
142
+
143
+ with gr.Row():
144
+ topic_input = gr.Textbox(label="Topic", placeholder="e.g., Combinatorics")
145
+ subtopic_input = gr.Textbox(
146
+ label="Subtopic", placeholder="e.g., Enumerative combinatorics"
147
+ )
148
+ difficulty_input = gr.Dropdown(
149
+ label="Difficulty", choices=["easy", "medium", "hard"], value="medium"
150
+ )
151
+
152
+ generate_btn = gr.Button("Generate Question")
153
+ question_output = gr.Markdown(label="Generated Question", render=True)
154
+ feedback_note = gr.Textbox(
155
+ label="Feedback (Optional)", placeholder="Enter feedback to refine question..."
156
+ )
157
+
158
+ with gr.Row():
159
+ feedback_btn = gr.Button("Submit Feedback")
160
+ solve_btn = gr.Button("Solve Question")
161
+
162
+ solution_output = gr.Markdown(label="Solution (Markdown)", render=True)
163
+ final_answer = gr.Markdown(label="Final Answer", render=True)
164
+
165
+ # Hidden session ID
166
+ session_state = gr.State()
167
+
168
+ # Events
169
+ generate_btn.click(
170
+ run_graph,
171
+ inputs=[topic_input, subtopic_input, difficulty_input],
172
+ outputs=[session_state, question_output, feedback_note],
173
+ )
174
+
175
+ feedback_btn.click(
176
+ handle_feedback,
177
+ inputs=[session_state, feedback_note],
178
+ outputs=[session_state, question_output, feedback_note],
179
+ )
180
+
181
+ solve_btn.click(
182
+ solve_question, inputs=[session_state], outputs=[solution_output, final_answer]
183
+ )
184
+
185
+ # -----------------------------
186
+ # Launch App
187
+ # -----------------------------
188
+ if __name__ == "__main__":
189
+ demo.launch()
prompts.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Default prompts"""
2
+
3
+ DESCRIPTION_INSTRUCTION = """Generate a short description for the topic {subtopic} in the area of {topic}.
4
+
5
+ Your goal is to generate a short, concise, well-structured description to the topic.
6
+
7
+ It will be used to used as query in retrieval and / or web-search
8
+
9
+ Keep the description concise and limited to one or two sentences.
10
+ """
11
+
12
+ QUESTION_INSTRUCTION = """As a teacher creating a quiz question on the topic of {subtopic} within the field of {topic}, your task is to analyze the context, review relevant questions, and craft a question that meets the following criteria:
13
+
14
+ Type: The question should be conceptual and/or technical question, not a factual question.
15
+ Relevance: The question must be related to the specified topic and subtopic.
16
+ Similarity: If relevant questions are provided, your question should be similar to them.
17
+ Difficulty: Ensure that the question aligns with the stated difficulty level and is of similar complexity to the provided questions.
18
+ Clarity: The question should be straightforward and concise.
19
+ Uniqueness: Your question should be distinctive and not a direct replica of the relevant questions, although you can modify parameters and numbers from them.
20
+
21
+ Examine any feedback provided and adjust your question accordingly.
22
+ {feedback}
23
+
24
+ List of relevant questions:
25
+ {relevant_questions}
26
+
27
+ Difficulty Level: {difficulty}
28
+
29
+ Format your question in markdown syntax (e.g., **bold**, `code`, or *italic* for emphasis).
30
+ **Do NOT include:**
31
+ - Backticks (```) or code blocks
32
+ - Prefixes like "Question:"
33
+ - Any extra text beyond the question itself.
34
+
35
+ Example of valid formatting:
36
+ What is value $x$ if $\sqrt{{3x-1}} + (1+x)^2 = 13$
37
+ """
38
+
39
+
40
+ STEP_INSTRUCTION = """
41
+ You are a helpful math tutor. Given the following math question, break it down into clear, logical steps needed to solve it.
42
+
43
+ Guidelines:
44
+ - Write each step as a **concise string** in a numbered list.
45
+ - If a step requires a **precise calculation** (e.g., solving an equation, evaluating an expression), end the step with: **(Calculation needed)**
46
+ - Do **not perform any calculations** or write code.
47
+
48
+ Example Output:
49
+ [
50
+ "Step 1: Step 1: Define variables",
51
+ "Step 2: Simplify the equation.",
52
+ "Step 3: Solve the simplified equation for x. (Calculation needed)",
53
+ "Step 4: Verify the solution."
54
+ ]
55
+
56
+ Question: {question}
57
+
58
+ Respond with a Python list of strings.
59
+ """
60
+
61
+
62
+ TOOL_INSTRUCTION = """
63
+ You are a math assistant. Given the problem and the step-by-step plan, review each step to determine if it needs an exact calculation.
64
+
65
+ For steps needing calculation:
66
+ - Generate Python code using **SymPy**.
67
+ - Always assign the final value to a variable named `result`.
68
+ - Always include **print(result)** at the end.
69
+ - Provide a clear description of what the code does.
70
+
71
+ Only generate code **aligned with the step** requiring it.
72
+
73
+ Problem: {question}
74
+ Steps: {steps}
75
+
76
+ Respond in JSON with key 'tool_requests' as a list of objects:
77
+ [
78
+ {{"code": "Python code here", "description": "What it calculates"}}
79
+ ]
80
+
81
+ If no step needs calculation, return an empty list.
82
+ """
83
+
84
+ VERIFICATION_INSTRUCTION = """
85
+ You are a math tutor. Review the following problem-solving steps and the results of calculations.
86
+
87
+ Question: {question}
88
+ Steps: {steps}
89
+ Tool Results: {tool_results}
90
+
91
+ Check if the steps and results are mathematically correct and consistent.
92
+ """
93
+
94
+
95
+ FINALIZE_INSTRUCTION = """
96
+ You are a math tutor. Given the problem, steps, and calculation results, write a clear and concise Markdown solution.
97
+
98
+ Include:
99
+ - Step-by-step solution
100
+ - Final answer (boxed or highlighted)
101
+
102
+ Question: {question}
103
+ Steps: {steps}
104
+ Tool Results: {tool_results}
105
+ Verified: {verified}
106
+
107
+ Respond in markdown format. ALWAYS write mathematical equations in between dollar signs (e.g., $x^2$).
108
+ """