S
Sant'Clear
  • Cursos
  • Blog
  • Serviços
  • Sobre
S
Sant'Clear

Desenvolvedor Full-Stack sênior focado em sistemas corporativos de alta complexidade e criticidade, com atuação prática em Java, Spring Boot, Angular, integrações e automação com IA.

Navegação
  • Engenharia de IA
  • Serviços
  • Sobre
Links
  • LinkedIn
  • GitHub
© 2026Sant'ClearConteúdo 100% gratuito
  1. Início
  2. Engenharia de IA
  3. Cap. 1

Neste artigo

  • Ambiente de Experimentação com Cursor e Jupyter
  • Extensões essenciais
  • Seleção do Kernel do Notebook
  • Preparação do Ambiente Python e Validação de Conectividade
  • Primeira Chamada de Inferência
  • Primeiro Projeto: Sumarização de Conteúdo Web
  • Arquitetura de Mensagens: Prompt de Sistema e Prompt de Usuário
  • Prompt de Sistema
  • Prompt de Usuário
  • Estrutura de Mensagens Esperada pela API
  • Estruturando as Mensagens da Aplicação
  • Consolidando o Pipeline de Inferência
  • Limitações da Extração Simples de Conteúdo Web
  • Desafio Prático
  • Fechamento da Etapa

Configurações e Experimentação com Modelos de Fronteira

Nesta etapa, realizaremos a transição do ambiente previamente configurado para a primeira interação programática com um modelo de linguagem de grande escala. O objetivo técnico é consolidar o uso do Cursor, dos Jupyter Notebooks e de um cliente compatível com a API da OpenAI para executar inferência inicial, compreendendo ao mesmo tempo a arquitetura de mensagens que governa o comportamento dos modelos de chat modernos.

Ao final deste processo, você terá percorrido o ciclo básico de uma aplicação orientada por LLM: entrada de dados, engenharia de mensagens, chamada de inferência e interpretação da saída.

Pré-requisito: antes de iniciar, conclua a configuração do ambiente descrita no README.md do repositório e valide que o projeto já está sincronizado no ambiente virtual .venv.


Ambiente de Experimentação com Cursor e Jupyter

Na engenharia aplicada de LLMs, a fase inicial de experimentação exige um ambiente que favoreça iteração rápida, isolamento de dependências e observabilidade do código em execução. Para isso, utilizaremos o Cursor como IDE principal e os Jupyter Notebooks (.ipynb) como interface de experimentação.

Extensões essenciais

Antes de executar qualquer notebook, valide se o ambiente do Cursor possui as extensões adequadas instaladas:

  1. No menu superior, acesse View > Extensions.
  2. Instale a extensão Python.
  3. Instale a extensão Jupyter.

Seleção do Kernel do Notebook

O Kernel é o processo responsável por executar o código do notebook. Em projetos com ambientes virtuais, selecionar o kernel incorreto é uma das causas mais comuns de falhas de importação e inconsistência de dependências.

Fluxo de seleção

  1. No canto superior direito do notebook, clique em Select Kernel.
  2. Escolha Python Environments....
  3. Selecione o interpretador associado ao ambiente virtual do projeto, normalmente algo como:
.venv/bin/python

ou, em ambientes Windows, o equivalente dentro da pasta .venv.

Nota Técnica: sempre que o projeto for sincronizado novamente, ou quando houver troca de máquina, conflito de cache ou atualização de dependências, vale revalidar o kernel ativo.


Preparação do Ambiente Python e Validação de Conectividade

Antes da primeira chamada de inferência, é recomendável validar se o endpoint que servirá o modelo está acessível. Em ambientes locais, isso é especialmente útil para detectar problemas de inicialização do Ollama ou incompatibilidades de rede entre o notebook e o serviço local.

