jdesiree commited on
Commit
3a3b324
·
verified ·
1 Parent(s): 82df420

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +146 -181
app.py CHANGED
@@ -8,6 +8,15 @@ import json
8
  import re
9
  import requests
10
 
 
 
 
 
 
 
 
 
 
11
  # --- Environment and Logging Setup ---
12
  logging.basicConfig(level=logging.INFO)
13
  logger = logging.getLogger(__name__)
@@ -17,55 +26,77 @@ hf_token = os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACEHUB_API_TOKE
17
  if not hf_token:
18
  logger.warning("Neither HF_TOKEN nor HUGGINGFACEHUB_API_TOKEN is set, the application may not work.")
19
 
20
- # --- HF API Configuration ---
21
- HF_API_URL = "https://api-inference.huggingface.co/models/Qwen/Qwen2.5-VL-7B-Instruct"
22
- HF_HEADERS = {"Authorization": f"Bearer {hf_token}"}
23
-
24
  metrics_tracker = EduBotMetrics(save_file="edu_metrics.json")
25
 
26
- # --- Tools ---
27
- tools = [
28
- {
29
- "type": "function",
30
- "function": {
31
- "name": "create_graph",
32
- "description": "Generates a plot (bar, line, or pie) and returns it as an HTML-formatted Base64-encoded image string. Use this tool when teaching concepts that benefit from visual representation, such as: statistical distributions, mathematical functions, data comparisons, survey results, grade analyses, scientific relationships, economic models, or any quantitative information that would be clearer with a graph. The data and labels arguments must be JSON-encoded strings.",
33
- "parameters": {
34
- "type": "object",
35
- "properties": {
36
- "data_json": {"type": "string"},
37
- "labels_json": {"type": "string"},
38
- "plot_type": {"type": "string", "enum": ["bar", "line", "pie"]},
39
- "title": {"type": "string"},
40
- "x_label": {"type": "string"},
41
- "y_label": {"type": "string"},
42
- },
43
- "required": ["data_json", "labels_json", "plot_type", "title"],
44
- },
45
- }
46
- }
47
- ]
48
-
49
- # --- UI: MathJax Configuration ---
50
- mathjax_config = '''
51
- <script>
52
- window.MathJax = {
53
- tex: {
54
- inlineMath: [['
55
 
56
- # --- Main Execution ---
57
- if __name__ == "__main__":
58
- try:
59
- logger.info("Starting EduBot...")
60
- demo = create_interface()
61
- demo.queue()
62
- demo.launch()
63
- except Exception as e:
64
- logger.error(f"Failed to launch EduBot: {e}")
65
- raise, '''
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
 
67
- # --- LLM Templates ---
68
- SYSTEM_MESSAGE = """You are EduBot, an expert multi-concept tutor designed to facilitate genuine learning and understanding. Your primary mission is to guide students through the learning process rather than providing direct answers to academic work.
69
 
70
  ## Core Educational Principles
71
  - Provide comprehensive, educational responses that help students truly understand concepts
@@ -106,15 +137,6 @@ When using the create_graph tool, format data as JSON strings:
106
  - data_json: '{"Category1": 25, "Category2": 40, "Category3": 35}'
107
  - labels_json: '["Category1", "Category2", "Category3"]'
108
 
109
- ## Function Calling Format
110
- When you need to create a graph, use this exact format:
111
-
112
- <function_call>
113
- {"name": "create_graph", "arguments": {"data_json": "{\"key1\": value1, \"key2\": value2}", "labels_json": "[\"label1\", \"label2\"]", "plot_type": "bar|line|pie", "title": "Graph Title", "x_label": "X Axis Label", "y_label": "Y Axis Label"}}
114
- </function_call>
115
-
116
- The graph will be automatically generated and displayed in your response.
117
-
118
  ## Response Guidelines
119
  - **For math problems**: Explain concepts, provide formula derivations, and guide through problem-solving steps without computing final numerical answers
120
  - **For multiple-choice questions**: Discuss the concepts being tested and help students understand how to analyze options rather than identifying the correct choice
@@ -130,45 +152,52 @@ The graph will be automatically generated and displayed in your response.
130
  - Encourage students to explain their thinking and reasoning
131
  - Provide honest, accurate feedback even when it may not be what the student wants to hear
132
 
133
- Your goal is to be an educational partner who empowers students to succeed through understanding, not a service that completes their work for them."""
134
 
