Aprenda cómo crear un agente que comprenda el contexto de su hogar, conozca sus preferencias e interactúe con usted y su hogar para realizar actividades que considere valiosas.
Este artículo describe la arquitectura y el diseño de un Asistente de hogar (HA) integración denominada agente-generador-hogar. Este proyecto utiliza LangChain y LangGraph para crear un agente de IA generativa que interactúa y automatiza tareas dentro de un entorno doméstico inteligente HA. El agente comprende el contexto de su hogar, aprende sus preferencias e interactúa con usted y su hogar para realizar actividades que usted considere valiosas. Las características clave incluyen la creación de automatizaciones, el análisis de imágenes y la gestión de estados de origen utilizando varios LLM (modelos de lenguaje grande). La arquitectura incluye modelos basados en la nube y en el borde para lograr un rendimiento y una rentabilidad óptimos. Las instrucciones de instalación, detalles de configuración e información sobre la arquitectura del proyecto y los diferentes modelos utilizados están incluidas y se pueden encontrar en la página agente-generativo-doméstico GitHub. El proyecto es de código abierto y agradece las contribuciones.
Estas son algunas de las funciones actualmente admitidas:
- Cree automatizaciones complejas de Home Assistant.
- Análisis y comprensión de la escena de la imagen.
- Análisis del estado inicial de entidades, dispositivos y áreas.
- Control total del agente sobre las entidades permitidas en el hogar.
- Memoria a corto y largo plazo mediante búsqueda semántica.
- Resumen automático del estado de origen para gestionar la duración del contexto LLM.
Este es mi proyecto personal y un ejemplo de lo que yo llamo hacking dirigido al aprendizaje. El proyecto no está afiliado a mi trabajo en amazon ni estoy asociado de ninguna manera con las organizaciones responsables de Home Assistant o LangChain/LangGraph.
Crear un agente para monitorear y controlar su hogar puede llevar a acciones inesperadas y potencialmente poner a su hogar y a usted mismo en riesgo debido a alucinaciones de LLM y preocupaciones de privacidad, especialmente cuando se exponen los estados de origen y la información del usuario a LLM basados en la nube. He tomado decisiones arquitectónicas y de diseño razonables para mitigar estos riesgos, pero no se pueden eliminar por completo.
Una de las primeras decisiones clave fue confiar en un enfoque híbrido de nube. Esto permite el uso de los modelos de razonamiento y planificación más sofisticados disponibles, lo que debería ayudar a reducir las alucinaciones. Se emplean modelos de borde más simples y más centrados en tareas para minimizar aún más los errores de LLM.
Otra decisión crítica fue aprovechar las capacidades de LangChain, que permiten ocultar información confidencial de las herramientas LLM y proporcionarla solo en tiempo de ejecución. Por ejemplo, la lógica de la herramienta puede requerir el uso del ID del usuario que realizó una solicitud. Sin embargo, estos valores generalmente no deberían ser controlados por el LLM. Permitir que el LLM manipule la identificación del usuario podría plantear riesgos de seguridad y privacidad. Para mitigar esto, utilicé el InyectadoToolArg anotación.
Además, el uso de grandes LLM basados en la nube genera importantes costos de nube, y el hardware de borde necesario para ejecutar modelos de LLM de borde puede ser costoso. Los costos combinados de operación e instalación probablemente sean prohibitivos para el usuario promedio en este momento. Se necesita un esfuerzo de toda la industria para “hacer que los LLM sean tan baratos como las CNN” para llevar a los agentes locales al mercado masivo.
Es importante ser consciente de estos riesgos y comprender que, a pesar de estas mitigaciones, todavía estamos en las primeras etapas de este proyecto y de los agentes internos en general. Queda mucho por hacer para que estos agentes sean asistentes verdaderamente útiles y dignos de confianza.
A continuación se muestra una vista de alto nivel de la arquitectura del agente generador doméstico.
La arquitectura de integración general sigue las mejores prácticas como se describe en Núcleo de asistente de hogar y cumple con Tienda comunitaria de asistente de hogar (HACS) requisitos de publicación.
El agente está construido usando LangGraph y usa HA conversación componente para interactuar con el usuario. El agente utiliza la API de Home Assistant LLM para obtener el estado de la casa y comprender las herramientas nativas de HA que tiene a su disposición. Implementé todas las demás herramientas disponibles para el agente usando LangChain. El agente emplea varios LLM, un modelo primario grande y muy preciso para razonamiento de alto nivel, modelos auxiliares especializados más pequeños para análisis de imágenes de cámaras, resumen del contexto del modelo primario y generación de incrustaciones para búsqueda semántica a largo plazo. El modelo principal está basado en la nube y los modelos auxiliares están basados en el borde y se ejecutan bajo el Ser framework en un ordenador ubicado en el domicilio.
Los modelos que se utilizan actualmente se resumen a continuación.
Agente basado en LangGraph
LangGraph potencia el agente de conversación, permitiéndole crear aplicaciones multiactor con estado utilizando LLM lo más rápido posible. Amplía las capacidades de LangChain, introduciendo la capacidad de crear y gestionar gráficos cíclicos esenciales para desarrollar tiempos de ejecución de agentes complejos. Un gráfico modela el flujo de trabajo del agente, como se ve en la imagen siguiente.
El flujo de trabajo del agente tiene cinco nodos, cada módulo de Python modifica el estado del agente, una estructura de datos compartida. Los bordes entre los nodos representan las transiciones permitidas entre ellos, con líneas continuas incondicionales y líneas discontinuas condicionales. Los nodos hacen el trabajo y los bordes indican qué hacer a continuación.
El __comenzar__ y __fin__ Los nodos informan al gráfico dónde comenzar y detenerse. El agente El nodo ejecuta el LLM principal y, si decide utilizar una herramienta, el acción El nodo ejecuta la herramienta y luego devuelve el control al agente. El resumir_y_recortar El nodo procesa el contexto del LLM para gestionar el crecimiento manteniendo la precisión si agente no tiene herramienta para llamar y la cantidad de mensajes cumple con las condiciones que se mencionan a continuación.
Gestión del contexto LLM
Debe administrar cuidadosamente la duración del contexto de los LLM para equilibrar el costo, la precisión y la latencia y evitar activar límites de velocidad, como la restricción de tokens por minuto de OpenAI. El sistema controla la longitud del contexto del modelo primario de dos maneras: recorta los mensajes en el contexto si exceden un parámetro máximo y el contexto se resume una vez que la cantidad de mensajes excede otro parámetro. Estos parámetros son configurables en const.py; su descripción está a continuación.
- CONTEXT_MAX_MESSAGES | Mensajes para mantener en contexto antes de eliminarlos | Predeterminado = 100
- CONTEXT_SUMMARIZE_THRESHOLD | Mensajes en contexto antes de la generación del resumen | Predeterminado = 20
El resumir_y_recortar El nodo en el gráfico recortará los mensajes solo después del resumen del contenido. Puede ver el código Python asociado con este nodo en el siguiente fragmento.
async def _summarize_and_trim(
state: State, config: RunnableConfig, *, store: BaseStore
) -> dict(str, list(AnyMessage)):
"""Coroutine to summarize and trim message history."""
summary = state.get("summary", "")if summary:
summary_message = SUMMARY_PROMPT_TEMPLATE.format(summary=summary)
else:
summary_message = SUMMARY_INITIAL_PROMPT
messages = (
(SystemMessage(content=SUMMARY_SYSTEM_PROMPT)) +
state("messages") +
(HumanMessage(content=summary_message))
)
model = config("configurable")("vlm_model")
options = config("configurable")("options")
model_with_config = model.with_config(
config={
"model": options.get(
CONF_VLM,
RECOMMENDED_VLM,
),
"temperature": options.get(
CONF_SUMMARIZATION_MODEL_TEMPERATURE,
RECOMMENDED_SUMMARIZATION_MODEL_TEMPERATURE,
),
"top_p": options.get(
CONF_SUMMARIZATION_MODEL_TOP_P,
RECOMMENDED_SUMMARIZATION_MODEL_TOP_P,
),
"num_predict": VLM_NUM_PREDICT,
}
)
LOGGER.debug("Summary messages: %s", messages)
response = await model_with_config.ainvoke(messages)
# Trim message history to manage context window length.
trimmed_messages = trim_messages(
messages=state("messages"),
token_counter=len,
max_tokens=CONTEXT_MAX_MESSAGES,
strategy="last",
start_on="human",
include_system=True,
)
messages_to_remove = (m for m in state("messages") if m not in trimmed_messages)
LOGGER.debug("Messages to remove: %s", messages_to_remove)
remove_messages = (RemoveMessage(id=m.id) for m in messages_to_remove)
return {"summary": response.content, "messages": remove_messages}
Estado latente
La latencia entre las solicitudes de los usuarios o el agente que toma medidas oportunas en nombre del usuario es fundamental que usted debe considerar en el diseño. Utilicé varias técnicas para reducir la latencia, incluido el uso de LLM auxiliares más pequeños y especializados que se ejecutan en el borde y facilitan el almacenamiento en caché de mensajes del modelo primario estructurando los mensajes para colocar contenido estático, como instrucciones y ejemplos, contenido inicial y variable, como contenido específico del usuario. información al final. Estas técnicas también reducen considerablemente los costos de uso del modelo primario.
Puede ver el rendimiento de latencia típico a continuación.
- Intentos de HA (por ejemplo, encender una luz) | < 1 segundo
- Analizar imagen de cámara (solicitud inicial) | < 3 segundos
- Agregar automatización | < 1 segundo
- Operaciones de memoria | < 1 segundo
Herramientas
El agente puede utilizar herramientas HA como se especifica en el API LLM y otras herramientas construidas en el marco LangChain como se define en herramientas.py. Además, también puede ampliar la API LLM con sus propias herramientas. El código le proporciona al LLM principal la lista de herramientas que puede llamar, junto con instrucciones sobre cómo usarlas en su mensaje del sistema y en la cadena de documentación de la definición de la función Python de la herramienta. Puede ver un ejemplo de instrucciones de cadena de documentación en el siguiente fragmento de código para get_and_analyze_camera_image herramienta.
@tool(parse_docstring=False)
async def get_and_analyze_camera_image( # noqa: D417
camera_name: str,
detection_keywords: list(str) | None = None,
*,
# Hide these arguments from the model.
config: Annotated(RunnableConfig, InjectedToolArg()),
) -> str:
"""
Get a camera image and perform scene analysis on it.Args:
camera_name: Name of the camera for scene analysis.
detection_keywords: Specific objects to look for in image, if any.
For example, If user says "check the front porch camera for
boxes and dogs", detection_keywords would be ("boxes", "dogs").
"""
hass = config("configurable")("hass")
vlm_model = config("configurable")("vlm_model")
options = config("configurable")("options")
image = await _get_camera_image(hass, camera_name)
return await _analyze_image(vlm_model, options, image, detection_keywords)
Si el agente decide utilizar una herramienta, el nodo LangGraph acción Se ingresa y el código del nodo ejecuta la herramienta. El nodo utiliza un mecanismo simple de recuperación de errores que le pedirá al agente que intente llamar a la herramienta nuevamente con los parámetros corregidos en caso de cometer un error. El siguiente fragmento de código muestra el código Python asociado con el acción nodo.
async def _call_tools(
state: State, config: RunnableConfig, *, store: BaseStore
) -> dict(str, list(ToolMessage)):
"""Coroutine to call Home Assistant or langchain LLM tools."""
# Tool calls will be the last message in state.
tool_calls = state("messages")(-1).tool_callslangchain_tools = config("configurable")("langchain_tools")
ha_llm_api = config("configurable")("ha_llm_api")
tool_responses: list(ToolMessage) = ()
for tool_call in tool_calls:
tool_name = tool_call("name")
tool_args = tool_call("args")
LOGGER.debug(
"Tool call: %s(%s)", tool_name, tool_args
)
def _handle_tool_error(err:str, name:str, tid:str) -> ToolMessage:
return ToolMessage(
content=TOOL_CALL_ERROR_TEMPLATE.format(error=err),
name=name,
tool_call_id=tid,
status="error",
)
# A langchain tool was called.
if tool_name in langchain_tools:
lc_tool = langchain_tools(tool_name.lower())
# Provide hidden args to tool at runtime.
tool_call_copy = copy.deepcopy(tool_call)
tool_call_copy("args").update(
{
"store": store,
"config": config,
}
)
try:
tool_response = await lc_tool.ainvoke(tool_call_copy)
except (HomeAssistantError, ValidationError) as e:
tool_response = _handle_tool_error(repr(e), tool_name, tool_call("id"))
# A Home Assistant tool was called.
else:
tool_input = llm.ToolInput(
tool_name=tool_name,
tool_args=tool_args,
)
try:
response = await ha_llm_api.async_call_tool(tool_input)
tool_response = ToolMessage(
content=json.dumps(response),
tool_call_id=tool_call("id"),
name=tool_name,
)
except (HomeAssistantError, vol.Invalid) as e:
tool_response = _handle_tool_error(repr(e), tool_name, tool_call("id"))
LOGGER.debug("Tool response: %s", tool_response)
tool_responses.append(tool_response)
return {"messages": tool_responses}
La API LLM indica al agente que siempre llame a las herramientas usando HA intenciones incorporadas al controlar Home Assistant y usar los intents `HassTurnOn` para bloquear y `HassTurnOff` para desbloquear un candado. Una intención describe la intención de un usuario generada por las acciones del usuario.
Puede ver la lista de herramientas LangChain que el agente puede utilizar a continuación.
- get_and_analyze_camera_image | ejecutar análisis de escena en la imagen desde una cámara
- upsert_memoria | agregar o actualizar una memoria
- agregar_automatización | crear y registrar una automatización HA
- get_entity_history | consultar la base de datos de HA para conocer el historial de la entidad
Hardware
Construí la instalación de HA en una Raspberry Pi 5 con almacenamiento SSD, Zigbee y conectividad LAN. Implementé los modelos perimetrales en Ollama en un servidor basado en Ubuntu con una CPU AMD de 64 bits a 3,4 GHz, GPU Nvidia 3090 y 64 GB de RAM del sistema. El servidor está en la misma LAN que Raspberry Pi.
He estado usando este proyecto en casa durante algunas semanas y lo encontré útil pero frustrante en algunas áreas en las que trabajaré para abordar. A continuación se muestra una lista de los pros y los contras de mi experiencia con el agente.
Ventajas
- El análisis de la escena de la imagen de la cámara es muy útil y flexible, ya que puede consultar casi cualquier cosa y no tener que preocuparse por tener el clasificador correcto como lo haría con un enfoque de ML tradicional.
- Las automatizaciones son muy fáciles de configurar y pueden ser bastante complejas. Es alucinante lo bueno que es el LLM principal para generar YAML compatible con HA.
- La latencia en la mayoría de los casos es bastante aceptable.
- Es muy fácil agregar herramientas LLM adicionales y estados gráficos con LangChain y LangGraph.
Contras
- El análisis de la imagen de la cámara parece menos preciso que los enfoques tradicionales de ML. Por ejemplo, detectar paquetes que están parcialmente oscurecidos es muy difícil de manejar para el modelo.
- El modelo primario podría tener costos elevados. Ejecutar un solo detector de paquetes una vez cada 30 minutos cuesta alrededor de $2,50 por día.
- El uso de resultados de modelos estructurados para los LLM auxiliares, que facilitarían el procesamiento de LLM posteriores, reduce considerablemente la precisión.
- El agente necesita ser más proactivo. Se espera que agregar un paso de planificación al gráfico de agentes solucione este problema.
A continuación se muestran algunos ejemplos de lo que puede hacer con la integración del agente generativo doméstico (HGA), como lo ilustran las capturas de pantalla del cuadro de diálogo Asistencia que tomé durante las interacciones con mi instalación de HA.
- Cree una automatización que se ejecute periódicamente.
El siguiente fragmento muestra que el agente domina YAML según lo que generó y registró como automatización de HA.
alias: Check Litter Box Waste Drawer
triggers:
- minutes: /30
trigger: time_pattern
conditions:
- condition: numeric_state
entity_id: sensor.litter_robot_4_waste_drawer
above: 90
actions:
- data:
message: The Litter Box waste drawer is more than 90% full!
action: notify.notify
- Verifique varias cámaras (video del autor).
https://github.com/user-attachments/assets/230baae5-8702-4375-a3f0-ffa981ee66a3
- Resuma el estado de origen (video del autor).
https://github.com/user-attachments/assets/96f834a8-58cc-4bd9-a899-4604c1103a98
- Memoria a largo plazo con búsqueda semántica.