Conteúdo da trilha
PadrãoComposição

Seccionamento

Sectioning

Seccionamento (Sectioning) é um padrão de projeto de IA para apps com LLM e agentes, do grupo Composição. Quebrar a tarefa em partes independentes e rodar todas em paralelo.

Elemento
03/70
Nível
Intermediário
Leitura
9 min
Atualizado
Nesta página 17
  1. 01Intenção
  2. 02Problema
  3. 03Solução
  4. 04Estrutura
  5. 05Pseudocódigo
  6. 06Cenários reais
  7. 07Aplicabilidade
  8. 08Quando não usar
  9. 09Como implementar
  10. 10Variações
  11. 11Custo, latência e falhas
  12. 12Como avaliar
  13. 13Em produção
  14. 14Segurança
  15. 15Prós e contras
  16. 16Código
  17. 17Teste seu entendimento
  18. ··Perguntas rápidas

Intenção

Quebrar a tarefa em partes independentes, definidas em código, e rodar todas em paralelo, cada uma com seu prompt focado. Serve para tarefas que olham a mesma entrada por vários ângulos ou que se dividem em pedaços sem dependência entre si.

Em uma frase
Uma banca de avaliação em que cada jurado olha só um quesito, ao mesmo tempo, e as notas são somadas no fim.

Problema

Felipe mantém o revisor automático de PRs do time. O prompt pede para revisar segurança, desempenho, testes, estilo e compatibilidade de API, tudo numa chamada. A revisão demora, e a qualidade é desigual: comentários de estilo aparecem sempre, e problemas de segurança só às vezes.

Quando ele olha os traces, vê o motivo. O modelo precisa segurar cinco listas de critérios ao mesmo tempo e tende a cobrir melhor o que é mais fácil de notar. Um problema de injeção de SQL fica escondido entre doze comentários sobre nomes de variáveis.

Felipe tentou separar em cinco chamadas, uma por aspecto. A qualidade melhorou, mas a revisão passou a levar cinco vezes mais tempo, porque as chamadas rodavam uma depois da outra.

A pergunta que este padrão responde: como dar a cada parte de uma tarefa uma chamada focada sem pagar a soma das latências?

Solução

Divida a tarefa em seções fixas, decididas no código: um aspecto por seção, ou um pedaço da entrada por seção. Cada seção tem seu próprio prompt, curto e especializado. Todas rodam ao mesmo tempo. Um passo final, em código ou com um modelo, junta os resultados.

A diferença para o Orquestrador e Trabalhadores é que a divisão não depende da entrada: o código sabe de antemão quais seções existem. A diferença para a Votação é que cada seção faz uma coisa diferente, em vez de todas fazerem a mesma.

O post “Building effective agents” da Anthropic (dezembro de 2024) chama isso de parallelization, na variação sectioning: quebrar em subtarefas independentes e rodar ao mesmo tempo. A outra variação é a votação, em que várias chamadas fazem o mesmo trabalho.

Estrutura

flowchart LR
  IN(["1. Diff do PR"]):::io --> S1(["2. Segurança"]):::llm
  IN --> S2(["2. Desempenho"]):::llm
  IN --> S3(["2. Testes"]):::llm
  IN --> S4(["2. Compatibilidade"]):::llm
  S1 --> J["3. Juntar e deduplicar"]:::tool
  S2 --> J
  S3 --> J
  S4 --> J
  J --> F{"4. Filtrar por severidade"}
  F --> OUT(["5. Comentários no PR"]):::io
  1. A mesma entrada vai para todas as seções.
  2. Cada seção roda com um prompt focado num aspecto e devolve achados estruturados.
  3. O código junta os achados, remove duplicados e ordena.
  4. Uma regra decide o que vira comentário (por severidade, por confiança).
  5. O resultado sai com a origem de cada achado registrada.

Exemplo trabalhado

O PR do Felipe altera api/users.py. Quatro seções rodam ao mesmo tempo sobre o mesmo diff. Saídas (simplificadas):

{ "section": "security", "file": "api/users.py", "line": 48, "severity": "high",
  "message": "consulta montada com f-string a partir do email do usuário" }
{ "section": "tests", "file": "api/users.py", "line": 48, "severity": "medium",
  "message": "não há teste para email malformado neste endpoint" }

A junção vê a mesma linha duas vezes. A regra de deduplicação por arquivo e linha manteria o high de segurança e descartaria o de testes, se a ordenação for por severidade. Por isso a chave de deduplicação precisa incluir a mensagem, ou você aceita que um comentário de testes some quando segurança já falou da linha.

Com uma chamada só, o modelo cobria estilo e mal via a injeção. O paper “Lost in the Middle” (Liu et al., 2023) ajuda a explicar: cinco listas de critérios na mesma janela fazem o critério do meio (segurança, no prompt do Felipe) receber menos atenção. Uma seção só de segurança não compete com estilo.

