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.
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.
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.
Uma banca de avaliação em que cada jurado olha só um quesito, ao mesmo tempo, e as notas são somadas no fim.
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?
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.
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
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.
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
“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.
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.
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.
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.
| 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 |
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" }] }
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.
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 | 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 |
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"] # 4O 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.
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.
Nos cenários reais Revisor automático de PR, Moderação de conteúdo e Pesquisa profunda com citações.