Lección 26 · 10 min · Gratis

Sistema de análisis de llamadas de ganancias multiagente (MAECAS)

Open In Colab

Las llamadas de ganancias de las empresas ofrecen información crucial sobre el rendimiento, la estrategia y las perspectivas futuras de una compañía. Sin embargo, estas transcripciones son extensas, densas y cubren diversos temas, lo que dificulta la extracción eficiente de información específica.

El problema

Las llamadas trimestrales de ganancias brindan información crítica sobre el rendimiento, las estrategias y las perspectivas de una empresa, pero extraer un análisis significativo presenta desafíos importantes:

  • Las transcripciones de las llamadas de ganancias son largas y densas, a menudo superando las 20 páginas de complejas discusiones financieras. La información clave está dispersa en el texto sin una organización clara.
  • Diferentes partes interesadas necesitan distintos tipos de información (métricas financieras, iniciativas estratégicas, factores de riesgo).
  • El análisis entre trimestres requiere el seguimiento manual de narrativas cambiantes en múltiples llamadas.
  • El análisis manual tradicional consume mucho tiempo, es inconsistente y propenso a omitir detalles importantes.

Por qué esto es importante

Para inversionistas, analistas y líderes empresariales, un análisis exhaustivo de las llamadas de ganancias ofrece un valor significativo:

  • Eficiencia de tiempo: Reduce el tiempo de análisis de días a minutos.
  • Soporte de decisiones: Proporciona información estructurada para decisiones de inversión y estratégicas.
  • Cobertura integral: Asegura que no se omita ninguna información importante.
  • Análisis consistente: Aplica el mismo rigor analítico a cada transcripción.
  • Detección de tendencias: Identifica patrones entre trimestres que de otro modo podrían pasar desapercibidos.

Nuestra solución

El Orquestador de Análisis de Llamadas de Ganancias transforma la forma en que se procesan las llamadas de ganancias a través de un flujo de trabajo multiagente que:

  1. Extrae información de las transcripciones trimestrales utilizando agentes de análisis especializados.
  2. Ofrece tanto informes completos como respuestas a consultas específicas.
  3. Identifica tendencias y patrones entre trimestres.
  4. Mantiene una base de conocimientos estructurada de la información de ganancias.

Agentes de análisis especializados

Nuestro sistema emplea agentes especializados que trabajan en coordinación para ofrecer un análisis exhaustivo:

Agente financiero: Extrae cifras de ingresos, márgenes de beneficio, métricas de crecimiento y otros indicadores de rendimiento cuantificables.

Agente estratégico: Identifica hojas de ruta de productos, expansiones de mercado, asociaciones e iniciativas de visión a largo plazo.

Agente de sentimiento: Evalúa la confianza, el tono y el entusiasmo de la gerencia en diferentes segmentos de negocio.

Agente de riesgo: Detecta desafíos en la cadena de suministro, el mercado, regulatorios, y evalúa su gravedad y planes de mitigación.

Agente de la competencia: Rastrea el posicionamiento competitivo, las discusiones sobre la cuota de mercado y las estrategias de diferenciación.

Agente temporal: Analiza tendencias entre trimestres para identificar la trayectoria del negocio y las prioridades en evolución.

Orquestación del flujo de trabajo

El orquestador sirve como coordinador central que:

  • Procesa y almacena eficientemente el texto de las transcripciones utilizando OCR avanzado.
  • Activa agentes especializados según las necesidades de análisis. Almacena información estructurada en una base de conocimientos centralizada.
  • Genera informes completos con resúmenes ejecutivos, análisis seccionales y perspectivas.
  • Responde a consultas específicas aprovechando la información relevante de varios trimestres.

Conjunto de datos

Para fines de demostración, usamos las transcripciones de las llamadas de ganancias trimestrales de NVIDIA de 2025:

  • Transcripción de la llamada de ganancias del primer trimestre de 2025
  • Transcripción de la llamada de ganancias del segundo trimestre de 2025
  • Transcripción de la llamada de ganancias del tercer trimestre de 2025
  • Transcripción de la llamada de ganancias del cuarto trimestre de 2025

Estas transcripciones contienen discusiones sobre resultados financieros, iniciativas estratégicas, condiciones del mercado y declaraciones prospectivas del equipo de gestión de NVIDIA y sus interacciones con analistas financieros.

Modelos de Mistral AI

Para nuestra implementación, usamos los LLM de Mistral AI:

mistral-small-latest: Se usa para análisis general y generación de respuestas.

mistral-large-latest: Se usa para la generación de resultados estructurados.

mistral-ocr-latest: Se usa para la extracción y procesamiento de transcripciones en PDF.

Este enfoque modular permite tanto la generación de informes detallados como la respuesta a preguntas específicas, manteniendo la eficiencia a través de la activación selectiva de agentes y la reutilización de la información.

Arquitectura de la solución

Solution Architecture

Instalación

Necesitamos mistralai para el uso de LLM.

!pip install mistralai
Collecting mistralai
  Downloading mistralai-1.6.0-py3-none-any.whl.metadata (30 kB)
Collecting eval-type-backport>=0.2.0 (from mistralai)
  Downloading eval_type_backport-0.2.2-py3-none-any.whl.metadata (2.2 kB)
Requirement already satisfied: httpx>=0.28.1 in /usr/local/lib/python3.11/dist-packages (from mistralai) (0.28.1)
Requirement already satisfied: pydantic>=2.10.3 in /usr/local/lib/python3.11/dist-packages (from mistralai) (2.10.6)
Requirement already satisfied: python-dateutil>=2.8.2 in /usr/local/lib/python3.11/dist-packages (from mistralai) (2.8.2)
Collecting typing-inspection>=0.4.0 (from mistralai)
  Downloading typing_inspection-0.4.0-py3-none-any.whl.metadata (2.6 kB)
Requirement already satisfied: anyio in /usr/local/lib/python3.11/dist-packages (from httpx>=0.28.1->mistralai) (4.9.0)
Requirement already satisfied: certifi in /usr/local/lib/python3.11/dist-packages (from httpx>=0.28.1->mistralai) (2025.1.31)
Requirement already satisfied: httpcore==1.* in /usr/local/lib/python3.11/dist-packages (from httpx>=0.28.1->mistralai) (1.0.7)
Requirement already satisfied: idna in /usr/local/lib/python3.11/dist-packages (from httpx>=0.28.1->mistralai) (3.10)
Requirement already satisfied: h11<0.15,>=0.13 in /usr/local/lib/python3.11/dist-packages (from httpcore==1.*->httpx>=0.28.1->mistralai) (0.14.0)
Requirement already satisfied: annotated-types>=0.6.0 in /usr/local/lib/python3.11/dist-packages (from pydantic>=2.10.3->mistralai) (0.7.0)
Requirement already satisfied: pydantic-core==2.27.2 in /usr/local/lib/python3.11/dist-packages (from pydantic>=2.10.3->mistralai) (2.27.2)
Requirement already satisfied: typing-extensions>=4.12.2 in /usr/local/lib/python3.11/dist-packages (from pydantic>=2.10.3->mistralai) (4.12.2)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.11/dist-packages (from python-dateutil>=2.8.2->mistralai) (1.17.0)
Requirement already satisfied: sniffio>=1.1 in /usr/local/lib/python3.11/dist-packages (from anyio->httpx>=0.28.1->mistralai) (1.3.1)
Downloading mistralai-1.6.0-py3-none-any.whl (288 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 288.7/288.7 kB 7.7 MB/s eta 0:00:00
[?25hDownloading eval_type_backport-0.2.2-py3-none-any.whl (5.8 kB)
Downloading typing_inspection-0.4.0-py3-none-any.whl (14 kB)
Installing collected packages: typing-inspection, eval-type-backport, mistralai
Successfully installed eval-type-backport-0.2.2 mistralai-1.6.0 typing-inspection-0.4.0

Importaciones

import os
import json
import hashlib
from datetime import datetime
from pathlib import Path
from typing import List, Dict, Any, Literal, Optional, Union
from abc import ABC, abstractmethod

from pydantic import BaseModel, Field
from mistralai.client import Mistral
from IPython.display import display, Markdown

Configurar claves de API

Aquí configuramos la clave de API de MistralAI.

os.environ['MISTRAL_API_KEY'] = '<YOUR MISTRALAI API KEY>'  # Get your API key from https://console.mistral.ai/api-keys/
api_key = os.environ.get('MISTRAL_API_KEY')

Inicializar cliente de Mistral

Aquí inicializamos el cliente de Mistral.

mistral_client = Mistral(api_key=os.environ.get("MISTRAL_API_KEY"))

Descargar datos

Usaremos las transcripciones de las llamadas de ganancias trimestrales de NVIDIA de 2025:

  • Transcripción de la llamada de ganancias del primer trimestre de 2025
  • Transcripción de la llamada de ganancias del segundo trimestre de 2025
  • Transcripción de la llamada de ganancias del tercer trimestre de 2025
  • Transcripción de la llamada de ganancias del cuarto trimestre de 2025

Estas transcripciones contienen discusiones sobre resultados financieros, iniciativas estratégicas, condiciones del mercado y declaraciones prospectivas del equipo de gestión de NVIDIA y sus interacciones con analistas financieros.

!wget "https://github.com/mistralai/cookbook/blob/main/mistral/agents/non_framework/earnings_calls/data/nvidia_earnings_2025_Q1.pdf" -O "nvidia_earnings_2025_Q1.pdf"
!wget "https://github.com/mistralai/cookbook/blob/main/mistral/agents/non_framework/earnings_calls/data/nvidia_earnings_2025_Q2.pdf" -O "nvidia_earnings_2025_Q2.pdf"
!wget "https://github.com/mistralai/cookbook/blob/main/mistral/agents/non_framework/earnings_calls/data/nvidia_earnings_2025_Q3.pdf" -O "nvidia_earnings_2025_Q3.pdf"
!wget "https://github.com/mistralai/cookbook/blob/main/mistral/agents/non_framework/earnings_calls/data/nvidia_earnings_2025_Q4.pdf" -O "nvidia_earnings_2025_Q4.pdf"
--2025-04-10 19:13:26--  https://github.com/mistralai/cookbook/blob/main/mistral/agents/earnings_calls/data/nvidia_earnings_2025_Q1.pdf
Resolving github.com (github.com)... 20.207.73.82
Connecting to github.com (github.com)|20.207.73.82|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/html]
Saving to: ‘nvidia_earnings_2025_Q1.pdf’

nvidia_earnings_202     [ <=>                ] 208.56K  --.-KB/s    in 0.1s    

2025-04-10 19:13:27 (1.58 MB/s) - ‘nvidia_earnings_2025_Q1.pdf’ saved [213562]

--2025-04-10 19:13:27--  https://github.com/mistralai/cookbook/blob/main/mistral/agents/earnings_calls/data/nvidia_earnings_2025_Q2.pdf
Resolving github.com (github.com)... 20.207.73.82
Connecting to github.com (github.com)|20.207.73.82|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/html]
Saving to: ‘nvidia_earnings_2025_Q2.pdf’

