TL;DR
LangChain tornou-se um dos mais usados Biblioteca Python para interagir com LLMs em menos de um ano, mas a LangChain era principalmente uma biblioteca para POCs pois não tinha a capacidade de criar aplicativos complexos e dimensionáveis.
Tudo mudou em agosto de 2023, quando eles lançaram Linguagem de expressão LangChain (LCEL), uma nova sintaxe que preenche a lacuna de Do POC à produção. Este artigo o guiará pelos prós e contras do LCEL, mostrando-lhe como ele simplifica o processo de criação de cadeias personalizadas e por que o senhor deve aprendê-la se estiver construindo Inscrições para o LLM!
Prompts, LLM e cadeias, vamos refrescar nossa memória
Antes de mergulhar na sintaxe do LCEL, acho que é bom refrescar nossa memória sobre os conceitos do LangChain, como LLM e Prompt ou até mesmo uma cadeia.
LLM: Em langchain, o llm é uma abstração em torno do modelo usado para fazer as conclusões, como openai gpt3.5, claude, etc...
Prompt: Esta é a entrada do objeto LLM, que fará perguntas ao LLM e fornecerá seus objetivos.
Cadeia: Refere-se a uma sequência de chamadas a um LLM ou a qualquer etapa de processamento do data.

Agora que as definições estão resolvidas, vamos supor que o senhor queira criar uma empresa! Precisamos de um nome muito legal e cativante e de um modelo de negócios para ganhar dinheiro!
Exemplo - Nome da empresa e modelo de negócios com cadeias antigas
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
de langchain_community.llms import OpenAI
USER_INPUT = "colorful socks" (meias coloridas)"
llm = OpenAI(temperature=0)
prompt_template_product = "Qual é um bom nome para uma empresa que fabrica ?"
company_name_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_product))
company_name_output = company_name_chain(USER_INPUT)
prompt_template_business = "Dê-me a melhor ideia de modelo de negócios para minha empresa chamada: "
business_model_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_business))
business_model_output = business_model_chain(company_name_output["text"])
print(company_name_output)
print(business_model_output)
>>>
>>>
Isso é bastante fácil de seguir, podemos ver um pouco de redundância, mas é administrável.
Vamos adicionar alguma personalização, tratando os casos em que o usuário não está usando nossa cadeia conforme o esperado.
Talvez o usuário insira algo completamente não relacionado ao objetivo da nossa cadeia? Nesse caso, queremos detectar isso e responder adequadamente.
Exemplo - Personalização e roteamento com cadeias antigas
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
de langchain_community.llms import OpenAI
importar ast
USER_INPUT = "Harrison Chase"
llm = OpenAI(temperature=0)
# -- O mesmo código anterior
prompt_template_product = "Qual é um bom nome para uma empresa que fabrica ?"
company_name_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_product))
prompt_template_business = "Dê-me a melhor ideia de modelo de negócios para minha empresa chamada: "
business_model_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_business))
# -- Novo código
prompt_template_is_product = (
"Seu objetivo é descobrir se a entrada do usuário é um nome de produto plausível."
"Perguntas, saudações, frases longas, celebridades ou outros insumos não relevantes não são considerados produtosn"
"input: n"
"Responda apenas com 'Verdadeiro' ou 'Falso' e nada mais."
)
prompt_template_cannot_respond = (
"O senhor não pode responder à entrada do usuário: n"
"Peça ao usuário para inserir o nome de um produto para que o senhor crie uma empresa a partir dele.n"
)
cannot_respond_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(prompt_template_cannot_respond))
company_name_chain = 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))
# Se usarmos bool em uma string não vazia, ela será True, portanto, precisamos de `literal_eval`
is_a_product = ast.literal_eval(is_a_product_chain(USER_INPUT)["text"])
if is_a_product:
company_name_output = company_name_chain(USER_INPUT)
business_model_output = business_model_chain(company_name_output["text"])
print(business_model_output)
E mais:
print(cannot_respond_chain(USER_INPUT))
Isso se torna um pouco mais difícil de entender, vamos resumir:
Vários problemas começam a surgir:
O que é a LangChain Expression Language (LCEL)?
O LCEL é uma interface e uma sintaxe unificadas para escrever cadeias prontas de produção compostas, mas há muito o que desvendar para entender o que isso significa.
Primeiro, tentaremos entender a nova sintaxe reescrevendo a cadeia anterior.
Exemplo - Nome da empresa e modelo de negócios com a LCEL
from langchain_core.runnables import RunnablePassthrough
from langchain.prompts import PromptTemplate
de langchain_community.llms import OpenAI
USER_INPUT = "colorful socks" (meias coloridas)"
llm = OpenAI(temperature=0)
prompt_template_product = "Qual é um bom nome para uma empresa que fabrica ?"
prompt_template_business = "Dê-me a melhor ideia de modelo de negócios para minha empresa chamada: "
chain = (
PromptTemplate.from_template(prompt_template_product)
| llm
|
| PromptTemplate.from_template(prompt_template_business)
| llm
)
business_model_output = chain.invoke()
Um monte de código incomum em apenas algumas linhas:
Mas será que é realmente mais fácil criar cadeias dessa forma?
Vamos testá-lo adicionando is_a_product_chain() e a ramificação se a entrada do usuário não estiver correta. Podemos até mesmo digitar a cadeia com o Python Typing, vamos fazer isso como uma boa prática.
Exemplo - Personalização e roteamento com o 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
de langchain_community.llms import OpenAI
USER_INPUT = "Harrrison Chase"
llm = OpenAI(temperature=0)
prompt_template_product = "Qual é um bom nome para uma empresa que fabrica ?"
prompt_template_cannot_respond = (
"O senhor não pode responder à entrada do usuário: n"
"Peça ao usuário para inserir o nome de um produto para que o senhor crie uma empresa a partir dele.n"
)
prompt_template_business = "Dê-me a melhor ideia de modelo de negócios para minha empresa chamada: "
prompt_template_is_product = (
"Seu objetivo é descobrir se a entrada do usuário é um nome de produto plausível."
"Perguntas, saudações, frases longas, celebridades ou outros insumos não relevantes não são considerados produtosn"
"input: n"
"Responda apenas com 'Verdadeiro' ou 'Falso' e nada mais."
)
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())
Vamos listar as diferenças:
Por que o LCEL é melhor para a industrialização?
Se eu estivesse lendo este artigo até este ponto exato e alguém me perguntasse se eu estava convencido sobre o LCEL, eu provavelmente diria que não. A sintaxe é muito diferente, e provavelmente posso organizar meu código em funções para obter quase exatamente o mesmo código. Mas estou aqui, escrevendo este artigo, portanto, deve haver algo mais.
Invocação, fluxo e lote prontos para uso
Ao usar o LCEL, sua cadeia automaticamente tem:
my_chain = prompt | llm
# ---invoke--- #
result_with_invoke = my_chain.invoke(“hello world!”)
# ---batch--- #
result_with_batch = my_chain.batch([“hello”, “world”, “!”])
# ---stream--- #
for chunk in my_chain.stream(“hello world!”):
print(chunk, flush=True, end=””)
Ao iterar, o senhor pode usar o método invoke para facilitar o processo de desenvolvimento. Mas, ao mostrar a saída da cadeia em uma interface do usuário, o senhor deseja transmitir a resposta. Agora o senhor pode usar o método stream sem reescrever nada.
Métodos assíncronos prontos para uso
Na maioria das vezes, o frontend e o backend do seu aplicativo estarão separados, o que significa que o frontend fará uma solicitação ao backend. Se tiver vários usuários, talvez seja necessário lidar com várias solicitações no backend ao mesmo tempo.
Como a maior parte do código no LangChain está apenas aguardando entre as chamadas de API, podemos aproveitar o código assíncrono para melhorar a escalabilidade da API. história de hambúrgueres simultâneos da documentação da FastAPI.
Não há necessidade de se preocupar com a implementação, pois os métodos assíncronos já estão disponíveis se o senhor usar o LCEL:
.ainvoke() / .abatch() / .astreamVersão assíncrona de invoke, batch e stream.
Também recomendo a leitura do Por que usar a página LCEL da documentação do LangChain? com exemplos para cada método sincronizado/assíncrono.
A Langchain obteve esses recursos “prontos para uso” ao criar uma interface unificada chamada “Runnable” (executável)”. Agora, para aproveitar totalmente o LCEL, precisamos nos aprofundar no que é essa nova interface Runnable.
A interface Runnable
Todos os objetos que usamos na sintaxe do LCEL até agora são Runnables. Trata-se de um objeto python criado pelo LangChain, que herda automaticamente todos os recursos de que falamos anteriormente e muito mais. Ao usar a sintaxe do LCEL, compomos um novo Runnable a cada etapa, o que significa que o objeto final criado também será um Runnable. O senhor pode saber mais sobre a interface na documentação oficial.
Todos os objetos do código abaixo são Runnable ou dicionários que são automaticamente convertidos em Runnable:
from langchain_core.runnables import RunnablePassthrough, RunnableParallel
from langchain.prompts import PromptTemplate
de langchain_community.llms import OpenAI
chain_number_one = (
PromptTemplate.from_template(prompt_template_product)
| llm
| # <- ISSO VAI MUDAR
| PromptTemplate.from_template(prompt_template_business)
| llm
)
chain_number_two = (
PromptTemplate.from_template(prompt_template_product)
| llm
| RunnableParallel(company=RunnablePassthrough()) # <- ISSO MUDOU
| PromptTemplate.from_template(prompt_template_business)
| llm
)
print(chain_number_one == chain_number_two)
>>> True
Por que usamos RunnableParallel() e não simplesmente Runnable()?
Porque cada Runnable dentro de um RunnableParallel é executado em paralelo. Isso significa que, se o senhor tiver três etapas independentes no seu Runnable, elas serão executadas ao mesmo tempo em diferentes threads da sua máquina, aumentando a velocidade da sua cadeia gratuitamente!
Desvantagens do LCEL
Apesar de suas vantagens, a LCEL tem algumas possíveis desvantagens:
Conclusão
Em conclusão, a LangChain Expression Language (LCEL) é uma ferramenta poderosa que traz uma nova perspectiva para a criação de aplicativos Python. Apesar de sua sintaxe não convencional, recomendo fortemente o uso da LCEL pelos seguintes motivos:
Para ir além...
A abstração executável
Em alguns casos, acredito que seja importante entender a abstração que a LangChain implementou para fazer a sintaxe do LCEL funcionar.
O senhor pode reimplementar as funcionalidades básicas do Runnable facilmente da seguinte forma:
classe Runnable:
def __init__(self, func):
self.func = func
def __or__(self, other):
def chained_func(*args, **kwargs):
# self.func está à esquerda, o outro está à direita
return other(self.func(*args, **kwargs))
return Runnable(chained_func)
def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs)
def add_ten(x):
Retorno x + 10
def divide_by_two(x):
retornar x / 2
runnable_add_ten = Runnable(add_ten)
runnable_divide_by_two = Runnable(divide_by_two)
chain = runnable_add_ten | runnable_divide_by_two
resultado = chain(8) # (8+10) / 2 = 9,0 deve ser a resposta
print(result)
>>> 9.0
Um Runnable é simplesmente um objeto python no qual o método .__or__() foi substituído.
Na prática, o LangChain adicionou muitas funcionalidades, como a conversão de dicionários em Runnable, recursos de tipagem, recursos de configuração e métodos de invocação, lote, fluxo e assíncrono!
Então, por que não experimentar o LCEL em seu próximo projeto?
Se o senhor quiser saber mais, recomendo vivamente que navegue Livro de receitas do LangChain no LCEL.

BLOG






