GNL
Dokümanlar menüsü
Core · Ücretsiz@gnldev/durable

Alt ajanlar: birim istektir

Başka bir ajanın agents listesindeki ajan, agent_<name> aracı olarak çağrılır ve motorun kendi ajan çalıştırması olarak, ebeveyninin bir kat altında çalışır. Tekillik, limitler ve onaylar her alt ajanı ayrı ayrı değil, isteğin tamamını sayar.

Alt ajan nedir#

Bir ajanın agents alanına kayıtlı ajan adlarını yazdığınızda, her biri o ajana agent_&lt;name&gt; adlı bir araç olarak sunulur. Aracın tek argümanı vardır: task. Araç açıklaması alt ajanın description alanından gelir.

Alt ajan ayrı bir kod yolu değildir. Doğrudan çalıştırmayla aynı ajan çalıştırmasından doğar; görevi devreden çalıştırmanın bir kat altında, agent:&lt;parent runId&gt;:&lt;delegation call&gt; run id'siyle. Kendi processor'ları, scorer'ları, guard'ı ve alt ajanları çalışır. Ebeveynin çağıranı, iptali, zaman aşımları ve onayları devirle birlikte aşağı iner.

Taint iki yönde de geçer. Ebeveyn güvenilmeyen içerik okumuşsa alt ajan taint'li başlar; yan etkileri aynı taintedSideEffects merdiveninden geçer. Alt ajan güvenilmeyen içerik okursa, döndükten sonra ebeveyn de taint'li olur.

Birim istektir#

Bir isteğin başlattığı her şey — ebeveyn, her kardeş alt ajan, alttaki her kat — tek bir istektir. Tek bir ajan çalıştırması için geçerli olan, istek için de geçerlidir:

  • İstek başına bir kez. idempotencyKey ve idempotencyWindow: 'run' taşıyan ya da idempotency: 'args' olan bir çağrı, isteğin tamamında tekilleştirilir. Kayıt istekte durur (xreq:&lt;runId&gt;:args-…); her çalıştırma kendi çağrısı altında bir aynasını tutar.
  • Limitler isteğindir. maxToolCalls, maxTokens, maxCostUsd ve maxConcurrency ebeveynin, her kardeşin ve alt ajanın çağrılarını birlikte sayar.
  • Onayların adresi vardır. Alt ajanın sorusunun kendi adresi olduğu için bir onay tam olarak bir çağrıyı yanıtlar.

Örnek: 30 çalışan bordro alt ajanlarına dağıtılıyor, biri on kez veriliyor. Ödeme aracı çalışan kimliğiyle anahtarlanmış ve idempotencyWindow: 'run' taşıyor. 0.10'dan önce her alt ajan ayrı bir pencereydi ve 40 ödeme yapılıyordu. 0.10'da pencere istektir; 30 ödeme yapılır.

Anahtarsız bir tekrar da sessizce düşürülmez: varsayılan sideEffectDuplicates: 'suspend' ile bir kişiye soru olur. Kişinin reddettiği tekrar aynı istekte bir daha sorulmaz.

Kayıt, işi yürüten çağrıyı adıyla taşır (ranBy). İşi yapan alt ajan çalıştırmasını telafi etmek (compensateRun) ya da silmek (purgeRun), doğrudan çalıştırmada olduğu gibi o etkiyi geri alır ve siler.

Örnek: iade alt ajanı olan bir masa ajanı#

desk ajanı işi refunder'a devredebilir. refund aracı bir yan etkidir, çalışmadan önce bir kişinin onayını ister ve istek boyunca sipariş numarasıyla anahtarlanır.

createGnl — desk, refunder'a devreder
import { tool } from 'ai';
import { z } from 'zod';
import { createGnl, gnlTool } from '@gnldev/durable';

const refund = gnlTool(
  tool({
    description: 'Refund an order',
    inputSchema: z.object({ orderId: z.string(), amount: z.number() }),
    execute: async ({ orderId, amount }) => payments.refund(orderId, amount),
  }),
  {
    sideEffect: true,
    confirm: true,                // a person approves before it runs
    idempotencyKey: (input) => (input as { orderId: string }).orderId,
    idempotencyWindow: 'run',     // once per request, across every sub-agent
  },
);

const gnl = createGnl({
  storage,
  agents: {
    desk: {
      model,
      system: 'You answer customer tickets. Hand refunds to the refunder.',
      agents: ['refunder'],       // desk sees the tool agent_refunder({ task })
    },
    refunder: {
      model,
      description: 'Refunds an order',
      system: 'You refund orders.',
      tools: { refund },
    },
  },
});

İsteği çalıştırın. Alt ajan refund'da durur ve soru ebeveynin sonucunda görünür:

çalıştır, soruyu oku, onayla devam et
import { STAFF } from '@gnldev/durable';

const request = {
  runId: 'ticket-881',
  prompt: 'Refund order A-1001, 40 EUR.',
  caller: STAFF,
  limits: { maxToolCalls: 20 },   // counts desk's calls and refunder's together
};

const first = await gnl.run('desk', request);
const [q] = first.interrupts;
// q.toolCallId  'call_0/call_0'   <delegation call>/<child call>
// q.toolName    'refund'
// q.args        { orderId: 'A-1001', amount: 40 }
// q.reason      "The 'refunder' sub-agent asks: 'refund' requires explicit confirmation before it runs."
// q.delegatedTo { runId: 'agent:ticket-881:call_0', agent: 'refunder' }