nvidia_earnings_202     [ <=>                ] 208.56K  --.-KB/s    in 0.1s    

2025-04-10 19:13:28 (1.53 MB/s) - ‘nvidia_earnings_2025_Q2.pdf’ saved [213563]

--2025-04-10 19:13:28--  https://github.com/mistralai/cookbook/blob/main/mistral/agents/earnings_calls/data/nvidia_earnings_2025_Q3.pdf
Resolving github.com (github.com)... 20.207.73.82
Connecting to github.com (github.com)|20.207.73.82|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/html]
Saving to: ‘nvidia_earnings_2025_Q3.pdf’

nvidia_earnings_202     [ <=>                ] 208.56K  --.-KB/s    in 0.1s    

2025-04-10 19:13:29 (1.59 MB/s) - ‘nvidia_earnings_2025_Q3.pdf’ saved [213562]

--2025-04-10 19:13:29--  https://github.com/mistralai/cookbook/blob/main/mistral/agents/earnings_calls/data/nvidia_earnings_2025_Q4.pdf
Resolving github.com (github.com)... 20.207.73.82
Connecting to github.com (github.com)|20.207.73.82|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/html]
Saving to: ‘nvidia_earnings_2025_Q4.pdf’

nvidia_earnings_202     [ <=>                ] 208.56K  --.-KB/s    in 0.1s    

2025-04-10 19:13:30 (1.51 MB/s) - ‘nvidia_earnings_2025_Q4.pdf’ saved [213563]

Iniciar modelos

  1. DEFAULT_MODEL - Para análisis general

  2. STRUCTURED_MODEL - Para resultados estructurados

  3. OCR_MODEL - Para analizar el documento de la llamada de ganancias.

DEFAULT_MODEL = "mistral-small-latest"
STRUCTURED_MODEL = "mistral-large-latest"
OCR_MODEL = "mistral-ocr-latest"

Modelos de datos

La solución utiliza modelos Pydantic especializados para estructurar y extraer información:

Modelos de análisis central

  • FinancialInsight: Captura métricas, valores y puntuaciones de confianza para el rendimiento financiero.
  • StrategicInsight: Representa iniciativas, descripciones, plazos y calificaciones de importancia.
  • SentimentInsight: Rastrea el sentimiento del tema, la evidencia y las atribuciones del orador.
  • RiskInsight: Documenta riesgos, impactos, mitigaciones y puntuaciones de gravedad.
  • CompetitorInsight: Registra segmentos de mercado, posicionamiento y dinámicas competitivas.
  • TemporalInsight: Identifica tendencias, patrones y evidencia de apoyo entre trimestres.

Modelos de flujo de trabajo

  • QueryAnalysis: Determina los trimestres requeridos, los tipos de agente y las dimensiones de análisis a partir de las consultas del usuario.
  • ReportSection: Estructura el contenido del informe con título, cuerpo y subsecciones opcionales.

Envoltorios de respuesta

  • Cada modelo de análisis tiene un envoltorio de respuesta correspondiente (por ejemplo, FinancialInsightsResponse) que empaqueta la información en formatos estructurados compatibles con las capacidades de análisis de la API de Mistral.

Los modelos utilizan tipos Literal de Python para campos categorizados (como niveles de sentimiento o tipos de tendencia) para aplicar una validación estricta y garantizar una terminología consistente, lo que permite comparaciones confiables entre trimestres al tiempo que proporciona una extracción, almacenamiento y recuperación de conocimientos consistentes en múltiples dimensiones de análisis tanto para informes completos como para consultas específicas.

Información financiera

class FinancialInsight(BaseModel):
    """Financial insights extracted from transcript"""
    metric_name: str = Field(description="Name of the financial metric")
    value: Optional[str] = Field(description="Numerical or textual value of the metric")
    context: str = Field(description="Surrounding context for the metric")
    quarter: Literal["Q1", "Q2", "Q3", "Q4"] = Field(description="Quarter the insight relates to (e.g., Q1, Q2)")
    confidence: float = Field(description="Confidence score for the insight with limits ge=0.0, le=1.0")

class FinancialInsightsResponse(BaseModel):
    """Wrapper for list of financial insights"""
    insights: List[FinancialInsight] = Field(description="Collection of financial insights")

Información estratégica

class StrategicInsight(BaseModel):
    """Strategic insights about business direction"""
    initiative: str = Field(description="Name of the strategic initiative")
    description: str = Field(description="Details about the strategic initiative")
    timeframe: Optional[str] = Field(description="Expected timeline for implementation")
    quarter: Literal["Q1", "Q2", "Q3", "Q4"] = Field(description="Quarter the insight relates to (e.g., Q1, Q2, Q3, Q4)")
    importance: int = Field(description="Importance rating with limits ge=1, le=5")

class StrategicInsightsResponse(BaseModel):
    """Wrapper for list of strategic insights"""
    insights: List[StrategicInsight] = Field(description="Collection of strategic insights")

Información de sentimiento

class SentimentInsight(BaseModel):
    """Insights about management sentiment"""
    topic: str = Field(description="Subject matter being discussed")
    sentiment: Literal["very negative", "negative", "neutral", "positive", "very positive"] = Field(description="Tone expressed by management")
    evidence: str = Field(description="Quote or context supporting the sentiment analysis")
    speaker: str = Field(description="Person who expressed the sentiment")
    quarter: Literal["Q1", "Q2", "Q3", "Q4"] = Field(description="Quarter the insight relates to (e.g., Q1, Q2, Q3, Q4)")

class SentimentInsightsResponse(BaseModel):
    """Wrapper for list of sentiment insights"""
    insights: List[SentimentInsight] = Field(description="Collection of sentiment insights")

Información de riesgo

class RiskInsight(BaseModel):
    """Identified risks or challenges"""
    risk_factor: str = Field(description="Name or type of risk identified")
    description: str = Field(description="Details about the risk")
    potential_impact: str = Field(description="Possible consequences of the risk")
    mitigation_mentioned: Optional[str] = Field(description="Strategies to address the risk")
    quarter: Literal["Q1", "Q2", "Q3", "Q4"] = Field(description="Quarter the insight relates to (e.g., Q1, Q2, Q3, Q4)")
    severity: int = Field(description="Severity rating with limits ge=1, le=5")

class RiskInsightsResponse(BaseModel):
    """Wrapper for list of risk insights"""
    insights: List[RiskInsight] = Field(description="Collection of risk insights")

Información del competidor

