| 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) -> AnalysisResult: |
| audit_log = ["🚀 Iniciando mesa de expertos agéntica..."] |
| doc_text = document_text or "" |
| |
| |
| 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) |
| |
| |
| 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: |
| |
| 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}") |
| |
| |
| from app.services.llm import generate_analysis |
| return await generate_analysis(tender, company_profile, doc_text, models) |
|
|