Generar imágenes por IA en tu propio ordenador

Ya vimos en la entrada anterior como poder tener nuestro propio chat-gpt sin pagar nada a nadie usando modelos opensource y nuestra GPU, ahora le toca el turno a la posibilidad de generar imágenes por Inteligencia Artificial mediante el mismo método, en casa, de forma privada y sin tener que pagar licencias. Al lío…

La imagen anterior ha sido generada en mi ordenador, con mi tarjeta gráfica y con un prompt muy sencillito, básicamente le he pedido un robot pintando con pinceles en la pantalla del ordenador… Y me ha salido esto (hay más parámetros, pero no he tocado nada especial). Para generar estas imágenes vamos a utilizar Stable Diffussion, que es un modelo de aprendizaje automático para generar imágenes digitales de alta calidad a partir de descripciones en lenguaje natural (wikipedia). Es de código abierto y no impone restricciones a las imágenes que produce.

Como somos hombres (y mujeres) de acción os voy a dar la receta rápida para tener stable difussion y un interfaz de usuario (automatic1111) funcionando en cuestión de minutos (bueno, esto depende de vuestra conexión a internet que hay muchos gigas que descargarse). La receta original, que os recomiendo seguir si queréis experimentar un poco más con el tema, la saqué de aquí: https://github.com/AbdBarho/stable-diffusion-webui-docker pero yo he preparado una imagen que ya tiene todo lo necesario, así que lo único que tenéis que hacer es crear un archivo docker-compose.yml con este contenido:

services:
  sd-auto:
    image: yoprogramo/sd-auto:78
    ports:
      - "7860:7860"
    volumes:
      - ./data:/data
      - ./output:/output
    stop_signal: SIGKILL
    environment:
      - CLI_ARGS=--allow-code --medvram --xformers --enable-insecure-extension-access --api
      - COMMANDLINE_ARGS=--share
    restart: unless-stopped
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              device_ids:
                - "0"
              capabilities:
                - compute
                - utility

Luego, estoy suponiendo que usáis linux y tenéis la configuración de docker y de la tarjeta gráfica que ya vimos en el anterior post, solo hay que ejecutar:

docker compose up -d

Como os he dicho el proceso de descarga inicial de la imagen y del modelo van a tardar un poco (reservaos mínimo 20Gb para todo), pero si todo va bien en unos minutos podréis acceder a la url http://localhost:7860 y veréis la interfaz de AUTOMATIC1111 para stable difussion.

Inicialmente el modelo descargado es sdv1.5-pruned-emaonly que tiene sus limitaciones pero cabe en casi todas la memorias. Ya solo queda hacer la prueba, poniendo algo en el prompt y dandole a Generate.

Si no tienes demasiada memoria en tu tarjeta gráfica te saldrá algo como esto:

Pero si has sido capaz de generar una imagen, se abre todo un abanico de modelos que probar y opciones con las que trastear… El primer sitio para visitar es este:

https://civitai.com

Como tener tu propia IA en casa

Todos hemos oído y probado las bondades de chat-gpt o usado github copilot con tremendo éxito, pero estos sistemas tienen un problema principal, que son de pago. Sus modelos son cerrados y hay que pagar una licencia para poder utilizarlos en cosas útiles. Sin embargo, existe otra manera de experimentar con la Inteligencia Artificial generativa en casa, sin pagar licencias y teniendo todo el control. Solo necesitas un equipo medianamente moderno, una GPU y una cantidad de memoria abundante (o no tanta, pero podrás jugar con menos modelos). Te cuento aquí como instalar tu propio servicio de IA en tu ordenador.

Eso si, te lo cuento solo para Linux, si tienes algún otro sistema operativo de esos de juguete tendrás que buscarte la vida (te dejo enlaces para que puedas hacerlo por tu cuenta).

