Desde hace un tiempo no paramos de oír las bondades de la IA generativa y cómo esta revolución promete un sinfín de casos de uso y de tareas “que ya no tendremos que hacer”.

Seguramente por esto muchísima gente se está acercando a ChatGPT, Bard y similares para usarlos en su día a día y hacer sus propias pruebas para ver cómo funciona eso.

El siguiente paso, al menos para los informáticos, es preguntar si el juguete tiene API y cómo se usa. El tercer paso es ver si existe “algo” que nos ayude a trabajar de una manera más eficiente con estos grandes modelos de lenguaje, algo que nos permite hacer aplicaciones complejas sin tener que reinventar la rueda...

Pues bien, a día de hoy este “algo” se llama LangChain y es de lo que vamos a hablar hoy. Mi compañera Bea Hernández ya escribió un post acerca del framework y sobre cómo usarlo con OpenAI para hacer una aplicación de preguntas sobre un contexto específico.

Vamos a profundizar en cómo hacer llamadas a los LLMs (Large Language Models) haciendo uso de chains (o cadenas). Las chains son una de las principales características de este framework. Aprenderemos a hacer cadenas básicas, a concatenarlas, a usarlas en paralelo y a utilizar tools para enriquecer los resultados. Para que sea más vistoso, lo acompañaremos de ejemplos.

Una de las ventajas del framework es que nos permite abstraernos de los modelos usados, en los ejemplos usaremos los modelos de Google Cloud, también conocidos como Vertex AI (la plataforma de IA de GCP).

Nuevas cadenas con LangChain Expression Language (LCEL)

En octubre del 2023 se realizó un cambio en LangChain que modifica sustancialmente el modo en el que se usan las cadenas. Además de cambiar la forma en la que se escribe el código, estas nuevas cadenas son más eficientes, permiten ser usadas en stream o batch incluso de manera asíncrona.

Estas nuevas cadenas se reconocen rápidamente al usar el operador | para encadenar elementos dentro de la cadena, un ejemplo de cadena muy sencillo en el que lanzamos un prompt a un LLM sería así:

chain = prompt | llm

Aunque las “viejas” cadenas continúan funcionando, se recomienda el uso de esta nueva sintaxis para los proyectos nuevos. Estos son los principales motivos:

  1. Porque ahora es como trabaja el framework.
  2. Una interface unificada para varios métodos (Stream, batch, asíncronas).
  3. Facilidad de composición (Secuencial, paralelo, añadir fallbacks...).
  4. Código mucho más simple (Menos líneas para el mismo resultado).

Ejemplo 1. Nuestra primera cadena

Para el primer ejemplo, vamos a dar nuestros primeros pasos con las cadenas montando un programa desde cero. El objetivo de nuestro programa será el crear webs estáticas (ficheros html) a partir de un tema que le facilitaremos en la llamada. Vamos a ver paso a paso cómo es el código de nuestro fichero .py.

Primero, como siempre van las importaciones:

from langchain.llms import VertexAI
from langchain.prompts import PromptTemplate
import os
from dotenv import load_dotenv
import uuid

#Cargamos la variables para obtener el project id donde hemos activado el api de vertex
load_dotenv()

Como hemos dicho anteriormente, usaremos el conector de LangChain para poder usar el LLM de Google Cloud de manera sencilla. A la hora de inicializar el LLM le indicamos el modelo a usar, el máximo de tokens de salida, la temperatura, la localización del modelo…

#Inicializamos nuestro llm con los siguientes parámetros
llm = VertexAI(
       model_name="text-bison", #nombre del modelo que usaremos
       max_output_tokens=2000, #número máximo de tokens en la respuesta
       temperature=0.1, #los siguientes parámetros nos dicen como de imaginativo va a ser el modelo
       top_p=0.8,
       top_k=40,
       project=os.getenv("PROJECT_ID"), #obtenemos el id del proyecto por variable
       location="europe-west1", #región del modelo 
       )

A continuación, viene el turno de nuestro prompt a partir de una plantilla y la generación de nuestra primera cadena en la que juntamos el LLM creado en el paso anterior y nuestro prompt. Template → Prompt → Cadena.

template = """Tienes que devolver el código html. Crea una web con esta premisa: {tema}
Código: """
prompt = PromptTemplate.from_template(template)
chain = prompt | llm

Una vez que tenemos lista nuestra cadena, ha llegado el momento de invocarla, pasando en la llamada el “tema” con el que queremos hacer nuestra flamante web.

#Llamando a nuestra primera cadena
tema = "tiene que hablar de piratas y ninjas"
solucion = chain.invoke({"tema": tema})
print(solucion)

Finalizamos esta primera fase guardando el resultado como un fichero html que podamos abrir desde nuestro navegador.

myuuid = uuid.uuid4()
my_web_name = 'webs/' + str(myuuid) + '.html'
with open(my_web_name, 'w') as f:
   f.write(solucion.lstrip().replace("```html","").replace("```",""))

Al ejecutar nuestro código tendremos un nuevo fichero en la carpeta webs con el contenido de nuestra web:

Fichero con el contenido de nuestra web.

No es una gran web, pero es un inicio y no hemos tenido que poner ninguna etiqueta html ni nada parecido, pura magia generativa.

