GNL 0.18: erken aşamada ve hızlı ilerliyor — API'ler sürümden sürüme değişebilirBize katıl

GNL
Core · Ücretsiz@gnldev/durable

Ajanın bir tool'u çalıştırmadan önce insana sor. Süreç ölünce cevabı kaybetme.

Sormak kolay kısım. Zor olan, soruyla cevap arasındaki saatler ve evet'ten hemen sonraki an.

Belirti#

Bir ajan bülten hazırlıyor ve 40.000 kişiye e-posta gönderen sendCampaign'i çağırıyor. Bunun bir insan evet demeden olmasını kimse istemez, o yüzden tool önce soruyor. Pazarlama sorumlusu ertesi sabah onaylıyor. Tool çalışıyor, ve konuşma kaydedilmeden hemen önce worker yeniden deploy ediliyor. Bir sonraki çağrıda ajan kayıtlı geçmişi okuyor, onaylanmış ama sonucu olmayan bir çağrı görüyor ve kampanyayı yeniden gönderiyor.

yalnızca AI SDK ile onay
import { generateText, tool } from 'ai';
import { z } from 'zod';

const sendCampaign = tool({
  description: 'Email a campaign to its whole audience',
  inputSchema: z.object({ campaignId: z.string() }),
  needsApproval: true, // the tool does not run until a person says yes
  execute: async ({ campaignId }) => mailer.sendCampaign(campaignId),
});

// Next morning: the marketing lead approved. Append the answer and call again.
messages.push({ role: 'tool', content: [{ type: 'tool-approval-response', approvalId, approved: true }] });
const result = await generateText({ model, tools: { sendCampaign }, messages }); // the campaign goes out
await db.saveMessages(messages.concat(result.response.messages));
// A redeploy before this save → the stored history still says "approved, not run"
// → the next call sends the campaign to 40,000 people again.

Neden olur#

AI SDK'nın needsApproval'ı doğru başlangıç noktası: tool çalışmaz, sonuçta bir tool-approval-request gelir. Bunu bir insana gösterirsin, onun tool-approval-response'unu mesajlara eklersin ve generateText'i yeniden çağırırsın; tool o zaman çalışır.

Aradaki her şey senin işin. Soru da cevap da yalnızca mesaj dizisinde var, onu sen saklarsın. Tool ikinci çağrının içinde çalışır, yeni mesajları da o döndükten sonra kaydedersin. Süreç bu iki an arasında ölürse kayıtlı geçmiş hâlâ onaylandı, henüz çalışmadı der ve bir sonraki çağrı onu yeniden çalıştırır.

Elle düzeltmek#

Bu boşluğu kapatınca kod, her riskli tool için yazdığın küçük bir durum makinesine dönüşür:

  • Kararın kaydı. Bekleyen onaylar için çağrıya göre anahtarlanmış, yeniden başlatmadan sağ çıkan ve eski bir isteği yenisinden ayırabilen bir tablo.
  • Tool'un çalıştığının kaydı. Etkiyle aynı adımda yazılmalı, yoksa tek bir evet iki kez kullanılabilir — düz retry'daki pencerenin aynısı.
  • Bir gelen kutusu. Birçok run'a dağılmış bekleyen onayların, bir insanın her çağrının argümanlarıyla ne yapacağını gördüğü tek bir yeri olmalı.

GNL ile#

Kapıyı tool'un üzerinde tanımla ve runDurable'ı çağır. sendCampaign'e gelen yeni bir çağrı çalışmaz; run durur ve result.interrupts hangi çağrının beklediğini argümanları ve senin yazdığın gerekçeyle söyler.

aynı ajan, dayanıklı
import { runDurable, gnlTool } from '@gnldev/durable';
import { PostgresStorage } from '@gnldev/durable/postgres';