135
- # --- Core Logic Functions ---
136
 
137
- def execute_function_call(function_name, function_args):
138
- """Execute the called function and return the result."""
139
- if function_name == "create_graph":
140
- try:
141
- return generate_plot(
142
- data_json=function_args.get("data_json", "{}"),
143
- labels_json=function_args.get("labels_json", "[]"),
144
- plot_type=function_args.get("plot_type", "bar"),
145
- title=function_args.get("title", "Graph"),
146
- x_label=function_args.get("x_label", ""),
147
- y_label=function_args.get("y_label", "")
148
- )
149
- except Exception as e:
150
- return f"<p style='color:red;'>Error creating graph: {str(e)}</p>"
151
- else:
152
- return f"<p style='color:red;'>Unknown function: {function_name}</p>"
153
 
154
- def parse_function_calls(text):
155
- """Parse function calls from model response."""
156
- function_calls = []
157
-
158
- # Look for function call patterns in the text
159
- function_pattern = r'<function_call>(.*?)</function_call>'
160
- matches = re.findall(function_pattern, text, re.DOTALL)
161
-
162
- for match in matches:
163
- try:
164
- # Parse the function call JSON
165
- call_data = json.loads(match.strip())
166
- function_calls.append(call_data)
167
- except json.JSONDecodeError:
168
- continue
169
-
170
- return function_calls
171
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  def smart_truncate(text, max_length=3000):
173
  """Truncates text intelligently to the last full sentence or word."""
174
  if len(text) <= max_length:
@@ -182,81 +211,29 @@ def smart_truncate(text, max_length=3000):
182
  words = text[:max_length].split()
183
  return ' '.join(words[:-1]) + "... [Response truncated]"
184
 
185
- def generate_response_with_tools(messages, max_retries=3):
186
- """Generate response with function calling capability."""
187
-
188
- # Format messages for the API
189
- formatted_messages = []
190
- formatted_messages.append(f"System: {SYSTEM_MESSAGE}")
191
-
192
- for msg in messages:
193
- if msg["role"] == "user":
194
- formatted_messages.append(f"User: {msg['content']}")
195
- elif msg["role"] == "assistant":
196
- formatted_messages.append(f"Assistant: {msg['content']}")
197
-
198
- conversation = "\n\n".join(formatted_messages)
199
- conversation += "\n\nAssistant: "
200
-
201
- payload = {
202
- "inputs": conversation,
203
- "parameters": {
204
- "max_new_tokens": 1000,
205
- "temperature": 0.7,
206
- "top_p": 0.9,
207
- "return_full_text": False
208
- }
209
- }
210
 
211
  for attempt in range(max_retries):
212
  try:
213
- response = requests.post(HF_API_URL, headers=HF_HEADERS, json=payload, timeout=30)
 
214
 
215
- if response.status_code == 503:
216
- # Model is loading
217
- if attempt < max_retries - 1:
218
- wait_time = 10 + (attempt * 5)
219
- logger.info(f"Model loading, waiting {wait_time} seconds...")
220
- time.sleep(wait_time)
221
- continue
222
- else:
223
- return "The model is currently loading. Please try again in a few moments."
224
 
225
- response.raise_for_status()
226
- result = response.json()
 
 
227
 
