LLMOps Patrones de Ingeniería de Prompts de Producción con Hamilton
LLMOps Patrones de Ingeniería de Prompts de Producción con Hamilton
Una visión general de las maneras de grado de producción para iterar en prompts con Hamilton

Lo que envías a tu modelo de lenguaje de gran tamaño (LLM) es bastante importante. Pequeñas variaciones y cambios pueden tener grandes impactos en los resultados, por lo que a medida que tu producto evoluciona, también la necesidad de evolucionar tus prompts. Los LLMs también están en constante desarrollo y lanzamiento, por lo que a medida que cambian, tus prompts también deberán cambiar. Por lo tanto, es importante establecer un patrón de iteración para operacionalizar cómo “implementas” tus prompts para que tú y tu equipo puedan avanzar de manera eficiente, pero también asegurarse de minimizar, si no evitar, los problemas de producción. En esta publicación, te guiaremos a través de las mejores prácticas para gestionar los prompts con Hamilton, un marco de micro-orquestación de código abierto, haciendo analogías con los patrones de MLOps y discutiendo compensaciones en el camino. Las conclusiones generales de esta publicación siguen siendo aplicables incluso si no usas Hamilton.
Algunas cosas antes de comenzar:
- Soy uno de los co-creadores de Hamilton.
- ¿No estás familiarizado con Hamilton? Desplázate hasta el final para obtener más enlaces.
- Si estás buscando una publicación que hable sobre “gestión de contexto”, esta no es esa publicación. Pero es la publicación que te ayudará con los detalles sobre cómo iterar y crear esa historia de iteración de “gestión de contexto de prompts” de grado de producción.
- Usaremos “prompt” e “plantilla de prompt” indistintamente.
- Supondremos que estos prompts se utilizan en un entorno de servicio web “en línea”.
- Utilizaremos el ejemplo del resumen de PDF de Hamilton para proyectar nuestros patrones.
- ¿Cuál es nuestra credibilidad aquí? Hemos pasado nuestras carreras construyendo herramientas de autoservicio de datos/MLOps, más famosamente para más de 100 científicos de datos de Stitch Fix. Así que hemos visto nuestra parte de interrupciones y enfoques a lo largo del tiempo.
Los prompts son para LLMs lo que los hiperparámetros son para modelos de ML
Punto: Los prompts + APIs de LLM son análogos a los hiperparámetros + modelos de aprendizaje automático.
En términos de prácticas de “Ops”, LLMOps todavía está en su infancia. MLOps es un poco más antiguo, pero aún ninguno de los dos es ampliamente adoptado si lo comparas con el conocimiento generalizado en torno a las prácticas de DevOps.
Las prácticas de DevOps se preocupan en gran medida por cómo enviar código a producción, y las prácticas de MLOps se preocupan por cómo enviar código y artefactos de datos (por ejemplo, modelos estadísticos) a producción. Entonces, ¿qué pasa con LLMOps? Personalmente, creo que está más cerca de MLOps, ya que tienes:
- El Método de Captura y Recaptura
- ¿Quieres mejorar tu pronóstico a corto plazo? Prueba con la detecci...
- El Lenguaje de las Ubicaciones Evaluando la Proficiencia de la IA G...
- tu flujo de trabajo de LLM es simplemente código.
- y una API de LLM es un artefacto de datos que se puede “ajustar” utilizando prompts, al igual que un modelo de aprendizaje automático (ML) y sus hiperparámetros.
Por lo tanto, es muy probable que te importe versionar la API de LLM + prompts de manera estrecha para buenas prácticas de producción. Por ejemplo, en la práctica de MLOps, querrías tener un proceso establecido para validar que tu modelo de ML todavía se comporte correctamente cuando se cambian sus hiperparámetros.
¿Cómo deberías pensar en operacionalizar un prompt?
Para ser claros, las dos partes a controlar son el LLM y los prompts. Al igual que en MLOps, cuando cambia el código o el artefacto del modelo, quieres poder determinar cuál fue. Para LLMOps, queremos el mismo discernimiento, separando el flujo de trabajo del LLM de la API de LLM + prompts. Es importante considerar que los LLMs (alojados por uno mismo o APIs) son en su mayoría estáticos, ya que actualizamos (o incluso controlamos) sus componentes internos con menos frecuencia. Por lo tanto, cambiar la parte de los prompts de la API de LLM + prompts es como crear un nuevo artefacto de modelo.
Hay dos formas principales de tratar las indicaciones:
- Indicaciones como variables de tiempo de ejecución dinámicas. La plantilla utilizada no es estática en una implementación.
- Indicaciones como código. La plantilla de indicaciones es estática/predefinida en una implementación.
La diferencia principal es la cantidad de elementos móviles que necesitas gestionar para garantizar una excelente historia de producción. A continuación, profundizaremos en cómo usar Hamilton en el contexto de estos dos enfoques.
Indicaciones como variables de tiempo de ejecución dinámicas
Pasando/Cargando Indicaciones Dinámicamente
Las indicaciones son solo cadenas de texto. Dado que las cadenas de texto son un tipo primitivo en la mayoría de los lenguajes, esto significa que son bastante fáciles de pasar de un lugar a otro. La idea es abstraer tu código para que en tiempo de ejecución pases las indicaciones requeridas. Más concretamente, “cargar/recargar” las plantillas de indicaciones siempre que haya una nueva “actualizada”.
La analogía de MLOps aquí sería recargar automáticamente el artefacto del modelo de ML (por ejemplo, un archivo pkl) cada vez que haya un nuevo modelo disponible.


