2025/01/24 | inaki
with openai
 
                  
                  En el mundo digital actual, los asistentes virtuales se han convertido en herramientas fundamentales para mejorar la interacción con los usuarios y brindar soporte automatizado en tiempo real. En este artículo, exploraremos paso a paso cómo instalar y configurar un asistente virtual utilizando la API de OpenAI en un proyecto desarrollado con Django. Este asistente será capaz de responder preguntas, procesar consultas, y mejorar la experiencia de los usuarios en tu plataforma.
A lo largo de esta guía, aprenderás a integrar OpenAI en tu aplicación Django, gestionar las solicitudes al asistente, y crear una interfaz web interactiva para que los usuarios puedan comunicarse fácilmente con el bot. Pero no nos detendremos ahí. Al final del artículo, abordaremos dos mejoras clave que llevarán tu asistente al siguiente nivel:
.txt: Configuraremos un archivo de texto donde podrás definir las directrices y personalidad del agente, logrando respuestas más consistentes y alineadas con los objetivos de tu proyecto.
Hemos creado un proyecto django con una app llamada assistant dentro de un directorio llamado applications:
applications/assistant
mkdir applications
cd applications
django-admin startapp assistant
En settings.py añadimos la app creada (en el caso que queramos añadirlo en una app aparte de home para que esté más ordenado)
INSTALLED_APPS = [
    ...
    'applications.assistant'
    ...
]
Editamos apps.py de applications/assistant
from django.apps import AppConfig
class AssistantConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'applications.assistant'
Instalar openai:
pip install openai
Creamos la API en openai, para eso necesitamos crear una cuenta de pago:
En settings.py añadir la api:
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "sk-proj-ktzgVEa1K...tu api de openai")
Crear archivo consumers.py en applications/assistant/consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer
class AssistantConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        # Aceptar la conexión WebSocket
        await self.accept()
    async def disconnect(self, close_code):
        # Opcional: Manejar la desconexión
        pass
    async def receive(self, text_data):
        # Recibir datos del cliente
        data = json.loads(text_data)
        user_message = data.get('message', '')
        # Responder al cliente
        await self.send(json.dumps({
            'message': f"Recibí tu mensaje: {user_message}"
        }))
En applications/assistant creamos un archivo urls.py
from django.urls import path
from . import views
urlpatterns = [
    path('chat/', views.chat_view, name='chat_view'),
    path('api/get_response/', views.get_bot_response, name='get_bot_response'),
    path('end_conversation/', views.end_conversation, name='end_conversation'),
]
En urls principal (el que tenemos junto a settings.py) añadimos la url para que apunte a nuestra url creada en assistant (también habrá que importar "include":
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
    path('admin/', admin.site.urls),
    path('assistant/', include('applications.assistant.urls')),
]
A la altura de settings.py creamos un asgi.py
import os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from applications.assistant.urls import websocket_urlpatterns
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'retegi.settings')
application = ProtocolTypeRouter({
    "http": get_asgi_application(),  # Manejo de solicitudes HTTP
    "websocket": AuthMiddlewareStack(  # Manejo de solicitudes WebSocket
        URLRouter(websocket_urlpatterns)  # Incluye las rutas WebSocket
    ),
})
En applications/assistant editamos el archivo views.py
from django.shortcuts import render
from django.http import JsonResponse
import openai
from django.conf import settings
import os
MAX_HISTORY = 20  # Máximo número de mensajes en el historial
def chat_view(request):
    return render(request, 'home/chat.html')
def load_instructions():
    instructions_path = os.path.join(settings.BASE_DIR, 'applications', 'assistant', 'indications', 'instructions.txt')
    try:
        with open(instructions_path, 'r', encoding='utf-8') as file:
            return file.read()
    except FileNotFoundError:
        return "Eres un asistente virtual amigable y útil."
