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

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

İstek limitleri & maliyet tavanı

RunLimits ile token/maliyet/araç-çağrısı tavanında kaçak bir isteği durdurun; tavan, isteğin başlattığı her çalıştırmayı, alt-ajanlar dahil, sayar.

Ne işe yarar / ne zaman kullanılır#

Bir çalıştırma kontrolden çıkabilir — pahalı bir model döngüsü, kaçak bir araç-çağrısı zinciri, bütçelemediğiniz bir maliyet. RunLimits, birikimli token'lara, birikimli USD maliyetine ve araç çağrısı sayısına bir tavan koyar, aynı anda yürüyen araç çağrısı sayısını sınırlar; ayrıca aynı çağrıyı tekrarlayıp duran bir aracı da tespit edebilir. Tavanlar isteğe aittir: isteğin başlattığı her çalıştırmayı sayar, ajanın kendisininkini ve altındaki her alt-ajanınkini. Tüm alanlar opsiyoneldir: bir alanı ayarlamazsanız o koruma KAPALIdır.

RunLimits alanları#

Her alan opsiyoneldir ve ayarlanmazsa bağımsız olarak kapalıdır:

typeRunLimits.maxTokens

İsteğin birikimli toplam token'ı (girdi + çıktı + cache'lenmiş). Yeni bir model adımı yalnızca toplam bu değerin altındayken başlar; toplamı bu değerin üstüne taşıyan adım çalıştırmayı durdurur.

typeRunLimits.maxCostUsd

İsteğin birikimli USD maliyeti. Yeni bir model adımı yalnızca maliyet bu değerin altındayken başlar; maliyeti bu değerin üstüne taşıyan adım çalıştırmayı durdurur.

typeRunLimits.maxToolCalls

İsteğin araç çağrısı sayısı bu değere ULAŞTIĞINDA (>=) BİR SONRAKİ araç çağrısını bloklar. İstekteki her çalıştırmanın çağrılarını sayar; bir alt-ajana devretmek çağrı sayılmaz. Her çağrı yürümeden önce bir koltuk alır, bu yüzden paralel çağrılar limiti aşamaz.

typeRunLimits.maxConcurrency

İsteğin kaç araç çağrısının aynı anda yürüyebileceği — ajanın kendisininkiler ve her alt-ajanınkiler. Tavanı aşan çağrı bir slot bekler; reddedilmez. Bir devretme slot almaz. Ölmüş bir sürecin tuttuğu slot, claim'i bayatladığında düşer; iptal edilen bir istek beklemeyi bırakır. Varsayılan: tavan yok.

typeRunLimits.loopDetection

Opsiyonel alt-yapılandırma: { maxRepeats?, onRepeat? }. Aynı argsHash ile art arda çağrılan aynı aracı tespit eder.

typeloopDetection.maxRepeats

Kaç ardışık özdeş (araç + argsHash) çağrının cevabı tetikleyeceği.

typeloopDetection.onRepeat

'block' anında ToolLoopDetectedError fırlatır; 'reflect' önce modele bir nudge iletir.

Kurulum / import#

RunLimits @gnldev/durable içinde yaşar. Bir çalıştırmaya verilir ve tüm istek için sayılır:

import { createGnl, RunLimitExceededError, type RunLimits } from '@gnldev/durable';

Adım adım kullanım#

gnl.run'a limits geçin. Bir tavana ulaşıldığında RunLimitExceededError fırlatılır ve çalıştırma bir sonraki adımı başlatmaz:

// A new model step starts only if budget is left. A step already running is not cut,
// so that one step can go past the ceiling; no step starts after it.
const limits: RunLimits = { maxTokens: 50_000, maxCostUsd: 0.5, maxToolCalls: 20 };

try {
  await gnl.run('asst', { runId: 'r5', prompt: 'do the task', limits });
} catch (e) {
  if (e instanceof RunLimitExceededError) {
    // e.detail: { kind, value, limit }
    console.log('limit exceeded:', e.detail.kind, e.detail.value, e.detail.limit);
  }
}

Aynı araç çağrısını tekrarlayıp duran bir modeli yakalamak için loopDetection ayarlayın. reflect, hiçbir şey bloklanmadan önce modele toparlanma şansı verir:

// Same tool + identical argsHash repeated 3 times IN A ROW → 'reflect' delivers a nudge
// instead of an immediate block ('block' throws ToolLoopDetectedError right away).
const limits: RunLimits = { loopDetection: { maxRepeats: 3, onRepeat: 'reflect' } };

