====== 2. Proxy Meteorológico con n8n, AEMET y Ollama ======
Este ejercicio consiste en crear un flujo de trabajo en n8n que actúe como un puente (proxy) entre un cliente de IA (Open WebUI) y los datos reales de la AEMET, procesados por un modelo local de Ollama.
===== Paso 1: Webhook y Extracción de Datos de AEMET (JSON Crudo) =====
El primer objetivo es crear un punto de entrada que, al ser consultado, devuelva el JSON técnico de la estación meteorológica de Valencia.
**Configuración detallada:**
* **Nodo Webhook:** * HTTP Method: ''GET''
* Path: ''clima-valencia''
* Response Mode: ''When Last Node Finishes''
* **Nodo HTTP Request (Paso A):** * URL: ''https://opendata.aemet.es/opendata/api/observacion/convencional/datos/estacion/8416Y'' (Estación Valencia Aeropuerto).
* Header: Añadir llave ''api_key'' con tu token de AEMET OpenData.
* **Nodo HTTP Request (Paso B):** * AEMET no envía los datos directamente, envía un JSON con una URL en el campo ''datos''.
* Debes crear un segundo nodo HTTP Request cuya URL sea la expresión: ''{{ $json.datos }}''.
* **Resultado esperado:** Al llamar a la URL del Webhook, el navegador debe mostrar el array de datos meteorológicos (temperatura, humedad, etc.).
===== Paso 2: Integración de IA con Ollama (Procesamiento de Lenguaje) =====
Añadiremos una capa de inteligencia para que la respuesta no sea un JSON técnico, sino un informe humano.
**Instrucciones:**
1. Instalar y levantar **Ollama** con el modelo ''llama3'' o ''mistral''.
2. En n8n, añadir el nodo **Ollama** (o un AI Agent con el modelo Ollama).
3. Configurar el **Prompt**:
> "Actúa como un meteorólogo. Basándote en estos datos: Temperatura {{ $json[0].ta }}°C y Humedad {{ $json[0].hr }}%, genera un breve informe del tiempo en Valencia en español."
4. Ahora, el flujo debe devolver una cadena de texto amigable.
===== Paso 3: Emulación de la API de OpenAI (Modo Proxy) =====
Este es el paso crítico. Para que **Open WebUI** reconozca nuestro n8n como si fuera "ChatGPT", debemos disfrazar la respuesta. Las aplicaciones de IA no esperan un texto suelto, esperan un objeto JSON específico.
**Configuración Técnica para el alumno:**
* **Cambiar Webhook:** El método debe ser obligatoriamente ''POST'' y el path ''v1/chat/completions''.
* **Nodo Respond to Webhook:** Debes configurar el cuerpo de la respuesta en formato **JSON** con la siguiente estructura exacta:
{
"id": "chatcmpl-n8n-aemet",
"object": "chat.completion",
"created": {{ Math.floor(Date.now() / 1000) }},
"model": "aemet-valencia-bot",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "{{ $node["Ollama"].json.output }}"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 0,
"completion_tokens": 0,
"total_tokens": 0
}
}
**¿Por qué hacemos esto?**
* **id/object/created:** Son campos de control que el estándar de OpenAI exige.
* **choices:** Es una lista porque la API original permite devolver varias respuestas, aunque aquí solo enviamos la nuestra en la posición ''[0]''.
* **content:** Es donde inyectamos el texto que generó Ollama en el paso anterior.
===== Paso 4: Consumo desde Open WebUI =====
Finalmente, integramos nuestro n8n como un proveedor de servicios en la interfaz de usuario.
**Instrucciones:**
* En **Open WebUI**, ir a //Settings > Connections > OpenAI API//.
* Añadir la URL de nuestro webhook de n8n (omitiendo el final ''/v1/chat/completions'').
* Seleccionar el nuevo modelo en el chat y comprobar que, al preguntar cualquier cosa, la IA responde con el clima real de Valencia obtenido de la AEMET.
===== Anexo Técnico: Emulación del Estándar OpenAI Chat Completions =====
Para que una interfaz como **Open WebUI** o cualquier cliente compatible pueda consumir nuestro flujo de n8n, el Webhook debe comportarse exactamente como un servidor de OpenAI. Esto implica cumplir con una ruta específica y una estructura de respuesta estricta.
==== 1. Configuración del Endpoint (Ruta) ====
Open WebUI busca por defecto el recurso en la ruta estándar de la API de Chat. Por lo tanto, el nodo **Webhook** de n8n debe configurarse con:
* **HTTP Method:** ''POST''
* **HTTP Path:** ''v1/chat/completions''
* **Response Mode:** ''When Last Node Finishes'' (Esto es crítico: n8n debe esperar a que Ollama termine para responder al chat).
==== 2. La Estructura del JSON de Respuesta ====
El cliente de chat espera un objeto JSON complejo. Si n8n responde solo con texto plano, el chat dará un error de "Formato Inválido". El nodo final **Respond to Webhook** debe configurarse para devolver un **JSON** con esta estructura mínima:
{
"id": "chatcmpl-n8n-aemet",
"object": "chat.completion",
"created": {{ Math.floor(Date.now() / 1000) }},
"model": "aemet-valencia-bot",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "{{ $node["Ollama"].json.output }}"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 0,
"completion_tokens": 0,
"total_tokens": 0
}
}
==== 3. Desglose de Campos Clave ====
| Campo | Función |
| **id** | Identificador único de la respuesta. Usamos un prefijo estático seguido de un ID aleatorio si se desea. |
| **created** | Marca de tiempo en formato Unix (segundos). En n8n usamos la expresión ''Math.floor(Date.now() / 1000)''. |
| **model** | Nombre que aparecerá en la interfaz del chat como "modelo activo". |
| **choices[0].message.content** | Aquí es donde insertamos la respuesta generada por el nodo de **Ollama**. Es el texto que el usuario leerá en su burbuja de chat. |
| **finish_reason** | Indica que la IA ha terminado de hablar. Debe ser ''"stop"'' para que el chat cierre la conexión correctamente. |
==== 4. Conexión en el Cliente (Open WebUI) ====
En la configuración de conexiones de Open WebUI:
* La **Base URL** debe ser la URL de n8n hasta antes de la versión: Ej. ''https://n8n.tu-centro.es/webhook/''.
* Automáticamente, el cliente añadirá ''v1/chat/completions'' al final de esa URL para realizar la petición POST.