2025/01/17 | AI
with openai

Buscar

MiniPC + OpenAI como asistente virtual

Descripción de la imagen

A lo largo del proceso para configurar y solucionar problemas con el asistente de voz, hemos realizado ajustes en el sistema operativo, configuraciones de audio y código del asistente. A continuación, detallo los principales cambios:

 

Se crea un entorno virtual para proyecto de asistente virtual con OpenAI. Creamos un archivo assistant.py (nano assistant.py) con el siguiente código (sustituimos "nuestra api", por la que se genere en OpenAI para el proyecto).

MATERIAL UTILIZADO

  • Hardware:
    • MiniPc  Beelink EQR5 AMD Ryzen 5 Pro 5650U (6C/ 12T hasta 4.2Ghz), 15GB SO-DIMM DDR4 + 500GB M.2 PCIe 3.0x4 SSD, Doble 1000 Mbps, WIFI 6, Bluetooth 5.2, Doble HDMI

    • Micrófono USB de Conferencia Omnidireccional de 360º con botón de Silencio.
    • Altavoces pc básicos con conector minijack + usb para alimentación.
  • Software:
    • Kali Linux
    • Python
    • API OpenAI

Aplicaciones instaladas en el entorno virtual:

Para su instalación lo más fácil puede ser crear un archivo por ejemplo llamado requirements.txt, pegar la lista de aplicaciones, guardar, y desde fuera ejecutar con el entorno activado:

pip install -r requirements.txt

 

Contenido de requirements.txt

annotated-types==0.7.0
anyio==4.8.0
certifi==2024.12.14
charset-normalizer==3.4.1
click==8.1.8
distro==1.9.0
gTTS==2.5.4
h11==0.14.0
httpcore==1.0.7
httpx==0.28.1
idna==3.10
jiter==0.8.2
openai==1.59.9
PyAudio==0.2.14
pydantic==2.10.5
pydantic_core==2.27.2
pyttsx3==2.98
requests==2.32.3
sniffio==1.3.1
SpeechRecognition==3.14.0
tqdm==4.67.1
typing_extensions==4.12.2
urllib3==2.3.0

Código del archivo principal creado en el entorno virtual: assistant.py (para después ejecutar mediante "python assistant.py"

from openai import OpenAI
import pyttsx3
import speech_recognition as sr
import os
import subprocess
from gtts import gTTS

# Configura tu clave de API desde una variable de entorno
api_key = 'AQUI VA LA API KEY CREADA EN OPENAI'
if not api_key:
    raise ValueError("Por favor, configura la variable de entorno 'OPENAI_API_KEY' con tu clave de OpenAI.")
client = OpenAI(api_key=api_key)

# Configura el motor de texto a voz
engine = pyttsx3.init()
engine.setProperty('rate', 150)

FLAG_FILE = "assistant_initialized.flag"  # Archivo que indica si ya se ejecutó el comando inicial
os.system("fuser -k /dev/snd/*")
def stop_audio_processes():
    """
    Finaliza los procesos que ocupan los dispositivos de audio.
    """

    os.system("killall arecord")
    os.system("killall aplay")

def speak(text):
    """
    Convierte texto a voz usando Google Text-to-Speech (gTTS) y lo reproduce.
    """
    tts = gTTS(text=text, lang='es', slow=False)
    tts.save("response.mp3")
    os.system("mpg123 response.mp3")  # Reproduce el archivo de audio

def record_audio(file_name="temp_audio.wav", duration=5):
    """
    Graba audio desde el micrófono sin convertirlo.
    """
    stop_audio_processes()
    print("Grabando...")
    try:
        # Graba directamente en mono sin conversión adicional
        subprocess.run(
            ["arecord", "-D", "plughw:2,0", "-f", "S16_LE", "-c", "1", "-r", "44100", "-t", "wav", "-d", str(duration), file_name],
            check=True
        )
        print("Grabación completa.")
        return file_name
    except subprocess.CalledProcessError as e:
        print(f"Error al grabar audio: {e}")
        return None

def transcribe_audio(file_name="temp_audio.wav"):
    """
    Convierte el archivo de audio grabado a texto usando speech_recognition.
    """
    recognizer = sr.Recognizer()
    try:
        with sr.AudioFile(file_name) as source:
            print("Procesando audio para transcripción...")
            audio = recognizer.record(source)
            text = recognizer.recognize_google(audio, language="es-ES")
            return text
    except sr.UnknownValueError:
        return ""
    except sr.RequestError as e:
        return f"Error en el servicio de reconocimiento de voz: {e}"

def get_response(prompt):
    """
    Obtiene la respuesta de OpenAI ChatGPT.
    """
    try:
        response = client.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=[
                {"role": "system", "content": "Eres un asistente útil."},
                {"role": "user", "content": prompt}
            ]
        )
        if response and response.choices:
            return response.choices[0].message.content
        else:
            return "Lo siento, no pude generar una respuesta. Inténtalo de nuevo."
    except Exception as e:
        return f"Error al conectarse con OpenAI: {e}"

