====== 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:
- 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.
- 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.
- 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. \\
- 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.
- Creamos el "router chain" especificando el modelo que se usará para ejecutar el prompt anterior.
- 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.
- 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.
<>
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
<>
{destinos}
<>
{{input}}
<