Hoy en día, los asistentes de voz se han convertido en componentes fundamentales en sistemas inteligentes que requieren un procesamiento avanzado del lenguaje natural. Este artículo proporciona una guía técnica para desarrollar un asistente de voz utilizando LangGraph, un framework diseñado para administrar sistemas de agentes complejos. A lo largo del texto, exploraremos cómo LangGraph permite la coordinación de múltiples nodos, creando flujos eficientes y altamente escalables. Esta guía está dirigida a desarrolladores interesados en aprovechar las capacidades de LangGraph para implementar soluciones en entornos de IA.
¿Qué es LangGraph?
LangGraph se presenta como una herramienta clave para los desarrolladores que enfrentan la complejidad de construir aplicaciones con múltiples agentes de modelos de lenguaje (LLM). Este framework, parte del ecosistema de LangChain. Ofrece una arquitectura eficiente para la orquestación de sistemas multi-agente, permitiendo definir claramente los flujos de trabajo y gestionar las interacciones entre los agentes. Su capacidad para crear grafos cíclicos optimiza el rendimiento y la flexibilidad de las aplicaciones, facilitando la integración de diversas funcionalidades en un solo entorno. Con LangGraph, los desarrolladores pueden implementar soluciones más coherentes y efectivas, transformando el enfoque tradicional del desarrollo de sistemas conversacionales.
Diseñando el flujo del asistente de voz
El proceso comienza con la captura de voz del usuario, que se convierte en texto para su procesamiento. Este texto sirve como entrada a un nodo de decisión, que evalúa la solicitud del usuario y determina qué tareas ejecutar de las cuatro opciones disponibles:
- Programe una reunión en el calendario.
- Redacta un correo electrónico con un mensaje específico.
- Responde a una pregunta a través de una búsqueda en Internet.
- Planifique un viaje que incluya encontrar vuelos, hoteles y sugerencias para la ropa adecuada para empacar para la época del año.
Al permitir que el asistente realice múltiples tareas simultáneamente, esta configuración no solo mejora la experiencia del usuario, sino que también demuestra la versatilidad y el poder del framework LangGraph en la construcción de sistemas conversacionales complejos.
Construyendo el componente de voz
Para construir el componente de voz, necesitamos un componente speech-to-text y otra pieza text-to-speech.
A continuación, puedes ver el código de las dos piezas:
- Speech to text
import speech_recognition as sr
from assistant import config as cfg
def parse_voice() -> tuple[bool, str]:
r = sr.Recognizer()
with sr.Microphone() as source:
recognized = False
try:
r.pause_threshold = 1.5
r.adjust_for_ambient_noise(source)
audio_data = r.listen(source)
text = r.recognize_google(audio_data, language=cfg.SELECTED_LANGUAGE)
recognized = True
except sr.UnknownValueError:
text = cfg.LANGUAGE_MSG["unknown_value"][cfg.SELECTED_LANGUAGE]
except sr.RequestError:
text = cfg.LANGUAGE_MSG["request_error"][cfg.SELECTED_LANGUAGE]
except sr.WaitTimeoutError:
text = cfg.LANGUAGE_MSG["request_error"][cfg.SELECTED_LANGUAGE]
return recognized, text
- Text to speech
def play_audio(text: str) -> None:
myobj = gTTS(text=text, lang=cfg.MSG_SELECTED_LANGUAGE, slow=False)
mp3_fp = io.BytesIO()
myobj.write_to_fp(mp3_fp)
mp3_fp.seek(0)
audio = AudioSegment.from_file(mp3_fp, format="mp3")
audio = audio.speedup(playback_speed=cfg.AUDIO_SPEED)
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as temp_mp3:
audio.export(temp_mp3.name, format="mp3")
temp_filename = temp_mp3.name
pygame.mixer.init()
pygame.mixer.music.load(temp_filename)
pygame.mixer.music.play()
while pygame.mixer.music.get_busy():
pygame.time.Clock().tick(10)
Construyendo el flujo de múltiples agentes
Con el componente de voz funcionando, procedemos a construir el núcleo del asistente de voz. En otras palabras, el flujo del proceso desde el momento en que se recibe la entrada hasta que se obtiene una respuesta. Para hacer esto, hacemos uso de gráficos y subgrafos para tener un orquestador multiagente, escalable y granular, haciendo que el flujo sea mantenible y replicable.
def build_assistant_graph() -> CompiledGraph:
graph_builder = StateGraph(GraphState)
graph_builder.add_node(Node.DECIDER.value, decider_node)
graph_builder.add_edge(START, Node.DECIDER.value)
graph_builder.add_conditional_edges(
Node.DECIDER.value,
route_tasks_nodes,
[Graph.MAIL.value, Graph.CALENDAR.value, Graph.TRAVEL.value, Graph.QUESTION.value, END],
)
graph_builder.add_node(Graph.MAIL.value, mail_graph)
graph_builder.add_node(Graph.CALENDAR.value, calendar_graph)
graph_builder.add_node(Graph.TRAVEL.value, travel_graph)
graph_builder.add_node(Graph.QUESTION.value, question_graph)
graph_builder.add_edge(Graph.MAIL.value, END)
graph_builder.add_edge(Graph.CALENDAR.value, END)
graph_builder.add_edge(Graph.TRAVEL.value, END)
graph_builder.add_edge(Graph.QUESTION.value, END)
return graph_builder.compile()
En el código anterior (correspondiente al gráfico de la Figura 1) puede ver cómo se define el gráfico principal, en el que se llama a un nodo de decisión y la respuesta se enruta en paralelo a través del resto de los subgrafos:
Un ejemplo de un subgráfico es el componente de viaje:
def build_travel_graph() -> CompiledGraph:
graph_builder = StateGraph(ViajeState)
graph_builder.add_node(TravelNode.PARSER.value, parser_travel_node)
graph_builder.add_node(TravelNode.FLIGHTS.value, flights_node)
graph_builder.add_node(TravelNode.PACKING.value, packagin_node)
graph_builder.add_edge(START, TravelNode.PARSER.value)
graph_builder.add_conditional_edges(
TravelNode.PARSER.value,
route_travel_nodes,
[
TravelNode.FLIGHTS.value,
TravelNode.PACKING.value,
],
)
graph_builder.add_node(TravelNode.BOOK.value, book_node)
graph_builder.add_edge(TravelNode.FLIGHTS.value, TravelNode.BOOK.value)
graph_builder.add_edge(TravelNode.BOOK.value, END)
graph_builder.add_edge(TravelNode.PACKING.value, END)
return graph_builder.compile()
Los nodos son agentes que realizan una función específica. En este caso, el tomador de decisiones es una llamada a un modelo gpt-4o-mini que, dependiendo de la entrada, decide qué nodo ejecutar, mientras que el resto de los nodos cubren otras funcionalidades como, por ejemplo, la búsqueda de respuestas a través de tavily o conexiones a través de API a clientes para buscar vuelos, hoteles, etc.
Modelo de decisión Pydantic:
class DeciderOptions(BaseModel):
mail: bool
calendar: bool
travel: bool
question: bool
Nodos organizadores de viajes:
NOTA: No se han desarrollado conexiones API, ya que estamos interesados en mostrar la funcionalidad de LangGraph.
...
def parser_travel_node(state: ViajeState) -> dict[str, Any]:
travel_chain = build_travel_chain()
user_input = state["travel_input"]
res = travel_chain.invoke({"text": user_input})
return {"travel": res}
def flights_node(state: TravelState) -> dict[str, str]:
# TODO: develop the api component to recommend flights if the date is not specific or to buy tickets if exact date
formatted_string = cfg.LANGUAGE_MSG["flights_output"][cfg.MSG_SELECTED_LANGUAGE].format(
destination=state["travel"].destination,
date=state["travel"].date
)
return {"flights_output": formatted_string}
...
Próximos pasos en tu Asistente de voz con LangGraph
Nuestro enfoque está en ampliar la funcionalidad, mejorar la experiencia del usuario y fortalecer la infraestructura de la aplicación para admitir una gama más amplia de capacidades. Los próximos pasos se dividirán en tres fases primarias:
1. API integration for functional operations
La fase inicial de desarrollo implicará la creación de integraciones de API para ampliar la capacidad del asistente, permitiendo a los usuarios interactuar sin problemas con los servicios esenciales. Esta etapa se centrará en las siguientes operaciones clave:
- Reserva de hoteles y vuelos: Implemente API para acceder a las plataformas de reserva de hoteles y aerolíneas, lo que permite a los usuarios comprobar la disponibilidad, hacer reservas y confirmar las reservas directamente a través de comandos de voz.
- Gestión de calendarios: Habilite las integraciones de API de calendario para permitir que el asistente configure, modifique y envíe invitaciones a reuniones, proporcionando una experiencia de programación manos libres.
- Automatización del correo electrónico: Integre con los servicios de correo electrónico para permitir que el asistente envíe, lea y clasifique correos electrónicos, lo que permite a los usuarios administrar sus bandejas de entrada de manera más efectiva.
2. Interfaz de usuario y optimización de la experiencia
A medida que la funcionalidad principal de la aplicación se expande, la siguiente fase se centrará en profesionalizar la experiencia del usuario para garantizar que se alinee con los estándares de la industria y mejore la satisfacción del usuario. Las áreas de enfoque incluirán:
- Rediseño del front-end: una actualización completa de la interfaz de usuario para modernizar la apariencia de la aplicación, haciéndolo visualmente atractivo y fácil de navegar. Esto puede implicar la adopción de un diseño limpio y minimalista con elementos fáciles de usar que guían a los usuarios intuitivamente a través de la funcionalidad de la aplicación.
- Refinamiento de la interacción de voz: Mejora la precisión, la capacidad de respuesta y la personalización de los sistemas de reconocimiento y respuesta de voz. Esto incluye optimizar las indicaciones de voz para que suenen naturales, mejorar la comprensión contextual y garantizar que el asistente responda con precisión en diversos casos de uso.
3. Escalado y optimización de la infraestructura
Para apoyar nuevas funcionalidades y garantizar que la aplicación pueda manejar una mayor demanda a medida que crece la adopción, es esencial establecer una infraestructura sólida. Esta etapa priorizará:
- Infraestructura en la nube escalable: Transición a una solución escalable basada en la nube capaz de manejar altas cargas, soportar un escalado rápido y proporcionar redundancia para mantener el tiempo de actividad.
- Seguridad y cumplimiento de los datos: Implemente estrictos protocolos de seguridad, incluido el cifrado de datos, las comunicaciones seguras de API y el cumplimiento del RGPD, para proteger los datos del usuario y fomentar la confianza.
- Monitoreo y optimización del rendimiento: Establecer un monitoreo continuo para el rendimiento del sistema y el tiempo de actividad de la API, lo que nos permite identificar y resolver problemas de manera proactiva. Esto puede incluir la configuración de sistemas de alerta automatizados y la optimización de los tiempos de respuesta del servidor.
Conclusión
En este artículo, hemos explorado cómo desarrollar un asistente de voz utilizando LangGraph, destacando la importancia de su marco para orquestar sistemas complejos. A través de la implementación de un flujo que convierte la entrada de voz en texto y utiliza un nodo de decisión para gestionar varias tareas, como programar reuniones, redactar correos electrónicos, responder preguntas y organizar viajes, hemos demostrado la versatilidad de LangGraph en la creación de aplicaciones conversacionales avanzadas.
Además, hemos detallado los pasos necesarios para instalar los componentes de audio esenciales en diferentes sistemas operativos, lo que permite la captura y reproducción de voz en tiempo real. Esta integración es crucial para proporcionar una experiencia de usuario fluida y efectiva. Al combinar el poder de LangGraph con las capacidades de procesamiento de audio, hemos sentado las bases de un asistente de voz robusto y funcional, capaz de adaptarse a las necesidades cambiantes de los usuarios en un entorno dinámico de inteligencia artificial. A medida que la tecnología de la voz continúa evolucionando, las posibilidades de mejorar y expandir este asistente son infinitas, abriendo la puerta a futuras innovaciones en la interacción humano-computadora.
Puedes ver todo el código que hemos usado para crear el Asistente de voz con LangGraph aquí.