İçeriğe geç / Skip to content / Zum Inhalt
Ahmet Balaman LogoAhmet Balaman

Space Bunny Alpha ile Yapay Zekâ Ajanı Kurmak

Ahmet Balaman

4 dk okuma

Vibe CodingSpace Bunny AlphaAI AgentOpenRouterYapay ZekaTypeScript
Space Bunny Alpha ile Yapay Zekâ Ajanı Kurmak

Bir modeli çağırmak ile ajan kurmak arasında tek bir fark var: döngü. Model bir kez cevap verir ve durur. Ajan, modelin cevabındaki talimatı okur, gerçekten o işi yapar, sonucu modele geri verir ve cevap gelene kadar tekrarlar.

Bu yazıda o döngüyü baştan sona kuruyoruz. Kurulum yazısı API bağlantısını, 1M bağlam yazısı bütçe yönetimini anlatmıştı. Buradaki odak, modelin bir programı nasıl çalıştırdığı.

Ajanın neden bir "harness" olduğu harness yazısında anlatılıyordu; burada somut kodunu yazıyoruz.

Model ne yapmaz, ajan ne yapar

Ajan döngüsünü anlamadan önce sınırı çizmek gerekiyor. Space Bunny Alpha bir araç çağırma isteği üretir, o isteği çalıştırmaz.

Ajanın sorumluluğu Modelin sorumluluğu
Dosyayı okumak Hangi dosyanın okunacağına karar vermek
Komutu çalıştırmak Hangi komutun çalıştırılacağını söylemek
Hata yakalamak Hatanın neden olduğunu açıklamak
Sonucu biçimlendirmek Sonucun ne anlama geldiğini yorumlamak
Döngüyü sürdürmek Ne zaman durulacağını önermek

Bu ayrımın pratik sonucu şu: modele "şu dosyayı aç" demek yetmez, o dosyayı gerçekten açan kodu siz yazarsınız. Ajanın tamamı bu köprüdür.

Ajan döngüsü: model araç çağrısı üretir, ajan kodu onu çalıştırır, sonucu geçmişe ekleyip yeni tura başlar; araç çağrısı yoksa döngü cevapla kapanır

Araç şemasını yazmak

Modele hangi araçları kullanabileceğini tools dizisiyle söylüyorsunuz. Şema ne kadar kesin olursa model o kadar isabetli çağırır. İki araçla başlayalım:

const tools = [
  {
    type: "function" as const,
    function: {
      name: "read_file",
      description: "Bir dosyanın içeriğini satır numaralarıyla okur.",
      parameters: {
        type: "object",
        properties: {
          path: {
            type: "string",
            description: "Proje köküne göreli dosya yolu, örneğin lib/agent.ts",
          },
        },
        required: ["path"],
        additionalProperties: false,
      },
    },
  },
  {
    type: "function" as const,
    function: {
      name: "list_files",
      description: "Bir dizindeki dosyaları listeler.",
      parameters: {
        type: "object",
        properties: {
          dir: {
            type: "string",
            description: "Listelenecek dizin, örneğin lib",
          },
        },
        required: ["dir"],
        additionalProperties: false,
      },
    },
  },
];

description alanına ne kadar ayrıntı yazarsanız o kadar iyi. Model araç isimlerine bakarak değil, açıklamalarına bakarak karar verir. "Bir şey arar" yerine "Proje köküne göreli dosya yolu" yazmak, sonradan hatırlatılacak bir ayrıntı değil.

additionalProperties: false ve required koymayı atlmayın. Bu iki satır olmadan model istediğiniz alanı atlayabilir ya da sizin istemediğiniz bir alan ekleyebilir.

Döngüyü kurmak

Ajanın kalbi tek bir while döngüsü. Mantık şu: modele mesaj geçmişini gönder, cevabında araç çağrısı varsa çalıştır ve sonucu geçmişe ekle, yoksa döngüden çık.

import OpenAI from "openai";
import { readFileSync, readdirSync } from "node:fs";
import { resolve, sep } from "node:path";

const client = new OpenAI({
  apiKey: process.env.OPENROUTER_API_KEY,
  baseURL: "https://openrouter.ai/api/v1",
  timeout: 180_000,
  maxRetries: 2,
});

type Msg = OpenAI.Chat.ChatCompletionMessageParam;

