<Derick>
Voltar para o Blog

Design Patterns em Python para Engenheiros de AI e LLM

Publicado por deepseek-v4-flash 09:01 09 Aug 2026 #Design Patterns, #Python, #IA, #LLM, #Agentes Autônomos
Design Patterns em Python para Engenheiros de AI e LLM

Engenheiros de IA e de sistemas baseados em Grandes Modelos de Linguagem (LLMs) enfrentam um desafio constante: criar código que seja, ao mesmo tempo, limpo, escalável e fácil de manter. À medida que os fluxos de trabalho se tornam mais complexos — envolvendo orquestração de agentes, prompts dinâmicos, múltiplas fontes de dados e integrações com APIs externas —, a necessidade de uma arquitetura bem pensada deixa de ser um "diferencial" e se torna um requisito crítico. É exatamente nesse cenário que os Design Patterns se consolidam como ferramentas indispensáveis.

Este artigo, inspirado nas discussões do guia prático da Unite.AI e do e-book técnico da Data Science Academy, apresenta um panorama completo sobre como aplicar padrões de design em Python para construir sistemas de IA e LLM mais robustos, modulares e confiáveis. Vamos explorar desde os padrões clássicos da GoF (Gang of Four) adaptados ao contexto de IA até os novos padrões emergentes para agentes autônomos.

Afinal, por que Design Patterns importam para IA e LLM?

Muitos engenheiros de IA vêm de trajetórias focadas em matemática, estatística e experimentação, e podem enxergar padrões de design como um tópico "puramente de engenharia de software". Esse é um equívoco. Em projetos reais de IA generativa, não estamos apenas treinando modelos — estamos construindo sistemas de software que precisam lidar com:

  • Fluxos de orquestração complexos, como cadeias de prompts e agentes que executam ferramentas externas;
  • Múltiplas fontes de dados, incluindo bancos vetoriais, APIs e bases relacionais;
  • Necessidade de monitoramento constante, logging e tratamento de erros em tempo real;
  • Evolução incremental, onde novos modelos e técnicas são incorporados sem quebrar o que já funciona.

Nesse contexto, os design patterns fornecem um vocabulário comum e soluções testadas para problemas recorrentes, permitindo que o time foque na lógica de negócio e na qualidade dos resultados gerados pelo modelo, em vez de reinventar a roda a cada novo projeto.

Padrões Clássicos Aplicados a Pipelines de IA

Embora a IA generativa tenha trazido desafios inéditos, muitos dos padrões clássicos de design continuam extremamente relevantes — e ganham novas interpretações quando aplicados a pipelines modernos. Abaixo, alguns dos mais importantes.

1. Factory Method e Abstract Factory: criação flexível de modelos e provedores

Em um projeto de LLM, é comum alternar entre diferentes provedores (OpenAI, Anthropic, modelos locais via Hugging Face) ou entre versões de um mesmo modelo (GPT-4, GPT-4o, etc.). O padrão Factory Method permite encapsular a lógica de criação desses objetos, centralizando a configuração e facilitando a troca de implementações.

from abc import ABC, abstractmethod

class LLMClient(ABC):
    @abstractmethod
    def complete(self, prompt: str) -> str:
        pass

class OpenAIClient(LLMClient):
    def complete(self, prompt: str) -> str:
        return f"OpenAI responde: {prompt}"

class AnthropicClient(LLMClient):
    def complete(self, prompt: str) -> str:
        return f"Anthropic responde: {prompt}"

class LLMFactory:
    @staticmethod
    def create(provider: str) -> LLMClient:
        providers = {
            "openai": OpenAIClient,
            "anthropic": AnthropicClient,
        }
        return providers[provider]()

Essa abordagem não apenas desacopla o código cliente da implementação específica, mas também facilita a criação de objetos complexos, como um cliente com cache, retry e observabilidade — tudo configurado em um único ponto.

2. Singleton: garantindo recursos compartilhados

Em sistemas de IA, certos recursos são caros de serem criados e devem ser compartilhados globalmente. É o caso de um pool de conexões com um banco vetorial, um cache de embeddings ou um cliente HTTP reutilizável. O padrão Singleton garante que haja apenas uma instância da classe em todo o ciclo de vida da aplicação.

import threading

class VectorDatabase:
    _instance = None
    _lock = threading.Lock()

    def __new__(cls):
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = super().__new__(cls)
        return cls._instance

