Skip to main content

Instalacion

pip install requests

Cliente Completo

import os
import json
import time
import requests
from typing import Optional, Dict, Any, List

class NeuracallClient:
    """Cliente para la API de Neuracall"""

    def __init__(
        self,
        client_id: Optional[str] = None,
        client_secret: Optional[str] = None,
        base_url: str = "https://api.neuracall.com"
    ):
        self.base_url = base_url
        self.client_id = client_id or os.environ["NEURACALL_CLIENT_ID"]
        self.client_secret = client_secret or os.environ["NEURACALL_CLIENT_SECRET"]
        self._token = None
        self._expires_at = 0

    def _get_token(self) -> str:
        """Obtiene o renueva el token de acceso"""
        if time.time() > self._expires_at - 300:
            response = requests.post(
                f"{self.base_url}/v1/auth",
                json={
                    "client_id": self.client_id,
                    "client_secret": self.client_secret
                }
            )
            response.raise_for_status()
            data = response.json()
            self._token = data["access_token"]
            self._expires_at = data["expires_at"]
        return self._token

    def _headers(self) -> Dict[str, str]:
        """Headers con autorizacion"""
        return {"Authorization": f"Bearer {self._get_token()}"}

    # ==================== CALL ANALYSIS ====================

    def create_analysis(
        self,
        audio_path: str,
        external_id: str,
        agent_id: str,
        agent_name: str,
        model_name: str,
        additional_parameters: Optional[Dict] = None
    ) -> Dict[str, Any]:
        """
        Crea un nuevo análisis de llamada.

        Args:
            audio_path: Ruta al archivo de audio
            external_id: ID externo de la llamada
            agent_id: ID del agente
            agent_name: Nombre del agente
            model_name: Nombre del modelo de análisis
            additional_parameters: Metadatos adicionales opciónales

        Returns:
            Datos del análisis creado
        """
        with open(audio_path, "rb") as audio_file:
            files = {"audio_file": audio_file}
            data = {
                "external_id": external_id,
                "agent_id": agent_id,
                "agent_name": agent_name,
                "model_name": model_name
            }
            if additional_parameters:
                data["additional_parameters"] = json.dumps(additional_parameters)

            response = requests.post(
                f"{self.base_url}/v1/call-analysis",
                headers=self._headers(),
                files=files,
                data=data
            )
            response.raise_for_status()
            return response.json()

    def get_analysis(self, analysis_id: str) -> Dict[str, Any]:
        """Obtiene un análisis por ID"""
        response = requests.get(
            f"{self.base_url}/v1/call-analysis/{analysis_id}",
            headers=self._headers()
        )
        response.raise_for_status()
        return response.json()

    def list_analyses(
        self,
        model_id: Optional[int] = None,
        start_date: Optional[str] = None,
        end_date: Optional[str] = None,
        page: int = 0,
        size: int = 20
    ) -> Dict[str, Any]:
        """Lista análisis con filtros y paginación"""
        params = {"page": page, "size": size}
        if model_id:
            params["model_id"] = model_id
        if start_date:
            params["start_date"] = start_date
        if end_date:
            params["end_date"] = end_date

        response = requests.get(
            f"{self.base_url}/v1/call-analysis",
            headers=self._headers(),
            params=params
        )
        response.raise_for_status()
        return response.json()

    def get_transcription(self, analysis_id: str) -> Dict[str, Any]:
        """Obtiene la transcripción de un análisis"""
        response = requests.get(
            f"{self.base_url}/v1/call-analysis/{analysis_id}/transcription",
            headers=self._headers()
        )
        response.raise_for_status()
        return response.json()

    def wait_for_completion(
        self,
        analysis_id: str,
        timeout: int = 600,
        poll_interval: int = 5
    ) -> Dict[str, Any]:
        """
        Espera a que un análisis se complete.

        Args:
            analysis_id: ID del análisis
            timeout: Tiempo máximo de espera en segúndos
            poll_interval: Intervalo entre consultas en segúndos

        Returns:
            Datos del análisis completado
        """
        start = time.time()
        while time.time() - start < timeout:
            result = self.get_analysis(analysis_id)
            status = result["status"]

            if status == "PROCESS_COMPLETED":
                return result
            elif status == "ERROR":
                raise Exception(f"Analysis failed: {result.get('error_message')}")

            time.sleep(poll_interval)

        raise TimeoutError(f"Analysis {analysis_id} did not complete within {timeout}s")

    # ==================== MODELS ====================

    def list_models(self, order_by_name: bool = False) -> List[Dict[str, Any]]:
        """Lista todos los modelos de análisis"""
        response = requests.get(
            f"{self.base_url}/v1/models",
            headers=self._headers(),
            params={"order_by_name": order_by_name}
        )
        response.raise_for_status()
        return response.json()

    def get_model(self, model_id: int) -> Dict[str, Any]:
        """Obtiene un modelo por ID"""
        response = requests.get(
            f"{self.base_url}/v1/models/{model_id}",
            headers=self._headers()
        )
        response.raise_for_status()
        return response.json()

    def create_model(
        self,
        name: str,
        description: str,
        prompt: str
    ) -> Dict[str, Any]:
        """Crea un nuevo modelo de análisis"""
        response = requests.post(
            f"{self.base_url}/v1/models",
            headers=self._headers(),
            json={
                "name": name,
                "description": description,
                "prompt": prompt
            }
        )
        response.raise_for_status()
        return response.json()

    # ==================== MODEL CATEGORIES ====================

    def list_categories(self, model_id: int) -> List[Dict[str, Any]]:
        """Lista las categorías de un modelo"""
        response = requests.get(
            f"{self.base_url}/v1/models/{model_id}/categories",
            headers=self._headers()
        )
        response.raise_for_status()
        return response.json()

    def create_category(
        self,
        model_id: int,
        name: str,
        description: str,
        order_number: int
    ) -> Dict[str, Any]:
        """Crea una categoría en un modelo"""
        response = requests.post(
            f"{self.base_url}/v1/models/{model_id}/categories",
            headers=self._headers(),
            json={
                "name": name,
                "description": description,
                "order_number": order_number
            }
        )
        response.raise_for_status()
        return response.json()

    # ==================== MODEL VARIABLES ====================

    def list_variables(self, model_id: int) -> List[Dict[str, Any]]:
        """Lista las variables de un modelo"""
        response = requests.get(
            f"{self.base_url}/v1/models/{model_id}/variables",
            headers=self._headers()
        )
        response.raise_for_status()
        return response.json()

    def create_variable(
        self,
        model_id: int,
        key: str,
        readable_name: str,
        description: str,
        var_type: str,
        weight: int,
        category_id: int,
        required: bool = True
    ) -> Dict[str, Any]:
        """Crea una variable en un modelo"""
        response = requests.post(
            f"{self.base_url}/v1/models/{model_id}/variables",
            headers=self._headers(),
            json={
                "key": key,
                "readable_name": readable_name,
                "description": description,
                "type": var_type,
                "weight": weight,
                "category_id": category_id,
                "required": required
            }
        )
        response.raise_for_status()
        return response.json()

    # ==================== MODEL METADATA ====================

    def list_metadata(self, model_id: int) -> List[Dict[str, Any]]:
        """Lista los campos de metadata de un modelo"""
        response = requests.get(
            f"{self.base_url}/v1/models/{model_id}/metadata",
            headers=self._headers()
        )
        response.raise_for_status()
        return response.json()

    def create_metadata(
        self,
        model_id: int,
        key: str,
        readable_name: str,
        meta_type: str,
        grouping_field: bool = False
    ) -> Dict[str, Any]:
        """Crea un campo de metadata en un modelo"""
        response = requests.post(
            f"{self.base_url}/v1/models/{model_id}/metadata",
            headers=self._headers(),
            json={
                "key": key,
                "readable_name": readable_name,
                "type": meta_type,
                "grouping_field": grouping_field
            }
        )
        response.raise_for_status()
        return response.json()

    # ==================== STATISTICS ====================

    def get_basic_stats(
        self,
        start_date: str,
        end_date: str,
        model_name: str
    ) -> Dict[str, Any]:
        """Obtiene estadísticas básicas"""
        response = requests.get(
            f"{self.base_url}/v1/stats/basic",
            headers=self._headers(),
            params={
                "start_date": start_date,
                "end_date": end_date,
                "model_name": model_name
            }
        )
        response.raise_for_status()
        return response.json()


