Importante

Você está vendo a versão anterior da nova experiência da Alura que estamos preparando para você. Em breve, ela ganha uma identidade visual novinha totalmente pensada em potencializar seus estudos!

2
respostas

[Projeto] Desafio Final

import json
import re
import time

from openai import OpenAI
from google.api_core.exceptions import TooManyRequests

# Configuração do cliente local
client = OpenAI(
    base_url="http://127.0.0.1:1234/v1",
    api_key="sk-proj-1234567890"
)

# ==========================================================
# 1. Carregar arquivo TXT em uma lista Python
# ==========================================================

linhas_resenhas = []

with open("./Resenhas.txt", "r", encoding="utf-8") as arquivo:
    for linha in arquivo:
        linha_limpa = linha.strip()

        if linha_limpa:
            linhas_resenhas.append(linha_limpa)

print("Resenhas carregadas:")

for linha in linhas_resenhas:
    print(linha)

# ==========================================================
# Converter as linhas para uma lista de dicionários
# ==========================================================

dados_resenhas = []

for resenha_str in linhas_resenhas:

    try:

        partes = resenha_str.split("$", 2)

        if len(partes) == 3:

            reviewer_id = partes[0]
            reviewer_name = partes[1]
            review_text = partes[2]

            dados_resenhas.append({
                "reviewerID": reviewer_id,
                "reviewerName": reviewer_name,
                "reviewText": review_text
            })

        else:
            print(f"Linha ignorada: {resenha_str}")

    except Exception as erro:
        print(f"Erro ao processar linha: {erro}")

# ==========================================================
# 2. Enviar ao modelo local
# ==========================================================

resenhas_json = json.dumps(
    dados_resenhas,
    ensure_ascii=False
)

prompt = f"""
Analise as resenhas abaixo.

Para cada resenha retorne um objeto JSON contendo:

- usuario
- resenha_original
- resenha_pt
- avaliacao

A avaliação deve ser em português:
- positiva
- negativa
- neutra

Retorne SOMENTE uma lista JSON válida.

Resenhas:

{resenhas_json}
"""

max_retries = 5
initial_delay = 2

lista_resenhas_processadas = None

for tentativa in range(max_retries):

    try:

        resposta = client.chat.completions.create(
            model="google/gemma-3-1b",
            messages=[
                {
                    "role": "user",
                    "content": prompt
                }
            ],
            temperature=0.3,
            max_completion_tokens=4000,
            stream=False
        )

        conteudo = resposta.choices[0].message.content.strip()

        # Tenta converter diretamente
        try:

            lista_resenhas_processadas = json.loads(conteudo)

        except json.JSONDecodeError:

            # Tenta extrair JSON caso venha misturado com texto
            json_match = re.search(
                r'\[\s*\{.*\}\s*\]',
                conteudo,
                re.DOTALL
            )

            if json_match:

                lista_resenhas_processadas = json.loads(
                    json_match.group(0)
                )

        if lista_resenhas_processadas:
            break

    except TooManyRequests:

        tempo_espera = initial_delay * (2 ** tentativa)

        print(
            f"Limite excedido. "
            f"Tentando novamente em {tempo_espera}s..."
        )

        time.sleep(tempo_espera)

    except Exception as erro:

        print(f"Erro inesperado: {erro}")
        break

# ==========================================================
# 3. Transformar resposta em lista de dicionários
# ==========================================================

if lista_resenhas_processadas:

    print("\nLista de dicionários criada com sucesso:\n")

    print(
        json.dumps(
            lista_resenhas_processadas,
            indent=2,
            ensure_ascii=False
        )
    )

else:

    print("Não foi possível obter um JSON válido.")

Continua no coment

2 respostas

Continuando...

# ==========================================================
# 4. Função solicitada no exercício
# ==========================================================

