Sistema de análisis de llamadas de ganancias multiagente (MAECAS)
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:
- Extrae información de las transcripciones trimestrales utilizando agentes de análisis especializados.
- Ofrece tanto informes completos como respuestas a consultas específicas.
- Identifica tendencias y patrones entre trimestres.
- 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

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)
[2K [90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m288.7/288.7 kB[0m [31m7.7 MB/s[0m eta [36m0:00:00[0m
[?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
DEFAULT_MODEL - Para análisis general
STRUCTURED_MODEL - Para resultados estructurados
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>