228
- if isinstance(result, list) and len(result) > 0:
229
- raw_response = result[0].get('generated_text', '').strip()
230
-
231
- # Process function calls
232
- function_calls = parse_function_calls(raw_response)
233
-
234
- for call in function_calls:
235
- if call.get("name") == "create_graph":
236
- # Execute the function call
237
- html_result = execute_function_call("create_graph", call.get("arguments", {}))
238
- # Replace the function call with the generated HTML
239
- pattern = r'<function_call>.*?</function_call>'
240
- raw_response = re.sub(pattern, html_result, raw_response, count=1, flags=re.DOTALL)
241
-
242
- return smart_truncate(raw_response)
243
- else:
244
- return "I apologize, but I received an unexpected response format. Please try again."
245
-
246
- except requests.exceptions.RequestException as e:
247
- logger.error(f"Request failed (attempt {attempt + 1}): {e}")
248
- if attempt < max_retries - 1:
249
- time.sleep(2)
250
- continue
251
- else:
252
- return f"I'm having trouble connecting right now. Please try again later. (Error: {e})"
253
  except Exception as e:
254
- logger.error(f"Unexpected error (attempt {attempt + 1}): {e}")
255
  if attempt < max_retries - 1:
256
  time.sleep(2)
257
  continue
258
  else:
259
- return f"An unexpected error occurred: {e}"
260
 
261
  def chat_response(message, history):
262
  """Process chat message and return response."""
@@ -264,16 +241,8 @@ def chat_response(message, history):
264
  # Track metrics
265
  metrics_tracker.log_interaction(message, "user_query")
266
 
267
- # Format conversation history
268
- messages = []
269
- for user_msg, bot_msg in history:
270
- messages.append({"role": "user", "content": user_msg})
271
- if bot_msg:
272
- messages.append({"role": "assistant", "content": bot_msg})
273
- messages.append({"role": "user", "content": message})
274
-
275
- # Generate response with tool support
276
- response = generate_response_with_tools(messages)
277
 
278
  # Log metrics
279
  metrics_tracker.log_interaction(response, "bot_response")
@@ -284,6 +253,15 @@ def chat_response(message, history):
284
  logger.error(f"Error in chat_response: {e}")
285
  return f"I apologize, but I encountered an error while processing your message: {str(e)}"
286
 
 
 
 
 
 
 
 
 
 
287
  # --- UI: Event Handlers ---
288
  def respond_and_update(message, history):
289
  """Main function to handle user submission."""
@@ -308,6 +286,10 @@ def respond_and_update(message, history):
308
 
309
  def clear_chat():
310
  """Clear the chat history."""
 
 
 
 
311
  return [], ""
312
 
313
  # --- UI: Interface Creation ---
@@ -386,21 +368,4 @@ if __name__ == "__main__":
386
  interface.launch()
387
  except Exception as e:
388
  logger.error(f"Failed to launch EduBot: {e}")