El beneficio aquí es que puedes implementar rápidamente nuevas indicaciones porque ¡no necesitas volver a implementar tu aplicación!
La desventaja de esta velocidad de iteración es una mayor carga operativa:
- Para alguien que esté monitoreando tu aplicación, no quedará claro cuándo ocurrió el cambio y si se ha propagado en tus sistemas. Por ejemplo, acabas de agregar una nueva indicación y ahora el LLM devuelve más tokens por solicitud, lo que provoca un aumento en la latencia; quien esté monitoreando probablemente se sentirá desconcertado, a menos que tengas una gran cultura de registro de cambios.
- Las semánticas de reversión implican tener que conocer otro sistema. No puedes simplemente revertir una implementación anterior para solucionar los problemas.
- Necesitarás un buen monitoreo para comprender qué se ejecutó y cuándo; por ejemplo, cuando el servicio al cliente te entrega un ticket para investigar, ¿cómo sabes qué indicación se utilizó?
- Necesitarás administrar y monitorear cualquier sistema que utilices para administrar y almacenar tus indicaciones. Este será un sistema adicional que deberás mantener fuera de lo que está sirviendo tu código.
- Necesitarás administrar dos procesos, uno para actualizar y enviar el servicio, y otro para actualizar y enviar las indicaciones. Sincronizar estos cambios será responsabilidad tuya. Por ejemplo, necesitas realizar un cambio de código en tu servicio para manejar una nueva indicación. Deberás coordinar el cambio en dos sistemas para que funcione, lo cual representa una carga operativa adicional para gestionar.
Cómo funcionaría con Hamilton
Nuestro flujo de resumen de PDF se vería así si eliminas las definiciones de funciones summarize_text_from_summaries_prompt
y summarize_chunk_of_text_prompt
:

Para operar las cosas, querrás inyectar las indicaciones en el momento de la solicitud:
from hamilton import base, driver
import summarization_shortend
# crear el driver
dr = ( driver.Builder()
.with_modules(summarization_sortened)
.build())
# obtener las indicaciones de algún lugar
summarize_chunk_of_text_prompt = """ALGUNA INDICACIÓN PARA {chunked_text}"""
summarize_text_from_summaries_prompt = """ALGUNA INDICACIÓN {summarized_chunks} ... {user_query}"""
# ejecutar y pasar el resultado de la indicación
result = dr.execute(
["texto_resumido"],
inputs={
"summarize_chunk_of_text_prompt": summarize_chunk_of_text_prompt,
...
})
O puedes cambiar tu código para cargar dinámicamente las indicaciones, es decir, agregar funciones para obtener las indicaciones de un sistema externo como parte del flujo de datos de Hamilton. En cada invocación, se consultará la indicación a usar (puedes guardar en caché esto para mejorar el rendimiento):
# prompt_template_loaders.py
def summarize_chunk_of_text_prompt(db_client: Client, other_args: str) -> str:
# código pseudo aquí, pero entiendes la idea:
_prompt = db_client.query("obtener última indicación X de la base de datos", other_args)
return _prompt
def summarize_text_from_summaries_prompt(db_client: Client, another_arg: str) -> str:
# código pseudo aquí, pero entiendes la idea:
_prompt = db_client.query("obtener última indicación Y de la base de datos", another_arg)
return _prompt
Código del driver:
from hamilton import base, driver
import prompt_template_loaders # <-- cargar esto para proporcionar la entrada de la indicación
import summarization_shortend
# crear el driver
dr = ( driver.Builder()
.with_modules(
prompt_template_loaders, # <-- Hamilton llamará a las funciones anteriores
summarization_sortened,
)
.build())
# ejecutar y pasar el resultado de la indicación
result = dr.execute(
["texto_resumido"],
inputs={
# no necesitas pasar las indicaciones en esta versión
})
¿Cómo registro las indicaciones utilizadas y monitoreo los flujos?
Aquí describimos algunas formas de monitorear lo que sucedió.
- Registrar los resultados de la ejecución. Es decir, ejecutar Hamilton y luego emitir información donde desees que vaya.
result = dr.execute(
["texto_resumido",
"summarize_chunk_of_text_prompt",
... # y cualquier otra cosa que desees extraer
"summarize_text_from_summaries_prompt"],
inputs={
# no necesitas pasar las indicaciones en esta versión
})
my_log_system(result) # envía lo que desees para guardar de forma segura en algún sistema que poseas.
Nota. En el ejemplo anterior, Hamilton te permite solicitar cualquier salida intermedia simplemente solicitando “funciones” (es decir, nodos en el diagrama) por su nombre. ¡Si realmente quieres obtener todas las salidas intermedias de todo el flujo de datos, puedes hacerlo y registrarlas donde quieras!
- Usar registros dentro de las funciones de Hamilton (para ver el poder de este enfoque, consulta mi antigua charla sobre registros estructurados):
import logging
logger = logging.getLogger(__name__)
def summarize_text_from_summaries_prompt(db_client: Client, another_arg: str) -> str:
# código pseudo aquí, pero entiendes la idea:
_prompt = db_client.query("obtener última indicación Y de la base de datos", another_arg)
logger.info(f"Se utilizó la indicación [{_prompt}]")
return _prompt
- Extender Hamilton para emitir esta información. Utiliza Hamilton para capturar información de las funciones ejecutadas, es decir, nodos, sin necesidad de insertar declaraciones de registro dentro del cuerpo de la función. Esto promueve la reutilización, ya que puedes alternar el registro entre la configuración de desarrollo y producción a nivel del Driver. Consulta GraphAdapters o escribe tu propio decorador de Python para envolver funciones con fines de monitoreo.
En cualquiera de los códigos anteriores, puedes incorporar fácilmente una herramienta de terceros para ayudar a rastrear y monitorear el código, así como la llamada a la API externa.
Indicaciones como código
Indicaciones como cadenas estáticas
Dado que las indicaciones son simplemente cadenas, también son muy adecuadas para almacenarse junto con tu código fuente. La idea es almacenar tantas versiones de indicaciones como desees dentro de tu código para que, en tiempo de ejecución, el conjunto de indicaciones disponibles sea fijo y determinista.
La analogía de MLOps aquí es que en lugar de volver a cargar los modelos dinámicamente, en su lugar se integra el modelo de ML en el contenedor o se codifica la referencia. Una vez implementado, tu aplicación tiene todo lo que necesita. La implementación es inmutable; nada cambia una vez que está en marcha. Esto simplifica la depuración y determinar qué está sucediendo.


Este enfoque tiene muchos beneficios operativos:
- Cada vez que se envía una nueva indicación, se fuerza una nueva implementación. Las reglas de reversión son claras si hay algún problema con una nueva indicación.
- Puedes enviar una solicitud de extracción (PR) para el código fuente y las indicaciones al mismo tiempo. Se vuelve más sencillo revisar qué cambio se hizo y las dependencias posteriores de las indicaciones con las que se tocará/interactuará.
- Puedes agregar comprobaciones a tu sistema de CI/CD para asegurarte de que las indicaciones incorrectas no lleguen a producción.
- Es más sencillo depurar un problema. Solo tienes que extraer el contenedor (Docker) que se creó y podrás replicar rápidamente cualquier problema del cliente de manera exacta y sencilla.
- No hay otro “sistema de indicaciones” que mantener o administrar. Simplifica las operaciones.
- No impide agregar monitoreo y visibilidad adicionales.
Cómo funcionaría con Hamilton
Las indicaciones se codificarían en funciones dentro del flujo de datos/gráfico acíclico dirigido (DAG):

