Muy feliz de finalmente tenerlo funcionando! no sé si es la mejor forma, pero la cuestión es que funciona :D
Estos días me estoy peleando en hacer funcionar un chatbot con stream de openai sobre django. Me ha llevado un par de días de hacerlo funcionar pero aquí lo tenemos :D
Básicamente después de perderme entre decenas de páginas de documentación y abrir y cerrar otras decenas más de pestañas del navegador, lo que terminé haciendo fue localizar una aplicación que sí hacía lo que yo quería y luego la he implementado a mi aplicación. La aplicación que funcionaba like a charm en local es Django OpenAI-Powered Chatbot with HTMX Streaming. También recomiendo esta otra documentación (Learn to use Websockets with Django by building your own ChatGPT) que si eres relativamente nuevo con esto de htmx y websockets te va a aclarar mucho el tema :)
Inicié implementando el manual de saaspegasus pero no funcionaba. Así que he decidido directamente integrar el código de django_chatbot a mi aplicación y tampoco funcionaba… hasta que he visto lo de daphne! El python manage.py runserver de toda la vida no es síncrono pero daphne si, y lo que permite es activar las conexiones asíncronas. Al aplicar esto, todo ha empezado a funcionar :D
Después ha venido la parte de ponerlo en producción que de nuevo me he perdido entre múltiples páginas pero finalmente lo que sacado y aquí lo dejo para saber como implementarlo la próxima vez y también para otro que llegue aquí después de mucho navegar. En este post me centro sólo en la parte de poner a funcionar la parte de producción.
Primero de todo, tendremos que añadir gunicorn a nuestro proyecto (y daphne para el entorno de desarrollo) y lo guardamos en requirements.txt para que no se nos olvide. No es necesario instalarlos por apt porqué lo estamos haciendo con pip en el entorno venv.
A continuación, en el servidor de producción creamos el fichero /etc/systemd/system/petitsuis_gunicorn.service con el siguiente contenido:
# vi /etc/systemd/system/petitsuis_gunicorn.service [Unit] Description=Gunicorn daemon for petitsuis After=network.target [Service] User=www-data Group=www-data WorkingDirectory=/var/www/html/petitsuis ExecStart=/var/www/html/petitsuis/venv/bin/gunicorn petitsuis.asgi:application -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000 --workers 4 --threads 2 --log-level info Restart=always [Install] WantedBy=multi-user.target
A continuación recargamos la configuración de systemd, paramos y arrancamos el servicio
# systemctl daemon-reload # systemctl stop petitsuis_gunicorn # systemctl start petitsuis_gunicorn
Con esto tenemos gunicorn sirviendo el contenido asgi en el puerto 8000.
Hasta el momento los servidores con django los configuro con uwsgi tal como expliqué hace años en este post Instalar entorno de producción de python. Así que de momento y como no me fio mucho de momento de gunicorn voy a dejar uwsgi como siempre para toda la aplicación y gunicorn para /ws/ que es donde está el websocket. Dejo comentada la configuración del backend nginx que también funciona.
Referencia: WebSocket proxying (nginx.org)
BACKEND NGINX (DONDE RESIDE LA APLICACIÓN)
# vi /etc/nginx/sites-available/petitsuis server { listen 80; server_name _; set_real_ip_from 192.168.XXX.0/24; real_ip_header X-Real-IP; real_ip_recursive on; client_max_body_size 200M; fastcgi_read_timeout 300; proxy_read_timeout 300; # wsgi application through uwsgi location / { include uwsgi_params; uwsgi_pass unix:/tmp/uwsgi-petitsuis.sock; uwsgi_read_timeout 300; } # wsgi application through gunicorn #location / { # proxy_pass http://localhost:8000; # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # proxy_set_header Host $host; # proxy_redirect off; #} location /ws/ { proxy_pass http://localhost:8000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; proxy_set_header Host $host; } # /static location /static/ { autoindex off; root /var/www/html/petitsuis/web/; } }
FRONTAL NGINX (DONDE ESTÁN LOS SSL Y ESTÁ EXPUESTO A INTERNET)
Ahora lo siguiente será modificar también el fichero de configuración de nginx del nginx frontend que es donde están todos los certificados ssl y también es la máquina expuesta a internet
# vi /etc/nginx/sites-enabled/petitsuis.capa8.net # INSTANCES petitsuis server { listen 80; listen [::]:80; server_name petitsuis.capa8.net; return 301 https://petitsuis.capa8.net$request_uri; #root /var/www/html; include snippets/certbot.conf; } # 2024/08/16 - Petitsuis (navalla suïssa de petites cosetes) server { server_name petitsuis.capa8.net; include snippets/certbot.conf; listen 443 ssl; listen [::]:443 ssl http2; ssl_certificate /etc/letsencrypt/live/petitsuis.capa8.net/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/petitsuis.capa8.net/privkey.pem; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; ssl_trusted_certificate /etc/letsencrypt/live/petitsuis.capa8.net/chain.pem; location / { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Host $remote_addr; proxy_set_header Accept-Encoding ""; proxy_pass http://192.168.100.37:80; # WebSocket support proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } }
El secreto de la masa está en las tres líneas de configuración debajo de “WebSocket support”.
En el servidor de backend he creado un script que se llama fast.sh que sirve para descargar el código del repositorio y reiniciar todos los servicios necesarios (el script no instala las dependencias nuevas y tienes que hacerlo a mano con pip install -r requirements.txt):
# fast.sh #!/bin/bash cd /var/www/html/petitsuis && source venv/bin/activate && git pull && python manage.py makemigrations && python manage.py migrate && cd .. && chown -R www-data:www-data * && service uwsgi restart && service nginx restart supervisorctl stop petitsuis-celery supervisorctl start petitsuis-celery systemctl stop petitsuis_gunicorn systemctl start petitsuis_gunicorn
Y listos! aquí queda funcionando la cosa :)