En resumen
LangChain se ha convertido en menos de un año en una de las bibliotecas de Python más utilizadas para interactuar con los modelos de lenguaje grande (LLM), pero, en su mayoría, se trataba de una biblioteca destinada a pruebas de concepto (POC), ya que carecía de la capacidad para crear aplicaciones complejas y escalables.
Todo cambió en agosto de 2023, cuando lanzaron LangChain Expression Language (LCEL), una nueva sintaxis que salva la brecha entre las pruebas de concepto y la producción. Este artículo te guiará a través de los entresijos de LCEL, mostrándote cómo simplifica la creación de cadenas personalizadas y por qué debes aprenderlo si estás desarrollando aplicaciones de LLM.
Prompts, modelos de lenguaje grande (LLM) y cadenas: refresquemos la memoria
Antes de profundizar en la sintaxis de LCEL, creo que conviene refrescar la memoria sobre conceptos de LangChain como LLM, «prompt» o incluso «chain».
LLM: En LangChain, «llm» es una abstracción del modelo utilizado para generar las completaciones, como OpenAI GPT-3.5, Claude, etc.
Indicación: Esta es la entrada del objeto LLM, que formulará preguntas al LLM y le indicará sus objetivos.
Cadena: Se refiere a una secuencia de llamadas a un modelo de lenguaje grande (LLM) o a cualquier paso data .

