Bir ajan ürünü yayımlayan herkes bu duyguyu bilir: çalışan bir demo hazırlamak hızlıdır, ama onu kullanıcının her gün güveneceği, kendi makinesinde bütün gün çökmeden çalışacak bir şeye dönüştürmek zordur. Zor olan da modeli bağlamak değildir. Modelin etrafındaki katmanın tamamıdır.
Bu katman birkaç farklı adla anılıyor; ben Ajan Çalıştırma Altyapısı diyeceğim. “Büyük model” ile “ürün özellikleri” arasında yer alır ve asıl çalışma ortamıdır: tek bir kullanıcı isteğini modelle art arda yapılan konuşma turlarına dönüştürür, aralara araç çağrıları yerleştirir, sonuçları geri besler, bağlam taşmadan önce sıkıştırır, ağ aksaklıklarında yeniden dener ve işlem çöktükten sonra bile konuşmayı kurtarır. Düşünmeyi model yapar; çalıştırma altyapısı bu düşüncenin güvenilir bir eylem dizisine dönüşmesini sağlar.
Orkas, kullanıcının kendi makinesinde çalışan bir masaüstü ajan uygulamasıdır ve çalıştırma altyapısı tamamen istemcide bulunur. Bu makale, o katmanın nasıl kurulduğunu anlatıyor: katmanlara nasıl ayrıldığı, çalıştırma döngüsünün nasıl işlediği, araçlarla modellerin nasıl soyutlandığı ve bellek ile oturumların nasıl yönetildiği. Kod ayrıntıları ayıklanıp genelleştirildi, ancak mühendislik yapısı gerçek.
Katmanlar
Bir ajan ürününü bileşenlerine ayırdığınızda, aşağıdan yukarıya kabaca şu katmanlar ortaya çıkar:
┌─────────────────────────────────────────┐
│ Ürün özellikleri (sohbet / beceriler / bağlayıcılar / eşitleme) │
├─────────────────────────────────────────┤
│ Ajan Çalıştırma Altyapısı (çalıştırma döngüsü / araçlar / oturum) │
├─────────────────────────────────────────┤
│ Sağlayıcı soyutlaması (birçok LLM sağlayıcısını birleştirme) │
├─────────────────────────────────────────┤
│ Temel altyapı (türler / hatalar / günlükleme / yapılandırma) │
└─────────────────────────────────────────┘Burada yapıya işlenmiş, önemli sonuçları olan bir tercih var: tüm model çıkarımı istemcide gerçekleşiyor. Masaüstü uygulaması ince bir istemci değil — çalıştırma altyapısını kendi içinde barındırıyor ve modeli doğrudan çağırıyor. Sunucu yalnızca hesapları, cihazlar arası eşitlemeyi ve faturalandırmayı yönetiyor; ajanı hiç çalıştırmıyor. Bu karar, sonraki hemen her şeyi şekillendirdi: oturumlar yerel diske kaydediliyor, araçlar doğrudan kullanıcının çalışma dizininde işlem yapıyor ve hassas veriler makineden hiç çıkmıyor.
Çalıştırma altyapısı birkaç parçaya ayrılıyor: çalıştırma döngüsü (yürütücü), oturum, araçlar, Sağlayıcı katmanı ve bellek. Bunları tek tek ele alalım.
Çalıştırma döngüsü: akış üreten bir üreteç
Çalıştırma altyapısının kalbi yürütücüdür. Yaptığı iş tek cümleyle şu: model “İşim bitti” diyene kadar onunla tekrar tekrar konuşmak.
Bir asenkron üreteç olarak uygulanıyor ve bu tercih önemli. Bir ajan çalıştırması, “isteği gönder, sonucu bekle”den çok daha fazlasıdır. Arada pek çok şey olur — model token üretir, araç çağırmak ister, araç tamamlanır, bağlam sıkıştırmayı tetikleyecek kadar uzar, ağ bağlantısı başarısız olur ve yeniden denenir. Geri çağırma işlevleri veya düz Promise'lerle bu ara durumları çağırana düzgün biçimde sunmak zordur. Üreteç kullanıldığında bunların hepsi yield ile aktarılan bir olay akışına dönüşür:
type AgentRunEvent =
| { type: "text_delta"; text: string } // model emitting tokens
| { type: "tool_start"; name: string; input: unknown } // a tool starts executing
| { type: "tool_end"; name: string; result: string } // a tool finished
| { type: "compaction"; tokensBefore: number; tokensAfter: number } // context compacted
| { type: "retry"; attempt: number; reason: string } // error, retrying
| { type: "done"; result: AgentRunResult } // terminalKullanıcı arayüzü bu olay akışına abone olur ve modelin çıktısını ve araçların çalışmasını gerçek zamanlı gösterir. Akış kullanmayan giriş noktası da içeride yalnızca “akışı sonuna kadar tüket, son done olayını al” işlemini yapar — iki giriş noktası da aynı uygulamayı paylaşır; dolayısıyla zamanla uyumunu kaybedecek ikinci bir kod yolu yoktur.
Bir turun içinde neler olur?
Adım adım açıldığında bir tur kabaca şöyle görünür:
- Kullanıcı mesajını (varsa görselleriyle birlikte) oturum geçmişine ekle.
- O anda kullanılabilen araçları, beceri dizinini ve benzerlerini ekleyerek sistem istemini oluştur.
- Model dizesini ayrıştır ve belirli bir Sağlayıcı ile model kimliğine çözümle.
- Tüm araçları modelin anlayabileceği tanımlara dönüştür ve geçmişle birlikte gönder.
- Modelin yanıt akışını tüket,
yieldile metni token token aktarırken modelin yaptığı araç çağrılarını topla. - Akış sona erdiğinde modelin durma nedenine bak:
- Eğer neden
tool_useise model bir araç çağırmak istiyordur — araçları çalıştır, ardından 5. adıma dön ve modele tekrar sor. - Aksi halde tur bitmiştir — sonucu oluştur,
yield doneişlemini yap ve dön.
Burada korumanız gereken bir değişmez kural var: modelin yaptığı her araç çağrısının hemen ardından geçmişte ona karşılık gelen bir araç sonucu bulunmalıdır. Model API'si bu eşleşmeyi kesin biçimde zorunlu tutar — bozarsanız sonraki istek ya hata verir ya da takılı kalır. Oturumun kendi kendini onarmasını ele alırken buna döneceğiz.
Araç çağrısı nasıl yönlendirilip geri döndürülür?
Model araçları kendisi çalıştırmaz; yalnızca “Şunu çağırmak istiyorum: read_file , şu argümanlarla” der. Yürütücü bu niyeti aldığında:
for (const call of toolUseBlocks) {
yield { type: "tool_start", name: call.name, input: call.input };
const tool = this.tools.get(call.name);
const ctx = { workingDir, signal, state: { sandboxEnv } };
const result = await tool.execute(call.input, ctx);
// append the result to the session as a tool-result message
session.addToolResult(call.id, result);
yield { type: "tool_end", name: call.name, result: result.content };
}Araçlar sırayla çalışır, sonuçlar modelin belirttiği sırayla geçmişe yazılır ve ardından bu sonuçlarla modele yeniden sorulur. Sonuçları gören model başka bir araç çağırabilir veya doğrudan son yanıtını verebilir. Ajanın çok adımlı görevleri tamamlamasını sağlayan şey tam olarak bu “sor → çağır → yanıtla → yeniden sor” döngüsüdür.
Bir ayrıntıyı ayrıca belirtmek gerekiyor: bazı araçlar görsel döndürür — ekran görüntüleri, üretilmiş resimler gibi. Ancak birçok model, araç sonucu kanalında görsel kabul etmez. Orkas bunu, görseli araç sonucundan sonra yerleştirilen ayrı bir kullanıcı mesajına ayırarak çözer — model önce “araç bu metni döndürdü” bilgisini okur, hemen sonraki turda da ilgili görseli görür. Sağlayıcılar arasındaki yetenek farklarını aşan küçük bir uzlaşma.
Bağlam taşmak üzereyken ne yapılmalı?
Uzun görevlerin en sık çarptığı duvar, bağlam penceresidir. Orkas pencerenin dolmasını beklemez — bir %60 eşiği belirler: her araç turundan sonra mevcut token'ların pencerenin ne kadarını kapladığını tahmin eder ve bu oran %60'ı geçtiğinde sıkıştırmayı önceden tetikler.
Sıkıştırma sırasında modelden önceki konuşmayı özetlemesi istenir; ardından yalnızca en yeni bölüm korunarak eski mesajlar bu özetle değiştirilir. Kulağa basit geliyor, ancak bir tuzak var: değişimden sonra korunan bölüm “eşleşmeyen bir araç sonucu” ile başlamamalıdır — “karşılık gelen çağrısı olmayan bir sonuç” bulunamaz; yoksa eşleşme kuralını yine bozmuş olursunuz. Bu yüzden sıkıştırma mantığı kesimin düzgün bir sınırda yapılmasını sağlar.
Burada açmaya değer daha ilginç bir tercih var: neden daha ayrıntılı bir yöntem yerine kaba bir “%60'ta tüm bloğu özetle” yaklaşımı? Örneğin her mesajı puanlayıp önemine göre budamak, araç çıktılarından yapılandırılmış bilgi çıkarmak veya katmanlı bir bellek ağacı tutmak mümkün. Bu yaklaşımlar makalelerde harika görünüyor, ancak üç nedenle bilinçli olarak o yola gitmedik.
Birincisi, önbellekleme. Modelin istem önbelleği öneke göre eşleşir: geçmişin başlangıç bölümü değişmediği sürece o bölüm önbellekten karşılanır; bu da hem maliyeti hem gecikmeyi azaltır. İnce ayrıntılı sıkıştırma geçmişin ortasını sürekli yeniden yazar; bu, önbellekteki önekin tekrar tekrar parçalanması demektir — her düzenleme büyük bir bölümün yeniden işlenmesini gerektirir. “Dokunma, eşikte bir kez sıkıştır” stratejisi, turların büyük çoğunluğunda öneki sabit tutar; yalnızca o tek sıkıştırma önbelleği geçersiz kılar. Önbelleğe çok daha uygundur.
İkincisi, karmaşıklık. Sürekli vurguladığımız “her araç çağrısı eşleşmeli” kuralı var ya — geçmişi ne kadar ayrıntılı budarsanız bir köşede bu kuralı bozma ihtimaliniz o kadar artar. Kaba bir özet yalnızca tek bir düzgün kesim noktasını korumak zorundadır; hata yapılabilecek yerlerin sayısı kat kat azalır. Bir uç durum sınıfının azalması, üretimde yaşanabilecek bir sorun sınıfının da azalmasıdır.
Üçüncüsü, daha iyi modellerin getirilerinden yararlanmak. Son birkaç yılda bağlam pencereleri düzenli olarak büyüdü ve modeller uzun bağlamı giderek daha iyi işliyor. Bugün karmaşık bir sıkıştırma algoritmasına emek harcamak, aslında küçülen bir sorunla mücadele etmek demek — siz ince ayarları bitirdiğinizde yeni neslin pencereyi ikiye katlaması ve karmaşıklığınızın tamamen yüke dönüşmesi olası. Buna karşılık özetlemeyi modelin kendisine bırakmak, model geliştikçe otomatik olarak iyileşir: önemli olanı ne kadar iyi seçerse özetin kalitesi o kadar yükselir ve biz tek satır değiştirmeyiz. Modelin sizin yerinize taşıyabileceği karmaşıklığı kendiniz taşımamalısınız.
Token tahmininde kolayca gözden kaçan bir konu var: Çince. Çinceyi İngilizceye dayalı sezgilerle, yani birkaç karakter başına yaklaşık bir token olarak tahmin ederseniz sayıyı ciddi biçimde düşük hesaplarsınız. Orkas tahmininde CJK karakterlerini ayrı ağırlıklandırır; aksi halde tamamen Çince bir konuşmanın eşik hesabı yanlış olur ve sıkıştırma gerektiği anda devreye girmez.
Hatalar ve yeniden denemeler
Kullanıcının makinesinde çalışan ve harici bir model API'sine bağlı olan bir sistemde hatalar istisna değil, olağan durumdur. Yürütücü bunları birkaç sınıfa ayırır ve her birine farklı davranır:
- Yeniden denenebilir: hız sınırları, zaman aşımları, kopan bağlantılar, 5xx. Rastgele sapma eklenen üstel bekleme uygulanır ve süre 30 saniyeyle sınırlandırılır; hata hız sınırından kaynaklanıyorsa ve sunucu
retry-aftergönderdiyse buna uyulur. - Yeniden denenemez: kimlik doğrulama hataları gibi durumlar — kaç kez denerseniz deneyin fayda etmez, bu yüzden hemen hata döndürülür.
- Özel: bağlam taşması. Önce sıkıştırma denenir, ardından bir kez yeniden denenir ve yalnızca bu da başarısız olursa hata döndürülür.
Bir sınıf daha var: “aracın kendisi başarısız oldu.” Bu, tüm turu başarısız kılmaz — araç hatası da model için bir bilgidir; model “bu komut hata verdi” bilgisini görünce pekâlâ başka bir yaklaşım deneyebilir. Çalıştırma altyapısı bu geçici araç hatalarını gerçek arızalardan ayırır: ne akışı keser ne de hataları kaybeder — bunlar sonradan istatistiklerde görünür. (Bu veriler daha sonra, bir sonraki makalenin konusu olan kendi kendini geliştirme mekanizmasını besler.)
Harici iptal sinyali (AbortSignal) her kritik noktada kontrol edilir. Kullanıcı “durdur”a bastığında mevcut tur anında durur — yeni bir yeniden deneme başlatılmaz.
Araç soyutlaması: genişletilebilecek kadar basit
Araç arayüzü bilinçli olarak yalın tutulmuştur:
interface AgentTool {
readonly name: string;
readonly description: string; // shown to the model
readonly inputSchema: Record<string, unknown>; // JSON Schema to constrain inputs
execute(input: Record<string, unknown>, ctx: ToolContext): Promise<ToolResult>;
}Araç yalnızca “bir ad + model için bir açıklama + bir girdi şeması + bir çalıştırma işlevi”dir. Yerleşik araçların tamamı — dosya okuma, dosya yazma, kabuk komutu çalıştırma, web araması ve içerik getirme — bu arayüzü uygular. Masaüstü katmanı bunun üzerine yerel kullanıma yönelik bir dizi araç ekler: bilgi tabanı araması, görsel üretimi, harici bağlayıcı çağrıları. Ancak arayüz aynıdır.
Yalın bir arayüzün getirisi, aracın nereden geldiğinin yürütücü açısından önemsiz olmasıdır: yerleşik, kullanıcı tanımlı veya bir beceriden yüklenmiş — hepsi aynı türden şeylerdir ve tek bir Map<string, AgentTool> içine kaydedilip her turda modelin okuyabileceği tanımlara dönüştürülür.
Kabuk komutları gibi yan etkili araçlar yalıtılmış bir çalıştırıcıdan geçer: zaman aşımları, çıktı uzunluğu sınırları, bir komut engelleme listesi ve işlemin genel ortamını değiştirmek yerine ayrı olarak iletilen ortam değişkenleri kullanılır. Genel ortamı değiştirmek, bu değişikliklerin çok sayıda alt işleme yayılmasına yol açar ve Electron gibi çok işlemli bir mimaride başlatmayı kolayca bozabilir.
Sağlayıcı katmanı: birçok modeli tek arayüzde birleştirmek
Kullanıcıların model tercihleri çok çeşitlidir ve bir ürün tek bir sağlayıcıya sıkı sıkıya bağlanamaz. Orkas, çalıştırma altyapısının altına farklı sağlayıcıların modellerini tek bir arayüz altında birleştiren bir Sağlayıcı soyutlaması yerleştirir:
interface LLMProvider {
readonly id: string;
complete(params: CompletionParams): Promise<CompletionResult>;
stream(params: CompletionParams): AsyncIterable<StreamEvent>;
validateAuth(): Promise<boolean>;
}Üstteki yürütücü yalnızca bu arayüzle konuşur; arkasında hangi sağlayıcının olduğunu bilmez. Bir kayıt mekanizması, model dizesine göre yönlendirmeyi yapar: açık bir provider/model biçimi doğrudan ayrıştırılır; tek başına verilen model adı ise önekine göre sağlayıcıya atanır. Kimlik doğrulama da (API anahtarı veya OAuth token'ı) burada yönetilir ve süresi dolan OAuth token'ı otomatik yenilenir.
Birçok modeli birleştirirken asıl baş ağrısı metin tamamlama değildir — sağlayıcıların davranış anlamlarının farklılaştığı köşe durumlardır. Başımıza gelen iki örnek var.
Biri, düşünme bloklarını sağlayıcılar arasında korumak. Akıl yürütme modelleri bir “düşünme” içeriği bölümü üretir; bazı sağlayıcılar bunu şifreler ve aynen geri göndermenizi ister, diğerleri farklı alanlarla temsil eder. Kullanıcı konuşmanın ortasında A sağlayıcısından B sağlayıcısına geçerse geçmişteki düşünme bölümünün imzası artık eşleşmez. Çözüm, geçmişteki her mesajı “bunu hangi model üretti” bilgisiyle damgalamaktır; böylece dönüşüm katmanı onu aynen koruyup korumayacağına karar verebilir: aynı modelse koru, farklı modelse kurallara göre daha sade bir gösterime indir.
Diğeri ise istem önbelleği. Aynı oturumun turları boyunca önek büyük ölçüde tekrarlanır ve bunu önbelleğe almak maliyet ve gecikmede anlamlı tasarruf sağlar. Uygulama, destekleyen sağlayıcılara oturum kimliğini önbellek anahtarı olarak iletir; bu sırada her sağlayıcının anahtar uzunluğu sınırını da gözetir — örneğin çok uzunsa kısaltır veya özet değerini alır.
Bunların hepsi zahmetli rutin işlerdir — ama üstteki yürütücünün “yalnızca tek tür model varmış” gibi davranmasını sağlayan şey tam olarak bu katmandır.
Bellek: her biri kendi işini yapan iki mekanizma
Orkas'ta “bellek” aslında tamamen farklı iki sorunu çözen, paralel çalışan iki mekanizmadır. Biri, “gerektiğinde gidip bakacağınız” büyük hacimli içerik için bilgi getirmeye dayalı bir bilgi tabanıdır. Diğeri, “her zaman akılda tutulması gereken” az sayıdaki temel bilgi için oturumlar arası bellektir. Birçok ürün bu ikisini birbirine karıştırır; ayrı tutmak her şeyi çok daha net hale getirir.
Bilgi tabanı: hibrit bilgi getirme
İlk mekanizma, hacimli ama yalnızca zaman zaman ilgili olan içerikleri hedefler — kullanıcının belgeleri, eski notları, alan bilgisi. Bu, vektörle bilgi getirme kullanan yerel bir bilgi tabanıdır ve iki arka uç seçeneği vardır: hafif, tamamen bellekte çalışan bir sürüm (testler ve geçici kullanım için) ve yerel veritabanında kalıcı olarak saklanan bir sürüm (üretim kullanımı için; tam metin dizini ve vektörlerle).
Veriler şu yolu izleyerek içeri alınır:
belgeler → satır sınırlarından parçalara ayırma (örtüşmeyle) → çift dizinleme
├─ tam metin dizini (anahtar sözcükler, gömme maliyeti yok)
└─ vektör dizini (bir gömme modeli yapılandırılmışsa)Bir anlam bütününü ortadan bölmemek için parçalar, aralarında biraz örtüşme bırakılarak satır sınırlarından kesilir. Bilgi getirme hibrittir: bir vektör taraması (anlamsal yakınlık) ve bir anahtar sözcük taraması (birebir eşleşmeler) yapılır; iki sonuç kümesi RRF (Karşılıklı Sıra Birleştirme) ile birleştirilir:
score = Σ 1 / (k + rank_i)Bir sonuç, taramalardan birinde ne kadar üst sıradaysa katkısı o kadar büyük olur; iki taramanın katkıları toplandığında hem anlamsal ilişki gözetilir hem de birebir metin eşleşmeleri kaybolmaz. Vektör ve anahtar sözcük ağırlıkları ayarlanabilir; varsayılan olarak anlama öncelik verilir. Birleştirmeden sonra sonuçlar “(belge, başlangıç satırı)” temelinde tekilleştirilir ve her konum için yalnızca en iyi sonuç tutulur. Ardından eşik altındakiler elenir ve ilk K sonuç döndürülür.
Neden yalnızca vektörlere güvenilmiyor? Çünkü vektörle bilgi getirme; özel adlarda, kod sembollerinde ve tam olarak belirtilmiş dizelerde sık sık başarısız olur — bunlar anlamsal açıdan özel olmayan, ama birebir metnin çok önemli olduğu sorgulardır. Yalnızca anahtar sözcük kullanımı ise “aynı anlam, farklı ifade” durumunu yakalayamaz. İkisini birlikte çalıştırmak, bilgi getirme kalitesiyle maliyet arasında son derece pratik bir dengedir.
Oturumlar arası bellek: kullanıcıyı akılda tutmak
Bilgi tabanı “elde tutulamayacak kadar çok içerik” sorununu çözer. Ama başka bir bilgi sınıfı daha vardır — hacmi küçüktür, yine de her zaman akılda kalmalıdır: bu kullanıcının kim olduğu, neyi tercih ettiği, geçen sefer nelerde anlaşıldığı. Bunlar bilgi getirme sürecinin “şans eseri hatırlamasına” bağlı olmamalı; her turda mevcut olmalıdır.
Orkas bunun için, içeriğe göre ikiye ayrılan ayrı bir oturumlar arası bellek katmanı oluşturur:
- Kullanıcı profili: hakkında kalıcı bilgiler; söz konusu olan kişi — rolü, tercihleri, iletişim tarzı, kullandığı teknoloji yığını.
- Olgu notları: hakkında kalıcı bilgiler; söz konusu olan iş — kararlar, kilometre taşları, proje kuralları.
İkisi de küçüktür ve her birinin birkaç bin karakterlik kesin bir üst sınırı vardır; bu, yalnızca uzun vadede gerçekten yararlı olanı tutmaya zorlar. Bilgi getirme sürecinden geçmezler; her turun başında doğrudan sistem istemine sabitlenirler. Böylece ajan, gidip bakması gerektiğini hatırlamak zorunda kalmadan bu bilgileri zaten “bilir”. Bu, bilgi tabanının tam tersi bir yaklaşımdır: bilgi tabanı “yalnızca gerektiğinde getir, sonra kaldır”, oturumlar arası bellek ise “her zaman mevcut, her zaman görünür” anlayışıyla çalışır.
Yazma işlemleri, modelin konuşma sırasında “bu uzun vadede hatırlanmaya değer” diye karar verdiğinde çağırdığı özel bir bellek aracı üzerinden yapılır; araç ekleme, alt dize değiştirme ve silmeyi destekler. Nelerin saklanıp nelerin saklanmayacağı aracın açıklamasında açıkça belirtilir: kullanıcı düzeltmeleri ve tercihleri en yüksek önceliğe sahiptir; kalıcı kararlar ve kurallar kaydedilir. Mevcut görevin geçici durumu, tek seferlik hata ayıklama bilgileri ve kolayca yeniden bulunabilecek şeyler ise kaydedilmez — bellek “kullanıcı ve proje hakkındaki kalıcı olgular” içindir, “bu sefer nerede kaldığım” için değil.
Kolayca gözden kaçabilecek ama oldukça önemli bir ayrıntı var: her yazma işleminden önce bir güvenlik taraması çalışır. Bu içerik sistem istemine aynen girer ve oturumlar boyunca uzun süre kalır — fiilen kalıcı bir istem enjeksiyonu yüzeyidir. Bu nedenle diske yazılmak üzere olan her bellek kaydı önce şüpheli örüntüler açısından taranır: klasik istem enjeksiyonu ifadeleri (“önceki tüm talimatları yok say” ve benzerleri), anahtarları dışarı sızdırmaya çalışan komutlar, metne gizlenmiş görünmez Unicode karakterleri. Eşleşme bulunursa kayıt doğrudan reddedilir. Buna tekilleştirme ve sınırı aşan içeriğin budanması da eklenince, bu bellek katmanı riskli bir yüke dönüşmeden yararlı kalır.
İki mekanizma birlikte iki ucu da kapsar — “çok büyük ama ara sıra gereken” ve “küçük ama sürekli gereken”: ilkini bilgi tabanı, ikincisini oturumlar arası bellek yönetir. Buna ajanın kendisine ilişkin anlayışını da eklediğinizde (bir sonraki makalenin konusu), bir Orkas ajanı aynı anda üç tür bellekle işe başlar — içerikle, kullanıcıyla ve kendisiyle ilgili bellek.
Oturumlar: çökmeye dayanıklı, onarılmaya hazır
Oturum, mesaj geçmişini yönetir. Temel sürüm, geçmiş budama ve sıkıştırma özelliklerine sahip, bellekte tutulan bir mesaj dizisinden ibarettir. Ancak kullanıcının makinesinde çalışan her şey, her an sonlandırılabileceğini varsaymalıdır — kullanıcı uygulamadan çıkar, sistem yeniden başlar veya bir gözetim zaman aşımı işlemi sonlandırır. Bu yüzden üretimde, her satırda bir mesaj olacak şekilde yerel bir JSONL dosyasına yazılan kalıcı bir oturum kullanılır.
İki yazma stratejisi vardır: yeni mesaj eklerken atomik ekleme kullanılır; tüm dosyayı yeniden yazan işlemlerde (sıkıştırma, temizleme) “geçici dosya yaz + atomik olarak yeniden adlandır” yöntemi uygulanır. Böylece yazma sırasında elektrik kesilse bile geride yarım kalmış bozuk bir kayıt bırakılmaz.
En ilginç parça, sonuçsuz kalmış araç çağrılarını onarmak. Eşleşme kuralına dönelim: model araç çağrısı yapar, çalıştırma altyapısı bunu yürütür, sonuç geri yazılır — bu üç adımdan herhangi biri kesintiye uğrarsa diskte “sonucu olmayan bir çağrı” kalır. O oturumu bir dahaki sefere yükleyip olduğu gibi modele gönderirseniz API ya reddeder ya da takılı kalır.
Onarım mantığı, oturum diskten her yüklendiğinde çalışır ve tekrar uygulanması sonucu değiştirmez:
- Tüm asistan mesajlarını tara ve yaptıkları araç çağrılarının kimliklerini topla.
- İlerleyen mesajlarda eşleşen araç sonuçlarını ara.
- Eşleşen sonucu olmayan her çağrı için “kesintiye uğradı” işaretli bir sonuç oluştur.
- Bu sırada sonuç sırasını çağrıların belirtildiği sırayla hizala ve karşılık gelen çağrısı olmayan sonuçları kaldır.
Bu işlemden sonra oturumun API'nin eşleşme gereksinimini karşılayan ve güvenle gönderilebilecek bir durumda olduğu garanti edilir. Mekanizma sıradan görünebilir, ama “kullanıcının konuşmasının tek bir çökme yüzünden kalıcı olarak kilitlenmesini” önleyen güvenlik ağı budur.
Geriye dönüp bakınca önem kazanan birkaç karar
Bunların tümünü bir araya getirince, bazı kararların değeri sonradan özellikle belirginleşiyor.
Ana arayüz olarak üreteçler. Akış kullanan ve kullanmayan yollar aynı uygulamayı paylaşır, ara durumlar doğal biçimde görünür hale gelir ve kullanıcı arayüzü istediği kadar ayrıntı gösterebilir. Bu tercih, “önce akışsız sürümü uygula, akışı sonra ekle” yaklaşımının doğuracağı bir tutarsızlık hatası sınıfını tamamen önledi.
Dolduğunda değil, %60'ta sıkıştır. Bu, kendisi de bir model çağrısı gerektiren sıkıştırma için pay bırakır ve son anda telaşa düşmeyi önler.
Eşleşme kuralı her yerde geçerli. Sıkıştırmanın kesim noktasından diske yazmaya ve yükleme sırasındaki onarıma kadar oturuma dokunan her yer aynı kuralı korur. Tek bir kural sayesinde hiçbir nokta kendi düzeltme mantığını icat etmek zorunda kalmaz.
Zahmetli rutin işler Sağlayıcı katmanında toplanır. Sağlayıcılar arasındaki tüm pürüzler — düşünme blokları, önbellek anahtarları, yetenek farkları — bu tek katmanda çözülür; böylece üstteki yürütücü sade kalır. Bir gün yeni bir model sağlayıcısı eklendiğinde değişiklik bu katmanın dışına neredeyse hiç taşmaz.
Sonuç
Orkas'ın çalıştırma altyapısında göz kamaştırıcı bir algoritma yok. Değeri, “bir ajanı gerçek ortamda güvenilir biçimde çalıştırma” işini sınırları net bir dizi modüle ayırmasında; her modül bir parçanın sorumluluğunu üstleniyor: yürütücü döngüyü ve yeniden denemeleri, araçlar yetenekleri, Sağlayıcı katmanı çok sayıda modeli birleştirmeyi, bellek bilgi getirmeyi, oturum ise kalıcılığı ve onarımı yönetiyor. Hiçbiri tek başına karmaşık değil; insanların her gün kullandığı bir sistemi ancak birlikte ayakta tutuyorlar.
Çıkarılacak ders şu: çalıştırma döngüsünü akış üreten bir üreteç yapın; ara durumları yönetmek çok daha kolaylaşır. Temel bir değişmez kural belirlendiğinde (“araç çağrıları eşleşmeli” gibi), onu sıkıştırmada, diske yazmada ve yüklemede tutarlı biçimde koruyun — hiçbir köşe istisna olmasın. Sağlayıcılar arasındaki zahmetli rutin işleri tek katmanda toplayın ve iş mantığından uzak tutun. Ve en açık olanı: işleminizin olabilecek en kötü anda sonlandırılacağını varsayın ve o an için gereken onarımı önceden yazın.
Bir sonraki makale Orkas'ın daha ilginç bir yönünü ele alıyor: bu ajanın kendi kullanımından nasıl öğrendiğini, deneyimi yeniden kullanılabilir becerilere nasıl dönüştürdüğünü ve zamanla kendini nasıl daha yararlı hale getirdiğini.