¿Se complica la cosa? No te preocupes y ponlo en modo fácil.
Añadir verbose y modo debug ha sido un truco usado por informáticos durante miles de años, en LangChain también podemos hacer uso de estas herramientas con las siguientes líneas de código:

from langchain.globals import set_debug
from langchain.globals import set_verbose

set_debug(True)
set_verbose(True)

Ejemplo 2. Mejorando gracias las cadenas

En este segundo ejemplo vamos a ver cómo podemos encadenar varias llamadas a los LLMs para dividir las tareas de forma que sean más sencillas de utilizar y obteniendo, además, mejores resultados. Para ello, partiremos del ejemplo anterior de la generación de webs, esta vez vamos a utilizar varias llamadas para obtener un mejor resultado.

Centrándonos en el caso que nos ocupa, podríamos descomponer el problema de hacer una web en tres subtareas:

Viendo la dependencia entre los pasos, la cadena nos quedaría de la siguiente manera:

Resultado de la cadena

Lo primero que haremos será montar las tres cadenas por separado, poniendo para cada cadena, su plantilla, su prompt y su cadena. Recuerda que siempre es bueno usar una nomenclatura sencilla y coherente a la hora de programar:

#Cadena tema

tema_template = """Eres un periodista especializado y se te ha pedido obtener información sobre un tema para una web estática.
Necesitas tener un apartado de introducción al tema y también tres apartados relacionados con el tema principal, separa los apartados con títulos.
El tema sobre el que trata la web es: {tema}
Tema: """
tema_prompt = PromptTemplate.from_template(tema_template)
tema_chain = tema_prompt | llm

#Cadena estructura

estructura_template = """Eres un programador web especializado en HTML. Tienes que hacer una web sencilla pero vistosa.
La web tiene que tener un diseño moderno. La web tiene header, body y footer.
Tienes que devolver el código html.
Crea una web con esta premisa: {tema}
estructura: """
estructura_prompt = PromptTemplate.from_template(estructura_template)
estructura_chain = estructura_prompt | llm

#Cadena html

html_template = """Eres un maquetador web y tienes que juntar la información que te han pasado junto con el código html.
El resultado tiene que ser solo el código html de la web.
Añade los css dentro del html, no como fichero, puedes usar los estilos de material framework.
Añade también las secciones al header.
El contenido: {contenido}
La base: {estructura}

Código: """
html_template = PromptTemplate.from_template(html_template)
chain_html = html_template | llm

Con RunnableParallel podemos montar nuestra primera cadena, la cual es la suma de dos de las cadenas que hemos creado justo ahora, importante ver cómo le indicamos el nombre de donde guardar el resultado de las ejecuciones.

Una vez tenemos nuestra cadena con la ejecución en paralelo, solo nos quedaría unirla con nuestra 3era cadena para tener el resultado completo.

#Ejecutamos en paralelo las primeras dos cadenas y luego la tercera
map_chain = RunnableParallel(contenido=tema_chain, estructura=estructura_chain)

final_chain = map_chain | chain_html

Ya solo nos queda decir cuál va a ser nuestro tema e invocar a nuestra cadena para que suceda la magia.

tema = "El imperio romano"
solucion = final_chain.invoke({"tema": tema})

Por último, tendríamos la parte de generar el fichero html y guardarlo en su sitio.

myuuid = uuid.uuid4()
my_web_name = 'webs/' + str(myuuid) + '.html'
with open(my_web_name, 'w') as f:
   f.write(solucion.lstrip().replace("```html","").replace("```",""))

Vemos que con la nueva versión nuestra web ya es más completa:

Nueva versión web más completa 1
Nueva versión web más completa 2

Ejemplo 3. Uso de tools para enriquecer datos

Las tools son Agentes que nos permiten de manera sencilla interactuar con el mundo exterior. En este ejemplo usaremos la tool que nos ofrece LangChain para obtener fácilmente información de la Wikipedia.

Nuestro objetivo: pedir información a la Wikipedia y luego resumir el contenido que nos ha devuelto la Wikipedia.

Empezamos instalando la librería de Wikipedia para poder realizar llamadas con Python.

!pip install wikipedia

Y la importación de las librerías correspondientes:

from langchain.tools import WikipediaQueryRun
from langchain.utilities import WikipediaAPIWrapper

wikipedia = WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper())

Ahora definimos una cadena que obtiene la entrada y la resume para nosotros usando el LLM de Google Cloud de los ejercicios anteriores:

resumen_template = """
Tienes que resumir este contenido en 100 palabras: {contenido}.
"""
resumen_template = PromptTemplate.from_template(resumen_template)
resumen_cadena = resumen_template | llm

Luego solo tenemos que montar una nueva cadena, añadiendo en el primer paso que vamos a usar la tool para obtener la información:

cadena =  {"contenido": wikipedia} | resumen_cadena
solucion =cadena.invoke("Walt Disney")

Para finalizar

Como hemos visto, gracias a LangChain es mucho más sencillo y productivo generar programas que hagan uso de la IA generativa. Gracias a las cadenas y las tools, vamos a poder dividir y organizar nuestras ejecuciones para que ganen en poder y sean más fáciles de controlar.

En este post hemos visto solo una pequeña parte de este framework, por lo que te recomiendo que si te has quedado con ganas de más corras a ver este webinar de Óscar Ferrer sobre LangChain.

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete

Estamos comprometidos.

Tecnología, personas e impacto positivo.