Langchain é um framework aberto utilizado para criar aplicações baseadas em Grandes Modelos de Linguagem (Large Language Models). Uma ferramenta versátil e poderosa. Mais detalhes podem ser vistos em https://python.langchain.com/docs/introduction/.
Uma opção interessante, principalmente quando se tratar de utilizar LLMs como classificadores, ou análise de dados, é a opção de retornar as saídas do modelo de linguagem em formato JSON. Esse processo permite a automatização de tarefas.
Nesse exemplo será apresentado como usar o Pydantic para recuperar as saídas geradas por LLM’s na forma de dados estruturados. Os modelos precisar ter implementado “tools” para gerar a saída no formato correto, em JSON.
Nesse exemplo iremos usar os modelos de linguagem para classificar sentimentos em textos. Queremos ao final gerar um score para classificar o texto como positivo, negativo ou neutro. O score final será calculado com base nas razões que o modelo de linguagem irá gerar para explicar o motivo do texto ter uma das classes.
O projeto está disponível no Github: https://github.com/duducosmos/analise_sentimentos_llm
Abaixo está o comando para instalar as bibliotecas necessárias
pip install langchain langchain-community openai langchain-openai pydantic
Vamos importar as bibliotecas necessárias
from langchain.prompts import PromptTemplate
from langchain_openai import AzureChatOpenAI
from langchain_ollama import ChatOllama
from pydantic import BaseModel, Field
from typing import List, Literal
Code language: JavaScript (javascript)
O prompt utilizado nesse caso será:
prompt_template = """
Você é um agente responsável por avaliar e classificar textos.
Cada texto será classificado como Positivo, Neutro ou Negativo, com base nos seguintes critérios:
1. **Positivo**: Textos que transmitam felicidade, alegria, compaixão ou sentimentos edificantes.
2. **Neutro**: Textos que apresentem informações ou explicações objetivas, sem transmitir emoções específicas ou tentar convencer o leitor.
3. **Negativo**: Textos que expressem raiva, ódio, tristeza, dor ou outros sentimentos negativos.
**Instruções para saída:**
A. A resposta deve ser um JSON **válido**.
B. O JSON deve conter:
B1. A classificação do texto (`sentimento`).
B2. Uma string de palavras-chave, seapardas por vírgula, que justifiquem a classificação. Exemplo "Amor, Gratidão, Felicidade"
B3. Um `veredito` explicando a razão da classificação.
** Exemplo de Entrada **
`Hoje foi um dia difícil. As coisas não saíram como planejei, e isso me deixou frustrado. No entanto, ao final da tarde, consegui refletir e encontrar um pouco de paz na situação. Preciso lembrar que a vida é cheia de altos e baixos, e sempre há algo a aprender.`
**Exemplo de saída:**
{{
"sentimentos": [
{{
"sentimento": "Negativo",
"palavras_chave": "difícil, frustrado, não saíram como planejei",
"veredito": "O texto expressa sentimentos de frustração e dificuldades enfrentadas no dia."
}},
{{
"sentimento": "Neutro",
"palavras_chave": "refletir, encontrar paz, "aprender",
"veredito": "O texto apresenta uma reflexão sobre a situação, de forma objetiva e sem carga emocional forte."
}},
{{
"sentimento": "Positivo",
"palavras_chave": "paz, altos e baixos, algo a aprender",
"veredito": "O texto termina com uma mensagem de esperança, destacando a importância de aprender com as experiências."
}}
]
}}
Analise a seguinte entrada:
{input_text}
Gere a saída em JSON **válido**:
"""
Code language: PHP (php)
Tendo a estrutura de como queremos que a LLM retorne o resultado, iremos preparar uma estrutura de dados, usando o Pydantic para tratar automaticamente a saída do classificador.
class Sentimento(BaseModel):
sentimento: str = Field(...,
description="Classificação do sentimento encontrado, Positivo, Negativo ou Neutro.")
palavras_chave: str = Field(..., description="Lista de palavras-chave separadas por virgula")
veredito: str = Field(...,description="Texto explicando o motivo da classificação")
class Sentimentos(BaseModel):
sentimentos: List[Sentimento] = Field(..., description="Lista de sentimentos encotrados no texto")
Irei apresentar como chamar o Azure OpenAI e també o Ollama, rodando o llama3.1
llm = AzureChatOpenAI(azure_deployment=azure_deployment,
api_version="2023-06-01-preview",
temperature=0)
llm_llama = ChatOllama(
base_url="http://192.168.100.123:11434/",
model="llama3.1",
format="json",
)
Code language: JavaScript (javascript)
O ponto interessante é passar o Pydantic gerado como sendo a saída que nos queremos receber do llm
llm_final = llm_llama.with_structured_output(Sentimentos)
Note que passamos a estrutura de dados Sentimentos como argumento para o método with_structured_output
.
A próxima etapa consiste na criação de uma cadeia, ou Chain:
senti_prompt = PromptTemplate(
input_variables=["input_text"],
template=prompt_template)
chain = senti_prompt | llm_final
Code language: JavaScript (javascript)
Por fim, iremos criar uma função para invocar o modelo:
def analise_sentimentos(input_text):
return chain .invoke({
"input_text": input_text
})
Code language: JavaScript (javascript)
Vamos rodar os seguintes exemplos:
exemplos = [
"""Hoje foi um dia maravilhoso! Consegui realizar várias tarefas importantes e passei momentos incríveis com minha família.
No entanto, fiquei um pouco chateado por não conseguir resolver um problema no trabalho.
Ainda assim, sinto gratidão por tudo que deu certo.""",
"""O sistema operacional atualizado oferece novas funcionalidades, como maior velocidade e interface mais intuitiva.
No entanto, alguns usuários podem enfrentar problemas de compatibilidade com softwares antigos.
De qualquer forma, as mudanças representam um avanço tecnológico significativo.""",
"""O dia foi extremamente desgastante. Perdi um prazo importante no trabalho, e isso me deixou desanimado.
Para piorar, enfrentei um trânsito terrível ao voltar para casa. Mas, ao chegar,
o sorriso do meu filho me fez lembrar que sempre há algo bom, mesmo nos dias mais difíceis"""
]
Code language: PHP (php)
for exemplo in exemplos:
print(analise_sentimentos(exemplo))
print("\n\n")
Code language: PHP (php)
Abaixo está a saída gerada:
sentimentos=[Sentimento(sentimento='Positivo', palavras_chave='maravilhoso, importante, incríveis, gratidão', veredito='O texto expressa sentimentos de alegria e satisfação com as realizações do dia.'), Sentimento(sentimento='Negativo', palavras_chave='chateado, problema no trabalho', veredito='O texto apresenta um sentimento negativo de frustração por não ter conseguido resolver o problema no trabalho.')]
sentimentos=[Sentimento(sentimento='Positivo', palavras_chave='novas funcionalidades, maior velocidade, interface mais intuitiva', veredito='O texto apresenta novidades positivas no sistema operacional.'), Sentimento(sentimento='Neutro', palavras_chave='problemas de compatibilidade, softwares antigos', veredito='O texto menciona possíveis problemas de ajuste, sem carregar um tom emocional forte.'), Sentimento(sentimento='Positivo', palavras_chave='avanço tecnológico significativo', veredito='O texto destaca a melhoria como um avanço significativo no campo da tecnologia.')]
sentimentos=[Sentimento(sentimento='Negativo', palavras_chave='desgastante, desanimado, trânsito terrível', veredito='O texto expressa sentimentos de frustração e dificuldades enfrentadas no dia.'), Sentimento(sentimento='Positivo', palavras_chave='sorriso do meu filho, algo bom', veredito='O texto termina com uma mensagem de esperança, destacando a importância de aprender com as experiências e encontrar o lado positivo das coisas.')]
Code language: JavaScript (javascript)
Vamos ajustar o prompt para podermos calcular um score de forma mais apropriada e adicionar o tratamento de erros, para o caso do modelo não retornar corretamente a saída. Abaixo está o código completo do projeto:
from langchain.prompts import PromptTemplate
from langchain_openai import AzureChatOpenAI
from langchain_ollama import ChatOllama
from pydantic import BaseModel, Field
from typing import List, Literal
import time
prompt_template = """
Você é um agente responsável por avaliar e classificar textos.
Cada texto será classificado como Positivo, Neutro ou Negativo, com base nos seguintes critérios:
1. **Positivo**: Textos que transmitam felicidade, alegria, compaixão ou sentimentos edificantes.
2. **Neutro**: Textos que apresentem informações ou explicações objetivas, sem transmitir emoções específicas ou tentar convencer o leitor.
3. **Negativo**: Textos que expressem raiva, ódio, tristeza, dor ou outros sentimentos negativos.
**Instruções para saída:**
A. A resposta deve ser um JSON **válido**.
B. O JSON deve conter:
B1. A classificação do texto (`sentimento`).
B2. Uma string de palavras-chave, seapardas por vírgula, que justifiquem a classificação. Exemplo "Amor, Gratidão, Felicidade"
B3. Um `veredito` explicando a razão da classificação.
** Exemplo de Entrada **
`Hoje foi um dia difícil. As coisas não saíram como planejei, e isso me deixou frustrado. No entanto, ao final da tarde, consegui refletir e encontrar um pouco de paz na situação. Preciso lembrar que a vida é cheia de altos e baixos, e sempre há algo a aprender.`
**Exemplo de saída:**
{{
"sentimentos": [
{{
"sentimento": "Negativo",
"palavras_chave": "difícil, frustrado, não saíram como planejei",
"veredito": "O texto expressa sentimentos de frustração e dificuldades enfrentadas no dia."
}},
{{
"sentimento": "Neutro",
"palavras_chave": "refletir, encontrar paz, "aprender",
"veredito": "O texto apresenta uma reflexão sobre a situação, de forma objetiva e sem carga emocional forte."
}},
{{
"sentimento": "Positivo",
"palavras_chave": "paz, altos e baixos, algo a aprender",
"veredito": "O texto termina com uma mensagem de esperança, destacando a importância de aprender com as experiências."
}}
]
}}
Analise a seguinte entrada:
{input_text}
Gere a saída em JSON **válido**:
"""
prompt_template_v2 = """
Você é um agente responsável por avaliar e classificar textos.
Cada texto será classificado como Positivo, Neutro ou Negativo, com base nos seguintes critérios:
1. **Positivo**: Textos que transmitam felicidade, alegria, compaixão ou sentimentos edificantes.
2. **Neutro**: Textos que apresentem informações ou explicações objetivas, sem transmitir emoções específicas ou tentar convencer o leitor.
3. **Negativo**: Textos que expressem raiva, ódio, tristeza, dor ou outros sentimentos negativos.
**Instruções para saída:**
A. A resposta deve ser um JSON **válido**.
B. O JSON deve conter:
B1. A classificação do texto (`sentimento`).
B2. Uma string de palavras-chave, seapardas por vírgula, que justifiquem a classificação. Exemplo "Amor, Gratidão, Felicidade"
B3. Um `veredito` explicando a razão da classificação.
** Exemplo de Entrada **
`Hoje foi um dia difícil. As coisas não saíram como planejei, e isso me deixou frustrado. No entanto, ao final da tarde, consegui refletir e encontrar um pouco de paz na situação. Preciso lembrar que a vida é cheia de altos e baixos, e sempre há algo a aprender.`
**Exemplo de saída:**
{{
"sentimentos": [
{{
"sentimento": "Negativo",
"palavras_chave": "difícil, frustrado, não saíram como planejei",
"veredito": "O texto expressa sentimentos de frustração e dificuldades enfrentadas no dia."
}},
{{
"sentimento": "Neutro",
"palavras_chave": "refletir, encontrar paz, "aprender",
"veredito": "O texto apresenta uma reflexão sobre a situação, de forma objetiva e sem carga emocional forte."
}},
{{
"sentimento": "Positivo",
"palavras_chave": "paz, altos e baixos, algo a aprender",
"veredito": "O texto termina com uma mensagem de esperança, destacando a importância de aprender com as experiências."
}}
]
}}
OBS: O total de elementos na lista de sentimentos deve ser ímpar, refletindo o total de sentimentos encotrado no texto.
Analise a seguinte entrada:
{input_text}
Gere a saída em JSON **válido**:
"""
class Sentimento(BaseModel):
sentimento: str = Field(...,
description="Classificação do sentimento encontrado, Positivo, Negativo ou Neutro.")
palavras_chave: str = Field(...,
description="Lista de palavras-chave separadas por virgula")
veredito: str = Field(...,
description="Texto explicando o motivo da classificação")
class Sentimentos(BaseModel):
sentimentos: List[Sentimento] = Field(
..., description="Lista de sentimentos encotrados no texto")
llm_llama = ChatOllama(
base_url="http://192.168.100.123:11434/",
model="llama3.1",
format="json",
)
llm_final = llm_llama.with_structured_output(Sentimentos)
senti_prompt = PromptTemplate(
input_variables=["input_text"],
template=prompt_template)
chain = senti_prompt | llm_final
# def analise_sentimentos(input_text):
# return chain.invoke({
# "input_text": input_text
# })
def analise_sentimentos(input_text, max_retries=3, verbose=False):
"""Analisa os sentimentos do texto e trata erros de validação."""
attempts = 0
while attempts < max_retries:
try:
time.sleep(0.5)
output = chain.invoke({"input_text": input_text})
time.sleep(0.5)
return output
except Exception as e:
time.sleep(1)
# Incrementa o número de tentativas
attempts += 1
if verbose:
print(f"Tentativa {attempts}/{max_retries} falhou. Erro: {e}")
if attempts >= max_retries:
# Se atingir o limite de tentativas, retorna o erro
raise RuntimeError(
f"Falha ao processar o texto após {max_retries} tentativas.") from e
def score(sentimentos: Sentimentos):
"""Calcula a pontuação total dos sentimentos."""
positivo = [
sentimento for sentimento in sentimentos.sentimentos if sentimento.sentimento == "Positivo"]
neutro = [
sentimento for sentimento in sentimentos.sentimentos if sentimento.sentimento == "Neutro"]
negativo = [
sentimento for sentimento in sentimentos.sentimentos if sentimento.sentimento == "Negativo"]
n = len(sentimentos.sentimentos)
score_positivo = len(positivo) / n
score_neutro = len(neutro) / n
score_negativo = len(negativo) / n
return [score_positivo, score_neutro, score_negativo]
exemplos = [
"""Hoje foi um dia maravilhoso. Consegui realizar várias tarefas importantes e passei momentos incríveis com minha família.
No entanto, fiquei um pouco chateado por não conseguir resolver um problema no trabalho.
Ainda assim, sinto gratidão por tudo que deu certo.""",
"""O sistema operacional atualizado oferece novas funcionalidades, como maior velocidade e interface mais intuitiva.
No entanto, alguns usuários podem enfrentar problemas de compatibilidade com softwares antigos.
De qualquer forma, as mudanças representam um avanço tecnológico significativo.""",
"""O dia foi extremamente desgastante. Perdi um prazo importante no trabalho, e isso me deixou desanimado.
Para piorar, enfrentei um trânsito terrível ao voltar para casa. Mas, ao chegar,
o sorriso do meu filho me fez lembrar que sempre há algo bom, mesmo nos dias mais difíceis"""
]
for exemplo in exemplos:
try:
sentimentos = analise_sentimentos(exemplo)
print(score(sentimentos))
print("\n\n")
except Exception as e:
print(f"Erro: {e}")
print("\n\n")
Code language: HTML, XML (xml)