A estratégia abaixo mantém o fluxo simples: criamos um cliente compatível com a API da OpenAI, apontamos para o endpoint local do Ollama e definimos funções utilitárias para verificar disponibilidade do serviço, confirmar a presença do modelo e executar chamadas com uma política mínima de repetição em caso de timeout inicial.

import os

from dotenv import load_dotenv
from IPython.display import Markdown, display
from openai import OpenAI, APIConnectionError, APITimeoutError, APIStatusError

from scraper import fetch_website_contents

load_dotenv()

URL_BASE_OLLAMA = "http://localhost:11434/v1"
TEMPO_LIMITE_PADRAO = 45.0
MODELO_PADRAO = "llama3.2:3b"


def verificar_modelo_disponivel(cliente: OpenAI, modelo: str = MODELO_PADRAO) -> bool:
    """
    Confirma se o modelo informado está carregado no Ollama.
    """
    try:
        modelos = {item.id for item in cliente.models.list().data}
    except (APIConnectionError, APITimeoutError, APIStatusError):
        return False

    return modelo in modelos


def gerar_chat_completion(
    cliente: OpenAI,
    mensagens: list[dict[str, str]],
    modelo: str = MODELO_PADRAO,
    tentativas: int = 2,
):
    """
    Envia mensagens ao modelo com retry simples para mitigar timeout de aquecimento.
    """
    ultima_excecao = None

    for tentativa in range(1, tentativas + 1):
        try:
            return cliente.chat.completions.create(
                model=modelo,
                messages=mensagens,
            )
        except APITimeoutError as erro:
            ultima_excecao = erro
            if tentativa < tentativas:
                print(f"⚠️ Timeout na tentativa {tentativa}/{tentativas}. Repetindo...")
            else:
                raise

    raise ultima_excecao

A primeira utilidade relevante é a criação do cliente apontando para o endpoint local do Ollama. Em seguida, realizamos uma verificação simples para garantir que o serviço responde corretamente.

def criar_cliente_ollama(
    url_base: str = URL_BASE_OLLAMA,
    tempo_limite: float = TEMPO_LIMITE_PADRAO,
) -> OpenAI:
    """
    Cria um cliente compatível com a API da OpenAI apontando para o Ollama local.
    """
    return OpenAI(
        base_url=url_base,
        api_key="ollama",  # Valor simbólico exigido pelo cliente
        timeout=tempo_limite,
    )


def verificar_endpoint_ollama(url_base: str = URL_BASE_OLLAMA) -> bool:
    """
    Verifica se o endpoint OpenAI-compatível do Ollama está acessível.
    """
    cliente = criar_cliente_ollama(url_base=url_base)

    try:
        cliente.models.list()
        print(f"OK: Endpoint disponível em {url_base}")
        return True

    except (APIConnectionError, APITimeoutError):
        print("Falha de conexão: confirme se o Ollama está ativo no ambiente local.")
        return False

    except APIStatusError as erro:
        print(f"Falha HTTP ({erro.status_code}): revise a configuração do serviço.")
        return False

Com a função definida, podemos executar a checagem inicial:

if verificar_endpoint_ollama():
    pass

Observação: neste estágio, o objetivo não é ainda resolver uma tarefa de negócio, mas apenas confirmar que o ambiente consegue se comunicar com o servidor que expõe o modelo.


Primeira Chamada de Inferência

Com o endpoint validado, podemos executar uma chamada mínima ao modelo apenas para confirmar que o pipeline está funcional. O objetivo aqui é testar a estrutura mais simples possível de mensagens, utilizando apenas uma entrada do usuário.

mensagem = "Oi, beleza, tudo suave?"

mensagens_teste = [
    {"role": "user", "content": mensagem}
]

mensagens_teste

Na sequência, instanciamos o cliente, confirmamos se o modelo está de fato disponível no Ollama e executamos a primeira inferência.

cliente_llm = criar_cliente_ollama()

