dspy.Refine: autocorreção em runtime sem recompilar o modelo
O DSPy fora do modo offline: gera, avalia contra função de recompensa, e se não passar, tenta de novo - antes do critique_node entrar.
Nos posts sobre DSPy mostrei como o BootstrapFewShot compila um roteador que classifica intenção e extrai parâmetros estruturados. Esse é o DSPy no modo offline, onde você compila uma vez, versiona o JSON, e usa em produção.
Existe outro modo de usar o DSPy que não envolve compilação: o dspy.Refine. É autocorreção em runtime, em que o modelo gera uma resposta, avalia contra uma função de recompensa, e se não passar, tenta de novo.
Esse post é sobre como o módulo de geração de respostas do assistente usa dspy.Refine para garantir conformidade de output sem depender apenas do prompt.
O problema que o Refine resolve
O módulo de geração recebe o contexto retornado pelas tools (dados de catálogo, detalhes de produto) e produz a resposta final para o usuário.
Com um dspy.Predict simples, o fluxo é: LLM recebe contexto → gera resposta → entrega.
Se a resposta violar uma regra da área - como por exemplo imperativo de compra, omitir um aviso obrigatório, usar dados que não estão no contexto - o critique_node vai intervir depois. Isso adiciona latência e ainda pode resultar numa mensagem de fallback se a violação for severa.
Com dspy.Refine, a violação é detectada e corrigida dentro do próprio módulo de geração, antes de sair para o critique_node. O critique ainda existe como guardrail final, mas recebe respostas que já passaram por uma camada de autocorreção.
A função de recompensa: compliance como métrica de runtime
A função de recompensa é o coração do Refine. Ela avalia a resposta gerada e retorna um score entre 0.0 e 1.0. Se o score for menor que o threshold configurado, o Refine gera uma nova resposta.
Cada regra contribui com 0.25 para o score. Se o score total for menor que threshold=1.0, o Refine gera uma nova resposta, onde o DSPy injeta feedback automático sobre qual regra falhou.
O módulo: Refine sobre um Predictor com contador de tentativas
O contador de tentativas existe por dois motivos. O primeiro é o Langfuse: com advisor_refine_attempt e advisor_total_refine_attempts nos metadados da trace, você sabe exatamente em que proporção das requests o Refine precisou de mais de uma chamada. O segundo é o alerta: se total_attempts > 1 aparecer em mais de 20% das traces, o prompt da Signature precisa ser ajustado.
O trade-off de latência que precisa ser monitorado
dspy.Refine(N=3, threshold=1.0) pode fazer até 3 chamadas ao LLM por request.
Na prática, a maioria das requests passa na primeira tentativa, pois o prompt da Signature já foi escrito para evitar as violações mais comuns. Mas há casos onde o contexto retornado pelas tools contém dados que induzem o LLM a gerar frases que falham numa das regras.
O monitoramento no Langfuse filtra por advisor_total_refine_attempts > 1:
- Se esse filtro retornar menos de 5% das traces: o Refine está funcionando como safety net, não como correção habitual, está tudo bem.
- Se retornar entre 5% e 20%: investigar quais regras falham com mais frequência. Provavelmente a
Signatureprecisa de exemplos mais explícitos para aquelas regras. - Se retornar mais de 20%: o Refine está virando correção habitual em vez de safety net. Isso é sinal de que a
Signatureou o prompt estão desalinhados com os dados que chegam das tools.
A resposta para taxas altas não é aumentar N, e sim melhorar o prompt para que a primeira tentativa passe, e usar o Refine apenas para o residual.
Refine vs critique_node: camadas diferentes, não redundantes
Uma pergunta que aparece naturalmente: se existe dspy.Refine na geração, por que ainda existe o critique_node?
O Refine avalia a resposta gerada contra regras locais, as que estão na função de recompensa. O critique_node vai além:
- Detecta alucinações comparando a resposta com o contexto real retornado pelas tools
- Aplica regex de violações que o Refine não cobre (ex: disclaimer de FGC indevido para produtos específicos)
- Tem Circuit Breaker próprio para falhas de infraestrutura DSPy
O Refine é a primeira barreira, dentro do módulo de geração. O critique_node é a barreira final, fora dele. São camadas complementares, não redundantes.
O Refine melhora a taxa de aprovação no critique e o critique garante que nada passe mesmo quando o Refine falha ou é bypassado.
Na semana que vem: QueryBuilder, como construir queries FT.SEARCH a partir de um modelo Pydantic, com escaping de caracteres especiais e fuzzy matching com regras de tokenização.