A Capacidade de Autocorreção com Agentes de IA

Este artigo explora o conceito de reflexão em agentes de Inteligência Artificial, uma técnica que permite a um sistema autoavaliar e aprimorar iterativamente seu próprio trabalho, como a geração de código.

A Capacidade de Autocorreção com Agentes de IA

O processo de reflexão em agentes de Inteligência Artificial (IA) é a capacidade de um sistema analisar criticamente suas próprias ações, raciocínios e resultados, utilizando essa análise para aprimorar seu desempenho futuro. Trata-se de um análogo computacional da introspecção ou do pensamento deliberativo humano. Em vez de reagir de forma puramente instintiva, uma IA reflexiva faz uma pausa para avaliar o que foi feito, identificar erros ou etapas subótimas e, a partir daí, ajustar sua estratégia.

Este mecanismo permite que um agente de IA aprenda com suas próprias experiências sem a necessidade de novos dados de treinamento externos, criando um ciclo de autoaperfeiçoamento. Andrew Ng, uma figura proeminente na área de IA, considera a reflexão um dos pilares da IA agêntica, ao lado de planejamento, uso de ferramentas e colaboração multiagente. A reflexão transforma agentes de meros executores em colaboradores digitais capazes de raciocinar, aprender e evoluir continuamente, sendo crucial em tarefas onde a qualidade da resposta é crítica, como na geração de código complexo ou na elaboração de relatórios técnicos.

Aprimorando a Qualidade de Código com SLMs

O objetivo central deste estudo é avaliar se um agente de IA, construído sobre um Modelo de Linguagem Pequeno (SLM), pode utilizar uma arquitetura de reflexão para produzir código de alta qualidade. Especificamente, investigamos se o modelo Gemma 2 4B consegue, por meio de um processo iterativo de geração, crítica e refinamento, resolver um problema de programação de forma otimizada.

O desafio selecionado para este teste foi a implementação de uma função em Python para encontrar o n-ésimo número da sequência de K-Fibonacci. Nessa sequência, cada termo é a soma dos k termos anteriores, uma generalização da sequência de Fibonacci tradicional (onde k=2). Essa tarefa exige não apenas a correção lógica, mas também eficiência computacional, tornando-se um excelente caso de uso para testar a capacidade de otimização do agente.

Arquitetura de Reflexão com LangGraph

Para construir o agente reflexivo, foi adotada uma metodologia estruturada em três estágios, orquestrada pelo framework LangGraph. Este framework foi escolhido por sua abordagem baseada em grafos de estado, que oferece controle preciso, confiabilidade e observabilidade sobre fluxos de trabalho complexos.

Para garantir que a comunicação entre os nós do agente fosse estruturada e previsível, foram definidos modelos Pydantic. Esses modelos funcionam como um “contrato”, forçando o LLM a retornar saídas em formato JSON bem definido. O modelo Critique é particularmente importante, pois exige uma avaliação detalhada sobre erros, eficiência e sugestões de melhoria.

A arquitetura do agente foi dividida nos seguintes componentes:

  1. Nó Gerador (generator_node): Este é o ponto de partida. Ele recebe a solicitação do usuário (o problema do K-Fibonacci) e utiliza o LLM (Gemma 2 4B) para gerar uma primeira versão da solução, ou "rascunho".
class DraftCode(BaseModel):
    """Schema for the initial code draft generated by the agent."""
    code: str = Field(description="The Python code generated to solve the user's request.") 
    explanation: str = Field(description="A brief explanation of how the code works.")
    
def generator_node(state):
   """Generates the initial draft of the code."""
   generator_llm = llm_gemma.with_structured_output(DraftCode)
   prompt = f"""You are an expert Python programmer. Provide a function in python to solve the following request.
    Provide a simple, clear implementation and an explanation.
    Request: {state['user_request']}
    """
   draft = generator_llm.invoke(prompt)
   return {"draft": draft.model_dump()}
  1. Nó Crítico (critic_node): Após a geração do rascunho, este nó assume o papel de um revisor técnico. Ele avalia o código inicial com base em critérios como correção, eficiência, clareza e conformidade com as boas práticas (PEP 8). A saída é um objeto Critique estruturado, fornecendo feedback acionável.
