Tabla de Contenidos

Chains

Chain simple

Un chain o “cadena” es el componente básico de langchain y pueden verse como “eslabones” de una cadena, en el que cada uno de ellos tiene una funcionalidad.

Por ejemplo, el chain más básico entrega a un modelo una pregunta (pueden usarse plantillas de prompts) y éste devuelve una respuesta.

Pueden combinarse diferentes, de forma que la salida de uno sea la entrada del siguiente y de este modo crear flujos más o menos complejos.

A continuación un ejemplo de un chain simple.

# Importamos dependencias
# Cuando no son plantillas estáticas, si no que pueden variar como resultado de una 
# conversación de chat, usamos 'ChatPromptTemplate', en lugar de 'PromptTemplate'
from langchain_ollama import ChatOllama
from langchain.prompts import ChatPromptTemplate 
from langchain.chains.llm import LLMChain
 
# Definimos el modelo con el que trabajaremos
modelo = ChatOllama(
    model = "llama3.2:1b",
    temperature = 0.9,
    verbose = False
)
 
#Definimos el prompt
prompt = """Escribe SÓLO UN título para una novela \
    del género: {genero}."""
 
plantilla_prompt = ChatPromptTemplate.from_template(prompt)
 
# Definimos el "chain" o el eslabón de cadena, a la que pasamos
cadena = LLMChain(llm=modelo, prompt=plantilla_prompt)    # Chain básico: pasamos una pregunta al modelo y éste contesta
 
# Ya sólo nos queda definir los datos de entrada y "correr" el preograma
genero = "terror"
titulo = cadena.run(genero)
print(titulo)

Chain compuesto

La unión de diferentes chains, puede dar lugar a flujos, de modo que la salida de uno sea la entrada de otro.
A continuación vemos un ejemplo de hasta 4 chains seguidos.

# A partir de la transcripción de una reunión entre 3 profesores que pasamos en un archivo txt, realizamos varias acciones:
#  1. Chain 1: Resumimos el contenido de la transcripción.
#  2. Chain 2: Lo pasamos a inglés.
#  3. Chain 3: El director responde en inglés a los profesores que mantuvieron la reunión.
#  4. Chain 4: Se traduce a español la respuesta del director.
 
# En primer lugar cargamos las dependencias
from langchain_ollama import ChatOllama
from langchain.prompts import ChatPromptTemplate 
from langchain.chains.llm import LLMChain
from langchain.chains.sequential import SequentialChain
import pprint
 
# Definimos el modelo con el que trabajaremos
modelo = ChatOllama(
    model = "llama3.2",
    temperature = 0.9,
    verbose = False
)
 
# La transcripción se puede poner aquí, aunque también se podría poner al final si el código se va a reutilizar
#with open("transcripcion_reunion.txt", 'r') as transcripcion:
#    reunion = transcripcion.read()
 
 
# Primera plantilla de prompt: Crea un resumen a partir de la transcripción de la reunión
prompt1 = ChatPromptTemplate.from_template(
    "Crea un resumen de la reunión mantenida por tres profesores, \
        de la cual tenemos la siguiente transcripción: "
    "\n\n{reunion}"
)
 
# Eslabón 1: input = transcripcion de la reunión, output = acta
eslabon1 = LLMChain(llm=modelo, prompt=prompt1, output_key="resumen_es")
 
# Segundo prompt: Traducción del resumen de la reunión del español al inglés
prompt2 = ChatPromptTemplate.from_template(
    "Traduce el siguiente texto al inglés: "
    "\n\n{resumen_es}"
)
 
# Segundo eslabón: input = resumen en español, output = resumen en inglés
eslabon2 = LLMChain(llm=modelo, prompt=prompt2, output_key="resumen_en")
 
# tercer prompt: Respuesta del director en inglés
prompt3 = ChatPromptTemplate.from_template(
    "El director del instituto, que habla inglés, lee el resumen de la reunión mantenida por los profesores: "
    "\n\n{resumen_en}\n"
    "Escribe la respuesta, en inglés, del director a los profesores explicando si le parece bien o no el cambio."
)
 
# tercer eslabón: input = resumen en inglés, output = respuesta en inglés
eslabon3 = LLMChain(llm=modelo, prompt=prompt3, output_key="respuesta_en")
 
# cuarto prompt: Traducción de la respuesta del inglés al español
prompt4 = ChatPromptTemplate.from_template(
    "Traduce el siguiente texto del inglés a español: "
    "\n\n{respuesta_en}"
)
 
