LLM'e rastgele kod çalıştırma gücü verme konusunda erkenden bir risk aldık. Bu yazı, neden bu riski aldığımız ve insanların bir temsilciyle (agent) sohbeti başlatıp bitirmesiyle aynı hızda açılıp kapanan binlerce sanal alanı (sandbox) aynı anda çalıştırmanın ne anlama geldiği hakkındadır.
Bir kullanıcının Adapt temsilcisiyle yaptığı her sohbet, kendi bilgisayarı tarafından desteklenir. Sadece paylaşımlı bir sunucuda kilitli bir konteyner değil, modelin istediğini yapabileceği yalıtılmış bir sanal makine (VM): yazılım yükleyebilir, program yazıp çalıştırabilir, internette gezinebilir, API'lerle konuşabilir. Biz bunlara sandbox diyoruz ve bunlar Adapt'in üzerine inşa edildiği temel ilkelerden biridir.
Tam Kontrol
LLM'ler kodlama konusunda dahidir ve benim işim büyük ölçüde onlar için çalışabilecekleri mükemmel geliştirici ortamını inşa etmek oldu.
Bir yapay zekayı dış dünyaya bağlamanın alışılagelmiş yolu, entegrasyonları elle oluşturmaktır; GitHub için özel bir bağlayıcı, HubSpot için bir başkası, Stripe için bir diğeri veya her hizmetin bir MCP sunucusu yayınlamasını beklemek. Bu yöntem ölçeklenemez ve ben her gün entegrasyon kodu yazmaktan pek hoşlanmıyorum.
Bu yüzden bu işi kendimiz yapmak yerine, modelin yapmasına izin veriyoruz. API sunan herhangi bir hizmete Adapt üzerinden erişilebilir, çünkü LLM'e o API ile konuşacak betiği veya programı yazması için gereken her şeyi sağlıyoruz. Adapt'e "yatay zeka" dediğimizde kastettiğimiz şeyin büyük bir kısmı budur: sabit bir araç listesine bağlı değildir, ihtiyaç duyduğu aracı anında oluşturabilir.
Bunun temeli, LLM'e sandbox üzerinde tam erişim sağlamaktır. Modele dosya sistemine sınırlı erişimi olan statik bir dil ve CLI araçları seti vermek yerine, ona her şeye tam erişim veriyoruz. Root yetkisiyle çalışıyor. Sandbox'larımız Node ve Python gibi yaygın çalışma zamanlarıyla gelse de, bir hizmetin API'si için en iyi SDK Go ile yazılmışsa ne olacak? Model sadece gidip onu yükleyebilir ve çalıştırabilir.

LLM'in bir Go programı yazması mı gerekiyor? Buyursun, Go'yu yüklesin ve çalıştırsın.
Peki, modelin istediği her şeyi yüklemesine ve hiçbir insanın doğrulamadığı kodları çalıştırmasına izin veriyorsak, bunu nasıl güvenli hale getiriyoruz? Neyse ki, güvenilmeyen kodları çalıştırması gereken ilk insanlar biz değiliz. Bunun için tam olarak iki çok popüler güvenli çalışma zamanı var: gVisor ve Firecracker. Şimdiye kadarki yolculuğumuz bizi her ikisiyle de çok yakından tanıştırdı.
gVisor'dan Firecracker'a
LLM'ler için güvenli sandbox'lara ilk girişimimiz "kolay" yaklaşımdı: GKE (Google Kubernetes Engine) üzerinde GKE Sandbox kullanarak her sandbox'ı gVisor ile çalıştırmak. Diğer tüm hizmetlerimizi zaten GKE üzerinde çalıştırıyoruz, bu yüzden bizim için doğal adım buydu.
gVisor, bir konteyner ile ana makine çekirdeği arasında yer alır. Bir programın, güvenilmeyen kodların kurcalamasını hiç istemeyeceğiniz gerçek Linux çekirdeğine doğrudan sistem çağrıları yapmasına izin vermek yerine, gVisor bu çağrıları kendi kullanıcı alanı çekirdeğinde yakalar ve kendisi hizmet eder. Normal bir konteynerin sunduğu kolaylıkların çoğunu, çok daha küçük bir saldırı yüzeyiyle elde edersiniz. GKE Sandbox ise tüm bunları paketler. Pod'ları (konteynerleri) dağıtırsınız ve bunlar, bizim neredeyse hiç altyapı yapılandırması yapmamıza gerek kalmadan şeffaf bir şekilde gVisor altında çalışır.
Ve bu başlangıçta gerçekten çok iyi çalıştı. "Temel" sandbox'ı bir Docker imajı olarak tanımladık ve GKE'nin bunu herhangi bir zamanda ihtiyaç duyduğumuz sandbox sayısına göre ölçeklendirmesine izin verdik. Sandbox'ların birlikte gönderildiği yazılımdaki güncellemeler, basit Dockerfile güncellemeleri ve bir manifest dosyasındaki sürüm artışından ibaretti.