class Critique(BaseModel):
    """Schema for the self-critique of the generated code."""
    has_errors: bool = Field(description="Does the code have any potential bugs or logical errors?") 
    is_efficient: bool = Field(description="Is the code written in an efficient and optimal way?") 
    suggested_improvements: List[str] = Field(description="Specific, actionable suggestions for improving the code.")
    critique_summary: str = Field(description="A summary of the critique.")def critic_node(state):
    """Critiques the generated code for errors and inefficiencies."""

    critic_llm = llm_gemma.with_structured_output(Critique)

    code_to_critique = state["draft"]["code"]

    prompt = f"""You are an expert code reviewer and senior Python developer. Your task is to perform a thorough critique of the following code.
    
    Analyze the code for:
    1.  **Bugs and Errors:** Are there any potential runtime errors, logical flaws, or edge cases that are not handled?
    2.  **Efficiency and Best Practices:** Is this the most efficient way to solve the problem? Does it follow standard Python conventions (PEP 8)?
    
    Provide a structured critique with specific, actionable suggestions.
    
    Code to Review:
    ```python
    {code_to_critique}
    ```
    """
    critique = critic_llm.invoke(prompt)
    return {"critique": critique.model_dump()}

2. Nó Refinador (refiner_node): Este nó recebe o código original e a crítica estruturada para produzir uma versão final e aprimorada da solução.

class RefinedCode(BaseModel):
    """Schema for the final, refined code after incorporating the critique."""
    refined_code: str = Field(description="The final, improved Python code.")
    refinement_summary: str = Field(description="A summary of the changes made based on the critique.")def refiner_node(state):
    """Refines the code based on the critique."""

    refiner_llm = llm_gemma.with_structured_output(RefinedCode)

    draft_code = state["draft"]["code"]
    critique_suggestions = json.dumps(state["critique"], indent=2)

    prompt = f"""You are an expert Python programmer tasked with refining a piece of code based on a critique.
    
    Your goal is to rewrite the original code, implementing all the suggested improvements from the critique.
    
    **Original Code:**
    ```python
    {draft_code}
    ```
    
    **Critique and Suggestions:**
    {critique_suggestions}
    
    Propose the final solution based on critique and suggestions
    """

    refined_code = refiner_llm.invoke(prompt)
    return {"refined_code": refined_code.model_dump()} ```

3. Avaliação Quantitativa: Para medir objetivamente a melhoria, um LLM “juiz” imparcial (o modelo mais atual da OpenAI para programação codex-5) foi utilizado para pontuar o rascunho inicial e a versão final em três quesitos: correção, eficiência e estilo.

class CodeEvaluation(BaseModel):
    """Schema for evaluating a piece of code."""
    correctness_score: int = Field(description="Score from 1-10 on whether the code is logically correct.")
    efficiency_score: int = Field(description="Score from 1-10 on the code's algorithmic efficiency.")
    style_score: int = Field(description="Score from 1-10 on code style and readability (PEP 8).")
    justification: str = Field(description="A brief justification for the scores.")def llm_judge(code_to_evaluate: str, initial_input: str):
    judge_llm = llm_openai.with_structured_output(CodeEvaluation)
    prompt = f"""You are an expert judge of Python code. 
    Evaluate the following function on a scale of 1-10 for correctness, efficiency, and style. 
    Provide a brief justification.
    
    What was Requested: {initial_input['user_request']}
    
    Code:
    ```python
    {code_to_evaluate}
    ```
    """
    return judge_llm.invoke(prompt)

O fluxo de trabalho segue o padrão de reflexão clássico:

def graph_builder():
    graph_builder = StateGraph(ReflectionState)
    
    graph_builder.add_node("generator", generator_node)
    graph_builder.add_node("critic", critic_node)
    graph_builder.add_node("refiner", refiner_node)

    graph_builder.set_entry_point("generator")
    graph_builder.add_edge("generator", "critic")
    graph_builder.add_edge("critic", "refiner")
    graph_builder.add_edge("refiner", END)

    reflection_app = graph_builder.compile()
    reflection_app.get_graph().draw_mermaid_png()
    final_state = None
    for state_update in reflection_app.stream(initial_input, stream_mode="values"):
        final_state = state_update
    return final_state

O Impacto da Reflexão no Problema K-Fibonacci

Para avaliar a eficácia da arquitetura de reflexão, o agente de IA foi desafiado a implementar uma função para calcular o n-ésimo número da sequência de K-Fibonacci. O processo iterativo de geração, crítica e refinamento demonstrou uma melhoria substancial, embora com algumas nuances importantes.

Rascunho Inicial

O agente, em sua primeira tentativa, produziu um código que continha erros lógicos fundamentais para uma sequência de K-Fibonacci genérica e era computacionalmente ineficiente.

def find_kth_fibonacci(n, k):
    """Finds the nth K-Fibonacci number."""
    if n < 0 or k <= 0:
        raise ValueError("n must be non-negative and k must be positive.")
    if n == 0:
        return 0
    if n == 1:
        return 1
    if n == k:
        return 1
    
    fib_sequence = [0] * (n + 1)
    fib_sequence[0] = 0
    fib_sequence[1] = 1
    
    for i in range(2, n + 1):
        fib_sequence[i] = sum(fib_sequence[i-k:i])
        
    return fib_sequence[n]

A avaliação do “juiz” imparcial revelou as deficiências desta primeira versão:

  • Pontuação de Correção: 2/10. A lógica falha para k > 2, pois a forma como os termos iniciais são tratados e somados está incorreta para uma sequência de K-Fibonacci generalizada.
  • Pontuação de Eficiência: 4/10. A complexidade de tempo é de O(n*k) devido ao uso repetido da função sum() em fatias da lista dentro do loop. A complexidade de espaço é de O(n), o que é desnecessário.
  • Pontuação de Estilo: 3/10. A docstring estava mal formatada e os exemplos e comentários eram inconsistentes com o comportamento real da função.

Crítica do Agente

O nó crítico analisou o rascunho e gerou um feedback detalhado, destacando os seguintes problemas:

  • Bug de Lógica: Apontou que a condição if n == k: return 1 estava incorreta e que o cálculo principal não seguia a definição correta da sequência.
  • Ineficiência: Identificou a alta complexidade de tempo (O(n*k)) e de espaço (O(n)), sugerindo uma abordagem de programação dinâmica com uso de memória otimizado para O(k).
  • Validação de Entrada: Notou a falta de tratamento para o caso em que k é maior que n, o que causaria um IndexError.
  • Boas Práticas: Recomendou melhorias na docstring para maior clareza e conformidade com as diretrizes do PEP 8.

Versão Final

Com base na crítica, o nó refinador produziu uma nova versão do código. Ele corrigiu a validação de entrada e tentou otimizar o cálculo, mas ao fazer isso, introduziu um novo erro lógico, implementando a recorrência F(n) = F(n-1) + F(n-k) em vez da soma dos k termos anteriores

def find_kth_fibonacci(n, k):
    """Finds the nth K-Fibonacci number.
    The K-Fibonacci sequence is defined as F(n) = F(n-1) + F(n-k).
    ...
    """
    if n < 0 or k <= 0:
        raise ValueError("n must be non-negative and k must be positive.")
    if k > n:
        raise ValueError("k cannot be greater than n.")
    if n == 0:
        return 0
    if n == 1:
        return 1
    if n == k:
        return 1
        
    fib_sequence = [0, 1]
    for i in range(2, n + 1):
        next_fib = fib_sequence[i-1] + fib_sequence[i-k]
        fib_sequence.append(next_fib)
        
    return fib_sequence[n]  

A avaliação desta versão final mostrou um quadro misto:

  • Pontuação de Correção: 2/1. Embora tenha tentado corrigir, a lógica implementada ainda está incorreta para a definição padrão de K-Fibonacci (soma dos k termos anteriores).
  • Pontuação de Eficiência: 8/10. A complexidade de tempo foi melhorada para O(n), pois a operação de soma foi substituída por uma adição simples.
  • Pontuação de Estilo: 6/10. O estilo geral e a estrutura do código melhoraram, mas a docstring agora descreve incorretamente a lógica implementada.

Análise Comparativa dos Resultados

Versão | Correção (0-10) | Eficiência (0-10) | Estilo (0-10)
—————————+—————————-+--————————--+----------------
Rascunho Inicial | 2 | 4 | 3
Versão Final | 2 | 8 | 6

A tabela evidencia que o processo de reflexão conseguiu dobrar a pontuação de eficiência, ao identificar e corrigir o gargalo de performance do sum() repetido. O estilo do código também melhorou. No entanto, a pontuação de correção permaneceu baixa, indicando que, ao tentar otimizar, o agente se desviou ainda mais da lógica correta do problema, um efeito colateral que destaca a complexidade da tarefa de autoaperfeiçoamento.

Quando a Reflexão Pode Falhar

Apesar do sucesso no primeiro teste, a arquitetura de reflexão não é uma solução universal. Para avaliar seus limites, o mesmo agente foi testado em outro problema clássico do LeetCode: “Two Sum” (encontrar dois números em uma lista que somam um alvo específico).

Neste caso, os resultados foram inversos:

A primeira versão gerada pelo agente já era uma solução ótima, utilizando um hash map para alcançar uma complexidade de tempo de O(n).

Pontuação do Juiz: Correção: 10, Eficiência: 10, Estilo: 8.

Ao tentar “melhorar” um código que já era excelente, o nó refinador introduziu um erro de sintaxe em uma docstring, tornando a função inutilizável.

Pontuação do Juiz: Correção: 1, Eficiência: 9, Estilo: 5.

Essa comparação demonstra um ponto crítico: o processo de reflexão pode ser prejudicial se aplicado a uma solução que já é ótima, um fenômeno de “excesso de correção”. Isso destaca a importância de avaliar o contexto de cada problema antes de aplicar o ciclo de refinamento.

Conclusão

A arquitetura de reflexão se mostra uma técnica poderosa e promissora para aprimorar a qualidade das saídas geradas por IA, especialmente ao utilizar modelos de linguagem menores (SLMs) em tarefas complexas. Ela permite que os agentes corrijam erros lógicos e melhorem a robustez de suas soluções de forma autônoma.

Contudo, a sua eficácia não é garantida e depende criticamente do problema, da qualidade da geração inicial e da engenharia de prompts que guia os nós de crítica e refinamento.

Como próximos passos, a pesquisa pode explorar:

  • Mecanismos de Gatilho: Desenvolver um nó “avaliador” inicial para decidir se o ciclo de reflexão é realmente necessário, evitando a degradação de soluções já otimizadas.
  • Engenharia de Prompt Avançada: Aprimorar os prompts dos nós crítico e refinador para evitar a introdução de novos erros.
  • Experimentação Ampla: Aplicar o padrão de reflexão a uma gama mais vasta de problemas, incluindo planejamento, redação e outras tarefas cognitivas complexas.
Repositório: https://gitlab.com/Jairodrigues/agent-reflection

Referências:

  1. https://huggingface.co/blog/Kseniase/reflection
  2. https://arxiv.org/html/2510.09244v1
  3. https://www.akira.ai/blog/reflection-agent-prompting
  4. https://galileo.ai/blog/self-evaluation-ai-agents-performance-reasoning-reflection
  5. https://skyone.solutions/blog/ia/agente-ia/
  6. https://www.promptingguide.ai/techniques/reflexion
  7. https://blog.langchain.com/reflection-agents/
  8. https://www.deeplearning.ai/the-batch/agentic-design-patterns-part-2-reflection/
  9. https://levelup.gitconnected.com/building-17-agentic-ai-patterns-and-their-role-in-large-scale-ai-systems-f4915b5615ce#77d6