tan-en-yao commited on
Commit
98bb628
·
1 Parent(s): 55890db

refactor: let Controller reason from chat history

Browse files
Files changed (2) hide show
  1. agents/orchestrator.py +20 -45
  2. app.py +5 -4
agents/orchestrator.py CHANGED
@@ -603,19 +603,13 @@ Return JSON:
603
 
604
  # Autonomous reasoning features
605
  self._reasoning_trace: Optional[ReasoningTrace] = None
606
- self._pending_follow_up: Optional[CompletenessAnalysis] = None
607
-
608
- # Accumulated context from incomplete input gathering (for multi-turn)
609
- self._accumulated_context: Dict[str, Any] = {}
610
- # Track the last question we asked (for context-aware classification)
611
- self._last_question: Optional[str] = None
612
 
613
  # Location data captured from tool results for map updates
614
  self._captured_location: Optional[Dict[str, Any]] = None
615
 
616
- # Multi-turn conversation history (per-session, cross-user isolated via gr.State)
617
  self._conversation_history: List[Dict[str, Any]] = []
618
- self._max_history_turns: int = 5 # Keep last 5 reports for context
619
 
620
  # Eager initialization to avoid delay on first request
621
  if eager_init and self.api_key:
@@ -993,7 +987,7 @@ Return JSON:
993
  self,
994
  user_message: str,
995
  image_analysis: Optional[str] = None,
996
- log_catcher: Optional[Any] = None
997
  ) -> Generator[Tuple[str, dict], None, None]:
998
  """
999
  Process infrastructure report through TRUE AUTONOMOUS multi-agent system.
@@ -1008,7 +1002,7 @@ Return JSON:
1008
  Args:
1009
  user_message: User's description of the issue
1010
  image_analysis: Optional image analysis from Claude Vision
1011
- log_catcher: Optional LogCatcher (kept for backward compatibility)
1012
 
1013
  Yields:
1014
  Tuples of (event_type, event_data) for UI updates
@@ -1047,15 +1041,19 @@ Return JSON:
1047
  except Empty:
1048
  break
1049
 
1050
- # Build context from previous turns
1051
- context_parts = []
1052
- if self._accumulated_context:
1053
- context_parts.append(f"Previous context: {json.dumps(self._accumulated_context)}")
1054
- if self._last_question:
1055
- context_parts.append(f"Last question asked: \"{self._last_question}\"")
1056
- if self._conversation_history:
1057
- context_parts.append(f"Previous reports in session: {len(self._conversation_history)}")
1058
- context = "\n".join(context_parts) if context_parts else "No previous context."
 
 
 
 
1059
 
1060
  trace.think("Delegating full control to Controller agent", "autonomy")
1061
  yield ("planning", {"status": "started", "message": "Controller is planning..."})
@@ -1064,11 +1062,10 @@ Return JSON:
1064
  # TRUE AUTONOMOUS TASK - Controller has full decision-making power
1065
  task = f"""You are the autonomous Controller for FixMyNeighborhood, an NYC infrastructure reporting system.
1066
 
1067
- USER INPUT: "{user_message}"
1068
  {f'IMAGE ANALYSIS: {image_analysis}' if image_analysis else 'No image provided.'}
1069
 
1070
- CONTEXT:
1071
- {context}
1072
 
1073
  YOUR AGENTS:
1074
  - triage_agent: Validates NYC addresses (call validate_address tool), classifies images as infrastructure or not
@@ -1239,6 +1236,7 @@ NOW: Analyze the input and decide what to do. You have full autonomy."""
1239
  print(f"[Controller] Raw result: {raw_result[:500]}...")
1240
 
1241
  # Detect response type (Controller decided what to do)
 
1242
  if "FOLLOW_UP:" in raw_result:
1243
  # Controller decided more info is needed
1244
  follow_up_match = re.search(r'FOLLOW_UP:\s*(.+?)(?:$|REJECTION:|REPORT:)', raw_result, re.DOTALL)
@@ -1246,10 +1244,6 @@ NOW: Analyze the input and decide what to do. You have full autonomy."""
1246
  follow_up_text = follow_up_match.group(1).strip()
1247
  trace.decide("Controller decided: need more information", confidence=0.9)
1248
 
1249
- # Extract what Controller understood for context
1250
- self._accumulated_context = self._extract_context_from_result(raw_result)
1251
- self._last_question = follow_up_text
1252
-
1253
  yield ("reasoning_update", {"trace": trace.to_display()})