class CompetitorInsight(BaseModel):
    """Insights about competitive positioning"""
    competitor: Optional[str] = Field(description="Name of the competitor company")
    market_segment: str = Field(description="Specific market area being discussed")
    positioning: str = Field(description="Competitive stance or market position")
    quarter: Literal["Q1", "Q2", "Q3", "Q4"] = Field(description="Quarter the insight relates to (e.g., Q1, Q2, Q3, Q4)")
    mentioned_by: str = Field(description="Person who mentioned the competitive information")

class CompetitorInsightsResponse(BaseModel):
    """Wrapper for list of competitor insights"""
    insights: List[CompetitorInsight] = Field(description="Collection of competitor insights")

Información temporal

class TemporalInsight(BaseModel):
    """Insights about trends across quarters"""
    trend_type: Literal["growth", "decline", "stable", "volatile", "emerging", "fading"] = Field(description="Direction or pattern of the trend")
    topic: str = Field(description="Subject matter of the trend")
    description: str = Field(description="Explanation of the trend's significance")
    quarters_observed: List[Literal["Q1", "Q2", "Q3", "Q4"]] = Field(description="Quarters where the trend appears")
    supporting_evidence: str = Field(description="Data or quotes supporting the trend identification")

class TemporalInsightsResponse(BaseModel):
    """Wrapper for list of temporal insights"""
    insights: List[TemporalInsight] = Field(description="Collection of temporal insights")

Análisis de consultas

class QueryAnalysis(BaseModel):
    """Analysis of user query to determine required components"""
    quarters: List[str] = Field(description="List of quarters to analyze")
    agent_types: List[str] = Field(description="List of agent types to use")
    temporal_analysis_required: bool = Field(description="Whether temporal analysis across quarters is needed")
    query_intent: str = Field(description="Brief description of user's intent")

Sección del informe

class ReportSection(BaseModel):
    """Section of the final report"""
    title: str = Field(description="Heading for the report section")
    content: str = Field(description="Main text content of the section")
    subsections: Optional[List["ReportSection"]] = Field(description="Nested sections within this section.")

Analizador de PDF

Nuestro analizador de PDF utiliza las capacidades de OCR de Mistral para extraer texto de alta calidad de las transcripciones de las llamadas de ganancias, al tiempo que implementa un sistema de almacenamiento en caché basado en archivos para mejorar el rendimiento. Este enfoque permite una extracción de texto precisa con una sobrecarga de procesamiento mínima para análisis repetidos.

class PDFParser:
    """Parse a transcript PDF file and extract text from all pages using Mistral OCR."""

    CACHE_DIR = Path("transcript_cache")

    @staticmethod
    def _ensure_cache_dir():
        """Make sure cache directory exists"""
        PDFParser.CACHE_DIR.mkdir(exist_ok=True)

    @staticmethod
    def _get_cache_path(file_path: str) -> Path:
        """Get the path for a cached transcript file"""
        # Create a hash of the file path to use as the cache filename
        file_hash = hashlib.md5(file_path.encode()).hexdigest()
        return PDFParser.CACHE_DIR / f"{file_hash}.txt"

    @staticmethod
    def read_transcript(file_path: str, mistral_client: Mistral) -> str:
        """Extract text from PDF transcript using Mistral OCR"""
        print(f"Processing PDF file: {file_path}")

        uploaded_pdf = mistral_client.files.upload(
            file={
                "file_name": file_path,
                "content": open(file_path, "rb"),
            },
            purpose="ocr"
        )

        signed_url = mistral_client.files.get_signed_url(file_id=uploaded_pdf.id)

        ocr_response = mistral_client.ocr.process(
            model=OCR_MODEL,
            document={
                "type": "document_url",
                "document_url": signed_url.url,
            }
        )

        text = "\n".join([x.markdown for x in (ocr_response.pages)])
        return text

    @staticmethod
    def get_transcript_by_quarter(company: str, quarter: str, year: str, mistral_client: Mistral) -> str:
        """Get the transcript for a specific quarter"""
        company_lower = company.lower()
        file_path = f"{company_lower}_earnings_{year}_{quarter}.pdf"

        PDFParser._ensure_cache_dir()
        cache_path = PDFParser._get_cache_path(file_path)

        # Check if transcript is in cache
        if cache_path.exists():
            print(f"Using cached transcript for {company} {year} {quarter}")
            with open(cache_path, "r", encoding="utf-8") as f:
                return f.read()
        else:
            try:
                print(f"Parsing transcript for {company} {year} {quarter}")
                transcript = PDFParser.read_transcript(file_path, mistral_client)

                # Store in cache for future use
                with open(cache_path, "w", encoding="utf-8") as f:
                    f.write(transcript)

                print(f"Cached transcript for {company} {year} {quarter}")
                return transcript
            except Exception as e:
                print(f"Error processing transcript: {str(e)}")
                raise

Almacenamiento de información

El sistema incluye un componente centralizado InsightsStore que:

  • Mantiene una base de datos JSON persistente de toda la información extraída.
  • Organiza la información por tipo (financiera, estratégica, etc.) y trimestre.
  • Proporciona una recuperación eficiente tanto para la generación de informes como para la respuesta a consultas.
  • Elimina el procesamiento redundante al almacenar en caché los resultados del análisis.
class InsightsStore:
    """Centralized storage for insights across all quarters and analysis types"""

    def __init__(self, company: str, year: str):
        self.company = company.lower()
        self.year = year
        self.db_path = Path(f"{self.company}_{self.year}_insights.json")
        self.insights = self._load_insights()

    def _load_insights(self) -> Dict:
        """Load insights from database file or initialize if not exists"""
        if self.db_path.exists():
            with open(self.db_path, "r", encoding="utf-8") as f:
                return json.load(f)
        else:
            return {
                "financial": {},
                "strategic": {},
                "sentiment": {},
                "risk": {},
                "competitor": {},
                "temporal": {}
            }

    def save_insights(self):
        """Save insights to database file"""
        with open(self.db_path, "w", encoding="utf-8") as f:
            json.dump(self.insights, f, indent=2)

    def add_insights(self, insight_type: str, quarter: str, insights: List):
        """Add insights for a specific type and quarter"""
        if quarter not in self.insights[insight_type]:
            self.insights[insight_type][quarter] = []

        # Convert insights to dictionaries for storage
        insight_dicts = []
        for insight in insights:
            if hasattr(insight, "dict"):
                insight_dicts.append(insight.dict())
            elif isinstance(insight, dict):
                insight_dicts.append(insight)
            else:
                insight_dicts.append({"content": str(insight)})

        # Append new insights
        self.insights[insight_type][quarter] = insight_dicts
        self.save_insights()

    def get_insights(self, insight_type=None, quarters=None):
        """Retrieve insights, optionally filtered by type and quarters"""
        if insight_type is None:
            return self.insights

        if quarters is None:
            return self.insights[insight_type]

        filtered = {}
        for q in quarters:
            if q in self.insights[insight_type]:
                filtered[q] = self.insights[insight_type][q]

        return filtered

Agentes especializados

Nuestro análisis se basa en cinco agentes centrados en el dominio, cada uno extrayendo información específica:

Agente financiero: Analiza métricas, cifras de ingresos, márgenes y tasas de crecimiento.

Agente estratégico: Identifica hojas de ruta de productos, expansiones de mercado e inversiones en I+D.

Agente de sentimiento: Evalúa el tono de la gerencia, los niveles de confianza y el entusiasmo en los temas.

Agente de riesgo: Detecta desafíos, incertidumbres y posibles amenazas con calificaciones de gravedad.

Agente de la competencia: Rastrea el posicionamiento competitivo, las discusiones sobre la cuota de mercado y las estrategias de diferenciación.

Cada agente procesa las transcripciones a través de prompts especializados, produciendo información estructurada que alimenta el análisis general.

Clase base Agent para agentes especializados

class Agent(ABC):
    """Base class for all specialized agents"""

    def __init__(self):
        self.client = mistral_client

    @abstractmethod
    def analyze(self, transcript: str, quarter: str) -> Any:
        """Analyze the transcript and return insights"""
        pass

Agente financiero

class FinancialAgent(Agent):
    """Agent for financial analysis of earnings call transcripts"""

    def analyze(self, transcript: str, quarter: str) -> List[FinancialInsight]:
        """Extract financial insights from transcript"""
        system_prompt = """
        You are a financial analyst focused on extracting key financial metrics and performance
        indicators from the earnings call transcripts.

        Focus on:
        - Revenue figures (overall and by segment)
        - Profit margins
        - Growth rates
        - Forward guidance
        - Capital expenditures
        - Cash flow metrics
        - Any financial KPIs mentioned

        Extract only facts that are explicitly stated in the transcript, with their proper context.
        Keep the insights as short as possible.
        """

        print(f"Extracting financial insights for quarter {quarter}...")

        response = self.client.chat.parse(
            model=STRUCTURED_MODEL,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": f"Extract financial insights from this earnings call transcript for {quarter}:\n\n{transcript}"}
            ],
            response_format=FinancialInsightsResponse,
            temperature=0.1
        )

        print(f"Financial agent completed for {quarter}")
        parsed_response = json.loads(response.choices[0].message.content)
        return parsed_response['insights']