# ==================== EJEMPLO DE USO ====================

if __name__ == "__main__":
    # Inicializar cliente (usa variables de entorno)
    client = NeuracallClient()

    # Listar modelos disponibles
    print("Modelos disponibles:")
    models = client.list_models()
    for model in models:
        print(f"  - {model['name']} (ID: {model['id']})")

    # Crear análisis
    print("\nCreando análisis...")
    analysis = client.create_analysis(
        audio_path="llamada.mp3",
        external_id="CALL-2024-001",
        agent_id="AGT-001",
        agent_name="Juan Perez",
        model_name=models[0]["name"]
    )
    print(f"Análisis creado: {analysis['id']}")
    print(f"Estado: {analysis['status']}")

    # Esperar a que complete
    print("\nEsperando resultado...")
    result = client.wait_for_completion(analysis["id"])

    # Mostrar resultados
    print(f"\n=== RESULTADO ===")
    print(f"NeuraScore: {result['score_percentage']}%")
    print(f"Resumen: {result['summary']}")

    if result.get("insights"):
        print("\nInsights:")
        for i, insight in enumerate(result["insights"], 1):
            print(f"  {i}. {insight}")

    if result.get("keywords"):
        print(f"\nKeywords: {', '.join(result['keywords'])}")

Uso Rápido

