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

Erro de validação Pydantic / LCEL ao concatenar cadeias com JsonOutputParser

Olá, pessoal!

Ao tentar executar o código da aula de cadeias concatenadas (onde usamos a classe Restaurantes e Destino herdando de BaseModel), a aplicação quebra apresentando o erro interno no LangChain/Pydantic:

TypeError: BaseModel.__init__() takes 1 positional argument but 2 were given

Segue meu codigo completo:

from langchain_openai import ChatOpenAI
from langchain.prompts import PromptTemplate
from langchain_core.output_parsers import JsonOutputParser, StrOutputParser
from pydantic import Field, BaseModel
from dotenv import load_dotenv
from langchain.globals import set_debug
import os

set_debug(True)

load_dotenv()
api_key=os.getenv("OPENAI_API_KEY")

class Destino(BaseModel):
    cidade:str = Field("A cidade recomendada para visitar")
    motivo:str = Field("motivo pelo qual é interessante visitar essa cidade")

class Restaurantes(BaseModel):
    cidade:str = Field("A cidade recomendada para visitar")
    restaurantes:str = Field("Restaurantes recomendados na cidade")

parseador_destino = JsonOutputParser(pydantic_object=Destino)
parseador_restaurantes = JsonOutputParser(pydantic_object=Restaurantes)

prompt_cidade = PromptTemplate(
    template = """
    Sugira uma cidade dao o meu interesse por {interesse}.
    {formato_de_saida}
    """,
    input_variables=["interesse"],
    partial_variables={"formato_de_saida": parseador_destino.get_format_instructions()}
)

prompt_restaurantes = PromptTemplate(
    template = """
    Sugira populares entre locais em {cidade}.
    {formato_de_saida}
    """,
    partial_variables={"formato_de_saida": parseador_restaurantes.get_format_instructions()}
)

modelo = ChatOpenAI(
    model="gpt-3.5-turbo",
    temperature=0.5,
    api_key=api_key
)

prompt_cultural = PromptTemplate(
    template="Sugira atividades e locais culturais em {cidade}"
)

cadeia_1 = prompt_cidade | modelo | parseador_destino
cadeia_2 = prompt_restaurantes | modelo | parseador_restaurantes
cadeia_3 = prompt_cultural | modelo | StrOutputParser

cadeia = (cadeia_1 | cadeia_2 | cadeia_3)

resposta = cadeia.invoke(
    {
        "interesse": "praias"
    }
)
print(resposta)
2 respostas

Olá, Alef. Como vai?

Esse erro TypeError: BaseModel.__init__() takes 1 positional argument but 2 were given é clássico e acontece por conta de uma sutil incompatibilidade na forma como o LangChain tenta instanciar os objetos do Pydantic por debaixo dos panos ao fazer o fluxo de dados na LCEL (LangChain Expression Language).

Vamos entender o que está acontecendo e como corrigir isso de maneira simples!

O motivo do erro

Quando você concatena cadeias usando o operador pipe (|) na LCEL, o resultado da cadeia_1 serve como a entrada obrigatória para a cadeia_2.

O seu parseador_destino (ao final da cadeia_1) devolve um dicionário Python estruturado (com as chaves cidade e motivo). Na sequência, a cadeia_2 tenta iniciar recebendo esse dicionário.

O problema principal está no seu prompt_restaurantes. O template dele espera apenas a variável {cidade}:

template = "Sugira populares entre locais em {cidade}."

Como a cadeia_1 entregou um dicionário contendo duas chaves (cidade e motivo), o LangChain se confunde ao tentar repassar todo esse bloco para o próximo passo. O parser tenta injetar o dicionário bruto ou remapeá-lo diretamente, gerando o conflito de argumentos posicionais no construtor do Pydantic (BaseModel).


Como Solucionar

Para resolver isso, você precisa garantir que a cadeia_2 receba apenas a chave cidade gerada pela cadeia_1. Podemos fazer isso de forma muito elegante na LCEL utilizando um dicionário mapeador ou uma função lambda para filtrar a entrada da segunda cadeia.

Veja o ajuste necessário nas definições das suas cadeias:

# Mantenha as definições isoladas das cadeias menores
cadeia_1 = prompt_cidade | modelo | parseador_destino
cadeia_2 = prompt_restaurantes | modelo | parseador_restaurantes

# Aqui está o segredo: filtramos a saída da cadeia_1 para que a cadeia_2 receba apenas o que precisa
cadeia_combinada = cadeia_1 | (lambda x: {"cidade": x["cidade"]}) | cadeia_2

# Para a cadeia_3, fazemos o mesmo mapeamento antes de entregar o valor
cadeia_final = cadeia_combinada | (lambda x: {"cidade": x["cidade"]}) | cadeia_3

# Execução final usando o novo encadeamento correto
resposta = cadeia_final.invoke(
    {
        "interesse": "praias"
    }
)
print(resposta)

O que mudou?

Ao aplicar a expressão (lambda x: {"cidade": x["cidade"]}) no meio do caminho, você intercepta o dicionário completo gerado pelo JsonOutputParser anterior e extrai estritamente o texto contido na chave "cidade".

Dessa forma, o prompt_restaurantes (e posteriormente o prompt_cultural) receberá exatamente a estrutura de dicionário contendo apenas o parâmetro esperado pelo template, eliminando o erro de inicialização do Pydantic.

Faça essa alteração no mapeamento do encadeamento e execute novamente com o set_debug(True) ativo para acompanhar o fluxo das variáveis limpas passando de uma cadeia para a outra!

Espero que possa ter lhe ajudado!

solução!

Entendi, mas percebi que a solução retirou a declaração da variável cadeia_3 o que acaba ocasionando no erro abaixo:

& "c:\Users\Alef Farias\Documents\docs\python_codes\langchain_course\langchain\Scripts\python.exe" "c:/Users/Alef Farias/Documents/docs/python_codes/langchain_course/main.py"
Traceback (most recent call last):
  File "c:\Users\Alef Farias\Documents\docs\python_codes\langchain_course\main.py", line 60, in <module>
    cadeia_final = cadeia_combinada | (lambda x: {"cidade": x["cidade"]}) | cadeia_3
                                                                            ^^^^^^^^
NameError: name 'cadeia_3' is not defined. Did you mean: 'cadeia_1'?

A solução q eu apliquei foi declarar a cadeia_3:

#definir as cadeias isoladas (A cadeia_3 precisa voltar pro codigo)
cadeia_1 = prompt_cidade | modelo | parseador_destino
cadeia_2 = prompt_restaurantes | modelo | parseador_restaurantes
cadeia_3 = prompt_cultural | modelo | StrOutputParser() # Estava faltando essa linha

#filtramos a saída da cadeia_1 para que a cadeia_2 receba apenas o que precisa
cadeia_combinada = cadeia_1 | (lambda x: {"cidade": x["cidade"]}) | cadeia_2

#fazemos o mapeamento antes de entregar o valor final
cadeia_final = cadeia_combinada | (lambda x: {"cidade": x["cidade"]}) | cadeia_3

resposta = cadeia_final.invoke(
    {
        "interesse": "praias"
    }
)
print(resposta)

Fazendo esse ajuste o codigo vai descobrir a cidade, achar os restaurantes e terminar mostrando o texto com as atividades que o StrOutputPaser da cadeia_3 processa.