JatsTheAIGen commited on
Commit
fa862fc
·
1 Parent(s): 11f308c

Process flow visualizer + key skills [for validation only) V4

Browse files
Files changed (3) hide show
  1. app.py +125 -103
  2. process_flow_visualizer.py +532 -29
  3. system_integrity_test.py +1 -0
app.py CHANGED
@@ -44,12 +44,7 @@ try:
44
  from src.agents.intent_agent import create_intent_agent
45
  from src.agents.synthesis_agent import create_synthesis_agent
46
  from src.agents.safety_agent import create_safety_agent
47
- try:
48
- from src.agents.skills_identification_agent import create_skills_identification_agent
49
- except ImportError:
50
- logger.warning("Skills identification agent not available, using placeholder")
51
- def create_skills_identification_agent(llm_router=None):
52
- return None
53
  from llm_router import LLMRouter
54
  from orchestrator_engine import MVPOrchestrator
55
  from context_manager import EfficientContextManager
@@ -384,35 +379,12 @@ def create_mobile_optimized_interface():
384
 
385
  # Wire up the submit handler INSIDE the gr.Blocks context
386
  if 'send_btn' in interface_components and 'message_input' in interface_components and 'chatbot' in interface_components:
387
- # Connect the submit handler with the GPU-decorated function
388
- # Include Details tab components as outputs
389
- outputs = [interface_components['chatbot'], interface_components['message_input']]
390
- if 'reasoning_display' in interface_components:
391
- outputs.append(interface_components['reasoning_display'])
392
- if 'performance_display' in interface_components:
393
- outputs.append(interface_components['performance_display'])
394
- if 'context_display' in interface_components:
395
- outputs.append(interface_components['context_display'])
396
- if 'session_info' in interface_components:
397
- outputs.append(interface_components['session_info'])
398
- if 'skills_tags' in interface_components:
399
- outputs.append(interface_components['skills_tags'])
400
- outputs.append(skills_display_row) # Add visibility control for skills display row
401
 
402
- # Add Process Flow outputs if available
403
- if process_flow_available:
404
- if 'flow_display' in interface_components:
405
- outputs.append(interface_components['flow_display'])
406
- if 'flow_stats' in interface_components:
407
- outputs.append(interface_components['flow_stats'])
408
- if 'performance_metrics' in interface_components:
409
- outputs.append(interface_components['performance_metrics'])
410
- if 'intent_details' in interface_components:
411
- outputs.append(interface_components['intent_details'])
412
- if 'synthesis_details' in interface_components:
413
- outputs.append(interface_components['synthesis_details'])
414
- if 'safety_details' in interface_components:
415
- outputs.append(interface_components['safety_details'])
416
 
417
  # Include session_info in inputs to pass session ID
418
  inputs = [interface_components['message_input'], interface_components['chatbot']]
