Instalacion
Copy
pip install requests
Cliente Completo
Copy
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
Copy
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
Copy
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
Copy
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
Copy
# 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
Copy
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()}")