# cuarto eslabón: input = acta en español, output = acta en inglés
eslabon4 = LLMChain(llm=modelo, prompt=prompt4, output_key="respuesta_es")
 
 
# Creamos el flujo, esto es la cadena total uniendo todos los eslabones
cadena = SequentialChain(
    chains = [eslabon1, eslabon2, eslabon3, eslabon4],
    input_variables=["reunion"],
    output_variables=["resumen_es", "resumen_en", "respuesta_en", "respuesta_es"],
    verbose=True
)
 
# Obtenemos la transcripción de la reunión
with open("transcripcion_reunion.txt", 'r') as transcripcion:
    reunion = transcripcion.read()
 
# Ejecutamos el flujo, pasando al flujo creado la entrada (trnacripción), y obtenemos todas las salidas intermedias y final
contestacion = cadena(reunion)
pprint.pprint(contestacion)

Cadena ruteada

En este caso la cadena tendrá diferentes ramas en paralelo y el sistema deberá rutear el flujo por una o por otra, en función del contexto, según los datos de entrada.

Vamos a ver un ejemplo de cadena con ruteo. Para construir el flujo, seguimos los sigientes pasos:

  1. Creamos un diccionario en que cada elemento tiene el nombre del prompt correspondiente a una de las ramas en paralelo, y el contenido es un chain básico que responderá a la entrada.
  2. Definimos un chain básico “por defecto”, por si la entrada no se ajusta a ninguna de las “ramas” que especificaremos, la entrada sea procesada por él.
  3. Definimos una “plantilla multiprompt”, en la que además de la entrada, explicamos el contexto e introducimos una lista de los prompts candidatos. Según este contexto, el modelo correspondiente deberá qué porompt candidato elegir (diccionario creado anteriormente). Importante: debe devolver la respuesta en JSON en el que se indica el nombre.
  4. Creamos el prompt de ruteo a partir de la plantilla anterior, especificamos la entrada (en nuestro caso será “input”) y especificamos el parser de salida, que tendrá que ser JSON.
  5. Creamos el “router chain” especificando el modelo que se usará para ejecutar el prompt anterior.
  6. Creamos el flujo final, que es un “Multiprompt chain” en el que especificamos: El “router chain” anterior para decidir qué “cadena” ejecutar, el diccionario con los diferentes chains básicos a ejecutar cuyo nombre debe corresponderse con los del JSON que genera el “router chain” y el “chain” básico por defecto.
  7. Ejecutamos el flujo anterior, que debe devolver una respuesta en la que podemos ver los pasos intermedios.
# Como ejemplo, tendremos como entrada una consulta a una empresa de 
# informática y electrónica que se ocupa tanto de servicio técnico 
# como de venta de artículos. El sistema deberá pasar la consulta 
# al departamento técnico o comercial, según proceda
 
from langchain_ollama import ChatOllama
from langchain.prompts import ChatPromptTemplate
from langchain.prompts import PromptTemplate
from langchain.chains.llm import LLMChain
from langchain.chains.router import MultiPromptChain
from langchain.chains.router.llm_router import LLMRouterChain, RouterOutputParser
import pprint
 
# Definimos el modelo con el que trabajaremos
# En este caso una temperatura alta nos entorpece la salida, que debe de ser
# un JSON. Al tener una temperatura alta, el modelo se vuelve "parlanchin" y
# añade texto explicativo innecesario que genera un error
modelo = ChatOllama(
    model = "llama3.2",
    temperature = 0.0,
    verbose = False
)
 
# Definimos las plantillas de los distintos departamentos
plantilla_comercial = """Eres un simpático comercial, comprensivo, empático \
y divertido. Estás encantado de responder a las consultas que te formulan \
los clientes, siempre encuentras una salida a las preguntas difíciles y \
sueles conseguir vender lo que te propones. 
 
Responde a la siguiente consulta de un cliente: 
{input}"""
 
plantilla_tecnico = """Eres un técnico informático con conocimientos avanzados \
de electrónica. Eres serio, conciso, y que le gusta ir al grano. \
Respondes a las cuestiones técnicas que te plantean los clientes de forma profesional, \
y precisa. Sabes adaptar las explicaciones cuando el cliente parace tener pocos \
conocimientos técnicos.
 
Responde a la siguiente consulta de un cliente: 
{input}"""
 
 
# Definimos una lista con información de los departamentos
info_departamentos = [
    {
        "nombre": "comercial",
        "descripcion": "Bueno para contestar a preguntas acerca de cuestiones comerciales",
        "plantilla": plantilla_comercial
    },
    {
        "nombre": "tecnico",
        "descripcion": "Bueno para contestar a preguntas técnicas de usuarios sin conocimientos de informática o electrónica",
        "plantilla": plantilla_tecnico
    }
]
 
