Spaces:
Sleeping
Sleeping
Added Graph Generating Functionality
Browse files
app.py
CHANGED
|
@@ -29,7 +29,7 @@ tools = [
|
|
| 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.
|
| 33 |
"parameters": {
|
| 34 |
"type": "object",
|
| 35 |
"properties": {
|
|
@@ -75,21 +75,34 @@ You recognize that students may seek direct answers to homework, assignments, or
|
|
| 75 |
- **Suggest study strategies**: Recommend effective learning approaches for the subject matter
|
| 76 |
|
| 77 |
## Tool Usage
|
| 78 |
-
You have access to a create_graph tool
|
| 79 |
-
-
|
| 80 |
-
-
|
| 81 |
-
-
|
| 82 |
-
-
|
|
|
|
|
|
|
|
|
|
| 83 |
|
| 84 |
-
When using the create_graph tool,
|
| 85 |
-
- data_json: '{"
|
| 86 |
-
- labels_json: '["
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
|
| 88 |
## Response Guidelines
|
| 89 |
- **For math problems**: Explain concepts, provide formula derivations, and guide through problem-solving steps without computing final numerical answers
|
| 90 |
- **For multiple-choice questions**: Discuss the concepts being tested and help students understand how to analyze options rather than identifying the correct choice
|
| 91 |
- **For essays or written work**: Discuss research strategies, organizational techniques, and critical thinking approaches rather than providing content or thesis statements
|
| 92 |
- **For factual questions**: Provide educational context and encourage students to synthesize information rather than stating direct answers
|
|
|
|
| 93 |
|
| 94 |
## Communication Guidelines
|
| 95 |
- Maintain a supportive, non-judgmental tone in all interactions
|
|
@@ -103,6 +116,41 @@ Your goal is to be an educational partner who empowers students to succeed throu
|
|
| 103 |
|
| 104 |
# --- Core Logic Functions ---
|
| 105 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
def smart_truncate(text, max_length=3000):
|
| 107 |
"""Truncates text intelligently to the last full sentence or word."""
|
| 108 |
if len(text) <= max_length:
|
|
@@ -113,7 +161,31 @@ def smart_truncate(text, max_length=3000):
|
|
| 113 |
if len(sentences) > 1:
|
| 114 |
return ' '.join(sentences[:-1]) + "... [Response truncated - ask for continuation]"
|
| 115 |
# Otherwise, split by word
|
| 116 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
"return_full_text": False
|
| 118 |
}
|
| 119 |
}
|
|
@@ -136,276 +208,87 @@ def smart_truncate(text, max_length=3000):
|
|
| 136 |
result = response.json()
|
| 137 |
|
| 138 |
if isinstance(result, list) and len(result) > 0:
|
| 139 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
else:
|
| 141 |
return "I apologize, but I received an unexpected response format. Please try again."
|
| 142 |
|
| 143 |
-
except requests.exceptions.
|
|
|
|
| 144 |
if attempt < max_retries - 1:
|
| 145 |
-
logger.warning(f"Request timeout, retrying... (attempt {attempt + 1})")
|
| 146 |
time.sleep(2)
|
| 147 |
continue
|
| 148 |
else:
|
| 149 |
-
return "I'm
|
| 150 |
-
except
|
|
|
|
| 151 |
if attempt < max_retries - 1:
|
| 152 |
-
logger.warning(f"Request failed: {e}, retrying... (attempt {attempt + 1})")
|
| 153 |
time.sleep(2)
|
| 154 |
continue
|
| 155 |
else:
|
| 156 |
-
return f"
|
| 157 |
-
|
| 158 |
-
return "I'm sorry, I encountered an error and couldn't generate a response."
|
| 159 |
-
|
| 160 |
-
def format_messages_for_hf(messages):
|
| 161 |
-
"""Format messages for HF API."""
|
| 162 |
-
formatted = ""
|
| 163 |
-
for msg in messages:
|
| 164 |
-
role = msg["role"]
|
| 165 |
-
content = msg["content"]
|
| 166 |
-
if role == "system":
|
| 167 |
-
formatted += f"System: {content}\n\n"
|
| 168 |
-
elif role == "user":
|
| 169 |
-
formatted += f"Human: {content}\n\n"
|
| 170 |
-
elif role == "assistant":
|
| 171 |
-
formatted += f"Assistant: {content}\n\n"
|
| 172 |
-
|
| 173 |
-
formatted += "Assistant: "
|
| 174 |
-
return formatted
|
| 175 |
-
|
| 176 |
-
def process_response_for_tools(response_text, original_query):
|
| 177 |
-
"""Check if we should generate a graph based on the response and query."""
|
| 178 |
-
# Simple heuristic - if the response mentions creating a chart/graph or the query requested one
|
| 179 |
-
should_create_graph = (
|
| 180 |
-
detect_tool_request(original_query) or
|
| 181 |
-
any(phrase in response_text.lower() for phrase in [
|
| 182 |
-
"let me create a", "i'll make a", "here's a chart", "here's a graph"
|
| 183 |
-
])
|
| 184 |
-
)
|
| 185 |
-
|
| 186 |
-
if should_create_graph:
|
| 187 |
-
# Try to extract data from context or create a simple example
|
| 188 |
-
if "grade" in original_query.lower() or "score" in original_query.lower():
|
| 189 |
-
data_json = '{"Math": 85, "Science": 92, "English": 78, "History": 88}'
|
| 190 |
-
labels_json = '["Math", "Science", "English", "History"]'
|
| 191 |
-
title = "Sample Grade Distribution"
|
| 192 |
-
plot_type = "bar"
|
| 193 |
-
elif "population" in original_query.lower():
|
| 194 |
-
data_json = '{"City A": 1200000, "City B": 950000, "City C": 800000}'
|
| 195 |
-
labels_json = '["City A", "City B", "City C"]'
|
| 196 |
-
title = "Population Comparison"
|
| 197 |
-
plot_type = "bar"
|
| 198 |
-
elif "time" in original_query.lower() or "trend" in original_query.lower():
|
| 199 |
-
data_json = '{"Jan": 20, "Feb": 25, "Mar": 30, "Apr": 28, "May": 35}'
|
| 200 |
-
labels_json = '["Jan", "Feb", "Mar", "Apr", "May"]'
|
| 201 |
-
title = "Monthly Trends"
|
| 202 |
-
plot_type = "line"
|
| 203 |
-
else:
|
| 204 |
-
# Default example
|
| 205 |
-
data_json = '{"Category A": 30, "Category B": 25, "Category C": 20, "Category D": 25}'
|
| 206 |
-
labels_json = '["Category A", "Category B", "Category C", "Category D"]'
|
| 207 |
-
title = "Sample Data Distribution"
|
| 208 |
-
plot_type = "pie"
|
| 209 |
-
|
| 210 |
-
try:
|
| 211 |
-
graph_html = generate_plot(data_json, labels_json, plot_type, title)
|
| 212 |
-
response_text += f"\n\n{graph_html}"
|
| 213 |
-
except Exception as e:
|
| 214 |
-
logger.error(f"Error generating graph: {e}")
|
| 215 |
-
response_text += f"\n\n<p style='color:orange;'>I tried to create a visualization but encountered an error. The concept explanation above should still be helpful!</p>"
|
| 216 |
-
|
| 217 |
-
return response_text
|
| 218 |
-
|
| 219 |
-
def respond_with_enhanced_streaming(message, history):
|
| 220 |
-
"""Streams the bot's response, with support for graph generation."""
|
| 221 |
-
timing_context = metrics_tracker.start_timing()
|
| 222 |
-
error_occurred = False
|
| 223 |
-
response = ""
|
| 224 |
-
mode = ""
|
| 225 |
|
|
|
|
|
|
|
| 226 |
try:
|
| 227 |
-
#
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
args.get("data_json", "{}"),
|
| 247 |
-
args.get("labels_json", "[]"),
|
| 248 |
-
args.get("plot_type", "bar"),
|
| 249 |
-
args.get("title", "Untitled"),
|
| 250 |
-
args.get("x_label", ""),
|
| 251 |
-
args.get("y_label", "")
|
| 252 |
-
)
|
| 253 |
-
response = graph_html
|
| 254 |
-
mode = "graph"
|
| 255 |
-
else:
|
| 256 |
-
response = model_output
|
| 257 |
-
mode = "text"
|
| 258 |
-
except json.JSONDecodeError:
|
| 259 |
-
# Not JSON → just plain text
|
| 260 |
-
response = model_output
|
| 261 |
-
mode = "text"
|
| 262 |
-
|
| 263 |
except Exception as e:
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
mode = "error"
|
| 267 |
-
|
| 268 |
-
finally:
|
| 269 |
-
metrics_tracker.stop_timing(timing_context, error_occurred)
|
| 270 |
-
|
| 271 |
-
return response, mode
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
# ===============================================================================
|
| 275 |
-
# UI CONFIGURATION SECTION - ALL UI RELATED CODE CENTRALIZED HERE
|
| 276 |
-
# ===============================================================================
|
| 277 |
|
| 278 |
-
# ---
|
| 279 |
-
html_head_content = '''
|
| 280 |
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 281 |
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 282 |
-
<link href="https://fonts.googleapis.com/css2?family=Oswald:wght@200..700&display=swap" rel="stylesheet">
|
| 283 |
-
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
|
| 284 |
-
<script>
|
| 285 |
-
// Force light theme in Gradio
|
| 286 |
-
window.addEventListener('DOMContentLoaded', function () {
|
| 287 |
-
const gradioURL = window.location.href;
|
| 288 |
-
const url = new URL(gradioURL);
|
| 289 |
-
const currentTheme = url.searchParams.get('__theme');
|
| 290 |
-
|
| 291 |
-
if (currentTheme !== 'light') {
|
| 292 |
-
url.searchParams.set('__theme', 'light');
|
| 293 |
-
window.location.replace(url.toString());
|
| 294 |
-
}
|
| 295 |
-
});
|
| 296 |
-
</script>
|
| 297 |
-
'''
|
| 298 |
-
|
| 299 |
-
# --- UI: MathJax Configuration ---
|
| 300 |
-
mathjax_config = '''
|
| 301 |
-
<script>
|
| 302 |
-
window.MathJax = {
|
| 303 |
-
tex: {
|
| 304 |
-
inlineMath: [['$', '$'], ['\\\\(', '\\\\)']],
|
| 305 |
-
displayMath: [['$$', '$$'], ['\\\\[', '\\\\]']],
|
| 306 |
-
packages: {'[+]': ['ams']}
|
| 307 |
-
},
|
| 308 |
-
svg: {fontCache: 'global'},
|
| 309 |
-
startup: {
|
| 310 |
-
ready: () => {
|
| 311 |
-
MathJax.startup.defaultReady();
|
| 312 |
-
// Re-render math when new content is added
|
| 313 |
-
const observer = new MutationObserver(function(mutations) {
|
| 314 |
-
MathJax.typesetPromise();
|
| 315 |
-
});
|
| 316 |
-
observer.observe(document.body, {childList: true, subtree: true});
|
| 317 |
-
}
|
| 318 |
-
}
|
| 319 |
-
};
|
| 320 |
-
</script>
|
| 321 |
-
'''
|
| 322 |
-
|
| 323 |
-
# --- UI: Event Handlers ---
|
| 324 |
-
def respond_and_update(message, history):
|
| 325 |
-
"""Main function to handle user submission."""
|
| 326 |
-
if not message.strip():
|
| 327 |
-
return history, ""
|
| 328 |
-
|
| 329 |
-
# Add user message to history
|
| 330 |
-
history.append({"role": "user", "content": message})
|
| 331 |
-
# Yield history to show the user message immediately, and clear the textbox
|
| 332 |
-
yield history, ""
|
| 333 |
-
|
| 334 |
-
# Stream the bot's response
|
| 335 |
-
full_response = ""
|
| 336 |
-
for response_chunk in respond_with_enhanced_streaming(message, history):
|
| 337 |
-
full_response = response_chunk
|
| 338 |
-
# Update the last message (bot's response)
|
| 339 |
-
if len(history) > 0 and history[-1]["role"] == "user":
|
| 340 |
-
history.append({"role": "assistant", "content": full_response})
|
| 341 |
-
else:
|
| 342 |
-
history[-1] = {"role": "assistant", "content": full_response}
|
| 343 |
-
yield history, ""
|
| 344 |
-
|
| 345 |
-
def clear_chat():
|
| 346 |
-
"""Clear the chat history."""
|
| 347 |
-
return [], ""
|
| 348 |
-
|
| 349 |
-
# --- UI: Interface Creation ---
|
| 350 |
def create_interface():
|
| 351 |
-
"""
|
| 352 |
|
| 353 |
-
with gr.Blocks(
|
| 354 |
-
title="EduBot",
|
| 355 |
-
fill_width=True,
|
| 356 |
-
fill_height=True,
|
| 357 |
-
theme=gr.themes.Origin(),
|
| 358 |
-
css="./styles.css"
|
| 359 |
-
) as demo:
|
| 360 |
-
# Add head content and MathJax
|
| 361 |
-
gr.HTML(html_head_content)
|
| 362 |
-
gr.HTML('<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>')
|
| 363 |
-
gr.HTML(mathjax_config)
|
| 364 |
|
| 365 |
-
|
| 366 |
-
# Title Section
|
| 367 |
-
gr.HTML('<div class="title-header"><h1>🎓 EduBot</h1></div>')
|
| 368 |
-
|
| 369 |
-
# Chat Section
|
| 370 |
-
with gr.Row():
|
| 371 |
-
chatbot = gr.Chatbot(
|
| 372 |
-
type="messages",
|
| 373 |
-
show_copy_button=True,
|
| 374 |
-
show_share_button=False,
|
| 375 |
-
avatar_images=None,
|
| 376 |
-
elem_id="main-chatbot",
|
| 377 |
-
container=False,
|
| 378 |
-
scale=1,
|
| 379 |
-
height="70vh"
|
| 380 |
-
)
|
| 381 |
-
|
| 382 |
-
# Input Section - fixed height
|
| 383 |
-
with gr.Row(elem_classes=["input-controls"]):
|
| 384 |
-
msg = gr.Textbox(
|
| 385 |
-
placeholder="Ask me about math, research, study strategies, or any educational topic...",
|
| 386 |
-
show_label=False,
|
| 387 |
-
lines=4,
|
| 388 |
-
max_lines=6,
|
| 389 |
-
elem_classes=["input-textbox"],
|
| 390 |
-
container=False,
|
| 391 |
-
scale=4
|
| 392 |
-
)
|
| 393 |
-
with gr.Column(elem_classes=["button-column"], scale=1):
|
| 394 |
-
send = gr.Button("Send", elem_classes=["send-button"], size="sm")
|
| 395 |
-
clear = gr.Button("Clear", elem_classes=["clear-button"], size="sm")
|
| 396 |
-
|
| 397 |
-
# Set up event handlers
|
| 398 |
-
msg.submit(respond_and_update, [msg, chatbot], [chatbot, msg])
|
| 399 |
-
send.click(respond_and_update, [msg, chatbot], [chatbot, msg])
|
| 400 |
-
clear.click(clear_chat, outputs=[chatbot, msg])
|
| 401 |
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
|
|
|
|
|
|
| 407 |
|
|
|
|
| 408 |
if __name__ == "__main__":
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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": {
|
|
|
|
| 75 |
- **Suggest study strategies**: Recommend effective learning approaches for the subject matter
|
| 76 |
|
| 77 |
## Tool Usage
|
| 78 |
+
You have access to a create_graph tool. Use this tool naturally when a visual representation would enhance understanding or when discussing concepts that involve data, relationships, patterns, or quantitative information. Consider creating graphs for:
|
| 79 |
+
- Mathematical concepts (functions, distributions, relationships)
|
| 80 |
+
- Statistical examples and explanations
|
| 81 |
+
- Scientific data and relationships
|
| 82 |
+
- Practice problems involving graph interpretation
|
| 83 |
+
- Comparative analyses
|
| 84 |
+
- Economic models or business concepts
|
| 85 |
+
- Any situation where visualization aids comprehension
|
| 86 |
|
| 87 |
+
When using the create_graph tool, format data as JSON strings:
|
| 88 |
+
- data_json: '{"Category1": 25, "Category2": 40, "Category3": 35}'
|
| 89 |
+
- labels_json: '["Category1", "Category2", "Category3"]'
|
| 90 |
+
|
| 91 |
+
## Function Calling Format
|
| 92 |
+
When you need to create a graph, use this exact format:
|
| 93 |
+
|
| 94 |
+
<function_call>
|
| 95 |
+
{"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"}}
|
| 96 |
+
</function_call>
|
| 97 |
+
|
| 98 |
+
The graph will be automatically generated and displayed in your response.
|
| 99 |
|
| 100 |
## Response Guidelines
|
| 101 |
- **For math problems**: Explain concepts, provide formula derivations, and guide through problem-solving steps without computing final numerical answers
|
| 102 |
- **For multiple-choice questions**: Discuss the concepts being tested and help students understand how to analyze options rather than identifying the correct choice
|
| 103 |
- **For essays or written work**: Discuss research strategies, organizational techniques, and critical thinking approaches rather than providing content or thesis statements
|
| 104 |
- **For factual questions**: Provide educational context and encourage students to synthesize information rather than stating direct answers
|
| 105 |
+
- Use graphs naturally when they would clarify or enhance your explanations
|
| 106 |
|
| 107 |
## Communication Guidelines
|
| 108 |
- Maintain a supportive, non-judgmental tone in all interactions
|
|
|
|
| 116 |
|
| 117 |
# --- Core Logic Functions ---
|
| 118 |
|
| 119 |
+
def execute_function_call(function_name, function_args):
|
| 120 |
+
"""Execute the called function and return the result."""
|
| 121 |
+
if function_name == "create_graph":
|
| 122 |
+
try:
|
| 123 |
+
return generate_plot(
|
| 124 |
+
data_json=function_args.get("data_json", "{}"),
|
| 125 |
+
labels_json=function_args.get("labels_json", "[]"),
|
| 126 |
+
plot_type=function_args.get("plot_type", "bar"),
|
| 127 |
+
title=function_args.get("title", "Graph"),
|
| 128 |
+
x_label=function_args.get("x_label", ""),
|
| 129 |
+
y_label=function_args.get("y_label", "")
|
| 130 |
+
)
|
| 131 |
+
except Exception as e:
|
| 132 |
+
return f"<p style='color:red;'>Error creating graph: {str(e)}</p>"
|
| 133 |
+
else:
|
| 134 |
+
return f"<p style='color:red;'>Unknown function: {function_name}</p>"
|
| 135 |
+
|
| 136 |
+
def parse_function_calls(text):
|
| 137 |
+
"""Parse function calls from model response."""
|
| 138 |
+
function_calls = []
|
| 139 |
+
|
| 140 |
+
# Look for function call patterns in the text
|
| 141 |
+
function_pattern = r'<function_call>(.*?)</function_call>'
|
| 142 |
+
matches = re.findall(function_pattern, text, re.DOTALL)
|
| 143 |
+
|
| 144 |
+
for match in matches:
|
| 145 |
+
try:
|
| 146 |
+
# Parse the function call JSON
|
| 147 |
+
call_data = json.loads(match.strip())
|
| 148 |
+
function_calls.append(call_data)
|
| 149 |
+
except json.JSONDecodeError:
|
| 150 |
+
continue
|
| 151 |
+
|
| 152 |
+
return function_calls
|
| 153 |
+
|
| 154 |
def smart_truncate(text, max_length=3000):
|
| 155 |
"""Truncates text intelligently to the last full sentence or word."""
|
| 156 |
if len(text) <= max_length:
|
|
|
|
| 161 |
if len(sentences) > 1:
|
| 162 |
return ' '.join(sentences[:-1]) + "... [Response truncated - ask for continuation]"
|
| 163 |
# Otherwise, split by word
|
| 164 |
+
words = text[:max_length].split()
|
| 165 |
+
return ' '.join(words[:-1]) + "... [Response truncated]"
|
| 166 |
+
|
| 167 |
+
def generate_response_with_tools(messages, max_retries=3):
|
| 168 |
+
"""Generate response with function calling capability."""
|
| 169 |
+
|
| 170 |
+
# Format messages for the API
|
| 171 |
+
formatted_messages = []
|
| 172 |
+
formatted_messages.append(f"System: {SYSTEM_MESSAGE}")
|
| 173 |
+
|
| 174 |
+
for msg in messages:
|
| 175 |
+
if msg["role"] == "user":
|
| 176 |
+
formatted_messages.append(f"User: {msg['content']}")
|
| 177 |
+
elif msg["role"] == "assistant":
|
| 178 |
+
formatted_messages.append(f"Assistant: {msg['content']}")
|
| 179 |
+
|
| 180 |
+
conversation = "\n\n".join(formatted_messages)
|
| 181 |
+
conversation += "\n\nAssistant: "
|
| 182 |
+
|
| 183 |
+
payload = {
|
| 184 |
+
"inputs": conversation,
|
| 185 |
+
"parameters": {
|
| 186 |
+
"max_new_tokens": 1000,
|
| 187 |
+
"temperature": 0.7,
|
| 188 |
+
"top_p": 0.9,
|
| 189 |
"return_full_text": False
|
| 190 |
}
|
| 191 |
}
|
|
|
|
| 208 |
result = response.json()
|
| 209 |
|
| 210 |
if isinstance(result, list) and len(result) > 0:
|
| 211 |
+
raw_response = result[0].get('generated_text', '').strip()
|
| 212 |
+
|
| 213 |
+
# Process function calls
|
| 214 |
+
function_calls = parse_function_calls(raw_response)
|
| 215 |
+
|
| 216 |
+
for call in function_calls:
|
| 217 |
+
if call.get("name") == "create_graph":
|
| 218 |
+
# Execute the function call
|
| 219 |
+
html_result = execute_function_call("create_graph", call.get("arguments", {}))
|
| 220 |
+
# Replace the function call with the generated HTML
|
| 221 |
+
pattern = r'<function_call>.*?</function_call>'
|
| 222 |
+
raw_response = re.sub(pattern, html_result, raw_response, count=1, flags=re.DOTALL)
|
| 223 |
+
|
| 224 |
+
return smart_truncate(raw_response)
|
| 225 |
else:
|
| 226 |
return "I apologize, but I received an unexpected response format. Please try again."
|
| 227 |
|
| 228 |
+
except requests.exceptions.RequestException as e:
|
| 229 |
+
logger.error(f"Request failed (attempt {attempt + 1}): {e}")
|
| 230 |
if attempt < max_retries - 1:
|
|
|
|
| 231 |
time.sleep(2)
|
| 232 |
continue
|
| 233 |
else:
|
| 234 |
+
return f"I'm having trouble connecting right now. Please try again later. (Error: {e})"
|
| 235 |
+
except Exception as e:
|
| 236 |
+
logger.error(f"Unexpected error (attempt {attempt + 1}): {e}")
|
| 237 |
if attempt < max_retries - 1:
|
|
|
|
| 238 |
time.sleep(2)
|
| 239 |
continue
|
| 240 |
else:
|
| 241 |
+
return f"An unexpected error occurred: {e}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
|
| 243 |
+
def chat_response(message, history):
|
| 244 |
+
"""Process chat message and return response."""
|
| 245 |
try:
|
| 246 |
+
# Track metrics
|
| 247 |
+
metrics_tracker.log_interaction(message, "user_query")
|
| 248 |
+
|
| 249 |
+
# Format conversation history
|
| 250 |
+
messages = []
|
| 251 |
+
for user_msg, bot_msg in history:
|
| 252 |
+
messages.append({"role": "user", "content": user_msg})
|
| 253 |
+
if bot_msg:
|
| 254 |
+
messages.append({"role": "assistant", "content": bot_msg})
|
| 255 |
+
messages.append({"role": "user", "content": message})
|
| 256 |
+
|
| 257 |
+
# Generate response with tool support
|
| 258 |
+
response = generate_response_with_tools(messages)
|
| 259 |
+
|
| 260 |
+
# Log metrics
|
| 261 |
+
metrics_tracker.log_interaction(response, "bot_response")
|
| 262 |
+
|
| 263 |
+
return response
|
| 264 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 265 |
except Exception as e:
|
| 266 |
+
logger.error(f"Error in chat_response: {e}")
|
| 267 |
+
return f"I apologize, but I encountered an error while processing your message: {str(e)}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
|
| 269 |
+
# --- Gradio Interface (UNCHANGED) ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 270 |
def create_interface():
|
| 271 |
+
"""Create and return the Gradio interface."""
|
| 272 |
|
| 273 |
+
with gr.Blocks(title="EduBot - AI Tutor", theme=gr.themes.Soft()) as interface:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
|
| 275 |
+
gr.Markdown("# EduBot - Your AI Learning Companion")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
|
| 277 |
+
chatbot = gr.Chatbot(height=400, show_label=False)
|
| 278 |
+
msg = gr.Textbox(label="Ask me anything!", placeholder="Type your question here...")
|
| 279 |
+
|
| 280 |
+
msg.submit(chat_response, [msg, chatbot], [chatbot])
|
| 281 |
+
msg.submit(lambda: "", None, [msg])
|
| 282 |
+
|
| 283 |
+
return interface
|
| 284 |
|
| 285 |
+
# --- Main Execution ---
|
| 286 |
if __name__ == "__main__":
|
| 287 |
+
try:
|
| 288 |
+
logger.info("Starting EduBot...")
|
| 289 |
+
interface = create_interface()
|
| 290 |
+
interface.queue()
|
| 291 |
+
interface.launch()
|
| 292 |
+
except Exception as e:
|
| 293 |
+
logger.error(f"Failed to launch EduBot: {e}")
|
| 294 |
+
raise
|