# Para usar a API oficial da OpenAI, remova o base_url do cliente e carregue sua chave do arquivo .env
if not verificar_modelo_disponivel(cliente_llm, MODELO_PADRAO):
    raise RuntimeError(
        f"Modelo '{MODELO_PADRAO}' não encontrado no Ollama. Execute: ollama pull {MODELO_PADRAO}"
    )

resposta_teste = gerar_chat_completion(
    cliente=cliente_llm,
    modelo=MODELO_PADRAO,
    mensagens=mensagens_teste,
)

resposta_teste.choices[0].message.content

Esse teste simples já valida quatro pontos centrais do ambiente:

  • o cliente consegue se conectar ao endpoint;
  • o modelo escolhido está instalado;
  • a estrutura de mensagens está correta;
  • o ciclo de requisição e resposta do LLM está funcional.

Primeiro Projeto: Sumarização de Conteúdo Web

A seguir, integraremos um utilitário de extração de conteúdo web com a inferência do LLM. Esse padrão já representa a base arquitetural de muitos produtos reais: capturamos um conteúdo bruto, estruturamos a tarefa por meio de prompts e solicitamos ao modelo uma transformação útil sobre o material coletado.

Antes de definir a instrução enviada ao modelo, vale inspecionar o conteúdo textual retornado pela rotina de extração:

url_exemplo = "https://santclear.github.io/resume/"
conteudo_site = fetch_website_contents(url_exemplo)

print(conteudo_site[:2000])

Essa inspeção é importante porque permite verificar se o texto coletado é suficiente para a tarefa desejada, se há excesso de ruído e se a estratégia de extração adotada faz sentido para aquele tipo de página.


Arquitetura de Mensagens: Prompt de Sistema e Prompt de Usuário

Modelos de chat modernos operam sobre uma sequência estruturada de mensagens, normalmente representadas como uma lista de dicionários contendo role e content. Compreender essa estrutura é fundamental para controlar comportamento, formato de saída e objetivo da resposta.

Prompt de Sistema

O Prompt de Sistema define o enquadramento comportamental do modelo. É nele que estabelecemos o papel do assistente, o tom da resposta, as restrições de saída e os critérios que devem orientar a inferência.

No exemplo abaixo, o modelo é instruído a atuar como um analista técnico de conteúdo web, com foco em resumo curto, objetivo e limpo de elementos de navegação.

PROMPT_SISTEMA = """
Você é um analista técnico de conteúdo web.
Leia o texto extraído de um website e produza um resumo curto, claro e objetivo.
Ignore menus, cabeçalhos, rodapés e elementos de navegação.
Responda em markdown, sem envolver a resposta em bloco de código.
"""

Prompt de Usuário

O Prompt de Usuário representa a instrução variável da tarefa. Em produtos reais, essa mensagem frequentemente é enriquecida com contexto adicional antes de ser enviada ao modelo.

PROMPT_USUARIO_BASE = """
A seguir está o conteúdo textual de um website.
Produza um resumo breve da página.
Quando houver anúncios, lançamentos ou atualizações relevantes, destaque os pontos principais.
"""

A separação entre essas duas camadas é o que permite transformar um único modelo em múltiplos comportamentos especializados.


Estrutura de Mensagens Esperada pela API

A API espera receber as mensagens em uma lista estruturada como esta:

mensagens_exemplo = [
    {"role": "system", "content": "Você é uma calculadora simples e direta."},
    {"role": "user", "content": "1 / 13 = ?"}
]

resposta_exemplo = gerar_chat_completion(
    cliente=cliente_llm,
    modelo=MODELO_PADRAO,
    mensagens=mensagens_exemplo,
)

resposta_exemplo.choices[0].message.content

Esse exemplo é deliberadamente simples para evidenciar a estrutura esperada pela API: uma sequência ordenada de mensagens, cada uma com seu papel explícito na interação.


Estruturando as Mensagens da Aplicação

No caso da sumarização, encapsulamos a criação das mensagens em uma função simples. Isso melhora a legibilidade do notebook e prepara o código para reutilização em funções maiores.

