Orchestration e custo
Compare três consultas fictícias com duração e custo conhecidos, depois execute realmente duas funções de worker concorrentes. A solução usa uma fila com uma posição de espera, produtor sujeito a backpressure, timers abortáveis e um ledger que separa orçamento reservado de consumo liquidado. Não chama modelos e não representa preços reais, mas executa e verifica a política local em vez de apenas desenhar uma fila.
Prepare Node e salve a resolução como custos-concorrentes.cjs. Os testes incorporados abrangem sucesso, falha transitória, acesso negado, efeito com resposta perdida, dois tetos, deadline e cancelamento manual. Observe os eventos antes de consultar a interpretação. A temporização é uma simulação executada no relógio local, não benchmark de provedor; uma integração real precisa manter os mesmos contratos e medir uso e duração efetivos.
AgentesAo terminar esta aula
- A conta idealizada identifica hipóteses que a medição real deve testar.
- Contribuições válidas não devem ser refeitas porque outra tarefa falhou.
- A ordem de orçamento define prioridades e precisa ser uma decisão explícita.
Antes de continuar: Leitura: Orchestration e custo
Faça a conta antes da execução
FundamentosAntes de executar, calcule à mão a soma nove e o máximo quatro das durações de A, B e C no exemplo nominal da leitura. Acrescente duas unidades de síntese e desenhe o caminho crítico. Para dois workers e durações 2, 4 e 3, A e B iniciam juntos; quando A termina em 2, C ocupa sua vaga e termina em 5. O total nominal é cinco antes da síntese e sete depois. Essa conta ainda não inclui falhas ou overhead. O código da resolução usa milissegundos distintos e tarefas extras para verificar mecanismos; não confunda os dois conjuntos de números.
↗ Workflows and agents↗ Multi-agent↗ Orchestration and handoffs
Execute a resolução e compare os relatórios low, high e complete. Ela protege uma unidade para síntese antes das consultas; o saldo disponível é limit menos reserved menos settled. Com teto sete, consultas podem ser negadas enquanto outras mantém reservas. Essa política falha imediatamente quando não há capacidade para reservar o pior uso da próxima tentativa: ela não espera que outra chamada devolva saldo. É uma escolha conservadora, não uma otimização universal. Mudar prioridades ou aguardar liberação poderia aceitar outro conjunto, mas exigiria deadline e justiça de agendamento adicionais.
↗ Workflows and agents↗ Multi-agent↗ Orchestration and handoffs
Implemente uma fila limitada
FundamentosA classe BoundedQueue possui capacity igual a um. put entrega diretamente a um consumidor esperando ou ocupa a posição; quando ela está cheia, o produtor aguarda uma Promise que take libera. Isso é backpressure verificável pelo contador, não descarte silencioso nem lançamento de todas as Promises em Promise.all. Dois workers iniciados uma única vez retiram tarefas até a fila fechar e mantém active no início e fim de cada tentativa. Os asserts exigem pico de exatamente dois, fila com no máximo um item e pelo menos uma espera do produtor. O array de entrada ainda está todo em memória; em carga grande, um produtor por iterator deve evitar materializá-lo.
↗ Workflows and agents↗ Multi-agent↗ Orchestration and handoffs
A função run aceita uma fila finita e fecha o canal quando o produtor termina. Fechar normalmente permite drenar itens já aceitos; cancelar descarta os que ainda não começaram e acorda tanto consumidores quanto produtor bloqueado. Esse detalhe impede deadlock no deadline: não basta abortar a chamada e deixar put esperando para sempre. Leia os eventos task, workerId e attempt e confirme que A e C válidos aparecem uma única vez. B falha temporariamente na primeira leitura e recebe somente uma nova tentativa, depois de uma espera pequena que também observa o signal.
↗ Workflows and agents↗ Multi-agent↗ Orchestration and handoffs
D representa acesso negado e termina sem retry. E simula escrita: o efeito ocorre, mas a resposta se perde. Embora o erro seja transitório, a política proíbe repetir qualquer tarefa com effect, conservando exatamente um efeito e pedindo reconciliação fora deste exemplo. Não é uma implementação distribuída de idempotência; é uma regra local segura de não insistir diante de escrita ambígua. Os asserts contam chamadas por ID e effects.E, de modo que repetir tudo depois da falha de um especialista quebra o teste. Essa escolha sacrifica recuperação automática para não duplicar uma escrita cujo destino ainda não oferece chave idempotente.
↗ Workflows and agents↗ Multi-agent↗ Orchestration and handoffs
Controle custo incerto e cancelamento
FundamentosBudget reserva o custo máximo de cada tentativa de forma síncrona antes do primeiro await. A reserva reduz available, mas não soma consumo definitivo. settle libera a reserva inteira e adiciona somente actual ao consumo liquidado, verificando saldo não negativo e impedindo liquidar duas vezes. Na fixture, uma tentativa iniciada e cancelada ou recusada consome uma unidade; sucesso consome task.cost, e E com efeito ambíguo consome seu custo inteiro. Esses valores são contratos artificiais explícitos, não estimativas de token. Uma implementação de provedor precisa reconciliar seu uso reportado e considerar margem quando o máximo não for garantido.
↗ Workflows and agents↗ Multi-agent↗ Orchestration and handoffs
A sequência de reserva é atômica apenas porque executa sem suspensão dentro de um processo JavaScript. Dois processos não compartilham esse Budget: para vários workers distribuídos, mova reserva, saldo e liquidação para transação ou primitiva atômica de um armazenamento compartilhado. A reserva da síntese permanece separada desde o início, garantindo uma unidade para concluir a resposta quando houver contribuições úteis. Se o workflow for cancelado, a síntese não roda e sua reserva é liberada com consumo zero. O finally assegura a liberação mesmo em um encerramento excepcional.
↗ Workflows and agents↗ Multi-agent↗ Orchestration and handoffs
O deadline dispara AbortController e fecha a fila. sleep remove seu listener e interrompe o timer cooperativamente. No cenário expired, duas consultas de cem milissegundos já iniciadas são canceladas; cada uma mantém consumo artificial de uma unidade, enquanto itens ainda não iniciados consomem zero. O relatório termina com reserved zero, settled dois e available oito em teto dez. Deadline limita a espera e impede novas tarefas; não devolve recursos já consumidos. Um SDK ou serviço que ignora AbortSignal pode continuar trabalhando e faturando: registre essa condição, reconcilie uso tardio e não libere uma reserva como se tivesse certeza de consumo zero.
↗ Workflows and agents↗ Multi-agent↗ Orchestration and handoffs
manual testa cancelamento antes da entrada de qualquer tarefa: pico ativo zero, consumo zero e nenhuma escrita. Em serviço real, acrescente cancelamento durante uma requisição, retorno tardio e timeout depois do efeito, separando “parei de aguardar” de “o destino interrompeu”. Este laboratório cobre timers cooperativos e fila no mesmo processo; não comprova cancelamento remoto, reversão de efeito ou ausência de cobrança. Antes de confiar no adapter externo, teste seu comportamento em uma conta isolada e preserve identificadores da operação para reconciliação.
↗ Workflows and agents↗ Multi-agent↗ Orchestration and handoffs
Compare desenhos com o mesmo conjunto de casos
FundamentosCompare low com teto sete, high com treze e complete com vinte mantendo o mesmo conjunto. No cenário completo os asserts exigem consumo quatorze: A três, B uma na falha e quatro no retry, C duas, D uma, E duas e síntese uma. Saldo final seis e reservas zero. O cenário high pode rejeitar E porque reservas conservadoras competem com consumo já liquidado; low protege síntese e aceita apenas o que consegue reservar. Os testes exigem saldo mínimo não negativo em todas as execuções, sem afirmar que a política encontra o conjunto de maior valor.
↗ Workflows and agents↗ Multi-agent↗ Orchestration and handoffs
Sua tabela deve registrar pico ativo, pico da fila, esperas do produtor, número de tentativas, contribuições válidas, reserva final e consumo. Acrescente tempo em fila e execução se integrar um provedor; não invente uma economia monetária com as unidades da fixture. Explique dois trade-offs: backpressure limita trabalho em espera mas pode atrasar o produtor, e reservar o pior consumo protege o teto mas pode rejeitar tarefas que caberiam após reconciliação. A solução preserva resultados válidos quando B falha, respeita limite de retries e não reexecuta escrita ambígua. Relacione essas evidências ao critério de qualidade da pesquisa, incluindo o resultado parcial quando uma fonte obrigatória fica ausente.
↗ 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.
Dois workers, fila máxima 1 e teto 10: reserva síntese 1; A custa máximo 3 e leitura falha uma vez; B custa 2 e nega acesso; C custa 3 e perde resposta após escrita. Defina invariantes e recuperação sem inventar consumo real.
Conferir raciocínio e critérios de domínio
A pode repetir leitura dentro do limite; B não recebe retry, pois acesso negado não melhora com insistência; C consulta recibo, não escreve novamente.
Reservas simultâneas mais a síntese nunca excedem 10; tarefas sem saldo não iniciam. Concorrência máxima 2 e fila máxima 1 exigem backpressure.
Cancelamento libera trabalho ainda não iniciado e reservas não consumidas, mas não desfaz a escrita de C. Sem consumos liquidados fornecidos, saldo final numérico é não determinado.
Evidências para autoavaliação ou revisão por pares
- Retry por classe de erro: A pode repetir leitura dentro do limite; B não recebe retry, pois acesso negado não melhora com insistência; C consulta recibo, não escreve novamente.
- Reserva e concorrência: Reservas simultâneas mais a síntese nunca excedem 10; tarefas sem saldo não iniciam. Concorrência máxima 2 e fila máxima 1 exigem backpressure.
- Cancelamento e saldo: Cancelamento libera trabalho ainda não iniciado e reservas não consumidas, mas não desfaz a escrita de C. Sem consumos liquidados fornecidos, saldo final numérico é não determinado.
Um erro frequente
Cancelar desfaz efeito externo.
Cancelamento da espera não desfaz efeito externo realizado.
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.