Agente estratégico.

class StrategicAgent(Agent):
    """Agent for strategic analysis of earnings call transcripts"""

    def analyze(self, transcript: str, quarter: str) -> List[StrategicInsight]:
        """Extract strategic insights from transcript"""
        system_prompt = """
        You are a business strategy analyst focused on the company's strategic direction.

        Extract insights about:
        - Product roadmaps
        - Market expansions
        - Strategic partnerships
        - R&D investments
        - Long-term vision
        - Business model changes
        - Market segments of focus

        Focus on extracting concrete strategic initiatives and plans, not general statements.
        Assign an importance score (1-5) based on how central it appears to the company's strategy.
        """

        print(f"Extracting strategic insights for quarter {quarter}...")

        response = self.client.chat.parse(
            model=STRUCTURED_MODEL,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": f"Extract strategic insights from this earnings call transcript for {quarter}:\n\n{transcript}"}
            ],
            response_format=StrategicInsightsResponse,
            temperature=0.1
        )

        print(f"Strategic agent completed for {quarter}")
        parsed_response = json.loads(response.choices[0].message.content)
        return parsed_response['insights']

Agente de sentimiento

class SentimentAgent(Agent):
    """Agent for sentiment analysis of earnings call transcripts"""

    def analyze(self, transcript: str, quarter: str) -> List[SentimentInsight]:
        """Extract sentiment insights from transcript"""
        system_prompt = """
        You are an expert in analyzing sentiment and tone in corporate communications.

        Focus on:
        - Management's confidence level
        - Tone when discussing different business segments
        - Enthusiasm for future prospects
        - Concerns or hesitations
        - Changes in sentiment when answering analyst questions

        Extract specific topics and the sentiment expressed about them by specific speakers.
        Use the transcript to find evidence of the sentiment you identify.
        """

        print(f"Extracting sentiment insights for quarter {quarter}...")

        response = self.client.chat.parse(
            model=STRUCTURED_MODEL,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": f"Extract sentiment insights from this earnings call transcript for {quarter}:\n\n{transcript}"}
            ],
            response_format=SentimentInsightsResponse,
            temperature=0.1
        )

        print(f"Sentiment agent completed for {quarter}")
        parsed_response = json.loads(response.choices[0].message.content)
        return parsed_response['insights']

Agente de riesgo

class RiskAgent(Agent):
    """Agent for risk analysis of earnings call transcripts"""

    def analyze(self, transcript: str, quarter: str) -> List[RiskInsight]:
        """Extract risk insights from transcript"""
        system_prompt = """
        You are a risk analyst specialized in identifying challenges, uncertainties, and risk factors
        mentioned in earnings calls.

        Focus on:
        - Supply chain challenges
        - Market uncertainties
        - Competitive pressures
        - Regulatory concerns
        - Technical challenges
        - Execution risks
        - Macroeconomic factors

        For each risk, identify its potential impact and any mentioned mitigation strategies.
        Assign a severity score (1-5) based on how serious the risk appears from the transcript.
        """

        print(f"Extracting risk insights for quarter {quarter}...")

        response = self.client.chat.parse(
            model=STRUCTURED_MODEL,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": f"Extract risk insights from this earnings call transcript for {quarter}:\n\n{transcript}"}
            ],
            response_format=RiskInsightsResponse,
            temperature=0.1
        )

        print(f"Risk agent completed for {quarter}")
        parsed_response = json.loads(response.choices[0].message.content)
        return parsed_response['insights']

Agente de la competencia

class CompetitorAgent(Agent):
    """Agent for competitive analysis of earnings call transcripts"""

    def analyze(self, transcript: str, quarter: str) -> List[CompetitorInsight]:
        """Extract competitor insights from transcript"""
        system_prompt = """
        You are a competitive intelligence analyst focused on the company's positioning relative to competitors.

        Focus on:
        - Direct mentions of competitors
        - Market share discussions
        - Competitive advantages or disadvantages
        - Differentiation strategies
        - Responses to competitive threats
        - Emerging competition

        Extract specific insights about the company's competitive positioning in different market segments.
        Note who mentioned the competitive information (CEO, CFO, analyst, etc.)
        """

        print(f"Extracting competitor insights for quarter {quarter}...")

        response = self.client.chat.parse(
            model=STRUCTURED_MODEL,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": f"Extract competitive insights from this earnings call transcript for {quarter}:\n\n{transcript}"}
            ],
            response_format=CompetitorInsightsResponse,
            temperature=0.1
        )

        print(f"Competitor agent completed for {quarter}")
        parsed_response = json.loads(response.choices[0].message.content)
        return parsed_response['insights']

Agente de análisis temporal

class TemporalAnalysisAgent(Agent):
    """Agent for analyzing trends across quarters"""

    def analyze(self, all_insights: Dict) -> List[TemporalInsight]:
        """Analyze trends and patterns across quarters"""
        system_prompt = """
        You are a trend analyst specialized in identifying patterns, changes, and developments
        across multiple quarters of earnings calls.

        Focus on:
        - Growing or declining emphasis on specific topics
        - Evolving business priorities
        - Shifts in competitive positioning
        - Changes in risk factors
        - Sentiment trends

        Identify meaningful patterns that show how the business is evolving over time.
        Use specific evidence from multiple quarters to support each trend you identify.
        """

        print("Running temporal analysis across quarters...")

        # Format insights for analysis
        formatted_insights = self._format_insights_for_analysis(all_insights)

        response = self.client.chat.parse(
            model=STRUCTURED_MODEL,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": f"Analyze these insights across quarters to identify trends and patterns:\n\n{formatted_insights}"}
            ],
            response_format=TemporalInsightsResponse,
            temperature=0.2
        )

        print("Temporal analysis completed")
        parsed_response = json.loads(response.choices[0].message.content)
        return parsed_response['insights']

    def _format_insights_for_analysis(self, all_insights: Dict) -> str:
        """Format all insights for temporal analysis"""
        formatted = ""
        for agent_type, quarters_data in all_insights.items():
            formatted += f"\n## {agent_type.capitalize()} Insights Across Quarters:\n"
            for quarter, insights in quarters_data.items():
                formatted += f"\n### {quarter}:\n"
                if isinstance(insights, list):
                    for insight in insights:
                        # Convert insight object to string representation
                        if isinstance(insight, dict):
                            insight_str = json.dumps(insight)
                        else:
                            insight_str = str(insight)
                        formatted += f"- {insight_str}\n"
                else:
                    formatted += f"{insights}\n"
        return formatted

Procesador de consultas

El Procesador de Consultas analiza las preguntas del usuario para determinar los componentes específicos necesarios:

  • Interpreta las consultas sobre las llamadas de ganancias de NVIDIA.
  • Identifica qué trimestres (Q1-Q4) son relevantes para la pregunta.
  • Determina qué tipos de agente deben activarse según el contenido de la consulta.
  • Decide si se requiere un análisis temporal entre trimestres.
  • Proporciona una interpretación clara de la intención del usuario.

Este componente asegura que el flujo de trabajo active solo las rutas de análisis necesarias, mejorando la eficiencia y manteniendo respuestas completas.

class QueryProcessor:
    """Processes user queries to determine workflow requirements"""

    def __init__(self):
        self.client = mistral_client

    def analyze_query(self, query: str, company: str) -> QueryAnalysis:
        """Analyze user query to determine required components"""
        system_prompt = f"""
        You are a query analyzer for {company} earnings call transcripts.
        Extract key information about which quarters and agent types are needed.

        For agent_types, select from these options: Financial, Strategic, Sentiment, Risk, Competitor
        For quarters, select from these options: Q1, Q2, Q3, Q4

        Determine if temporal analysis is needed (comparing across quarters).
        Provide a brief description of the user's intent.
        """

        print(f"Analyzing query: {query}")

        response = self.client.chat.parse(
            model=STRUCTURED_MODEL,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": f"Analyze this query about {company}: {query}"}
            ],
            response_format=QueryAnalysis,
            temperature=0
        )

        print("Query analysis completed")
        parsed_response = json.loads(response.choices[0].message.content)

        # Display query analysis results
        print(f"Quarters needed: {parsed_response['quarters']}")
        print(f"Agent types needed: {parsed_response['agent_types']}")
        print(f"Temporal analysis required: {parsed_response['temporal_analysis_required']}")
        print(f"Query intent: {parsed_response['query_intent']}")

        return parsed_response

Capa de orquestación