# 1. Ahora definimos la variable "ramal" que será un diccionario en el que cada elemento se corresponde 
# con uno de los posibles ramales de ruteo. Cada elemento tendrá como nombre el  especificado en 
# info_departamentos, y su contenido será un chain de tipo LLMChain con la plantilla correspondiente 
# y el modelo de IA a usar
ramales = {}
for info_dpto in info_departamentos:
    nombre = info_dpto["nombre"]
    plantilla = info_dpto["plantilla"]
    prompt = ChatPromptTemplate.from_template(template=plantilla)
    eslabon = LLMChain(llm=modelo, prompt=prompt)  # El objetivo del bucle es crear este chain y ...
    ramales[nombre] = eslabon  # ... meterlo en una lista donde cada elemento son los posibles chains en paralelo
 
# destinos: lista (también otra variable destinos_str tipo cadena de caracteres) que tiene en cada 
# elemento cadena "nombre: descripción" de cada departamento
destinos = [f"{p['nombre']}:{p['descripcion']}" for p in info_departamentos]
destinos_str = "\n".join(destinos)
 
#pprint.pprint(destinos)
#pprint.pprint(destinos_str)
 
# 2. Definimos un prompt "por defecto" para usar si el sistema no identifica la consulta con ninguno
# de los dos departamentos
default_prompt = ChatPromptTemplate.from_template("{input}")
default_chain = LLMChain(llm=modelo, prompt=default_prompt)
 
# 3. Escribimos el prompt que seleccionará el ruteo (ramal) correspondiente
plantilla_multiprompt_ruteo = """Dado un texto sin procesar correspondiente a una consulta \
para una empresa de informática y electrónica, selecciona la opción que mejor se adapte a \
la naturaleza de dicha consulta. Se proporcionarán los nombres \
de las opciones disponibles y una descripción para discernir cuál es la más adecuada. \
También puedes revisar la entrada original si crees que al revisarla, se obtendrá \
una mejor respuesta del modelo.
 
<<FORMATO>>
Devuelve SÓLO y ÚNICAMENTE un código en formato JSON de la siguiente manera:
'''json
{{{{
    "destination": string \ nombre del mensaje a utilizar o "DEFAULT"
    "next_inputs": string \ una versión potencialmente modificada de la entrada original
}}}}
'''
 
RECUERDA: "destination" DEBE ser uno de los nombres candidatos especificados a continuación \
O puede ser "DEFAULT" si el mensaje no es adecuado para ninguno de los mensajes candidatos.
 
RECUERDA: "next_inputs" puede ser simplemente el mensaje original si no crees \
que se necesiten modificaciones.
 
RECUERDA: Devuelve SÓLO y ÚNICAMENTE el output en formato JSON
 
<<PROMPTS CANDIDATOS>>
{destinos}
 
<<INPUT>>
{{input}}
 
<<OUTPUT>>
"""
 
# Sustituimos en la plantilla multiprompt, {destinos} por la cadena de destinos
plantilla_ruteo = plantilla_multiprompt_ruteo.format(destinos=destinos_str)
 
# 4. Creamos el prompt de ruteo a partir de las plantilla multiprompt
prompt_ruteo = PromptTemplate(
    template=plantilla_ruteo,
    input_variables=["input"],
    output_parser=RouterOutputParser(),
)
 
# 5. Creamos el "chain" de tipo "router chain", especificando modelo y prompt que define el ruteo
cadena_ruteo = LLMRouterChain.from_llm(modelo,prompt_ruteo)
 
# 6. Creamos el flujo completo definiendo los posibles destinos
cadena_consulta = MultiPromptChain(router_chain=cadena_ruteo,
                          destination_chains=ramales,
                          default_chain=default_chain,
                          verbose=True
                          )
# 7. Ejecutamos
#respuesta = cadena_consulta.run("Buenos días, tras cambiar algunos componentes del PC, no arranca y emite un pitido intermitente ¿Qué puedo hacer?")
#respuesta = cadena_consulta.run("Buenas tardes, quiero saber qué ordenador necesito para trabajar. Sobre todo, mirar el correo, manejar programas ofimáticos y navegar port internet ¿Cuánto puede costar?")
respuesta = cadena_consulta.run("Buenos días, me pongo en contacto con ustedes desde un distribuidor de componentes electrónicos. Nos gustaría concertar una cita para enseñarles nuestro catálogo y poder hacer negocios juntos.")
 
pprint.pprint(respuesta)