Skip to content

Despliegue

Cómo ai-agent-eval-harness-healthtech llega a producción, descrito a nivel de arquitectura. Producción corre en Google Cloud Run (una única instancia de contenedor, región us-central1) construida desde la misma imagen que produce el Dockerfile del proyecto. El despliegue es un Cloud Build desde fuente, sin llaves mediante Workload Identity Federation, condicionado al tag-guard de CI y que preserva secretos. Los comandos exactos de operador viven en la referencia privada de comandos de operador; esta página es la referencia pública de la forma del despliegue.

El destino de producción es una demo pública, siempre accesible, construida desde la imagen exacta que el proyecto distribuye. Cloud Run, ejecutando una sola revisión de contenedor, nos da:

  • Un endpoint HTTPS administrado mapeado a un dominio personalizado para la demo en vivo.
  • Escala a cero cuando está inactivo y una sola instancia bajo carga, de modo que el tráfico a escala de demo se mantiene dentro de la franquicia siempre gratuita de Cloud Run y el gasto real se mantiene cerca de $0 / mes.
  • Un despliegue sin llaves: el job de CI acuña un token de Google Cloud de corta vida mediante Workload Identity Federation (OIDC); no se almacena ninguna llave de cuenta de servicio.
  • Un build desde fuente que reconstruye desde el Dockerfile distribuido y preserva las variables de entorno y los vínculos con Secret Manager existentes del servicio.

El techo de una sola instancia es deliberado, no una limitación del host: el limitador de tasa, la caché de respuestas y el checkpointer del human-in-the-loop pausado son todos por proceso, así que una segunda instancia no los compartiría. Un despliegue multi-instancia necesitaría un almacén compartido (Redis, Postgres), lo cual está fuera de alcance para la demo.

El despliegue es del lado del servidor y está condicionado a una matriz de CI en verde; ningún operador publica una versión de producción a mano.

  1. Se hace push de una etiqueta de versión que coincide con vX.Y.Z.
  2. El flujo de trabajo tag-guard vuelve a ejecutar la matriz de CI completa contra el commit exacto etiquetado.
  3. El flujo de trabajo deploy corre solo después de que tag-guard se completa, y solo cuando esa corrida concluyó con éxito en la etiqueta. Una matriz de CI en rojo significa que el despliegue se omite y nada se publica.
  4. El job de despliegue hace checkout del commit exacto validado, se autentica en Google Cloud sin llaves mediante Workload Identity Federation (OIDC) y dispara un Cloud Build desde fuente que reconstruye la imagen desde el Dockerfile distribuido y despliega una nueva revisión de Cloud Run.
  5. Como la reconstrucción es desde fuente, preserva las variables de entorno y los vínculos con Secret Manager que ya están en el servicio. El tráfico se desplaza solo una vez que la nueva revisión está sana; una revisión fallida mantiene el tráfico en la anterior sana.

El despliegue sin llaves se apoya en una relación de confianza única con Google Cloud, tras la cual cada versión fluye por la canalización anterior:

  • Workload Identity Federation. Se establece un pool y un proveedor de federación para el repositorio, junto con una cuenta de servicio de despliegue de mínimo privilegio que el pool puede suplantar (limitada solo a lo que un despliegue desde fuente de Cloud Run necesita). El proveedor y la identidad de despliegue se referencian desde variables de repositorio no secretas; nunca se crea ni se almacena ninguna llave de cuenta de servicio.
  • Secret Manager. Las credenciales de los proveedores de completado -OpenAI (primario) y Anthropic (respaldo y juez de evaluación)- y los secretos de integración (embeddings/reranking administrados, la capa de datos de la demo, la voz y la verificación de bots en el borde) se guardan en Google Secret Manager y se vinculan al servicio. Como el despliegue reconstruye desde fuente, estos vínculos persisten a cada despliegue posterior.

