Inteligência Artificial

4 fev, 2026

Desenvolver aplicações de AI com o melhor prompt e contexto

Publicidade

Quem já esta atuando com inteligência artificial desde 2022 sabe que por muito tempo (e ate hoje) o contexto e pergunta fornecido a IA é extremamente importante, principalmente em grandes contextos como chatbot corporativos, multiagentes e fluxos complexos de automação, qual quer virgula, letra maiúscula, mudança de palavra que para nós é meramente igual pode quebrar toda a performance e confiabilidade final.

Passamos para a fase de se aprofundar em prompt com a tal da “engenharia de prompt” que inclusive a Anthropic lançou seu curso gratuito que na tradução seria algo como Fluência em prompt ( https://www.anthropic.com/learn/claude-for-you) eu mesmo terminei o curso e percebi o quão complexo e bem estruturado deve ser os contextos para nossos agentes e suas instruções. Recentemente a mais ou menos 3 semanas venho estudando um framework em python chamado Dspy que tem como principal objetivo abstrair essa complexidade mudando para uma abordagem de programação modular e não de forma manual com prompts.

OBS: Ao final vou disponibilizar um projeto completo funcional no github.

Como surgiu o Dspy:

Ele foi desenvolvido por alunos De Stanford (pra variar rsrsrs) de forma modular, ou seja, você tem assinaturas (que são uma espécie de contratos), módulos que são a forma algoritmia de definir qual tipo de estratégia de raciocinou será utilizado, como CoT (Chain Of Thought), few-shot, ReAct que é basicamente raciocinar e agir entrou outros. Portanto o Dspy surgiu para resolver a fragilidade e a falta de escalabilidade referente ao prompt engineer manual, mas mais na frente vai ficar muito claro.

Motivadores para se utilizar o Despy:

1° Inadequação do “Prompt Engineering”:Os fundadores notaram que o desenvolvimento de aplicações de IA era baseado em tentativas A/B até acertar o prompt e descobriram strings estáticas e frágeis, com o Despy você definir a entrada e a saída e ele se encarrega de encontrar o melhor prompt e salvar.

2° Portabilidade entre Modelos: Um prompt otimizado pelo Dspy pode mudar facilmente entre modelos, seja GPT, Gemini, Kimi etc.. Isso por que o Dspy aprende novamente o melhor prompt para seu cenário e modelo.

3° Programabilidade: Transformar o design de sistemas de IA em algo próximo da engenharia de software onde grandes frameworks como Langchain, Crew.AI e SDKs são próximos a engenharia de software isso deixa mais suave e familiar.

4° Auto-refinamento: O modelo recebe uma nova chance de gerar a saída, agora ciente do erro anterior e das instruções de correção, transformando a inferência em um processo de “autocura”.

Em resumo, o DSPy nasceu da pergunta: ” Podemos projetar programas de LLM que aprendam a se aprimorar sozinhos em vez de reescrevermos prompts manualmente”.

Conceitos principais do Dspy:

Signatures:Declaram a tarefa (entrada/saídas) sem especificar como o modelo deve realizá-la.

from dspy import ReAct

class QAWithReAct(dspy.Signature):
    """Responder perguntas usando ferramentas externas quando necessário."""
    question: str = dspy.InputField()
    answer: str = dspy.OutputField(desc="Resposta final para o usuário")

Modules: Define a estratégia que são blocos de construção reutilizáveis que encapsulam técnicas de raciocínio como ‘ChainOfThought’ e ‘ReAct’.

class CoTSentimentClassifier(dspy.Module):
    def __init__(self):
        super().__init__()
        self.cot = dspy.ChainOfThought(SentimentSignature)

    def forward(self, sentence: str) -> dspy.Prediction:
        # O LM é induzido a “pensar passo a passo” antes de dar o rótulo.
        return self.cot(sentence=sentence)

Optimizers: A Otimização são algoritmos que ajustam automaticamente os prompts para maximizar uma métrica de avaliação definida pelo usuário:

 # Otimizador
   optimzer = dspy.BootstrapFewShot(
       metric=sentiment_accuracy,
       max_bootstrapped_demos=4
   )

Essa estrutura aumenta drasticamente a precisão em tarefas complexas, como problemas matemáticos.

Conteúdo do artigo
Imagem gerada por Airton Lira Junior – 30/01/2026.

DSPy, a coleta de dados

Esta é a base que permite a transição do ajuste manual de prompts para a otimização sistemática. Diferente do treinamento tradicional de deep learning que exige milhares de registros, o DSPy é projetado para funcionar com conjuntos de dados muito pequenos, muitas vezes necessitando de apenas 5 a 10 exemplos para começar a gerar resultados robustos.

1. A Unidade Básica: dspy.Example

Toda a estrutura de dados no framework gira em torno do objeto dspy.Example. Ele funciona como um dicionário Python especializado que armazena os campos de entrada e as saídas esperadas para o seu programa

2. Definição de Entradas com .with_inputs()

Ao coletar seus dados, você deve informar explicitamente ao framework quais campos são as entradas da tarefa utilizando o método .with_inputs(). Isso é crucial para que os otimizadores saibam quais informações estarão disponíveis para o modelo no momento da inferência e quais devem ser geradas ou aprendidas

Por exemplo:

 def _format_for_dspy(self, df: pd.DataFrame) -> list[Example]:
        """Formats a DataFrame into a list of dspy.Example objects."""
        formatted_examples = []
        for _, row in tqdm(df.iterrows(), total=df.shape[0], desc="Formatting examples"):
            example = Example(
                text=row['text'],
                sentiment=row['sentiment']
            ).with_inputs("text")
            formatted_examples.append(example)
        return formatted_examples

3. Dados Não Rotulados e Bootstrapping:

Uma das maiores vantagens do DSPy é a capacidade de trabalhar com dados incompletos ou sem rótulos. Se você possuir apenas as perguntas (inputs), o compilador pode utilizar um modelo de linguagem mais forte (como o GPT-4o) atuando como um “professor” para gerar automaticamente as cadeias de raciocínio e respostas corretas (traços) durante o processo de bootstrapping. Esses traços bem-sucedidos tornam-se, então, o conjunto de treinamento para otimizar modelos menores ou mais eficientes. Da hora não?

Para quem não entendeu essa questão de “professor” e bootstraping deixa eu simplificar:

DSPy permite otimizar programas de IA sem precisar de dados prontos com respostas. Vou quebrar isso em partes simples. Normalmente, para treinar IA você precisa de:

Pergunta: "Qual a capital da França?" → Resposta: "Paris"
Pergunta: "2+2=?" → Resposta: "4"

Mas e se você só tem as perguntas? Sem respostas prontas.

Como o DSPy Resolve (Bootstrapping)

1. Você dá só as perguntas

perguntas = [
    "Qual a capital da França?",
    "Quanto é 2+2?",
    "Explique gravidade"
]

2. DSPy usa um “Professor” (LM forte)

  • Configura GPT-4o (ou Gemini Pro) como teacher_settings
  • Esse professor inventa as respostas + raciocínio:
Pergunta: "Qual a capital da França?"
Professor GPT-4o gera:
→ Raciocínio: "França é um país europeu..."
→ Resposta: "Paris"

3. Gera “traços” automáticos

Traço 1: pergunta → [raciocínio] → Paris ✓ (funciona bem)
Traço 2: pergunta → [raciocínio ruim] → Londres ✗ (descarta)
Traço 3: pergunta → [raciocínio] → Paris ✓ (guarda)

4. Filtra os “bons traços”

DSPy testa cada traço gerado:

  • Funcionou? → Guarda como “few-shot example”
  • Falhou? → Descarta

5. Um código simples na prática:

# 1. Só perguntas (sem respostas)
trainset = [{"question": "Capital da França?"} for _ in range(20)]

# 2. Professor GPT-4o gera respostas
teleprompter = BootstrapFewShot(metric=validate_qa)  # usa GPT-4o como teacher

# 3. Otimiza Llama3.2 local
compiled = teleprompter.compile(program, trainset=trainset)

3.Configuração e Flexibilidade de Modelos:

Independência de Modelo: Capacidade de alternar entre APIs remotas (OpenAI, Anthropic) e modelos locais via Ollama ou SGLang.

Configuração Centralizada: Uso do dspy.settings.configure para gerenciar LMs e modelos de recuperação (RM) globalmente

DSPy permite trocar modelos de IA com 1 linha de código. Não importa se é OpenAI, local ou Google.

1. Independência de Modelo (Plug & Play)

Mesma lógica, LMs diferentes:

# Seu programa DSPy (igual sempre)
classifier = SentimentClassifier()

# === OPÇÃO 1: OpenAI caro (produção) ===
lm_openai = dspy.OpenAI(model='gpt-4o-mini')
dspy.settings.configure(lm=lm_openai)
result1 = classifier("Gostei muito!")  # Usa GPT-4o-mini

# === OPÇÃO 2: Modelo LOCAL grátis (teste) ===
lm_local = dspy.OllamaLocal(model='llama3.2:1b', base_url='http://localhost:11434')
dspy.settings.configure(lm=lm_local)
result2 = classifier("Gostei muito!")  # Usa Llama LOCAL

# === OPÇÃO 3: Google Gemini (rápido/barato) ===
lm_gemini = dspy.Google(model='gemini-1.5-flash')
dspy.settings.configure(lm=lm_gemini)
result3 = classifier("Gostei muito!")  # Usa Gemini

Resultado: result1.sentiment, result2.sentiment, result3.sentiment usam o MESMO código, só mudando a config.

Um lugar controla TUDO:

# Config global (válida para TODO o programa DSPy)
dspy.settings.configure(
    lm=dspy.OpenAI(model='gpt-4o-mini'),     # LM principal
    rm=dspy.FaissRM(                         # Retriever para RAG
        embedding_model=dspy.OpenAI(model='text-embedding-3-small')
    ),
    cache=False,                              # Cache de chamadas
    temperature=0.7                           # Criatividade
)

# Agora TODO programa usa essa config automaticamente
program1 = ChainOfThought(signature)
program2 = MIPROv2(metric)
# Ambos usam GPT-4o-mini + Faiss automaticamente

Lista de LMs Suportados (2026):

Remotos:

– OpenAI: gpt-4o, gpt-4o-mini

– Anthropic: claude-3.5-sonnet

– Google: gemini-2.0-pro

– Mistral: mistral-large

Locais:

– Ollama: llama3.2, phi3, gemma2

– SGLang: serve qualquer modelo local

– vLLM: alta performance local

Aplicações Práticas e Estudos de Caso:

RAG (Geração Aumentada de Recuperação): Construção de pipelines de busca e resposta otimizáveis.

Raciocínio Multi-hop: O uso do módulo SimplifiedBaleen para tarefas complexas que exigem múltiplas etapas de busca.

Text-to-SQL e Classificação: Exemplos de como o DSPy lida com extração de dados estruturados e tarefas de negócios como análise de NPS

Asserções e Sugestões (Assertions & Suggestions): Imposição de restrições computacionais em tempo de execução com mecanismos de backtracking (retrocesso).

Módulo Refine: O sucessor das asserções para o auto-refinamento iterativo de saídas baseado em feedback

Um exemplo que gosto muiot que é o Text-to-SQL:

import dspy
import sqlite3
from typing import List

# Configuração (troque pela sua API)
dspy.settings.configure(lm=dspy.OpenAI(model='gpt-4o-mini'))

# Schema do banco (exemplo e-commerce)
SCHEMA = """
Tabelas:
- products: id, name, price, category, stock
- orders: id, product_id, customer_id, quantity, order_date
- customers: id, name, email, city
"""

class TextToSQLSignature(dspy.Signature):
    """Gera SQL válido para consulta de banco de dados.
    
    Schema das tabelas:
    {SCHEMA}
    
    Use apenas SELECT, WHERE, JOIN, GROUP BY, ORDER BY.
    """
    question: str = dspy.InputField(desc="Pergunta em linguagem natural")
    sql_query: str = dspy.OutputField(desc="SQL válido e otimizado")

class SQLExecutor(dspy.Module):
    def __init__(self):
        super().__init__()
        self.generator = dspy.ChainOfThought(TextToSQLSignature)
        
    def forward(self, question: str, conn: sqlite3.Connection) -> dspy.Prediction:
        # Gera SQL
        sql_pred = self.generator(question=question)
        sql = sql_pred.sql_query.strip()
        
        try:
            # Executa e pega resultados
            cursor = conn.execute(sql)
            results = cursor.fetchall()
            columns = [desc[0] for desc in cursor.description]
            
            return dspy.Prediction(
                sql_query=sql,
                results=results,
                columns=columns,
                error=None
            )
        except Exception as e:
            return dspy.Prediction(
                sql_query=sql,
                results=[],
                columns=[],
                error=str(e)
            )

# Banco de teste em memória
def create_test_db():
    conn = sqlite3.connect(':memory:')
    conn.execute("""
        CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, price REAL, category TEXT, stock INTEGER);
        CREATE TABLE orders (id INTEGER PRIMARY KEY, product_id INTEGER, customer_id INTEGER, quantity INTEGER);
        CREATE TABLE customers (id INTEGER PRIMARY KEY, name TEXT, city TEXT);
    """)
    
    # Dados de teste
    conn.executemany("INSERT INTO products VALUES (?, ?, ?, ?, ?)",
        [(1, 'iPhone 15', 5000.0, 'eletronicos', 10),
         (2, 'Notebook Dell', 3000.0, 'eletronicos', 5),
         (3, 'Mesa Gamer', 800.0, 'moveis', 20)])
    
    conn.executemany("INSERT INTO customers VALUES (?, ?, ?)",
        [(1, 'João Silva', 'São Paulo'),
         (2, 'Maria Santos', 'Rio de Janeiro')])
    
    conn.executemany("INSERT INTO orders VALUES (?, ?, ?, ?)",
        [(1, 1, 1, 2), (2, 2, 1, 1), (3, 1, 2, 1)])
    
    return conn

# USO
if __name__ == "__main__":
    conn = create_test_db()
    sql_module = SQLExecutor()
    
    questions = [
        "Qual o produto mais caro?",
        "Quantos iPhones temos em estoque?",
        "Qual cliente comprou mais produtos?",
        "Total de vendas por categoria"
    ]
    
    for q in questions:
        result = sql_module(question=q, conn=conn)
        print(f"\n Pergunta: {q}")
        print(f"SQL: {result.sql_query}")
        print(f"Resultados: {list(zip(result.columns, result.results)) if result.results else 'OK'}")
        if result.error:
            print(f"Erro: {result.error}")
    
    conn.close()

Repare que no contrato (Signature) eu especifico apenas o que vai entrar e a saida esperada e ele se encarrega no do prompt:

 question: str = dspy.InputField(desc="Pergunta em linguagem natural")
    sql_query: str = dspy.OutputField(desc="SQL válido e otimizado")

A Saída será algo como:

Conteúdo do artigo

Certo e agora como otimizar para selecionar os melhores prompts? E ai que vem os optimizers…

Conteúdo do artigo
Conteúdo do artigo
Conteúdo do artigo
Conteúdo do artigo
Conteúdo do artigo
Conteúdo do artigo
Conteúdo do artigo
Conteúdo do artigo
Imagem durante o projeto do gitub.

O resultado será como:

Conteúdo do artigo
Imagem gerada durante a codificação do projeto

Resultado de performance esperado:

Score inicial: 40% SQLs inválidos
Score MIPROv2: 95% SQLs executam perfeitamente
Tempo: 5-10min (light mode)

O objetivo deste artigo foi, acima de tudo, despertar a sua curiosidade sobre como o DSPy está transformando o processo artesanal de “prompt engineering” em uma disciplina de engenharia de software rigorosa e sistemática. Ao longo desta exploração, vimos que a fascinante ideia central da biblioteca é tratar modelos de linguagem como funções parametrizadas dentro de um grafo computacional, permitindo que o comportamento do sistema seja definido por código estruturado em vez de strings frágeis.

Essa mudança de paradigma permite que os desenvolvedores se concentrem na lógica declarativa por meio de assinaturas e módulos reutilizáveis, delegando ao compilador do DSPy a tarefa de gerar instruções e exemplos otimizados para maximizar métricas específicas. Seja na construção de sistemas RAG robustos ou no desenvolvimento de agentes complexos, o framework oferece uma base para criar software de IA que é portátil entre diferentes modelos, confiável e capaz de se auto-aperfeiçoar com base em dados.

Melhores práticas para dividir datasets no Dspy:

As melhores práticas para dividir datasets no DSPy seguem uma lógica de engenharia de software rigorosa, adaptada para a natureza estocástica dos modelos de linguagem. Diferente do aprendizado profundo tradicional, o DSPy permite começar com volumes muito pequenos de dados, mas exige separação cuidadosa para garantir a generalização (ou modularização que mencionei anteriormente).

1. Separação Rigorosa de Conjuntos

É fundamental manter conjuntos distintos para evitar o overfitting (ajuste excessivo) dos prompts aos exemplos de treino. As fontes sugerem três divisões principais:

Trainset: Usado pelos otimizadores para realizar o bootstrapping (geração automática de exemplos de raciocínio) e ajustar as instruções.

Devset (ou Valset): Utilizado durante o processo de compilação por algoritmos de busca (como o Random Search) para selecionar qual versão do programa obteve a melhor pontuação na métrica

Testset: Reservado exclusivamente para a validação final, garantindo que as melhorias obtidas durante a otimização funcionem em dados nunca vistos pelo compilado

class SentimentMiproManager:
    def __init__(self, train_size=0.8): # Adicionado parâmetro de proporção
        
        full_dataset = sentiment_dataset_train()
        self.base_program = SentimentClassifier()

        if not full_dataset:
            print("Erro: Dataset vazio!")
            return
        
        # --- SEÇÃO DE SEPARAÇÃO (SPLIT) ---
        # Embaralhamos para garantir que a distribuição de classes seja aleatória
        random.seed(42) 
        random.shuffle(full_dataset)
        
        split_idx = int(len(full_dataset) * train_size)
        self.trainset = full_dataset[:split_idx]  # Usado para compilar/otimizar
        self.testset = full_dataset[split_idx:]   # Usado para avaliação final
        # ----------------------------------

        print(f"Dataset carregado: {len(self.trainset)} treino / {len(self.testset)} teste")
        self.compiled_program = None


def run_mipro_optimization(self, num_candidates: int = 2):
        if not dspy.settings.lm:
            raise ValueError("LM não configurado! Chame setup_llm() primeiro.")
        
        print(f"\n{'='*60}")
        print(f"Iniciando Otimização MIPROv2 com {len(self.trainset)} exemplos...")
        
        teleprompter = MIPROv2(
            metric=self._metric,
            prompt_model=dspy.settings.lm,
            task_model=dspy.settings.lm,
            auto="light",
            num_threads=1,
            verbose=False
        )     


# O MIPRO usa o trainset para criar os prompts e demonstrações
        self.compiled_program = teleprompter.compile(
            self.base_program,
            trainset=self.trainset,
            max_bootstrapped_demos=2,
            max_labeled_demos=4
        ) 

Tópicos avançados do MiProV2:

O MIPROv2 (Multi-prompt Instruction PRoposals Optimizer Version 2) é um dos otimizadores mais robustos do DSPy, projetado para sistemas de larga escala onde a precisão máxima é essencial. Ele se diferencia por ser “data-aware” (sensível aos dados) e “demonstration-aware” (sensível às demonstrações), otimizando simultaneamente as instruções em linguagem natural e os exemplos few-shot para cada módulo do programa

1. Para que serve?

O MIPROv2 serve para substituir o ajuste manual de prompts por um processo de otimização matemática. Ele é ideal para:

• Sistemas de produção onde cada ganho percentual de acurácia é valioso.

• Cenários com conjuntos de dados moderados a grandes (ex: mais de 200 exemplos para evitar overfitting).

• Situações onde o desenvolvedor deseja que o framework encontre as melhores instruções e os melhores exemplos de uma só vez.

2. Funcionamento Interno

O MIPROv2 opera através de um ciclo de três estágios principais:

1. Estágio de Bootstrapping (Inicialização): O otimizador executa o programa em várias entradas do conjunto de treino para coletar traços (traces) de comportamento de entrada e saída. Ele filtra esses traços, mantendo apenas aqueles que resultaram em pontuações altas de acordo com a métrica definida.

2. Estágio de Proposta Fundamentada (Grounded Proposal): O MIPROv2 analisa o código do programa, os dados e os traços coletados para redigir múltiplas variações de instruções para cada prompt individual no pipeline.

3. Estágio de Busca Discreta (Discrete Search): Utiliza Otimização Bayesiana para explorar o espaço de busca.

◦ Ele amostraminibatches do treino para avaliar combinações de instruções e traços.

◦ Ummodelo substituto (surrogate model) probabilístico é atualizado com os resultados, prevendo quais direções de busca são mais promissoras através de uma função de aquisição chamada Expected Improvement (EI).

3. Parâmetros Avançados:

O MIPROv2 permite um controle fino sobre o orçamento computacional e a estratégia de busca através dos seguintes parâmetros:

auto: Define configurações automáticas de hiperparâmetros. Pode ser “light” (rápido e barato), “medium” ou “heavy” (busca exaustiva).

metric: A função Python que avalia a saída e guia a otimização.

max_bootstrapped_demos: Define o número máximo de exemplos gerados automaticamente pelo “professor” a serem incluídos no prompt.

max_labeled_demos: Define o número máximo de exemplos do conjunto de treino (com rótulos reais) a serem incluídos no prompt.

minibatch_size: Tamanho do lote de dados usado em cada etapa da busca discreta para acelerar a avaliação.

minibatch_full_eval_steps: Frequência com que o otimizador realiza uma avaliação completa no conjunto de dados, em vez de apenas no minibatch.

num_threads: Número de threads para processamento paralelo durante a compilação.

prompt_model: O modelo de linguagem específico encarregado de gerar as propostas de novas instruções (pode ser um modelo mais forte que o modelo “estudante”).

teacher_settings: Configurações de LM para o programa “professor” que gera os traços iniciais durante o bootstrapping.

Em termos de resultados práticos, o uso do MIPROv2 em modo light elevou a acurácia de agentes ReAct de 24% para 51% e de sistemas de classificação de 62% para 82%

Conteúdo do artigo
Imagem gerada pelo material desse curso por Airton Lira Junior.

Esperamos ter demonstrado que a era das “tentativas e erros” manuais em prompts está sendo superada por um futuro onde a programação sistemática de modelos de fundação é o novo padrão para a inteligência artificial. O DSPy não é apenas uma ferramenta, mas um convite para reimaginar como construímos sistemas inteligentes de forma escalável e mensurável

Convido você a dar uma Star e seguir meu projeto de aprendizado do Dsypy:

Bebam agua, se exercitem e obrigado!

#IA #InteligenciaArtificial #MachineLearning #AI #GenerativeAI #DataScience #Tecnologia #Innovation #Python #Coding #SoftwareDevelopment #Programação #DevLife #PythonProgramming #Backend #DSPy #LLM #PromptEngineering #NLP #DeepLearning