def get_bot_response(request):
    if request.method == 'POST':
        user_message = request.POST.get('message', '')
        openai.api_key = settings.OPENAI_API_KEY
        # Carga las instrucciones desde el archivo
        instructions = load_instructions()
        # Obtener el historial de la sesión
        conversation_history = request.session.get('conversation_history', [])
        # Agregar el mensaje del usuario al historial
        conversation_history.append({"role": "user", "content": user_message})
        # Limitar el historial al máximo definido
        if len(conversation_history) > MAX_HISTORY:
            conversation_history = conversation_history[-MAX_HISTORY:]
        try:
            # Incluir las instrucciones como el primer mensaje en el historial
            messages = [{"role": "system", "content": instructions}] + conversation_history
            # Enviar el historial completo al modelo
            response = openai.ChatCompletion.create(
                model="gpt-3.5-turbo",
                messages=messages
            )
            bot_reply = response['choices'][0]['message']['content']
            # Agregar la respuesta del bot al historial
            conversation_history.append({"role": "assistant", "content": bot_reply})
            # Guardar el historial actualizado en la sesión
            request.session['conversation_history'] = conversation_history
            return JsonResponse({'reply': bot_reply})
        except Exception as e:
            print(f"Error con OpenAI: {e}")  # Depuración
            return JsonResponse({'error': str(e)})
    return JsonResponse({'error': 'Invalid request'})
def end_conversation(request):
    if 'conversation_history' in request.session:
        del request.session['conversation_history']
    return JsonResponse({'status': 'Conversación finalizada'})
Creamos un directorio "templates" a la altura de manage.py y lo configuramos en settings.py y también habrá que import os 'import os':
import os
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
                'django.template.context_processors.i18n',
            ],
        },
    },
]Archivo index.html con el código javascript necesario:
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
  <body>
      <div>
        <div id="chat-box">
          <!-- Los mensajes del chat se mostrarán aquí -->
        </div>
      </div>
      <!-- Zona de entrada fija -->
      <div>
        <form id="message-form" method="post">
          <input type="text" id="user-message">
          <input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}">
          <button>Enviar</button>
        </form>
      </div>
<!--OpenAI-->
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
    $(document).ready(function () {
        // Función para enviar mensaje
        function sendMessage() {
            let userMessage = $('#user-message').val();
            if (userMessage.trim() !== '') {
                // Limpiar el chat-box antes de agregar nuevos mensajes
                //$('#chat-box').empty();
                $('#chat-box').append(`<p class="user-message"><strong>Tú:</strong> ${userMessage}</p>`);
                $('#user-message').val('');
                // Desplazarse automáticamente al final del chat
                $('#chat-box').scrollTop($('#chat-box')[0].scrollHeight);
                $.ajax({
                    type: 'POST',
                    url: '/assistant/api/get_response/',
                    data: {
                        'message': userMessage,
                        'csrfmiddlewaretoken': $('input[name="csrfmiddlewaretoken"]').val()
                    },
                    success: function (response) {
                        if (response.reply) {
                            $('#chat-box').append(`<p class="bot-message"><strong>Bot:</strong> ${response.reply}</p>`);
                            $('#chat-box').scrollTop($('#chat-box')[0].scrollHeight);
                        } else {
                            $('#chat-box').append('<p class="bot-message"><strong>Error:</strong> Algo salió mal.</p>');
                        }
                    },
                    error: function () {
                        $('#chat-box').append('<p class="bot-message"><strong>Error:</strong> No se pudo conectar con el servidor.</p>');
                    }
                });
            }
        }
        // Enviar mensaje al hacer clic en el botón
        $('#send-button').click(function () {
            sendMessage();
        });
        // Enviar mensaje al presionar Enter
        $('#user-message').keypress(function (e) {
            if (e.which === 13) {
                e.preventDefault();
                sendMessage();
            }
        });
    });
</script>
  </body>
</html>
Observación: Al realizar pruebas vemos que funciona correctamente pero no tiene memoria. Hemos realizado pruebas, y en el caso de añadir memoria (por ejemplo de las útimas 10 preguntas y respuesta) se perdía la posibilidad de utilizar el archivo instructions.txt que contiene instrucciones para el agente virtual. Estoy haciendo pruebas para poder utilizar tanto la memoria como el archivo instrucions.txt
Prueba