@@ -768,10 +740,90 @@ async def process_message_async(message: str, history: Optional[List], session_i
768
 
769
  return error_history, "", reasoning_data, {}, {}, session_id, ""
770
 
771
- def process_message(message: str, history: Optional[List], session_id: Optional[str] = None) -> Tuple[List, str, dict, dict, dict, str, str, str, gr.update, str, dict, dict, dict, dict, dict]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
772
  """
773
  Synchronous wrapper for async processing
774
- Returns (history, empty_string, reasoning_data, performance_data, context_data, session_id, skills_html, skills_content, skills_visible_update, flow_display, flow_stats, performance_metrics, intent_details, synthesis_details, safety_details)
775
  """
776
  import asyncio
777
 
@@ -789,24 +841,8 @@ def process_message(message: str, history: Optional[List], session_id: Optional[
789
  skills_html = result[6]
790
  skills_content, skills_visible = _update_skills_display(skills_html)
791
 
792
- # Return all required outputs (15 total)
793
- return (
794
- result[0], # history
795
- result[1], # empty_string
796
- result[2], # reasoning_data
797
- result[3], # performance_data
798
- result[4], # context_data
799
- result[5], # session_id
800
- result[6], # skills_html
801
- skills_content, # skills_content
802
- gr.update(visible=skills_visible), # skills_visible_update
803
- "", # flow_display
804
- {}, # flow_stats
805
- {}, # performance_metrics
806
- {}, # intent_details
807
- {}, # synthesis_details
808
- {} # safety_details
809
- )
810
  except Exception as e:
811
  logger.error(f"Error in process_message: {e}", exc_info=True)
812
  error_history = list(history) if history else []
@@ -835,24 +871,9 @@ def process_message(message: str, history: Optional[List], session_id: Optional[
835
  "confidence_calibration": {"overall_confidence": 0.2, "sync_error": True}
836
  }
837
 
838
- # Return all required outputs for error case
839
- return (
840
- error_history, # history
841
- "", # empty_string
842
- reasoning_data, # reasoning_data
843
- {}, # performance_data
844
- {}, # context_data
845
- session_id, # session_id
846
- "", # skills_html
847
- "", # skills_content
848
- gr.update(visible=False), # skills_visible_update
849
- "", # flow_display
850
- {}, # flow_stats
851
- {}, # performance_metrics
852
- {}, # intent_details
853
- {}, # synthesis_details
854
- {} # safety_details
855
- )
856
 
857
  # Decorate the chat handler with GPU if available
858
  if SPACES_GPU_AVAILABLE and GPU is not None:
@@ -885,8 +906,11 @@ else:
885
  performance_data = result[3]
886
  context_data = result[4]
887
 
888
- # Create mock agent results for visualization
889
- intent_result = {
 
 
 
890
  "primary_intent": reasoning_data.get("chain_of_thought", {}).get("step_1", {}).get("hypothesis", "unknown"),
891
  "confidence_scores": {"overall": reasoning_data.get("confidence_calibration", {}).get("overall_confidence", 0.7)},
892
  "secondary_intents": [],
@@ -896,18 +920,31 @@ else:
896
  "agent_id": "INTENT_REC_001"
897
  }
898
 
899
- synthesis_result = {
 
 
 
 
 
 
 
 
 
 
 
 
900
  "final_response": result[0][-1]["content"] if result[0] else "",
901
  "draft_response": "",
902
  "source_references": ["INTENT_REC_001"],
903
  "coherence_score": 0.85,
904
  "synthesis_method": "llm_enhanced",
905
- "intent_alignment": {"intent_detected": intent_result["primary_intent"], "alignment_score": 0.8},
906
  "processing_time": performance_data.get("processing_time", 0.5) - 0.15,
907
  "agent_id": "RESP_SYNTH_001"
908
  }
909
 
910
- safety_result = {
 
911
  "original_response": result[0][-1]["content"] if result[0] else "",
912
  "safety_checked_response": result[0][-1]["content"] if result[0] else "",
913
  "warnings": [],
@@ -923,34 +960,22 @@ else:
923
  "agent_id": "SAFETY_BIAS_001"
924
  }
925
 
926
- # Update process flow visualization
 
 
 
927
  flow_updates = update_process_flow_visualization(
928
  user_input=message,
929
- intent_result=intent_result,
930
- synthesis_result=synthesis_result,
931
- safety_result=safety_result,
932
- final_response=result[0][-1]["content"] if result[0] else "",
933
  session_id=session_id,
934
- processing_time=performance_data.get("processing_time", 1.0)
 
935
  )
936
  except Exception as e:
937
  logger.error(f"Error updating process flow: {e}")
938
  flow_updates = {}
939
 
940
- # Return all updates including process flow
941
- if process_flow_available and flow_updates:
942
- return (
943
- result[0], result[1], result[2], result[3], result[4], result[5],
944
- result[6], skills_content, gr.update(visible=skills_visible),
945
- flow_updates.get("flow_display", ""),
946
- flow_updates.get("flow_stats", {}),
947
- flow_updates.get("performance_metrics", {}),
948
- flow_updates.get("intent_details", {}),
949
- flow_updates.get("synthesis_details", {}),
950
- flow_updates.get("safety_details", {})
951
- )
952
- else:
953
- return result
954
  chat_handler_fn = chat_handler_wrapper
955
 
956
  # Initialize orchestrator on module load
@@ -985,13 +1010,10 @@ def initialize_orchestrator():
985
  'safety_check': create_safety_agent(llm_router),
986
  }
987
 
988
- # Add skills identification agent if available
989
  skills_agent = create_skills_identification_agent(llm_router)
990
- if skills_agent is not None:
991
- agents['skills_identification'] = skills_agent
992
- logger.info("✓ Skills identification agent initialized")
993
- else:
994
- logger.info("⚠ Skills identification agent not available")
995
 
996
  logger.info(f"✓ Initialized {len(agents)} agents")
997
 
 
44
  from src.agents.intent_agent import create_intent_agent
45
  from src.agents.synthesis_agent import create_synthesis_agent
46
  from src.agents.safety_agent import create_safety_agent
47
+ from src.agents.skills_identification_agent import create_skills_identification_agent
 
 
 
 
 
48
  from llm_router import LLMRouter
49
  from orchestrator_engine import MVPOrchestrator
50
  from context_manager import EfficientContextManager
 
379
 
380
  # Wire up the submit handler INSIDE the gr.Blocks context
381
  if 'send_btn' in interface_components and 'message_input' in interface_components and 'chatbot' in interface_components:
382
+ # Store interface components globally for dynamic return values
383
+ global _interface_components
384
+ _interface_components = interface_components
 
 
 
 
 
 
 
 
 
 
 
385
 
386
+ # Build outputs list dynamically
387
+ outputs = _build_outputs_list(interface_components, process_flow_available)
 
 
 
 
 
 
 
 
 
 
 
 
388
 
389
  # Include session_info in inputs to pass session ID
390
  inputs = [interface_components['message_input'], interface_components['chatbot']]
 
740
 
741
  return error_history, "", reasoning_data, {}, {}, session_id, ""
742
 
743
+ # Global variable to store interface components for dynamic return values
744
+ _interface_components = {}
745
+
746
+ def _build_outputs_list(interface_components: dict, process_flow_available: bool) -> list:
747
+ """
748
+ Build outputs list dynamically based on available interface components
749
+ """
750
+ outputs = [interface_components['chatbot'], interface_components['message_input']]
751
+
752
+ # Add Details tab components
753
+ if 'reasoning_display' in interface_components:
754
+ outputs.append(interface_components['reasoning_display'])
755
+ if 'performance_display' in interface_components:
756
+ outputs.append(interface_components['performance_display'])
757
+ if 'context_display' in interface_components:
758
+ outputs.append(interface_components['context_display'])
759
+ if 'session_info' in interface_components:
760
+ outputs.append(interface_components['session_info'])
761
+ if 'skills_tags' in interface_components:
762
+ outputs.append(interface_components['skills_tags'])
763
+
764
+ # Add Process Flow outputs if available
765
+ if process_flow_available:
766
+ if 'flow_display' in interface_components:
767
+ outputs.append(interface_components['flow_display'])
768
+ if 'flow_stats' in interface_components:
769
+ outputs.append(interface_components['flow_stats'])
770
+ if 'performance_metrics' in interface_components:
771
+ outputs.append(interface_components['performance_metrics'])
772
+ if 'intent_details' in interface_components:
773
+ outputs.append(interface_components['intent_details'])
774
+ if 'synthesis_details' in interface_components:
775
+ outputs.append(interface_components['synthesis_details'])
776
+ if 'safety_details' in interface_components:
777
+ outputs.append(interface_components['safety_details'])
778
+
779
+ return outputs
780
+
781
+ def _build_dynamic_return_values(result: tuple, skills_content: str, interface_components: dict, process_flow_available: bool = False, flow_updates: dict = None) -> tuple:
782
+ """
783
+ Build return values dynamically based on available interface components
784
+ This ensures the return values match the outputs list exactly
785
+ """
786
+ return_values = []
787
+
788
+ # Base components (always present)
789
+ return_values.extend([
790
+ result[0], # chatbot (history)
791
+ result[1], # message_input (empty_string)
792
+ ])
793
+
794
+ # Add Details tab components
795
+ if 'reasoning_display' in interface_components:
796
+ return_values.append(result[2]) # reasoning_data
797
+ if 'performance_display' in interface_components:
798
+ return_values.append(result[3]) # performance_data
799
+ if 'context_display' in interface_components:
800
+ return_values.append(result[4]) # context_data
801
+ if 'session_info' in interface_components:
802
+ return_values.append(result[5]) # session_id
803
+ if 'skills_tags' in interface_components:
804
+ return_values.append(skills_content) # skills_content
805
+
806
+ # Add Process Flow outputs if available
807
+ if process_flow_available:
808
+ if 'flow_display' in interface_components:
809
+ return_values.append(flow_updates.get("flow_display", "") if flow_updates else "")
810
+ if 'flow_stats' in interface_components:
811
+ return_values.append(flow_updates.get("flow_stats", {}) if flow_updates else {})
812
+ if 'performance_metrics' in interface_components:
813
+ return_values.append(flow_updates.get("performance_metrics", {}) if flow_updates else {})
814
+ if 'intent_details' in interface_components:
815
+ return_values.append(flow_updates.get("intent_details", {}) if flow_updates else {})
816
+ if 'synthesis_details' in interface_components:
817
+ return_values.append(flow_updates.get("synthesis_details", {}) if flow_updates else {})
818
+ if 'safety_details' in interface_components:
819
+ return_values.append(flow_updates.get("safety_details", {}) if flow_updates else {})
820
+
821
+ return tuple(return_values)
822
+
823
+ def process_message(message: str, history: Optional[List], session_id: Optional[str] = None) -> tuple:
824
  """
825
  Synchronous wrapper for async processing
826
+ Returns dynamic tuple based on available interface components
827
  """
828
  import asyncio
829
 
 
841
  skills_html = result[6]
842
  skills_content, skills_visible = _update_skills_display(skills_html)
843
 
844
+ # Return dynamic values based on available components
845
+ return _build_dynamic_return_values(result, skills_content, _interface_components, process_flow_available)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
846
  except Exception as e:
847
  logger.error(f"Error in process_message: {e}", exc_info=True)
848
  error_history = list(history) if history else []
 
871
  "confidence_calibration": {"overall_confidence": 0.2, "sync_error": True}
872
  }
873
 
874
+ # Return dynamic values for error case
875
+ error_result = (error_history, "", reasoning_data, {}, {}, session_id, "")
876
+ return _build_dynamic_return_values(error_result, "", _interface_components, process_flow_available)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
877
 
878
  # Decorate the chat handler with GPU if available
879
  if SPACES_GPU_AVAILABLE and GPU is not None:
 
906
  performance_data = result[3]
907
  context_data = result[4]
908
 
909
+ # Create dynamic agent results for visualization
910
+ step_results = {}
911
+
912
+ # Intent result
913
+ step_results["intent_result"] = {
914
  "primary_intent": reasoning_data.get("chain_of_thought", {}).get("step_1", {}).get("hypothesis", "unknown"),
915
  "confidence_scores": {"overall": reasoning_data.get("confidence_calibration", {}).get("overall_confidence", 0.7)},
916
  "secondary_intents": [],
 
920
  "agent_id": "INTENT_REC_001"
921
  }
922
 
923
+ # Skills result (if available)
924
+ if "skills_result" in reasoning_data: # Check if skills data is in reasoning_data
925
+ step_results["skills_result"] = reasoning_data["skills_result"]
926
+ else:
927
+ step_results["skills_result"] = {
928
+ "identified_skills": [],
929
+ "confidence_score": 0.7,
930
+ "processing_time": performance_data.get("processing_time", 0.5) * 0.2,
931
+ "agent_id": "SKILLS_ID_001"
932
+ }
933
+
934
+ # Synthesis result
935
+ step_results["synthesis_result"] = {
936
  "final_response": result[0][-1]["content"] if result[0] else "",
937
  "draft_response": "",
938
  "source_references": ["INTENT_REC_001"],
939
  "coherence_score": 0.85,
940
  "synthesis_method": "llm_enhanced",
941
+ "intent_alignment": {"intent_detected": step_results["intent_result"]["primary_intent"], "alignment_score": 0.8},
942
  "processing_time": performance_data.get("processing_time", 0.5) - 0.15,
943
  "agent_id": "RESP_SYNTH_001"
944
  }
945
 
946
+ # Safety result
947
+ step_results["safety_result"] = {
948
  "original_response": result[0][-1]["content"] if result[0] else "",
949
  "safety_checked_response": result[0][-1]["content"] if result[0] else "",
950
  "warnings": [],
 
960
  "agent_id": "SAFETY_BIAS_001"
961
  }
962
 
963
+ # Final response
964
+ step_results["final_response"] = result[0][-1]["content"] if result[0] else ""
965
+
966
+ # Update process flow visualization dynamically
967
  flow_updates = update_process_flow_visualization(
968
  user_input=message,
 
 
 
 
969
  session_id=session_id,
970
+ processing_time=performance_data.get("processing_time", 1.0),
971
+ **step_results
972
  )
973
  except Exception as e:
974
  logger.error(f"Error updating process flow: {e}")
975
  flow_updates = {}
976
 
977
+ # Return dynamic values including process flow
978
+ return _build_dynamic_return_values(result, skills_content, _interface_components, process_flow_available, flow_updates)
 
 
 
 
 
 
 
 
 
 
 
 
979
  chat_handler_fn = chat_handler_wrapper
980
 
981
  # Initialize orchestrator on module load
 
1010
  'safety_check': create_safety_agent(llm_router),
1011
  }
1012
 
1013
+ # Add skills identification agent
1014
  skills_agent = create_skills_identification_agent(llm_router)
1015
+ agents['skills_identification'] = skills_agent
1016
+ logger.info("✓ Skills identification agent initialized")
 
 
 
1017
 
1018
  logger.info(f"✓ Initialized {len(agents)} agents")
1019
 
process_flow_visualizer.py CHANGED
@@ -18,6 +18,60 @@ class ProcessFlowVisualizer:
18
  self.flow_history = []
19
  self.current_session_id = None
20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  def create_process_flow_tab(self, interface_components: Dict[str, Any]) -> gr.TabItem:
22
  """
23
  Create the Process Flow visualization tab
@@ -118,48 +172,50 @@ class ProcessFlowVisualizer:
118
 
119
  def update_process_flow(self,
120
  user_input: str,
121
- intent_result: Dict[str, Any],
122
- synthesis_result: Dict[str, Any],
123
- safety_result: Dict[str, Any],
124
- final_response: str,
125
  session_id: str,
126
- processing_time: float) -> Dict[str, Any]:
 
127
  """
128
  Update the process flow visualization with new data
 
 
 
 
 
 
 
129
  """
130
  try:
131
- # Create flow entry
132
  flow_entry = {
133
  "timestamp": datetime.now().isoformat(),
134
  "session_id": session_id,
135
  "user_input": user_input,
136
- "intent_result": intent_result,
137
- "synthesis_result": synthesis_result,
138
- "safety_result": safety_result,
139
- "final_response": final_response,
140
  "processing_time": processing_time,
141
- "flow_id": str(uuid.uuid4())[:8]
 
142
  }
143
 
144
  # Add to history
145
  self.flow_history.append(flow_entry)
146
 
147
- # Generate flow visualization
148
- flow_html = self._generate_flow_html(flow_entry)
149
 
150
  # Generate statistics
151
  stats = self._generate_flow_statistics()
152
 
153
- # Generate performance metrics
154
- performance = self._generate_performance_metrics(flow_entry)
 
 
 
155
 
156
  return {
157
  "flow_display": flow_html,
158
  "flow_stats": stats,
159
  "performance_metrics": performance,
160
- "intent_details": intent_result,
161
- "synthesis_details": synthesis_result,
162
- "safety_details": safety_result
163
  }
164
 
165
  except Exception as e:
@@ -168,10 +224,461 @@ class ProcessFlowVisualizer:
168
  "flow_display": self._get_error_flow_html(str(e)),
169
  "flow_stats": {"error": str(e)},
170
  "performance_metrics": {"error": str(e)},
171
- "intent_details": {},
172
- "synthesis_details": {},
173
- "safety_details": {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
 
176
  def _generate_flow_html(self, flow_entry: Dict[str, Any]) -> str:
177
  """
@@ -532,18 +1039,14 @@ def create_process_flow_tab(interface_components: Dict[str, Any]) -> gr.TabItem:
532
  return process_flow_visualizer.create_process_flow_tab(interface_components)
533
 
534
  def update_process_flow_visualization(user_input: str,
535
- intent_result: Dict[str, Any],
536
- synthesis_result: Dict[str, Any],
537
- safety_result: Dict[str, Any],
538
- final_response: str,
539
  session_id: str,
540
- processing_time: float) -> Dict[str, Any]:
 
541
  """
542
- Update the process flow visualization
543
  """
544
  return process_flow_visualizer.update_process_flow(
545
- user_input, intent_result, synthesis_result, safety_result,
546
- final_response, session_id, processing_time
547
  )
548
 
549
  def clear_flow_history() -> Dict[str, Any]:
 
18
  self.flow_history = []
19
  self.current_session_id = None
20
 
21
+ # Dynamic flow step definitions - easily configurable for future modifications
22
+ self.flow_steps_config = {
23
+ "user_input": {
24
+ "icon": "👤",
25
+ "title": "User Input",
26
+ "description_template": '"{user_input[:100]}{ellipsis}"',
27
+ "metrics": ["input_length"]
28
+ },
29
+ "intent_recognition": {
30
+ "icon": "🧠",
31
+ "title": "Intent Recognition",
32
+ "description_template": "<strong>Intent:</strong> {primary_intent} | <strong>Confidence:</strong> {confidence:.2f}",
33
+ "metrics": ["confidence", "processing_time"],
34
+ "show_confidence_bar": True
35
+ },
36
+ "skills_identification": {
37
+ "icon": "🎯",
38
+ "title": "Skills Identification",
39
+ "description_template": "<strong>Skills Found:</strong> {skills_count} | <strong>Categories:</strong> {categories_count}",
40
+ "metrics": ["skills_count", "confidence_score"],
41
+ "show_confidence_bar": True
42
+ },
43
+ "response_synthesis": {
44
+ "icon": "🔄",
45
+ "title": "Response Synthesis",
46
+ "description_template": "<strong>Method:</strong> {synthesis_method} | <strong>Coherence:</strong> {coherence_score:.2f}",
47
+ "metrics": ["coherence_score", "synthesis_method"]
48
+ },
49
+ "safety_check": {
50
+ "icon": "🛡️",
51
+ "title": "Safety Check",
52
+ "description_template": "<strong>Safety Score:</strong> {safety_score:.2f} | <strong>Warnings:</strong> {warnings_count}",
53
+ "metrics": ["safety_score", "warnings_count"],
54
+ "show_confidence_bar": True
55
+ },
56
+ "final_response": {
57
+ "icon": "✅",
58
+ "title": "Final Response",
59
+ "description_template": "<strong>Length:</strong> {response_length} characters | <strong>Status:</strong> {status}",
60
+ "metrics": ["response_length", "status"]
61
+ }
62
+ }
63
+
64
+ # Dynamic metrics configuration
65
+ self.metrics_config = {
66
+ "processing_time": {"label": "Total Processing Time", "format": "{:.2f}s"},
67
+ "confidence": {"label": "Intent Confidence", "format": "{:.2f}"},
68
+ "safety_score": {"label": "Safety Score", "format": "{:.2f}"},
69
+ "llm_inferences": {"label": "LLM Inferences", "format": "{}"},
70
+ "agents_executed": {"label": "Agents Executed", "format": "{}"},
71
+ "skills_count": {"label": "Skills Identified", "format": "{}"},
72
+ "categories_count": {"label": "Categories Found", "format": "{}"}
73
+ }
74
+
75
  def create_process_flow_tab(self, interface_components: Dict[str, Any]) -> gr.TabItem:
76
  """
77
  Create the Process Flow visualization tab
 
172
 
173
  def update_process_flow(self,
174
  user_input: str,
 
 
 
 
175
  session_id: str,
176
+ processing_time: float,
177
+ **step_results) -> Dict[str, Any]:
178
  """
179
  Update the process flow visualization with new data
180
+ Dynamically handles any number of steps and results
181
+
182
+ Args:
183
+ user_input: The user's input message
184
+ session_id: Current session identifier
185
+ processing_time: Total processing time
186
+ **step_results: Dynamic step results (intent_result, synthesis_result, etc.)
187
  """
188
  try:
189
+ # Create flow entry with dynamic step results
190
  flow_entry = {
191
  "timestamp": datetime.now().isoformat(),
192
  "session_id": session_id,
193
  "user_input": user_input,
 
 
 
 
194
  "processing_time": processing_time,
195
+ "flow_id": str(uuid.uuid4())[:8],
196
+ **step_results # Dynamically include all step results
197
  }
198
 
199
  # Add to history
200
  self.flow_history.append(flow_entry)
201
 
202
+ # Generate flow visualization dynamically
203
+ flow_html = self._generate_dynamic_flow_html(flow_entry)
204
 
205
  # Generate statistics
206
  stats = self._generate_flow_statistics()
207
 
208
+ # Generate performance metrics dynamically
209
+ performance = self._generate_dynamic_performance_metrics(flow_entry)
210
+
211
+ # Generate dynamic details for each step
212
+ details = self._generate_dynamic_details(flow_entry)
213
 
214
  return {
215
  "flow_display": flow_html,
216
  "flow_stats": stats,
217
  "performance_metrics": performance,
218
+ **details # Dynamically include all step details
 
 
219
  }
220
 
221
  except Exception as e:
 
224
  "flow_display": self._get_error_flow_html(str(e)),
225
  "flow_stats": {"error": str(e)},
226
  "performance_metrics": {"error": str(e)},
227
+ **{f"{step}_details": {} for step in self.flow_steps_config.keys() if step != "user_input"}
228
+ }
229
+
230
+ def _generate_dynamic_flow_html(self, flow_entry: Dict[str, Any]) -> str:
231
+ """
232
+ Generate HTML visualization of the process flow dynamically
233
+ """
234
+ user_input = flow_entry["user_input"]
235
+ processing_time = flow_entry["processing_time"]
236
+
237
+ # Generate dynamic steps HTML
238
+ steps_html = self._generate_dynamic_steps_html(flow_entry)
239
+
240
+ # Generate dynamic metrics HTML
241
+ metrics_html = self._generate_dynamic_metrics_html(flow_entry)
242
+
243
+ html = f"""
244
+ <div class="process-flow-container">
245
+ <style>
246
+ .process-flow-container {{
247
+ font-family: 'Inter', system-ui, sans-serif;
248
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
249
+ border-radius: 12px;
250
+ padding: 20px;
251
+ margin: 10px 0;
252
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
253
+ }}
254
+
255
+ .flow-header {{
256
+ text-align: center;
257
+ margin-bottom: 20px;
258
+ padding: 15px;
259
+ background: rgba(255, 255, 255, 0.9);
260
+ border-radius: 8px;
261
+ border-left: 4px solid #2196F3;
262
+ }}
263
+
264
+ .flow-step {{
265
+ display: flex;
266
+ align-items: center;
267
+ margin: 15px 0;
268
+ padding: 12px;
269
+ background: rgba(255, 255, 255, 0.8);
270
+ border-radius: 8px;
271
+ border-left: 4px solid #4CAF50;
272
+ transition: all 0.3s ease;
273
+ }}
274
+
275
+ .flow-step:hover {{
276
+ transform: translateX(5px);
277
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
278
+ }}
279
+
280
+ .step-icon {{
281
+ font-size: 24px;
282
+ margin-right: 15px;
283
+ min-width: 40px;
284
+ }}
285
+
286
+ .step-content {{
287
+ flex: 1;
288
+ }}
289
+
290
+ .step-title {{
291
+ font-weight: 600;
292
+ color: #2c3e50;
293
+ margin-bottom: 5px;
294
+ }}
295
+
296
+ .step-details {{
297
+ font-size: 14px;
298
+ color: #7f8c8d;
299
+ line-height: 1.4;
300
+ }}
301
+
302
+ .metrics-grid {{
303
+ display: grid;
304
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
305
+ gap: 15px;
306
+ margin-top: 20px;
307
+ }}
308
+
309
+ .metric-card {{
310
+ background: rgba(255, 255, 255, 0.9);
311
+ padding: 15px;
312
+ border-radius: 8px;
313
+ text-align: center;
314
+ border-top: 3px solid #3498db;
315
+ }}
316
+
317
+ .metric-value {{
318
+ font-size: 24px;
319
+ font-weight: bold;
320
+ color: #2c3e50;
321
+ }}
322
+
323
+ .metric-label {{
324
+ font-size: 12px;
325
+ color: #7f8c8d;
326
+ text-transform: uppercase;
327
+ letter-spacing: 1px;
328
+ }}
329
+
330
+ .confidence-bar {{
331
+ width: 100%;
332
+ height: 8px;
333
+ background: #ecf0f1;
334
+ border-radius: 4px;
335
+ overflow: hidden;
336
+ margin-top: 5px;
337
+ }}
338
+
339
+ .confidence-fill {{
340
+ height: 100%;
341
+ background: linear-gradient(90deg, #e74c3c 0%, #f39c12 50%, #27ae60 100%);
342
+ transition: width 0.5s ease;
343
+ }}
344
+
345
+ @media (max-width: 768px) {{
346
+ .process-flow-container {{
347
+ padding: 15px;
348
+ margin: 5px 0;
349
+ }}
350
+
351
+ .flow-step {{
352
+ flex-direction: column;
353
+ text-align: center;
354
+ }}
355
+
356
+ .step-icon {{
357
+ margin-right: 0;
358
+ margin-bottom: 10px;
359
+ }}
360
+
361
+ .metrics-grid {{
362
+ grid-template-columns: 1fr;
363
+ }}
364
+ }}
365
+ </style>
366
+
367
+ <div class="flow-header">
368
+ <h3>🔄 Process Flow Visualization</h3>
369
+ <p><strong>Session:</strong> {flow_entry["session_id"]} |
370
+ <strong>Time:</strong> {processing_time:.2f}s |
371
+ <strong>Flow ID:</strong> {flow_entry["flow_id"]}</p>
372
+ </div>
373
+
374
+ {steps_html}
375
+
376
+ {metrics_html}
377
+ </div>
378
+ """
379
+
380
+ return html
381
+
382
+ def _generate_dynamic_steps_html(self, flow_entry: Dict[str, Any]) -> str:
383
+ """
384
+ Generate HTML for flow steps dynamically based on available data
385
+ """
386
+ steps_html = ""
387
+
388
+ for step_key, step_config in self.flow_steps_config.items():
389
+ if step_key == "user_input":
390
+ # Special handling for user input
391
+ user_input = flow_entry["user_input"]
392
+ ellipsis = "..." if len(user_input) > 100 else ""
393
+ description = step_config["description_template"].format(
394
+ user_input=user_input,
395
+ ellipsis=ellipsis
396
+ )
397
+ else:
398
+ # Handle other steps dynamically
399
+ step_result = flow_entry.get(f"{step_key}_result", {})
400
+ if not step_result:
401
+ continue # Skip steps that don't have results
402
+
403
+ # Extract data for this step
404
+ step_data = self._extract_step_data(step_key, step_result, flow_entry)
405
+ description = step_config["description_template"].format(**step_data)
406
+
407
+ # Generate confidence bar if needed
408
+ confidence_bar = ""
409
+ if step_config.get("show_confidence_bar") and step_key != "user_input":
410
+ step_result = flow_entry.get(f"{step_key}_result", {})
411
+ confidence_value = self._extract_confidence_value(step_key, step_result)
412
+ if confidence_value is not None:
413
+ confidence_bar = f"""
414
+ <div class="confidence-bar">
415
+ <div class="confidence-fill" style="width: {confidence_value * 100}%"></div>
416
+ </div>
417
+ """
418
+
419
+ steps_html += f"""
420
+ <div class="flow-step">
421
+ <div class="step-icon">{step_config["icon"]}</div>
422
+ <div class="step-content">
423
+ <div class="step-title">{step_config["title"]}</div>
424
+ <div class="step-details">
425
+ {description}
426
+ {confidence_bar}
427
+ </div>
428
+ </div>
429
+ </div>
430
+ """
431
+
432
+ return steps_html
433
+
434
+ def _generate_dynamic_metrics_html(self, flow_entry: Dict[str, Any]) -> str:
435
+ """
436
+ Generate HTML for metrics dynamically based on available data
437
+ """
438
+ metrics_html = '<div class="metrics-grid">'
439
+
440
+ # Calculate dynamic metrics
441
+ metrics_data = self._calculate_dynamic_metrics(flow_entry)
442
+
443
+ for metric_key, metric_value in metrics_data.items():
444
+ if metric_key in self.metrics_config:
445
+ config = self.metrics_config[metric_key]
446
+ formatted_value = config["format"].format(metric_value)
447
+ label = config["label"]
448
+
449
+ metrics_html += f"""
450
+ <div class="metric-card">
451
+ <div class="metric-value">{formatted_value}</div>
452
+ <div class="metric-label">{label}</div>
453
+ </div>
454
+ """
455
+
456
+ metrics_html += '</div>'
457
+ return metrics_html
458
+
459
+ def _extract_step_data(self, step_key: str, step_result: Dict[str, Any], flow_entry: Dict[str, Any]) -> Dict[str, Any]:
460
+ """
461
+ Extract relevant data for a specific step
462
+ """
463
+ data = {}
464
+
465
+ if step_key == "intent_recognition":
466
+ data.update({
467
+ "primary_intent": step_result.get("primary_intent", "unknown"),
468
+ "confidence": step_result.get("confidence_scores", {}).get(step_result.get("primary_intent", "unknown"), 0.0),
469
+ "processing_time": step_result.get("processing_time", 0)
470
+ })
471
+ elif step_key == "skills_identification":
472
+ identified_skills = step_result.get("identified_skills", [])
473
+ data.update({
474
+ "skills_count": len(identified_skills),
475
+ "categories_count": len(set(skill.get("category", "unknown") for skill in identified_skills)),
476
+ "confidence_score": step_result.get("confidence_score", 0.0)
477
+ })
478
+ elif step_key == "response_synthesis":
479
+ data.update({
480
+ "synthesis_method": step_result.get("synthesis_method", "unknown"),
481
+ "coherence_score": step_result.get("coherence_score", 0.0)
482
+ })
483
+ elif step_key == "safety_check":
484
+ safety_analysis = step_result.get("safety_analysis", {})
485
+ data.update({
486
+ "safety_score": safety_analysis.get("overall_safety_score", 0.0),
487
+ "warnings_count": len(step_result.get("warnings", []))
488
+ })
489
+ elif step_key == "final_response":
490
+ final_response = flow_entry.get("final_response", "")
491
+ data.update({
492
+ "response_length": len(final_response),
493
+ "status": "Delivered successfully"
494
+ })
495
+
496
+ return data
497
+
498
+ def _extract_confidence_value(self, step_key: str, step_result: Dict[str, Any]) -> Optional[float]:
499
+ """
500
+ Extract confidence value for a step
501
+ """
502
+ if step_key == "intent_recognition":
503
+ return step_result.get("confidence_scores", {}).get(step_result.get("primary_intent", "unknown"), 0.0)
504
+ elif step_key == "skills_identification":
505
+ return step_result.get("confidence_score", 0.0)
506
+ elif step_key == "safety_check":
507
+ return step_result.get("safety_analysis", {}).get("overall_safety_score", 0.0)
508
+ return None
509
+
510
+ def _calculate_dynamic_metrics(self, flow_entry: Dict[str, Any]) -> Dict[str, Any]:
511
+ """
512
+ Calculate metrics dynamically based on available data
513
+ """
514
+ metrics = {
515
+ "processing_time": flow_entry.get("processing_time", 0)
516
+ }
517
+
518
+ # Count LLM inferences dynamically
519
+ llm_inferences = 0
520
+ agents_executed = 0
521
+
522
+ for step_key in self.flow_steps_config.keys():
523
+ if step_key == "user_input" or step_key == "final_response":
524
+ continue
525
+
526
+ step_result = flow_entry.get(f"{step_key}_result", {})
527
+ if step_result:
528
+ agents_executed += 1
529
+ # Each agent typically makes 1-2 LLM inferences
530
+ llm_inferences += step_result.get("llm_inferences", 1)
531
+
532
+ metrics.update({
533
+ "llm_inferences": llm_inferences,
534
+ "agents_executed": agents_executed
535
+ })
536
+
537
+ # Add step-specific metrics
538
+ intent_result = flow_entry.get("intent_result", {})
539
+ if intent_result:
540
+ metrics["confidence"] = intent_result.get("confidence_scores", {}).get(intent_result.get("primary_intent", "unknown"), 0.0)
541
+
542
+ safety_result = flow_entry.get("safety_result", {})
543
+ if safety_result:
544
+ metrics["safety_score"] = safety_result.get("safety_analysis", {}).get("overall_safety_score", 0.0)
545
+
546
+ skills_result = flow_entry.get("skills_result", {})
547
+ if skills_result:
548
+ metrics["skills_count"] = len(skills_result.get("identified_skills", []))
549
+ metrics["categories_count"] = len(set(skill.get("category", "unknown") for skill in skills_result.get("identified_skills", [])))
550
+
551
+ return metrics
552
+
553
+ def _generate_dynamic_details(self, flow_entry: Dict[str, Any]) -> Dict[str, Any]:
554
+ """
555
+ Generate dynamic details for each step
556
+ """
557
+ details = {}
558
+
559
+ for step_key in self.flow_steps_config.keys():
560
+ if step_key == "user_input":
561
+ continue
562
+
563
+ step_result = flow_entry.get(f"{step_key}_result", {})
564
+ if step_result:
565
+ details[f"{step_key}_details"] = step_result
566
+ else:
567
+ details[f"{step_key}_details"] = {}
568
+
569
+ return details
570
+
571
+ def _generate_dynamic_performance_metrics(self, flow_entry: Dict[str, Any]) -> Dict[str, Any]:
572
+ """
573
+ Generate performance metrics dynamically
574
+ """
575
+ performance = {
576
+ "overall": {
577
+ "total_processing_time": flow_entry.get("processing_time", 0),
578
+ "success_rate": 1.0
579
  }
580
+ }
581
+
582
+ # Dynamically add metrics for each step
583
+ for step_key in self.flow_steps_config.keys():
584
+ if step_key == "user_input" or step_key == "final_response":
585
+ continue
586
+
587
+ step_result = flow_entry.get(f"{step_key}_result", {})
588
+ if step_result:
589
+ step_performance = {
590
+ "processing_time": step_result.get("processing_time", 0),
591
+ "agent_id": step_result.get("agent_id", "unknown")
592
+ }
593
+
594
+ # Add step-specific metrics
595
+ if step_key == "intent_recognition":
596
+ step_performance["confidence"] = step_result.get("confidence_scores", {}).get(step_result.get("primary_intent", "unknown"), 0.0)
597
+ elif step_key == "skills_identification":
598
+ step_performance["confidence_score"] = step_result.get("confidence_score", 0.0)
599
+ step_performance["skills_count"] = len(step_result.get("identified_skills", []))
600
+ elif step_key == "response_synthesis":
601
+ step_performance["coherence_score"] = step_result.get("coherence_score", 0.0)
602
+ step_performance["synthesis_method"] = step_result.get("synthesis_method", "unknown")
603
+ elif step_key == "safety_check":
604
+ step_performance["safety_score"] = step_result.get("safety_analysis", {}).get("overall_safety_score", 0.0)
605
+ step_performance["warnings_count"] = len(step_result.get("warnings", []))
606
+
607
+ performance[step_key] = step_performance
608
+
609
+ # Calculate overall metrics
610
+ performance["overall"]["llm_inferences"] = sum(step_result.get("llm_inferences", 1) for step_result in [flow_entry.get(f"{step}_result", {}) for step in self.flow_steps_config.keys() if step not in ["user_input", "final_response"]] if step_result)
611
+ performance["overall"]["agents_executed"] = len([step_result for step_result in [flow_entry.get(f"{step}_result", {}) for step in self.flow_steps_config.keys() if step not in ["user_input", "final_response"]] if step_result])
612
+
613
+ return performance
614
+
615
+ def add_flow_step(self, step_key: str, step_config: Dict[str, Any]) -> None:
616
+ """
617
+ Add a new flow step dynamically for future modifications
618
+
619
+ Args:
620
+ step_key: Unique identifier for the step
621
+ step_config: Configuration dictionary with icon, title, description_template, etc.
622
+ """
623
+ self.flow_steps_config[step_key] = step_config
624
+ logger.info(f"Added new flow step: {step_key}")
625
+
626
+ def remove_flow_step(self, step_key: str) -> None:
627
+ """
628
+ Remove a flow step dynamically
629
+
630
+ Args:
631
+ step_key: Identifier of the step to remove
632
+ """
633
+ if step_key in self.flow_steps_config:
634
+ del self.flow_steps_config[step_key]
635
+ logger.info(f"Removed flow step: {step_key}")
636
+ else:
637
+ logger.warning(f"Flow step not found: {step_key}")
638
+
639
+ def add_metric(self, metric_key: str, metric_config: Dict[str, Any]) -> None:
640
+ """
641
+ Add a new metric dynamically
642
+
643
+ Args:
644
+ metric_key: Unique identifier for the metric
645
+ metric_config: Configuration with label and format
646
+ """
647
+ self.metrics_config[metric_key] = metric_config
648
+ logger.info(f"Added new metric: {metric_key}")
649
+
650
+ def get_flow_steps(self) -> Dict[str, Any]:
651
+ """
652
+ Get current flow steps configuration
653
+ """
654
+ return self.flow_steps_config.copy()
655
+
656
+ def get_metrics_config(self) -> Dict[str, Any]:
657
+ """
658
+ Get current metrics configuration
659
+ """
660
+ return self.metrics_config.copy()
661
+
662
+ def configure_step_order(self, step_order: List[str]) -> None:
663
+ """
664
+ Configure the order of steps in the flow
665
+
666
+ Args:
667
+ step_order: List of step keys in desired order
668
+ """
669
+ # Reorder the flow_steps_config based on the provided order
670
+ ordered_config = {}
671
+ for step_key in step_order:
672
+ if step_key in self.flow_steps_config:
673
+ ordered_config[step_key] = self.flow_steps_config[step_key]
674
+
675
+ # Add any remaining steps not in the order list
676
+ for step_key, step_config in self.flow_steps_config.items():
677
+ if step_key not in ordered_config:
678
+ ordered_config[step_key] = step_config
679
+
680
+ self.flow_steps_config = ordered_config
681
+ logger.info(f"Reordered flow steps: {step_order}")
682
 
683
  def _generate_flow_html(self, flow_entry: Dict[str, Any]) -> str:
684
  """
 
1039
  return process_flow_visualizer.create_process_flow_tab(interface_components)
1040
 
1041
  def update_process_flow_visualization(user_input: str,
 
 
 
 
1042
  session_id: str,
1043
+ processing_time: float,
1044
+ **step_results) -> Dict[str, Any]:
1045
  """
1046
+ Update the process flow visualization dynamically
1047
  """
1048
  return process_flow_visualizer.update_process_flow(
1049
+ user_input, session_id, processing_time, **step_results
 
1050
  )
1051
 
1052
  def clear_flow_history() -> Dict[str, Any]:
system_integrity_test.py ADDED
@@ -0,0 +1 @@
 
 
1
+