Como modelo de AI vamos a utilizar llama, modelo opensource de Meta y lo vamos a instalar con ollama. Hay varias guías para instalarlo directamente en tu ordenador, pero las últimas versiones de ubuntu (yo tengo la 24.04) son ciertamente reticentes a instalar paquetes python en el sistema, por lo que la solución más sencilla será usar docker para ello. Vamos a suponer que tenemos una GPU nvidia, y la porción de docker-compose necesaria para instalarte ollama sería esta:

  ollama:
    volumes:
      - ./ollama:/root/.ollama
    container_name: ollama
    pull_policy: always
    tty: true
    ports:
      - "11434:11434"
    restart: unless-stopped
    image: ollama/ollama:${OLLAMA_DOCKER_TAG-latest}
    deploy:
      resources:
        reservations:
          devices:
            - driver: ${OLLAMA_GPU_DRIVER-nvidia}
              count: ${OLLAMA_GPU_COUNT-1}
              capabilities:
                - gpu

Con esta configuración lo que hacemos es lanzar un servidor ollama accesible desde el puerto 11434 donde podemos usar el tty o el api. No voy a entrar en muchos detalles de cómo usar ollama, pero os recomiendo que le echéis un vistazo porque es la «madre del cordero» o de la llama, en este caso.

Para que esto funcione correctamente con la gpu hay que hacer un par de cositas previamente. Os recomiendo que miréis este repositorio para ver si se ha mejorado/modificado algo: https://github.com/valiantlynx/ollama-docker pero básicamente consiste en ejecutar lo siguiente:

curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
  && curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
    sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
    sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit

# Configure NVIDIA Container Toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker

Dado que vamos a necesitar un interfaz para gestionar los modelos y tener chats y demás lo siguiente que vamos a incluir en nuestro docker-compose es open-webui, modificamos nuestro docker-compose.yml para agregar lo siguiente (yo ya he contruido la imagen y la he subido a docker hub):

  open-webui:
    image: yoprogramo/open-webui:${WEBUI_DOCKER_TAG-latest}
    container_name: open-webui
    volumes:
      - ./open-webui:/app/backend/data
    depends_on:
      - ollama
    ports:
      - ${OPEN_WEBUI_PORT-3000}:8080
    environment:
      - 'OLLAMA_BASE_URL=http://ollama:11434'
      - 'WEBUI_SECRET_KEY='
    extra_hosts:
      - host.docker.internal:host-gateway
    restart: unless-stopped

Y creamos un archivo .env con el siguiente contenido:

OLLAMA_GPU_COUNT=all
SCARF_NO_ANALYTICS=true
DO_NOT_TRACK=true
ANONYMIZED_TELEMETRY=false

Y lanzar las imágenes si todo ha ido bien:

docker compose up -d

Con esto ya tendríamos corriendo nuestro servidor ollama y open-webui en nuestro propio ordenador… Simplemente tenemos que acceder con el navegador a localhost:3000

Lo primero que tenéis que hacer, una vez creado un usuario en el sistema (si, el primer usuario que se crea es administrador) es descargarse algún modelo de IA, para eso hay que entrar en la página de administración y acceder a la opción que pone «Obtener un modelo de Ollama.com», escribir el deseado y darle al botón de la derecha para descargarlo. En la imagen por ejemplo nos descargamos el modelo llama3.1 de 70B (son cerca de 42Gb, así que deberías tener espacio de sobra).

Una vez descargado ya estamos listos para usarlo, vete a la opción «nuevo Chat», selecciona el modelo en el desplegable superior y chatea con tu nueva AI…

En próximas entregas ya entraremos en más cosas que podemos hacer con nuestra IA local, seguro que no nos deja indiferentes.

Depurando PHP con Xdebug y Docker

Hasta hace poco he estado depurando PHP al viejo estilo, poniendo error_log, print_r y demás mensajes aquí y allá. La verdad es que era algo poco dañino y que, si programas bien, tampoco da mucho trabajo. Sin embargo echaba de menos las facilidades de java en el eclipse donde podía depurar de manera increíblemente eficiente sin modificar nada de mi código. El caso es que utilizando docker como base del desarrollo/despliegue de PHP se hacía muy complicado configurar las cosas para depurar. Al final, por circunstancias que no vienen al caso, me animé a ver cómo podríamos depurar código php desplegado usando docker… Y os hago aquí un resumen:

El problema básico

Supongamos que queremos hacer un programa básico en php y desplegarlo para poder verlo en nuestro navegador. Por ahora nos vamos a limitar a este archivo

phpinfo.php

<?php
phpinfo();

Si, es un archivo muy simplón, pero primero tenemos que desplegarlo utilizando docker y verlo en un navegador. Para ello creamos un directorio para nuestro proyecto (llamemosle php-samples, por ejemplo) y dentro del mismo creamos un subdirectorio llamado web y dentro de este directorio creamos el archivo phpinfo.php con el contenido anterior.

La forma más rápida

En el directorio superior creamos un archivo llamado docker-compose.yml con este contenido:

services:
  php:
    image: yoprogramo/php8.2:1.0.2
    volumes:
        - ./web:/var/www/Website
    ports:
        - 8080:80

Ya puestos cargamos el proyecto en visual studio code y nos quedará algo así:

Desde el terminal, o desde el menú de la extensión docker si la tenéis instalada, levantamos el contenedor. Una vez levantado ya podremos acceder a la url:

http://localhost:8080/phpinfo.php

Vale, prueba superada, ya tenemos nuestro servidor ejecutando php, ahora crearemos otro archivo dentro de web con un programa en php un poco más complejo para poder depurarlo a gusto. Vamos a llamar a ese archivo index.php y le daremos este contenido:

<?php
function isPrime($num) {
    if ($num <= 1) return false;
    if ($num == 2) return true;
    if ($num % 2 == 0) return false;
    for ($i = 3; $i <= sqrt($num); $i += 2) {
        if ($num % $i == 0) return false;
    }
    return true;
}

$randomNumber = rand(0, 19);

echo "<table border='1'>";
echo "<tr><th>Número</th><th>Primos menores que $randomNumber</th></tr>";
echo "<tr><td>$randomNumber</td><td>";

for ($i = 0; $i < $randomNumber; $i++) {
    if (isPrime($i)) {
        echo "$i ";
    }
}

echo "</td></tr>";
echo "</table>";
?>

Ahora podemos acceder a la url index.php (o no poner ruta ya que es el archivo que se cargará por defecto)

Vale, pero todo este rollo era para poder depurar el código, para poder hacerlo tenemos que hacer tres cosas:

  • Cambiar la imagen docker por yoprogramo/php8.2:1.0.2-xdebug en el docker-compose.yml y añadir este apartado al servicio php:
extra_hosts:
  - "host.docker.internal:host-gateway"
  • Instalar el plugin «PHP Debug»
  • Configurar el visual studio code para php, para ello pinchamos en el icono de depuración (run and debug) y pinchamos donde pone «create a launch.json file)

Esto nos creará un archivo por defecto donde tendremos que modificar la configuración «Listen for Xdebug» para añadir esto:

"pathMappings": {
   "/var/www/Website": "${workspaceRoot}/web"
}

Pulsamos sobre el icono de iniciar depuración con es configuración (o pulsamos F5)