Ahora que ya hemos aclarado las definiciones, ¡supongamos que queremos crear una Compañia! ¡Necesitamos un nombre realmente genial y pegadizo, y un modelo de negocio para ganar dinero!
Ejemplo: Compañia y modelo de negocio con cadenas tradicionales
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain_community.llms import OpenAI
USER_INPUT = "calcetines de colores"
llm = OpenAI(temperature=0)
prompt_template_product = "¿Qué nombre sería adecuado para una Compañia se dedica a?"
Compañia= LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_product))
Compañia= Compañia(USER_INPUT)
prompt_template_business = "Dame la mejor idea de modelo de negocio para mi Compañia : "
business_model_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_business))
business_model_output = business_model_chain(Compañia["text"])
print(Compañia)
print(business_model_output)
>>>
>>>
Es bastante fácil de seguir; se aprecia cierta redundancia, pero es manejable.
Añadamos un poco de personalización para gestionar los casos en los que el usuario no utilice nuestra cadena como se espera.
¿Quizás el usuario introduzca algo que no tenga nada que ver con el objetivo de nuestra cadena? En ese caso, queremos detectarlo y responder adecuadamente.
Ejemplo: personalización y enrutamiento con cadenas antiguas
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain_community.llms import OpenAI
import ast
USER_INPUT = "Harrison Chase"
llm = OpenAI(temperature=0)
# —- El mismo código que antes
prompt_template_product = "¿Qué nombre le pondrías a una Compañia se dedica a ?"
Compañia= LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_product))
prompt_template_business = "Dame la mejor idea de modelo de negocio para mi Compañia : "
business_model_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_business))
# —- Código nuevo
prompt_template_is_product = (
"Tu objetivo es determinar si la entrada del usuario es un nombre de producto plausible"
"Las preguntas, los saludos, las frases largas, los nombres de famosos u otras entradas no relevantes no se consideran nombres de producto"
"Entrada: n"
"Responde solo con 'Verdadero' o 'Falso' y nada más"
)
prompt_template_cannot_respond = (
"No puedes responder a la entrada del usuario: n"
"Pide al usuario que introduzca el nombre de un producto para que puedas crear una Compañia de él.n"
)
cannot_respond_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_cannot_respond))
Compañia= LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_product))
business_model_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_business))
is_a_product_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_is_product))
# Si utilizamos «bool» con una cadena no vacía, el resultado será «True», por lo que necesitamos «literal_eval»
is_a_product = ast.literal_eval(is_a_product_chain(USER_INPUT)["text"])
if is_a_product:
Compañia= Compañia(USER_INPUT)
business_model_output = business_model_chain(Compañia["text"])
print(business_model_output)
else:
print(cannot_respond_chain(USER_INPUT))
Esto se vuelve un poco más difícil de entender, así que resumamos:
Empiezan a surgir varios problemas:
¿Qué es el lenguaje de expresiones LangChain (LCEL)?
LCEL es una interfaz y una sintaxis unificadas para escribir cadenas modulables y listas para su implementación; hay mucho que analizar para comprender lo que esto significa.
En primer lugar, intentaremos comprender la nueva sintaxis reescribiendo la cadena anterior.
Ejemplo: Compañia y modelo de negocio con LCEL
from langchain_core.runnables import RunnablePassthrough
from langchain.prompts import PromptTemplate
from langchain_community.llms import OpenAI
USER_INPUT = "calcetines de colores"
llm = OpenAI(temperature=0)
prompt_template_product = "¿Qué nombre le pondrías a una Compañia ?"
prompt_template_business = "Dame la mejor idea de modelo de negocio para mi Compañia : "
chain = (
PromptTemplate.from_template(prompt_template_product)
| llm
|
| PromptTemplate.from_template(prompt_template_business)
| llm
)
business_model_output = chain.invoke()
Mucho código inusual, en solo unas pocas líneas:
¿Pero es realmente más fácil de combinar crear cadenas de esta manera?
Pongámoslo a prueba añadiendo la función `is_a_product_chain()` y la ramificación en caso de que la entrada del usuario no sea correcta. Incluso podemos definir el tipo de la cadena con Python Typing; hagámoslo como buena práctica.
Ejemplo: personalización y enrutamiento con LCEL
from typing import Dict
from langchain_core.runnables import RunnablePassthrough, RunnableBranch
from langchain.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain.output_parsers import BooleanOutputParser
from langchain_community.llms import OpenAI
USER_INPUT = "Harrrison Chase"
llm = OpenAI(temperature=0)
prompt_template_product = "¿Qué nombre le pondrías a una Compañia fabrica ?"
prompt_template_cannot_respond = (
"No puedes responder a la entrada del usuario: n"
"Pide al usuario que introduzca el nombre de un producto para que puedas crear una Compañia de él.n"
)
prompt_template_business = "Dame la mejor idea de modelo de negocio para mi Compañia : "
prompt_template_is_product = (
"Tu objetivo es determinar si la entrada del usuario es un nombre de producto plausible"
"Las preguntas, los saludos, las frases largas, los nombres de famosos u otras entradas no relevantes no se consideran nombres de productos"
"entrada: n"
"Responde solo con 'Verdadero' o 'Falso' y nada más"
)
answer_user_chain = (
PromptTemplate.from_template(prompt_template_product)
| llm
|
| PromptTemplate.from_template(prompt_template_business)
| llm
).with_types(input_type=Dict[str, str], output_type=str)
is_product_chain = (
PromptTemplate.from_template(prompt_template_is_product)
| llm
| BooleanOutputParser(true_val='True', false_val='False')
).with_types(input_type=Dict[str, str], output_type=bool)
cannot_respond_chain = (
PromptTemplate.from_template(prompt_template_cannot_respond) | llm
).with_types(input_type=Dict[str, str], output_type=str)
full_chain = RunnableBranch(
(is_product_chain, answer_user_chain),
cannot_respond_chain
).with_types(input_type=Dict[str, str], output_type=str)
print(full_chain.invoke())
Enumeremos las diferencias:
¿Por qué es LCEL más adecuado para la industrialización?
Si hubiera leído este artículo hasta este preciso momento y alguien me preguntara si me ha convencido LCEL, probablemente diría que no. La sintaxis es demasiado diferente, y seguramente podría organizar mi código en funciones para obtener prácticamente el mismo resultado. Pero aquí estoy, escribiendo este artículo, así que debe de haber algo más.
Invocación, transmisión y procesamiento por lotes listos para usar
Al utilizar LCEL, su cadena cuenta automáticamente con:
my_chain = prompt | llm
# ———invoke——— #
result_with_invoke = my_chain.invoke(«hello world!»)
# ———lote——— #
result_with_batch = my_chain.batch([“hola”, “mundo”, “!”])
# ———stream——— #
for chunk in my_chain.stream(“hello world!”):
print(chunk, flush=True, end=””)
Al realizar iteraciones, puedes utilizar el método `invoke` para facilitar el proceso de desarrollo. Sin embargo, cuando se muestra el resultado de la cadena en una interfaz de usuario, es preferible transmitir la respuesta. Ahora puedes utilizar el método `stream` sin necesidad de reescribir nada.
Métodos asíncronos integrados
En la mayoría de los casos, el frontend y el backend de tu aplicación estarán separados, lo que significa que el frontend enviará una solicitud al backend. Si tienes varios usuarios, es posible que tengas que gestionar varias solicitudes en tu backend al mismo tiempo.
Dado que la mayor parte del código de LangChain consiste simplemente en esperar entre llamadas a la API, podemos aprovechar el código asíncrono para mejorar la escalabilidad de la API; si quieres entender por qué es importante, te recomiendo leer la historia de las «hamburguesas concurrentes» de la documentación de FastAPI.
No hay por qué preocuparse por la implementación, ya que los métodos asíncronos ya están disponibles si utilizas LCEL:
.ainvoke() / .abatch() / .astream: versiones asíncronas de invoke, batch y stream.
También recomiendo leer la página «¿Por qué usar LCEL?» de la documentación de LangChain, que incluye ejemplos para cada método síncrono y asíncrono.
Langchain logró esas funciones «listas para usar» creando una interfaz unificada denominada «Runnable». Ahora, para sacar el máximo partido a LCEL, debemos profundizar en qué consiste esta nueva interfaz Runnable.
La interfaz Runnable
Todos los objetos que hemos utilizado hasta ahora en la sintaxis LCEL son Runnables. Se trata de un objeto de Python creado por LangChain, que hereda automáticamente todas las características de las que hemos hablado anteriormente y muchas más. Al utilizar la sintaxis LCEL, creamos un nuevo Runnable en cada paso, lo que significa que el objeto final creado también será un Runnable. Puedes obtener más información sobre la interfaz en la documentación oficial.
Todos los objetos del código siguiente son de tipo «Runnable» o diccionarios que se convierten automáticamente en «Runnable»:
from langchain_core.runnables import RunnablePassthrough, RunnableParallel
from langchain.prompts import PromptTemplate
from langchain_community.llms import OpenAI
chain_number_one = (
PromptTemplate.from_template(prompt_template_product)
| llm
| # <— THIS WILL CHANGE
| PromptTemplate.from_template(prompt_template_business)
| llm
)
chain_number_two = (
PromptTemplate.from_template(prompt_template_product)
| llm
| RunnableParallel(company=RunnablePassthrough()) # <— THIS CHANGED
| PromptTemplate.from_template(prompt_template_business)
| llm
)
print(cadena_número_uno == cadena_número_dos)
>>> True
¿Por qué utilizamos RunnableParallel() y no simplemente Runnable()?
Esto se debe a que todos los Runnable dentro de un RunnableParallel se ejecutan en paralelo. Esto significa que, si tu Runnable contiene tres pasos independientes, estos se ejecutarán al mismo tiempo en diferentes subprocesos de tu máquina, ¡lo que mejora la velocidad de tu cadena sin ningún esfuerzo adicional!
Inconvenientes del LCEL
A pesar de sus ventajas, el LCEL presenta algunos posibles inconvenientes:
Conclusión
En conclusión, el lenguaje de expresiones LangChain (LCEL) es una potente herramienta que aporta una nueva perspectiva al desarrollo de aplicaciones en Python. A pesar de su sintaxis poco convencional, recomiendo encarecidamente utilizar LCEL por las siguientes razones:
Para profundizar…
La abstracción ejecutable
En algunos casos, creo que es importante comprender la abstracción que LangChain ha implementado para que la sintaxis LCEL funcione.
Puedes reimplementar fácilmente las funcionalidades básicas de `Runnable` de la siguiente manera:
class Runnable:
def __init__(self, func):
self.func = func
def __or__(self, other):
def chained_func(*args, **kwargs):
# self.func va a la izquierda, other va a la derecha
return other(self.func(*args, **kwargs))
return Runnable(chained_func)
def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs)
def add_ten(x):
return x + 10
def dividir_por_dos(x):
return x / 2
runnable_add_ten = Runnable(add_ten)
runnable_divide_by_two = Runnable(divide_by_two)
chain = runnable_add_ten | runnable_divide_by_two
result = chain(8) # (8+10) / 2 = 9.0 debería ser la respuesta
print(result)
>>> 9.0
Un Runnable es simplemente un objeto de Python en el que se ha sobrescrito el método .__or__().
En la práctica, LangChain ha añadido muchas funcionalidades, como la conversión de diccionarios a Runnable, capacidades de tipificación, capacidades de configuración y métodos de invocación, procesamiento por lotes, streaming y asíncronos.
Entonces, ¿por qué no pruebas LCEL en tu próximo proyecto?
Si quieres saber más, te recomiendo encarecidamente que eches un vistazo al libro de recetas de LangChain en LCEL.

BLOG






