Yazılım Mimarileri ve Tasarım Desenleri Üzerine

.NET’te Akıllı LLM Yönlendirme | Microsoft.Extensions.AI ile Multi-Model Routing

.NET’te Akıllı LLM Yönlendirme | Microsoft.Extensions.AI ile Multi-Model RoutingMerhaba,

Bu içeriğimizde birden fazla LLM/model/provider kullanılan .NET uygulamalarında çok önemli bir yapı taşı olan Multi-Model Routing yeteneğini inceliyor olacağız. Bu yetenek ile gelen isteğin, mahiyetine göre hangi AI modeli tarafından işleneceğine runtime’da karar verilebilmekte ve bu yönlendirme neticesinde IChatClient arayüzüyle tek referans üzerinden çalışmalara devam edilebilmektedir. Burada özellikle tekrar vurgulamakta fayda görmekteyim ki, bu özellik, LLM’e cevap üretmemekte! gelen isteği hangi AI modele göndereceğine karar vermektedir.

Önce problemi anlayalım…

Normal şartlarda tek LLM’i çalışmalarda akış mantığı şu şekilde tasarlanmaktadır;

Uygulama
│
▼
IChatClient
│
▼
GPT / Gemini / Claude

Esasında bu tarz bir çalışmada kaç adet LLM’in olduğu önem arz etmemekte, kaç adet olursa olsun istek sürekli aynı modele gitmektedir. Ancak gerçek senaryolarda şöyle bir ihtiyaç söz konusu olabilmektedir;

┌──► GPT-4.1
│
Kullanıcı isteği ──► Router
│
├──► Gemini
│
└──► Daha ucuz model

Yani gelen isteğin mahiyetine göre hangi modelin iş yapacağı runtime’da belirlenmesi gerekebilmektedir. Şöyle ki; “Merhaba, napıyon?” tarzı bir prompt’un ucuz ve hızlı bir model tarafından işlenmesi, ancak “Bu C# kodundaki concurrency problemini analiz et ve düzeltilmiş versiyonunu oluştur.” tarzı bir prompt’un ise işin ehli daha güçlü bir modele yönlendirilmesi istenebilir.

RoutingChatClient

İşte böyle bir durum için RoutingChatClient abstract class’ı geliştirilmiştir.

Bu class’ın temel mantığı şudur;

var router = RoutingChatClient.Create((context, ct) =>
    new(
        IsComplexRequest(context)
            ? powerfulClient
            : cheapClient));

Her gelen prompt’ta IsComplexRequest kontrolüne göre hangi AI modelin IChatClient referansıyla temsil edileceğini belirlemektedir.

Request
│
▼
RoutingChatClient
│
├── Complex? ──► PowerfulClient
│
└── Simple? ──► CheapClient

Daha gerçekçi bir örnek için aşağıdaki kodu inceleyelim;

    var router = RoutingChatClient.Create((routingContext, cancellationToken) =>
    {
        return new(new Func<RoutingContext, bool>(context =>
        {
            var message = context.Messages.LastOrDefault(m => m.Role == ChatRole.User);
            if (message.Text.Contains("ses") || message.Text.Contains("çevir"))
                return false;

            return true;
        })(routingContext) ? textAgentChatClient : speechAgentChatClient);
    });

    var response = await router.GetResponseAsync(request.Message);

Burada görüldüğü üzere RoutingChatClient class’ı, her GetResponseAsync metodu tetiklendiğinde (yahut streaming çağrısı olan GetStreamingResponseAsync metodu tetiklendiğinde) arkaplanda SelectClientAsync metodunu çalıştırarak bu isteğe karşın hangi AI modelin seçileceğini belirlemekte ve bunu da IChatClient olarak bizlere vermektedir. Zaten kaynağına bakarsanız eğer RoutingChatClient class’ının da esasında IChatClient türünden olduğunu görüyoruz.Router’ı kullanan kodun esasında arka planda kaç model olduğunu bilmesine gerek kalmadığından bu özellik vurgulanmaya değer…

SemanticRoutingChatClient

Şimdi gelelim, gelen istekte AI modeline yönlendirme sürecini net kurallar dayatarak belirlememizi sağlayan RoutingChatClient class’ından ziyade anlamına göre seçebilmemizi sağlayan SemanticRoutingChatClient sınıfına…

SemanticRoutingChatClient sınıfı, gelen isteğin anlamına göre hangi AI modelinin çalıştırılacağını belirleyebilmemizi sağlayan özel bir sınıftır. Davranışsal olarak şu şekilde bir işleyişe sahiptir:

Diyelim ki elimizde Fast Model, Coding Model ve Reasoning Model olmak üzere üç adet model olsun ve her biri için aşağıdaki gibi örnek ifadeler tanımlıyor olalım:

Fast Model
– “Hava bugün nasıl?”
– “Bunu İngilizceye çevir”
– “HTTP nedir?”

Coding Model
– “C# kodunu düzelt”
– “Bu SQL sorgusunu optimize et”
– “API endpoint’i oluştur”

Reasoning Model
– “Bu mimariyi analiz et”
– “Bu problemin kök nedenini bul”
– “İki yaklaşımı karşılaştır”

