jdesiree commited on
Commit
d1fae69
·
verified ·
1 Parent(s): 978e4b1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +120 -152
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
- # Define tools
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 for visualization
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 provide a helpful educational response AND include a JSON configuration for creating a graph or chart.
736
 
737
- Format your response with explanatory text followed by a JSON block like this:
738
 
739
- ```json
740
- {{
741
- "data": {{"Category 1": value1, "Category 2": value2}},
742
- "plot_type": "bar|line|pie",
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
- Provide your educational response followed by the JSON configuration.
 
 
751
  """
752
  prompt = tool_prompt
753
  else:
754
  prompt = user_query
755
 
756
- # Generate response using the LLM (self.llm is accessible here)
757
- response = self.llm.invoke(prompt)
 
 
 
 
 
 
 
 
 
 
 
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 process_json_tools(state: EducationalAgentState) -> dict:
778
- """Process JSON tool configurations from the model response"""
779
- start_process_tools_time = time.perf_counter()
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
- content = last_ai_message.content
795
-
796
- # Look for JSON blocks in the message
797
- json_pattern = r'```json\s*\n?(.*?)\n?```'
798
- json_matches = re.findall(json_pattern, content, re.DOTALL)
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("process_tools", process_json_tools)
874
 
875
  # Add edges
876
  workflow.add_edge(START, "decide_tools")
877
  workflow.add_edge("decide_tools", "call_model")
878
- workflow.add_edge("call_model", "process_tools")
879
- workflow.add_edge("process_tools", END)
 
 
 
 
 
 
 
 
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())