Voltar ao blog
27 de maio de 20265 min de leitura

Feedback Loop em Orquestradores de Agentes: Aprendizado sem Machine Learning

ailearningmetricsprogramming

Toda vez que o orquestrador seleciona um agente e o resultado é bom, essa informação se perde. Na próxima requisição similar, ele toma a mesma decisão do zero, sem considerar que já acertou antes. E quando erra, erra de novo pelo mesmo motivo. O sistema não aprende. A solução é registrar cada decisão com seu resultado (sucesso ou falha, rating do usuário), acumular padrões ao longo do tempo, e usar esse histórico para melhorar decisões futuras. Não é machine learning pesado. É um sistema de feedback simples que calcula confiança por agente e sugere ações baseado em padrões temporais.

Guia de tópicos:

  • O Problema: Decisões sem Memória
  • Estrutura do Feedback
  • Métricas por Agente: Confiança Calculada
  • Padrões Temporais: Quando o Sistema Funciona Melhor
  • Sugestões Proativas Baseadas em Histórico
  • Maturidade do Sistema: De Nascente a Expert
  • Insights Agregados
  • Considerações Finais
  • Links Indicativos

O Problema: Decisões sem Memória

O orquestrador toma dezenas de decisões por sessão: qual agente usar, se precisa coordenar múltiplos agentes, qual workflow executar. Cada decisão pode ser boa ou ruim. Se o agente de "auth" foi selecionado para uma requisição sobre "validação de token" e o resultado foi bom, isso é informação valiosa. Na próxima vez que alguém pedir algo sobre tokens, o sistema deveria ter mais confiança em delegar para "auth".

Sem feedback loop, o sistema é stateless. Cada requisição é processada como se fosse a primeira. A seleção de agente depende apenas do input atual, sem considerar histórico. Isso significa que erros se repetem e acertos não são reforçados.

O que eu queria era simples: o sistema ficar melhor com o uso. Não precisa de treinamento, não precisa de fine-tuning, não precisa de GPU. Só precisa de um registro de "essa decisão funcionou" ou "essa decisão não funcionou" e um cálculo que use essa informação.


Estrutura do Feedback

Cada decisão gera um registro de feedback com contexto suficiente para análise posterior:

type DecisionFeedback struct {
    DecisionID string    `json:"decision_id"`
    Success    bool      `json:"success"`
    Timestamp  time.Time `json:"timestamp"`
    Context    string    `json:"context"`
    AgentUsed  string    `json:"agent_used"`
    UserRating int       `json:"user_rating"` // 1-5
}

O DecisionID é o identificador único da decisão (geralmente o span ID do sistema de observabilidade). Success é binário: o workflow completou sem erro ou não. Context é o input original do usuário. AgentUsed é qual agente foi selecionado. UserRating é opcional e vai de 1 a 5.

O rating é o sinal mais forte mas também o mais raro. A maioria dos usuários não vai dar rating explícito para cada interação. Por isso o Success — que é automático: se não deu erro, é sucesso — serve como proxy. Não é perfeito (um resultado sem erro pode ser ruim), mas é melhor que nada e não exige ação do usuário.


Métricas por Agente: Confiança Calculada

Cada agente acumula métricas de performance ao longo do tempo:

type AgentMetrics struct {
    TotalUses    int
    SuccessCount int
    AvgRating    float64
    LastUsed     time.Time
    Confidence   float64
}

func (al *AdvancedLearning) updateAgentMetrics(agent string, success bool, rating int) {
    metrics := al.agentPerformance[agent]
    metrics.TotalUses++
    metrics.LastUsed = time.Now()

    if success {
        metrics.SuccessCount++
    }

    // Média móvel do rating
    oldAvg := metrics.AvgRating
    metrics.AvgRating = (oldAvg*float64(metrics.TotalUses-1) + float64(rating)) / float64(metrics.TotalUses)

    // Confiança = success rate * usage weight
    successRate := float64(metrics.SuccessCount) / float64(metrics.TotalUses)
    usageWeight := math.Min(float64(metrics.TotalUses)/10.0, 1.0)
    metrics.Confidence = successRate * usageWeight
}

A confiança é o produto de dois fatores: taxa de sucesso e peso de uso. A taxa de sucesso é direta: se o agente acertou 9 de 10 vezes, tem 90% de success rate. O peso de uso evita que um agente com 1 uso e 100% de sucesso tenha confiança máxima. Ele precisa de pelo menos 10 usos para que o peso chegue a 1.0. Com 5 usos e 100% de sucesso, a confiança é 0.5 (100% × 5/10). Com 10 usos e 90% de sucesso, a confiança é 0.9 (90% × 10/10).

Isso resolve o problema de cold start: agentes novos começam com confiança baixa independente dos primeiros resultados. Conforme acumulam histórico, a confiança converge para o valor real de performance.


Padrões Temporais: Quando o Sistema Funciona Melhor

Uma observação interessante é que a performance do sistema varia com o horário e o dia da semana. Isso acontece porque serviços externos de LLM têm padrões de carga. De madrugada, menos gente usando, respostas mais rápidas e precisas. Em horário comercial, mais carga, mais timeouts.

O sistema detecta esses padrões automaticamente:

type TemporalPattern struct {
    Hour        int
    DayOfWeek   int
    Frequency   int
    SuccessRate float64
}