389
- raise], ['\\\\(', '\\\\)']],
390
- displayMath: [['$', '$'], ['\\\\[', '\\\\]']],
391
- packages: {'[+]': ['ams']}
392
- },
393
- svg: {fontCache: 'global'},
394
- startup: {
395
- ready: () => {
396
- MathJax.startup.defaultReady();
397
- // Re-render math when new content is added
398
- const observer = new MutationObserver(function(mutations) {
399
- MathJax.typesetPromise();
400
- });
401
- observer.observe(document.body, {childList: true, subtree: true});
402
- }
403
- }
404
- };
405
- </script>
406
- '''
 
8
  import re
9
  import requests
10
 
11
+ # --- LangChain Imports ---
12
+ from langchain.tools import BaseTool
13
+ from langchain.agents import initialize_agent, AgentType
14
+ from langchain.llms.huggingface_hub import HuggingFaceHub
15
+ from langchain.memory import ConversationBufferWindowMemory
16
+ from langchain.prompts import PromptTemplate
17
+ from pydantic import BaseModel, Field
18
+ from typing import Type, Optional
19
+
20
  # --- Environment and Logging Setup ---
21
  logging.basicConfig(level=logging.INFO)
22
  logger = logging.getLogger(__name__)
 
26
  if not hf_token:
27
  logger.warning("Neither HF_TOKEN nor HUGGINGFACEHUB_API_TOKEN is set, the application may not work.")
28
 
 
 
 
 
29
  metrics_tracker = EduBotMetrics(save_file="edu_metrics.json")
30
 
31
+ # --- LangChain Tool Definition ---
32
+ class GraphInput(BaseModel):
33
+ data_json: str = Field(description="JSON string of data for the graph")
34
+ labels_json: str = Field(description="JSON string of labels for the graph")
35
+ plot_type: str = Field(description="Type of plot: bar, line, or pie")
36
+ title: str = Field(description="Title for the graph")
37
+ x_label: str = Field(description="X-axis label", default="")
38
+ y_label: str = Field(description="Y-axis label", default="")
39
+
40
+ class CreateGraphTool(BaseTool):
41
+ name = "create_graph"
42
+ description = """Generates a plot (bar, line, or pie) and returns it as an HTML-formatted Base64-encoded image string. Use this tool when teaching concepts that benefit from visual representation, such as: statistical distributions, mathematical functions, data comparisons, survey results, grade analyses, scientific relationships, economic models, or any quantitative information that would be clearer with a graph. The data and labels arguments must be JSON-encoded strings."""
43
+ args_schema: Type[BaseModel] = GraphInput
44
+
45
+ def _run(self, data_json: str, labels_json: str, plot_type: str,
46
+ title: str, x_label: str = "", y_label: str = "") -> str:
47
+ try:
48
+ return generate_plot(
49
+ data_json=data_json,
50
+ labels_json=labels_json,
51
+ plot_type=plot_type,
52
+ title=title,
53
+ x_label=x_label,
54
+ y_label=y_label
55
+ )
56
+ except Exception as e:
57
+ return f"<p style='color:red;'>Error creating graph: {str(e)}</p>"
 
 
58
 
59
+ # --- LangChain Setup ---
60
+ def create_langchain_agent():
61
+ """Initialize LangChain agent with tools and memory."""
62
+
63
+ # Initialize LLM
64
+ llm = HuggingFaceHub(
65
+ repo_id="Qwen/Qwen2.5-VL-7B-Instruct",
66
+ huggingfacehub_api_token=hf_token,
67
+ model_kwargs={
68
+ "temperature": 0.7,
69
+ "max_new_tokens": 1000,
70
+ "top_p": 0.9,
71
+ "return_full_text": False
72
+ }
73
+ )
74
+
75
+ # Initialize tools
76
+ tools = [CreateGraphTool()]
77
+
78
+ # Initialize memory
79
+ memory = ConversationBufferWindowMemory(
80
+ memory_key="chat_history",
81
+ k=10,
82
+ return_messages=True
83
+ )
84
+
85
+ # Create agent
86
+ agent = initialize_agent(
87
+ tools=tools,
88
+ llm=llm,
89
+ agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
90
+ memory=memory,
91
+ verbose=False,
92
+ max_iterations=3,
93
+ early_stopping_method="generate"
94
+ )
95
+
96
+ return agent
97
 
98
+ # --- System Prompt ---
99
+ SYSTEM_PROMPT = """You are EduBot, an expert multi-concept tutor designed to facilitate genuine learning and understanding. Your primary mission is to guide students through the learning process rather than providing direct answers to academic work.
100
 
101
  ## Core Educational Principles
102
  - Provide comprehensive, educational responses that help students truly understand concepts
 
137
  - data_json: '{"Category1": 25, "Category2": 40, "Category3": 35}'
138
  - labels_json: '["Category1", "Category2", "Category3"]'
139
 
 
 
 
 
 
 
 
 
 
140
  ## Response Guidelines
141
  - **For math problems**: Explain concepts, provide formula derivations, and guide through problem-solving steps without computing final numerical answers
142
  - **For multiple-choice questions**: Discuss the concepts being tested and help students understand how to analyze options rather than identifying the correct choice
 
152
  - Encourage students to explain their thinking and reasoning
153
  - Provide honest, accurate feedback even when it may not be what the student wants to hear
154
 
155
+ Your goal is to be an educational partner who empowers students to succeed through understanding, not a service that completes their work for them.
156
 
157
+ Question: {input}"""
158
 
159
+ # --- Global Agent Instance ---
160
+ agent = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
 
162
+ def get_agent():
163
+ """Get or create the LangChain agent."""
164
+ global agent
165
+ if agent is None:
166
+ agent = create_langchain_agent()
167
+ return agent
 
 
 
 
 
 
 
 
 
 
 
168
 
169
+ # --- UI: MathJax Configuration ---
170
+ mathjax_config = '''
171
+ <script>
172
+ window.MathJax = {
173
+ tex: {
174
+ inlineMath: [['\\\\(', '\\\\)']],
175
+ displayMath: [['$', '$'], ['\\\\[', '\\\\]']],
176
+ packages: {'[+]': ['ams']}
177
+ },
178
+ svg: {fontCache: 'global'},
179
+ startup: {
180
+ ready: () => {
181
+ MathJax.startup.defaultReady();
182
+ // Re-render math when new content is added
183
+ const observer = new MutationObserver(function(mutations) {
184
+ MathJax.typesetPromise();
185
+ });
186
+ observer.observe(document.body, {childList: true, subtree: true});
187
+ }
188
+ }
189
+ };
190
+ </script>
191
+ '''
192
+
193
+ # --- HTML Head Content ---
194
+ html_head_content = '''
195
+ <meta charset="utf-8">
196
+ <meta name="viewport" content="width=device-width, initial-scale=1">
197
+ <title>EduBot - AI Educational Assistant</title>
198
+ '''
199
+
200
+ # --- Core Logic Functions ---
201
  def smart_truncate(text, max_length=3000):
202
  """Truncates text intelligently to the last full sentence or word."""
203
  if len(text) <= max_length:
 
211
  words = text[:max_length].split()
212
  return ' '.join(words[:-1]) + "... [Response truncated]"
213
 
214
+ def generate_response_with_langchain(message, max_retries=3):
215
+ """Generate response using LangChain agent."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
 
217
  for attempt in range(max_retries):
218
  try:
219
+ # Get the agent
220
+ current_agent = get_agent()
221
 
222
+ # Format the prompt
223
+ formatted_prompt = SYSTEM_PROMPT.format(input=message)
 
 
 
 
 
 
 
224
 
225
+ # Get response from agent
226
+ response = current_agent.run(formatted_prompt)
227
+
228
+ return smart_truncate(response)
229
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  except Exception as e:
231
+ logger.error(f"LangChain error (attempt {attempt + 1}): {e}")
232
  if attempt < max_retries - 1:
233
  time.sleep(2)
234
  continue
235
  else:
236
+ return f"I apologize, but I encountered an error while processing your message: {str(e)}"
237
 
238
  def chat_response(message, history):
239
  """Process chat message and return response."""
 
241
  # Track metrics
242
  metrics_tracker.log_interaction(message, "user_query")
243
 
244
+ # Generate response with LangChain
245
+ response = generate_response_with_langchain(message)
 
 
 
 
 
 
 
 
246
 
247
  # Log metrics
248
  metrics_tracker.log_interaction(response, "bot_response")
 
253
  logger.error(f"Error in chat_response: {e}")
254
  return f"I apologize, but I encountered an error while processing your message: {str(e)}"
255
 
256
+ def respond_with_enhanced_streaming(message, history):
257
+ """Enhanced streaming response function."""
258
+ try:
259
+ response = chat_response(message, history)
260
+ yield response
261
+ except Exception as e:
262
+ logger.error(f"Error in streaming response: {e}")
263
+ yield f"I apologize, but I encountered an error: {str(e)}"
264
+
265
  # --- UI: Event Handlers ---
266
  def respond_and_update(message, history):
267
  """Main function to handle user submission."""
 
286
 
287
  def clear_chat():
288
  """Clear the chat history."""
289
+ # Reset agent memory
290
+ global agent
291
+ if agent is not None:
292
+ agent.memory.clear()
293
  return [], ""
294
 
295
  # --- UI: Interface Creation ---
 
368
  interface.launch()
369
  except Exception as e:
370
  logger.error(f"Failed to launch EduBot: {e}")
371
+ raise