import logging from typing import Callable, Awaitable from app.models.schemas import SubReview, AgentReview, OverallStatus logger = logging.getLogger(__name__) from app.agents.brand_agent import BrandAgent from app.agents.channel_agent import ChannelAgent from app.agents.legal_agent import LegalAgent from app.agents.tone_agent import ToneAgent from app.agents.lead_agent import LeadAgent from app.services.gemini_service import GeminiService from app.services.reference_docs import ReferenceDocsService # Type alias for the callback function AgentCallback = Callable[[str, SubReview | None], Awaitable[None]] class AnalysisService: """ Orchestrates the multi-agent proof analysis. Runs agents sequentially and provides callbacks for real-time updates. """ # Agent execution order AGENT_ORDER = ["Legal Agent", "Brand Agent", "Tone Agent", "Channel Agent"] def __init__( self, gemini_service: GeminiService, reference_docs: ReferenceDocsService, ): """ Initialize the analysis service with all required agents. Args: gemini_service: Service for Gemini API calls reference_docs: Service for loading reference documents """ self.gemini_service = gemini_service self.reference_docs = reference_docs # Initialize agents self.agents = { "Legal Agent": LegalAgent(), "Brand Agent": BrandAgent(gemini_service, reference_docs), "Tone Agent": ToneAgent(), "Channel Agent": ChannelAgent(gemini_service, reference_docs), } self.lead_agent = LeadAgent(gemini_service) async def analyze_proof( self, file_data: bytes, file_type: str, on_agent_update: AgentCallback | None = None, is_wip: bool = False, ) -> AgentReview: """ Analyze a proof using all agents sequentially. Args: file_data: Raw bytes of the file to analyze file_type: MIME type of the file on_agent_update: Optional callback for real-time agent updates. Called with (agent_name, None) when agent starts, and (agent_name, review) when agent completes. is_wip: Whether this is a work-in-progress analysis Returns: Complete AgentReview with all agent results and overall verdict """ logger.info(f"[ANALYSIS] Starting proof analysis - file_type: {file_type}, file_size: {len(file_data)} bytes, is_wip: {is_wip}") reviews: dict[str, SubReview] = {} # Run each agent sequentially for agent_name in self.AGENT_ORDER: agent = self.agents[agent_name] logger.info(f"[ANALYSIS] Starting agent: {agent_name}") # Notify that agent is starting if on_agent_update: await on_agent_update(agent_name, None) # Run the agent review = await agent.analyze(file_data, file_type) reviews[agent_name] = review logger.info(f"[ANALYSIS] Agent completed: {agent_name} - ragStatus: {review.ragStatus}") # Notify that agent completed if on_agent_update: await on_agent_update(agent_name, review) # Get lead agent synthesis logger.info("[ANALYSIS] Starting lead agent synthesis") if on_agent_update: await on_agent_update("Summary", None) overall_status, summary, financial_promotion_reason = await self.lead_agent.synthesize(reviews) logger.info(f"[ANALYSIS] Analysis complete - overallStatus: {overall_status}") # Build the complete AgentReview return AgentReview( legalAgentReview=reviews["Legal Agent"], brandAgentReview=reviews["Brand Agent"], toneAgentReview=reviews["Tone Agent"], channelAgentReview=reviews["Channel Agent"], leadAgentSummary=summary, overallStatus=overall_status, financialPromotionReason=financial_promotion_reason, )