GKE Sandbox altında çalışan yüzlerce sandbox Pod'u.
Ancak gVisor'ı kolaylaştıran aynı soyutlama, sürekli mücadele ettiğimiz şey oldu. gVisor, Linux sistem çağrısı yüzeyini kullanıcı alanında yeniden uyguladığı için, her şey gerçek bir çekirdekteki gibi davranmıyor ve modelimizin hayal ettiği iş yükleri, iş yüklerinin olabileceği kadar tahmin edilemez. Güvenlik sağlayan bu araya girme işlemi, sistem çağrısı ve G/Ç yoğunluklu işlerde size maliyet olarak geri dönüyor. Tüm yaşam döngüsü için GKE'ye güvenmek, kontrol etmek istediğimiz kısımların (önyükleme süresi, paketleme yoğunluğu, ağ oluşturma ve makineleri ne kadar agresif bir şekilde geri dönüştürdüğümüz) en az kontrole sahip olduğumuz kısımlar olduğu anlamına geliyordu. Yukarıdaki başıboş OutOfcpu Pod'u, başkasının zamanlayıcısını gitmek istediğinden daha fazla zorladığınızda görmeye başladığınız türden bir şeydir.
Bizi Firecracker'a iten şey buydu.
Firecracker mikroVM'leri, her biri kendi konuk çekirdeğine sahip, donanım sanallaştırmasıyla çalışan ancak saniyenin çok küçük bir kısmında, sadece birkaç megabaytlık ek yükle önyükleme yapacak şekilde küçültülmüş gerçek sanal makinelerdir. AWS'nin çok sayıda Lambda ve Fargate iş yükünü paylaşımlı donanım üzerinde paketlemek için inşa ettiği teknolojinin aynısıdır. Bize paylaşımlı bir çekirdekten daha güçlü bir yalıtım sınırı sağlar, anında hissettirecek kadar hızlı önyükleme yapar ve tek bir ana makineye birçoğunu sığdıracak kadar küçüktür.
Bunun karşılığındaki ödünleşim ise Firecracker'ın size bir VM vermesi ve başka pek bir şey sunmamasıdır. Zamanlama, ağ oluşturma ve yaşam döngüsü düzenlemesi yapan GKE tarzı bir katman yoktur. Bu yüzden biz de bir tane inşa ettik ve adını orc koyduk.
rootfs sadece bir imajdır
Konteynerlerden uzaklaşırken vazgeçmek istemediğimiz bir şey, bir sandbox'ı düz bir Dockerfile olarak tanımlamaktı. Konteynerler bunu önemsiz hale getiriyor; VM'ler ise geleneksel olarak bunu yapmaz, çünkü bir mikroVM bir OCI imajını değil, bir kök dosya sistemini önyükler.
Bu yüzden orc, ikisi arasında köprü kurar. Bir VM oluşturması istendiğinde, sıradan bir Docker/OCI imajını alır ve anında VM'in kök dosya sistemini oluşturur, sonucu önbelleğe alır, böylece aynı imajın sonraki önyüklemeleri hızlı olur. Temel sandbox'ımız hala sadece bir Dockerfile'dır ve orc onu talep anında önyüklenebilir bir rootfs'e dönüştürür.
Bu, iş akışımızı GKE günleriyle aynı tutar: bir Dockerfile'ı düzenle, yeni bir sandbox gönder; bu sırada altta gerçek VM'lerde çalışmaya devam et. Ve bu, henüz yeni adım atmaya başladığımız bir kapıyı açar. Herhangi bir OCI imajı bir mikroVM olabildiği için, sandbox'ları varsayılan imaj dışındaki imajlardan önyükleyebiliriz. İçinde halihazırda Postgres ve pgvector yüklü bir VM mi istiyorsunuz? orc'u o imaja yönlendirin ve onu kendi yalıtılmış makineniz olarak alın. Sandbox, tek bir sabit ortam olmaktan çıkar ve "işin ihtiyaç duyduğu herhangi bir imaj, kendi VM'i olarak önyüklenmiş" haline gelir.
Ölçekte Çalıştırma
İşte bunu gerçekten zor bir problem haline getiren şey: her sohbetin kendi sandbox'ı vardır. Sohbet başına bir makine. Herhangi bir anda binlerce sandbox'ımız canlıdır ve bu sayı asla sabit kalmaz. Biri ne zaman bir sohbet açsa, bir sandbox ortaya çıkmalıdır; bir sohbet ne zaman sessizleşse, ödemesini yapmamamız için bir tanesi yok olmalıdır. Sürekli olarak sandbox'ları açıp kapatıyoruz.
İki sayı her şeye hakimdir: bir sandbox'ı ne kadar hızlı hazır hale getirebileceğimiz ve bir ana makineye kaç tane sığdırabileceğimiz.
Başlatma gecikmesi. Bir Firecracker mikroVM birkaç yüz milisaniyede önyüklenir. Bu, sıcak bir havuz tutmamıza gerek kalmayacak kadar hızlıdır ki bu da geçişin daha sessiz kazanımlarından biridir. GKE altında, başlatma süresini gizlemek için yedek kapasite tutmak zorunda kalırdık. orc ile yeni bir sandbox, siz fark etmeden hazır olur, bu yüzden bir sohbet başladığında talep üzerine bir tane oluşturur ve sohbet bittiğinde onu kaldırırız. Artık başında beklememiz veya ödememiz gereken boş bir havuz yok.
Yoğunluk. Her mikroVM çok küçük olduğu için, bir fiziksel ana makineye birçoğunu sığdırabiliriz. Her sandbox'ın CPU ve belleğini, aşırı kaynak ayırmak yerine gerçekten ihtiyaç duyduğu şeye göre boyutlandırıyoruz; binlercesini ekonomik bir şekilde çalıştırmamızı sağlayan şey budur.
orc'un kendisi kasıtlı olarak küçüktür. Basit bir API ile konuşan bir kontrol düzlemidir: belirli bir imajdan N vCPU ve M megabayt bellekli bir VM oluşturur, içine komutlar akıtır, içindeki dosyaları okur ve yazar, daha sonra bulabilmemiz için etiketler ve işimiz bittiğinde onu siler. Her konuk, PID 1 olarak küçük bir init süreci çalıştırır ve kendi yalıtılmış ağına sahip olur. Hepsi bu kadar. Sihir tek bir zekice hilede değil, bu ilkelerin bir filoyu çalıştıracak kadar sıkıcı ve hızlı olmasında yatıyor.
Tüm bu tesisatın karşılığı, başladığımız şeydir: herhangi bir şeyi yükleyebilen, bir program yazabilen, bir API'ye erişebilen ve size gerçek bir bilgisayar üzerinde bir yanıt verebilen bir model.