const tools = {
  // the same tool without needsApproval — approve takes its place (only an authorised person may say yes)
  sendCampaign: gnlTool(sendCampaign, {
    sideEffect: true,
    approve: { reason: (args) => `send campaign ${(args as { campaignId: string }).campaignId} to its audience?` },
  }),
};
const journal = new PostgresStorage({ connectionString: process.env.DATABASE_URL }).runs;
const runId = `campaign:${campaignId}`; // the id of THIS job — never a session id

const first = await runDurable({ runId, journal, model, tools, prompt });
// first.interrupts → [{ toolCallId, toolName: 'sendCampaign', args, reason }] — show it to a person

// Hours later, in any process, with the toolCallId the person answered:
await runDurable({ runId, journal, model, tools, prompt,
  approvals: { [toolCallId]: true } }); // false = deny

Cevap saatler sonra, başka bir süreçten gelebilir: aynı runId ve approvals içindeki kararla runDurable'ı yeniden çağır. Biten adımlar journal'dan okunur, onaylanan çağrı bir kez çalışır ve ajan devam eder.

Karar senin mesaj dizine değil journal'a yazılır. Süreç evet'ten sonra ve tool çalışmadan önce ölürse, bir sonraki çağrı kayıtlı kararı yeniden söylenmesine gerek kalmadan uygular. Tool'un sonucu kaydedildikten sonra hiçbir çağrı onu bir daha çalıştırmaz.

Askıdaki bütün run'ların onayları tek bir yerde toplanır: Studio'nun gelen kutusu tool'u, durdurduğu run'ı ve çalışacağı argümanların tam halini gösterir.

Not
Hayır da bir cevap. approvals içinde çağrı için false verirsen ret kaydedilir; tool hiç çalışmaz ve model izin verilmediğini okur, böylece kullanıcıya söyleyebilir.

GNL'in çözmediği durumlar#

Çağrının içindeki çökme. Süreç sendCampaign çalışırken ölürse e-postaların çıkıp çıkmadığını kimse bilmez. GNL tahmin etmez: devam etme SideEffectRetryBlockedError ile durur, ya da tool'unun recover()'ı e-posta sağlayıcısına sorar.

Kim onaylayabilir. approve sorusuna evet yalnız yetkili kişiden alınır: varsayılan olarak personel (operatör, Studio) ya da uygulamanın kendi sunucusu, veya createGnl'deki approvers kuralının izin verdiği kişi. İsteyenin kendi evet'i approval_not_permitted (403) ile reddedilir. Cevabı isteyen versin istiyorsan soruyu confirm ile sor. Pazarlama sorumlusunun doğru yetkili olup olmadığı yine senin yazacağın kuraldır.

Bir evet, bir çağrı. Onay tek bir tool çağrısına aittir. Model kampanyayı yeni bir çağrı olarak yeniden planlarsa yeniden sorar. Varsayılan olarak kaydedilmiş bir evet çökmeden sağ çıkar; her denemenin yeniden sorması gerekiyorsa limits.approvalScope: 'attempt' kullan.

Kaynaklar#

Bu sayfadaki her iddianın herkese açık repoda bir testi var:

  • packages/durable/test/approval-persistence.test.ts — onaylandı, tool çalışmadan çöktü, onay verilmeden devam edildi: tool bir kez çalışır; ret de kalıcıdır
  • packages/durable/test/suspend-cross-process.test.ts — bir süreç askıya alır, başka biri bekleyen çağrıyı okuyup onaylar, tool bir kez çalışır
  • packages/durable/test/faz3-confirm-thread.test.ts — confirm kapısı: ilk çağrı askıya alınır, onay tam bir kez çalıştırır, ret etkiyi hiç tetiklemez
  • packages/durable/test/approval-scope.test.ts — journal'a yazılmış onay ile tek bir denemede harcanan onay
  • packages/durable/test/approval-two-questions.test.ts — tek çağrıda rıza ve izin: isteyen izin sorusunu cevaplayamaz, personel cevaplar ve çağrı bir kez çalışır; kimseyi adlandırmayan doğrudan çağrı yetkili sayılır

Mekanizmanın tamamı: human-in-the-loop onayları. github.com/gnlhq/gnldev