Padrões de Projeto que Toda Equipe de IA Deveria Conhecer

Use Factory, Strategy, Proxy, Decorator, Composite, Chain of Responsibility e Mediator para construir arquiteturas de agentes de IA escaláveis e sustentáveis.
Domine os padrões Factory, Strategy, Proxy, Decorator, Composite, Chain of Responsibility e Mediator para construir arquiteturas de agentes de IA escaláveis e sustentáveis.

TL;DR

  • Aprenda os principais padrões de projeto que realmente importam para sistemas de IA.
  • Veja como cada padrão mapeia para problemas reais de orquestração de IA.
  • Use exemplos concretos de código para aplicar em sua stack.
  • Entenda os trade-offs, não apenas a teoria.
  • Aplique um roteiro simples para introduzir padrões sem engenharia excessiva.

O Problema da Falta de Estado: Por que a IA Precisa de Arquitetura

LLMs são stateless (sem estado).

Toda vez que você envia um prompt para o GPT, Claude ou qualquer modelo, ele esquece tudo fora da requisição atual. Ele não lembra da sua mensagem anterior, do plano que criou dois passos atrás, ou das ferramentas que acabou de chamar. Qualquer “memória” vem do sistema ao redor dele, não do modelo em si.

Isso significa que a camada de orquestração deve ser dona de:

  • Histórico da conversa
  • Regras de negócio
  • Roteamento de ferramentas
  • Estado de execução

Se você ignorar isso, acabará com uma pilha de scripts: chamadas ad-hoc para o modelo, templates de prompt espalhados e lógica duplicada em torno de ferramentas e memória. Funciona para uma demo. Quebra em produção.

Padrões de projeto ajudam a transformar essa bagunça em uma arquitetura clara e combinável.

Abaixo estão 7 padrões que mapeiam diretamente para problemas reais de IA, com exemplos que você pode adaptar.


1. Padrão Factory: Padronizando a Criação de Agentes

Problema

Você precisa de diferentes agentes: "Coder", "Researcher", "Critic", "Planner". Cada um precisa de:

  • Prompts de sistema específicos
  • Configuração do modelo (temperatura, nome do modelo)
  • Acesso a ferramentas
  • Conexão com vector store ou memória

Codificar essa configuração em todos os lugares leva a lógica duplicada e bugs sempre que você muda algo.

Abordagem ruim:

# configuração espalhada
researcher = Agent(
    role="researcher",
    model="gpt-4o",
    tools=[web_search, retrieve_docs],
    memory=DocStore("research_index"),
)

coder = Agent(
    role="coder",
    model="gpt-4o",
    tools=[code_exec, unit_test],
    memory=None,
)

Toda vez que você muda como um pesquisador deve funcionar, você edita vários lugares.

Padrão

Use uma Factory para centralizar a criação de agentes.

class AgentFactory:
    def __init__(self, vector_store, tools, models):
        self.vector_store = vector_store
        self.tools = tools
        self.models = models

    def create(self, agent_type: str):
        if agent_type == "researcher":
            return Agent(
                role="researcher",
                model=self.models["deep_reasoning"],
                tools=[self.tools["web_search"], self.tools["retrieval"]],
                memory=self.vector_store["research"],
            )
        if agent_type == "coder":
            return Agent(
                role="coder",
                model=self.models["code_first"],
                tools=[self.tools["code_exec"], self.tools["tests"]],
                memory=None,
            )
        if agent_type == "critic":
            return Agent(
                role="critic",
                model=self.models["deep_reasoning"],
                tools=[self.tools["style_checker"]],
                memory=None,
            )
        raise ValueError(f"Unknown agent type: {agent_type}")

Uso:

factory = AgentFactory(vector_store, tools, models)

researcher = factory.create("researcher")
coder = factory.create("coder")
critic = factory.create("critic")

Quando Usar

  • Você tem mais de um tipo de agente.
  • Você quer atualizar o comportamento em um lugar.
  • Você está construindo frameworks reutilizáveis ou plataformas internas.

Quando Não Usar

  • Você tem um único agente e não espera variação.
  • Seu caso de uso é experimental e muda diariamente.

2. Padrão Strategy: Trocando “Cérebros” Dinamicamente

Problema

Nem toda tarefa precisa do raciocínio nível GPT-4. Algumas tarefas são simples:

  • Resumir um parágrafo curto
  • Extrair alguns campos
  • Reformatar texto

Se todas as requisições vão para um modelo pesado, você queima orçamento e aumenta a latência.

Uso de modelo hard-coded:

def answer(query: str) -> str:
    # sempre usa o modelo mais caro
    return call_llm("gpt-4o", query)

Padrão

Use Strategy para definir uma família de opções de “cérebro” e escolher a correta em tempo de execução.