El EarningsCallAnalysisOrchestrator coordina todo el flujo de trabajo de análisis con funciones clave:

  • process_transcript(): Analiza una transcripción trimestral con todos los agentes especializados.
  • generate_comprehensive_report(): Crea informes detallados en los trimestres seleccionados.
  • answer_query(): Proporciona respuestas específicas a preguntas sobre llamadas de ganancias.
  • _generate_report_sections(): Produce secciones estructuradas del informe (financieras, estratégicas, etc.).
  • _generate_query_response(): Elabora respuestas enfocadas a partir de información relevante.
  • _compile_report(): Ensambla todas las secciones en un documento markdown coherente.

Esta orquestación asegura una utilización eficiente de los recursos al tiempo que ofrece informes de análisis detallados y respuestas precisas a las consultas.

class EarningsCallAnalysisOrchestrator:
    """Agentic workflow orchestrator for earnings call analysis combining report generation and query capabilities"""

    def __init__(self, company: str, year: str, mistral_client: Mistral):
        self.company = company
        self.year = year
        self.insights_store = InsightsStore(company, year)

        # Initialize agents
        self.financial_agent = FinancialAgent()
        self.strategic_agent = StrategicAgent()
        self.sentiment_agent = SentimentAgent()
        self.risk_agent = RiskAgent()
        self.competitor_agent = CompetitorAgent()
        self.temporal_agent = TemporalAnalysisAgent()

        # Initialize query processor
        self.query_processor = QueryProcessor()

        # Initialize Mistral client
        self.client = mistral_client

    def process_transcript(self, quarter: str):
        """Process a transcript and store all insights"""
        print(f"\n=== Processing {self.company} {self.year} {quarter} transcript ===\n")

        try:
            # Get transcript for this quarter
            transcript = PDFParser.get_transcript_by_quarter(self.company, quarter, self.year, self.client)

            # Run all agents on the transcript
            financial_insights = self.financial_agent.analyze(transcript, quarter)
            self.insights_store.add_insights("financial", quarter, financial_insights)

            strategic_insights = self.strategic_agent.analyze(transcript, quarter)
            self.insights_store.add_insights("strategic", quarter, strategic_insights)

            sentiment_insights = self.sentiment_agent.analyze(transcript, quarter)
            self.insights_store.add_insights("sentiment", quarter, sentiment_insights)

            risk_insights = self.risk_agent.analyze(transcript, quarter)
            self.insights_store.add_insights("risk", quarter, risk_insights)

            competitor_insights = self.competitor_agent.analyze(transcript, quarter)
            self.insights_store.add_insights("competitor", quarter, competitor_insights)

            print(f"\n=== Completed processing {self.company} {self.year} {quarter} transcript ===\n")
            return True
        except Exception as e:
            print(f"Error processing transcript for {quarter}: {str(e)}")
            return False

    def process_all_transcripts(self):
        """Process all quarterly transcripts for the year"""
        all_success = True
        for quarter in ["Q1", "Q2", "Q3", "Q4"]:
            success = self.process_transcript(quarter)
            all_success = all_success and success
        return all_success

    def generate_comprehensive_report(self, quarters=None):
        """Generate a comprehensive report for specified quarters or all quarters"""
        if quarters is None:
            quarters = ["Q1", "Q2", "Q3", "Q4"]

        print(f"\n=== Generating comprehensive report for {self.company} {self.year} {', '.join(quarters)} ===\n")

        # Ensure all needed transcripts are processed
        for quarter in quarters:
            if quarter not in self.insights_store.get_insights("financial"):
                print(f"Processing missing transcript for {quarter}...")
                self.process_transcript(quarter)

        # Get all insights for the specified quarters
        all_insights = {
            "financial": self.insights_store.get_insights("financial", quarters),
            "strategic": self.insights_store.get_insights("strategic", quarters),
            "sentiment": self.insights_store.get_insights("sentiment", quarters),
            "risk": self.insights_store.get_insights("risk", quarters),
            "competitor": self.insights_store.get_insights("competitor", quarters)
        }

        # Run temporal analysis if multiple quarters
        if len(quarters) > 1:
            temporal_insights = self.temporal_agent.analyze(all_insights)
            quarters_key = "_".join(sorted(quarters))
            self.insights_store.add_insights("temporal", quarters_key, temporal_insights)
        else:
            temporal_insights = []

        # Generate report sections
        report_sections = self._generate_report_sections(quarters, all_insights, temporal_insights)

        # Compile final report
        report_content = self._compile_report(report_sections, quarters)

        # Save report to file
        output_file = f"{self.company}_{self.year}_{'_'.join(quarters)}_Analysis.md"
        with open(output_file, "w", encoding="utf-8") as f:
            f.write(report_content)

        print(f"\n=== Report saved to {output_file} ===\n")
        return output_file, report_content

    def answer_query(self, query: str):
        """Answer a specific query about earnings calls"""
        print(f"\n=== Processing query: {query} ===\n")

        # Analyze the query to determine which quarters and agents to use
        query_analysis = self.query_processor.analyze_query(query, self.company)

        # Ensure we have the necessary insights
        for quarter in query_analysis["quarters"]:
            if quarter not in self.insights_store.get_insights("financial"):
                print(f"Processing missing transcript for {quarter}...")
                self.process_transcript(quarter)

        # Collect relevant insights based on the query
        relevant_insights = {}
        for agent_type in query_analysis["agent_types"]:
            agent_key = agent_type.lower()
            relevant_insights[agent_key] = self.insights_store.get_insights(agent_key, query_analysis["quarters"])

        # Get temporal insights if needed
        temporal_insights = None
        if query_analysis["temporal_analysis_required"] and len(query_analysis["quarters"]) > 1:
            # Either use existing temporal insights or generate new ones
            quarters_key = "_".join(sorted(query_analysis["quarters"]))
            if quarters_key in self.insights_store.get_insights("temporal"):
                temporal_insights = self.insights_store.get_insights("temporal")[quarters_key]
            else:
                temporal_insights = self.temporal_agent.analyze(relevant_insights)
                self.insights_store.add_insights("temporal", quarters_key, temporal_insights)

        # Generate response to the query
        response = self._generate_query_response(query, query_analysis, relevant_insights, temporal_insights)

        print("\n=== Query processing completed ===\n")
        return response

    def _generate_report_sections(self, quarters, all_insights, temporal_insights):
        """Generate all sections for the comprehensive report"""
        print("Generating report sections...")

        report_sections = {}

        # Executive Summary
        report_sections["executive_summary"] = self._generate_executive_summary(quarters, all_insights, temporal_insights)

        # Financial Performance
        report_sections["financial_performance"] = self._generate_financial_section(quarters, all_insights)

        # Strategic Initiatives
        report_sections["strategic_initiatives"] = self._generate_strategic_section(quarters, all_insights)

        # Market Positioning
        report_sections["market_positioning"] = self._generate_market_section(quarters, all_insights)

        # Risk Assessment
        report_sections["risk_assessment"] = self._generate_risk_section(quarters, all_insights)

        # Quarterly Trends
        if len(quarters) > 1:
            report_sections["quarterly_trends"] = self._generate_trends_section(temporal_insights)

        # Outlook and Projections
        if "Q4" in quarters or len(quarters) > 2:
            report_sections["outlook"] = self._generate_outlook_section(quarters, all_insights, temporal_insights)

        print("Report sections generated")
        return report_sections

    def _generate_executive_summary(self, quarters, all_insights, temporal_insights):
        """Generate executive summary from all insights"""
        system_prompt = f"""
        You are a senior financial analyst creating an executive summary for a comprehensive report on {self.company}'s
        performance across {', '.join(quarters)} of {self.year}.

        Create a concise, high-level overview that captures:
        1. Key financial performance highlights
        2. Major strategic developments
        3. Notable shifts in market positioning
        4. Significant risks or challenges
        5. Overall business trajectory

        Keep the summary professional, balanced, and data-driven.

        IMPORTANT FORMATTING INSTRUCTIONS:
          - Use bullet points (not numbers) for lists with only one item
          - Only use numbered lists when there are multiple items that need to be ordered
          - Format subheadings as bold text using ** for emphasis
        """

        # Format insights for the summary
        formatted_insights = ""
        for insight_type, quarters_data in all_insights.items():
            formatted_insights += f"\n## {insight_type.capitalize()} Insights:\n"
            for quarter, insights in quarters_data.items():
                formatted_insights += f"\n### {quarter}:\n"
                for insight in insights[:5]:  # Limit to 5 insights per type per quarter
                    formatted_insights += f"- {json.dumps(insight)}\n"

        # Add temporal insights if available
        if temporal_insights:
            formatted_insights += "\n## Temporal Trends:\n"
            for insight in temporal_insights[:10]:  # Limit to 10 temporal insights
                if isinstance(insight, dict):
                    formatted_insights += f"- Trend: {insight.get('trend_type', 'N/A')}, Topic: {insight.get('topic', 'N/A')}\n"
                    formatted_insights += f"  Description: {insight.get('description', 'N/A')}\n"
                else:
                    formatted_insights += f"- {str(insight)}\n"

        response = self.client.chat.complete(
            model=DEFAULT_MODEL,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": f"Generate an executive summary for {self.company}'s {self.year} performance based on these insights:\n\n{formatted_insights}"}
            ],
            temperature=0.3
        )

        return {
            "title": "Executive Summary",
            "content": response.choices[0].message.content
        }

    def _generate_financial_section(self, quarters, all_insights):
        """Generate financial performance section"""
        system_prompt = f"""
        You are a financial analyst creating a detailed report section on {self.company}'s financial performance
        across {', '.join(quarters)} of {self.year}.

        Create a comprehensive analysis that includes:
        1. Quarter-by-quarter revenue analysis (overall and by segment)
        2. Profitability metrics and trends
        3. Cash flow and balance sheet highlights
        4. Key performance indicators and their trajectories
        5. Comparison of actual results vs. guidance

        Use subsections with clear headings, and include specific figures whenever available.

        IMPORTANT FORMATTING INSTRUCTIONS:
          - Use bullet points (not numbers) for lists with only one item
          - Only use numbered lists when there are multiple items that need to be ordered
          - Format subheadings as bold text using ** for emphasis
        """

        # Format financial insights for all quarters
        financial_insights = ""
        for quarter, insights in all_insights["financial"].items():
            financial_insights += f"\n## {quarter} Financial Insights:\n"
            for insight in insights:
                if isinstance(insight, dict):
                    financial_insights += f"- Metric: {insight.get('metric_name', 'N/A')}, Value: {insight.get('value', 'N/A')}\n"
                    financial_insights += f"  Context: {insight.get('context', 'N/A')}\n"
                else:
                    financial_insights += f"- {str(insight)}\n"

        response = self.client.chat.complete(
            model=DEFAULT_MODEL,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": f"Generate a comprehensive financial performance section for {self.company}'s {self.year} based on these insights:\n\n{financial_insights}"}
            ],
            temperature=0.3
        )

        return {
            "title": "Financial Performance Analysis",
            "content": response.choices[0].message.content
        }

    def _generate_strategic_section(self, quarters, all_insights):
        """Generate strategic initiatives section"""
        system_prompt = f"""
        You are a business strategy analyst creating a detailed report section on {self.company}'s strategic initiatives
        across {', '.join(quarters)} of {self.year}.

        Create a comprehensive analysis that includes:
        1. Key strategic priorities and how they evolved
        2. Product roadmap developments
        3. Major partnerships and acquisitions
        4. R&D focus areas and investments
        5. Market expansion efforts

        Organize by major strategic themes, highlighting changes in emphasis over time.

        IMPORTANT FORMATTING INSTRUCTIONS:
          - Use bullet points (not numbers) for lists with only one item
          - Only use numbered lists when there are multiple items that need to be ordered
          - Format subheadings as bold text using ** for emphasis
        """

        # Format strategic insights for all quarters
        strategic_insights = ""
        for quarter, insights in all_insights["strategic"].items():
            strategic_insights += f"\n## {quarter} Strategic Insights:\n"
            for insight in insights:
                if isinstance(insight, dict):
                    strategic_insights += f"- Initiative: {insight.get('initiative', 'N/A')}, Importance: {insight.get('importance', 'N/A')}/5\n"
                    strategic_insights += f"  Description: {insight.get('description', 'N/A')}\n"
                    if insight.get('timeframe'):
                        strategic_insights += f"  Timeframe: {insight.get('timeframe')}\n"
                else:
                    strategic_insights += f"- {str(insight)}\n"

        response = self.client.chat.complete(
            model=DEFAULT_MODEL,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": f"Generate a comprehensive strategic initiatives section for {self.company}'s {self.year} based on these insights:\n\n{strategic_insights}"}
            ],
            temperature=0.3
        )

        return {
            "title": "Strategic Initiatives Analysis",
            "content": response.choices[0].message.content
        }

    def _generate_market_section(self, quarters, all_insights):
        """Generate market positioning section"""
        system_prompt = f"""
        You are a market analyst creating a detailed report section on {self.company}'s competitive positioning
        across {', '.join(quarters)} of {self.year}.

        Create a comprehensive analysis that includes:
        1. {self.company}'s position in key market segments
        2. Competitive dynamics with major rivals
        3. Market share developments
        4. Differentiation strategies
        5. Emerging competition and responses

        Organize by major market segments, analyzing competitive position in each.

        IMPORTANT FORMATTING INSTRUCTIONS:
          - Use bullet points (not numbers) for lists with only one item
          - Only use numbered lists when there are multiple items that need to be ordered
          - Format subheadings as bold text using ** for emphasis
        """

        # Format competitor insights for all quarters
        competitor_insights = ""
        for quarter, insights in all_insights["competitor"].items():
            competitor_insights += f"\n## {quarter} Competitive Insights:\n"
            for insight in insights:
                if isinstance(insight, dict):
                    competitor_insights += f"- Market Segment: {insight.get('market_segment', 'N/A')}\n"
                    if insight.get('competitor'):
                        competitor_insights += f"  Competitor: {insight.get('competitor')}\n"
                    competitor_insights += f"  Positioning: {insight.get('positioning', 'N/A')}\n"
                    competitor_insights += f"  Mentioned by: {insight.get('mentioned_by', 'N/A')}\n"
                else:
                    competitor_insights += f"- {str(insight)}\n"

        # Also include sentiment insights as they relate to market positioning
        for quarter, insights in all_insights["sentiment"].items():
            competitor_insights += f"\n## {quarter} Sentiment Insights (Market Related):\n"
            for insight in insights:
                if isinstance(insight, dict) and any(market_term in insight.get('topic', '').lower() for market_term in
                                                    ['market', 'competitor', 'competition', 'position', 'share']):
                    competitor_insights += f"- Topic: {insight.get('topic', 'N/A')}, Sentiment: {insight.get('sentiment', 'N/A')}\n"
                    competitor_insights += f"  Speaker: {insight.get('speaker', 'N/A')}\n"
                    competitor_insights += f"  Evidence: {insight.get('evidence', 'N/A')}\n"

        response = self.client.chat.complete(
            model=DEFAULT_MODEL,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": f"Generate a comprehensive market positioning section for {self.company}'s {self.year} based on these insights:\n\n{competitor_insights}"}
            ],
            temperature=0.3
        )

        return {
            "title": "Market Positioning Analysis",
            "content": response.choices[0].message.content
        }

    def _generate_risk_section(self, quarters, all_insights):
        """Generate risk assessment section"""
        system_prompt = f"""
        You are a risk analyst creating a detailed report section on {self.company}'s risk factors and challenges
        across {', '.join(quarters)} of {self.year}.

        Create a comprehensive analysis that includes:
        1. Major risk categories (supply chain, competition, regulatory, etc.)
        2. Evolution of key risks throughout the year
        3. Mitigation strategies mentioned by management
        4. Emerging vs. declining risk factors
        5. Assessment of risk management effectiveness

        Organize by risk categories, with severity assessments and trends over time.

        IMPORTANT FORMATTING INSTRUCTIONS:
          - Use bullet points (not numbers) for lists with only one item
          - Only use numbered lists when there are multiple items that need to be ordered
          - Format subheadings as bold text using ** for emphasis
        """

        # Format risk insights for all quarters
        risk_insights = ""
        for quarter, insights in all_insights["risk"].items():
            risk_insights += f"\n## {quarter} Risk Insights:\n"
            for insight in insights:
                if isinstance(insight, dict):
                    risk_insights += f"- Risk Factor: {insight.get('risk_factor', 'N/A')}, Severity: {insight.get('severity', 'N/A')}/5\n"
                    risk_insights += f"  Description: {insight.get('description', 'N/A')}\n"
                    risk_insights += f"  Potential Impact: {insight.get('potential_impact', 'N/A')}\n"
                    if insight.get('mitigation_mentioned'):
                        risk_insights += f"  Mitigation: {insight.get('mitigation_mentioned')}\n"
                else:
                    risk_insights += f"- {str(insight)}\n"

        response = self.client.chat.complete(
            model=DEFAULT_MODEL,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": f"Generate a comprehensive risk assessment section for {self.company}'s {self.year} based on these insights:\n\n{risk_insights}"}
            ],
            temperature=0.3
        )

        return {
            "title": "Risk Assessment",
            "content": response.choices[0].message.content
        }

    def _generate_trends_section(self, temporal_insights):
        """Generate quarterly trends section"""
        system_prompt = f"""
        You are a business analyst creating a detailed report section on {self.company}'s quarter-to-quarter trends
        across multiple dimensions.

        Create a comprehensive analysis that includes:
        1. Major trends across all analysis dimensions (financial, strategic, etc.)
        2. Inflection points or significant shifts during the year
        3. Business cycle position and momentum
        4. Management focus evolution
        5. Market reception changes

        Highlight the most significant developments and their implications.

        IMPORTANT FORMATTING INSTRUCTIONS:
          - Use bullet points (not numbers) for lists with only one item
          - Only use numbered lists when there are multiple items that need to be ordered
          - Format subheadings as bold text using ** for emphasis
        """

        # Format temporal insights
        formatted_temporal_insights = ""
        for insight in temporal_insights:
            if isinstance(insight, dict):
                formatted_temporal_insights += f"- Trend: {insight.get('trend_type', 'N/A')}, Topic: {insight.get('topic', 'N/A')}\n"
                formatted_temporal_insights += f"  Description: {insight.get('description', 'N/A')}\n"
                formatted_temporal_insights += f"  Quarters: {', '.join(insight.get('quarters_observed', ['N/A']))}\n"
                formatted_temporal_insights += f"  Evidence: {insight.get('supporting_evidence', 'N/A')}\n\n"
            else:
                formatted_temporal_insights += f"- {str(insight)}\n"

        response = self.client.chat.complete(
            model=DEFAULT_MODEL,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": f"Generate a comprehensive quarterly trends section for {self.company}'s {self.year} based on these insights:\n\n{formatted_temporal_insights}"}
            ],
            temperature=0.3
        )

        return {
            "title": "Quarterly Trends Analysis",
            "content": response.choices[0].message.content
        }

    def _generate_outlook_section(self, quarters, all_insights, temporal_insights):
        """Generate outlook and projections section"""
        system_prompt = f"""
        You are a forward-looking analyst creating a detailed outlook section for {self.company}
        based on earnings call insights across multiple quarters.

        Create a comprehensive outlook that includes:
        1. Forward guidance from management
        2. Key initiatives to watch in the coming year
        3. Potential challenges and opportunities
        4. Market segment outlooks
        5. Long-term strategic trajectory

        Focus particularly on the most recent quarter and guidance, but incorporate the full context.

        IMPORTANT FORMATTING INSTRUCTIONS:
          - Use bullet points (not numbers) for lists with only one item
          - Only use numbered lists when there are multiple items that need to be ordered
          - Format subheadings as bold text using ** for emphasis
        """

        # Get the latest quarter's insights
        latest_quarter = sorted(quarters)[-1]

        # Financial insights from latest quarter
        latest_insights = f"\n## {latest_quarter} Financial Insights:\n"
        if latest_quarter in all_insights["financial"]:
            for insight in all_insights["financial"][latest_quarter]:
                if isinstance(insight, dict):
                    latest_insights += f"- Metric: {insight.get('metric_name', 'N/A')}, Value: {insight.get('value', 'N/A')}\n"
                    latest_insights += f"  Context: {insight.get('context', 'N/A')}\n"
                else:
                    latest_insights += f"- {str(insight)}\n"

        # Strategic insights from latest quarter
        latest_insights += f"\n## {latest_quarter} Strategic Insights:\n"
        if latest_quarter in all_insights["strategic"]:
            for insight in all_insights["strategic"][latest_quarter]:
                if isinstance(insight, dict):
                    latest_insights += f"- Initiative: {insight.get('initiative', 'N/A')}, Importance: {insight.get('importance', 'N/A')}/5\n"
                    latest_insights += f"  Description: {insight.get('description', 'N/A')}\n"
                else:
                    latest_insights += f"- {str(insight)}\n"

        # Add sentiment insights about future outlook
        latest_insights += f"\n## {latest_quarter} Sentiment on Future Outlook:\n"
        if latest_quarter in all_insights["sentiment"]:
            for insight in all_insights["sentiment"][latest_quarter]:
                if isinstance(insight, dict) and any(future_term in insight.get('topic', '').lower() for future_term in
                                                    ['outlook', 'future', 'guidance', 'next quarter', 'next year', 'projection']):
                    latest_insights += f"- Topic: {insight.get('topic', 'N/A')}, Sentiment: {insight.get('sentiment', 'N/A')}\n"
                    latest_insights += f"  Speaker: {insight.get('speaker', 'N/A')}\n"
                    latest_insights += f"  Evidence: {insight.get('evidence', 'N/A')}\n"

        # Add temporal insights if available
        if temporal_insights:
            latest_insights += "\n## Overall Trends:\n"
            for insight in temporal_insights:
                if isinstance(insight, dict):
                    latest_insights += f"- Trend: {insight.get('trend_type', 'N/A')}, Topic: {insight.get('topic', 'N/A')}\n"
                    latest_insights += f"  Description: {insight.get('description', 'N/A')}\n"
                else:
                    latest_insights += f"- {str(insight)}\n"

        response = self.client.chat.complete(
            model=DEFAULT_MODEL,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": f"Generate a comprehensive outlook and projections section for {self.company} based on their {self.year} earnings calls:\n\n{latest_insights}"}
            ],
            temperature=0.3
        )

        return {
            "title": "Outlook and Projections",
            "content": response.choices[0].message.content
        }

    def _compile_report(self, report_sections, quarters):
        """Compile all sections into a final comprehensive report"""
        print("Compiling final comprehensive report...")

        # Assemble full report content
        report_content = f"# {self.company} {self.year} Earnings Call Analysis\n\n"

        if len(quarters) == 4:
            report_content += f"## Annual Comprehensive Analysis Report\n\n"
        else:
            report_content += f"## Analysis Report for {', '.join(quarters)}\n\n"

        # Add date generated
        report_content += f"*Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*\n\n"

        # Add executive summary
        report_content += f"# {report_sections['executive_summary']['title']}\n\n"
        report_content += f"{report_sections['executive_summary']['content']}\n\n"

        # Add financial performance
        report_content += f"# {report_sections['financial_performance']['title']}\n\n"
        report_content += f"{report_sections['financial_performance']['content']}\n\n"

        # Add strategic initiatives
        report_content += f"# {report_sections['strategic_initiatives']['title']}\n\n"
        report_content += f"{report_sections['strategic_initiatives']['content']}\n\n"

        # Add market positioning
        report_content += f"# {report_sections['market_positioning']['title']}\n\n"
        report_content += f"{report_sections['market_positioning']['content']}\n\n"

        # Add risk assessment
        report_content += f"# {report_sections['risk_assessment']['title']}\n\n"
        report_content += f"{report_sections['risk_assessment']['content']}\n\n"

        # Add quarterly trends if available
        if 'quarterly_trends' in report_sections:
            report_content += f"# {report_sections['quarterly_trends']['title']}\n\n"
            report_content += f"{report_sections['quarterly_trends']['content']}\n\n"

        # Add outlook if available
        if 'outlook' in report_sections:
            report_content += f"# {report_sections['outlook']['title']}\n\n"
            report_content += f"{report_sections['outlook']['content']}\n\n"

        return report_content

    def _generate_query_response(self, query, query_analysis, relevant_insights, temporal_insights):
        """Generate response to a specific query"""
        system_prompt = f"""
        You are an expert analyst of {self.company} earnings calls.
        Provide a clear, concise response to the user's query based on the insights provided.
        Focus only on answering what was asked, using the most relevant insights.
        Include specific data points and evidence from the earnings calls.
        """

        # Format insights for prompt
        insights_formatted = ""
        for insight_type, quarters_data in relevant_insights.items():
            insights_formatted += f"\n## {insight_type.capitalize()} Insights:\n"
            for quarter, insights in quarters_data.items():
                insights_formatted += f"\n### {quarter}:\n"
                for insight in insights:
                    if isinstance(insight, dict):
                        insight_formatted = json.dumps(insight)
                    else:
                        insight_formatted = str(insight)
                    insights_formatted += f"- {insight_formatted}\n"

        # Add temporal insights if available
        temporal_formatted = ""
        if temporal_insights:
            temporal_formatted += "\n## Temporal Trends:\n"
            for insight in temporal_insights:
                if isinstance(insight, dict):
                    temporal_formatted += f"- Trend: {insight.get('trend_type', 'N/A')}, Topic: {insight.get('topic', 'N/A')}\n"
                    temporal_formatted += f"  Description: {insight.get('description', 'N/A')}\n"
                    temporal_formatted += f"  Quarters: {', '.join(insight.get('quarters_observed', ['N/A']))}\n"
                    temporal_formatted += f"  Evidence: {insight.get('supporting_evidence', 'N/A')}\n"
                else:
                    temporal_formatted += f"- {str(insight)}\n"

        user_prompt = f"""
        Query: {query}

        Quarters analyzed: {', '.join(query_analysis['quarters'])}
        Agent types used: {', '.join(query_analysis['agent_types'])}

        Insights collected:
        {insights_formatted}
        """

        if temporal_formatted:
            user_prompt += f"""
            Temporal insights:
            {temporal_formatted}
            """

        response = self.client.chat.complete(
            model=DEFAULT_MODEL,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": user_prompt}
            ],
            temperature=0.3
        )

        return response.choices[0].message.content

