jdesiree commited on
Commit
10ba1d2
·
verified ·
1 Parent(s): b2186b2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +113 -101
app.py CHANGED
@@ -12,9 +12,6 @@ import logging
12
  import re
13
  import json
14
  from datetime import datetime
15
-
16
- current_time = datetime.now()
17
-
18
  from typing import Annotated, Sequence, TypedDict, List, Optional, Any, Type
19
  from pydantic import BaseModel, Field
20
 
@@ -38,16 +35,19 @@ load_dotenv(".env")
38
  HF_TOKEN = os.getenv("HF_TOKEN") or os.getenv("HUGGINGFACEHUB_API_TOKEN")
39
  print("Environment variables loaded.")
40
 
 
 
 
 
41
  # --- Environment and Logging Setup ---
42
- # Create a custom logger for metrics
43
  def setup_metrics_logger():
44
  """Setup a simple file logger for human-readable metrics"""
45
- logger = logging.getLogger('metrics')
46
- logger.setLevel(logging.INFO)
47
 
48
  # Avoid duplicate handlers
49
- if logger.handlers:
50
- return logger
51
 
52
  # Create file handler
53
  log_file = 'performance_metrics.log'
@@ -57,17 +57,18 @@ def setup_metrics_logger():
57
  formatter = logging.Formatter('%(message)s')
58
  handler.setFormatter(formatter)
59
 
60
- logger.addHandler(handler)
61
- return logger
62
 
63
  # Initialize the logger
64
  metrics_logger = setup_metrics_logger()
65
 
66
  def log_metric(message):
67
  """Log a human-readable metric message with automatic timestamp"""
 
68
  timestamped_message = f"{message} | Logged: {current_time:%Y-%m-%d %H:%M:%S}"
69
  metrics_logger.info(timestamped_message)
70
- logger.info(timestamped_message) # Also log to console using info.logger
71
 
72
  # Support both token names for flexibility
73
  hf_token = HF_TOKEN
@@ -112,6 +113,8 @@ def Create_Graph_Tool(graph_config: str) -> str:
112
  Include educational_context to explain why the visualization helps learning.
113
  """
114
  start_create_graph_tool_time = time.perf_counter()
 
 
115
  try:
116
  # Validate it's proper JSON
117
  config = json.loads(graph_config)
@@ -125,9 +128,15 @@ def Create_Graph_Tool(graph_config: str) -> str:
125
  # Add educational context if provided
126
  if educational_context:
127
  context_html = f'<div style="margin: 10px 0; padding: 10px; background: #f8f9fa; border-left: 4px solid #007bff; font-style: italic;">💡 {educational_context}</div>'
128
- return context_html + graph_html
 
 
 
 
 
 
129
 
130
- return graph_html
131
 
132
  except json.JSONDecodeError as e:
133
  logger.error(f"Invalid JSON provided to graph tool: {e}")
@@ -135,13 +144,8 @@ def Create_Graph_Tool(graph_config: str) -> str:
135
  except Exception as e:
136
  logger.error(f"Error in graph generation: {e}")
137
  return f'<p style="color:red;">Error creating graph: {str(e)}</p>'
138
- end_create_graph_tool_time = time.perf_counter()
139
- graph_create_graph_tool_time = end_create_graph_tool_time - start_create_graph_tool_time
140
-
141
- log_metric(f"Graph tool creation time: {graph_create_graph_tool_time:0.4f} seconds. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
142
 
143
  # --- Tool Decision Engine (Updated for LangGraph) ---
144
- start_graph_decision_time = time.perf_counter()
145
  class Tool_Decision_Engine:
146
  """Uses LLM to intelligently decide when visualization tools would be beneficial"""
147
 
@@ -168,6 +172,9 @@ Decision:"""
168
 
169
  def should_use_visualization(self, query: str) -> bool:
170
  """Enhanced decision logic with explicit exclusions"""
 
 
 
171
  try:
172
  # Explicit exclusions for common non-visual queries