from neuracall_client import NeuracallClient

client = NeuracallClient()

# Analizar una llamada
result = client.create_analysis(
    audio_path="llamada.mp3",
    external_id="CALL-001",
    agent_id="AGT-001",
    agent_name="Juan Perez",
    model_name="Evaluación Servicio"
)

# Esperar y obtener resultados
completed = client.wait_for_completion(result["id"])
print(f"Score: {completed['score_percentage']}%")

Manejo de Errores

import requests

try:
    result = client.create_analysis(...)
except requests.exceptions.HTTPError as e:
    if e.response.status_code == 401:
        print("Error de autenticación")
    elif e.response.status_code == 400:
        print(f"Datos invalidos: {e.response.json()}")
    else:
        print(f"Error HTTP: {e}")
except Exception as e:
    print(f"Error: {e}")

Gestión de Modelos

Crear un Modelo Completo

from neuracall_client import NeuracallClient

client = NeuracallClient()

# 1. Crear el modelo base
model = client.create_model(
    name="Evaluación Ventas Q1",
    description="Modelo para evaluar llamadas de ventas",
    prompt="Evalúa la llamada considerando: saludo, identificación de necesidades, presentación y cierre."
)
model_id = model["id"]
print(f"Modelo creado: {model['name']} (ID: {model_id})")

# 2. Crear categorías
categories_data = [
    {"name": "Apertura", "description": "Saludo e identificación", "order_number": 1},
    {"name": "Descubrimiento", "description": "Identificación de necesidades", "order_number": 2},
    {"name": "Presentación", "description": "Exposición de la solución", "order_number": 3},
    {"name": "Cierre", "description": "Confirmación y siguientes pasos", "order_number": 4}
]

categories = {}
for cat_data in categories_data:
    cat = client.create_category(
        model_id=model_id,
        name=cat_data["name"],
        description=cat_data["description"],
        order_number=cat_data["order_number"]
    )
    categories[cat["name"]] = cat["id"]
    print(f"  Categoría creada: {cat['name']} (ID: {cat['id']})")