Inicializar el sistema para las llamadas de ganancias de NVIDIA 2025

company = "NVIDIA"
year = "2025"
orchestrator = EarningsCallAnalysisOrchestrator(company, year, mistral_client)

Procesar todas las transcripciones trimestrales

Procesamos todas las transcripciones trimestrales y generamos diferentes perspectivas a la vez, lo que hace que tanto la generación de informes como la respuesta a consultas sean más eficientes.

print("Processing all quarterly transcripts...")
quarters = ["Q1", "Q2", "Q3", "Q4"]
for quarter in quarters:
    success = orchestrator.process_transcript(quarter)
    if success:
        print(f"✓ Successfully processed {quarter} transcript")
    else:
        print(f"✗ Failed to process {quarter} transcript")
Processing all quarterly transcripts...

=== Processing NVIDIA 2025 Q1 transcript ===

Parsing transcript for NVIDIA 2025 Q1
Processing PDF file: nvidia_earnings_2025_Q1.pdf
Cached transcript for NVIDIA 2025 Q1
Extracting financial insights for quarter Q1...
Financial agent completed for Q1
Extracting strategic insights for quarter Q1...
Strategic agent completed for Q1
Extracting sentiment insights for quarter Q1...
Sentiment agent completed for Q1
Extracting risk insights for quarter Q1...
Risk agent completed for Q1
Extracting competitor insights for quarter Q1...
Competitor agent completed for Q1

