import asyncio from app.schemas.analysis import AnalysisResult from app.schemas.company import CompanyProfile from app.schemas.tender import Tender from app.services.llm import call_gemini, _parse_gemini_response, call_gemini_with_model from app.services.report import generate_markdown_report from app.config import settings async def legal_agent_task(tender: Tender, company: CompanyProfile, document_text: str = "", model: str | None = None, tender_details: dict | None = None) -> str: details_str = f"\nSCRAPED DETAILS: {tender_details}" if tender_details else "" prompt = ( f"AGENT ROLE: Legal & Compliance Expert (Chilean Public Procurement)\n" f"GOAL: Analyze administrative bases and compliance risks.\n" f"TENDER: {tender.name} (Type: {tender.type})\n" f"COMPANY: {company.name}\n" f"EXTRACTED TEXT: {document_text[:5000]}\n" f"{details_str}\n" f"TASK: Identify 3 legal gaps/risks. Respond in Spanish." ) return await call_gemini_with_model(prompt, model) async def technical_agent_task(tender: Tender, company: CompanyProfile, document_text: str = "", model: str | None = None, tender_details: dict | None = None) -> str: details_str = f"\nSCRAPED DETAILS: {tender_details}" if tender_details else "" prompt = ( f"AGENT ROLE: Technical Architect\n" f"GOAL: Evaluate technical feasibility.\n" f"TENDER: {tender.name} - {tender.description}\n" f"COMPANY: {company.industry} - {company.experience}\n" f"EXTRACTED TEXT: {document_text[:5000]}\n" f"{details_str}\n" f"TASK: Identify 3 technical challenges. Respond in Spanish." ) return await call_gemini_with_model(prompt, model) async def strategy_agent_task(tender: Tender, company: CompanyProfile, document_text: str = "", model: str | None = None, tender_details: dict | None = None) -> str: details_str = f"\nSCRAPED DETAILS: {tender_details}" if tender_details else "" prompt = ( f"AGENT ROLE: Risk & Strategy Specialist\n" f"GOAL: Calculate ROI and strategy.\n" f"TENDER: {tender.name}\n" f"COMPANY: {company.name}\n" f"{details_str}\n" f"TASK: Identify 3 strategic risks and a win strategy. Respond in Spanish." ) return await call_gemini_with_model(prompt, model) async def run_full_analysis(tender: Tender, company_profile: CompanyProfile, document_text: str | None = None, models: dict | None = None, tender_details: dict | None = None, amd_settings: dict | None = None) -> AnalysisResult: # Inject user-provided AMD settings if present if amd_settings: settings.amd_inference_url = amd_settings.get("url") settings.amd_api_key = amd_settings.get("key") print(f"!!! AMD NODE ACTIVATED: {settings.amd_inference_url} !!!") audit_log = ["🚀 Iniciando mesa de expertos agéntica..."] doc_text = document_text or "" # Use selected models or defaults chosen_models = models or { "legal": "Llama-3.3-70B (Groq)" if settings.groq_api_key else "Gemini 2.5 Flash", "tech": "Llama-3.1-8B (Groq)" if settings.groq_api_key else "Qwen-2.5 (Featherless)", "risk": "Llama-3.3-70B (Groq)" if settings.groq_api_key else "Qwen-2.5 (Featherless)" } audit_log.append(f"👨‍⚖️ Agente Legal ({chosen_models.get('legal')})") audit_log.append(f"👨‍💻 Agente Técnico ({chosen_models.get('tech')})") audit_log.append(f"🕵️ Agente de Riesgo ({chosen_models.get('risk')})") tasks = [ legal_agent_task(tender, company_profile, doc_text, chosen_models.get("legal"), tender_details), technical_agent_task(tender, company_profile, doc_text, chosen_models.get("tech"), tender_details), strategy_agent_task(tender, company_profile, doc_text, chosen_models.get("risk"), tender_details) ] responses = await asyncio.gather(*tasks) legal_resp, tech_resp, strat_resp = responses audit_log.append("💡 Consolidando hallazgos...") synthesis_prompt = ( f"SISTEMA DE CONSENSO ANDESOPS AI (ESTRUCTURA DE ALTO IMPACTO)\n" f"Licitación: {tender.name}\n" f"Comprador: {tender.buyer}\n" f"Reporte Legal: {legal_resp}\n" f"Reporte Técnico: {tech_resp}\n" f"Reporte Estratégico: {strat_resp}\n\n" f"Genera un JSON 'AnalysisResult' siguiendo estas reglas estrictas:\n" f"1. fit_score (int 0-100)\n" f"2. decision ('Recommended', 'Review Carefully', 'Not Recommended')\n" f"3. executive_summary: Un resumen ejecutivo de alto nivel, profesional y persuasivo.\n" f"4. risks: Lista de {{title, severity, explanation}} con los riesgos críticos detectados.\n" f"5. key_requirements: Lista de requisitos técnicos/administrativos ineludibles.\n" f"6. compliance_gaps: Brechas que la empresa debe cerrar para ganar.\n" f"7. action_plan: Pasos concretos a seguir.\n" f"8. strategic_roadmap: Un roadmap estratégico en Markdown que explique cómo ganar.\n" f"9. proposal_draft: **CRÍTICO** - Genera un borrador de propuesta técnica formal y detallado en Markdown.\n" f" Debe incluir: \n" f" - Portada (Título de Licitación, Empresa, Fecha)\n" f" - Introducción y Objetivos\n" f" - Solución Técnica Propuesta (basada en el reporte técnico)\n" f" - Metodología de Implementación\n" f" - Propuesta de Valor Diferenciadora (por qué elegirnos)\n" f" - Cronograma estimado\n" f" - Conclusión Profesional\n" f"10. requirement_responses: " + (f"Genera exactamente {tender_details.get('metadata', {}).get('question_count', 0)} pares de {{question, answer}} basados en las preguntas reales del mercado. " if tender_details and tender_details.get('metadata', {}).get('question_count', 0) > 0 else "Genera solo 3 preguntas y respuestas basadas en requisitos hipotéticos/claves ya que no hay preguntas de mercado activas. ") + "\n" f"11. report_markdown: Un reporte general para consumo interno.\n" f"Responde ÚNICAMENTE con el JSON plano. No incluyas explicaciones fuera del JSON." ) final_output = await call_gemini(synthesis_prompt, is_json=True) # Fallback for synthesis if Gemini/Groq failed to return valid JSON if not final_output and settings.groq_api_key: from app.services.llm import call_groq final_output = await call_groq(synthesis_prompt, "llama-3.3-70b-versatile") parse_result = _parse_gemini_response(final_output) if parse_result: try: # Ensure report_markdown exists if not parse_result.get("report_markdown"): parse_result["report_markdown"] = generate_markdown_report(parse_result) result = AnalysisResult(**parse_result) result.audit_log = audit_log + (result.audit_log or []) result.raw_responses = { "legal": legal_resp, "technical": tech_resp, "strategy": strat_resp } return result except Exception as e: print(f"Synthesis Validation Error: {e}") # Ultimate fallback to the logic in llm.py from app.services.llm import generate_analysis return await generate_analysis(tender, company_profile, doc_text, models)