Hata fırlatıldığında journal tutarlı kalır — limiti yükseltin ve çalıştırmanın bıraktığı yerden devam etmek için AYNI runId ile resume edin:

// The journal stays consistent when RunLimitExceededError is thrown.
// Raise the ceiling and resume the SAME runId to continue past the cut-off point:
await gnl.run('asst', { runId: 'r5', prompt: 'continue', limits: { maxTokens: 200_000 } });

Limitler isteğindir#

Bir istek, tek bir çağrının başlattığı her şeydir: ajanın çalıştırması ve altındaki her alt-ajan çalıştırması. maxToolCalls, maxTokens ve maxCostUsd hepsini sayar — üst ajanın çağrılarını, her kardeşinkini ve alt-ajanınkini. İşi bir alt-ajana devretmek kendi başına bir araç çağrısı sayılmaz. Yeni bir model adımı yalnızca bütçe kaldıysa başlar: 60 token'lık bir bütçede istek artık doğrudan da alt-ajanlar üzerinden de 60'ta durur (0.10'dan önce: doğrudan 75, alt-ajanlar üzerinden 90).

Paralel gönderilen her çağrı, yürümeden önce isteğin araç-çağrısı toplamında tek bir atomik adımla bir koltuk alır; biten çağrı koltuğunu kendisini saydığı adımda geri verir. Limitin kabul ettiği her çağrı çalışır, fazlası çalışmaz: limit 5 iken on iki paralel çağrının 0.10'dan önce on ikisi de çalışıyordu, şimdi beşi çalışır; limit 50 iken 200 paralel çağrıdan memory, Sqlite ve Postgres'te tam 50'si çalışır. incrBy'ı değer döndürmeyen özel bir journal, compare-and-set koltuğunu korur: limit asla aşılmaz, ama limitin sınırındaki bir çağrı, başka biri biterken reddedilebilir.

// The limits are the request's: the agent's calls and every sub-agent's count
// toward one total. Handing work to a sub-agent is not itself a tool call.
const limits: RunLimits = {
  maxToolCalls: 50,  // 200 parallel calls → exactly 50 run
  maxConcurrency: 4, // at most 4 tool calls of the request execute at the same time
};

await gnl.run('coordinator', { runId: 'r7', prompt: 'process the batch', limits });

Bir GNL sunucusuna gönderilen istek kendi limits'ini taşıyabilir, ama yalnızca maxCostUsd, maxTokens, maxToolCalls, maxConcurrency ve loopDetection.maxRepeats'i sıkılaştırabilir — sunucunun değeriyle isteğinkinden küçük olanı geçerlidir. İsteğin limits'indeki başka her ad 400 ile reddedilir; sunucunun kendi limitlerinin hepsi çalıştırmaya ulaşır.

CLI'nin yazdığı bir projede dağıtımın kendi sınırı gnl.config.ts içindeki limits alanıdır (maxCostUsd, maxTokens, maxToolCalls ve döngü algılama). gnl dev ile gnl add host'un yazdığı src/app.ts onu sunucu tarafı sınır olarak createRestApi'ye verir: istek hiçbirini söylemediğinde olduğu gibi uygulanır, istek onu yalnızca sıkılaştırabilir. Studio Playground çalıştırmaları REST isteği değildir ve bununla sınırlanmaz.

gnl.config.ts
export default {
  // the server-side bound: `gnl dev` and the src/app.ts that `gnl add host` writes hand it to createRestApi
  limits: { maxCostUsd: 0.5, maxToolCalls: 20 },
};

maxCostUsd tavanı artık kendini adlandırmayan bir modelde de devreye girer. Fiyatlama model kimliğini yalnızca cevaptan okurdu; bunu atlayan bir sağlayıcı, proxy ya da middleware her adımı unknown, yani 0 $ olarak fiyatlar ve tavan hiç tetiklenmezdi. Modelin yapılandırılmış kimliği (wrapLanguageModel'in tuttuğu) artık yedektir ve her adımın yanına configuredModelId olarak yazılır; cevabın kendi kimliği yine önce gelir ve fiyatsız model uyarısı gerçek kimliği söyler. Bundan önce yazılmış kayıtlarda alan yoktur ve eskisi gibi fiyatlanır.

Streaming: bloklanan/limit ihlallerini yakalamak#

streamDurable / gnl.stream(name, opts), bir loop, maxToolCalls, duplicate ya da tainted BLOCK ateşlendiğinde çağrılan bir onBlocked callback'i kabul eder. Guard'ın ürettiği HAM yapılandırılmış ihlali alır — fırlatılan bir hatanın taşıyacağı bilgiyle aynısını. result.text artık bir block'ta da REDDEDİLİR; bu, gnl.run'dan fırlatılan RunLimitExceededError'ı yansıtır, böylece stream'in doğrudan bir tüketicisi bunu sessizce kaçıramaz.

// onBlocked fires for a loop / maxToolCalls / duplicate / tainted BLOCK — you get the
// RAW breach and decide what (if anything) the user sees. GNL does not invent a
// user-facing message.
const result = await gnl.stream('asst', {
  runId: 'r6',
  prompt: 'do the task',
  limits,
  onBlocked: (breach) => {
    // breach.kind identifies which guard fired; breach.message is a raw diagnostic
    // string; breach.detail carries that guard's own structured payload.
    console.log('blocked:', breach.kind, breach.message);
  },
});

try {
  const text = await result.text;
} catch (e) {
  // result.text REJECTS on a block — mirrors runDurable throwing, so a direct
  // consumer of the stream cannot silently miss the breach.
}

API referansı#

classRunLimitExceededError

Bir tavan aşıldığında enforceStepLimits tarafından fırlatılır. detail: { kind, value, limit }; kind bir RunLimitKind'dır.

typeRunLimitKind

'maxCostUsd' | 'maxTokens' | 'maxToolCalls' — RunLimitExceededError.detail içinde hangi tavanın aşıldığını belirtir.

fnenforceStepLimits

maxTokens/maxCostUsd'yi isteğin toplamı üzerinden kontrol eder: yeni bir model adımından önce (bütçeye ulaşıldıysa durdurur) ve her adım journal'landıktan sonra (bütçe aşıldıysa durdurur). maxCostUsd ve maxTokens'ın ikisi de null ise erken döner.

typeRunOptions.limits

RunOptions.limits ile ayarlanır ve runDurable/streamDurable'a iletilir. Alt-ajanlar aynı limitlerle çalışır, isteğin toplamı üzerinden sayılır.

typeonBlocked

streamDurable / gnl.stream(name, opts) üzerinde callback seçeneği: (breach) => void. Bir loop, maxToolCalls, duplicate ya da tainted guard BLOCK olduğunda çağrılır. breach: { kind, message, detail } — kullanıcıya gösterilecek bir metin değil, ham yapılandırılmış payload. Bir block ateşlendiğinde result.text de reddedilir.

Organizasyon-seviyesi bütçeyle aynı değil
RunLimits / RunLimitExceededError istek başınadır — @gnldev/server'daki organizasyon-seviyesi bütçe sisteminden (BudgetExceededError → HTTP 402, budget-quota sayfasında belgelenir) FARKLIDIR; o sistem harcamayı istekler boyunca organizasyon başına izler. İkisini birbirine karıştırmayın: bir istek RunLimits sınırları içinde kalıp yine de organizasyon bütçesi tarafından reddedilebilir, ya da tersi olabilir.
Not
Tavan konusunda dürüst olalım: yeni bir model adımı yalnızca bütçe kaldıysa başlar, ve bütçeye tam ulaşıldığında çalıştırma bir sonraki adımdan önce durur. Başlamış bir adım yarıda kesilmez; bu yüzden o tek adım toplamı tavanın üstüne taşıyabilir — garanti 'bütçe bitince yeni adım yok'tur, 'tam limitin asla üstüne çıkmaz' değil. Tek bir adımı sınırlamak için modele sağlayıcı tarafında max_tokens geçin.
GNL kullanıcıya gösterilecek mesajınızı yazmaz
onBlocked size framework'ün yapılandırılmış ihlalini verir — kind, message, detail — bir son kullanıcı için düşünülmüş bir cümle değil. Kullanıcının ne göreceğine (görecekse) karar vermek uygulamanızın işidir: GNL kullanıcıya gösterilecek bir mesaj icat etmez.
İlgili
Sıkça birlikte kullanılan organizasyon-seviyesi karşılığı için budget-quota'ya, RunLimits'in karşılaştırdığı maliyet rakamının nasıl hesaplandığı için cost-observability'ye ve tekrarlanan yan-etkili bir çağrıya karşı kardeş koruma için duplicate-guard'a bakın.