TL;DR
LangChain se ha convertido en uno de los más utilizados Biblioteca Python para interactuar con LLMs en menos de un año, pero LangChain era sobre todo una biblioteca para POCs ya que carecía de la capacidad de crear aplicaciones complejas y escalables.
Todo cambió en agosto de 2023 cuando lanzaron Lenguaje de expresión LangChain (LCEL), una nueva sintaxis que salva las distancias entre De POC a producción. Este artículo le guiará a través de los entresijos de LCEL, mostrándole cómo simplifica la creación de cadenas personalizadas y por qué debe aprenderlo si está construyendo Solicitudes LLM!
Prompts, LLM y cadenas, refresquemos la memoria
Antes de sumergirnos en la sintaxis LCEL, creo que es beneficioso refrescar nuestra memoria sobre conceptos de LangChain como LLM y Prompt o incluso una Cadena.
LLM: En langchain, llm es una abstracción en torno al modelo utilizado para realizar las terminaciones como openai gpt3.5, claude, etc...
Pregunte a: Es la entrada del objeto LLM, que formulará las preguntas LLM y dará sus objetivos.
Cadena: Se refiere a una secuencia de llamadas a un LLM, o a cualquier paso de procesamiento del data.

Ahora que las definiciones están fuera del camino, ¡supongamos que queremos crear una empresa! Necesitamos un nombre realmente atractivo y pegadizo, ¡y un modelo de negocio para ganar dinero!
Ejemplo - Nombre de empresa y modelo de negocio con cadenas antiguas
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain_community.llms import OpenAI
USER_INPUT = "calcetines de colores"
llm = OpenAI(temperatura=0)
prompt_template_product = "¿Cuál es un buen nombre para una empresa que fabrica?"
nombre_empresa_cadena = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_product))
nombre_empresa_salida = nombre_empresa_cadena(USER_INPUT)
prompt_template_business = "Deme la mejor idea de modelo de negocio para mi empresa llamada: "
cadena_modelo_empresa = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_empresa))
business_model_output = business_model_chain(nombre_empresa_output["texto"])
print(nombre_empresa_salida)
print(salida_modelo_de_negocio)
>>>
>>>
Esto es bastante fácil de seguir, podemos ver un poco de redundancia, pero es manejable.
Añadamos algo de personalización gestionando los casos en los que el usuario no está utilizando nuestra cadena como se esperaba.
¿Puede que 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
importar ast
USER_INPUT = "Harrison Chase"
llm = OpenAI(temperatura=0)
# -- El mismo código que antes
prompt_template_product = "¿Cuál es un buen nombre para una empresa que fabrica?"
nombre_empresa_cadena = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_product))
prompt_template_business = "Deme la mejor idea de modelo de negocio para mi empresa llamada: "
cadena_modelo_empresa = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_empresa))
# -- Nuevo código
prompt_template_is_product = (
"Su objetivo es encontrar si la entrada del usuario es un nombre de producto plausible"
"Las preguntas, los saludos, las frases largas, las celebridades u otras entradas no relevantes no se consideran productosn"
"entrada: n"
"Responda sólo con 'Verdadero' o 'Falso' y nada másen"
)
prompt_template_cannot_respond = (
"No puede responder a la entrada del usuario: n"
"Pida al usuario que introduzca el nombre de un producto para que usted cree una empresa a partir de él.n"
)
cadena_no_puede_responder = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_no_puede_responder))
nombre_empresa_cadena = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_product))
cadena_modelo_empresa = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_empresa))
is_a_product_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_is_product))
# Si usamos bool en una str no vacía será True, por lo que necesitamos `literal_eval`.
is_a_product = ast.literal_eval(is_a_product_chain(USER_INPUT)["text"])
si es_un_producto:
nombre_empresa_salida = nombre_empresa_cadena(USER_INPUT)
business_model_output = business_model_chain(nombre_empresa_output["texto"])
print(salida_modelo_de_negocio)
si no:
print(cadena_no_puede_responder(USER_INPUT))
Esto se hace un poco más difícil de entender, resumámoslo:
Comienzan a surgir múltiples problemas:
¿Qué es el lenguaje de expresión LangChain (LCEL)?
LCEL es una interfaz unificada y una sintaxis para escribir cadenas listas para la producción componibles, hay mucho que desentrañar para entender lo que significa.
Primero intentaremos comprender la nueva sintaxis reescribiendo la cadena de antes.
Ejemplo - Nombre de la empresa 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(temperatura=0)
prompt_template_product = "¿Cuál es un buen nombre para una empresa que fabrica?"
prompt_template_business = "Deme la mejor idea de modelo de negocio para mi empresa llamada: "
cadena = (
PromptTemplate.from_template(prompt_template_product)
| llm
|
| PromptTemplate.from_template(prompt_template_business)
| llm
)
business_model_output = chain.invoke()
Una gran cantidad de código inusual, en tan sólo unas pocas líneas :
Pero, ¿es realmente más componible crear cadenas de esta forma?
Pongámoslo a prueba añadiendo la función is_a_product_chain() y la ramificación si la entrada del usuario no es correcta. Incluso podemos escribir la cadena con Python Typing, hagámoslo como una 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(temperatura=0)
prompt_template_product = "¿Cuál es un buen nombre para una empresa que fabrica?"
prompt_template_cannot_respond = (
"No puede responder a la entrada del usuario: n"
"Pida al usuario que introduzca el nombre de un producto para que usted cree una empresa a partir de él.n"
)
prompt_template_business = "Deme la mejor idea de modelo de negocio para mi empresa llamada: "
prompt_template_is_product = (
"Su objetivo es encontrar si la entrada del usuario es un nombre de producto plausible"
"Las preguntas, los saludos, las frases largas, las celebridades u otras entradas no relevantes no se consideran productosn"
"entrada: n"
"Responda sólo con 'Verdadero' o 'Falso' y nada másen"
)
cadena_usuario_respuesta = (
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_cadena_producto = (
PromptTemplate.from_template(prompt_template_is_product)
| llm
| BooleanOutputParser(true_val='Verdadero', false_val='Falso')
).with_types(input_type=Dict[str, str], output_type=bool)
cadena_no_puede_responder = (
PromptTemplate.from_template(prompt_template_cannot_respond) | llm
).with_types(input_type=Dict[str, str], output_type=str)
cadena_completa = Rama_ejecutable(
(cadena_producto, cadena_usuario_respuesta),
cadena_no_puede_responder
).with_types(input_type=Dict[str, str], output_type=str)
print(cadena_completa.invocar())
Enumeremos las diferencias:
¿Por qué LCEL es mejor para la industrialización?
Si estuviera leyendo este artículo hasta este punto exacto, y alguien me preguntara si me convence LCEL, probablemente diría que no. La sintaxis es demasiado diferente, y probablemente pueda organizar mi código en funciones para obtener casi exactamente el mismo código. Pero estoy aquí, escribiendo este artículo, así que debe haber algo más.
Fuera de la caja invocar, flujo y lote
Al utilizar LCEL su cadena dispone automáticamente de:
mi_cadena = prompt | llm
# ---invoke--- #
resultado_con_invocar = mi_cadena.invocar(“¡hola mundo!”)
# ---lote--- #
resultado_con_lote = mi_cadena.lote([“hola”, “mundo”, “!”])
# ---stream--- #
for chunk in mi_cadena.stream(“¡hola mundo!”):
print(chunk, flush=Verdadero, end=””)
Cuando itere, puede utilizar el método invocar para facilitar el proceso de desarrollo. Pero cuando muestre la salida de su cadena en una interfaz de usuario, querrá transmitir la respuesta. Ahora puede utilizar el método stream sin reescribir nada.
Métodos asíncronos fuera de la caja
La mayoría de las veces, el frontend y el backend de su aplicación estarán separados, lo que significa que el frontend hará una petición al backend. Si tiene varios usuarios, puede que necesite gestionar varias peticiones en su backend al mismo tiempo.
Dado que la mayor parte del código en LangChain es simplemente esperar entre llamadas a la API, podemos aprovechar el código asíncrono para mejorar la escalabilidad de la API, si quiere entender por qué es importante le recomiendo que lea el documento hamburguesas concurrentes historia de la documentación FastAPI.
No hay necesidad de preocuparse por la implementación, porque los métodos asíncronos ya están disponibles si utiliza LCEL:
.ainvoke() / .abatch() / .astream: versiones asíncronas de invocar, lote y flujo.
También recomiendo leer el Por qué utilizar la página LCEL de la documentación LangChain con ejemplos para cada método sync / async.
Langchain consiguió esas características “listas para usar” creando una interfaz unificada llamada “Ejecutable”. Ahora, para aprovechar plenamente LCEL, tenemos que sumergirnos en lo que es esta nueva interfaz Runnable.
La interfaz Runnable
Todos los objetos que hemos utilizado en la sintaxis LCEL hasta ahora son Runnables. Se trata de un Objeto python creado por LangChain, este objeto hereda automáticamente todas las características de las que hemos hablado antes y muchas más. Al utilizar la sintaxis LCEL, componemos un nuevo Runnable en cada paso, lo que significa que el objeto final creado será también un Runnable. Puede obtener más información sobre la interfaz en la documentación oficial.
Todos los objetos del código siguiente son Runnables o diccionarios que se convierten automáticamente en un Runnable :
from langchain_core.runnables import RunnablePassthrough, RunnableParallel
from langchain.prompts import PromptTemplate
from langchain_community.llms import OpenAI
número_de_cadena_uno = (
PromptTemplate.from_template(prompt_template_product)
| llm
| # <- ESTO CAMBIARÁ
| PromptTemplate.from_template(prompt_template_business)
| llm
)
número_de_cadena_dos = (
PromptTemplate.from_template(prompt_template_product)
| llm
| RunnableParallel(empresa=RunnablePassthrough()) # <- ESTO CAMBIÓ
| PromptTemplate.from_template(prompt_template_business)
| llm
)
print(número_de_cadena_uno == número_de_cadena_dos)
>>> Verdadero
¿Por qué utilizamos RunnableParallel() y no simplemente Runnable() ?
Porque, cada Runnable dentro de un RunnableParallel se ejecuta en paralelo. Esto significa que si tiene 3 pasos independientes en su Runnable, se ejecutarán al mismo tiempo en diferentes hilos de su máquina, ¡mejorando la velocidad de su cadena de forma gratuita!
Inconvenientes de LCEL
A pesar de sus ventajas, LCEL presenta algunos inconvenientes potenciales:
Conclusión
En conclusión, LangChain Expression Language (LCEL) es una potente herramienta que aporta una nueva perspectiva a la creación de aplicaciones Python. A pesar de su sintaxis poco convencional, recomiendo encarecidamente el uso de LCEL por las siguientes razones :
Para ir más lejos...
La abstracción ejecutable
En algunos casos, creo que es importante comprender la abstracción que LangChain ha implementado para hacer funcionar la sintaxis LCEL.
Puede reimplementar las funcionalidades básicas de Runnable fácilmente de la siguiente manera:
clase Runnable:
def __init__(self, func):
self.func = func
def __or__(self, other):
def chained_func(*args, **kwargs):
# self.func está a la izquierda, other está a la derecha
return other(self.func(*args, **kwargs))
return Runnable(función_encadenada)
def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs)
def sumar_diez(x):
devolver x + 10
def dividir_entre_dos(x):
devolver x / 2
runnable_add_ten = Runnable(add_ten)
runnable_divide_by_two = Runnable(divide_by_two)
cadena = runnable_add_ten | runnable_divide_by_two
resultado = cadena(8) # (8+10) / 2 = 9,0 debería ser la respuesta
print(resultado)
>>> 9.0
Un Runnable es simplemente un objeto python en el que se ha sobrescrito el método .__or__().
En la práctica, LangChain ha añadido un montón de funcionalidades como la conversión de diccionarios a Runnable, capacidades de tipado, capacidades de configuración, ¡y métodos invoke, batch, stream y async!
Así que, ¿por qué no prueba LCEL en su próximo proyecto?
Si quiere saber más, le recomiendo encarecidamente que eche un vistazo a Libro de cocina LangChain en LCEL.

BLOG