Checagem +20 XP de primeiraFelipe rodou cinco revisões em sequência, uma por aspecto, e a qualidade subiu. A revisão passou a levar cinco vezes mais tempo. O que falta para ser seccionamento?

Pseudocódigo

schema Finding { file: string, line: int, severity: "high" | "medium" | "low", message: string }
schema Findings { items: list[Finding] }

SECTIONS = {
    "security":      SECURITY_PROMPT,
    "performance":   PERFORMANCE_PROMPT,
    "tests":         TESTS_PROMPT,
    "compatibility": API_COMPAT_PROMPT,
}

function review(diff):
    # 2. todas as seções em paralelo
    parallel {
        for name, prompt in SECTIONS:
            results[name] = llm(prompt, diff) -> Findings
            trace.span("section." + name, results[name])
    }

    # 3. juntar em código
    all = flatten(results)
    unique = dedupe(all, key=(f.file, f.line, similar(f.message)))

    # 4. filtrar
    return [f for f in unique if f.severity in ["high", "medium"]]    # 5

Cenários reais

  • Revisor automático de PR. Cada aspecto da revisão tem seu prompt e roda em paralelo sobre o mesmo diff. Segurança passa a ter a atenção inteira de uma chamada.
  • Moderação de conteúdo. Cada política (violência, discurso de ódio, spam, dados pessoais) é checada por uma seção própria, e a decisão final combina os resultados.
  • Pesquisa profunda com citações. Perguntas fixas do relatório (mercado, concorrentes, regulação) são pesquisadas ao mesmo tempo, cada uma por uma seção.

Aplicabilidade

  • A tarefa tem aspectos ou partes que você consegue listar antes de ver a entrada.
  • Cada aspecto se beneficia de um prompt focado.
  • As partes não dependem umas das outras.
  • Latência importa, e a fila de chamadas é o gargalo.

Quando não usar

  • Uma chamada única já faz bem. Seções multiplicam o custo de entrada, porque cada uma lê a entrada inteira.
  • A divisão depende do conteúdo. Use Orquestrador e Trabalhadores.
  • As partes dependem umas das outras. Use Encadeamento de Prompts.
  • A entrada é grande e você quer dividi-la em pedaços para depois juntar um resumo. Esse é o caso específico do Map-Reduce de Documentos.

Erro comum: seções que dependem uma da outra

“Primeiro segurança, depois testes usando os achados de segurança” não é seccionamento. É cadeia. O paralelo só vale se cada seção puder rodar sem o resultado das outras.

Se a divisão muda conforme o diff (um PR de infra pede seções que um PR de CSS não pede), quem decide a lista é um modelo, e o padrão vira Orquestrador e Trabalhadores.

Checagem +20 XP de primeiraUm revisor tem seções de segurança, testes e estilo. A seção de testes deveria comentar só os arquivos que segurança marcou como arriscados. Qual padrão é esse desenho?

Como implementar

  1. Liste as seções e escreva um prompt curto para cada, com critérios só daquele aspecto.
  2. Defina um schema de saída comum, para juntar sem adivinhação.
  3. Rode as seções ao mesmo tempo, com limite de concorrência.
  4. Decida o que acontece se uma seção falhar: seguir sem ela ou falhar tudo.
  5. Escreva a junção em código: deduplicação, ordenação, filtros.
  6. Use Cache de Prompt para a entrada comum, se o provedor permitir, colocando a entrada antes da instrução de cada seção.
  7. Avalie cada seção sozinha e o resultado final.

Limite a concorrência. Quatro seções num PR cabem. Quarenta PRs ao mesmo tempo, quatro seções cada, são 160 chamadas num estouro. Um semáforo de, por exemplo, 8 chamadas ao mesmo tempo respeita o rate limit do provedor. A seção mais lenta ainda define a latência; o teto só espalha o pico.

Escolha o modelo por seção. Estilo e “teste faltando” costumam rodar num modelo pequeno. Segurança merece o maior. Isso é uma cascata dentro do seccionamento: atenção cara só no aspecto que paga a conta.

Custo da entrada repetida

Quatro seções lêem o mesmo diff quatro vezes. Suponha um diff de 3.000 tokens e 200 tokens de prompt por seção. Sem cache, são cerca de 12.800 tokens de entrada. Com cache de prompt no prefixo comum (o diff primeiro, a instrução depois), o provedor cobra o diff cheio uma vez e as leituras seguintes saem baratas.

A latência não soma: você espera a seção mais lenta. Se segurança leva 8 segundos e estilo leva 3, o total é perto de 8, mais a junção. É por isso que uma seção que ficou lenta merece alerta próprio.