class ModelStrategy:
    def generate(self, prompt: str) -> str:
        raise NotImplementedError

class CheapModelStrategy(ModelStrategy):
    def generate(self, prompt: str) -> str:
        return call_llm("gpt-4o-mini", prompt)

class ExpensiveModelStrategy(ModelStrategy):
    def generate(self, prompt: str) -> str:
        return call_llm("gpt-4o", prompt)

class Router:
    def __init__(self, cheap: ModelStrategy, expensive: ModelStrategy):
        self.cheap = cheap
        self.expensive = expensive

    def handle(self, task: dict) -> str:
        if self._is_simple(task):
            return self.cheap.generate(task["prompt"])
        return self.expensive.generate(task["prompt"])

    def _is_simple(self, task: dict) -> bool:
        return len(task["prompt"]) < 300 and not task.get("requires_tools")

Uso:

router = Router(
    cheap=CheapModelStrategy(),
    expensive=ExpensiveModelStrategy(),
)

response = router.handle({"prompt": user_input, "requires_tools": False})

Benefícios

  • Reduz custos roteando tarefas triviais para modelos mais baratos.
  • Mantém o código de chamada estável enquanto você muda as regras de roteamento.

Quando Não Usar

  • Você sempre usa um único modelo fixo.
  • Você ainda não mede custo ou latência.

3. Padrão Proxy: O Guardião do Seu LLM

Problema

Chamadas diretas para APIs de LLM podem causar:

  • Custo descontrolado
  • Problemas de limite de taxa (rate-limit)
  • Falta de observabilidade
  • Riscos de conformidade e PII (Informações Pessoais Identificáveis)

Código sem um proxy:

def ask_llm(prompt: str) -> str:
    return openai.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": prompt}],
    )

Cada parte do seu código chama o provedor diretamente. Você não pode aplicar regras em um só lugar.

Padrão

Use um Proxy entre seu app e o provedor de LLM.

class LLMProxy:
    def __init__(self, client, cache, limiter, pii_filter):
        self.client = client
        self.cache = cache
        self.limiter = limiter
        self.pii_filter = pii_filter

    def generate(self, model: str, messages: list[dict]) -> str:
        key = self._cache_key(model, messages)

        cached = self.cache.get(key)
        if cached:
            return cached

        self.limiter.check_quota()

        safe_messages = self.pii_filter.strip(messages)

        response = self.client.chat(model=model, messages=safe_messages)

        self.cache.set(key, response)
        return response

    def _cache_key(self, model: str, messages: list[dict]) -> str:
        # implementar um hash estável
        ...

Uso:

proxy = LLMProxy(client, cache, limiter, pii_filter)

def ask_llm(prompt: str) -> str:
    return proxy.generate("gpt-4o", [{"role": "user", "content": prompt}])

Benefícios

  • Controle central de custo, logs e segurança.
  • Fácil integração com ferramentas como LiteLLM ou Helicone.
  • Mantém a lógica de negócio limpa.

Quando Não Usar

  • Experimentos apenas locais.
  • Scripts únicos onde custo e segurança não são preocupações.

4. Padrão Decorator: Observabilidade Sem Poluição

Problema

Você quer logar:

  • Entradas
  • Saídas
  • Uso de tokens
  • Tempo de execução

Adicionar código de log dentro de cada função torna tudo ruidoso.

Abordagem ruim:

def search_web(query: str) -> str:
    start = time.time()
    print("search_web called with:", query)
    result = call_api(query)
    print("result:", result, "took", time.time() - start)
    return result

Padrão

Use Decorators para envolver comportamento ao redor de funções.