Con todo configurado solo nos queda reiniciar el docker con la nueva imagen y poner algún breakpoint en el código (eso se hace pinchando a la izquierda del número de línea donde queramos que se pare la ejecución, por ejemplo dentro del bucle de index.php

Ahora volvemos a cargar la página index.php y si todo ha ido bien la ejecución se parará y podremos ver en el editor los valores de las variables

Y continuar la ejecución o hacerlo paso a paso

Si queréis saber cual es el camino difícil o cómo hacerlo con vuestras propias imágenes docker, solo tenéis que preguntar…

AMAZON SES y cómo enviar correos desde un servidor ubuntu

Enviar correo desde una máquina virtual en Amazon siempre ha sido un castigo. Las limitaciones al puerto 25 y a los controles de tráfico de Amazon hacían poco recomendable poner un servidor de correo «normal» en la infraestructura. Sin embargo – y pagando, claro está – Amazon ha puesto a disposición de todo el mundo un servicio para poder enviar correos sin demasiada complicación (aunque, como veremos, también tiene sus limitaciones).

Lo primero es lo primero, si quieres mandar correos usando Amazon SES. La información general la puedes ver aquí: https://aws.amazon.com/es/ses/ y create una identidad verificada (tendrás que cambiar cosas en el dns para que puedas enviar correo desde cuentas de tu dominio. Lo siguiente será crear una configuración de SMTP para tu cuenta, eso te dará un servidor, usuario y contraseña que usar para mandar correos (y los puertos correspondientes)… Anotalos muy bien que será lo que vamos a utilizar.

Al principio tendrás unas limitaciones muy importantes (para probar no nos afectan demasiado) y tendrás que crear direcciones de correo validada, hazlo y prueba que puedes enviar correos a esas cuentas antes de continuar. Los pasos para poder enviar correo desde un servidor ubuntu serían los siguientes:

  1. Instala postfix
sudo apt install -y postfix libsasl2-modules
  1. Añade estas líneas a /etc/postfix/sasl_passwd
smtp_tls_note_starttls_offer = yes 
smtp_tls_security_level = encrypt 
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd 
smtp_sasl_security_options = noanonymous 
relayhost = [email-smtp.xx-xxxx-xx.amazonaws.com]:587 
smtp_sasl_auth_enable = yes 
smtp_use_tls = yes 
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt 
mydestination = 
  1. En /etc/postfix/sasl_passwd
[email-smtp.xx-xxxx-x.amazonaws.com]:587 USUARIO:PASSWORD
  1. Lanza las modificaciones
sudo newaliases 
sudo postmap hash:/etc/postfix/sasl_passwd 
sudo systemctl restart postfix

Y ya estaría, ya puedes enviar correos desde cuentas de tu dominio con sendmail. Si ves algún problema siempre puedes consultar el log en /var/log/mail.log

Si todo va bien lo siguiente es pedir a Amazon que, por favor, os pongan en producción el sistema para poder enviar correos a todo el mundo. Y ya si eso, en otro post, os cuento como configurar una imagen docker para que lo use…

Hacer un bot de telegram sin usar ninguna librería de telegram

Entiendo que conoces la aplicación telegram, lleva con nosotros mucho tiempo y, aunque en nuestro país es mucho más usada whatsapp, telegram tiene también sus adeptos, sobre todo para canales de distribución o para uso secundario con aquellos que ya usan esta herramienta.

Una de las características más importantes de esta plataforma es la separación entre usuarios y bots, los usuarios tienen que tener un número de teléfono registrado y no es tan sencillo hacerse con una cuenta adicional si no tienes otro teléfono, la plataforma distingue perfectamente entre personas y bots, que serían los programas a los que solo se permite interacturar con personas que hayan hablado con ellos antes o con canales de distribución a los que se hayan añadido como administradores (antes podían añadirse como miembros, pero esa posibilidad ya no existe). El caso es que llevo un tiempo desarrollando bots para esta plataforma, usando diversas librerías y después de tantear todos los límites de telegram y de las librerías me he visto obligado a ir directo al api de telegram para bots… Y resulta que no solo es sencillo, sino muy sencillo iniciarse en la creación de bots sin ninguna librería adicional… En este caso vamos a crear un bot de cero y sin librerías de por medio.

Pero, como decía Jack el destripador: vayamos por partes…

Lo primero es crear un bot, telegram pone a nuestra disposición el bot llamado botfather que nos permite crear bots asociados a nuestra cuenta (un máximo de 20 por si os sentís tentados de crear armadas de bots) y se crean simplemente con el comando /newbot y responder unas pocas preguntas… Como podemos ver en la imagen:

Lo importante, y fundamental, es el token que nos genera botfather, esto será todo lo que necesitamos para acceder al api, que como bien nos indican está en https://core.telegram.org/bots/api

Lo segundo será decidir en qué lenguaje y para qué plataforma vamos a crear nuestro bot, en nuestro caso y para simplificar las cosas vamos a hacerlo en python que puede correr en cualquier sitio (linux, mac, windows o incluso serverless), en mi caso vamos a crear un bot que, cuando se lo pedimos, nos genere una nueva contraseña (luego ya veremos si queremos mejorarlo). Por cierto, todo el código que generemos aquí estará en el repositorio https://github.com/yoprogramo/nomorepassbot para que podáis usarlo y modificarlo a voluntad.

Hemos dicho que no tendría dependencias de ninguna librería telegram, pero no hemos dicho que no la tuviese de alguna otra, así que es momento de decidir si queremos usar alguna librería http, en mi caso, por simplicidad he elegido la librería httpx, como tampoco quiero ir instalando cosas de más voy a utilizar pipenv para crear mi entorno de trabajo (vosotros podéis hacerlo como queráis)

La lógica del programa será sencilla, simplemente preguntaremos periódicamente a telegram si tenemos updates, y si los tenemos, miraremos a ver si alguna petición contiene la cadena «password» y si la contiene devolveremos una nueva contraseña generada por nosotros en ese momento. Para recuperar los updates haremos un bucle infinito en donde llamaremos a la función getUpdates y si recibimos elementos los recorreremos uno a uno para tratarlos.

La función para generar una contraseña aleatoria será esta:

def generatePassword(num=10):
    # Generamos una contraseña aleatoria de num caracteres
    letras = string.ascii_letters + string.digits
    password = ''.join(random.choice(letras) for i in range(num))
    return password

Lo primero que tenemos que hacer es dar un valor a nuestro token y establecer la url del api de telegram:

token = 'TOKEN'
url_base = f'https://api.telegram.org/bot{token}/'

Y la función que recupera las actualizaciones será, por tanto esta:

r = httpx.get(url_base + 'getUpdates')
    updates = r.json()['result']

La función para enviar un mensaje a un usuario (o a un chat) se llama sendMessage y recibe como parámetros (entre otros) chat_id y text, así que poniendo todo junto nos quedaría esta función:

while True:
    r = httpx.get(url_base + 'getUpdates')
    updates = r.json()['result']
    for update in updates:
        if 'message' in update:
            message = update['message']
            if 'text' in message:
                text = message['text']
                if 'from' in update['message']:
                    user = update['message']['from']
                    if 'password' in text:
                        respuesta = 'La contraseña es ' + generatePassword(10)
                        r = httpx.post(url_base + 'sendMessage', data={'chat_id': user['id'], 'text': respuesta})
                else:
                    continue 
    time.sleep(1) 

Si ejecutamos esto (y tenemos el token que nos ha dado godfather) ya podremos pedir la contraseña y recibiremos respuesta (al hablar con nuestro bot, claro):

Claro que si dejamos el código así el bot se empeñará en darnos contraseñas una detrás de otra sin parar, y es que telegram no sabe si hemos tratado o no el update que nos ha enviado, para ello hay que usar el parámetro offset para indicar cual será la siguiente actualización a tratar y cambiando el código un poco nos quedaría:

offset = 0
while True:
    r = httpx.get(url_base + 'getUpdates', params={'offset': offset+1})
    updates = r.json()['result']
    for update in updates:
        offset = update['update_id']
        if 'message' in update:
            message = update['message']
            if 'text' in message:
                text = message['text']
                if 'from' in update['message']:
                    user = update['message']['from']
                    if 'password' in text:
                        respuesta = 'La contraseña es ' + generatePassword(10)
                        r = httpx.post(url_base + 'sendMessage', data={'chat_id': user['id'], 'text': respuesta})
                else:
                    continue
    time.sleep(1)

Obviamente nos falta tratar las respuestas incorrectas y los timeouts y demás (y alguna respuesta adicional que nos puede dar telegram si hacemos demasiadas peticiones), pero eso lo dejo para que lo mejoréis en el repositorio https://github.com/yoprogramo/nomorepassbot