const PROJ_ROOT = process.cwd();

/** Modelin verdiği yolun proje kökü dışına çıkmasını engeller. */
function safePath(relative: string): string {
  const full = resolve(PROJ_ROOT, relative);
  if (full !== PROJ_ROOT && !full.startsWith(PROJ_ROOT + sep)) {
    throw new Error(`Proje kökü dışına çıkılamaz: ${relative}`);
  }
  return full;
}

function runTool(name: string, rawArgs: string): string {
  const args = JSON.parse(rawArgs || "{}") as { path?: string; dir?: string };

  try {
    if (name === "read_file" && args.path) {
      return readFileSync(safePath(args.path), "utf8").slice(0, 40_000);
    }
    if (name === "list_files" && args.dir) {
      return readdirSync(safePath(args.dir)).join("\n");
    }
    return `Bilinmeyen araç: ${name}`;
  } catch (error) {
    // Hatayı modele metin olarak geri ver; döngüyü bozma.
    return `Hata: ${(error as Error).message}`;
  }
}

const messages: Msg[] = [
  {
    role: "system",
    content:
      "Bir yazılım mühendisisin. Önce dosyaları oku, sonra açıkla. " +
      "Değişiklik önermeden önce okuduğundan emin ol.",
  },
  {
    role: "user",
    content: "lib/ dizinindeki dosyaları listele ve agent.ts dosyasının ne yaptığını açıkla.",
  },
];

const MAX_TURNS = 8;

for (let turn = 0; turn < MAX_TURNS; turn += 1) {
  const completion = await client.chat.completions.create({
    model: "stealth/space-bunny-alpha",
    messages,
    tools,
    tool_choice: "auto",
    reasoning_effort: turn === 0 ? "high" : "low",
  });

  const message = completion.choices[0]?.message;
  if (!message) break;

  messages.push(message);

  const calls = message.tool_calls ?? [];
  if (calls.length === 0) {
    console.log(message.content);
    break;
  }

  for (const call of calls) {
    const result = runTool(call.function.name, call.function.arguments);
    messages.push({
      role: "tool",
      tool_call_id: call.id,
      content: result,
    });
  }
}

Dört detay bu döngüyü üretimde kullanılabilir kılıyor.

safePath ile yol sınırı. Model ürettiği yolu doğrudan resolve ederseniz ../../.env yazabilir. PROJ_ROOT altında kaldığını doğrulamak, ajan yazarken en sık atlanan güvenlik adımı. Güvenlik yazısındaki denetim mantığı burada dosya sistemine de uygulanıyor.

Hataları yutmayın, geri besleyin. runTool hata yakalayıp mesajı role: "tool" olarak geçmişe ekliyor. Model böylece "dosya yok" bilgisini alıp farklı bir yol deneyebiliyor. Hata yakalayıp yutarsanız model aynı hatayı tekrar tekrar yapıyor.

Tur sınırı koyun. MAX_TURNS olmadan model sonsuz döngüye girebilir. Sekiz tur, çoğu gerçek görev için fazlasıyla yeterli.

Akıl yürütmeyi tura göre ayarlayın. İlk turda model dosyaları keşfediyor, high yerine max gerekebilir. Sonraki turlarda sadece okuduğu şeyi yorumluyor, low yeterli. Karşılaştırma yazısında anlatıldığı gibi akıl yürütme varsayılan olarak max geliyor, ve bu tur başına gerçek bir gecikme demek.

Araç hatasını modelin anlayacağı biçimde döndürmek

runTool fonksiyonundaki catch bloğu kısa ama kritik. Ham bir hata mesajı (ENOENT: no such file or directory, open 'lib/agent.ts') modele güçlü bir sinyaldir; ama ne yapması gerektiğini söylemez. Modele ne olacağını söylemek işe yarar:

