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

Otomatik REST API + OpenAPI + SSE

createGnl yapılandırmasını tek satırda durable HTTP API'ye çevirir: /agents/:name/run|resume|stream (SSE), /workflows, /runs, /usage ve üretilmiş /openapi.json.

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

Elinde zaten bir createGnl yapılandırması (agent'lar + workflow'lar + storage) varsa, bunu dışarıya bir HTTP API olarak açmak için endpoint'leri elle yazman gerekmez. createRestApi config'i okuyup çalıştırma, resume, SSE streaming, run geçmişi ve kullanım (usage) uçlarını otomatik üretir — hepsi runDurable üzerinden aktığı için exactly-once ve durability garantileri bedava gelir.

Tipik senaryo: bir Hono uygulamasına app.mount('/api', createRestApi(config, opts)) ile bağlayıp, bir istemciyi (web, CLI, başka bir servis) doğrudan bu uçlara konuşturmak. Auth, çok organizasyonluluk ve bütçe/kota opsiyonel — vermezsen uçlar açık ve tek organizasyonlu çalışır (aşağıda örneklenmiştir).

Kurulum / import#

Paket @gnldev/server; journal/storage için @gnldev/durable ve alt-export'u @gnldev/durable/sqlite kullanılır.

npm install
npm install @gnldev/server @gnldev/durable hono
config oluştur
import { Hono } from 'hono';
import { createRestApi } from '@gnldev/server';
import { createGnl } from '@gnldev/durable';
import { SqliteStorage } from '@gnldev/durable/sqlite';

const storage = new SqliteStorage('runs.db');
const config = {
  storage,
  agents: {
    starwars: { model: buildModel(), system: SYSTEM, tools: makeSwapiTools(), maxSteps: 4 },
  },
};

Adım adım kullanım#

Config hazır olunca createRestApi(config, opts) bir Hono router döner; bunu istediğin bir yola bağlarsın (ör. /api).

app.ts
const app = new Hono();
app.mount('/api', createRestApi(config, { title: 'SWAPI Free', auth }));

// POST /api/agents/starwars/run     {"runId":"demo-1","prompt":"..."}
// POST /api/agents/starwars/stream  -> SSE (text-delta/tool-call/tool-result/interrupt/done)
// GET  /api/usage                   -> token/maliyet raporu

Çalıştırma isteğinde runId zorunludur — bu, exactly-once garantisinin idempotency anahtarıdır; aynı runId ile tekrar POST atarsan tamamlanmış tool çağrıları yeniden çalışmaz.

curl — çalıştır
curl -s -X POST http://localhost:3001/api/agents/starwars/run \
  -H 'content-type: application/json' \
  -d '{"runId":"demo-1","prompt":"Who is Luke Skywalker?"}'

Akış istiyorsan aynı gövdeyle /agents/:name/stream uçuna POST at — yanıt bir SSE akışıdır (text-delta, tool-call, tool-result, interrupt, done event'leri). Bir tool insan onayı için askıya alındıysa (interrupt), aynı runId ile /agents/:name/resume uçuna { runId, approvals } gönderilerek devam ettirilir — girdi (prompt/messages) journal'dan okunur, tekrar vermene gerek yok.

Kullanım/maliyet raporu için GET /usage:

curl — kullanım
curl -s http://localhost:3001/api/usage
# {"org":null,"usage":{...},"limit":null,"exceeded":false}

Üretilen OpenAPI 3.1 şeması GET /openapi.json üzerinden servis edilir — kayıtlı her agent ve workflow için run/resume/stream yolları otomatik eklenir; bunu Swagger/Redoc gibi araçlara doğrudan verebilirsin.

Node sunucuları ve Next.js#

createRestApi bir fetch işleyicisi döndürür. Node'da onu @gnldev/server/node'daki toNodeHandler ile bağlayın:

Express, Fastify, Koa, node:http, Nest
import { createServer } from 'node:http';
import { toNodeHandler } from '@gnldev/server/node';

express().use('/api', toNodeHandler(api));                                // Express
await fastify.register(middie); fastify.use('/api', toNodeHandler(api)); // Fastify + @fastify/middie
koa.use(c2k((req, res, _next) => toNodeHandler(api)(req, res)));        // Koa + koa-connect
createServer(toNodeHandler(api)).listen(3000);                          // node:http
nestApp.use('/api', toNodeHandler(api));                                // Nest (Express or Fastify)

Canlı sunucularda ölçülmüş ve Express 5, Fastify 5 ile Koa 3'ü ayağa kaldıran bir testle korunan üç kural: rota olarak değil ara katman olarak bağlayın (Fastify'da rota, JSON ayrıştırıcı gövdeyi okuduktan sonra çalışır ve köprü 500 body_consumed_upstream döner); istek gövdesini ayrıştıran her şeyden önce bağlayın; ve Koa'da üçüncü parametreyi yazın — koa-connect fn.length'e bakar, iki parametreyle Koa önce kendi 404'ünü döner.

