Nota
Hola, bienvenido a la Comunidad de Entusiastas de Raspberry Pi, Arduino y ESP32 de SunFounder en Facebook. Profundice en Raspberry Pi, Arduino y ESP32 junto a otros entusiastas.
¿Por qué unirte?
Soporte de Expertos: Resuelve problemas post-venta y desafíos técnicos con la ayuda de nuestra comunidad y equipo.
Aprende y Comparte: Intercambia consejos y tutoriales para mejorar tus habilidades.
Vistas Previas Exclusivas: Obtén acceso anticipado a nuevos anuncios de productos y vistas previas.
Descuentos Especiales: Disfruta de descuentos exclusivos en nuestros productos más nuevos.
Promociones y Sorteos Festivos: Participa en sorteos y promociones de temporada.
👉 ¿Listo para explorar y crear con nosotros? Haz clic en [here] y únete hoy.
6. Chatbot de Voz Local
En esta lección, combinará todo lo que ha aprendido — reconocimiento de voz (STT), conversión de texto a voz (TTS) y un LLM local (Ollama) — para construir un chatbot de voz completamente offline que se ejecuta en su Pironman 5 Pro MAX.
El flujo de trabajo es simple:
Escuchar — El micrófono captura su voz y la transcribe con Vosk.
Pensar — El texto se envía a un LLM local ejecutándose en Ollama (por ejemplo,
llama3.2:3b).Hablar — El chatbot responde en voz alta usando Piper TTS.
Esto crea un robot conversacional manos libres que puede entender y responder en tiempo real.
Antes de Empezar
Asegúrese de haber preparado lo siguiente:
Probado Piper TTS (1. Probando Piper) y elegido un modelo de voz que funcione.
Probado Vosk STT (Probar Vosk) y elegido el paquete de idioma correcto (por ejemplo,
en-us).Instalado Ollama (1. Instalar Ollama (LLM) y Descargar un Modelo) en su Pi o en otro ordenador, y descargado un modelo como
llama3.2:3b(o uno más pequeño comomoondream:1.8bsi la memoria es limitada).
Ejecutar el Código
Abra el script de ejemplo:
cd ~/sunfounder-voice-assistant/examples/ sudo nano local_voice_chatbot.py
Actualice los parámetros según sea necesario:
stt = Vosk(language="en-us"): Cambie esto para que coincida con su acento/paquete de idioma (por ejemplo,en-us,zh-cn,es).tts.set_model("en_US-amy-low"): Reemplácelo con el modelo de voz de Piper que verificó en 1. Probando Piper.llm = Ollama(ip="localhost", model="llama3.2:3b"): Actualice tantoipcomomodelsegún su propia configuración.ip: Si Ollama se ejecuta en el mismo Pi, uselocalhost. Si Ollama se ejecuta en otro ordenador de su LAN, active Expose to network en Ollama y configureipcon la IP LAN de ese ordenador.model: Debe coincidir exactamente con el nombre del modelo que descargó/activó en Ollama.
Ejecute el script:
cd ~/sunfounder-voice-assistant/examples/ sudo python3 local_voice_chatbot.py
Después de ejecutarlo, debería ver:
El bot lo saluda con un mensaje de bienvenida hablado.
Espera la entrada de voz.
Vosk transcribe su voz a texto.
El texto se envía a Ollama, que transmite una respuesta.
La respuesta se limpia (eliminando razonamientos ocultos) y se reproduce en voz alta con Piper.
Detenga el programa en cualquier momento con
Ctrl+C.
Código
import re
import time
from sunfounder_voice_assistant.llm import Ollama
from sunfounder_voice_assistant.stt import Vosk
from sunfounder_voice_assistant.tts import Piper
# Inicializar reconocimiento de voz
stt = Vosk(language="en-us")
# Inicializar TTS
tts = Piper()
tts.set_model("en_US-amy-low")
# Instrucciones para el LLM
INSTRUCTIONS = (
"You are a helpful assistant. Answer directly in plain English. "
"Do NOT include any hidden thinking, analysis, or tags like <think>."
)
WELCOME = "Hello! I'm your voice chatbot. Speak when you're ready."
# Inicializar conexión con Ollama
llm = Ollama(ip="localhost", model="llama3.2:3b")
llm.set_max_messages(20)
llm.set_instructions(INSTRUCTIONS)
# Utilidad: limpiar razonamientos ocultos
def strip_thinking(text: str) -> str:
if not text:
return ""
text = re.sub(r"<\s*think[^>]*>.*?<\s*/\s*think\s*>", "", text, flags=re.DOTALL|re.IGNORECASE)
text = re.sub(r"<\s*thinking[^>]*>.*?<\s*/\s*thinking\s*>", "", text, flags=re.DOTALL|re.IGNORECASE)
text = re.sub(r"```(?:\s*thinking)?\s*.*?```", "", text, flags=re.DOTALL|re.IGNORECASE)
text = re.sub(r"\[/?thinking\]", "", text, flags=re.IGNORECASE)
return re.sub(r"\s+\n", "\n", text).strip()
def main():
print(WELCOME)
tts.say(WELCOME)
try:
while True:
print("\n🎤 Escuchando... (Presione Ctrl+C para detener)")
# Recopilar transcripción final de Vosk
text = ""
for result in stt.listen(stream=True):
if result["done"]:
text = result["final"].strip()
print(f"[TÚ] {text}")
else:
print(f"[TÚ] {result['partial']}", end="\r", flush=True)
if not text:
print("[INFO] No se reconoció nada. Intente de nuevo.")
time.sleep(0.1)
continue
# Consultar Ollama con streaming
reply_accum = ""
response = llm.prompt(text, stream=True)
for next_word in response:
if next_word:
print(next_word, end="", flush=True)
reply_accum += next_word
print("")
# Limpiar y hablar
clean = strip_thinking(reply_accum)
if clean:
tts.say(clean)
else:
tts.say("Sorry, I didn't catch that.")
time.sleep(0.05)
except KeyboardInterrupt:
print("\n[INFO] Deteniendo...")
finally:
tts.say("Goodbye!")
print("Bye.")
if __name__ == "__main__":
main()
Análisis del Código
Importaciones y configuración global
import re
import time
from sunfounder_voice_assistant.llm import Ollama
from sunfounder_voice_assistant.stt import Vosk
from sunfounder_voice_assistant.tts import Piper
Incorpora los tres subsistemas que construyó anteriormente: Vosk para voz a texto (STT), Ollama para el LLM y Piper para texto a voz (TTS).
Inicializar STT (Vosk)
stt = Vosk(language="en-us")
Carga el modelo de Vosk para inglés estadounidense.
Cambie el código de idioma (por ejemplo, zh-cn, es) para que coincida con su paquete de voz y obtener mejor precisión.
Inicializar TTS (Piper)
tts = Piper()
tts.set_model("en_US-amy-low")
Crea un motor Piper y selecciona una voz específica. Elija un modelo que haya probado en 1. Probando Piper. Las voces de menor calidad son más rápidas y usan menos CPU.
Instrucciones del LLM y mensaje de bienvenida
INSTRUCTIONS = (
"You are a helpful assistant. Answer directly in plain English. "
"Do NOT include any hidden thinking, analysis, or tags like <think>."
)
WELCOME = "Hello! I'm your voice chatbot. Speak when you're ready."
Dos decisiones clave de UX:
Mantener respuestas cortas y directas (ayuda con la claridad del TTS).
Prohibir explícitamente etiquetas de “cadena de pensamiento” ocultas para reducir salidas ruidosas.
Conectar a Ollama y establecer el alcance de la conversación
llm = Ollama(ip="localhost", model="llama3.2:3b")
llm.set_max_messages(20)
llm.set_instructions(INSTRUCTIONS)
ip="localhost"asume que el servidor Ollama se ejecuta en el mismo Pi. Si se ejecuta en otra máquina de la LAN, ponga la IP LAN de ese ordenador y active Expose to network en Ollama.set_max_messages(20)mantiene un historial de conversación corto. Reduzca esto si la memoria/latencia es un problema.
Eliminar razonamientos o etiquetas ocultas antes de hablar
def strip_thinking(text: str) -> str:
if not text:
return ""
text = re.sub(r"<\s*think[^>]*>.*?<\s*/\s*think\s*>", "", text, flags=re.DOTALL|re.IGNORECASE)
text = re.sub(r"<\s*thinking[^>]*>.*?<\s*/\s*thinking\s*>", "", text, flags=re.DOTALL|re.IGNORECASE)
text = re.sub(r"```(?:\s*thinking)?\s*.*?```", "", text, flags=re.DOTALL|re.IGNORECASE)
text = re.sub(r"\[/?thinking\]", "", text, flags=re.IGNORECASE)
return re.sub(r"\s+\n", "\n", text).strip()
Algunos modelos pueden emitir etiquetas de estilo interno (por ejemplo, <think>…).
Esta función las elimina para que su TTS solo hable la respuesta final.
Consejo: Si ve otros artefactos en pantalla (porque transmite tokens sin procesar), esta función ya garantiza que la salida hablada se mantenga limpia.
Bucle principal: saludar una vez, luego escuchar → pensar → hablar
print(WELCOME)
tts.say(WELCOME)
Saluda al usuario por terminal y altavoz. Ocurre una vez al inicio.
Escuchar (STT con streaming y parciales en vivo)
print("\n🎤 Escuchando... (Presione Ctrl+C para detener)")
text = ""
for result in stt.listen(stream=True):
if result["done"]:
text = result["final"].strip()
print(f"[TÚ] {text}")
else:
print(f"[TÚ] {result['partial']}", end="\r", flush=True)
stream=Trueproduce transcripciones parciales para retroalimentación inmediata y una transcripción final cuando termina la expresión.El texto reconocido final se almacena en
texty se imprime una vez.
Protección: Si no se reconoce nada, se omite la llamada al LLM:
if not text:
print("[INFO] No se reconoció nada. Intente de nuevo.")
time.sleep(0.1)
continue
Esto evita enviar mensajes vacíos al modelo (ahorra tiempo y tokens).
Pensar (LLM) con impresión en streaming
reply_accum = ""
response = llm.prompt(text, stream=True)
for next_word in response:
if next_word:
print(next_word, end="", flush=True)
reply_accum += next_word
print("")
Envía la transcripción final al LLM local e imprime los tokens a medida que llegan para baja latencia.
Mientras tanto, acumula la respuesta completa en
reply_accumpara su post-procesamiento.
Nota: Si prefiere no mostrar los tokens sin procesar, establezca stream=False y simplemente imprima la cadena final.
Hablar (limpiar primero, luego TTS una vez)
clean = strip_thinking(reply_accum)
if clean:
tts.say(clean)
else:
tts.say("Sorry, I didn't catch that.")
Limpia el texto final para eliminar etiquetas ocultas, luego habla exactamente una vez.
Mantener el TTS en un solo paso evita mensajes repetidos como “[LLM] / [SAY]”.
Salida y limpieza
except KeyboardInterrupt:
print("\n[INFO] Deteniendo...")
finally:
tts.say("Goodbye!")
print("Bye.")
Use Ctrl+C para detener. El bot dice una breve despedida para señalar una salida limpia.
Solución de Problemas y Preguntas Frecuentes
El modelo es demasiado grande (error de memoria)
Use un modelo más pequeño como
moondream:1.8bo ejecute Ollama en un ordenador más potente.Sin respuesta de Ollama
Asegúrese de que Ollama esté ejecutándose (
ollama serveo la aplicación de escritorio abierta). Si es remoto, active Expose to network y verifique la dirección IP.Vosk no reconoce la voz
Verifique que su micrófono funcione. Pruebe otro paquete de idioma (
zh-cn,es, etc.) si es necesario.Piper silencioso o con errores
Confirme que el modelo de voz elegido esté descargado y probado en 1. Probando Piper.
Respuestas demasiado largas o fuera de tema
Edite
INSTRUCTIONSpara agregar: “Keep answers short and to the point.”