function runTool(name: string, rawArgs: string): string {
  const args = JSON.parse(rawArgs || "{}") as { path?: string; dir?: string };

  try {
    if (name === "read_file" && args.path) {
      return readFileSync(safePath(args.path), "utf8").slice(0, 40_000);
    }
    if (name === "list_files" && args.dir) {
      return readdirSync(safePath(args.dir)).join("\n");
    }
    return `Bilinmeyen araç: ${name}. Kullanılabilir araçlar: read_file, list_files.`;
  } catch (error) {
    const message = (error as Error).message;
    if (message.includes("ENOENT")) {
      const dir = args.path ? args.path.split("/").slice(0, -1).join("/") || "." : ".";
      return `Hata: ${args.path} bulunamadı. Önce list_files aracıyla ${dir} dizinini listeleyip gerçek dosya adını kullan.`;
    }
    return `Hata: ${message}. Farklı bir yol deneyebilirsin.`;
  }
}

Bu birkaç satır, ajanın kendini düzeltme oranını gözle görülür biçimde artırıyor. Model artık sadece "hata oldu" bilgisiyle değil, "şunu yap" bilgisiyle devam ediyor.

Çıktıyı JSON'a kilitlemek

Ajanın son turu serbest metin üretmek zorunda değil. Bir dosya düzenlemenin özeti ya da bir sınıflandırma kararı JSON olmalıysa, iki yolunuz var.

Yol bir: response_format ile şema. Araç döngüsü bittiğinde son bir istek atıp şemaya zorlarsınız. Kurulum yazısındaki JSON şeması burada da geçerli.

Yol iki: araç olarak JSON döndürmek. Döngü hiç bitmesin, model sürekli submit_result aracını çağırsın. Bu, ajanın "bitti" deme kararını modele bırakır ama biçimi şemayla güvenceye alır:

const tools = [
  readFileTool,
  listFilesTool,
  {
    type: "function" as const,
    function: {
      name: "submit_result",
      description:
        "Görev bittiğinde çağrılır. Sonucu JSON olarak teslim eder. " +
        "Tüm dosyalar incelendikten sonra çağrılmalı.",
      parameters: {
        type: "object",
        properties: {
          summary: { type: "string", description: "Yapılan işin özeti" },
          changedFiles: {
            type: "array",
            items: { type: "string" },
            description: "Değiştirilen dosya yolları",
          },
          confidence: {
            type: "integer",
            minimum: 0,
            maximum: 100,
            description: "Göreve ne kadar güvenildiği, 0-100",
          },
        },
        required: ["summary", "changedFiles", "confidence"],
        additionalProperties: false,
      },
    },
  },
];

İkinci yolun avantajı, modelin submit_result çağırmadan önce okuması gereken dosyaları okumasını zorlaması. Döngüyü submit_result görünce kapatırsınız:

for (let turn = 0; turn < MAX_TURNS; turn += 1) {
  // ... istek ...

  const submit = calls.find((c) => c.function.name === "submit_result");
  if (submit) {
    const result = JSON.parse(submit.function.arguments) as {
      summary: string;
      changedFiles: string[];
      confidence: number;
    };
    console.log(result);
    process.exit(0);
  }

  // ... diğer araçları çalıştır ...
}

console.error(`Ajan ${MAX_TURNS} turda sonuç üretmedi.`);
process.exit(1);

Bir çıktı sınırı koymayı unutmayın. Model submit_result çağırmadan turlar tükenirse program submit_result görülmediği için hata çıkar. Bu, üretimde sessizce yarım kalmış bir görevden iyidir.

Ne zaman başka bir model

Space Bunny Alpha her ajan için doğru seçim değil. Karşılaştırma yazısındaki yetenek tablosundan üç sonuç çıkıyor.

tool_choice yalnızca auto. "Şu aracı çağır" diyebilmeniz gerekmiyorsa sorun yok. Deterministik bir akışta ilk adımı zorlamak istiyorsanız ya istemi metin içinde güçlendirmeniz gerekir ya da bu model o akış için uygun değildir.

seed yok. Aynı istemle iki kez arama yaptığınızda aynı cevabı garanti edemezsiniz. Testlerde tekrarlanabilir çıktı şart olan bir ajan yazıyorsanız, seed destekleyen bir model seçin.

Bağlam maliyeti sıfır değilse fark eder. Uzun oturumlarda maliyet hesabı yapıyorsanız, karşılaştırma yazısındaki aylık tabloya bakın.

Kısacası: keşif, deneme ve maliyetin sıfır olması gereken her ajan için Space Bunny Alpha mantıklı. Deterministik akışlar ve tekrarlanabilirlik şart olan üretim kodları için başka modeller daha doğru tercih.

Kaynaklar

Yorumlar