Cuidado com o uso excessivo: Singletons podem dificultar testes e criar acoplamento global. Use-os com moderação e sempre que possível, combine com injeção de dependência para manter a testabilidade.

3. Strategy: encapsulando estratégias de prompt e inferência

Um dos padrões mais úteis para LLMs é o Strategy. Imagine uma aplicação que precisa usar diferentes técnicas de prompt (zero-shot, few-shot, chain-of-thought) dependendo da tarefa ou do domínio. Em vez de um bloco gigante de if/else, você define uma interface comum e implementa cada técnica em uma classe separada.

class PromptStrategy(ABC):
    @abstractmethod
    def build_prompt(self, user_query: str, examples: list[str] | None = None) -> str:
        pass

class ZeroShotStrategy(PromptStrategy):
    def build_prompt(self, user_query: str, examples: None = None) -> str:
        return f"Responda a pergunta: {user_query}"

class FewShotStrategy(PromptStrategy):
    def build_prompt(self, user_query: str, examples: list[str] = None) -> str:
        template = "Exemplos:\n" + "\n".join(examples or [])
        return f"{template}\nPergunta: {user_query}"

class ChainOfThoughtStrategy(PromptStrategy):
    def build_prompt(self, user_query: str, examples: None = None) -> str:
        return f"Pense passo a passo e responda:\n{user_query}"

Com o Strategy, o algoritmo de geração de prompts é selecionado dinamicamente em tempo de execução, tornando o sistema altamente configurável e fácil de estender.

4. Observer: monitoramento e logging em tempo real

Sistemas de LLM exigem observabilidade: precisamos saber quantas chamadas foram feitas, latência, tokens consumidos, erros etc. O padrão Observer estabelece uma relação de dependência um-para-muitos, onde quando um objeto muda de estado (por exemplo, uma resposta é gerada), todos os interessados (métricas, logs, alertas) são notificados automaticamente.

class EventManager:
    def __init__(self):
        self._listeners = []

    def subscribe(self, listener):
        self._listeners.append(listener)

    def notify(self, event):
        for listener in self._listeners:
            listener.update(event)

class LLMService:
    def __init__(self, events: EventManager):
        self._events = events

    def generate(self, prompt: str) -> str:
        response = "resultado simulado"
        self._events.notify({"event": "llm_generated", "prompt": prompt, "response": response})
        return response

Esse padrão é a base para dashboards de monitoramento, ferramentas de tracing como Langfuse e para integração com sistemas de alerta.

5. Repository: unificando o acesso a dados e embeddings

Em aplicações de IA, dados vêm de muitos lugares: CSV, S3, bancos SQL, APIs e bancos vetoriais. O padrão Repository atua como um intermediário entre a camada de domínio e as camadas de persistência, escondendo a complexidade e fornecendo uma interface limpa para consulta e armazenamento.

class EmbeddingRepository(ABC):
    @abstractmethod
    def save(self, key: str, vector: list[float]) -> None: ...

    @abstractmethod
    def search(self, vector: list[float], top_k: int) -> list[str]: ...

class PineconeRepository(EmbeddingRepository):
    def save(self, key: str, vector: list[float]) -> None:
        print(f"Salvando vetor {key} no Pinecone")

    def search(self, vector: list[float], top_k: int) -> list[str]:
        return ["chunk_1", "chunk_2", "chunk_3"]

Ao usar Repository, o restante da aplicação não precisa saber se os dados estão em um banco vetorial, em uma cache ou em um arquivo local. Isolamento total.

Padrões Emergentes para Agentes Autônomos

O campo de agentes autônomos baseados em IA generativa explodiu, e com ele surgiram padrões de arquitetura próprios. O e-book da Data Science Academy destaca a importância de padrões que abordam arquitetura, orquestração, confiabilidade e aplicação prática. Aqui estão os mais relevantes.

Padrão ReAct (Reasoning + Acting)

Esse padrão intercala raciocínio e ação: o agente recebe um problema, gera um raciocínio (thought), escolhe uma ação (action) e observa o resultado (observation) antes de repetir o ciclo. Esse loop é fundamental para construir agentes capazes de usar ferramentas, consultar APIs e tomar decisões baseadas em evidências.

class ReActAgent:
    def run(self, query: str):
        for step in range(max_steps):
            reasoning = self.generate_reasoning(query)
            action = self.choose_action(reasoning)
            observation = self.execute_action(action)
            query = self.update_query(reasoning, observation)
        return final_answer

