""" Iterative reasoning orchestrator. This module orchestrates the main iterative reasoning loop, coordinating between Oracle, step execution, and output sending. """ from typing import Optional, Callable, Any from datetime import datetime from core.logger import setup_logger from core.nats_event_bus import nats_bus as event_bus from .models import IterativeContext, StepAction from .oracle_client import OracleClient from .step_executor import StepExecutor from .formatters import KnowledgeFormatter class IterativeOrchestrator: """Orchestrates the iterative reasoning process""" def __init__( self, oracle_client: OracleClient, step_executor: StepExecutor, formatter: KnowledgeFormatter, output_sender: Callable, logger_name: str = 'orchestrator' ): self.logger = setup_logger(logger_name, service_name='think_service') self.oracle_client = oracle_client self.step_executor = step_executor self.formatter = formatter self.send_output = output_sender async def run( self, user_message: str, identity: str, channel: str, modality: str ) -> Optional[str]: """ Generate response using iterative step-by-step reasoning. Oracle decides one step at a time and sophisticated stopping criteria ensure efficient completion. """ self.logger.info(f"[💭] 🔄 Starting iterative reasoning for: '{user_message[:50]}...'") # Initialize iterative context context = IterativeContext(user_message, identity, channel, modality) try: # Send initial status to Matrix await self.send_output( f"🔄 **Iterative Reasoning Started**\n\nAnalyzing your request step by step...", channel, modality ) # Main iterative reasoning loop while True: # Check stopping criteria should_stop, stop_reason = context.should_stop() if should_stop: self.logger.info(f"[💭] 🛑 Stopping iteration: {stop_reason}") await self.send_output( f"🎯 **Reasoning Complete**\n\n{stop_reason}\n\n{context.get_summary()}", channel, modality ) break # Goal satisfaction check (every 3 steps) if context.step_count > 0 and context.step_count % 3 == 0: can_answer = await self.oracle_client.check_goal_satisfaction(context) if can_answer: context.goal_satisfied = True self.logger.info(f"[💭] ✅ Goal satisfaction confirmed at step {context.step_count}") # Will be caught by stopping criteria on next iteration continue # Ask Oracle for next step self.logger.info(f"[💭] 🔮 Requesting step {context.step_count + 1} from Oracle") next_step = await self.oracle_client.request_next_step(context) if not next_step: self.logger.warning(f"[💭] ⚠️ Oracle failed to provide next step") await self.send_output( f"❌ **Oracle Error**: Failed to get next step decision", channel, modality ) break self.logger.info(f"[💭] 📋 Step {context.step_count + 1}: {next_step.action} -> {next_step.target} (args: {getattr(next_step, 'function_args', {})})") # Show Oracle's decision to user await self.send_output( f"🧠 **Oracle Decision**: {next_step.action}\n" f"**Target**: {next_step.target or 'N/A'}\n" f"**Reasoning**: {next_step.reasoning}", channel, modality ) # Check if Oracle signals readiness for synthesis if next_step.action == StepAction.SYNTHESIZE_FINAL.value or next_step.ready: self.logger.info(f"[💭] 🎯 Oracle signals ready for synthesis") await self.send_output( f"🎯 **Ready for Synthesis**: Oracle indicates sufficient information gathered", channel, modality ) break # Execute the step directly self.logger.info(f"[💭] ⚙️ Executing step {context.step_count + 1}") step_result = await self.step_executor.execute_step(next_step, context) self.logger.info(f"[💭] ✅ Step {context.step_count + 1} execution completed, success={step_result.success}") context.add_step_result(step_result) self.logger.info(f"[💭] 📝 Step {context.step_count} result added to context (total: {len(context.completed_steps)} completed)") # Send detailed execution result to Matrix if step_result.success: result_summary = self.formatter.format_step_result_for_matrix(step_result) await self.send_output( f"✅ **Step {context.step_count} Completed**: {next_step.action}\n" f"**Execution Time**: {step_result.execution_time_ms:.0f}ms\n" f"**Result**: {result_summary}", channel, modality ) else: await self.send_output( f"❌ **Step {context.step_count} Failed**: {step_result.error_message}\n" f"**Execution Time**: {step_result.execution_time_ms:.0f}ms", channel, modality ) # Final synthesis await self.send_output( f"🔮 **Synthesizing Final Response**\n\nCombining insights from {len(context.completed_steps)} successful steps...", channel, modality ) final_response = await self.oracle_client.synthesize_final_response(context) if final_response: # Finalize interaction with sentiment/depth detection await self._finalize_interaction( identity, user_message, final_response, context ) self.logger.info(f"[💭] ✅ Iterative reasoning completed successfully") return final_response else: self.logger.error(f"[💭] ⚠️ Final synthesis failed") await self.send_output( f"❌ **Synthesis Failed**\n\nI gathered information but couldn't synthesize a final response. Please try rephrasing your question.", channel, modality ) return None except Exception as e: self.logger.exception(f"[💭] Error in iterative reasoning: {e}") await self.send_output( f"❌ **Iterative Reasoning Error**\n\nSomething went wrong during the reasoning process: {str(e)}", channel, modality ) return None async def _finalize_interaction( self, identity: str, user_message: str, response_content: str, context: IterativeContext ): """ Finalize interaction by asking oracle to analyze sentiment/depth. Called after synthesis completes. """ try: # Ask Oracle to analyze the interaction self.logger.debug(f"[💭] Requesting interaction analysis from oracle...") analysis = await self.oracle_client.analyze_interaction(context, user_message, response_content) sentiment = analysis.get("sentiment", "positive") depth = analysis.get("depth", 0.3) # Publish interaction completion event for identity service await event_bus.emit("vi.interaction.completed", { "internal_id": identity, "sentiment": sentiment, # positive, neutral, negative "depth": depth, # 0.0-1.0 "summary": f"Conversation with {len(context.completed_steps)} reasoning steps", "timestamp": datetime.utcnow().isoformat() }) self.logger.info(f"[💭] 📊 Interaction finalized: {identity} (sentiment={sentiment}, depth={depth:.2f})") except Exception as e: self.logger.error(f"[💭] Error finalizing interaction: {e}") # Don't publish event if we couldn't analyze properly