Al combinar este código con git, tienes un sistema de versionado ligero para todo tu flujo de datos (es decir, “cadena”), por lo que siempre puedes discernir en qué estado estaba el mundo, dado un SHA de confirmación de git. Si deseas administrar y tener acceso a múltiples indicaciones en cualquier momento dado, Hamilton tiene dos poderosas abstracciones para permitirte hacerlo: @config.when
y módulos de Python. Esto te permite almacenar y mantener disponibles todas las versiones anteriores de las indicaciones y especificar cuál utilizar a través del código.
@config.when (docs)
Hamilton tiene el concepto de decoradores, que son solo anotaciones en funciones. El decorador @config.when
permite especificar implementaciones alternativas para una función, es decir, “nodo”, en tu flujo de datos. En este caso, especificamos indicaciones alternativas.
from hamilton.function_modifiers import [email protected](version="v1")def summarize_chunk_of_text_prompt__v1() -> str: """V1 prompt for summarizing chunks of text.""" return f"Resumir este texto. Extraer los puntos clave con razonamiento.\n\nContenido:"@config.when(version="v2")def summarize_chunk_of_text_prompt__v2(content_type: str = "un artículo académico") -> str: """V2 prompt for summarizing chunks of text.""" return f"Resumir este texto de {content_type}. Extraer los puntos clave con razonamiento. \n\nContenido:"
Puedes seguir agregando funciones anotadas con @config.when
, lo que te permite cambiar entre ellas utilizando la configuración pasada al Driver
de Hamilton. Al instanciar el Driver
, construirá el flujo de datos utilizando la implementación de indicación asociada con el valor de configuración.
from hamilton import base, driver
import summarization
# create driver
dr = (
driver.Builder()
.with_modules(summarization)
.with_config({"version": "v1"}) # Se elige V1. Usa "v2" para V2.
.build()
)
Cambio de módulo
En lugar de usar @config.when
, también puedes colocar tus diferentes implementaciones de prompts en diferentes módulos de Python. Luego, al construir el Driver
, pasa el módulo correcto para el contexto que deseas utilizar.
Entonces aquí tenemos un módulo que contiene la versión V1 de nuestro prompt:
# prompts_v1.py
def summarize_chunk_of_text_prompt() -> str:
"""Prompt V1 para resumir fragmentos de texto."""
return f"Resumir este texto. Extraer los puntos clave con razonamiento.\n\nContenido:"
Aquí tenemos un módulo que contiene la versión V2 (observa cómo difieren ligeramente):
# prompts_v2.py
def summarize_chunk_of_text_prompt(content_type: str = "un artículo académico") -> str:
"""Prompt V2 para resumir fragmentos de texto."""
return f"Resumir este texto de {content_type}. Extraer los puntos clave con razonamiento. \n\nContenido:"
En el código del driver a continuación, elegimos el módulo correcto basado en algún contexto.
# run.py
from hamilton import driver
import summarization
import prompts_v1
import prompts_v2
# create driver -- pasando el módulo correcto que queremos
dr = (
driver.Builder()
.with_modules(
prompts_v1, # o prompts_v2
summarization,
)
.build()
)
El enfoque de módulo nos permite encapsular y versionar conjuntos completos de prompts juntos. Si deseas retroceder en el tiempo (a través de git) o ver cuál fue la versión de prompt aprobada, solo necesitas navegar hasta el commit correcto y luego buscar en el módulo correcto.
¿Cómo registro los prompts utilizados y monitoreo los flujos?
Suponiendo que estás usando git para rastrear tu código, no necesitarías registrar qué prompts se están utilizando. En cambio, solo necesitarías saber qué SHA del commit de git está implementado y podrás rastrear la versión de tu código y los prompts simultáneamente.
Para monitorear los flujos, al igual que el enfoque anterior, tienes los mismos ganchos de monitoreo disponibles a tu disposición, y no los repetiré aquí, pero son:
- Solicitar cualquier salida intermedia y registrarla fuera de Hamilton.
- Registrarlos desde dentro de la función tú mismo, o construir un decorador de Python / GraphAdapter para hacerlo a nivel de framework.
- Integrar herramientas de terceros para monitorear tu código y llamadas a la API de LLM.
- ¡O todas las anteriores!
¿Qué pasa con las pruebas A/B de mis prompts?
Con cualquier iniciativa de ML, es importante medir el impacto comercial de los cambios. Del mismo modo, con LLMs + prompts, será importante probar y medir los cambios en función de métricas comerciales importantes. En el mundo de MLOps, estarías probando modelos de ML en un entorno A/B para evaluar su valor comercial dividiendo el tráfico entre ellos. Para garantizar la aleatoriedad necesaria en las pruebas A/B, no sabrías en tiempo de ejecución qué modelo usar hasta que se lance una moneda. Sin embargo, para poner esos modelos en marcha, ambos deben seguir un proceso para calificarlos. Por lo tanto, para los prompts, debemos pensar de manera similar.
Los dos patrones de ingeniería de prompts anteriores no te impiden realizar pruebas A/B de prompts, pero significa que debes administrar un proceso para habilitar tantas plantillas de prompts como estés probando en paralelo. Si también estás ajustando rutas de código, tenerlos en el código será más sencillo para discernir y depurar lo que está sucediendo, y puedes utilizar el decorador de @config.when
/ intercambio de módulos de Python para este propósito. En lugar de tener que depender críticamente de tu pila de registro/monitoreo/observabilidad para decirte qué prompt se utilizó si los cargas/pasas de forma dinámica y luego tienes que hacer un mapeo mental de qué prompts van con qué rutas de código.
Ten en cuenta que todo esto se vuelve más difícil si necesitas cambiar varios prompts para una prueba A/B porque tienes varios de ellos en un flujo. Por ejemplo, tienes dos prompts en tu flujo de trabajo y estás cambiando LLMs, querrás probar el cambio de manera holística, en lugar de individualmente por prompt. Nuestro consejo es que al poner los prompts en el código, tu vida operativa será más sencilla, ya que sabrás qué dos prompts pertenecen a qué rutas de código sin tener que hacer ningún mapeo mental.
Resumen
En esta publicación, explicamos dos patrones para gestionar las solicitudes en un entorno de producción con Hamilton. El primer enfoque trata las solicitudes como variables de tiempo de ejecución dinámicas, mientras que el segundo trata las solicitudes como código para configuraciones de producción. Si valoras la reducción de la carga operativa, entonces nuestro consejo es codificar las solicitudes como código, ya que es más simple operativamente, a menos que la velocidad para cambiarlas realmente importe para ti.
Para resumir:
- Solicitudes como variables de tiempo de ejecución dinámicas. Usa un sistema externo para pasar las solicitudes a tus flujos de datos de Hamilton, o usa Hamilton para extraerlas de una base de datos. Para depurar y monitorear, es importante poder determinar qué solicitud se usó para una invocación dada. Puedes integrar herramientas de código abierto o usar algo como la Plataforma DAGWorks para asegurarte de saber qué se usó para cualquier invocación de tu código.
- Solicitudes como código. Codificar las solicitudes como código permite una fácil versionización con git. La gestión de cambios se puede hacer a través de solicitudes de extracción y verificaciones de CI/CD. Funciona bien con las características de Hamilton como
@config.when
y el cambio de módulo a nivel de controlador porque determina claramente qué versión de la solicitud se utiliza. Este enfoque refuerza el uso de cualquier herramienta que puedas usar para monitorear o rastrear, como la Plataforma DAGWorks, ya que las solicitudes para una implementación son inmutables.
¡Queremos saber de ti!
Si te emociona alguna de estas cosas o tienes opiniones fuertes, ¡deja un comentario o pasa por nuestro canal de Slack! Algunos enlaces para alabar / quejarse / chatear:
- 📣 únete a nuestra comunidad en Slack: estaremos encantados de ayudarte a responder cualquier pregunta que puedas tener o para que comiences.
- ⭐️ danos una estrella en GitHub.
- 📝 déjanos un problema si encuentras algo.
- 📚 lee nuestra documentación.
- ⌨️ aprende interactivamente sobre Hamilton en tu navegador.
Otros enlaces/publicaciones de Hamilton que podrían interesarte:
- tryhamilton.dev: un tutorial interactivo en tu navegador.
- Hamilton + Lineage en 10 minutos.
- Cómo usar Hamilton con Pandas en 5 minutos.
- Cómo usar Hamilton con Ray en 5 minutos.
- Cómo usar Hamilton en un entorno de cuaderno.
- Historia general e introducción a Hamilton.
- Los beneficios de crear flujos de datos con Hamilton (Publicación de usuario orgánico sobre Hamilton).