173
  exclusion_patterns = [
@@ -183,6 +190,9 @@ Decision:"""
183
  # Check exclusions first
184
  for pattern in exclusion_patterns:
185
  if re.search(pattern, query_lower):
 
 
 
186
  return False
187
 
188
  # Create decision prompt
@@ -198,19 +208,21 @@ Decision:"""
198
  logger.info(f"Tool decision for '{query[:50]}...': {decision_text}")
199
 
200
  # More strict parsing
201
- if "YES" in decision_text and "NO" not in decision_text:
202
- return True
203
- return False
 
 
 
 
204
 
205
  except Exception as e:
206
  logger.error(f"Error in tool decision making: {e}")
207
- # Default to no tools if decision fails
 
 
208
  return False
209
 
210
- end_graph_decision_time = time.perf_counter()
211
- graph_decision_time = end_graph_decision_time - start_graph_decision_time
212
-
213
- log_metric(f"Tool decision time: {graph_decision_time:0.4f} seconds. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
214
  # --- System Prompt ---
215
  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 rather than providing direct answers to academic work.
216
  ## Core Educational Principles
@@ -266,6 +278,7 @@ class Qwen25SmallLLM(Runnable):
266
  super().__init__()
267
  logger.info(f"Loading model: {model_path} (use_4bit={use_4bit})")
268
  start_Loading_Model_time = time.perf_counter()
 
269
 
270
  try:
271
  # Load tokenizer
@@ -321,6 +334,9 @@ class Qwen25SmallLLM(Runnable):
321
 
322
  def invoke(self, input: Input, config=None) -> Output:
323
  """Main invoke method for Runnable compatibility"""
 
 
 
324
  # Handle both string and dict inputs for flexibility
325
  if isinstance(input, dict):
326
  prompt = input.get('input', str(input))
@@ -351,10 +367,19 @@ class Qwen25SmallLLM(Runnable):
351
  )
352
 
353
  new_tokens = [out[len(inp):] for inp, out in zip(inputs.input_ids, outputs)]
354
- return self.tokenizer.batch_decode(new_tokens, skip_special_tokens=True)[0].strip()
 
 
 
 
 
 
355
 
356
  except Exception as e:
357
  logger.error(f"Generation error: {e}")
 
 
 
358
  return f"[Error generating response: {str(e)}]"
359
 
360
  @property
@@ -371,6 +396,7 @@ class Educational_Agent:
371
 
372
  def __init__(self):
373
  start_init_and_langgraph_time = time.perf_counter()
 
374
 
375
  self.llm = Qwen25SmallLLM(model_path="Qwen/Qwen2.5-1.5B-Instruct", use_4bit=True)
376
  self.tool_decision_engine = Tool_Decision_Engine(self.llm)
@@ -407,6 +433,7 @@ class Educational_Agent:
407
  def call_model(state: EducationalAgentState) -> dict:
408
  """Call the model with tool decision logic"""
409
  start_call_model_time = time.perf_counter()
 
410
 
411
  messages = state["messages"]
412
 
@@ -503,6 +530,7 @@ Otherwise, provide a regular educational response.
503
  def handle_tools(state: EducationalAgentState) -> dict:
504
  """Handle tool execution"""
505
  start_handle_tools_time = time.perf_counter()
 
506
 
507
  try:
508
  messages = state["messages"]
@@ -573,7 +601,9 @@ Otherwise, provide a regular educational response.
573
 
574
  def chat(self, message: str, thread_id: str = "default") -> str:
575
  """Main chat interface"""
576
- start_chat_time = time.perf_counter()
 
 
577
  try:
578
  config = {"configurable": {"thread_id": thread_id}}
579
 
@@ -650,11 +680,6 @@ window.MathJax = {
650
  </script>
651
  '''
652
 
653
- end_init_and_langgraph_time = time.perf_counter()
654
- init_and_langgraph_time = end_init_and_langgraph_time - start_init_and_langgraph_time
655
-
656
- log_metric(f"Init and LangGraph workflow setup time: {init_and_langgraph_time:0.4f} seconds. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
657
-
658
  # --- HTML Head Content ---
659
  html_head_content = '''
660
  <meta charset="utf-8">
@@ -680,28 +705,36 @@ window.addEventListener('DOMContentLoaded', function () {
680
  '''
681
 
682
  # --- Core Logic Functions ---
683
- start_smart_truncate_time = time.perf_counter()
684
  def smart_truncate(text, max_length=3000):
685
  """Truncates text intelligently to the last full sentence or word."""
 
 
 
686
  if len(text) <= max_length:
 
 
 
687
  return text
688
 
689
  # Try to split by sentence
690
  sentences = re.split(r'(?<=[.!?])\s+', text[:max_length])
691
  if len(sentences) > 1:
692
- return ' '.join(sentences[:-1]) + "... [Response truncated - ask for continuation]"
693
- # Otherwise, split by word
694
- words = text[:max_length].split()
695
- return ' '.join(words[:-1]) + "... [Response truncated]"
 
696
 
697
- end_smart_truncate_time = time.perf_counter()
698
- smart_truncate_time = end_smart_truncate_time - start_smart_truncate_time
699
-
700
- log_metric(f"Smart Truncate time: {smart_truncate_time:0.4f} seconds. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
 
701
 
702
- start_generate_response_with_agent_time = time.perf_counter()
703
  def generate_response_with_agent(message, max_retries=3):
704
  """Generate response using LangGraph agent."""
 
 
705
 
706
  for attempt in range(max_retries):
707
  try:
@@ -711,7 +744,13 @@ def generate_response_with_agent(message, max_retries=3):
711
  # Use the agent's chat method
712
  response = current_agent.chat(message)
713
 
714
- return smart_truncate(response)
 
 
 
 
 
 
715
 
716
  except Exception as e:
717
  logger.error(f"Agent error (attempt {attempt + 1}): {e}")
@@ -719,61 +758,32 @@ def generate_response_with_agent(message, max_retries=3):
719
  time.sleep(2)
720
  continue
721
  else:
 
 
 
722
  return f"I apologize, but I encountered an error while processing your message: {str(e)}"
723
- end_generate_response_with_agent_time = time.perf_counter()
724
- generate_response_with_agent_time = end_generate_response_with_agent_time - start_generate_response_with_agent_time
725
-
726
- log_metric(f"Smart Truncate time: {generate_response_with_agent_time:0.4f} seconds. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
727
-
728
- start_chat_response_time = time.perf_counter()
729
 
730
  def chat_response(message, history=None):
731
  """Process chat message and return response."""
 
 
 
732
  try:
733
- # Track metrics with timing context
734
- start_time = time.time()
735
- timing_context = {
736
- 'start_time': start_time,
737
- 'chunk_count': 0,
738
- 'provider_latency': 0.0
739
- }
740
-
741
- try:
742
- # Log start of interaction
743
- metrics_tracker.log_interaction(
744
- query=message,
745
- response="",
746
- timing_context=timing_context,
747
- error_occurred=False
748
- )
749
- logger.info("Metrics interaction logged successfully")
750
- except Exception as metrics_error:
751
- logger.error(f"Error in metrics_tracker.log_interaction: {metrics_error}")
752
-
753
  # Generate response with LangGraph agent
754
  response = generate_response_with_agent(message)
755
 
756
- # Log final metrics
757
- try:
758
- metrics_tracker.log_interaction(
759
- query=message,
760
- response=response,
761
- timing_context=timing_context,
762
- error_occurred=False
763
- )
764
- except Exception as metrics_error:
765
- logger.error(f"Error in final metrics logging: {metrics_error}")
766
 
767
  return response
768
 
769
  except Exception as e:
770
  logger.error(f"Error in chat_response: {e}")
 
 
 
771
  return f"I apologize, but I encountered an error while processing your message: {str(e)}"
772
-
773
- end_chat_response_time = time.perf_counter()
774
- chat_response_time = end_chat_response_time - start_chat_response_time
775
-
776
- log_metric(f"Smart Truncate time: {chat_response_time:0.4f} seconds. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
777
 
778
  def respond_and_update(message, history):
779
  """Main function to handle user submission."""
@@ -792,13 +802,13 @@ def respond_and_update(message, history):
792
 
793
  def clear_chat():
794
  """Clear the chat history."""
795
- # Note: LangGraph handles conversation history automatically through thread_id
796
- # We could clear by using a new thread_id, but for now we'll keep it simple
797
  return [], ""
798
 
799
- start_agent_warmup_time = time.perf_counter()
800
  def warmup_agent():
801
  """Warm up the agent with a test query to preload everything."""
 
 
 
802
  logger.info("Warming up LangGraph agent with test query...")
803
  try:
804
  current_agent = get_agent()
@@ -807,18 +817,21 @@ def warmup_agent():
807
  test_response = current_agent.chat("Hello, this is a warmup test.")
808
  logger.info(f"LangGraph agent warmup completed successfully! Test response length: {len(test_response)} chars")
809
 
 
 
 
 
810
  except Exception as e:
811
  logger.error(f"LangGraph agent warmup failed: {e}")
812
-
813
- end_agent_warmup_time = time.perf_counter()
814
- agent_warmup_time = end_agent_warmup_time - start_agent_warmup_time
815
-
816
- log_metric(f"Smart Truncate time: {agent_warmup_time:0.4f} seconds. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
817
 
818
  # --- UI: Interface Creation ---
819
- start_create_interface_time = time.perf_counter()
820
  def create_interface():
821
  """Creates and configures the complete Gradio interface."""
 
 
822
 
823
  # Read CSS file
824
  custom_css = ""
@@ -881,13 +894,12 @@ def create_interface():
881
 
882
  # Apply CSS at the very end
883
  gr.HTML(f'<style>{custom_css}</style>')
884
-
885
- return demo
886
-
887
- end_create_interface_time = time.perf_counter()
888
- create_interface_time = end_create_interfacep_time - start_create_interface_time
889
-
890
- log_metric(f"Smart Truncate time: {create_interface_time:0.4f} seconds. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
891
 
892
  # --- Main Execution ---
893
  if __name__ == "__main__":
 
12
  import re
13
  import json
14
  from datetime import datetime
 
 
 
15
  from typing import Annotated, Sequence, TypedDict, List, Optional, Any, Type
16
  from pydantic import BaseModel, Field
17
 
 
35
  HF_TOKEN = os.getenv("HF_TOKEN") or os.getenv("HUGGINGFACEHUB_API_TOKEN")
36
  print("Environment variables loaded.")
37
 
38
+ # --- Setup main logger first ---
39
+ logging.basicConfig(level=logging.INFO)
40
+ logger = logging.getLogger(__name__)
41
+
42
  # --- Environment and Logging Setup ---
 
43
  def setup_metrics_logger():
44
  """Setup a simple file logger for human-readable metrics"""
45
+ metrics_logger = logging.getLogger('metrics')
46
+ metrics_logger.setLevel(logging.INFO)
47
 
48
  # Avoid duplicate handlers
49
+ if metrics_logger.handlers:
50
+ return metrics_logger
51
 
52
  # Create file handler
53
  log_file = 'performance_metrics.log'
 
57
  formatter = logging.Formatter('%(message)s')
58
  handler.setFormatter(formatter)
59
 
60
+ metrics_logger.addHandler(handler)
61
+ return metrics_logger
62
 
63
  # Initialize the logger
64
  metrics_logger = setup_metrics_logger()
65
 
66
  def log_metric(message):
67
  """Log a human-readable metric message with automatic timestamp"""
68
+ current_time = datetime.now()
69
  timestamped_message = f"{message} | Logged: {current_time:%Y-%m-%d %H:%M:%S}"
70
  metrics_logger.info(timestamped_message)
71
+ logger.info(timestamped_message)
72
 
73
  # Support both token names for flexibility
74
  hf_token = HF_TOKEN
 
113
  Include educational_context to explain why the visualization helps learning.
114
  """
115
  start_create_graph_tool_time = time.perf_counter()
116
+ current_time = datetime.now()
117
+
118
  try:
119
  # Validate it's proper JSON
120
  config = json.loads(graph_config)
 
128
  # Add educational context if provided
129
  if educational_context:
130
  context_html = f'<div style="margin: 10px 0; padding: 10px; background: #f8f9fa; border-left: 4px solid #007bff; font-style: italic;">💡 {educational_context}</div>'
131
+ result = context_html + graph_html
132
+ else:
133
+ result = graph_html
134
+
135
+ end_create_graph_tool_time = time.perf_counter()
136
+ graph_create_graph_tool_time = end_create_graph_tool_time - start_create_graph_tool_time
137
+ log_metric(f"Graph tool creation time: {graph_create_graph_tool_time:0.4f} seconds. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
138
 
139
+ return result
140
 
141
  except json.JSONDecodeError as e:
142
  logger.error(f"Invalid JSON provided to graph tool: {e}")
 
144
  except Exception as e:
145
  logger.error(f"Error in graph generation: {e}")
146
  return f'<p style="color:red;">Error creating graph: {str(e)}</p>'
 
 
 
 
147
 
148
  # --- Tool Decision Engine (Updated for LangGraph) ---
 
149
  class Tool_Decision_Engine:
150
  """Uses LLM to intelligently decide when visualization tools would be beneficial"""
151
 
 
172
 
173
  def should_use_visualization(self, query: str) -> bool:
174
  """Enhanced decision logic with explicit exclusions"""
175
+ start_graph_decision_time = time.perf_counter()
176
+ current_time = datetime.now()
177
+
178
  try:
179
  # Explicit exclusions for common non-visual queries
180
  exclusion_patterns = [
 
190
  # Check exclusions first
191
  for pattern in exclusion_patterns:
192
  if re.search(pattern, query_lower):
193
+ end_graph_decision_time = time.perf_counter()
194
+ graph_decision_time = end_graph_decision_time - start_graph_decision_time
195
+ log_metric(f"Tool decision time (excluded): {graph_decision_time:0.4f} seconds. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
196
  return False
197
 
198
  # Create decision prompt
 
208
  logger.info(f"Tool decision for '{query[:50]}...': {decision_text}")
209
 
210
  # More strict parsing
211
+ result = "YES" in decision_text and "NO" not in decision_text
212
+
213
+ end_graph_decision_time = time.perf_counter()
214
+ graph_decision_time = end_graph_decision_time - start_graph_decision_time
215
+ log_metric(f"Tool decision time: {graph_decision_time:0.4f} seconds. Decision: {result}. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
216
+
217
+ return result
218
 
219
  except Exception as e:
220
  logger.error(f"Error in tool decision making: {e}")
221
+ end_graph_decision_time = time.perf_counter()
222
+ graph_decision_time = end_graph_decision_time - start_graph_decision_time
223
+ log_metric(f"Tool decision time (error): {graph_decision_time:0.4f} seconds. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
224
  return False
225
 
 
 
 
 
226
  # --- System Prompt ---
227
  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 rather than providing direct answers to academic work.
228
  ## Core Educational Principles
 
278
  super().__init__()
279
  logger.info(f"Loading model: {model_path} (use_4bit={use_4bit})")
280
  start_Loading_Model_time = time.perf_counter()
281
+ current_time = datetime.now()
282
 
283
  try:
284
  # Load tokenizer
 
334
 
335
  def invoke(self, input: Input, config=None) -> Output:
336
  """Main invoke method for Runnable compatibility"""
337
+ start_invoke_time = time.perf_counter()
338
+ current_time = datetime.now()
339
+
340
  # Handle both string and dict inputs for flexibility
341
  if isinstance(input, dict):
342
  prompt = input.get('input', str(input))
 
367
  )
368
 
369
  new_tokens = [out[len(inp):] for inp, out in zip(inputs.input_ids, outputs)]
370
+ result = self.tokenizer.batch_decode(new_tokens, skip_special_tokens=True)[0].strip()
371
+
372
+ end_invoke_time = time.perf_counter()
373
+ invoke_time = end_invoke_time - start_invoke_time
374
+ log_metric(f"LLM Invoke time: {invoke_time:0.4f} seconds. Input length: {len(prompt)} chars. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
375
+
376
+ return result
377
 
378
  except Exception as e:
379
  logger.error(f"Generation error: {e}")
380
+ end_invoke_time = time.perf_counter()
381
+ invoke_time = end_invoke_time - start_invoke_time
382
+ log_metric(f"LLM Invoke time (error): {invoke_time:0.4f} seconds. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
383
  return f"[Error generating response: {str(e)}]"
384
 
385
  @property
 
396
 
397
  def __init__(self):
398
  start_init_and_langgraph_time = time.perf_counter()
399
+ current_time = datetime.now()
400
 
401
  self.llm = Qwen25SmallLLM(model_path="Qwen/Qwen2.5-1.5B-Instruct", use_4bit=True)
402
  self.tool_decision_engine = Tool_Decision_Engine(self.llm)
 
433
  def call_model(state: EducationalAgentState) -> dict:
434
  """Call the model with tool decision logic"""
435
  start_call_model_time = time.perf_counter()
436
+ current_time = datetime.now()
437
 
438
  messages = state["messages"]
439
 
 
530
  def handle_tools(state: EducationalAgentState) -> dict:
531
  """Handle tool execution"""
532
  start_handle_tools_time = time.perf_counter()
533
+ current_time = datetime.now()
534
 
535
  try:
536
  messages = state["messages"]
 
601
 
602
  def chat(self, message: str, thread_id: str = "default") -> str:
603
  """Main chat interface"""
604
+ start_chat_time = time.perf_counter()
605
+ current_time = datetime.now()
606
+
607
  try:
608
  config = {"configurable": {"thread_id": thread_id}}
609
 
 
680
  </script>
681
  '''
682
 
 
 
 
 
 
683
  # --- HTML Head Content ---
684
  html_head_content = '''
685
  <meta charset="utf-8">
 
705
  '''
706
 
707
  # --- Core Logic Functions ---
 
708
  def smart_truncate(text, max_length=3000):
709
  """Truncates text intelligently to the last full sentence or word."""
710
+ start_smart_truncate_time = time.perf_counter()
711
+ current_time = datetime.now()
712
+
713
  if len(text) <= max_length:
714
+ end_smart_truncate_time = time.perf_counter()
715
+ smart_truncate_time = end_smart_truncate_time - start_smart_truncate_time
716
+ log_metric(f"Smart Truncate time: {smart_truncate_time:0.4f} seconds. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
717
  return text
718
 
719
  # Try to split by sentence
720
  sentences = re.split(r'(?<=[.!?])\s+', text[:max_length])
721
  if len(sentences) > 1:
722
+ result = ' '.join(sentences[:-1]) + "... [Response truncated - ask for continuation]"
723
+ else:
724
+ # Otherwise, split by word
725
+ words = text[:max_length].split()
726
+ result = ' '.join(words[:-1]) + "... [Response truncated]"
727
 
728
+ end_smart_truncate_time = time.perf_counter()
729
+ smart_truncate_time = end_smart_truncate_time - start_smart_truncate_time
730
+ log_metric(f"Smart Truncate time: {smart_truncate_time:0.4f} seconds. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
731
+
732
+ return result
733
 
 
734
  def generate_response_with_agent(message, max_retries=3):
735
  """Generate response using LangGraph agent."""
736
+ start_generate_response_with_agent_time = time.perf_counter()
737
+ current_time = datetime.now()
738
 
739
  for attempt in range(max_retries):
740
  try:
 
744
  # Use the agent's chat method
745
  response = current_agent.chat(message)
746
 
747
+ result = smart_truncate(response)
748
+
749
+ end_generate_response_with_agent_time = time.perf_counter()
750
+ generate_response_with_agent_time = end_generate_response_with_agent_time - start_generate_response_with_agent_time
751
+ log_metric(f"Generate response with agent time: {generate_response_with_agent_time:0.4f} seconds. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
752
+
753
+ return result
754
 
755
  except Exception as e:
756
  logger.error(f"Agent error (attempt {attempt + 1}): {e}")
 
758
  time.sleep(2)
759
  continue
760
  else:
761
+ end_generate_response_with_agent_time = time.perf_counter()
762
+ generate_response_with_agent_time = end_generate_response_with_agent_time - start_generate_response_with_agent_time
763
+ log_metric(f"Generate response with agent time (error): {generate_response_with_agent_time:0.4f} seconds. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
764
  return f"I apologize, but I encountered an error while processing your message: {str(e)}"
 
 
 
 
 
 
765
 
766
  def chat_response(message, history=None):
767
  """Process chat message and return response."""
768
+ start_chat_response_time = time.perf_counter()
769
+ current_time = datetime.now()
770
+
771
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
772
  # Generate response with LangGraph agent
773
  response = generate_response_with_agent(message)
774
 
775
+ end_chat_response_time = time.perf_counter()
776
+ chat_response_time = end_chat_response_time - start_chat_response_time
777
+ log_metric(f"Chat response time: {chat_response_time:0.4f} seconds. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
 
 
 
 
 
 
 
778
 
779
  return response
780
 
781
  except Exception as e:
782
  logger.error(f"Error in chat_response: {e}")
783
+ end_chat_response_time = time.perf_counter()
784
+ chat_response_time = end_chat_response_time - start_chat_response_time
785
+ log_metric(f"Chat response time (error): {chat_response_time:0.4f} seconds. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
786
  return f"I apologize, but I encountered an error while processing your message: {str(e)}"
 
 
 
 
 
787
 
788
  def respond_and_update(message, history):
789
  """Main function to handle user submission."""
 
802
 
803
  def clear_chat():
804
  """Clear the chat history."""
 
 
805
  return [], ""
806
 
 
807
  def warmup_agent():
808
  """Warm up the agent with a test query to preload everything."""
809
+ start_agent_warmup_time = time.perf_counter()
810
+ current_time = datetime.now()
811
+
812
  logger.info("Warming up LangGraph agent with test query...")
813
  try:
814
  current_agent = get_agent()
 
817
  test_response = current_agent.chat("Hello, this is a warmup test.")
818
  logger.info(f"LangGraph agent warmup completed successfully! Test response length: {len(test_response)} chars")
819
 
820
+ end_agent_warmup_time = time.perf_counter()
821
+ agent_warmup_time = end_agent_warmup_time - start_agent_warmup_time
822
+ log_metric(f"Agent warmup time: {agent_warmup_time:0.4f} seconds. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
823
+
824
  except Exception as e:
825
  logger.error(f"LangGraph agent warmup failed: {e}")
826
+ end_agent_warmup_time = time.perf_counter()
827
+ agent_warmup_time = end_agent_warmup_time - start_agent_warmup_time
828
+ log_metric(f"Agent warmup time (error): {agent_warmup_time:0.4f} seconds. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
 
 
829
 
830
  # --- UI: Interface Creation ---
 
831
  def create_interface():
832
  """Creates and configures the complete Gradio interface."""
833
+ start_create_interface_time = time.perf_counter()
834
+ current_time = datetime.now()
835
 
836
  # Read CSS file
837
  custom_css = ""
 
894
 
895
  # Apply CSS at the very end
896
  gr.HTML(f'<style>{custom_css}</style>')
897
+
898
+ end_create_interface_time = time.perf_counter()
899
+ create_interface_time = end_create_interface_time - start_create_interface_time
900
+ log_metric(f"Create interface time: {create_interface_time:0.4f} seconds. Timestamp: {current_time:%Y-%m-%d %H:%M:%S}")
901
+
902
+ return demo
 
903
 
904
  # --- Main Execution ---
905
  if __name__ == "__main__":