Next.js köprü istemez: bir route handler Request alır, Response döner. Tek bir catch-all rota, klasörünün altındaki her isteği klasör öneki kesilmiş olarak API'ye verir — next.config'e bir şey eklemek gerekmez:

app/api/[...path]/route.ts
// app/api/[...path]/route.ts
import { createRestApi } from '@gnldev/server';
import { PostgresStorage } from '@gnldev/durable/postgres';

const api = createRestApi({ storage: new PostgresStorage({ connectionString: process.env.DATABASE_URL }), agents: {} });

function handle(req: Request): Response | Promise<Response> {
  const url = new URL(req.url);
  url.pathname = url.pathname.replace(/^\/api(?=\/|$)/, '') || '/';
  return api.fetch(new Request(url, req));
}
export { handle as GET, handle as POST, handle as PUT, handle as PATCH, handle as DELETE, handle as OPTIONS };

Next.js 16.4'te next build ve next start ile ölçüldü: ajan listesi, bir çalıştırma ve server-sent events olarak okunan bir /stream, bellekte ve Postgres'te. Bu Node çalışma zamanıdır (route handler'ın varsayılanı); Edge çalışma zamanı denenmedi, depolar Node ister.

Kesilen bir model akışı cevap değildir#

Bir çalıştırmanın bitişi, model akışının söylediğinden okunur, varsayılmaz. finish parçası olmadan duran ve bir iptal, bir drain ya da bir zaman aşımının durdurmadığı bir akış, çalıştırmayı run_unfinished koduyla failed bitirir — completed değil — ve POST /agents/:name/run 502 cevaplar. /agents/:name/stream'de 200 zaten gönderilmiştir, bu yüzden kodu error olayı taşır. Bir executor'ın close()'da durdurduğu çalıştırma ise released biter: başka bir süreç onu sürdürür, başarısız olmamıştır.

400 değil 502'dir, çünkü istek sağlamdı: kesinti çağıranın yukarısında oldu ve istemcinin yeniden deneme mantığı 400'ü "asla yeniden deneme" diye okur, ki bu tam tersidir. Aynı runId'yi yeniden sürün. Kesilen adım için tamamlanmış hiçbir şey kaydedilmez, dolayısıyla model yeniden çağrılır ve tamamlanan her adım journal'dan yeniden oynatılır; bir tool çağrısından sonra kesilen adım, journal'lanmış çağrısını yeniden oynatır, yani tool bir kez çalışır. Yeni bir runId her şeyi yeniden çalıştırırdı, etkisi olmuş bir tool çağrısı dahil. Kesintiden önce akan metin kaybolmaz: çalıştırmanın olay günlüğündedir.

Kesinti sayılanlar: hiç finish parçası olmaması ya da akışın kendisinin bozulması; interrupted bir finishReason (@ai-sdk/openai 4.0.100 ve @ai-sdk/anthropic 4.0.86'dan itibaren bildirilir); v2 olmayan bir modelde sağlayıcı nedeni ve kullanım bilgisi olmayan bir other bitişi; ya da bir adaptörün akışın bitiş nedeni olmadan bittiğini bildirmesi (@ai-sdk/openai-compatible). Bu, motorun okuduğu sinyallerin listesidir, her kesintinin yakalanacağı sözü değil: adaptör varsayılan finish'ini gönderdiğinde bir v2 modelin kesintisi ve interrupted'dan eski bir adaptörde bir proxy kullanım bilgisini zaten gönderdikten sonraki kesinti bir bitiş gibi okunur ve 200 cevaplanır. wrapLanguageModel arkasındaki bir v2 model kesilmediği hâlde kesilmiş okunabilir; modeli sarmadan kullanın ya da kullanım bilgisi bildirmesini sağlayın.

Alt sınırlar da onunla birlikte yükseldi: ai 7.0.149, @ai-sdk/openai 4.0.100, @ai-sdk/anthropic 4.0.86 ve @ai-sdk/provider 4.0.32, kesilen akışı interrupted olarak bildiren ilk sürümlerdir. Her akışı finish_reason olmadan bitiren OpenAI uyumlu bir uç nokta artık her denemede run_unfinished cevaplar, eskiden 200 cevaplardı — yeniden deneme aynı gövdeyi alır, bu yüzden sunucuyu ya da önündeki gateway'i düzeltin. Tekrarlandığında nelere bakılacağını anlatan tam kayıt docs/errors/run_unfinished.md.

API referansı#

fncreateRestApi

createGnl config'inden Hono router üretir: /agents/:name/run|resume|stream, /agents, /workflows/:name/run, /workflows, /runs, /runs/:id, /usage, /openapi.json.

typeRestApiOptions

createRestApi ikinci argümanı: title (OpenAPI başlığı), auth (opsiyonel AuthProvider/ReadWriteAuth), org (opt-in çok organizasyonluluk), budgets (bütçe/kota fallback limitleri).

fnbuildOpenApi

Agent ve workflow adı listesinden OpenAPI 3.1 şeması üretir; createRestApi bunu /openapi.json ucunda otomatik çağırır, doğrudan da import edilebilir.

fnpipeAgentStream

streamDurable sonucunu (AI SDK StreamTextResult) SSE'ye pompalar: text-delta/tool-call/tool-result/error/interrupt/done event'leri.

fninterruptsFromSteps

Tamamlanmış step listesinden askıya alınmış tool çağrılarını (Interrupt[]) çıkarır — runDurable'daki suspend mantığıyla aynı; pipeAgentStream bunu stream sonunda interrupt event'i için kullanır.

fntoNodeHandler

toNodeHandler(handler) — node:http ve onun üstüne kurulu her çatı (Express, @fastify/middie ile Fastify, koa-connect ile Koa, Nest) için (req, res). Gövdeyi önce başka bir şey okuduysa 500 body_consumed_upstream döner.

fnstreamDurable

@gnldev/durable'dan gelir (createRestApi.gnl.stream() içinde dolaylı kullanılır): durable+streaming çalıştırma — aynı exactly-once/suspend garantileri, sonuç fullStream olarak akar.

classRunUnfinishedError

run_unfinished'ın arkasındaki hata: model akışı finish parçası olmadan bitti ve bu tarafta hiçbir şey onu durdurmadı. 502 cevaplanır.

İpucu
Auth vermezsen tüm uçlar AÇIKTIR (mevcut davranış korunur). Prodüksiyonda en azından roleAuth (@gnldev/auth) ile bir auth geçmen önerilir — GET uçları read, POST uçları write yetkisi ister.
Idempotency
Her çalıştırma isteğinde runId zorunludur — bu exactly-once garantisinin anahtarıdır. Aynı runId ile tekrar istek atmak yeni bir iş DEĞİL, var olanın devamı/tekrarı sayılır (tamamlanmış tool'lar yeniden çalışmaz; askıdaki iş varsa bütçe kapısı da atlanır).