1254
  yield ("needs_info", {
1255
  "message": follow_up_text,
@@ -1264,10 +1258,6 @@ NOW: Analyze the input and decide what to do. You have full autonomy."""
1264
  rejection_text = rejection_match.group(1).strip()
1265
  trace.decide("Controller decided: reject request", confidence=0.9)
1266
 
1267
- # Clear context on rejection
1268
- self._accumulated_context = {}
1269
- self._last_question = None
1270
-
1271
  yield ("reasoning_update", {"trace": trace.to_display()})
1272
  yield ("needs_info", {
1273
  "message": rejection_text,
@@ -1310,8 +1300,6 @@ NOW: Analyze the input and decide what to do. You have full autonomy."""
1310
  "response_days": self._extract_field(raw_result, r'(?:Expected Response|response)[:\s]+~?(\d+)', "7"),
1311
  }
1312
  self._add_to_history(user_message, report_summary)
1313
- self._accumulated_context = {}
1314
- self._last_question = None
1315
  trace.think(f"Report stored (total: {len(self._conversation_history)})", "multi-turn")
1316
 
1317
  yield ("reasoning_update", {"trace": trace.to_display()})
@@ -1325,19 +1313,6 @@ NOW: Analyze the input and decide what to do. You have full autonomy."""
1325
  "reasoning_trace": trace.to_display(),
1326
  })
1327
 
1328
- def _extract_context_from_result(self, result: str) -> Dict[str, Any]:
1329
- """Extract understood context from Controller's response."""
1330
- context = {}
1331
- # Try to extract what Controller understood
1332
- if "pothole" in result.lower():
1333
- context["issue_type"] = "pothole"
1334
- elif "streetlight" in result.lower():
1335
- context["issue_type"] = "streetlight"
1336
- elif "drain" in result.lower():
1337
- context["issue_type"] = "drain"
1338
- # Add more as needed
1339
- return context
1340
-
1341
  def _format_result(self, result: str, user_message: str) -> str:
1342
  """Format the controller result into a user-friendly message."""
1343
  # More flexible patterns to handle various agent output formats
 
603
 
604
  # Autonomous reasoning features
605
  self._reasoning_trace: Optional[ReasoningTrace] = None
 
 
 
 
 
 
606
 
607
  # Location data captured from tool results for map updates
608
  self._captured_location: Optional[Dict[str, Any]] = None
609
 
610
+ # Completed reports history (for session awareness)
611
  self._conversation_history: List[Dict[str, Any]] = []
612
+ self._max_history_turns: int = 5
613
 
614
  # Eager initialization to avoid delay on first request
615
  if eager_init and self.api_key:
 
987
  self,
988
  user_message: str,
989
  image_analysis: Optional[str] = None,
990
+ chat_history: Optional[List[Dict[str, str]]] = None,
991
  ) -> Generator[Tuple[str, dict], None, None]:
992
  """
993
  Process infrastructure report through TRUE AUTONOMOUS multi-agent system.
 
1002
  Args:
1003
  user_message: User's description of the issue
1004
  image_analysis: Optional image analysis from Claude Vision
1005
+ chat_history: Full conversation history for Controller to reason about
1006
 
1007
  Yields:
1008
  Tuples of (event_type, event_data) for UI updates
 
1041
  except Empty:
1042
  break
1043
 
1044
+ # Format conversation history for Controller (TRUE AUTONOMOUS - Controller reasons about context)
1045
+ conversation_context = ""
1046
+ if chat_history and len(chat_history) > 0:
1047
+ conv_lines = ["CONVERSATION HISTORY (you have full context - reason about what user needs):"]
1048
+ for msg in chat_history:
1049
+ role = msg.get("role", "unknown")
1050
+ content = msg.get("content", "")
1051
+ if role == "user":
1052
+ conv_lines.append(f"User: {content}")
1053
+ elif role == "assistant":
1054
+ # Truncate long assistant responses
1055
+ conv_lines.append(f"Agent: {content[:500]}{'...' if len(content) > 500 else ''}")
1056
+ conversation_context = "\n".join(conv_lines)
1057
 
1058
  trace.think("Delegating full control to Controller agent", "autonomy")
1059
  yield ("planning", {"status": "started", "message": "Controller is planning..."})
 
1062
  # TRUE AUTONOMOUS TASK - Controller has full decision-making power
1063
  task = f"""You are the autonomous Controller for FixMyNeighborhood, an NYC infrastructure reporting system.
1064
 
1065
+ CURRENT USER INPUT: "{user_message}"
1066
  {f'IMAGE ANALYSIS: {image_analysis}' if image_analysis else 'No image provided.'}
1067
 
1068
+ {conversation_context if conversation_context else 'No previous conversation.'}
 
1069
 
1070
  YOUR AGENTS:
1071
  - triage_agent: Validates NYC addresses (call validate_address tool), classifies images as infrastructure or not
 
1236
  print(f"[Controller] Raw result: {raw_result[:500]}...")
1237
 
1238
  # Detect response type (Controller decided what to do)