if __name__ == "__main__":
    # Solo ejecuta `fuser -k` la primera vez
    if not os.path.exists(FLAG_FILE):
        print("Finalizando procesos de audio por primera vez...")
        stop_audio_processes()
        with open(FLAG_FILE, "w") as flag:
            flag.write("initialized")

    print("Asistente iniciado. Di 'hola lucy' para activarlo y 'eso es todo lucy' para desactivarlo.")

    assistant_active = False  # Comienza en modo inactivo

    while True:
        try:
            # Grabar audio
            audio_file = record_audio()
            if not audio_file:
                print("No se pudo grabar el audio. Intentando nuevamente...")
                continue

            # Transcribir el audio grabado
            user_input = transcribe_audio(audio_file).lower()
            print(f"Tú: {user_input}")

            # Activar el asistente
            if any(trigger in user_input for trigger in ["hola lucy", "hola luci", "hola", "lucy", "luci"]):
                assistant_active = True
                speak("Hola, dime.")
                continue

            # Desactivar el asistente
            if "eso es todo" in user_input:
                assistant_active = False
                speak("De acuerdo.")
                continue

            # Responder solo si el asistente está activo
            if assistant_active and user_input:
                response = get_response(user_input)
                print(f"Asistente: {response}")
                speak(response)
        except Exception as e:
            print(f"Error durante la ejecución: {e}")
        finally:
            # Limpieza de archivos temporales
            if os.path.exists("temp_audio.wav"):
                os.remove("temp_audio.wav")

 


 

1. Cambios en el Sistema

Configuraciones Generales

  • Deshabilitar PulseAudio:

    • Hemos detenido y deshabilitado PulseAudio para evitar conflictos con ALSA:
systemctl --user stop pulseaudio.service
systemctl --user disable pulseaudio.service
pulseaudio --kill
killall pulseaudio
    • Verificamos que no hubiera procesos de PulseAudio activos con:
fuser -v /dev/snd/*

Salida:

  • Forzar el Uso de ALSA:
    • Configuramos ALSA como el backend exclusivo para el manejo de audio.
  • Para detener procesos:
kill -9 <PID>
    • Ejemplo para matar el primer proceso:
kill -9 1092
    • Matar todos los procesos:
fuser -k /dev/snd/*

 

2. Archivos del Sistema Modificados

Archivo ~/.asoundrc

  • Este archivo se utilizó para configurar explícitamente los dispositivos de entrada (micrófono USB) y salida (analógica de 3.5 mm):
# Micrófono USB (Entrada)
pcm.input {
    type plug
    slave {
        pcm "hw:2,0"
        format S16_LE
        channels 1
        rate 44100
    }
}

# Salida Analógica de 3.5 mm (Salida)
pcm.output {
    type plug
    slave {
        pcm "hw:1,0"
        format S16_LE
        channels 2
        rate 44100
    }
}

pcm.!default pcm.output

Configuración de ALSA

  • Reiniciamos y configuramos ALSA después de realizar cambios:
sudo alsactl init

3. Comandos Más Utilizados

Diagnóstico y Verificación

  • Listar Dispositivos de Audio:

aplay -l
arecord -l
  • Verificar Procesos que Usan Dispositivos de Audio:
fuser -v /dev/snd/*

Pruebas Manuales

  • Grabar Audio desde el Micrófono USB:

arecord -D plughw:2,0 -f S16_LE -c 1 -r 44100 test_audio.wav
  • Convertir Audio a Estéreo:
sox test_audio.wav test_audio_stereo.wav channels 2
  • Reproducir Audio en el Conector de 3.5 mm:
aplay -D plughw:1,0 test_audio_stereo.wav

Gestión de Procesos de Audio

  • Detener Procesos de Grabación/Reproducción:
killall arecord
killall aplay

 


 

4. Cambios en el Código del Asistente

  • Grabación de Audio: Configuramos la función para grabar desde el micrófono USB (plughw:2,0) y convertir el audio a estéreo si es necesario.

  • Reproducción de Audio: Configuramos la función para reproducir audio en la salida analógica de 3.5 mm (plughw:1,0).

  • Gestión de Errores: Agregamos manejo de excepciones para evitar que el asistente falle por errores inesperados.

  • Limpieza de Archivos Temporales: Eliminamos archivos de audio temporales después de cada ejecución.

Resumen Final

  • Archivos Modificados del sistema: ~/.asoundrc
  • Configuraciones Deshabilitadas: PulseAudio.
  • Backend Utilizado: ALSA.
  • Comandos Clave: arecord, aplay, sox, fuser.
  • Resultados: El asistente ahora graba, procesa y reproduce audio correctamente, utilizando ALSA y configuraciones específicas para los dispositivos de entrada y salida.

Observación:

El asistente virtual funciona, pero requiere algunas mejoras como:

  • La voz es en inglés por lo que es necesario en el caso del castellano utilizar una librería adecuada
  • Mientras el assistente habla, no escucha, sería adecuado que también escuche mientras habla, para interrumpirle la conversación, etc.

 

 

 

 

 

 

 

 

 

 

 

 

 

Comentarios