Orchestration e custo
Três especialistas podem trabalhar ao mesmo tempo e ainda entregar um resultado mais caro e mais lento do que uma única execução bem organizada. O paralelismo reduz o tempo de caminhos independentes, mas acrescenta coordenação, repete contexto e aumenta pressão sobre limites do provedor. Nosso caso será uma pesquisa de três fornecedores. Vamos comparar execução sequencial, paralela e paralela com concorrência limitada, distinguindo tempo idealizado de comportamento observado.
Os números do simulador são unidades didáticas de tempo e custo, não preços de modelos nem benchmarks. Eles ajudam a derivar a conta antes de medir um sistema real. A decisão arquitetural será escolher quando paralelizar, quanto contexto enviar e onde impor orçamento. Também vamos estudar falhas, retries e cancelamento, porque um fluxo que parece eficiente no caso nominal pode multiplicar custo quando vários especialistas insistem sobre o mesmo problema.
JavaScriptAgentesAo terminar esta aula
- Paralelismo só reduz caminhos independentes.
- Custo inclui coordenação, contexto, síntese e retries.
- Concorrência, duração e orçamento são limites complementares.
Antes de continuar: Laboratório: Multi-agent patterns
Dependências determinam o ganho possível
FundamentosSe três consultas independentes levam duas, quatro e três unidades de tempo, a sequência leva nove unidades e o paralelismo ideal leva quatro. Esse cálculo supõe recursos disponíveis, ausência de overhead e nenhuma dependência entre consultas. Quando a síntese leva mais duas unidades, ela aparece depois das consultas em ambos os desenhos. Se uma consulta depende de outra, esse caminho não pode ser reduzido por simplesmente iniciar tudo junto. O grafo de dependências define o caminho crítico; o número de agentes, isoladamente, não define latência.
↗ Workflows and agents↗ Multi-agent↗ Orchestration and handoffs
A execução real possui overhead de agendamento, serialização, rede, contexto e síntese. Um limite de taxa pode fazer chamadas paralelas esperarem ou falharem, e retries simultâneos podem gerar novas ondas de carga. Por isso, meça início e fim de cada etapa, tempo em fila e número de tentativas. Uma barra única de duração total não explica o gargalo. Compare os mesmos casos e configurações, evitando atribuir ao padrão multiagente uma diferença causada por modelo, tamanho de contexto ou disponibilidade do serviço.
↗ Workflows and agents↗ Multi-agent↗ Orchestration and handoffs
Custo acumula em cada fronteira
FundamentosO custo de uma chamada depende da configuração e do uso do serviço; no orçamento da aplicação, conte entradas, saídas, ferramentas e tentativas efetivamente cobradas conforme a tabela atual do provedor. Não invente um valor genérico por agente. Na comparação local, uma unidade de custo é apenas um peso atribuído à tarefa. A soma de três especialistas é diferente do custo da síntese final, e o controlador também pode consumir chamadas para decidir a distribuição. Um diagrama que desenha somente especialistas omite parte da conta.
↗ Workflows and agents↗ Multi-agent↗ Orchestration and handoffs
Contexto compartilhado amplia a entrada de cada especialista. Se todos recebem um documento extenso, o mesmo material pode ser processado várias vezes. Enviar recortes relevantes pode reduzir custo, mas também omitir uma restrição necessária. A minimização precisa preservar as premissas da tarefa, e sua qualidade deve ser avaliada. Cache, quando disponível, possui regras próprias e não deve ser tratado como desconto automático em toda repetição. Registre uso real e compare com os critérios de qualidade: custo menor que produz mais respostas sem evidência pode ser uma economia falsa.
↗ Workflows and agents↗ Multi-agent↗ Orchestration and handoffs
Orçamento precisa existir antes da chamada
FundamentosO simulador verifica se o custo planejado da próxima etapa cabe no orçamento. Isso impede iniciar uma tarefa que já ultrapassaria o teto didático. Em sistemas reais, o uso final pode ser incerto: reserve uma margem ou um limite máximo por chamada e reconcilie o consumo observado depois. O orçamento também deve cobrir retries e síntese. Reservar todo o valor para especialistas deixa o controlador sem capacidade de produzir a resposta final ou explicar uma falha. Defina o resultado quando o orçamento for insuficiente, como proposta parcial ou encerramento com pendências.
↗ Workflows and agents↗ Multi-agent↗ Orchestration and handoffs
Concorrência limitada controla quantas tarefas estão ativas ao mesmo tempo. Ela é diferente de limite de custo: duas chamadas longas podem gastar mais que dez pequenas. Use limites complementares de concorrência, duração, tentativas e consumo. Para estados compartilhados, preserve contribuições por identificador e não deixe dois workers sobrescreverem a mesma saída. Ao cancelar uma pesquisa, chamadas já iniciadas podem continuar ou já ter consumido recursos. Registre o que foi interrompido e o que permaneceu em andamento; cancelamento da interface não implica reversão de efeitos externos.
↗ Workflows and agents↗ Multi-agent↗ Orchestration and handoffs
const tarefas=[{id:'A',tempo:2,custo:3},{id:'B',tempo:4,custo:4},{id:'C',tempo:3,custo:2}];
console.log({sequencial:tarefas.reduce((a,t)=>a+t.tempo,0),paraleloIdeal:Math.max(...tarefas.map(t=>t.tempo)),custo:tarefas.reduce((a,t)=>a+t.custo,0)});
function executarOrcamento(teto) {
let gasto=0; const feitas=[],omitidas=[];
for (const t of tarefas) {
if (gasto+t.custo>teto) { omitidas.push(t.id); continue; }
gasto+=t.custo; feitas.push(t.id);
}
return {teto,gasto,feitas,omitidas};
}
console.log(executarOrcamento(7));console.log(executarOrcamento(9));A resolução do exercício implementa de fato a fila de dois workers descrita aqui: capacidade de espera um, reservas síncronas no mesmo processo, liquidação separada e AbortSignal. O cálculo nominal acima continua útil para planejar o caminho crítico, mas não é a prova do controle de concorrência. Inspecione peakActive, queuePeak, backpressure e eventos por tentativa. A leitura e a execução usam escalas didáticas diferentes; nenhuma delas demonstra latência ou preço de um provedor real.
↗ Workflows and agents↗ Multi-agent↗ Orchestration and handoffs
Falhas podem multiplicar a coordenação
FundamentosSe cada especialista tenta três vezes e o supervisor reinicia todos em cada rodada, a quantidade de chamadas cresce rapidamente. Não é necessário um benchmark para reconhecer esse mecanismo: o produto entre unidades, tentativas e rodadas estabelece um limite superior útil no desenho. Evite reiniciar contribuições já válidas quando apenas uma falhou, e defina quais erros justificam retry. Acesso negado ou argumento inválido não se tornam corretos por repetição. Timeouts de efeitos exigem reconciliação, conforme vimos nas semanas de persistência.
↗ Workflows and agents↗ Multi-agent↗ Orchestration and handoffs
Avalie pelo menos casos nominal, uma falha transitória, um especialista lento e orçamento insuficiente. Conte contribuições válidas, chamadas repetidas e resposta final. O benefício do paralelismo deve aparecer em latência observada sem degradar esses critérios. Às vezes, uma primeira consulta elimina a necessidade das outras, tornando execução sequencial adaptativa mais econômica. A decisão madura registra esse tradeoff: latência mínima, consumo máximo e qualidade desejada competem, e a arquitetura precisa escolher uma política explicável para o contexto do produto.
↗ Workflows and agents↗ Multi-agent↗ Orchestration and handoffs
Retry de uma leitura temporariamente indisponível e repetição de um efeito ambíguo exigem políticas diferentes. A resolução autoriza no máximo duas tentativas de leitura e bloqueia retry em qualquer escrita, inclusive quando a resposta se perde depois do efeito. O cancelamento encerra o trabalho local cooperativo, mas não reverte a escrita nem apaga consumo. Essa separação permite testar segurança da coordenação antes de integrar modelos, sem atribuir ao AbortController garantias que o serviço remoto não oferece.
↗ Workflows and agents↗ Multi-agent↗ Orchestration and handoffs
Exercício aplicado
Três consultas de fornecedores competem por um orçamento limitado. Compare sequência, paralelismo e concorrência de dois workers, preservando contribuições válidas quando somente uma consulta falha.
- Calcule soma, máximo e custo nominal com os dados fornecidos.
- Execute dois tetos e compare tarefas omitidas.
- Desenhe a fila de dois workers e acrescente síntese ao caminho crítico.
- Instrumente retries e reserva de orçamento na integração escolhida.
Abrir resolução comentada
Comece distinguindo o cálculo nominal da leitura da execução concorrente. No caso nominal, sequência custa nove unidades de tempo, paralelo ideal quatro e fila de dois workers cinco, antes da síntese. A resolução executa timers locais com dois workers e capacity um, mede ocupação e provoca erros. Producer e workers são Promises realmente concorrentes; nenhum lote de soma nominal substitui essa evidência.
Cada tentativa reserva seu máximo antes de iniciar; consumo settled só muda na liquidação. Uma reserva inicial protege a síntese. B possui retry de leitura limitado a duas tentativas totais, enquanto D permanente e E com escrita ambígua não repetem. O caso completo liquida quatorze unidades e deixa seis disponíveis; os casos de teto menor verificam ausência de saldo negativo, sem prometer seleção ótima.
Deadline e cancelamento fecham a fila, liberam produtores bloqueados e abortam timers cooperativos. As tentativas iniciadas ainda consomem as unidades definidas pela fixture; a reserva não consumida é devolvida. Os asserts verificam reserva final zero, fila limitada, concorrência dois, um efeito em E, preservação de resultados e zero trabalho no cancelamento prévio. Um destino remoto pode ignorar cancelamento; reconciliação e armazenamento atômico distribuído permanecem responsabilidades da integração.
const assert = require('node:assert/strict');
class BoundedQueue {
constructor(capacity) {
this.capacity=capacity; this.items=[]; this.readers=[];
this.producers=[]; this.closed=false; this.peak=0; this.backpressure=0;
}
async put(item) {
while (!this.closed && !this.readers.length && this.items.length===this.capacity) {
this.backpressure++;
await new Promise((resolve,reject)=>this.producers.push({resolve,reject}));
}
if(this.closed) throw new Error('queue-closed');
if(this.readers.length) this.readers.shift()(item);
else {this.items.push(item); this.peak=Math.max(this.peak,this.items.length);}
}
async take() {
if(this.items.length) {
const item=this.items.shift(); this.producers.shift()?.resolve(); return item;
}
if(this.closed) return null;
return new Promise(resolve=>this.readers.push(resolve));
}
close(discard=false) {
this.closed=true;
if(discard) this.items=[];
for(const r of this.readers.splice(0)) r(null);
for(const p of this.producers.splice(0)) p.reject(new Error('queue-closed'));
}
}
function sleep(ms,signal) {
return new Promise((resolve,reject)=>{
const abort=()=>{clearTimeout(timer);signal.removeEventListener('abort',abort);
const error=new Error('cancelled');error.cancelled=true;reject(error);};
const timer=setTimeout(()=>{signal.removeEventListener('abort',abort);resolve();},ms);
signal.addEventListener('abort',abort,{once:true});if(signal.aborted)abort();
});
}
class Budget {
constructor(limit){this.limit=limit;this.reserved=0;this.settled=0;this.minimum=limit;}
available(){return this.limit-this.reserved-this.settled;}
reserve(maximum){
if(maximum>this.available())return null;
// Operação síncrona e indivisível somente neste processo JavaScript.
this.reserved+=maximum;this.minimum=Math.min(this.minimum,this.available());
let done=false;
return actual=>{
assert.ok(!done && actual>=0 && actual<=maximum);done=true;
this.reserved-=maximum;this.settled+=actual;
assert.ok(this.available()>=0 && this.reserved>=0);
};
}
}
async function run(tasks,{limit,deadlineMs=1000,externalSignal}={}){
const queue=new BoundedQueue(1),budget=new Budget(limit),controller=new AbortController();
const signal=controller.signal,events=[],results={},effects={};
const synthesisReservation=budget.reserve(1);
let synthesisSettled=false;
let active=0,peakActive=0;
const cancel=()=>{controller.abort();queue.close(true);};
const deadline=setTimeout(cancel,deadlineMs);
externalSignal?.addEventListener('abort',cancel,{once:true});
if(externalSignal?.aborted)cancel();
async function worker(workerId){
for(let task; (task=await queue.take())!==null;){
if(signal.aborted)break;
for(let attempt=1;attempt<=2;attempt++){
if(signal.aborted){results[task.id]={status:'cancelled'};break;}
const settle=budget.reserve(task.cost);
if(!settle){results[task.id]={status:'budget'};break;}
let actual=1,retry=false;
active++;peakActive=Math.max(peakActive,active);
events.push({task:task.id,workerId,attempt,event:'start',active});
try{
await sleep(task.ms,signal);
if(task.mode==='denied')throw Object.assign(new Error('denied'),{permanent:true});
if(task.mode==='transient' && attempt===1)
throw Object.assign(new Error('temporary'),{transient:true});
if(task.effect){
effects[task.id]=(effects[task.id]||0)+1;
if(task.mode==='ambiguous') {
actual=task.cost;
throw Object.assign(new Error('response-lost-after-effect'),{transient:true});
}
}
actual=task.cost;results[task.id]={status:'ok',attempt};
}catch(error){
retry=error.transient && !task.effect && !signal.aborted && attempt<2;
results[task.id]={status:error.cancelled?'cancelled':retry?'retry':'failed',reason:error.message};
}finally{
active--;settle(actual);
events.push({task:task.id,workerId,attempt,event:'settle',actual,
reserved:budget.reserved,settled:budget.settled,available:budget.available()});
}
if(!retry)break;
// Espera limitada também respeita o deadline; não ocupa uma reserva financeira.
try{await sleep(2,signal);}catch{results[task.id]={status:'cancelled'};break;}
}
}
}
const workers=[worker(1),worker(2)];
const producer=(async()=>{
try{for(const task of tasks)await queue.put(task);}
catch(error){if(!signal.aborted)throw error;}
finally{queue.close(signal.aborted);}
})();
try{
await Promise.all([producer,...workers]);
for(const task of tasks)results[task.id]??={status:signal.aborted?'not-started-cancelled':'not-started'};
// A reserva inicial protege a síntese; cancelar libera-a sem consumo.
const synthesize=!signal.aborted && Object.values(results).some(r=>r.status==='ok');
if(synthesisReservation){
synthesisReservation(synthesize?1:0);synthesisSettled=true;
if(synthesize)events.push({event:'synthesis',actual:1});
}
return {results,effects,events,peakActive,queuePeak:queue.peak,
backpressure:queue.backpressure,cancelled:signal.aborted,
budget:{limit,reserved:budget.reserved,settled:budget.settled,
available:budget.available(),minimum:budget.minimum}};
}finally{
clearTimeout(deadline);externalSignal?.removeEventListener('abort',cancel);
if(synthesisReservation && !synthesisSettled)synthesisReservation(0);
}
}
(async()=>{
const tasks=[{id:'A',ms:10,cost:3},{id:'B',ms:12,cost:4,mode:'transient'},
{id:'C',ms:5,cost:2},{id:'D',ms:5,cost:2,mode:'denied'},
{id:'E',ms:5,cost:2,effect:true,mode:'ambiguous'}];
const complete=await run(tasks,{limit:20});
assert.equal(complete.peakActive,2);assert.ok(complete.queuePeak<=1);
assert.ok(complete.backpressure>0);
assert.equal(complete.events.filter(e=>e.task==='A'&&e.event==='start').length,1);
assert.equal(complete.events.filter(e=>e.task==='B'&&e.event==='start').length,2);
assert.equal(complete.events.filter(e=>e.task==='D'&&e.event==='start').length,1);
assert.equal(complete.events.filter(e=>e.task==='E'&&e.event==='start').length,1);
assert.equal(complete.effects.E,1);assert.equal(complete.budget.settled,14);
const low=await run(tasks,{limit:7});
const high=await run(tasks,{limit:13});
const slow=Array.from({length:5},(_,i)=>({id:'slow-'+i,ms:100,cost:3}));
const expired=await run(slow,{limit:10,deadlineMs:15});
assert.equal(expired.cancelled,true);assert.equal(expired.budget.settled,2);
assert.equal(expired.budget.reserved,0);assert.deepEqual(expired.effects,{});
assert.ok(Object.values(expired.results).some(r=>r.status==='not-started-cancelled'));
const manualController=new AbortController();manualController.abort();
const manual=await run(slow,{limit:10,externalSignal:manualController.signal});
assert.equal(manual.budget.settled,0);assert.equal(manual.peakActive,0);
for(const report of [complete,low,high,expired,manual]){
assert.ok(report.budget.minimum>=0 && report.budget.available>=0);
assert.equal(report.budget.reserved,0);
}
console.log(JSON.stringify({complete,low,high,expired,manual},null,2));
})().catch(error=>{console.error(error);process.exitCode=1;});
Como conferir seu resultado
- O relatório distingue tempo idealizado de tempo observado.
- A fila respeita o limite de tarefas ativas.
- Falhas permanentes não recebem retries e resultados válidos não são repetidos.
- Pico ativo é dois e pico da fila não supera um; backpressure é observado.
- Todos os cenários terminam com reserva zero e saldo disponível não negativo.
- Deadline cancela timers e fila; consumo iniciado permanece liquidado.
- Escrita ambígua produz um efeito e zero retries, mesmo com erro transitório.
Aplique em um problema novo
Primeiro resolva sem consultar a resposta. Explique suas decisões e guarde a evidência. A conclusão de leitura é independente desta autoavaliação.
Confira seus pré-requisitos
- Calcular caminho crítico e custos.
- Entender concorrência/reserva/cancelamento.
Consultas independentes duram 3/5/2 unidades; síntese 1. Compare sequência, paralelo ideal e dois workers. Custo 3/4/2, síntese 1, teto 8.
Conferir raciocínio e critérios de domínio
Sequência: 3+5+2+1=11 unidades. Paralelo ideal: max(3,5,2)+1=6. Com dois workers, tarefas de 3 e 5 iniciam; a de 2 ocupa o primeiro worker ao tempo 3 e termina ao tempo 5; a síntese termina ao tempo 6.
O custo total é 3+4+2+1=10, acima do teto 8. A decisão é não admitir as três: preservando síntese 1, apenas uma combinação de consultas com custo <=7 pode iniciar, com contribuição omitida explicitada.
Retry é reservado a leitura com erro transitório dentro do orçamento. Timeout de escrita exige consulta do recibo, pois repetir pode duplicar um efeito já produzido.
Evidências para autoavaliação ou revisão por pares
- Tempo: Sequência: 3+5+2+1=11 unidades. Paralelo ideal: max(3,5,2)+1=6. Com dois workers, tarefas de 3 e 5 iniciam; a de 2 ocupa o primeiro worker ao tempo 3 e termina ao tempo 5; a síntese termina ao tempo 6.
- Orçamento: O custo total é 3+4+2+1=10, acima do teto 8. A decisão é não admitir as três: preservando síntese 1, apenas uma combinação de consultas com custo <=7 pode iniciar, com contribuição omitida explicitada.
- Retry: Retry é reservado a leitura com erro transitório dentro do orçamento. Timeout de escrita exige consulta do recibo, pois repetir pode duplicar um efeito já produzido.
Um erro frequente
Paralelismo reduz soma dos custos.
Custos somam tentativas mesmo quando executadas em paralelo.
Teste sua compreensão
Responda com suas palavras antes de abrir o comentário. Saber explicar uma decisão é parte do domínio.
1. Mais agentes sempre reduzem latência?
Não. Dependências e overhead podem dominar o tempo.
O ganho depende do caminho crítico e dos recursos disponíveis.
2. Limitar concorrência limita todo custo?
Não. Cada chamada pode possuir consumo diferente.
Use também limites de duração, tentativas e orçamento.
3. A simulação demonstra preços de modelos?
Não. Seus pesos são unidades didáticas.
Preços e uso real precisam de fonte e medição correspondentes.
Seu progresso fica salvo neste navegador. Concluir a leitura não substitui demonstrar o domínio nos exercícios.
Referências e aprofundamento
Documentação oficial e trabalhos originais. As referências registram o escopo e as limitações para você conferir o que sustentam.
- Workflows and agents
LangChain • consulta: 2026-10-06
AgentesAutomaçãoRouting, parallelization, orchestrator-workers e evaluator-optimizer.
Limites: Planner/executor/reviewer é adaptação didática dos padrões; limitar ciclos e avaliar contra baseline.
- Multi-agent
LangChain • consulta: 2026-10-06
AgentesTrade-offs de padrões, número de chamadas, tokens, latência e paralelismo.
Limites: Exemplos de custo são ilustrativos e não um benchmark geral.
- Orchestration and handoffs
OpenAI • consulta: 2026-10-06
OpenAIAgentesHandoffs transferem responsabilidade; agents-as-tools mantêm o gestor responsável; especialistas e custos de divisão.
Limites: Não demonstra superioridade universal de múltiplos agentes; medir qualidade, custo e latência.