1239
+ # No Python context extraction - Controller reasons from chat history
1240
  if "FOLLOW_UP:" in raw_result:
1241
  # Controller decided more info is needed
1242
  follow_up_match = re.search(r'FOLLOW_UP:\s*(.+?)(?:$|REJECTION:|REPORT:)', raw_result, re.DOTALL)
 
1244
  follow_up_text = follow_up_match.group(1).strip()
1245
  trace.decide("Controller decided: need more information", confidence=0.9)
1246
 
 
 
 
 
1247
  yield ("reasoning_update", {"trace": trace.to_display()})
1248
  yield ("needs_info", {
1249
  "message": follow_up_text,
 
1258
  rejection_text = rejection_match.group(1).strip()
1259
  trace.decide("Controller decided: reject request", confidence=0.9)
1260
 
 
 
 
 
1261
  yield ("reasoning_update", {"trace": trace.to_display()})
1262
  yield ("needs_info", {
1263
  "message": rejection_text,
 
1300
  "response_days": self._extract_field(raw_result, r'(?:Expected Response|response)[:\s]+~?(\d+)', "7"),
1301
  }
1302
  self._add_to_history(user_message, report_summary)
 
 
1303
  trace.think(f"Report stored (total: {len(self._conversation_history)})", "multi-turn")
1304
 
1305
  yield ("reasoning_update", {"trace": trace.to_display()})
 
1313
  "reasoning_trace": trace.to_display(),
1314
  })
1315
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1316
  def _format_result(self, result: str, user_message: str) -> str:
1317
  """Format the controller result into a user-friendly message."""
1318
  # More flexible patterns to handle various agent output formats
app.py CHANGED
@@ -330,6 +330,7 @@ def process_report(message: str, image, history: list, session_logs: list, sessi
330
  """Build all output values for yield."""
331
  return (
332
  history,
 
333
  get_processing_status(),
334
  get_status_text(agent_states["research"]["status"], agent_states["research"].get("duration_formatted")),
335
  get_tools_markdown(agent_states["research"]["tools"]),
@@ -428,8 +429,8 @@ def process_report(message: str, image, history: list, session_logs: list, sessi
428
  progress_msg_idx += 1
429
 
430
  try:
431
- # Process through orchestrator with log_catcher for real-time events
432
- for event_type, data in orchestrator.process(message, image_analysis, log_catcher):
433
 
434
  # Handle reasoning trace updates (Autonomous Feature #1)
435
  if event_type == "reasoning_update":
@@ -816,7 +817,7 @@ with gr.Blocks(title="FixMyNeighborhood - Multi-Agent Reporter") as demo:
816
  process_report,
817
  inputs=[message_input, image_input, chatbot, session_logs, session_orchestrator, reasoning_display],
818
  outputs=[
819
- chatbot, processing_status, research_status, research_tools,
820
  report_status, report_tools, map_display, logs_display,
821
  session_logs, session_orchestrator, reasoning_display
822
  ]
@@ -826,7 +827,7 @@ with gr.Blocks(title="FixMyNeighborhood - Multi-Agent Reporter") as demo:
826
  process_report,
827
  inputs=[message_input, image_input, chatbot, session_logs, session_orchestrator, reasoning_display],
828
  outputs=[
829
- chatbot, processing_status, research_status, research_tools,
830
  report_status, report_tools, map_display, logs_display,
831
  session_logs, session_orchestrator, reasoning_display
832
  ]
 
330
  """Build all output values for yield."""
331
  return (
332
  history,
333
+ None, # Clear image_input after submit
334
  get_processing_status(),
335
  get_status_text(agent_states["research"]["status"], agent_states["research"].get("duration_formatted")),
336
  get_tools_markdown(agent_states["research"]["tools"]),
 
429
  progress_msg_idx += 1
430
 
431
  try:
432
+ # Process through orchestrator with chat history (Controller reasons about context)
433
+ for event_type, data in orchestrator.process(message, image_analysis, chat_history=history):
434
 
435
  # Handle reasoning trace updates (Autonomous Feature #1)
436
  if event_type == "reasoning_update":
 
817
  process_report,
818
  inputs=[message_input, image_input, chatbot, session_logs, session_orchestrator, reasoning_display],
819
  outputs=[
820
+ chatbot, image_input, processing_status, research_status, research_tools,
821
  report_status, report_tools, map_display, logs_display,
822
  session_logs, session_orchestrator, reasoning_display
823
  ]
 
827
  process_report,
828
  inputs=[message_input, image_input, chatbot, session_logs, session_orchestrator, reasoning_display],
829
  outputs=[
830
+ chatbot, image_input, processing_status, research_status, research_tools,
831
  report_status, report_tools, map_display, logs_display,
832
  session_logs, session_orchestrator, reasoning_display
833
  ]