1. Introduction to LCEL

LCEL (LangChain Expression Language) is a simple and easy-to-use framework for building complex chains. It provides a unified interface and composite primitives to make building chains easier. Each LCEL object implements the Runnable interface, defining a set of commonly used invocation methods (such as invoke, batch, stream, ainvoke, etc.). Therefore, the chains of LCEL objects can also automatically support these invocation methods, making the chain of each LCEL object itself an LCEL object.

2. Invocation

2.1. Without using LCEL

Without using LCEL, you can use the following code snippet to pass a topic string and retrieve a joke string.

from typing import List
import openai

prompt_template = "Tell me a short joke about {topic}"
client = openai.OpenAI()

def call_chat_model(messages: List[dict]) -> str:
    response = client.chat.completions.create(
        model="gpt-3.5-turbo", 
        messages=messages,
    )
    return response.choices[0].message.content

def invoke_chain(topic: str) -> str:
    prompt_value = prompt_template.format(topic=topic)
    messages = [{"role": "user", "content": prompt_value}]
    return call_chat_model(messages)

invoke_chain("ice cream")

2.2. Using LCEL

In contrast, using LCEL can achieve the same functionality more concisely. The following code snippet demonstrates how to easily build a chain using LCEL objects.

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

prompt = ChatPromptTemplate.from_template(
    "Tell me a short joke about {topic}"
)
output_parser = StrOutputParser()
model = ChatOpenAI(model="gpt-3.5-turbo")
chain = (
    {"topic": RunnablePassthrough()} 
    | prompt
    | model
    | output_parser
)

chain.invoke("ice cream")

3. Streaming

3.1. Without using LCEL

The following code snippet demonstrates how to stream process results without using LCEL.

from typing import Iterator

def stream_chat_model(messages: List[dict]) -> Iterator[str]:
    stream = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=messages,
        stream=True,
    )
    for response in stream:
        content = response.choices[0].delta.content
        if content is not None:
            yield content

def stream_chain(topic: str) -> Iterator[str]:
    prompt_value = prompt.format(topic=topic)
    stream = stream_chat_model([{"role": "user", "content": prompt_value}])
    for chunk in stream:
        print(chunk, end="", flush=True)

stream_chain("ice cream")

3.2. Using LCEL

Streaming process results using LCEL is more convenient. The following code snippet demonstrates how to stream with LCEL.

for chunk in chain.stream("ice cream"):
    print(chunk, end="", flush=True)

4. Batch Processing

4.1. Without using LCEL

The following code snippet demonstrates how to parallel process a batch of inputs without using LCEL.

from concurrent.futures import ThreadPoolExecutor

def batch_chain(topics: list) -> list:
    with ThreadPoolExecutor(max_workers=5) as executor:
        return list(executor.map(invoke_chain, topics))

batch_chain(["ice cream", "spaghetti", "dumplings"])

4.2. Using LCEL

Batch processing using LCEL is very straightforward. The following code snippet demonstrates how to use LCEL for batch processing.

chain.batch(["ice cream", "spaghetti", "dumplings"])