=== Completed processing NVIDIA 2025 Q1 transcript ===

✓ Successfully processed Q1 transcript

=== Processing NVIDIA 2025 Q2 transcript ===

Parsing transcript for NVIDIA 2025 Q2
Processing PDF file: nvidia_earnings_2025_Q2.pdf
Cached transcript for NVIDIA 2025 Q2
Extracting financial insights for quarter Q2...
Error processing transcript for Q2: Unterminated string starting at: line 2 column 3 (char 4)
✗ Failed to process Q2 transcript

=== Processing NVIDIA 2025 Q3 transcript ===

Parsing transcript for NVIDIA 2025 Q3
Processing PDF file: nvidia_earnings_2025_Q3.pdf
Cached transcript for NVIDIA 2025 Q3
Extracting financial insights for quarter Q3...
Financial agent completed for Q3
Extracting strategic insights for quarter Q3...
Strategic agent completed for Q3
Extracting sentiment insights for quarter Q3...
Sentiment agent completed for Q3
Extracting risk insights for quarter Q3...
Risk agent completed for Q3
Extracting competitor insights for quarter Q3...
Competitor agent completed for Q3

=== Completed processing NVIDIA 2025 Q3 transcript ===

✓ Successfully processed Q3 transcript

=== Processing NVIDIA 2025 Q4 transcript ===

