Eu lembro da primeira vez que precisei padronizar extração de e-mails — gastei horas ajustando prompts. Com DSPy, mudei a abordagem: descrevo assinaturas (input/output) como classes em Python e deixo o framework procurar o melhor prompt. Neste post vou contar como isso mudou meu fluxo, mostrar exemplos com Sabia4 (modelo usado em PT-BR), dar dicas de otimização e discutir quando agentes são realmente úteis — tudo com uns devaneios e uma anedota sobre um bug que só apareceu em produção.
1) Por que DSPy muda o jogo (DSPy introdução)
Quando eu comecei a mexer com LLMs, eu caí no padrão de sempre: “interagir com modelo = escrever prompt”. E por um tempo isso funcionou. Só que, na prática, prompt vira uma string frágil: qualquer ajuste pequeno muda o resultado, fica difícil versionar, testar e repetir. Foi aí que eu entendi a proposta do DSPy: e se eu pudesse programar o comportamento da LLM sem ficar gerenciando prompt manualmente?
DSPy como framework declarativo (menos prompt, mais estrutura)
O DSPy (ou “DSPy”, como a maioria chama) é uma biblioteca em Python que traz um jeito diferente de construir soluções de IA generativa focadas em texto. A ideia central é tratar o sistema como código estruturado, com módulos e regras claras, em vez de um amontoado de prompts colados no projeto. Como o Cobus Greyling resume:
DSPy é um framework declarativo para construir sistemas AI modulares em vez de prompts frágeis. — Cobus Greyling
Na prática, isso muda o meu trabalho: eu paro de “tunar prompt” no braço e começo a declarar o que entra e o que sai, deixando a biblioteca otimizar o resto.
Assinaturas input/output: trocando strings por contratos
O coração dessa abordagem são as assinaturas input/output. Em vez de eu escrever um prompt gigante, eu descrevo um “contrato” do comportamento: quais campos entram, quais campos saem, e o que eu espero do modelo. Isso costuma ser feito como classes (muitas vezes no estilo Pydantic), o que deixa tudo mais legível e testável.
class ResumoAssinatura(Signature):
texto: str
resumo: str
Repara como isso é diferente de “prompt engineering”: eu não estou discutindo vírgula, tom e formatação numa string. Eu estou definindo um componente com entradas e saídas.
Compilation: o DSPy cria o prompt “ideal” para o seu caso
O detalhe que me fez virar a chave é que o prompt ainda existe, só que não sou eu que gerencio. O DSPy faz a signature compilation: ele compila essas assinaturas em prompts otimizados para aquele cenário, usando um dataset para treinar/testar e escolher o melhor caminho. Eu achei que era exagero… até ver a primeira compilação gerar um prompt melhor do que o que eu tinha escrito.
Benefício prático: menos retrabalho e mais reprodutibilidade
Menos mão-de-obra ajustando prompt na tentativa e erro.
Mais reprodutibilidade: assinaturas e dados viram referência do comportamento.
Mais modularidade: dá para trocar modelos (ex.: projetos em português com Sabia4) sem reescrever tudo.
Se você quiser a fonte oficial, eu recomendo começar por dspy.ai (link na descrição do vídeo), além do código e exemplos no GitHub.
2) Assinaturas em ação: input fields, output fields e compilação (assinaturas DSPy)
No DSPy, o “core” da ideia são as assinaturas DSPy. Eu gosto de pensar nelas como um contrato: eu descrevo o que entra e o que sai, e deixo o DSPy encontrar o melhor jeito de transformar uma coisa na outra. Em vez de eu ficar “na unha” escrevendo prompt, eu declaro a estrutura do processo.
Como escrever uma signature (assinatura campos)
Na prática, uma assinatura é uma classe em Python, normalmente estendendo BaseModel do Pydantic. Dentro dela, eu declaro input_field (campos de entrada) e output_field (campos de saída), com tipos e descrições. Isso é importante porque o DSPy usa essas informações para guiar a geração.
Descrever o comportamento de cada campo melhora a saída gerada pelo compilador DSPy. — trecho do vídeo
Por que descrever bem os campos ajuda (assinaturas input/output)
Quando eu capricho nas descrições dos campos, eu estou dando contexto para o compilador. E aí entra um ponto-chave: signature compilation. O DSPy pega a assinatura e compila isso em prompts e instruções otimizadas, reduzindo a necessidade de prompt manual e melhorando consistência. Ou seja: eu declaro o comportamento, e o DSPy monta o “prompt certo” para aquele objetivo.
Exemplo prático: triagem de tickets com ExtractTicket
Um caso bem comum é classificar tickets de suporte a partir de um e-mail. Aqui vai um exemplo simples:
from pydantic import BaseModel
import dspy
class ExtractTicket(BaseModel):
email: str = dspy.InputField(desc="Texto do e-mail do cliente")
nome: str = dspy.OutputField(desc="Nome do cliente, se aparecer")
problema: str = dspy.OutputField(desc="Problema principal em uma frase")
urgencia: int = dspy.OutputField(desc="Nível de urgência de 0 a 9")
sentimento: str = dspy.OutputField(desc="positivo, neutro ou negativo")
Repara como eu não escrevi “o prompt”. Eu só defini a assinatura campos e deixei claro o que cada saída significa. Isso é ouro para triagem e priorização automatizada.
Dica: use Enum no Pydantic para consistência
Para evitar variações (“negativo”, “ruim”, “insatisfeito”), eu limito valores com Enum:
from enum import Enum
class Sentimento(str, Enum):
positivo = "positivo"
neutro = "neutro"
negativo = "negativo"
Depois, eu uso sentimento: Sentimento na assinatura. Isso melhora validação, padroniza saída e ajuda quando eu treino com golden datasets ou bootstrapped demos para fortalecer os few-shots gerados na compilação.
3) Módulos essenciais: Predict, ChainOfThought e providers (módulos DSPy)
O que mais me chamou atenção no DSPy é que ele já nasce pensando em múltiplos modelos e em como eu descrevo comportamento como código. Em vez de eu ficar só “escrevendo prompt”, eu monto módulos e conecto tudo com um provider. Como o vídeo reforça, frameworks hoje já dão suporte a diferentes LLMs, e o DSPy entra forte nisso: OpenAI, Anthropic, Databricks, Gemini e também LLMs locais via OLAMA e LM Studio. Isso muda o jogo pra testar rápido, inclusive em PT-BR com o Sabia4 (“Sabiazinho 4”).
No final das contas, é literalmente criar uma classe que descreve o comportamento desejado e usar módulos existentes. — trecho do vídeo
Predict: o “chamando LM” mais direto
O dspy.Predict é o módulo mais simples: ele parece uma chamada comum de LLM. Eu uso quando quero inferência direta, tipo “dado X, gere Y”, sem muita cerimônia. É ótimo pra tarefas de extração, classificação e respostas curtas, onde eu só preciso do output final.
Quando usar: respostas objetivas, pipelines simples, baseline rápido.
Vantagem: menos complexidade e fácil de plugar em qualquer fluxo.
# exemplo conceitual
import dspy
extract = dspy.Predict("texto -> campos")
out = extract(texto="...")
ChainOfThought: reasoning e diagnóstico
Já o ChainOfThought entra quando eu quero mais transparência e capacidade de depurar. Ele tenta produzir um raciocínio intermediário (o famoso reasoning) e isso ajuda a entender por que o modelo chegou naquela resposta. Em muitos setups, dá pra inspecionar esse “miolo” via channel/atributos do retorno, o que é útil pra ajustar comportamento e identificar falhas.
Quando usar: problemas com múltiplos passos, lógica, validação, explicações.
Vantagem: melhora o diagnóstico e facilita otimização/tuning.
LM providers: um API unificado pra vários modelos
Os LM providers são a ponte entre seus módulos DSPy e o modelo de fato. Eu gosto porque posso trocar de provider sem reescrever tudo: OpenAI, Anthropic, Databricks, Gemini, e também rodar local com OLAMA ou LM Studio. No exemplo com Maritaca/Sabia4, a ideia é usar uma API “OpenAI-like”, mantendo o mesmo formato de chamada.
Provider | Uso típico |
|---|---|
OpenAI/Anthropic/Gemini | produção via API |
Databricks | ambiente corporativo |
OLAMA / LM Studio | testes locais e custo baixo |
Setup: config + .env (e venv)
Pra não virar bagunça, eu sigo a recomendação do vídeo: uso venv no Python e centralizo configuração. Normalmente eu deixo chaves e base URL num .env e aponto o DSPy pro provider escolhido (incluindo API_BASE quando for OpenAI-like).
# .env (exemplo)
API_KEY="..."
API_BASE="https://..."
4) Otimização automática: MIPRO, teleprompter e pipelines (otimizadores DSPy)
Uma das coisas que mais me chamou atenção no DSPy é que eu paro de “escrever prompt no braço” o tempo todo e começo a programar o comportamento como código. Em vez de ficar ajustando texto e torcendo pra dar certo, eu descrevo o que eu quero (via módulos e signatures) e deixo a biblioteca me ajudar a chegar no melhor resultado. E é aqui que entram os otimizadores DSPy: eles fazem o tuning automático para o meu desafio, com base em métricas e testes.
Otimizadores DSPy: MIPROv2 extensão e teleprompter
Na prática, os otimizadores pegam o meu programa (módulos + signature) e tentam melhorar a performance gerando:
Instruções automáticas (um “prompt” melhor, só que derivado do meu objetivo e dos dados).
Few-shot examples (demonstrações curtas e relevantes, escolhidas/geradas para aumentar acerto).
MIPROv2 melhora propostas de instruções e busca discreta para otimização. — trecho da documentação DSPy
Eu costumo pensar assim: MIPRO (MIPROv2) é forte para propor e refinar instruções (inclusive com essa ideia de “busca discreta”), enquanto o teleprompter brilha quando eu preciso compilar boas demos (few-shot) e organizar exemplos que “ensinam” o modelo a responder do jeito certo.
Compilação iterativa e otimização pipeline com métricas
O fluxo é bem direto: eu rodo uma teleprompter compile ou um otimizador como MIPROv2 em cima de um dataset (pode ser um golden dataset pequeno). A cada rodada, o DSPy avalia com uma métrica e recompila o programa, ajustando instruções e exemplos. Mesmo quando eu não tenho labels perfeitos, dá para usar heurísticas, validações parciais e checks de formato para guiar a otimização pipeline.
Exemplo bem simples de como isso aparece no código:
compiled = teleprompter.compile(program, trainset=golden, metric=score_fn)
Recursos para produção: do pipeline ao deploy
Além de otimizar, eu gosto que o ecossistema já pensa em produção: streaming de saída, caching para reduzir custo/latência, e caminhos mais claros para deploy e monitoramento. Isso ajuda quando o pipeline cresce (por exemplo, com RAG, etapas de extração, classificação e resposta), porque eu consigo evoluir o sistema sem virar uma colcha de retalhos.
Minha experiência: menos erro em tickets com poucas iterações
Num projeto de triagem de tickets, eu rodei tuning por algumas rodadas em um golden dataset (curto, mas bem revisado). Depois da compilação iterativa, eu vi uma queda clara nos erros de classificação, principalmente nos casos “cinzentos” (assuntos parecidos). O ganho veio mais de instruções melhores + few-shot examples do que de qualquer ajuste manual que eu fazia antes.
5) Agentes, hype e o que realmente usar (compilação programa)
Agora eu entro no item dois do projeto: agentes. É o termo da modinha, tá em alta, e sim, pode ser extremamente relevante. Mas eu bato na mesma tecla do vídeo: a gente precisa separar o que é tendência do que realmente melhora o nosso processo.
Precisamos sempre saber separar o que é hype do que é realmente útil dentro do processo. — trecho do vídeo
Quando “agente” é útil de verdade (e quando é só barulho)
Pra mim, agente vale a pena quando existe trabalho multi-step, com decisões no meio do caminho, e quando a programação modular ajuda a manter tudo legível e testável. Se o seu problema é “pegar um input e gerar um output”, muitas vezes um Predict ou um ChainOfThought bem definido já resolve, sem inventar uma orquestra inteira.
Casos práticos: onde eu vejo ganho real
Automação de triagem: classificar tickets, priorizar bugs, encaminhar para o time certo e pedir mais dados quando faltar contexto.
Workflows multi-step com RAG: buscar documentos, checar evidências, responder e registrar as fontes. Aqui, agentes podem coordenar etapas, mas o coração continua sendo RAG + avaliação.
Deploy e monitoramento: em produção, o “agente” só faz sentido se vier junto de deploy monitoring, cache, logs e métricas. Sem isso, você só automatiza erro em escala.
DSPy: menos mágica, mais engenharia (instruções automáticas)
O que eu gosto no DSPy é que ele puxa a conversa pra engenharia: signatures claras, módulos pequenos e otimização. Em vez de “prompt artesanal”, você pode usar instruções automáticas via otimizadores, e isso combina bem com pipelines mais complexos. Muitos tutoriais do ecossistema já cobrem RAG, otimizadores e core development pra apps de produção, além de integração com provedores de LM.
Minha regra prática: protótipo pequeno antes de comprar a promessa
Tem muita solução paga e curso vendendo “agentes milagrosos”. Eu prefiro um teste simples, com métrica e dataset mínimo. Começo assim:
Defino uma signature objetiva (inputs/outputs).
Provo o fluxo com
PredictouChainOfThought.Se precisar, componho módulos e só então penso em agente.
Faço compilação programa e avalio: melhorou custo, tempo, qualidade?
Se o ganho não for claro, eu não “agentifico” o sistema. Eu simplifico.
6) Wild cards: anedotas, analogias e um cenário hipotético
Uma anedota (bem real) sobre assinatura e um bug “fantasma”
Teve um dia em que eu jurei que o problema era o modelo. O pipeline funcionava lindo no meu ambiente, mas em produção aparecia um erro intermitente: respostas truncadas, campos faltando, e um “tom” diferente do esperado. Eu estava naquela fase clássica de “deve ser o prompt”, porque a gente cresce achando que interagir com LLM é só isso: escrever prompt e torcer.
O que resolveu foi quase anticlimático: eu formalizei a assinatura no DSPy e, na primeira rodada, percebi que um campo estava mal tipado. Era um detalhe bobo (string onde eu tratava como lista), mas em produção chegava com variações reais de dados. Quando eu trouxe isso pra uma assinatura clara, o erro deixou de ser “místico” e virou “engenharia”. Aí caiu a ficha do valor da programação modular: manutenção vira ajuste de contrato, não caça ao tesouro.
Uma analogia: DSPy como “compilador de prompts” (meu tutorial visão)
Hoje eu explico DSPy assim: é como um compilador de prompts. Você descreve o que quer (tipos, campos, critérios) e ele tenta transformar isso em instruções otimizadas pro modelo. Parece exagero até você ver a primeira compilação gerar um prompt melhor que o que você escreveu.
Pareceu exagero até eu ver a primeira compilação gerar um prompt melhor que o que eu escrevi. — relato pessoal
E aqui entra meu “tutorial visão”: em vez de ficar lapidando texto, eu lapido estrutura. Isso é o coração do framework declarativo: menos adivinhação, mais especificação.
Cenário hipotético: e se uma sprint de QA virasse um pipeline DSPy + golden dataset?
Imagina substituir uma sprint inteira de QA manual por um pipeline com DSPy, um golden dataset e métricas automáticas. Benefícios? Regressão todo dia, comparação objetiva, e menos fricção quando você troca um módulo. Eu já senti isso em PT-BR usando o Sabia4 (no lançamento recente do modelo SAB, dia 17): bom custo-benefício e respostas bem alinhadas ao nosso contexto.
Mas tem risco: teste automatizado não é sinônimo de verdade. Você pode “overfit” no golden dataset, e produção sempre inventa um caso novo. Então fica meu aviso: não teste em produção sem monitoramento. Log, métricas, amostragem humana e rollback precisam existir.
Confissão final: às vezes eu ainda escrevo prompt na mão
Pra fechar: eu ainda escrevo um prompt à mão de vez em quando, por nostalgia e por velocidade. Só que agora eu faço isso sabendo que, quando o projeto cresce, o caminho mais seguro é declarar contratos, modularizar e deixar o DSPy “compilar” o comportamento. Esse é o tipo de prática que faz LLM parar de ser truque e virar produto.