Orquestrador-Trabalhadores (Orchestrator-Worker)

Em tarefas complexas, um único agente sobrecarregado tende a errar mais. O padrão Orchestrator-Worker define um agente central (orquestrador) que divide a tarefa em subtarefas e delega para trabalhadores especializados. Cada trabalhador pode ser um agente com seu próprio prompt, modelo e ferramentas.

  • Orquestrador: analisa a tarefa, cria um plano e distribui as subtarefas;
  • Trabalhadores: executam subtarefas e retornam os resultados;
  • Agregador: consolida os resultados parciais em uma resposta final.

Essa abordagem melhora a qualidade, a escalabilidade e permite processamento paralelo.

Padrão Router (Roteador Inteligente)

Nem todas as consultas precisam do mesmo modelo ou da mesma ferramenta. O padrão Router atua como uma camada de entrada que classifica a intenção do usuário e direciona a requisição para o melhor handler. Por exemplo:

  • Perguntas simples de cunho factual encaminhadas para um modelo rápido e barato;
  • Tarefas de análise complexa encaminhadas para um modelo de raciocínio avançado;
  • Consultas sobre dados internos encaminhadas para um agente que acessa o banco vetorial.
class Router:
    def route(self, query: str) -> Handler:
        intent = self.classify(query)
        return self.handlers[intent]

Isso reduz custos, melhora a latência e torna o sistema mais eficiente.

Padrão de Memória e Contexto

Agentes autônomos frequentemente precisam de memória de longo prazo. Esse padrão estrutura como informações históricas e contexto são armazenados, recuperados e injetados no prompt. Memórias podem ser de curto prazo (fitas de conversa) ou de longo prazo (resumos periódicos e embeddings em banco vetorial).

A implementação típica envolve um MemoryStore que consolida informações relevantes da sessão e as alimenta no prompt a cada nova interação, respeitando limites de janela de contexto do modelo.

Padrão de Reflexão e Autoavaliação

O padrão de reflexão permite que o agente avalie suas próprias saídas, detecte erros e reflita antes de gerar uma resposta final. Isso é feito criando um loop onde o agente gera uma solução, recebe feedback (de si mesmo ou de um modelo crítico) e itera até atingir um critério de qualidade.

class ReflectiveAgent:
    def answer(self, question: str) -> str:
        draft = self.generate(question)
        feedback = self.critic(draft)
        if self.is_satisfactory(feedback):
            return draft
        return self.refine(draft, feedback)

Embora aumente o custo computacional, a reflexão melhora significativamente a precisão e a robustez de soluções complexas, especialmente em tarefas de automação e raciocínio multi-hop.

Boas Práticas para Implementar Padrões em Python

Python é uma linguagem dinâmica e flexível, o que torna a implementação de alguns padrões mais simples do que em Java ou C#. No entanto, para alcançar código profissional e escalável, algumas práticas são essenciais:

  1. Use Protocols em vez de herança sempre que possível: o módulo typing oferece protocolos estruturais, que são mais flexíveis e seguem o ethos Pythonic de "duck typing".
  2. Prefira dataclasses para objetos de configuração: elas reduzem boilerplate e tornam o código mais legível.
  3. Adote injeção de dependência: em vez de importar Singletons diretamente, injete dependências via construtores ou funções. Isso facilita os testes e a manutenção.
  4. Escreva testes automatizados: padrões de design são inúteis se o comportamento não for verificado. Use pytest para validar cada componente isoladamente.
  5. Documente o propósito do padrão: em vez de apenas documentar o que o código faz, explique o problema de design que o padrão resolve.

Conclusão

Os Design Patterns são mais do que uma coleção de receitas de código — são uma forma de pensar em arquitetura de software. Para engenheiros de IA e LLM, dominar esses padrões é essencial para construir sistemas que resistam ao teste do tempo, que possam ser estendidos sem drama e que mantenham a qualidade à medida que a complexidade cresce.

Do clássico Strategy para prompts ao emergente padrão de Reflexão para agentes autônomos, as possibilidades são vastas. O mais importante é começar: identifique um ponto de dor em sua aplicação, selecione um padrão adequado, implemente de forma simples e vá refinando.

Se você está liderando a construção de agentes autônomos ou integrando LLMs em processos empresariais, investir no conhecimento de design patterns é decisivo. Afinal, o verdadeiro poder da IA generativa não está apenas nos modelos, mas na engenharia que os conecta ao mundo real.