func (al *AdvancedLearning) analyzeTemporalPatterns() {
    patternMap := make(map[string]*TemporalPattern)

    for _, fb := range al.feedback {
        hour := fb.Timestamp.Hour()
        dayOfWeek := int(fb.Timestamp.Weekday())
        key := fmt.Sprintf("%d_%d", hour, dayOfWeek)

        if patternMap[key] == nil {
            patternMap[key] = &TemporalPattern{
                Hour:      hour,
                DayOfWeek: dayOfWeek,
            }
        }

        pattern := patternMap[key]
        pattern.Frequency++
        if fb.Success {
            pattern.SuccessRate = (pattern.SuccessRate*float64(pattern.Frequency-1) + 1.0) / float64(pattern.Frequency)
        } else {
            pattern.SuccessRate = (pattern.SuccessRate * float64(pattern.Frequency-1)) / float64(pattern.Frequency)
        }
    }
}

O sistema agrupa feedback por hora e dia da semana, e calcula a taxa de sucesso para cada slot temporal. Depois de algumas semanas de uso, ele sabe que segunda-feira às 14h tem 95% de sucesso mas sexta-feira às 18h tem 70%. Essa informação pode ser usada para ajustar timeouts (mais generoso em horários de pico) ou para avisar o usuário que respostas podem demorar mais em determinados períodos.


Sugestões Proativas Baseadas em Histórico

Com padrões temporais e métricas de agente, o sistema pode fazer sugestões antes do usuário pedir:

func (al *AdvancedLearning) GetProactiveSuggestions(context string) []string {
    now := time.Now()
    currentHour := now.Hour()
    currentDay := int(now.Weekday())

    suggestions := make([]string, 0)

    // Sugestões baseadas em padrões temporais
    for _, pattern := range al.patterns {
        if pattern.Hour == currentHour && pattern.DayOfWeek == currentDay && pattern.SuccessRate > 0.8 {
            suggestions = append(suggestions, fmt.Sprintf(
                "Based on patterns, this is a good time for %s operations", context))
        }
    }

    // Sugestões baseadas em performance de agentes
    scores := []agentScore{}
    for agent, metrics := range al.agentPerformance {
        score := metrics.Confidence * metrics.AvgRating / 5.0
        scores = append(scores, agentScore{agent, score})
    }

    sort.Slice(scores, func(i, j int) bool {
        return scores[i].score > scores[j].score
    })

    if len(scores) > 0 {
        suggestions = append(suggestions, fmt.Sprintf(
            "Consider using agent '%s' (confidence: %.2f)", scores[0].name, scores[0].score))
    }

    return suggestions
}

As sugestões aparecem antes do processamento começar. Se o sistema sabe que o agente "auth" tem confiança 0.95 para o tipo de requisição atual, ele sugere. Se sabe que o horário atual tem alta taxa de sucesso, ele informa. Não é intrusivo: são dicas opcionais que o usuário pode ignorar.


Maturidade do Sistema: De Nascente a Expert

O sistema classifica sua própria maturidade baseado na quantidade de feedback acumulado:

func (al *AdvancedLearning) calculateMaturity() string {
    if len(al.feedback) < 10 {
        return "nascent"
    } else if len(al.feedback) < 50 {
        return "developing"
    } else if len(al.feedback) < 200 {
        return "mature"
    }
    return "expert"
}

Isso é útil para calibrar expectativas. Um sistema "nascent" com 5 feedbacks não tem dados suficientes para sugestões confiáveis. Um sistema "expert" com 200+ feedbacks tem padrões estatisticamente significativos. A maturidade pode ser exibida num painel de insights para que o usuário saiba o quanto pode confiar nas sugestões.

Na prática, um usuário ativo atinge "developing" em uma semana e "mature" em um mês. "Expert" leva alguns meses de uso regular. Os thresholds são conservadores de propósito: é melhor o sistema dizer "ainda estou aprendendo" do que dar sugestões ruins com pouco dado.


Insights Agregados

O comando insights combina todas as métricas em um resumo:

func (al *AdvancedLearning) GetInsights() map[string]interface{} {
    totalFeedback := len(al.feedback)
    successCount := 0
    totalRating := 0.0

    for _, fb := range al.feedback {
        if fb.Success {
            successCount++
        }
        totalRating += float64(fb.UserRating)
    }

    return map[string]interface{}{
        "total_decisions":   totalFeedback,
        "success_rate":      float64(successCount) / float64(totalFeedback),
        "average_rating":    totalRating / float64(totalFeedback),
        "agent_performance": al.agentPerformance,
        "temporal_patterns": len(al.patterns),
        "learning_maturity": al.calculateMaturity(),
    }
}

O output para o usuário fica algo como: "21 decisões totais, 95.2% de sucesso, rating médio 4.2/5, maturidade: developing, 8 padrões temporais identificados". É um snapshot rápido de como o sistema está performando e evoluindo.


Considerações Finais

O sistema de aprendizado descrito aqui é deliberadamente simples. Não tem rede neural, não tem gradient descent, não tem nada que precise de GPU. É aritmética básica: contadores, médias e proporções. E funciona. A confiança por agente melhora a seleção. Os padrões temporais ajustam expectativas. As sugestões proativas economizam tempo.

O que importa levar deste artigo: "aprendizado" em software não precisa ser machine learning. Um sistema que registra decisões, calcula métricas simples, e usa essas métricas para informar decisões futuras já é um sistema que aprende. A barreira de entrada é zero: um slice de structs e algumas divisões.

A limitação principal é que tudo vive em memória. Se o processo reinicia, o aprendizado se perde. A evolução natural é persistir o feedback em um arquivo JSON e carregar no startup. São 10 linhas de código a mais e o sistema mantém seu conhecimento entre sessões.


Links indicativos: