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/optimizer

Maliyet optimizer'ı

Ajanlarının yaptığı işi düşürmeden maliyetini düşür. Ölçüm, başkasının benchmark'ında değil, ajanın gerçekten yaptığı konuşmalarda yapılır.

Ajanın parası nereye gidiyor#

Bir ajanın parası beş yere gider. Her adımda yeniden gönderilen aynı açılış (sistem prompt'u, araç tanımları). Kullanıldıktan sonra taşınmaya devam eden eski araç çıktısı. Basit işi yapan pahalı model. Acil olmayan işin etkileşimli fiyattan ödenmesi. Gerekenden uzun cevaplar. Bunların çoğu kodundan görünmez, çünkü her adım tek başına makul görünür.

Cevap zaten journal'da. Kaydedilen her model adımı, sağlayıcının faturaladığını taşır: girdi token'ı, çıktı token'ı, önbellekten okunan token. @gnldev/optimizer bu kaydı okur. Ajanının bunlardan hangisine para ödediğini söyler, her biri için kolu verir ve bir değişikliğin işi aynı bıraktığını, değişikliği yapmadan önce kanıtlamanı sağlar.

npm install
npm install @gnldev/optimizer

1. Teşhis: faturadan okunur#

diagnoseAgent, ajanın son tamamlanan koşularını okur. Maliyeti, token'ları, önbellek okumalarını ve yazmalarını model başına toplar. Hiçbir model çağrılmaz.

diagnose.ts
import { diagnoseAgent } from '@gnldev/optimizer';
import { SqliteStorage } from '@gnldev/durable/sqlite';

const storage = new SqliteStorage('runs.db');

// Where the money went — read from what the provider billed. No model is called.
const d = await diagnoseAgent({ journal: storage.runs, agent: 'support' });
for (const f of d.findings) console.log(f.lever, f.tokens, f.estimatedSavingUsd?.toFixed(2) ?? 'unpriced', f.why);
  • Bir koşu içinde yeniden gönderilen açılış. İlk adımdan sonraki her adım, koşunun açılışını yeniden gönderir. İlk adımın girdi token'ları bu açılıştır, yani bu sayı doğrudan faturadan gelir. Sağlayıcının önbellekten zaten okuduğu düşülür. Kalan, koşu başına bir önbellek yazımından sonra önbelleğin hâlâ kazandırabileceği kısımdır.
  • Birbirinden 5 dakika içinde başlayan koşuların yeniden gönderdiği aynı talimatlar. Sağlayıcı önbelleği birkaç dakika tutar. Aynı sistem prompt'uyla bir başkasından kısa süre sonra başlayan koşu, onu önbellekten okuyabilir. Bu sayı bir tahmindir: journal talimatları saklar, token sayılarını değil.
  • Eski araç çıktısı. Bir araç sonucu sonraki her adımda taşınır. keepLast (varsayılan 2) adımdan eski sonuçlar sayılır. Bu da bir tahmindir (4 karakter bir token sayılır) ve teşhis bunu söyler.

Sağlayıcı kısa bir öneki önbelleğe almaz. minCacheTokens (varsayılan 1024), önbelleğe alınabilir sayılan en kısa açılıştır. OpenAI'ın alt sınırı ve çoğu model için Anthropic'inki budur. Daha kısa bir açılış asla tasarruf sayılmaz; teşhis bunun yerine bir not ekler.

Bulgular tahmini tasarrufa göre sıralı gelir. Her biri arkasındaki sayıyı tek cümleyle söyler. Fiyatlar @gnldev/durable fiyat tablosundan gelir. Tabloda olmayan bir model fiyatsız diye raporlanır, asla bedava diye değil.

2. İki kol#

Bulgulara iki değişiklik etki eder. İkisi de sağlayıcıya bir adımda gönderileni değiştirir, journal'a yazılanı asla değiştirmez. İkisi de isteğin saf fonksiyonudur; devam eden bir koşu aynı şeyi sorar.

withCacheBreakpoints(model, { cacheKey, ttl }) (ve ara katman olarak cacheBreakpoints) her sağlayıcıdan prompt önbelleğini ister. Anthropic: sistem mesajına ve son mesaja önbellek işareti, 4 işaret sınırı içinde. OpenAI: cacheKey değerin promptCacheKey olarak gönderilir; cacheKey yoksa anahtar gönderilmez. Diğer sağlayıcılar: dokunulmaz. Kalite değişemez, çünkü sağlayıcıya aynı token'lar gönderilir.

Anthropic'te önbelleğe yazmak düz girdiden pahalıdır: ttl 5 dakikaysa (varsayılan) 1,25 kat, 1 saatse 2 kat. Tek adımlık bir koşu, hiç okumayacağı bir önbelleğe para öder. Teşhis, ajanın koşularının bunun için yeterince uzun olup olmadığını söyler.

maskOldToolOutputs, keepLast (varsayılan 2) adımdan eski araç çıktısını, yalnızca o adım için, tek satırlık bir notla değiştirir. minChars (varsayılan 200) karakterden kısa bir sonuç, yaşı ne olursa olsun kalır. Journal tam çıktıyı saklar. Bu kol ajanın yaptığını değiştirebilir; önce yeniden oynat.

agent.ts
import { maskOldToolOutputs, withCacheBreakpoints } from '@gnldev/optimizer';
import type { AgentConfig } from '@gnldev/durable';
import { openai } from '@ai-sdk/openai';

const agent: AgentConfig = {
  model: withCacheBreakpoints(openai('gpt-4.1'), { cacheKey: 'support' }),
  processors: [maskOldToolOutputs({ keepLast: 2 })],
};
Ücretli anahtarla ölçüldü
OpenAI, gpt-4o-mini, aynı 1.964 token'lık açılışla 4 koşu. Önbellek anahtarı olmadan: önbellekten 0 token okundu. withCacheBreakpoints(model, { cacheKey }) ile: 2. ile 4. koşuların her biri önbellekten 1.792 token okudu. Çağrı başına %44, toplamda %33 daha ucuz. GNL'in hesapladığı maliyet, OpenAI'ın ham kullanım verisinden bağımsız hesaplanan maliyete eşitti.

3. Sahip olduğun koşularda kanıtla#

replayCounterfactual, kayıtlı bir koşuyu bir adayla yeniden oynatır: başka bir model, sistem prompt'u ya da bağlam politikası (prepareStep). Soru "ucuz model iyi mi" değil, "bu konuşmada aynı işi yapar mıydı". Yeniden oynatmanın kendisi @gnldev/experiment içindedir (0.16'da optimizer'dan oraya taşındı, geride takma ad kalmadı); @gnldev/optimizer ile birlikte kurulur.

replay.ts
import { replayCounterfactual } from '@gnldev/experiment'; // installed with @gnldev/optimizer
import { SqliteStorage } from '@gnldev/durable/sqlite';
import { openai } from '@ai-sdk/openai';

const storage = new SqliteStorage('runs.db');

// Would a cheaper model have done the same work? Replay a recorded run — no tool is ever executed.
declare const tools: Parameters<typeof replayCounterfactual>[0]['tools']; // the agent's own tool definitions
const r = await replayCounterfactual({ journal: storage.runs, runId: 'order-42', model: openai('gpt-4.1-mini'), tools });
console.log(r.divergence ?? 'same calls', r.recorded.costUsd, r.candidate.costUsd);

Hiçbir araç çalışmaz. Her aracın execute fonksiyonu kayıtta bir aramayla değiştirilir. Kaydın yaptığı bir çağrı (aynı araç, aynı argümanlar) kayıtlı sonucu alır. Kaydın hiç yapmadığı bir çağrının verilecek sonucu yoktur. Yeniden oynatma orada durur ve bir sapma raporlar. Sonuçta iki tarafın maliyeti ve çağrıları, adayın atladığı çağrılar ve çağrıları başka sırayla yapıp yapmadığı vardır.

replayAgent aynısını ajanın son koşuları için tek bir adayla yapar ve özetler: sapma oranı, çağrı atlanan koşular, sırası değişen koşular, maliyet oranı. Bir model fiyatsızsa oran uydurulmaz. Yeniden oynatılamayan bir koşu (kayıtlı girdi yok, model hatası, zaman aşımı) ayrı listelenir ve hiçbir sayıya girmez.

4. Trafik yokken: sabit bir set#

DEFAULT_SUITE, araç kullanan bir ajanın yaptığı 26 senaryodur: iadeler, sorgular, çok adımlı işler, düz sohbet, zor argümanlar. Her birinin küçük bir sahte depo üstünde sahte araçları ve bir programın kontrol ettiği bir beklentisi var: yapılması gereken çağrılar, yapılmaması gerekenler, cevabın söylemesi gereken bilgiler. Hiçbir hakem model karar vermez. DEFAULT_SUITE ve runSuite de @gnldev/experiment içindedir; compareOnSuite optimizer'da kalır.

runSuite seti tek modelle koşar. compareOnSuite iki modelle koşar ve maliyet oranını, gerilemeleri ve iyileşmeleri raporlar. Sorulamayan bir model (404, hız sınırı, zaman aşımı) senaryoyu error diye işaretler. Hatalar kalite hatalarından ayrı tutulur: geçme oranına da karşılaştırmaya da girmez.

5. Denenenler saklanır#

saveExperiment bir yeniden oynatmanın sonucunu journal'da saklar, listExperiments en yeniden başlayarak geri okur. Kayıtta koşu kimlikleri, çağrılar ve sayılar var; konuşma metni yok. Böylece eğilim görünür kalır: geçen hafta koşuların %8'inde sapan bir aday, bu hafta %3'ünde sapıyor.

renderReport, teşhisi ve saklanan yeniden oynatmaları bir Markdown rapora çevirir: para nereye gidiyor, bulgular, denenen yeniden oynatmalar ve bir öneri. Önerir; hiçbir şeyi değiştirmez.

Studio'da#

Studio'ya optimizer'ı verirsen bir Optimizer sekmesi gösterir: ajan başına teşhis, ajanın son koşularının senin sunduğun bir aday modelle yeniden oynatılması, şimdiye kadar denenenler ve indirilebilir bir rapor.

studio.ts
import { studioOptimizer } from '@gnldev/optimizer';
import { createStudioApp } from '@gnldev/studio';
import { toJournal, type CreateGnlConfig } from '@gnldev/durable';
import { SqliteStorage } from '@gnldev/durable/sqlite';
import { openai } from '@ai-sdk/openai';

declare const config: CreateGnlConfig; // the config createGnl is given
const storage = new SqliteStorage('runs.db');

const studio = createStudioApp({
  reader: toJournal(storage),
  apiBase: '/studio',
  optimizer: studioOptimizer({ config, models: { 'gpt-4.1-mini': openai('gpt-4.1-mini') } }),
});

studioOptimizer({ config, models, pricing }) şunları alır: createGnl'a verilen config (ajanlarının araçları yeniden oynatmayı cevaplar), Studio'nun gösterdiği adlarla aday modeller ve isteğe bağlı fiyatlar. Studio her çağrıya, hizmet ettiği isteğin okuyucusunu verir; bu okuyucu çağıranın organizasyonuyla sınırlıdır. Studio kendisi hiçbir model tutmaz. Yeniden oynatma bir model çağırır, bu yüzden yazma yetkisi ister ve denetim kaydına yazılır. Studio bir fetch handler'dır: Hono uygulamasında app.mount(...) ile bağla.

Bilerek yapmadıkları#

İstek başına modeller arasında yönlendirme, koşunun ortasında daha büyük bir modele geçme, "benzer soru" önbelleğinden cevap verme, araç argümanlarını ya da sonuçlarını sıkıştırma. Her biri bir koşunun yaptığını kimse görmeden değiştirebilir. Yönlendirilen bir koşu sağlayıcı önbelleğini kaybeder; önbellekten gelen bir cevap, yapılması gereken araç çağrılarını atlar.

Bir koşunun modeli koşunun ortasında asla değişmez. Daha ucuz bir model, ajana yaptığın bir değişikliktir ve yeni koşuları için geçerlidir.

API#

fndiagnoseAgent

diagnoseAgent({ journal, agent, limit?, keepLast?, minCacheTokens?, pricing? }) — ajanın son tamamlanan koşularını okur; model çağırmaz. journal: deponun runs'ı, deponun kendisi ya da bir journal.

typeAgentDiagnosis

diagnoseAgent'ın döndürdüğü: model başına maliyet ve token'lar, repeatedPrefix, sharedInstructions (tahmini), oldToolOutput (tahmini), tasarrufa göre sıralı bulgular ve notlar.

fnwithCacheBreakpoints

withCacheBreakpoints(model, { cacheKey?, ttl? }) — sağlayıcısından prompt önbelleğini isteyen model. Ajanın model'i olarak ver.

fncacheBreakpoints

cacheBreakpoints({ cacheKey?, ttl? }) — aynısı, AI SDK model ara katmanı olarak.

fnmaskOldToolOutputs

maskOldToolOutputs({ keepLast?, minChars? }) — eski araç çıktısını adımdan çıkaran bir processor (ve AI SDK prepareStep). Journal onu saklar.

fnreplayCounterfactual

@gnldev/experiment'tan (0.16'dan önce optimizer'daydı). replayCounterfactual({ journal, runId, model, tools?, system?, maxSteps?, prepareStep?, pricing?, timeoutMs? }) — tek bir kayıtlı koşu yeniden oynatılır; hiçbir araç çalışmaz; kaydın hiç yapmadığı çağrı bir sapmadır.

fnreplayAgent

replayAgent — ajanın son tamamlanan koşuları üzerinde replayCounterfactual, bir özetle: sapma oranı, atlanan çağrılar, sıra değişikliği, maliyet oranı.

constDEFAULT_SUITE

@gnldev/experiment'tan. Sahte araçları ve bir programın kontrol ettiği beklentileri olan 26 senaryo.

fnrunSuite

@gnldev/experiment'tan. runSuite({ model, scenarios?, pricing?, maxSteps?, concurrency?, timeoutMs? }) — geçme oranı, ayrı tutulan hatalar, maliyet.

fncompareOnSuite

compareOnSuite({ baseline, candidate, ... }) — iki modelde aynı senaryolar: iki modelin de koştuğu senaryolar üzerinde maliyet oranı, gerilemeler, iyileşmeler.

fnsaveExperiment

saveExperiment(journal, { agent, model, limit, keepLast?, result }) — bir yeniden oynatmanın sonucunu saklar; konuşma metni yok.

fnlistExperiments

listExperiments({ journal, agent, limit? }) — ajanın saklanan yeniden oynatmaları, en yenisi önce.

fnrenderReport

renderReport({ agent, diagnosis, experiments }) — öneri içeren bir Markdown rapor. Hiçbir şeyi değiştirmez.

fnstudioOptimizer

studioOptimizer({ config, models, pricing? }) — Studio'nun Optimizer sekmesinin çağırdığı nesne.

Bir değişikliği benimsemek senin değişikliğin
Buradaki hiçbir şey bir ajanı düzenlemez. Daha ucuz bir model işi yaptıysa, ajanın modelini kendi kodunda değiştir; değişiklik diğerleri gibi gözden geçirilsin ve test edilsin. Başlamış koşular başladıkları modelle devam eder.