Tutorial 16: Enviando la Solicitud HTTP a la API de OpenAI
Tutorial 16: Enviando la Solicitud HTTP a la API de OpenAI
¡Hola y bienvenido de nuevo!
En esta lección vamos a crear el método final que enviará la solicitud HTTP a la API de OpenAI usando cURL. Este es el paso crucial donde nuestra aplicación se comunica con la IA para transcribir el audio. ¡Vamos a ello!
📋 Contenido del Tutorial
¿Qué es cURL y por qué lo usamos?
Corrigiendo errores en el código
Creando el método convert
El método getFile para manejar archivos
Configurando cURL paso a paso
Código completo
Explicación detallada
Probando la aplicación
Próximos pasos
🔗 1. ¿Qué es cURL y por qué lo usamos?
¿Qué es cURL?
cURL (Client URL) es una biblioteca y herramienta de línea de comandos que permite transferir datos usando varios protocolos (HTTP, HTTPS, FTP, etc.). En PHP, cURL nos permite hacer solicitudes HTTP a servidores externos.
¿Por qué usar cURL?
| Razón | Explicación |
|---|---|
| Flexibilidad | Soporta múltiples métodos HTTP (GET, POST, PUT, DELETE) |
| Headers | Podemos enviar headers personalizados |
| Archivos | Soporta subida de archivos |
| Seguridad | Soporta HTTPS y autenticación |
| Control | Configuraciones detalladas (timeouts, etc.) |
Alternativas a cURL
| Opción | Pros | Contras |
|---|---|---|
| cURL | Poderoso, flexible | Más complejo |
| file_get_contents() | Simple | Limitado, no soporta POST fácilmente |
| Guzzle | Moderno, orientado a objetos | Requiere librería externa |
| AJAX/JavaScript | Del lado del cliente | No aplica para PHP |
🐛 2. Corrigiendo errores en el código
Errores comunes en los headers
Header incorrecto:
'Authorication: Bearer ' . API_TOKEN // ❌ Mal escrito
Header correcto:
'Authorization: Bearer ' . API_TOKEN // ✅ Bien escrito
Content-Type incorrecto:
'Content-Type: mutlipart/form-data' // ❌ Mal escrito
Content-Type correcto:
'Content-Type: multipart/form-data' // ✅ Bien escrito
Código corregido para getHeader()
public function getHeader() { if ($this->dataType === "ASR") { return [ 'Authorization: Bearer ' . API_TOKEN, // ← Corregido 'Content-Type: multipart/form-data' // ← Corregido ]; } else { return [ 'Authorization: Bearer ' . API_TOKEN, // ← Corregido 'Content-Type: application/json' ]; } }
🚀 3. Creando el método convert
Estructura del método
public function convert() { // 1. Obtener la URL de la API $apiUrl = $this->getApiUrl(); // 2. Inicializar cURL $ch = curl_init($apiUrl); // 3. Configurar opciones de cURL curl_setopt($ch, CURLOPT_POST, true); $this->getFile(); curl_setopt($ch, CURLOPT_POSTFIELDS, $this->getData()); curl_setopt($ch, CURLOPT_HTTPHEADER, $this->getHeader()); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 4. Ejecutar la solicitud $response = curl_exec($ch); // 5. Cerrar la conexión curl_close($ch); // 6. Procesar la respuesta if ($response) { return json_decode($response, true); } else { $this->error = "API REQUEST FAILED"; return false; } }
¿Qué hace cada paso?
| Paso | Función | Descripción |
|---|---|---|
| 1 | getApiUrl() | Obtiene el endpoint correcto |
| 2 | curl_init() | Inicia una sesión cURL |
| 3 | curl_setopt() | Configura las opciones |
| 4 | curl_exec() | Ejecuta la solicitud |
| 5 | curl_close() | Cierra la sesión |
| 6 | json_decode() | Procesa la respuesta JSON |
📁 4. El método getFile para manejar archivos
¿Por qué necesitamos getFile()?
Cuando enviamos un archivo a la API, necesitamos crear un objeto CURLFile que represente el archivo. Esto le dice a cURL que debe enviar un archivo binario.
Implementación
public function getFile() { if ($this->dataType === 'ASR') { $this->file = curl_file_create($this->file); } }
¿Qué hace curl_file_create()?
// Antes de curl_file_create() $this->file = "files/video.mp4"; // Solo un string // Después de curl_file_create() $this->file = CURLFile Object { [name] => files/video.mp4 [mime] => video/mp4 [postname] => video.mp4 }
Uso en el método convert
public function convert() { $apiUrl = $this->getApiUrl(); $ch = curl_init($apiUrl); // Crear el objeto CURLFile antes de enviar $this->getFile(); // ← Convierte $this->file a CURLFile curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $this->getData()); // ... resto de la configuración }
⚙️ 5. Configurando cURL paso a paso
Opciones de cURL usadas
| Opción | Constante | Valor | Descripción |
|---|---|---|---|
| POST | CURLOPT_POST | true | Hace una solicitud POST |
| POSTFIELDS | CURLOPT_POSTFIELDS | Datos | Los datos a enviar |
| HTTPHEADER | CURLOPT_HTTPHEADER | Array | Headers HTTP |
| RETURNTRANSFER | CURLOPT_RETURNTRANSFER | true | Retorna la respuesta como string |
Explicación de cada opción
1. CURLOPT_POST
curl_setopt($ch, CURLOPT_POST, true);
Indica que haremos una solicitud POST
Es el método correcto para enviar archivos
2. CURLOPT_POSTFIELDS
curl_setopt($ch, CURLOPT_POSTFIELDS, $this->getData());
Los datos que enviaremos en el POST
Para ASR: un array con el archivo y el modelo
Para traducción: un string JSON
3. CURLOPT_HTTPHEADER
curl_setopt($ch, CURLOPT_HTTPHEADER, $this->getHeader());
Los headers HTTP que enviaremos
Incluye autenticación y tipo de contenido
4. CURLOPT_RETURNTRANSFER
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
Hace que
curl_exec()retorne la respuesta como stringSi es
false, muestra la respuesta directamente
Opciones adicionales recomendadas
// Timeout (límite de tiempo) curl_setopt($ch, CURLOPT_TIMEOUT, 60); // Verificar SSL (en producción) curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); // User Agent curl_setopt($ch, CURLOPT_USERAGENT, 'MyWhisper/1.0');
💻 6. Código completo
Archivo: backend/classes/Whisper.php
<?php /** * Clase Whisper * * Maneja la subida, transcripción y traducción de archivos * utilizando la API de OpenAI */ class Whisper { /** * @var string Mensajes de error */ public $error; /** * @var string Tipo de operación (ASR o TRANSLATE) */ public $dataType; /** * @var string Ruta del archivo para transcripción */ public $file; /** * @var string Texto para traducción */ public $content; /** * @var string Idioma destino para la traducción */ public $lang; /** * @var PDO Conexión a la base de datos */ private $DB; /** * Constructor - Establece conexión a la base de datos */ public function __construct() { $db = new DB(); $this->DB = $db->connect(); } /** * Obtiene el mensaje de error actual * * @return string El mensaje de error */ public function errors() { return $this->error; } /** * Obtiene la URL de la API según el tipo de operación * * @return string URL del endpoint de la API */ public function getApiUrl() { if ($this->dataType === "ASR") { return "https://api.openai.com/v1/audio/transcriptions"; } else { return "https://api.openai.com/v1/chat/completions"; } } /** * Obtiene los headers necesarios para la API * * @return array Headers para la solicitud HTTP */ public function getHeader() { if ($this->dataType === "ASR") { return [ 'Authorization: Bearer ' . API_TOKEN, 'Content-Type: multipart/form-data' ]; } else { return [ 'Authorization: Bearer ' . API_TOKEN, 'Content-Type: application/json' ]; } } /** * Prepara los datos para enviar a la API * * @return array|string Datos preparados según el tipo de operación */ public function getData() { if ($this->dataType === "ASR") { return [ 'file' => $this->file, 'model' => 'whisper-1', 'language' => 'es', 'response_format' => 'json' ]; } else { return json_encode([ 'model' => 'gpt-3.5-turbo', 'messages' => [ [ 'role' => 'system', 'content' => 'You will be provided with a text, and your task is to translate it into ' . $this->lang ], [ 'role' => 'user', 'content' => $this->content ] ], 'temperature' => 0.7 ]); } } /** * Convierte el archivo a un objeto CURLFile */ public function getFile() { if ($this->dataType === 'ASR') { // Verificar que el archivo existe if (!file_exists($this->file)) { $this->error = "El archivo no existe"; return false; } $this->file = curl_file_create($this->file); return true; } return true; } /** * Envía la solicitud a la API de OpenAI * * @return array|false Respuesta de la API o false en caso de error */ public function convert() { // 1. Obtener la URL de la API $apiUrl = $this->getApiUrl(); if (!$apiUrl) { $this->error = "No se pudo obtener la URL de la API"; return false; } // 2. Inicializar cURL $ch = curl_init($apiUrl); if (!$ch) { $this->error = "Error al inicializar cURL"; return false; } // 3. Configurar opciones de cURL curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 120); // 2 minutos máximo // 4. Crear el objeto CURLFile if (!$this->getFile()) { return false; } // 5. Configurar datos y headers curl_setopt($ch, CURLOPT_POSTFIELDS, $this->getData()); curl_setopt($ch, CURLOPT_HTTPHEADER, $this->getHeader()); // 6. Ejecutar la solicitud $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlError = curl_error($ch); // 7. Cerrar la conexión curl_close($ch); // 8. Verificar errores de cURL if ($curlError) { $this->error = "Error cURL: " . $curlError; return false; } // 9. Verificar código HTTP if ($httpCode !== 200) { $this->error = "Error HTTP: " . $httpCode . " - " . $response; return false; } // 10. Procesar la respuesta if ($response) { $data = json_decode($response, true); // Verificar si hay error en la respuesta if (isset($data['error'])) { $this->error = "Error de API: " . $data['error']['message']; return false; } return $data; } else { $this->error = "API REQUEST FAILED - No se recibió respuesta"; return false; } } /** * Sube un archivo al servidor * * @param array $file Arreglo $_FILES * @return string|false Ruta del archivo o false en caso de error */ public function upload($file) { $fileTmp = $file['tmp_name']; $filename = basename($file['name']); $fileSize = $file['size']; $errors = $file['error']; $mime = $file['type']; // Obtener la extensión del archivo $ext = pathinfo($filename, PATHINFO_EXTENSION); $ext = strtolower($ext); // Obtener el directorio raíz del proyecto $parentDirectoy = dirname(dirname(dirname(__FILE__))); $allowedMedia = ['video/mp4','video/mpeg', 'audio/mpeg','audio/mpeg3','audio/wav']; if (in_array($mime, $allowedMedia)) { if ($fileSize <= 20000000) { $folder = 'files/'; $file = $folder . md5(time() . mt_rand()) . '.' . $ext; move_uploaded_file($fileTmp, $parentDirectoy . '/' . $file); return $file; } else { $this->error = "El archivo es demasiado grande (máximo 20 MB)"; return false; } } else { $this->error = "Formato de archivo inválido"; return false; } } } ?>
Archivo: index.php (sección PHP actualizada)
<?php include 'backend/init.php'; $error = null; $result = null; if ($_SERVER['REQUEST_METHOD'] === "POST") { if (isset($_FILES['file']) && !empty($_FILES['file']['name'])) { // 1. Subir el archivo $file = $whisperObj->upload($_FILES['file']); if ($file) { // 2. Configurar para transcripción $whisperObj->dataType = 'ASR'; $whisperObj->file = $file; // 3. Enviar a la API $response = $whisperObj->convert(); if ($response) { // Mostrar el resultado echo "<div style='max-width:600px;margin:20px auto;padding:20px;background:#f0f9ff;border-radius:10px;'>"; echo "<h3>📝 Transcripción completada</h3>"; echo "<pre style='white-space:pre-wrap;font-family:inherit;'>" . htmlspecialchars($response['text'] ?? '') . "</pre>"; echo "</div>"; } else { $error = $whisperObj->errors(); } } else { $error = $whisperObj->errors(); } } else { $error = "Por favor selecciona un archivo para convertir a texto"; } } ?>
📖 7. Explicación detallada
Flujo completo de la solicitud
1. Usuario selecciona un archivo ↓ 2. JavaScript envía el formulario ↓ 3. PHP recibe la solicitud POST ↓ 4. $whisperObj->upload() guarda el archivo ↓ 5. Se configura $whisperObj->dataType = 'ASR' ↓ 6. Se configura $whisperObj->file = $file ↓ 7. Se llama a $whisperObj->convert() ↓ 8. convert() obtiene la URL con getApiUrl() ↓ 9. convert() inicializa cURL con curl_init() ↓ 10. convert() configura las opciones de cURL ↓ 11. getFile() convierte el archivo a CURLFile ↓ 12. getData() prepara los datos para enviar ↓ 13. getHeader() prepara los headers HTTP ↓ 14. cURL envía la solicitud a la API ↓ 15. La API procesa el archivo ↓ 16. cURL recibe la respuesta ↓ 17. convert() decodifica el JSON ↓ 18. La respuesta se muestra al usuario
¿Qué es CURLFile?
CURLFile es una clase en PHP que representa un archivo para ser enviado mediante cURL.
// Crear un objeto CURLFile $fileObject = curl_file_create('/ruta/al/archivo.mp4'); // Equivalente a: $fileObject = new CURLFile('/ruta/al/archivo.mp4'); // El objeto contiene: // - name: la ruta del archivo // - mime: el tipo MIME (detectado automáticamente) // - postname: el nombre para el campo POST
Manejo de errores completo
// 1. Error de cURL if ($curlError) { $this->error = "Error cURL: " . $curlError; return false; } // 2. Error HTTP (código de estado) if ($httpCode !== 200) { $this->error = "Error HTTP: " . $httpCode; return false; } // 3. Error de API (respuesta con error) if (isset($data['error'])) { $this->error = "Error de API: " . $data['error']['message']; return false; } // 4. Error de respuesta vacía if (!$response) { $this->error = "API REQUEST FAILED"; return false; }
🧪 8. Probando la aplicación
Preparación del archivo de prueba
Paso 1: Consigue un archivo de audio con voz
Puedes grabar un mensaje corto
Descargar un audio de prueba
Paso 2: Sube el archivo
Haz clic en "Upload File"
Selecciona el archivo
Paso 3: Espera el procesamiento
El loader se mostrará mientras se procesa
Puede tomar unos segundos
Paso 4: Verifica el resultado
La transcripción se mostrará en la página
Posibles errores y soluciones
| Error | Causa | Solución |
|---|---|---|
API REQUEST FAILED | No hay respuesta de la API | Verificar conexión a internet |
Error HTTP: 401 | API Key inválida | Verificar API_KEY en .env |
Error HTTP: 429 | Demasiadas solicitudes | Esperar unos minutos |
Error HTTP: 413 | Archivo demasiado grande | Usar archivos más pequeños |
Error cURL: 28 | Timeout | Aumentar CURLOPT_TIMEOUT |
📊 9. Resumen de la clase Whisper
Métodos completos
| Método | Descripción | Llama a |
|---|---|---|
__construct() | Constructor, conecta a BD | - |
errors() | Devuelve el error | - |
getApiUrl() | Obtiene URL de la API | - |
getHeader() | Obtiene headers HTTP | - |
getData() | Prepara los datos | - |
getFile() | Convierte archivo a CURLFile | - |
convert() | Envía solicitud a la API | getApiUrl(), getFile(), getData(), getHeader() |
upload() | Sube archivo al servidor | - |
Diagrama de dependencias
convert()
↓
├── getApiUrl()
│ ↓
│ └── dataType
│
├── getFile()
│ ↓
│ └── file → CURLFile
│
├── getData()
│ ↓
│ ├── dataType → ASR → file + model
│ └── dataType → TRANSLATE → JSON
│
└── getHeader()
↓
├── dataType → ASR → multipart/form-data
└── dataType → TRANSLATE → application/json🎯 10. Próximos pasos
Lo que hemos logrado
✅ Método convert() implementado
✅ Comunicación con la API usando cURL
✅ Manejo de archivos con CURLFile
✅ Procesamiento de respuestas JSON
✅ Manejo de errores completo
✅ Transcripción funcional
Lo que viene en la próxima lección
En la siguiente lección vamos a:
Guardar la transcripción en la base de datos
Mostrar la lista de archivos recientes
Crear la página de vista para ver transcripciones
Implementar el reproductor de audio/video
Avance del código de la próxima lección
// Guardar transcripción en la base de datos public function saveTranscription($fileId, $content) { $sql = "UPDATE files SET content = :content WHERE ID = :id"; $stmt = $this->DB->prepare($sql); return $stmt->execute([ ':content' => $content, ':id' => $fileId ]); } // Obtener archivos recientes public function getRecentFiles($limit = 10) { $sql = "SELECT * FROM files ORDER BY ID DESC LIMIT :limit"; $stmt = $this->DB->prepare($sql); $stmt->execute([':limit' => $limit]); return $stmt->fetchAll(); }
❓ Preguntas frecuentes
¿Qué es cURL?
Es una biblioteca que permite hacer solicitudes HTTP desde PHP
Usada para comunicarse con APIs externas
¿Por qué necesito curl_file_create()?
Para que cURL sepa que está enviando un archivo
Crea un objeto CURLFile con la información del archivo
¿Qué pasa si la API no responde?
cURL tendrá un timeout y devolverá un error
Se maneja con CURLOPT_TIMEOUT
¿Por qué usar json_decode()?
La API devuelve una respuesta en JSON
json_decode() convierte a array para trabajar en PHP
¿Qué significa el código HTTP 200?
Es el código de éxito ("OK")
Indica que la solicitud fue procesada correctamente
¿Cómo sé si la transcripción fue exitosa?
La respuesta tendrá un campo 'text' con el texto transcrito
Si hay error, la respuesta tendrá un campo 'error'
¡Excelente trabajo! Ahora tenemos una aplicación completamente funcional que puede transcribir audio/video usando la API de OpenAI. En la próxima lección, añadiremos la funcionalidad de guardar y mostrar las transcripciones.
Comentarios
Publicar un comentario