Parsing transcript for NVIDIA 2025 Q4
Processing PDF file: nvidia_earnings_2025_Q4.pdf
Cached transcript for NVIDIA 2025 Q4
Extracting financial insights for quarter Q4...
Financial agent completed for Q4
Extracting strategic insights for quarter Q4...
Strategic agent completed for Q4
Extracting sentiment insights for quarter Q4...
Sentiment agent completed for Q4
Extracting risk insights for quarter Q4...
Risk agent completed for Q4
Extracting competitor insights for quarter Q4...
Competitor agent completed for Q4

=== Completed processing NVIDIA 2025 Q4 transcript ===

✓ Successfully processed Q4 transcript

Generación de informes

Generamos un informe completo organizando la información de todos los trimestres en secciones estructuradas que incluyen un resumen ejecutivo, análisis financiero, iniciativas estratégicas, posicionamiento en el mercado, evaluación de riesgos y perspectivas futuras.

print("\nGenerating comprehensive annual report...")
report_file, report_content = orchestrator.generate_comprehensive_report(quarters)
Generating comprehensive annual report...

=== Generating comprehensive report for NVIDIA 2025 Q1, Q2, Q3, Q4 ===

Processing missing transcript for Q2...

=== Processing NVIDIA 2025 Q2 transcript ===

Using cached transcript for NVIDIA 2025 Q2
Extracting financial insights for quarter Q2...
Financial agent completed for Q2
Extracting strategic insights for quarter Q2...
Strategic agent completed for Q2
Extracting sentiment insights for quarter Q2...
Sentiment agent completed for Q2
Extracting risk insights for quarter Q2...
Risk agent completed for Q2
Extracting competitor insights for quarter Q2...
Competitor agent completed for Q2

=== Completed processing NVIDIA 2025 Q2 transcript ===

Running temporal analysis across quarters...
Temporal analysis completed
Generating report sections...
Report sections generated
Compiling final comprehensive report...

=== Report saved to NVIDIA_2025_Q1_Q2_Q3_Q4_Analysis.md ===
print(f"\nDisplaying report saved to: {report_file}")
display(Markdown(report_content))
Displaying report saved to: NVIDIA_2025_Q1_Q2_Q3_Q4_Analysis.md
<IPython.core.display.Markdown object>

Respuesta a consultas

Nuestro sistema analiza las preguntas del usuario para determinar los trimestres relevantes, los tipos de agente y las dimensiones de análisis, luego proporciona respuestas específicas utilizando solo la información más aplicable.

Consulta-1

Consulta - ¿Cuáles fueron las métricas financieras clave de NVIDIA en el primer y segundo trimestre de 2025?

Agentes utilizados - Agente financiero

Trimestres - Q1, Q2

Análisis temporal requerido - Falso

Año fiscal - 2025

query = "What were the key financial metrics in Q1 and Q2?"
answer = orchestrator.answer_query(query)
=== Processing query: What were the key financial metrics in Q1 and Q2? ===

Analyzing query: What were the key financial metrics in Q1 and Q2?
Query analysis completed
Quarters needed: ['Q1', 'Q2']
Agent types needed: ['Financial']
Temporal analysis required: True
Query intent: User wants to know the key financial metrics for NVIDIA in Q1 and Q2, likely to compare performance between these two quarters.
Running temporal analysis across quarters...
Temporal analysis completed

=== Query processing completed ===
display(Markdown(answer))
<IPython.core.display.Markdown object>

Consulta-2

Consulta - Identifica los cambios estratégicos en el negocio automotriz de NVIDIA a lo largo de 2025.

Agentes utilizados - Agente estratégico

Trimestres - Q1, Q2, Q3, Q4

Análisis temporal requerido - Verdadero

Año fiscal - 2025

query = "Identify strategic shifts in NVIDIA's automotive business across 2025"
answer = orchestrator.answer_query(query)
=== Processing query: Identify strategic shifts in NVIDIA's automotive business across 2025 ===

Analyzing query: Identify strategic shifts in NVIDIA's automotive business across 2025
Query analysis completed
Quarters needed: ['Q1', 'Q2', 'Q3', 'Q4']
Agent types needed: ['Strategic']
Temporal analysis required: True
Query intent: User wants to identify strategic shifts in NVIDIA's automotive business throughout the year 2025.

=== Query processing completed ===
display(Markdown(answer))
<IPython.core.display.Markdown object>

Consulta-3

Consulta - ¿Qué riesgos destacó NVIDIA en su llamada de ganancias del cuarto trimestre, y cómo se comparan con los mencionados en el tercer trimestre?

Agentes utilizados - Agente de riesgo

Trimestres - Q3, Q4

Análisis temporal requerido - Verdadero

Año fiscal - 2025

query = "What risks did NVIDIA highlight in their Q4 earnings call, and how do they compare to those mentioned in Q3?"
answer = orchestrator.answer_query(query)
=== Processing query: What risks did NVIDIA highlight in their Q4 earnings call, and how do they compare to those mentioned in Q3? ===

Analyzing query: What risks did NVIDIA highlight in their Q4 earnings call, and how do they compare to those mentioned in Q3?
Query analysis completed
Quarters needed: ['Q3', 'Q4']
Agent types needed: ['Risk']
Temporal analysis required: True
Query intent: Identify and compare risks highlighted by NVIDIA in their Q3 and Q4 earnings calls.
Running temporal analysis across quarters...
Temporal analysis completed

=== Query processing completed ===
display(Markdown(answer))
<IPython.core.display.Markdown object>
Lección del curso «Mistral Cookbook» de Mistral AI, publicado con licencia MIT. Traducción y adaptación al español de IA con Clase. IA con Clase no está afiliado a Mistral AI. Ver el original · Licencia
Esta lección es gratuita. El resto del curso se abre con la Membresía de IA con Clase, que incluye todos los cursos del catálogo. Ver precios