def montar_mensagens_resumo(conteudo_site: str) -> list[dict[str, str]]:
    """
    Organiza as mensagens no formato esperado pela API de chat.
    """
    return [
        {"role": "system", "content": PROMPT_SISTEMA},
        {"role": "user", "content": PROMPT_USUARIO_BASE + conteudo_site},
    ]

Podemos inspecionar diretamente a estrutura gerada:

montar_mensagens_resumo(conteudo_site)

Esse tipo de inspeção intermediária é útil durante a fase de desenvolvimento porque ajuda a verificar se o contexto está sendo montado exatamente como o modelo irá recebê-lo.


Consolidando o Pipeline de Inferência

Agora reunimos todas as etapas em uma função única: coleta do conteúdo, montagem do contexto e chamada ao modelo. Mesmo sendo um exemplo simples, esse fluxo já representa a base de muitas aplicações reais de IA generativa.

def gerar_resumo_site(url: str, modelo: str = MODELO_PADRAO) -> str:
    """
    Extrai o conteudo textual de um site e solicita ao modelo um resumo em markdown.
    """
    conteudo_site = fetch_website_contents(url)

    resposta = gerar_chat_completion(
        cliente=cliente_llm,
        modelo=modelo,
        mensagens=montar_mensagens_resumo(conteudo_site),
    )

    return resposta.choices[0].message.content

Com a função definida, já podemos testar a geração do resumo em formato textual:

gerar_resumo_site("https://santclear.github.io/resume/")

Para melhorar a visualização no notebook, encapsulamos a exibição em uma segunda função que renderiza a resposta como Markdown:

def exibir_resumo_site(url: str) -> None:
    resumo = gerar_resumo_site(url)
    display(Markdown(resumo))

E então executamos a exibição renderizada:

exibir_resumo_site("https://santclear.github.io/resume/")

Esse pipeline consolida os blocos essenciais de uma aplicação baseada em LLM:

  1. coleta do dado bruto;
  2. estruturação do contexto;
  3. chamada ao modelo;
  4. exibição da saída processada.

Limitações da Extração Simples de Conteúdo Web

O utilitário fetch_website_contents funciona bem em páginas cujo conteúdo principal já está presente no HTML inicial. Em sites fortemente dependentes de JavaScript, renderização dinâmica ou mecanismos mais agressivos de proteção, essa abordagem pode não capturar a totalidade do conteúdo.

Nesses casos, a evolução natural da stack envolve ferramentas como Selenium ou Playwright, capazes de renderizar a página antes da extração.

A forma mais direta de observar essas diferenças é testar a mesma função em sites distintos:

exibir_resumo_site("https://iclnoticias.com.br/")
exibir_resumo_site("https://tvtnews.com.br/")

Ao comparar os resultados, o desenvolvedor passa a perceber que a qualidade da resposta do LLM depende não apenas do modelo, mas também da qualidade do texto de entrada obtido no processo de coleta.


Desafio Prático

Altere o PROMPT_SISTEMA para que o modelo assuma o papel de um consultor de produto SaaS e reanalise a mesma página sob a ótica de proposta de valor, clareza de posicionamento e maturidade comercial.

O objetivo deste exercício é observar, na prática, como a troca da camada de instrução altera a natureza da resposta, mesmo quando o conteúdo de entrada permanece idêntico.


Fechamento da Etapa

Ao concluir esta etapa, você terá estabelecido a base operacional para os próximos capítulos: validar um endpoint, estruturar mensagens, executar inferência e reaproveitar esse padrão para múltiplos problemas de negócio.

A partir daqui, a progressão natural é evoluir de chamadas simples para aplicações mais estruturadas, integrando APIs, orquestração de contexto e, posteriormente, arquiteturas mais sofisticadas baseadas em agentes e recuperação de informação.

AnteriorFundamentos de TransformersPróximaConstrução do primeiro produto de IA
Voltar ao programa do curso