Solucionado (ver solução)
Solucionado
(ver solução)
3
respostas

[Dúvida] Funcionamento de Cadeias no LangChain

Olá,

Assisti a aula do professor sobre a revisão do pipeline básico de RAG e fiquei com dúvida no funcionamento da cadeia que o LangChain permite, visto que este é um dos objetivos da existência desta biblioteca. Em aula, o professor não explica essa parte. Acredito que isso é algo que também deveria ser explicado. A minha questão principal seria esclarecer o funcionamento da lógica desse encadeamento de variáveis, pois não é algo muito intuitivo pra quem está familiarizado com a programação em Python.

Grato desde já.

3 respostas

Olá, Nelson! Como vai?

Entendo que a lógica de encadeamento de variáveis no LangChain pode ser um pouco desafiadora no início.

Especialmente se você está acostumado com a programação em Python tradicional. O LangChain é uma biblioteca projetada para facilitar o uso de modelos de linguagem, permitindo que você crie pipelines complexos de maneira mais organizada e modular.

No LangChain, uma "cadeia" ou "chain" é uma sequência de operações ou transformações que você pode aplicar a dados ou consultas. Cada etapa na cadeia pode ser vista como um bloco que processa a entrada e gera uma saída, que é então passada para o próximo bloco na cadeia. Isso é semelhante ao conceito de encadeamento de funções, onde a saída de uma função é a entrada para a próxima.

Por exemplo, no contexto do LangChain, você pode ter uma cadeia que começa com a leitura de documentos, seguida pela divisão desses documentos em pedaços menores (chunks), a criação de embeddings para esses pedaços e finalmente a consulta a um banco de dados vetorial para recuperar informações relevantes. Cada um desses passos pode ser configurado como parte de uma cadeia, permitindo que você construa um fluxo de processamento de dados de maneira clara e estruturada.

Um exemplo prático seria:

  1. Carregar Documentos: Usar um loader para ler documentos de texto.
  2. Dividir em Chunks: Usar um splitter para dividir o texto em pedaços menores.
  3. Criar Embeddings: Converter esses pedaços em vetores numéricos usando um modelo de embeddings.
  4. Consultar Banco de Dados: Usar um retriever para buscar os pedaços mais relevantes com base em uma consulta.

Cada um desses passos é configurado como um componente na cadeia e o LangChain cuida de passar os dados de um componente para o próximo. Isso permite que você se concentre mais na lógica de alto nível do que nos detalhes de implementação de cada etapa.

Espero que essa explicação ajude a esclarecer como as cadeias funcionam no LangChain. É uma abordagem poderosa para construir pipelines de processamento de linguagem natural de forma modular e escalável.

Abraço e bons estudos!

Caso este post tenha lhe ajudado, por favor, marcar como solucionado

Olá Daniel, tudo bem?

Creio que a ideia em si eu entendi.

O que não estou entendendo é quando o professor encadeou alguns comandos com o operador pipe (|), sem estar relacionado com o RAG em si. Segue o código de exemplo a seguir:

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

prompt_consulta_seguro = ChatPromptTemplate.from_messages(
    [
        ("system", "Responda usando exclusivamente o conteúdo fornecido. \n\nContexto: \n{contexto}"),
        ("human", "{query}")
    ]
)

modelo = ChatOpenAI(
    model="gpt-4.1-nano",
    temperature=0.5,
    api_key=openai_api_key
)

cadeia = prompt_consulta_seguro | modelo | StrOutputParser()

cadeia.invoke({ "query": pergunta, "contexto": contexto})

Neste caso, o que está sendo executado no template antes de ser enviado para o modelo em si?

Outra questão é como o StrOutputParser sabe qual pedaço quebrar?

Minhas dúvidas estão mais no funcionamento de cada comando do que no encadeamento em si.

Grato desde já!

solução!

Olá Nelson! Tudo bem?

Esse encadeamento que você observou utiliza a LCEL (LangChain Expression Language), esse assunto é a base de como o LangChain funciona. Esse assunto vi em algum curso anterior na formação Engenharia de IA. A lógica por trás do operador | (pipe) é criar um fluxo de dados em que cada componente entrega exatamente o que o próximo espera receber.

Quando você define cadeia = prompt | modelo | parser o que ocorre ao executar o .invoke() segue esta sequência:

O prompt recebe o seu dicionário com as chaves {query} e {contexto} e as injeta nos lugares corretos do texto. Ele transforma isso em um objeto chamado PromptValue, que é basicamente a lista de mensagens (System e Human) já formatada e pronta para que o modelo consiga processar.

O modelo recebe essas mensagens, processo e retorna um objeto chamado BaseMessage (que contém a resposta + um monte de metadados da API).

O parser recebe esse objeto com a respostas + metadados (como contagem de tokens e IDs), ignora a parte dos metadados e extrai apenas o texto da resposta e o que sai é a string final.

Sem o operador |, você teria que capturar o resultado de cada função e passar manualmente para a próxima. O operador Pipe | automatiza essa "passagem de bastão" entre os componentes.

Quanto a sua pergunta, "Como o StrOutputParser sabe o que extrair?". Como informado anteriormente, os modelos retornam um objeto do tipo BaseMessage que contém atributos content, response_metadata, usage_metada, o que o StrOutputParser() faz é apenas extrair/ler o conteúdo de texto (o atributo .content) e converter em uma String pura.