def analisar_resenhas(lista_dicionarios, separador="\n---\n"):
    """
    Percorre a lista de resenhas e retorna:

    - Quantidade de avaliações positivas
    - Quantidade de avaliações negativas
    - Quantidade de avaliações neutras
    - String contendo todas as resenhas unidas
    """

    positivas = 0
    negativas = 0
    neutras = 0

    resenhas_unidas = []

    for item in lista_dicionarios:

        avaliacao = item.get("avaliacao", "").lower()

        if avaliacao == "positiva":
            positivas += 1

        elif avaliacao == "negativa":
            negativas += 1

        elif avaliacao == "neutra":
            neutras += 1

        resenhas_unidas.append(
            item.get("resenha_pt", "")
        )

    texto_final = separador.join(resenhas_unidas)

    return {
        "positivas": positivas,
        "negativas": negativas,
        "neutras": neutras
    }, texto_final

# ==========================================================
# Executando a função
# ==========================================================

if lista_resenhas_processadas:

    estatisticas, texto_concatenado = analisar_resenhas(
        lista_resenhas_processadas
    )

    print("\nResumo das avaliações:")
    print(estatisticas)

    print("\nResenhas concatenadas:")
    print(texto_concatenado)

Olá, Paulo. Como vai?

Sensacional a estrutura do seu Desafio Final! Você construiu uma solução de ponta a ponta extremamente robusta. O seu código resolve problemas reais de engenharia de prompt e IA, como a limpeza de strings via Expressões Regulares (re.search), o tratamento de exceções de requisições e a implementação manual do algoritmo de Exponential Backoff (atraso exponencial) para lidar com o limite de requisições (TooManyRequests).

Para complementar o seu projeto e deixá-lo ainda mais alinhado com as melhores práticas de mercado ao trabalhar com modelos locais (como o Gemma-3-1b via LM Studio), preparei três sugestões de otimização:

1. Forçando o Modo JSON Nativo (Structured Outputs)

Como você está utilizando a biblioteca oficial da OpenAI apontando para o seu servidor local, você pode instruir o modelo a responder estritamente em formato JSON utilizando o parâmetro response_format. Isso reduz drasticamente as chances do modelo enviar textos explicativos fora do JSON e diminui a dependência do bloco re.search.

Basta modificar a chamada do método adicionando esse parâmetro:

resposta = client.chat.completions.create(
    model="google/gemma-3-1b",
    messages=[
        {
            "role": "user",
            "content": prompt
        }
    ],
    temperature=0.3,
    max_completion_tokens=4000,
    response_format={"type": "json_object"},  # Força a saída estruturada
    stream=False
)

2. Defesa contra Strings Vazias na Função de Análise

Na sua função analisar_resenhas, você utiliza o método .get("resenha_pt", "") para recuperar o texto traduzido. Uma boa prática para evitar que o seu separador (\n---\n) crie blocos vazios no texto final (caso alguma tradução falhe ou venha em branco) é filtrar a lista antes do join:

# Substituindo a linha do join para ignorar strings vazias ou nulas
resenhas_validas = [texto for texto in resenhas_unidas if texto.strip()]
texto_final = separador.join(resenhas_validas)

3. Modularização e Fechamento Seguro de Arquivos

O seu uso do gerenciador de contexto with open na leitura do arquivo TXT foi perfeito. Para projetos maiores, uma boa prática é encapsular essa primeira etapa de extração em uma função dedicada. Isso isola a lógica de parsing e limpa o fluxo principal do seu script:

def carregar_e_estruturar_resenhas(caminho_arquivo):
    dados = []
    with open(caminho_arquivo, "r", encoding="utf-8") as arquivo:
        for linha in arquivo:
            linha_limpa = linha.strip()
            if int(linha_limpa.count("$")) >= 2:
                partes = linha_limpa.split("$", 2)
                dados.append({
                    "reviewerID": partes[0],
                    "reviewerName": partes[1],
                    "reviewText": partes[2]
                })
    return dados

Parabéns pelo excelente nível técnico aplicado no tratamento de erros e no consumo da API do modelo de linguagem. Seu projeto final ficou digno de um desenvolvedor experiente em IA para Dados!

Espero que possa ter lhe ajudado!