def trace(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        duration = time.time() - start

        log_event(
            name=func.__name__,
            args=args,
            kwargs=kwargs,
            duration_ms=int(duration * 1000),
        )
        return result
    return wrapper

@trace
def search_web(query: str) -> str:
    return call_api(query)

Todas as funções marcadas com @trace tornam-se observáveis sem mudar sua lógica principal.

Benefícios

  • Separação limpa entre lógica e observabilidade.
  • Fácil integração com ferramentas de rastreamento (ex: LangSmith, OpenTelemetry).

Quando Não Usar

  • Caminhos extremamente críticos em performance onde o overhead do decorator não é aceitável.
  • Casos onde você precisa ver toda a lógica inline para fins de auditoria.

5. Padrão Composite: Lidando com Fluxos Multi-Etapas como Unidades Únicas

Problema

Fluxos complexos de IA frequentemente têm etapas aninhadas:

  • Pesquisar → Planejar → Codificar → Revisar
  • Recuperar → Gerar → Validar → Persistir

Se você tratar cada etapa como uma entidade de nível superior separada, você perde estrutura e reuso.

Padrão

Use Composite para que tanto etapas atômicas quanto grupos de etapas compartilhem a mesma interface.

class Task:
    def run(self, context: dict) -> dict:
        raise NotImplementedError

class SimpleTask(Task):
    def __init__(self, name, handler):
        self.name = name
        self.handler = handler

    def run(self, context: dict) -> dict:
        return self.handler(context)

class CompositeTask(Task):
    def __init__(self, name, children: list[Task]):
        self.name = name
        self.children = children

    def run(self, context: dict) -> dict:
        state = context
        for child in self.children:
            state = child.run(state)
        return state

Uso:

research = SimpleTask("research", research_handler)
draft = SimpleTask("draft", draft_handler)
review = SimpleTask("review", review_handler)

write_article = CompositeTask("write_article", [research, draft, review])

result = write_article.run({"topic": "multi-agent systems"})

Benefícios

  • Trate fluxos de múltiplas etapas como tarefas únicas.
  • Componha fluxos maiores a partir de unidades menores.

Quando Não Usar

  • Fluxos lineares muito simples sem reuso.
  • Casos onde a orquestração já é tratada por um motor externo (ex: n8n, Airflow) e você não precisa de uma representação em código.

6. Padrão Chain of Responsibility: Roteamento de Ferramentas e Agentes

Problema

Um único agente ou função tenta lidar com toda requisição:

  • Algumas queries precisam de busca na web.
  • Outras precisam de acesso ao banco de dados.
  • Outras precisam de execução de código.

Lógica hard-coded vira um grande bloco if/else.

Padrão

Use Chain of Responsibility para passar uma requisição através de um pipeline de handlers até que um assuma a responsabilidade.

class Handler:
    def __init__(self, next_handler=None):
        self.next = next_handler

    def handle(self, request: dict):
        if self.next:
            return self.next.handle(request)
        return None

class WebSearchHandler(Handler):
    def handle(self, request: dict):
        if "search" in request["intent"]:
            return search_web(request["query"])
        return super().handle(request)

class DbQueryHandler(Handler):
    def handle(self, request: dict):
        if "db" in request["intent"]:
            return query_db(request["query"])
        return super().handle(request)

Uso:

pipeline = WebSearchHandler(
    next_handler=DbQueryHandler(
        next_handler=Handler()  # fallback padrão
    )
)

response = pipeline.handle({"intent": "search", "query": "vector databases"})

Benefícios

  • Cada handler tem uma responsabilidade única.
  • Fácil de adicionar, remover ou reordenar etapas.

Quando Não Usar

  • Quando as regras de roteamento são simples e estáveis.
  • Quando você já usa um componente de roteador dedicado.

7. Padrão Mediator: Coordenação em Sistemas Multi-Agentes

Problema

Em um setup multi-agente, agentes começam a chamar uns aos outros diretamente:

  • Pesquisador chama Codificador
  • Codificador chama Crítico
  • Crítico chama Pesquisador de novo

Isso cria acoplamento oculto e loops de feedback complexos.

Padrão

Use um Mediator que gerencia a comunicação. Agentes falam com o mediador, não uns com os outros.

class Mediator:
    def __init__(self, factory: AgentFactory):
        self.factory = factory

    def handle(self, task: dict) -> str:
        researcher = self.factory.create("researcher")
        coder = self.factory.create("coder")
        critic = self.factory.create("critic")

        research = researcher.run(task)
        code = coder.run({"spec": research})
        review = critic.run({"code": code})

        if review["status"] == "approve":
            return code
        return self._iterate(task, review)

    def _iterate(self, task: dict, review: dict) -> str:
        # implementar loop de revisão
        ...

Agentes continuam simples. O mediador é dono da lógica do fluxo de trabalho.

Benefícios

  • Lugar central para mudar a orquestração.
  • Mais fácil de raciocinar sobre loops e modos de falha.

Quando Não Usar

  • Sistemas de agente único sem colaboração.
  • Fluxos extremamente simples que não justificam um coordenador.

Um Roteiro Prático de Adoção

Não tente implementar todos os padrões de uma vez. Uma sequência segura:

  1. Proxy: Coloque um proxy na frente do seu provedor de LLM para ganhar observabilidade, controle de custo e segurança.
  2. Factory: Padronize como agentes são criados para remover duplicação de setup.
  3. Strategy: Adicione roteamento de modelo para controlar custo e latência.
  4. Decorator: Adicione rastreamento e logs sem tocar na lógica de negócio.
  5. Composite / Chain of Responsibility / Mediator: Introduza orquestração estruturada conforme os fluxos crescem.

Se você já sente que sua base de código está virando “apenas scripts”, você está no ponto onde esses padrões valem a pena.


Go to top