El servicio lee su configuración de los ajustes de entorno de Cloud Run y de los vínculos con Secret Manager (los ajustes de la aplicación mapean cada campo a una variable de entorno en mayúsculas):

  • Generación corre una cascada de dos proveedores: OpenAI gpt-4o-mini (primario) con respaldo en Anthropic claude-haiku-4-5 ante una falla transitoria del upstream (ver ADR-0002). El juez de evaluación es Anthropic claude-haiku-4-5.
  • Embeddings usan por defecto el embedder administrado de Voyage cuando hay una llave de Voyage presente y recaen en el BAAI/bge-small-en-v1.5 incorporado en caso contrario, de modo que el servicio sigue funcionando sin llave de embeddings.
  • El estado de la conversación usa el MemorySaver en memoria por defecto (un hilo de HITL pausado no sobrevive a un reinicio de revisión); un checkpointer durable respaldado por Postgres se selecciona automáticamente cuando se configura una cadena de conexión de Postgres.
  • Las primitivas de resiliencia -el limitador de tasa de ventana deslizante, la caché de respuestas y el fallback de proveedor- son por proceso, razón por la cual el servicio corre una sola instancia.
  • La base de conocimiento incorporada se ingiere en una colección de Chroma vacía en el primer arranque.

El Grafo de Ejecución del Agente se alimenta de un modo de streaming de server-sent-events (SSE) en /chat y /chat/resume: una solicitud que acepta text/event-stream recibe eventos de ejecución por nodo en lugar de una única respuesta JSON. El diseño de streaming está registrado en ADR-0009.

Para que el grafo en vivo se sienta vivo, esos eventos deben llegar al navegador de forma incremental -a medida que cada nodo se ejecuta- en lugar de ser almacenados en búfer y entregados como un solo bloque al final del turno. La aplicación vacía cada registro a medida que el stream de LangGraph lo produce, pero si el borde preserva la entrega incremental es una propiedad de despliegue que se verifica contra la revisión en ejecución, no se asume: una puerta de publicación confirma que los registros llegan espaciados a lo largo del turno (graph_topology, luego node_started / node_completed, luego turn_completed) en lugar de colapsar en un volcado posterior al turno. Una versión no afirma tener un grafo de ejecución en vivo a menos que el servicio en ejecución demostrablemente fluya de forma incremental.

Cloud Run conserva cada revisión desplegada, así que el rollback es una operación de tráfico, no una reconstrucción:

  • Revertir desplazando todo el tráfico de vuelta a la revisión anterior sana; es instantáneo y no necesita una corrida de CI.
  • Avanzar un build corregido revirtiendo el commit ofensor en la rama por defecto y cortando una nueva etiqueta vX.Y.Z, lo que vuelve a ejecutar tag-guard y, en verde, redespliega.

Una revisión fallida nunca toma tráfico: Cloud Run sigue sirviendo la revisión anterior sana hasta que la nueva esté lista.

Cloud Run ejecuta el mismo Dockerfile que el proyecto distribuye: la imagen es idéntica en el desarrollo local, CI y producción. Ella:

  • vincula uvicorn a $PORT (Cloud Run inyecta 8080; las corridas locales y de Hugging Face usan por defecto 7860), de modo que una imagen sirve en cualquier puerto que el host provea,
  • se ejecuta como un usuario app no root sobre una base python:3.12-slim con solo ca-certificates y curl agregados,
  • escribe solo en /tmp y en el virtualenv de la app, y no requiere GPU.

La misma imagen corre sin cambios en otros hosts capaces de Docker; solo difieren el puerto y el cableado de la plataforma:

  • Hugging Face Spaces (Docker SDK, nivel gratuito CPU Basic): una demo secundaria genuinamente de costo cero y siempre activa desde la imagen idéntica, sirviendo en el puerto 7860 con la misma base de conocimiento incorporada.
  • Render (nivel gratuito, sin probar): el mismo Dockerfile debería ejecutarse cuando el servicio se configura para escuchar en el $PORT provisto por Render; esta ruta está documentada pero no ejercitada.
  • Docker local: la misma imagen corre localmente para el desarrollo y para verificar la cordura antes de una etiqueta de versión.