Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -131,76 +131,6 @@ hf_token = HF_TOKEN
|
|
| 131 |
if not hf_token:
|
| 132 |
logger.warning("Neither HF_TOKEN nor HUGGINGFACEHUB_API_TOKEN is set, the application may not work.")
|
| 133 |
|
| 134 |
-
# LangGraph State Definition
|
| 135 |
-
class EducationalAgentState(TypedDict):
|
| 136 |
-
messages: Annotated[Sequence[BaseMessage], add_messages]
|
| 137 |
-
needs_tools: bool
|
| 138 |
-
educational_context: Optional[str]
|
| 139 |
-
|
| 140 |
-
@tool(return_direct=False)
|
| 141 |
-
def Create_Graph_Tool(graph_config: str) -> str:
|
| 142 |
-
"""
|
| 143 |
-
Creates educational graphs and charts to help explain concepts to students.
|
| 144 |
-
|
| 145 |
-
Use this tool ONLY when teaching concepts that would benefit from visual representation, such as:
|
| 146 |
-
- Mathematical functions and relationships (quadratic equations, exponential growth)
|
| 147 |
-
- Statistical distributions and data analysis (normal curves, survey results)
|
| 148 |
-
- Scientific trends and comparisons (temperature changes, population growth)
|
| 149 |
-
- Economic models and business metrics (profit over time, market shares)
|
| 150 |
-
- Grade distributions or performance analysis (test score ranges)
|
| 151 |
-
- Any quantitative concept that's clearer with visualization
|
| 152 |
-
|
| 153 |
-
Input should be a JSON string with this structure:
|
| 154 |
-
{
|
| 155 |
-
"data": {"Category A": 25, "Category B": 40, "Category C": 35},
|
| 156 |
-
"plot_type": "bar",
|
| 157 |
-
"title": "Student Performance by Subject",
|
| 158 |
-
"x_label": "Subjects",
|
| 159 |
-
"y_label": "Average Score",
|
| 160 |
-
"educational_context": "This visualization helps students see performance patterns across subjects"
|
| 161 |
-
}
|
| 162 |
-
|
| 163 |
-
Plot types:
|
| 164 |
-
- "bar": Best for comparing categories, showing distributions, or discrete data
|
| 165 |
-
- "line": Best for showing trends over time or continuous relationships
|
| 166 |
-
- "pie": Best for showing parts of a whole or proportions
|
| 167 |
-
|
| 168 |
-
Always create meaningful educational data that illustrates the concept you're teaching.
|
| 169 |
-
Include educational_context to explain why the visualization helps learning.
|
| 170 |
-
"""
|
| 171 |
-
start_create_graph_tool_time = time.perf_counter()
|
| 172 |
-
current_time = datetime.now()
|
| 173 |
-
|
| 174 |
-
try:
|
| 175 |
-
# Validate it's proper JSON
|
| 176 |
-
config = json.loads(graph_config)
|
| 177 |
-
|
| 178 |
-
# Add educational context if provided
|
| 179 |
-
educational_context = config.get("educational_context", "")
|
| 180 |
-
|
| 181 |
-
# Call your generate_plot function
|
| 182 |
-
graph_html = generate_plot(graph_config)
|
| 183 |
-
|
| 184 |
-
# Add educational context if provided
|
| 185 |
-
if educational_context:
|
| 186 |
-
context_html = f'<div style="margin: 10px 0; padding: 10px; background: #f8f9fa; border-left: 4px solid #007bff; font-style: italic;">💡 {educational_context}</div>'
|
| 187 |
-
result = context_html + graph_html
|
| 188 |
-
else:
|
| 189 |
-
result = graph_html
|
| 190 |
-
|
| 191 |
-
end_create_graph_tool_time = time.perf_counter()
|
| 192 |
-
graph_create_graph_tool_time = end_create_graph_tool_time - start_create_graph_tool_time
|
| 193 |
-
log_metric(f"Graph tool creation time: {graph_create_graph_tool_time:0.4f} seconds. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
|
| 194 |
-
|
| 195 |
-
return result
|
| 196 |
-
|
| 197 |
-
except json.JSONDecodeError as e:
|
| 198 |
-
logger.error(f"Invalid JSON provided to graph tool: {e}")
|
| 199 |
-
return '<p style="color:red;">Graph generation failed - invalid JSON format</p>'
|
| 200 |
-
except Exception as e:
|
| 201 |
-
logger.error(f"Error in graph generation: {e}")
|
| 202 |
-
return f'<p style="color:red;">Error creating graph: {str(e)}</p>'
|
| 203 |
-
|
| 204 |
# Tool Decision Engine (Updated for LangGraph)
|
| 205 |
class Tool_Decision_Engine:
|
| 206 |
"""Uses LLM to intelligently decide when visualization tools would be beneficial"""
|
|
@@ -279,6 +209,83 @@ Decision:"""
|
|
| 279 |
log_metric(f"Tool decision time (error): {graph_decision_time:0.4f} seconds. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
|
| 280 |
return False
|
| 281 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 282 |
# System Prompt with ReAct Framework for Phi-3-mini
|
| 283 |
SYSTEM_PROMPT = """You are Mimir, an expert multi-concept tutor designed to facilitate genuine learning and understanding. Your primary mission is to guide students through the learning process. You do so concisely, without excessive filler language or flowery content.
|
| 284 |
|
|
@@ -702,7 +709,7 @@ class Educational_Agent:
|
|
| 702 |
|
| 703 |
def _create_langgraph_workflow(self):
|
| 704 |
"""Create the complete LangGraph workflow with improved tool calling"""
|
| 705 |
-
#
|
| 706 |
tools = [Create_Graph_Tool]
|
| 707 |
tool_node = ToolNode(tools)
|
| 708 |
|
|
@@ -728,33 +735,40 @@ class Educational_Agent:
|
|
| 728 |
needs_tools = state.get("needs_tools", False)
|
| 729 |
|
| 730 |
if needs_tools:
|
| 731 |
-
# Create tool prompt
|
| 732 |
tool_prompt = f"""
|
| 733 |
You are an educational AI assistant. The user has asked: "{user_query}"
|
| 734 |
|
| 735 |
-
This query would benefit from a visualization. Please
|
| 736 |
|
| 737 |
-
|
| 738 |
|
| 739 |
-
|
| 740 |
-
|
| 741 |
-
"
|
| 742 |
-
|
| 743 |
-
"title": "Descriptive Title",
|
| 744 |
-
"x_label": "X Axis Label",
|
| 745 |
-
"y_label": "Y Axis Label",
|
| 746 |
-
"educational_context": "Explanation of why this visualization helps learning"
|
| 747 |
-
}}
|
| 748 |
-
```
|
| 749 |
|
| 750 |
-
|
|
|
|
|
|
|
| 751 |
"""
|
| 752 |
prompt = tool_prompt
|
| 753 |
else:
|
| 754 |
prompt = user_query
|
| 755 |
|
| 756 |
-
#
|
| 757 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 758 |
|
| 759 |
# Create AI message
|
| 760 |
ai_message = AIMessage(content=response)
|
|
@@ -774,69 +788,15 @@ class Educational_Agent:
|
|
| 774 |
error_message = AIMessage(content=f"I encountered an error generating a response: {str(e)}")
|
| 775 |
return {"messages": [error_message]}
|
| 776 |
|
| 777 |
-
def
|
| 778 |
-
"""
|
| 779 |
-
|
| 780 |
-
current_time = datetime.now()
|
| 781 |
-
|
| 782 |
-
messages = state["messages"]
|
| 783 |
-
|
| 784 |
-
# Get the last AI message
|
| 785 |
-
last_ai_message = None
|
| 786 |
-
for msg in reversed(messages):
|
| 787 |
-
if isinstance(msg, AIMessage):
|
| 788 |
-
last_ai_message = msg
|
| 789 |
-
break
|
| 790 |
-
|
| 791 |
-
if not last_ai_message or not last_ai_message.content:
|
| 792 |
-
return {"messages": []}
|
| 793 |
|
| 794 |
-
|
| 795 |
-
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
|
| 799 |
-
|
| 800 |
-
if not json_matches:
|
| 801 |
-
logger.info("No JSON configuration found in message")
|
| 802 |
-
return {"messages": []}
|
| 803 |
-
|
| 804 |
-
# Process the first JSON match
|
| 805 |
-
json_config = json_matches[0].strip()
|
| 806 |
-
|
| 807 |
-
try:
|
| 808 |
-
# Validate JSON
|
| 809 |
-
config_dict = json.loads(json_config)
|
| 810 |
-
|
| 811 |
-
# Check if it's a valid graph configuration
|
| 812 |
-
required_keys = ['data', 'plot_type', 'title']
|
| 813 |
-
if all(key in config_dict for key in required_keys):
|
| 814 |
-
logger.info("Processing valid graph configuration")
|
| 815 |
-
|
| 816 |
-
# Call the graph tool
|
| 817 |
-
tool_result = Create_Graph_Tool.invoke({"graph_config": json_config})
|
| 818 |
-
|
| 819 |
-
# Create a tool message
|
| 820 |
-
tool_message = ToolMessage(
|
| 821 |
-
content=tool_result,
|
| 822 |
-
tool_call_id="graph_tool_call_1"
|
| 823 |
-
)
|
| 824 |
-
|
| 825 |
-
end_process_tools_time = time.perf_counter()
|
| 826 |
-
process_tools_time = end_process_tools_time - start_process_tools_time
|
| 827 |
-
log_metric(f"Process JSON tools time: {process_tools_time:0.4f} seconds. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
|
| 828 |
-
|
| 829 |
-
return {"messages": [tool_message]}
|
| 830 |
-
else:
|
| 831 |
-
logger.warning("JSON found but missing required graph configuration keys")
|
| 832 |
-
return {"messages": []}
|
| 833 |
-
|
| 834 |
-
except json.JSONDecodeError as e:
|
| 835 |
-
logger.error(f"Invalid JSON in message: {e}")
|
| 836 |
-
return {"messages": []}
|
| 837 |
-
except Exception as e:
|
| 838 |
-
logger.error(f"Error processing JSON tools: {e}")
|
| 839 |
-
return {"messages": []}
|
| 840 |
|
| 841 |
def make_tool_decision(state: EducationalAgentState) -> dict:
|
| 842 |
"""Decide whether tools are needed and update state"""
|
|
@@ -870,13 +830,21 @@ class Educational_Agent:
|
|
| 870 |
# Add nodes
|
| 871 |
workflow.add_node("decide_tools", make_tool_decision)
|
| 872 |
workflow.add_node("call_model", call_model)
|
| 873 |
-
workflow.add_node("
|
| 874 |
|
| 875 |
# Add edges
|
| 876 |
workflow.add_edge(START, "decide_tools")
|
| 877 |
workflow.add_edge("decide_tools", "call_model")
|
| 878 |
-
|
| 879 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 880 |
|
| 881 |
# Compile the workflow
|
| 882 |
return workflow.compile(checkpointer=MemorySaver())
|
|
|
|
| 131 |
if not hf_token:
|
| 132 |
logger.warning("Neither HF_TOKEN nor HUGGINGFACEHUB_API_TOKEN is set, the application may not work.")
|
| 133 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
# Tool Decision Engine (Updated for LangGraph)
|
| 135 |
class Tool_Decision_Engine:
|
| 136 |
"""Uses LLM to intelligently decide when visualization tools would be beneficial"""
|
|
|
|
| 209 |
log_metric(f"Tool decision time (error): {graph_decision_time:0.4f} seconds. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
|
| 210 |
return False
|
| 211 |
|
| 212 |
+
# LangGraph State Definition
|
| 213 |
+
class EducationalAgentState(TypedDict):
|
| 214 |
+
messages: Annotated[Sequence[BaseMessage], add_messages]
|
| 215 |
+
needs_tools: bool
|
| 216 |
+
educational_context: Optional[str]
|
| 217 |
+
|
| 218 |
+
@tool(return_direct=False)
|
| 219 |
+
def Create_Graph_Tool(
|
| 220 |
+
data: dict,
|
| 221 |
+
plot_type: str,
|
| 222 |
+
title: str = "Generated Plot",
|
| 223 |
+
x_label: str = "",
|
| 224 |
+
y_label: str = "",
|
| 225 |
+
educational_context: str = ""
|
| 226 |
+
) -> str:
|
| 227 |
+
"""
|
| 228 |
+
Creates educational graphs and charts to help explain concepts to students.
|
| 229 |
+
|
| 230 |
+
Use this tool ONLY when teaching concepts that would benefit from visual representation, such as:
|
| 231 |
+
- Mathematical functions and relationships (quadratic equations, exponential growth)
|
| 232 |
+
- Statistical distributions and data analysis (normal curves, survey results)
|
| 233 |
+
- Scientific trends and comparisons (temperature changes, population growth)
|
| 234 |
+
- Economic models and business metrics (profit over time, market shares)
|
| 235 |
+
- Grade distributions or performance analysis (test score ranges)
|
| 236 |
+
- Any quantitative concept that's clearer with visualization
|
| 237 |
+
|
| 238 |
+
Args:
|
| 239 |
+
data: Dictionary with string keys and numeric values {"Category A": 25, "Category B": 40}
|
| 240 |
+
plot_type: "bar", "line", or "pie"
|
| 241 |
+
title: Title for the chart
|
| 242 |
+
x_label: X-axis label
|
| 243 |
+
y_label: Y-axis label
|
| 244 |
+
educational_context: Explanation of why this visualization helps learning
|
| 245 |
+
"""
|
| 246 |
+
start_create_graph_tool_time = time.perf_counter()
|
| 247 |
+
current_time = datetime.now()
|
| 248 |
+
|
| 249 |
+
try:
|
| 250 |
+
# Call the generate_plot function directly
|
| 251 |
+
content, artifact = generate_plot(
|
| 252 |
+
data=data,
|
| 253 |
+
plot_type=plot_type,
|
| 254 |
+
title=title,
|
| 255 |
+
x_label=x_label,
|
| 256 |
+
y_label=y_label
|
| 257 |
+
)
|
| 258 |
+
|
| 259 |
+
# Check if there was an error
|
| 260 |
+
if "error" in artifact:
|
| 261 |
+
return f'<p style="color:red;">Graph generation failed: {artifact["error"]}</p>'
|
| 262 |
+
|
| 263 |
+
# Convert the base64 image to HTML
|
| 264 |
+
base64_image = artifact["base64_image"]
|
| 265 |
+
|
| 266 |
+
# Add educational context if provided
|
| 267 |
+
context_html = ""
|
| 268 |
+
if educational_context:
|
| 269 |
+
context_html = f'<div style="margin: 10px 0; padding: 10px; background: #f8f9fa; border-left: 4px solid #007bff; font-style: italic;">💡 {educational_context}</div>'
|
| 270 |
+
|
| 271 |
+
# Create the complete HTML with image
|
| 272 |
+
result = f"""{context_html}
|
| 273 |
+
<div style="text-align: center; margin: 20px 0;">
|
| 274 |
+
<img src="data:image/png;base64,{base64_image}"
|
| 275 |
+
style="max-width: 100%; height: auto; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);"
|
| 276 |
+
alt="{title}" />
|
| 277 |
+
</div>"""
|
| 278 |
+
|
| 279 |
+
end_create_graph_tool_time = time.perf_counter()
|
| 280 |
+
graph_create_graph_tool_time = end_create_graph_tool_time - start_create_graph_tool_time
|
| 281 |
+
log_metric(f"Graph tool creation time: {graph_create_graph_tool_time:0.4f} seconds. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
|
| 282 |
+
|
| 283 |
+
return result
|
| 284 |
+
|
| 285 |
+
except Exception as e:
|
| 286 |
+
logger.error(f"Error in graph generation: {e}")
|
| 287 |
+
return f'<p style="color:red;">Error creating graph: {str(e)}</p>'
|
| 288 |
+
|
| 289 |
# System Prompt with ReAct Framework for Phi-3-mini
|
| 290 |
SYSTEM_PROMPT = """You are Mimir, an expert multi-concept tutor designed to facilitate genuine learning and understanding. Your primary mission is to guide students through the learning process. You do so concisely, without excessive filler language or flowery content.
|
| 291 |
|
|
|
|
| 709 |
|
| 710 |
def _create_langgraph_workflow(self):
|
| 711 |
"""Create the complete LangGraph workflow with improved tool calling"""
|
| 712 |
+
# Use the updated Create_Graph_Tool
|
| 713 |
tools = [Create_Graph_Tool]
|
| 714 |
tool_node = ToolNode(tools)
|
| 715 |
|
|
|
|
| 735 |
needs_tools = state.get("needs_tools", False)
|
| 736 |
|
| 737 |
if needs_tools:
|
| 738 |
+
# Create tool prompt that guides the model to use structured parameters
|
| 739 |
tool_prompt = f"""
|
| 740 |
You are an educational AI assistant. The user has asked: "{user_query}"
|
| 741 |
|
| 742 |
+
This query would benefit from a visualization. Please call the Create_Graph_Tool with appropriate structured parameters.
|
| 743 |
|
| 744 |
+
For the data parameter, create a meaningful dictionary with string keys and numeric values that illustrate the concept being discussed.
|
| 745 |
|
| 746 |
+
Choose the appropriate plot_type:
|
| 747 |
+
- "bar" for comparing categories or discrete data
|
| 748 |
+
- "line" for showing trends over time or continuous relationships
|
| 749 |
+
- "pie" for showing parts of a whole or proportions
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 750 |
|
| 751 |
+
Create a descriptive title and appropriate axis labels. Include an educational_context explaining why this visualization helps learning.
|
| 752 |
+
|
| 753 |
+
Call the tool with these structured parameters, don't format as JSON.
|
| 754 |
"""
|
| 755 |
prompt = tool_prompt
|
| 756 |
else:
|
| 757 |
prompt = user_query
|
| 758 |
|
| 759 |
+
# Bind tools to LLM if needed
|
| 760 |
+
if needs_tools:
|
| 761 |
+
model_with_tools = self.llm
|
| 762 |
+
# For Phi-3, we need to manually bind tools if supported
|
| 763 |
+
try:
|
| 764 |
+
if hasattr(self.llm, 'bind_tools'):
|
| 765 |
+
model_with_tools = self.llm.bind_tools(tools)
|
| 766 |
+
response = model_with_tools.invoke(prompt)
|
| 767 |
+
except:
|
| 768 |
+
# Fallback if tool binding not supported
|
| 769 |
+
response = self.llm.invoke(prompt)
|
| 770 |
+
else:
|
| 771 |
+
response = self.llm.invoke(prompt)
|
| 772 |
|
| 773 |
# Create AI message
|
| 774 |
ai_message = AIMessage(content=response)
|
|
|
|
| 788 |
error_message = AIMessage(content=f"I encountered an error generating a response: {str(e)}")
|
| 789 |
return {"messages": [error_message]}
|
| 790 |
|
| 791 |
+
def should_continue(state: EducationalAgentState) -> str:
|
| 792 |
+
"""Route to tools or end based on the last message"""
|
| 793 |
+
last_message = state["messages"][-1]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 794 |
|
| 795 |
+
# Check if the last message has tool calls
|
| 796 |
+
if hasattr(last_message, "tool_calls") and last_message.tool_calls:
|
| 797 |
+
return "tools"
|
| 798 |
+
else:
|
| 799 |
+
return END
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 800 |
|
| 801 |
def make_tool_decision(state: EducationalAgentState) -> dict:
|
| 802 |
"""Decide whether tools are needed and update state"""
|
|
|
|
| 830 |
# Add nodes
|
| 831 |
workflow.add_node("decide_tools", make_tool_decision)
|
| 832 |
workflow.add_node("call_model", call_model)
|
| 833 |
+
workflow.add_node("tools", tool_node)
|
| 834 |
|
| 835 |
# Add edges
|
| 836 |
workflow.add_edge(START, "decide_tools")
|
| 837 |
workflow.add_edge("decide_tools", "call_model")
|
| 838 |
+
|
| 839 |
+
# Add conditional edge from call_model
|
| 840 |
+
workflow.add_conditional_edges(
|
| 841 |
+
"call_model",
|
| 842 |
+
should_continue,
|
| 843 |
+
{"tools": "tools", END: END}
|
| 844 |
+
)
|
| 845 |
+
|
| 846 |
+
# After tools, go back to call_model for final response
|
| 847 |
+
workflow.add_edge("tools", "call_model")
|
| 848 |
|
| 849 |
# Compile the workflow
|
| 850 |
return workflow.compile(checkpointer=MemorySaver())
|