omarash2016 commited on
Commit
72ce905
Β·
verified Β·
1 Parent(s): 9c694b7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +57 -30
app.py CHANGED
@@ -1,5 +1,6 @@
1
  import os
2
  import sys
 
3
  import gradio as gr
4
  from langchain_openai import ChatOpenAI
5
  from langgraph.prebuilt import create_react_agent
@@ -9,6 +10,7 @@ from langchain_mcp_adapters.tools import load_mcp_tools
9
  from langchain_core.messages import HumanMessage, SystemMessage
10
 
11
  # --- Configuration ---
 
12
  NEBIUS_API_KEY = os.getenv("NEBIUS_API_KEY")
13
  NEBIUS_BASE_URL = "https://api.studio.nebius.ai/v1/"
14
 
@@ -21,29 +23,41 @@ AVAILABLE_MODELS = [
21
  # --- System Prompt ---
22
  SYSTEM_PROMPT = """You are a 'Vibe Coding' Python Tutor.
23
  Your goal is to teach by DOING and then providing resources.
 
24
  BEHAVIOR GUIDELINES:
25
  1. **Greetings & Small Talk**: If the user says "hello", "hi", or asks non-coding questions, respond conversationally and politely. Ask them what they want to learn today.
26
  - DO NOT generate the lesson structure, files, or resources for simple greetings.
 
27
  2. **Teaching Mode**: ONLY when the user asks a coding question or requests a topic (e.g., "dictionaries", "how do loops work"):
28
  - **The Lesson**: Explain the concept clearly.
29
  - **The Code**: ALWAYS create a Python file, run it, and show the output using tools ('write_file', 'run_python_script').
30
  - **The Context**: Use 'list_directory' to see the student's workspace.
31
- CRITICAL: When in "Teaching Mode", you must end your response with these exact separators:
 
 
 
 
 
 
32
  ---SECTION: VIDEOS---
33
  (List 2-3 YouTube search queries or URLs relevant to the topic)
 
34
  ---SECTION: ARTICLES---
35
  (List 2-3 documentation links or course names, e.g., RealPython, FreeCodeCamp)
 
36
  ---SECTION: QUIZ---
37
  (Create 2 short multiple-choice question. Use HTML <details> and <summary> tags to hide the answer. Example:
38
  **Question**: ...
39
  - A) ...
40
  - B) ...
41
-
42
  <details><summary>πŸ‘€ Reveal Answers</summary>Correct is A because...</details>)
43
  """
44
 
45
  def parse_agent_response(full_text):
46
- """Splits the single LLM response into 4 UI components."""
 
 
 
47
  chat_content = full_text
48
 
49
  # Default values for "waiting" state
@@ -51,36 +65,57 @@ def parse_agent_response(full_text):
51
  articles = "### πŸ“š Articles & Courses\n*Ask a coding question to get resources!*"
52
  quiz = "### 🧠 Quick Quiz\n*Ask a coding question to take a quiz!*"
53
 
 
 
 
 
 
 
54
  try:
55
- # Only try to split if the separators exist (i.e., we are in Teaching Mode)
56
- if "---SECTION: VIDEOS---" in full_text:
57
- parts = full_text.split("---SECTION: VIDEOS---")
58
- chat_content = parts[0].strip()
59
- remainder = parts[1]
 
 
 
 
 
60
 
61
- if "---SECTION: ARTICLES---" in remainder:
62
- v_parts = remainder.split("---SECTION: ARTICLES---")
63
- # Add headers back for the UI
64
- videos = f"### πŸ“Ί Recommended Videos\n{v_parts[0].strip()}"
65
- remainder = v_parts[1]
 
 
 
 
 
 
66
 
67
- if "---SECTION: QUIZ---" in remainder:
68
- a_parts = remainder.split("---SECTION: QUIZ---")
69
- articles = f"### πŸ“š Articles & Courses\n{a_parts[0].strip()}"
70
- quiz = f"### 🧠 Quick Quiz\n{a_parts[1].strip()}"
71
- else:
72
- articles = f"### πŸ“š Articles & Courses\n{remainder.strip()}"
73
- else:
74
- videos = f"### πŸ“Ί Recommended Videos\n{remainder.strip()}"
 
 
75
  except Exception as e:
76
  print(f"Parsing error: {e}")
 
 
77
 
78
  return chat_content, videos, articles, quiz
79
 
80
  async def run_tutor_dashboard(user_message, model_id):
81
  """
82
  Main function to run the agent loop.
83
- Now accepts 'model_id' to select the LLM dynamically.
84
  """
85
  server_params = StdioServerParameters(
86
  command=sys.executable,
@@ -103,7 +138,6 @@ async def run_tutor_dashboard(user_message, model_id):
103
 
104
  agent_executor = create_react_agent(llm, tools)
105
 
106
- # Prepend the SystemMessage to ensure the agent follows instructions
107
  inputs = {
108
  "messages": [
109
  SystemMessage(content=SYSTEM_PROMPT),
@@ -116,7 +150,6 @@ async def run_tutor_dashboard(user_message, model_id):
116
  return parse_agent_response(final_text)
117
 
118
  # --- Gradio Dashboard UI ---
119
- # Professional "Slate" theme
120
  theme = gr.themes.Soft(
121
  primary_hue="slate",
122
  secondary_hue="indigo",
@@ -133,11 +166,9 @@ theme = gr.themes.Soft(
133
  with gr.Blocks(title="AI Python Tutor", theme=theme, fill_height=True) as demo:
134
  # --- Header with Model Selector ---
135
  with gr.Row(variant="compact", elem_classes="header-row"):
136
- # scale=1: Consumes all available space, pushing the next column to the right
137
  with gr.Column(scale=1):
138
  gr.Markdown("## 🐍 AI Python Tutor")
139
 
140
- # scale=0: Takes only minimum required width, sitting on the far right
141
  with gr.Column(scale=0, min_width=250):
142
  model_selector = gr.Dropdown(
143
  choices=AVAILABLE_MODELS,
@@ -151,7 +182,6 @@ with gr.Blocks(title="AI Python Tutor", theme=theme, fill_height=True) as demo:
151
  with gr.Row(equal_height=True):
152
  # Left Column: Chat & Input
153
  with gr.Column(scale=3, variant="panel"):
154
- # Custom Header with Focus Mode Button
155
  with gr.Row():
156
  gr.Markdown("### πŸ’¬ Interactive Session")
157
  fullscreen_btn = gr.Button("β›Ά Focus Mode", size="sm", variant="secondary", scale=0, min_width=120)
@@ -191,7 +221,6 @@ with gr.Blocks(title="AI Python Tutor", theme=theme, fill_height=True) as demo:
191
  with gr.Column(scale=2) as right_col:
192
  gr.Markdown("### πŸŽ’ Learning Dashboard")
193
 
194
- # Tabbed interface for cleaner look
195
  with gr.Tabs():
196
  with gr.TabItem("πŸ“Ί Videos"):
197
  video_box_side = gr.Markdown(value="### Recommended Videos\n*Ask a topic to see video suggestions!*")
@@ -265,14 +294,12 @@ with gr.Blocks(title="AI Python Tutor", theme=theme, fill_height=True) as demo:
265
  video_box_bottom, article_box_bottom, quiz_box_bottom
266
  ]
267
 
268
- # Added model_selector to inputs
269
  submit_btn.click(
270
  respond,
271
  [msg, chatbot, model_selector],
272
  outputs_list
273
  )
274
 
275
- # Added model_selector to inputs
276
  msg.submit(
277
  respond,
278
  [msg, chatbot, model_selector],
 
1
  import os
2
  import sys
3
+ import re
4
  import gradio as gr
5
  from langchain_openai import ChatOpenAI
6
  from langgraph.prebuilt import create_react_agent
 
10
  from langchain_core.messages import HumanMessage, SystemMessage
11
 
12
  # --- Configuration ---
13
+ # Ensure your API key is set in your environment variables
14
  NEBIUS_API_KEY = os.getenv("NEBIUS_API_KEY")
15
  NEBIUS_BASE_URL = "https://api.studio.nebius.ai/v1/"
16
 
 
23
  # --- System Prompt ---
24
  SYSTEM_PROMPT = """You are a 'Vibe Coding' Python Tutor.
25
  Your goal is to teach by DOING and then providing resources.
26
+
27
  BEHAVIOR GUIDELINES:
28
  1. **Greetings & Small Talk**: If the user says "hello", "hi", or asks non-coding questions, respond conversationally and politely. Ask them what they want to learn today.
29
  - DO NOT generate the lesson structure, files, or resources for simple greetings.
30
+
31
  2. **Teaching Mode**: ONLY when the user asks a coding question or requests a topic (e.g., "dictionaries", "how do loops work"):
32
  - **The Lesson**: Explain the concept clearly.
33
  - **The Code**: ALWAYS create a Python file, run it, and show the output using tools ('write_file', 'run_python_script').
34
  - **The Context**: Use 'list_directory' to see the student's workspace.
35
+
36
+ CRITICAL FORMATTING INSTRUCTIONS:
37
+ When in "Teaching Mode", you MUST end your response by strictly following this format.
38
+ Do not add extra text between the sections.
39
+
40
+ (End of your main lesson text)
41
+
42
  ---SECTION: VIDEOS---
43
  (List 2-3 YouTube search queries or URLs relevant to the topic)
44
+
45
  ---SECTION: ARTICLES---
46
  (List 2-3 documentation links or course names, e.g., RealPython, FreeCodeCamp)
47
+
48
  ---SECTION: QUIZ---
49
  (Create 2 short multiple-choice question. Use HTML <details> and <summary> tags to hide the answer. Example:
50
  **Question**: ...
51
  - A) ...
52
  - B) ...
 
53
  <details><summary>πŸ‘€ Reveal Answers</summary>Correct is A because...</details>)
54
  """
55
 
56
  def parse_agent_response(full_text):
57
+ """
58
+ Robust parsing using Regex to handle LLM formatting inconsistencies.
59
+ Splits the single LLM response into 4 UI components: Chat, Videos, Articles, Quiz.
60
+ """
61
  chat_content = full_text
62
 
63
  # Default values for "waiting" state
 
65
  articles = "### πŸ“š Articles & Courses\n*Ask a coding question to get resources!*"
66
  quiz = "### 🧠 Quick Quiz\n*Ask a coding question to take a quiz!*"
67
 
68
+ # Regex patterns that allow for slight variations in spacing or casing
69
+ # Examples caught: "---SECTION: VIDEOS---", "---SECTION:VIDEOS ---", "--- section: videos ---"
70
+ video_pattern = r"---SECTION:\s*VIDEOS\s*---"
71
+ article_pattern = r"---SECTION:\s*ARTICLES\s*---"
72
+ quiz_pattern = r"---SECTION:\s*QUIZ\s*---"
73
+
74
  try:
75
+ # 1. Check if we are in Teaching Mode (look for Video section)
76
+ # re.split returns a list. If found, index 0 is before the split, index 1 is after.
77
+ split_video = re.split(video_pattern, full_text, flags=re.IGNORECASE, maxsplit=1)
78
+
79
+ if len(split_video) > 1:
80
+ chat_content = split_video[0].strip()
81
+ remaining_resources = split_video[1]
82
+
83
+ # 2. Extract Videos vs Articles
84
+ split_article = re.split(article_pattern, remaining_resources, flags=re.IGNORECASE, maxsplit=1)
85
 
86
+ if len(split_article) > 0:
87
+ # Everything before ARTICLE tag is Video content
88
+ video_content = split_article[0].strip()
89
+ if video_content:
90
+ videos = f"### πŸ“Ί Recommended Videos\n{video_content}"
91
+
92
+ if len(split_article) > 1:
93
+ remaining_quiz = split_article[1]
94
+
95
+ # 3. Extract Articles vs Quiz
96
+ split_quiz = re.split(quiz_pattern, remaining_quiz, flags=re.IGNORECASE, maxsplit=1)
97
 
98
+ if len(split_quiz) > 0:
99
+ article_content = split_quiz[0].strip()
100
+ if article_content:
101
+ articles = f"### πŸ“š Articles & Courses\n{article_content}"
102
+
103
+ if len(split_quiz) > 1:
104
+ quiz_content = split_quiz[1].strip()
105
+ if quiz_content:
106
+ quiz = f"### 🧠 Quick Quiz\n{quiz_content}"
107
+
108
  except Exception as e:
109
  print(f"Parsing error: {e}")
110
+ # Fallback: Just show everything in chat if parsing fails completely, so no data is lost
111
+ chat_content = full_text
112
 
113
  return chat_content, videos, articles, quiz
114
 
115
  async def run_tutor_dashboard(user_message, model_id):
116
  """
117
  Main function to run the agent loop.
118
+ Accepts 'model_id' to select the LLM dynamically.
119
  """
120
  server_params = StdioServerParameters(
121
  command=sys.executable,
 
138
 
139
  agent_executor = create_react_agent(llm, tools)
140
 
 
141
  inputs = {
142
  "messages": [
143
  SystemMessage(content=SYSTEM_PROMPT),
 
150
  return parse_agent_response(final_text)
151
 
152
  # --- Gradio Dashboard UI ---
 
153
  theme = gr.themes.Soft(
154
  primary_hue="slate",
155
  secondary_hue="indigo",
 
166
  with gr.Blocks(title="AI Python Tutor", theme=theme, fill_height=True) as demo:
167
  # --- Header with Model Selector ---
168
  with gr.Row(variant="compact", elem_classes="header-row"):
 
169
  with gr.Column(scale=1):
170
  gr.Markdown("## 🐍 AI Python Tutor")
171
 
 
172
  with gr.Column(scale=0, min_width=250):
173
  model_selector = gr.Dropdown(
174
  choices=AVAILABLE_MODELS,
 
182
  with gr.Row(equal_height=True):
183
  # Left Column: Chat & Input
184
  with gr.Column(scale=3, variant="panel"):
 
185
  with gr.Row():
186
  gr.Markdown("### πŸ’¬ Interactive Session")
187
  fullscreen_btn = gr.Button("β›Ά Focus Mode", size="sm", variant="secondary", scale=0, min_width=120)
 
221
  with gr.Column(scale=2) as right_col:
222
  gr.Markdown("### πŸŽ’ Learning Dashboard")
223
 
 
224
  with gr.Tabs():
225
  with gr.TabItem("πŸ“Ί Videos"):
226
  video_box_side = gr.Markdown(value="### Recommended Videos\n*Ask a topic to see video suggestions!*")
 
294
  video_box_bottom, article_box_bottom, quiz_box_bottom
295
  ]
296
 
 
297
  submit_btn.click(
298
  respond,
299
  [msg, chatbot, model_selector],
300
  outputs_list
301
  )
302
 
 
303
  msg.submit(
304
  respond,
305
  [msg, chatbot, model_selector],