Variações

  • Seções por aspecto. A mesma entrada, critérios diferentes. É o caso do revisor de PR.
  • Seções por pedaço. Partes diferentes da entrada, o mesmo critério. Quando a junção é um resumo, vira Map-Reduce.
  • Seção de guarda em paralelo. Uma seção checa segurança da entrada enquanto outra já gera a resposta. Se a guarda reprovar, a resposta é descartada.
  • Seções com modelos diferentes. Aspectos simples num modelo pequeno, aspectos difíceis num maior.

Seções por pedaço parecem Map-Reduce, e a fronteira é a junção. Se cada pedaço devolve achados do mesmo tipo e o código só concatena, ainda é seccionamento. Se cada pedaço devolve um resumo e um modelo junta um texto só, você saiu para Map-Reduce.

A seção de guarda em paralelo é o caso em que você aceita jogar trabalho fora. A resposta já está sendo gerada enquanto a guarda lê a entrada. Se a guarda reprovar, você descarta tokens já gastos. Vale quando a latência da resposta importa mais que o custo do descarte, e quando a guarda é mais barata que gerar de novo depois.

Custo, latência e falhas

Aspecto O que acontece Como aparece no trace
Chamadas Uma por seção, mais a junção quando usa modelo spans section.* irmãos
Latência A da seção mais lenta, mais a junção spans sobrepostos no tempo
Custo Cada seção lê a entrada inteira; cache de prompt reduz tokens de entrada multiplicados pelo número de seções
Falha: duplicados Duas seções apontam o mesmo problema achados quase iguais no resultado final
Falha: seção silenciosa Uma seção falha ou não acha nada nunca contagem zero persistente numa seção
Falha: rate limit Muitas seções simultâneas erros 429 em spans de seção

Como avaliar

  • Por seção: precisão e recall de cada aspecto num dataset com problemas conhecidos.
  • Final: achados relevantes por PR, comentários inúteis por PR, e latência.
  • Comparação: a mesma avaliação com a chamada única de antes.

O dataset de um revisor de PR não é “PRs aleatórios”. São diffs com problemas plantados: uma injeção na linha 48, um N+1, um endpoint sem teste. Sem o plantio, recall de segurança é um palpite. Com o plantio, a seção de segurança tem um caso de ouro por tipo de falha.

Quando a chamada única já encontra o N+1 e a seção de desempenho não, o prompt da seção está pior que o bloco antigo. Copie o trecho que funcionava. Não invente critérios novos no escuro.

{ "diff": "fixtures/pr-311.diff", "expected": [{ "section": "security", "file": "api/users.py", "line": 48, "kind": "sql_injection" }] }

Em produção

  • Spans por seção com contagem de achados, tokens e duração.
  • Painel de achados por seção ao longo do tempo. Uma seção que nunca acha nada merece revisão.
  • Alerta para a seção mais lenta quando ela dominar a latência total.

A junção é o lugar onde o seccionamento costuma degradar em silêncio. Deduplicar por arquivo e linha é simples demais, como o código desta página admite. Achados diferentes na mesma linha somem. Prefira uma chave que inclua um resumo da mensagem, ou mantenha os dois e deixe a UI do PR agrupar.

Se você coloca um modelo na junção para “escrever o comentário final”, você devolve um gargalo serial e a chance de o modelo suavizar um high de segurança. Junção em código, comentário no texto que a seção já escreveu.

Segurança

O diff veio do usuário. Uma seção de segurança que lê o PR pode ser alvo de prompt injection no próprio código comentado. Isole a instrução da seção do texto do diff, e não deixe a seção de segurança executar nada: ela só devolve achados.

A junção em código também evita um modelo final que “reescreva” achados de segurança para baixo. Severidade mínima para comentar fica numa regra, não num prompt.

Prós e contras

Prós Contras
Cada aspecto recebe atenção total Custo de entrada multiplicado
Latência da seção mais lenta, não da soma A junção precisa lidar com duplicados
Prompts curtos, fáceis de testar e ajustar Mais chamadas simultâneas para o rate limit
Seções novas entram sem mexer nas outras Divisão fixa não se adapta a entradas incomuns

Código

As duas abas rodam quatro seções em paralelo sobre o mesmo diff e juntam os achados em código.

Python. Use quando o pipeline é assíncrono em Python e as seções devolvem um schema comum.

import asyncio
from typing import Literal

import anthropic
from pydantic import BaseModel

client = anthropic.AsyncAnthropic()


class Finding(BaseModel):
    file: str
    line: int
    severity: Literal["high", "medium", "low"]
    message: str


class Findings(BaseModel):
    items: list[Finding]