Burada kullanıcıdan gelen istek doğrultusunda tabi ki de kelime eşleşmesi yapılmamakta, isteğin embedding’i alınarak bu örnek ifadelerin embedding’i ile benzerlik kıyaslaması sağlanmaktadır.

User Request
│
▼
Embedding
│
├── Fast Profile 0.21
├── Coding Profile 0.43
└── Reasoning Profile 0.89
│
▼
Reasoning Model

Bu işlem neticesinde en benzer ifadeye sahip olan model, gelen isteği işlemek için sorumlu tutulmakta ve yola devam edilmektedir…

Kullanıcı
│
▼
SemanticRoutingChatClient
│
Embedding Model
│
▼
Semantic Search
/ | \
/ | \
▼ ▼ ▼
General Coding Reasoning
Gemini GPT-4.1 GPT-5

Bunun için aşağıdaki örneği inceleyebilirsiniz:

.
.
.
    var profiles = new Dictionary<IChatClient, IReadOnlyList<string>>
    {
        [textAgentChatClient] = [
            "Hikaye yaz",
            "Bir şiir oluştur",
            "Bir slogan üret",
            "Yaratıcı isimler öner",
            "Bir reklam metni hazırla"
            ],
        [speechAgentChatClient] = [
            "Bu metni seslendir",
            "Bu metni Türkçe seslendir",
            "Bu metni İngilizce seslendir",
            "Bu metni Fransızca seslendir",
            "Bu metni Almanca seslendir"
            ],
    };

    var router = new SemanticRoutingChatClient(
        embeddingGenerator: embeddingGenerator,
        clientProfiles: profiles,
        defaultClient: textAgentChatClient);

    var response = await router.GetResponseAsync(request.Message);
.
.
.

Burada embeddingGenerator router’ın semantic matching için kullanacağı embedding modeline karşılık gelmektedir. Yani bu model cevap üretmemekte sadece profiles’daki verilerin embedding’ini üreterek karşılaştırma zeminini oluşturmaktadır. Tabi burada şunu da bilmekte fayda vardır ki, bu implementasyon neticesinde profile data’ları lazy olarak oluşturulmakta ve cache’lenmektedir. Yani GetResponseAsync metodu ne zaman tetiklenirse o sırada kullanıcıdan gelen mesajla birlikte profile ifadelerinin (utterances) embedding’leri grup olarak (batch) oluşturulmaktadır.

Ayrıca defaultClient neyin nesi la hoca? diye sorarsanız eğer bu da, semantic router’ın ‘bilmiyorum’ diyebilmesini sağlayan bir varsayılan yapılandırmasıdır.

FailoverChatClient

Evet, sıra geldi kullandığımız model çalışmazsa eğer kullanıcıdan gelen mesajın alternatif olarak hangi model tarafından işleneceğini belirleyen FailoverChatClient mekanizmasına.

FailoverChatClient, hata durumunda yedek LLM’e geçiş (fallback/failover) mekanizmasıdır.

FailoverChatClient ile birinci model yahut servis hata verdiği takdirde, çıktı henüz kullanıcıya taahhüt edilmeden (yani commit edilmeden) yedek LLM devreye girmektedir.

Aşağıdaki gibi kullanılabilir:

.
.
.
    var failoverClient = new OrderedFailoverChatClient(clients: [
            textAgentChatClient,
            speechAgentChatClient
           ]);

    var response = await failoverClient.GetResponseAsync(request.Message);
.
.
.

Peki hangi hatalarda failover yapılır? sorunuzu duyar gibiyim… Burada her exception için bu durumu düşünmemek lazımdır… 429 Too Many Requests, 500 Internal Server Error, 503 Service Unavailable, timeout ve network error gibi geçici veya altyapısal problemler failover için mantıklı adaylardır diyebiliriz. Ancak model ‘bu isteği gerçekleştiremiyorum’ diyerek normal bir cevap üretmişse bu teknik anlamda bir failover durumu değildir! Benzer şekilde yanlış API key, yanlış model adı yahut kalıcı configuration hatalarında körlemesine başka bir modele geçmek her zaman doğru bir mimari olmayacaktır.

Nihai olarak;
RoutingChatClient ile kurallara, SemanticRoutingChatClient ile anlama, FailoverChatClient ile de dayanıklılığa göre çalışan bu üçlü yapının, uygulamalarımızın birden fazla LLM’i tek bir IChatClient referansı arkasında yönetebilmesinin kapısını aralamakta ve bu da bizlere; her isteği doğru modele, doğru maliyetle ve kesintisiz bir deneyimle ulaştırabilmenin mimari bir disiplinini kazandırmaktadır.

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Örnek çalışmaya aşağıdaki GitHub reposundan erişebilirsiniz.
https://github.com/gncyyldz/SmartModelRouting
Bu repository, ilgili konuya dair örnek çalışmanın kaynak kodlarını ve mimari yapısını içermektedir. Detaylar için GitHub üzerinden incelemede bulunabilirsiniz.


GitHub’da Görüntüle →

Mobil sürümden çık