const done = await gnl.run('desk', {
  ...request,
  approvals: { [q.toolCallId]: true },
});
// refund ran once; done.interrupts is []

Çalıştırmanın gösterdiği toolCallId'yi değiştirmeden yanıtlayın. Devam edildiğinde tamamlanmış iş journal'dan tekrar oynatılır, devir onayla birlikte aşağı iner ve refund bir kez çalışır. ticket-881'i yeniden çalıştırmak onu ikinci kez çalıştırmaz.

Ebeveynin modeline ne döner#

Devir, ebeveynin üzerinde dallanabileceği bir sonuç döndürür. Sonuç alt ajanın metninden değil, araç sonuçlarından okunur:

agent_<name> ne döndürür
// what an agent_<name> call returns to the parent's model
type DelegationResult = {
  status: 'completed' | 'failed' | 'suspended';
  text: string;
  interrupts: Interrupt[];   // addressed <delegation call>/<child call> when suspended
  failed?: Array<{ tool: string; kind: 'error' | 'blocked' | 'denied'; message: string }>;
}

failed, alt ajandaki son çağrısı başarısız olan, engellenen ya da reddedilen her aracı listeler. Aynı aracın başarısızlığından sonra gelen bir başarı sayılmaz.

Alt ajandan gelen onaylar#

Alt ajanın sorusu &lt;delegation call&gt;/&lt;child call&gt; adresini taşır. Çağrılarını ikisi de call_0 diye numaralayan iki alt ajan iki ayrı soru üretir. Her alt kat adrese yeniden önek ekler.

Soru delegatedTo: { runId, agent } taşır: alt ajanın çalıştırması ve adı; o alt ajan da devrettiyse bir sonraki adım için via. Kişinin okuduğu reason run id'yi değil alt ajanın adını söyler: The 'refunder' sub-agent asks: …. İki kat şöyle okunur: The 'lead' sub-agent asks: The 'medic' sub-agent asks: ….

Aynı runId ile approvals: { [interrupt.toolCallId]: true } vererek devam edin. Gösterilen id'yi yanıtlayan bir istemcinin başka bir şey yapması gerekmez; alt ajanın id'sini kendiniz kurmayın.

Alt ajanlar boyunca çalıştırma limitleri#

limits'i çalıştırmaya verin. maxToolCalls, maxTokens, maxCostUsd ve maxConcurrency isteğin tamamını sayar. Devir çağrısının kendisi (agent_&lt;name&gt;) bir araç çağrısı değildir ve eşzamanlılık yuvası tutmaz; alt ajanın yaptığı çağrılar sayılır.

Bir araç çağrısı çalışmadan önce isteğin toplamı üzerinde tek bir atomik adımla yer ayırır; bu yüzden paralel çağrılar maxToolCalls'u aşamaz (200 paralel çağrı, limit 50: bellek, SQLite, Postgres ve Redis'te 50 çalıştı). Yeni bir model adımı ancak token ve maliyet bütçesi kaldıysa başlar.

Çalıştırma limitleri

Kapsanmayanlar#

Dikkat
  • Limitli Postgres, 0.9'a göre daha fazla iş yapar; çünkü alt ajanların çağrıları artık istekte sayılıyor: limitli 200 alt ajan, 2299 → 2668 ms (10 çalıştırma).
  • Önceki bir sürümün yazdığı Redis deposu, onu paylaşan her süreç 0.10'a geçtikten sonra storage.rebuildKeyIndex() bir kez çağrılana kadar anahtarları SCAN ile listeler.
  • Tek istekte 2000'den fazla alt ajan ölçülmedi. Bellek içi adaptör büyük dağılımlarda karesel büyür.
  • Ağ yönlendiricisi bir adımın durumunu ve başarısız araçlarını yapılandırılmış devir sonucu olarak değil, metin olarak okur (FAILED (tool: reason)).
  • maxTokens ve maxCostUsd, aynı anda çalışan her çalıştırma için hâlâ bir adım kadar aşılabilir: eşzamanlı her çalıştırma, bütçe kaldığı sürece bir adım başlatabilir.

API referansı#

typeAgentConfig.agents

AgentConfig.agents: string[] — kayıtlı ajan adları; her biri { task } girdisi alan agent_<name> aracı olarak sunulur.

typeagent_<name> result

{ status: 'completed' | 'failed' | 'suspended', text, interrupts, failed? } — failed?: Array<{ tool, kind: 'error' | 'blocked' | 'denied', message }>.

typeInterrupt.delegatedTo

Interrupt.delegatedTo?: { runId, agent?, via? } — hangi alt ajanın sorduğu, çalıştırması ve bir sonraki adım.

typeRunLimits

RunLimits — maxToolCalls, maxTokens, maxCostUsd, maxConcurrency isteğin tamamını sayar.

fncreateAgentTool

createAgentTool(config, { description? }) — createGnl olmadan parçalardan kurulan alt ajan; runDurable ile çalışır ve aynı istek penceresini, adreslemeyi ve sonucu alır.

fnlistRuns({ topLevel })

listRuns({ topLevel: true }) / GET /runs?topLevel=true — istekleri listeler, alt ajan çalıştırmalarını dışarıda bırakır; alt çalıştırmanın özeti parentRunId taşır.