SECTIONS = {
    "security": "Você revisa só segurança: injeção, autenticação, segredos, permissões.",
    "performance": "Você revisa só desempenho: consultas em loop, alocação, complexidade.",
    "tests": "Você revisa só testes: casos faltando para o código alterado.",
    "compatibility": "Você revisa só compatibilidade de API pública: assinaturas e contratos.",
}


async def section(name: str, prompt: str, diff: str) -> list[dict]:
    reply = await client.messages.parse(  # 2
        model="claude-sonnet-5-5", max_tokens=4000, system=prompt,
        messages=[{"role": "user", "content": f"<diff>\n{diff}\n</diff>"}],
        output_format=Findings,
    )
    return [{"section": name, **f.model_dump()} for f in reply.parsed_output.items]


async def review(diff: str) -> list[dict]:
    results = await asyncio.gather(
        *(section(n, p, diff) for n, p in SECTIONS.items()), return_exceptions=True
    )
    findings = [f for r in results if not isinstance(r, Exception) for f in r]  # seção falha: segue
    seen, unique = set(), []
    for f in sorted(findings, key=lambda f: ["high", "medium", "low"].index(f["severity"])):
        key = (f["file"], f["line"])  # 3. deduplicar
        if key not in seen:
            seen.add(key)
            unique.append(f)
    return [f for f in unique if f["severity"] != "low"]  # 4

O que você ganhou: quatro revisões focadas no tempo de uma. O que você perdeu: a deduplicação por arquivo e linha é simples; achados diferentes na mesma linha somem.

TypeScript. Use quando o revisor roda numa action ou serviço Node.

import Anthropic from "@anthropic-ai/sdk";
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";
import { z } from "zod";

const client = new Anthropic();

const Findings = z.object({
  items: z.array(z.object({
    file: z.string(), line: z.number().int(),
    severity: z.enum(["high", "medium", "low"]), message: z.string(),
  })),
});

const SECTIONS: Record<string, string> = {
  security: "Você revisa só segurança: injeção, autenticação, segredos, permissões.",
  performance: "Você revisa só desempenho: consultas em loop, alocação, complexidade.",
  tests: "Você revisa só testes: casos faltando para o código alterado.",
  compatibility: "Você revisa só compatibilidade de API pública: assinaturas e contratos.",
};

async function section(name: string, system: string, diff: string) {
  const { parsed_output } = await client.messages.parse({
    model: "claude-sonnet-5-5", max_tokens: 4000, system,
    messages: [{ role: "user", content: `<diff>\n${diff}\n</diff>` }],
    output_config: { format: zodOutputFormat(Findings) },
  });
  return (parsed_output?.items ?? []).map((f) => ({ section: name, ...f }));
}

export async function review(diff: string) {
  const settled = await Promise.allSettled(
    Object.entries(SECTIONS).map(([n, p]) => section(n, p, diff)), // 2. em paralelo
  );
  const all = settled.flatMap((r) => (r.status === "fulfilled" ? r.value : []));
  const order = { high: 0, medium: 1, low: 2 };
  const seen = new Set<string>();
  return all
    .sort((a, b) => order[a.severity] - order[b.severity])
    .filter((f) => {
      const key = `${f.file}:${f.line}`; // 3
      if (seen.has(key)) return false;
      seen.add(key);
      return f.severity !== "low"; // 4
    });
}

O que você ganhou: allSettled deixa uma seção falhar sem derrubar a revisão. O que você perdeu: o mesmo limite da deduplicação simples.

Teste seu entendimento

Checagem +20 XP de primeiraQuatro seções leem o mesmo diff. Segurança nunca devolve achado há duas semanas, e o painel mostra contagem zero. Qual é a leitura mais útil?

Checagem +20 XP de primeiraVocê vai revisar PRs de 8 mil tokens de diff. Sem cache, quatro seções custam quatro leituras. Qual movimento corta o custo de entrada sem matar o paralelo?

Checagem +20 XP de primeiraTrês chamadas revisam o mesmo diff com o mesmo prompt de segurança, e a decisão é por maioria. Isso é seccionamento?

Checagem +20 XP de primeiraA seção de desempenho estoura o rate limit e falha. As outras três devolvem achados. O que o código desta página faz, e você manteria?

Perguntas rápidas

O que é Seccionamento?

Seccionamento (Sectioning) é um padrão de projeto de IA para apps com LLM e agentes, do grupo Composição. Quebrar a tarefa em partes independentes e rodar todas em paralelo.

Quais smells o padrão Seccionamento ajuda a curar?

Fila Indiana de Chamadas.

Onde Seccionamento aparece na prática?

Nos cenários reais Revisor automático de PR, Moderação de conteúdo e Pesquisa profunda com citações.

Para ir além

Relações

Smells que ajuda a curar 1
Refatorações que chegam aqui 1
Buscar