# 3. Crear variables
variables_data = [
    # Apertura (20 puntos)
    {"key": "greeting", "readable_name": "Saludo", "description": "Saludo cordial y profesional",
     "type": "INTEGER", "weight": 10, "category": "Apertura"},
    {"key": "identification", "readable_name": "Identificación", "description": "Se identificó correctamente",
     "type": "INTEGER", "weight": 10, "category": "Apertura"},

    # Descubrimiento (25 puntos)
    {"key": "open_questions", "readable_name": "Preguntas Abiertas", "description": "Usó preguntas abiertas",
     "type": "INTEGER", "weight": 15, "category": "Descubrimiento"},
    {"key": "active_listening", "readable_name": "Escucha Activa", "description": "Demostró escucha activa",
     "type": "INTEGER", "weight": 10, "category": "Descubrimiento"},

    # Presentación (30 puntos)
    {"key": "benefits", "readable_name": "Presentación de Beneficios", "description": "Presentó beneficios claramente",
     "type": "INTEGER", "weight": 15, "category": "Presentación"},
    {"key": "objections", "readable_name": "Manejo de Objeciones", "description": "Manejó objeciones efectivamente",
     "type": "INTEGER", "weight": 15, "category": "Presentación"},

    # Cierre (25 puntos)
    {"key": "proposal", "readable_name": "Propuesta Clara", "description": "Hizo una propuesta clara",
     "type": "INTEGER", "weight": 10, "category": "Cierre"},
    {"key": "next_steps", "readable_name": "Próximos Pasos", "description": "Confirmó los próximos pasos",
     "type": "INTEGER", "weight": 15, "category": "Cierre"},

    # Variable de extracción (sin peso)
    {"key": "product_interest", "readable_name": "Producto de Interés", "description": "Producto mencionado",
     "type": "STRING", "weight": 0, "category": "Descubrimiento", "required": False}
]

for var_data in variables_data:
    var = client.create_variable(
        model_id=model_id,
        key=var_data["key"],
        readable_name=var_data["readable_name"],
        description=var_data["description"],
        var_type=var_data["type"],
        weight=var_data["weight"],
        category_id=categories[var_data["category"]],
        required=var_data.get("required", True)
    )
    print(f"  Variable creada: {var['readable_name']} (peso: {var['weight']})")

# 4. Crear campos de metadata
metadata_fields = [
    {"key": "campaign", "readable_name": "Campaña", "type": "STRING", "grouping_field": True},
    {"key": "product", "readable_name": "Producto", "type": "STRING", "grouping_field": True},
    {"key": "region", "readable_name": "Región", "type": "STRING", "grouping_field": True}
]

for meta_data in metadata_fields:
    meta = client.create_metadata(
        model_id=model_id,
        key=meta_data["key"],
        readable_name=meta_data["readable_name"],
        meta_type=meta_data["type"],
        grouping_field=meta_data["grouping_field"]
    )
    print(f"  Metadata creada: {meta['readable_name']} (agrupación: {meta['grouping_field']})")

print(f"\nModelo '{model['name']}' configurado completamente!")

Listar Configuración de un Modelo

# Obtener toda la configuración de un modelo
model_id = 15

model = client.get_model(model_id)
print(f"Modelo: {model['name']}")

categories = client.list_categories(model_id)
print(f"\nCategorías ({len(categories)}):")
for cat in categories:
    print(f"  - {cat['name']} (orden: {cat['order_number']})")

variables = client.list_variables(model_id)
print(f"\nVariables ({len(variables)}):")
total_weight = 0
for var in variables:
    print(f"  - {var['readable_name']}: {var['type']} (peso: {var['weight']})")
    total_weight += var['weight']
print(f"  Total de pesos: {total_weight}")

metadata = client.list_metadata(model_id)
print(f"\nMetadata ({len(metadata)}):")
for meta in metadata:
    grouping = "✓" if meta['grouping_field'] else "✗"
    print(f"  - {meta['readable_name']}: {meta['type']} (agrupación: {grouping})")

Manejo de Errores en Modelos

import requests

try:
    # Intentar modificar un modelo con análisis existentes
    client.create_variable(
        model_id=15,
        key="new_variable",
        readable_name="Nueva Variable",
        description="...",
        var_type="INTEGER",
        weight=10,
        category_id=42
    )
except requests.exceptions.HTTPError as e:
    if e.response.status_code == 409:
        print("Error: No se puede modificar un modelo que ya tiene análisis asociados.")
        print("Solución: Crea una nueva versión del modelo.")
    elif e.response.status_code == 404:
        print("Error: El modelo o categoría no existe.")
    else:
        print(f"Error HTTP: {e.response.status_code} - {e.response.json()}")