Design Patterns: Builder

İsa ÖZGÜR
6 min readMar 5, 2024

--

Görsel Kaynağı: https://clearmeasure.com/what-is-a-private-build/

Merhabalar 🖐🏻

Bu yazımda sizlere Creational Design Pattern’lerden olan Builder Pattern’den bahsedeceğim. Öncelikle önemli kavramları oturtup mantığını anladıktan sonra işin içerisine her yazımda olduğu gibi bir örnek senaryo ekleyerek konuyu pekiştireceğiz. Generative Ai dünyasına da bu anlatım içerisinde küçük dokunuşlar gerçekleştireceğiz. Hazırsanız tanım kısmımızla başlayalım!

Tanımlar

1.Creational Design Pattern Nedir?(Yaratımsal Tasarım Kalıpları)

  • İsminden de anlaşılacağı gibi bu tasarım deseni biçimi, uygulamamız içerisindeki nesnelerin oluşturulma şeklini kontrol eder.
  • Yapısal olarak bir nesnenin oluşturulma davranışını soyutlayarak belirlenen desene uygun şekilde manipüle edilmesini garanti eder.
  • Buradaki soyutlamaktan kastımız, pattern kullanılırken o nesneyle ilgili işlemin nasıl inşaa edildiğini bilmemize gerek olmadığı ve ek bir işlem gerektirmediğidir. Bu sorumluluk, işlemi gerçekleştiren sınıfa aittir.

2.Builder Pattern Nedir?(Kurucu Deseni)

  • Builder pattern, Creational Design Pattern tiplerindendir.
  • Kullanım şekli olarak “yapısal olarak karmaşık verilerin daha rahat oluşturulması” veya “dinamik inşaa süreçleri” gibi farklı durumlara göre yeni yeni davranışlar eklenerek değeri değişebilen veriler için kullanılır.
  • Çıktı olarak elde edilecek veri ve türü bellidir ama öncesinde kullanılacağı senaryoya göre bu çıktının hangi bileşenlerle hangi özelliklere sahip olacağı belli değildir, duruma göre değişir.

— Örneğimizi daha iyi anlamak için giriş yapmamız gereken 2 diğer kavramı da görelim;

3.Generative AI Nedir?(Üretici Yapay Zeka)

  • Generative AI, insanların yapabileceği yaratıcı işleri yapabilen yapay zeka sistemlerini ifade eder. Bu tür AI sistemleri, mevcut verilerden öğrenir ve bu bilgileri kullanarak tamamen yeni içerik, veri veya çözümler üretebilir.
  • Generative AI, genellikle makine öğrenimi ve derin öğrenme tekniklerini kullanarak, örneğin metin, resimler, müzik ve videolar gibi yeni ve özgün içerikler oluşturabilir.
  • Metin üretme, resim ve grafik üretimi, ses üretimi gibi hemen hemen her tipte veriyi isteklerimiz doğrultusunda yapay zeka destekli bir biçimde kolayca oluşturmamızı sağlayan süreçler bütünüdür.

4.Yapay Zekada Prompt Kavramı Nedir?

  • Yapay zekada “prompt” kavramı, özellikle dil modelleri ve diğer Generative AI sistemleri bağlamında kullanılan bir terimdir. Bir prompt, yapay zeka sistemine verilen bir girdi veya talimattır.
  • Bu girdi, yapay zeka modelinin ne tür bir çıktı üreteceğini belirler. Başka bir deyişle, prompt, modelin ne üzerine odaklanacağını, hangi tür bilgiyi işleyeceğini veya hangi tür bir yanıt üreteceğini yönlendirir.
  • ÖR: “Benim için uzay araçlarını anlatan bir makale yaz.” şeklinde yapay zekaya verilen bir input bu dünyada “prompt” olarak adlandırılır.

— Temel kavramlarla konumuza giriş yaptığımıza göre gerçek hayat senaryomuz üzerinden pattern’imizi kavrayalım.

Senaryo: Yapılacak İsteğe Göre Dinamik Prompt İnşaası

  • Senaryo açıklamasında da göründüğü gibi çeşitli ihtiyaçlara göre Ai’dan isteyeceklerimiz(prompt) değişebilir.

NOT:

a) Örnek için OpenAi API kullanılacaktır.

Open Ai API Dokümantasyonları: https://platform.openai.com/docs/introduction

b) OpenAi API entegrasyonları için resmi olmayan ama etkili olarak gördüğüm aşağıdaki kütüphaneyi kullanıyor olacağım;

OpenAI library by OkGoDolt

c) Entegrasyon için kullanacağım platform ise .NET Core 7 olacaktır.

  • Doğrudan metinsel bir biçimde” özellikler eklenerek gelen input üzerinden oluşturulan prompt ile “ses verisi üzerinden” oluşturulan prompt’un nasıl farklı davranışlarla Builder pattern ile dinamik şekilde inşaa edilebildiğini adım adım görelim.
  1. PromptBuilder Sınıfı:
public class PromptBuilder
{
private string _subject;
private string _description;
private int? _wordCount;
private PromptType _promptType;

public PromptBuilder SetSubject(string subject)
{
_subject = subject;
return this;
}

public PromptBuilder SetDescription(string description)
{
_description = description;
return this;
}

public PromptBuilder SetWordCount(int? wordCount)
{
_wordCount = wordCount;
return this;
}

public PromptBuilder SetPromptType(PromptType promptType)
{
_promptType = promptType;
return this;
}

public string Build()
=> GetFormattedPromptByPromptType(_promptType);

private string GetFormattedPromptByPromptType(PromptType promptType)
{
string inputText = $"Konu: {_subject}\n";
if (!string.IsNullOrWhiteSpace(_description))
inputText += $"Açıklama: {_description}\n";

string wordCountFilter = "";
if (_wordCount.HasValue)
wordCountFilter = $"en az {_wordCount.Value} kelimelik";

return $"Son cümlenin yarım bırakılmadan mutlaka anlamlı bir biçimde bitirildiği {wordCountFilter} Türkçe dilinde bir {promptType.GetDisplayName()} yazısı yaz.\n{inputText}";
}
}
  • Kod parçasında prompt ifademizi dinamik bir biçimde yaratabilmeye olanak sağlayan “PromptBuilder” nesnesini görüyoruz. Hadi birlikte bu kodu işlevlerine göre de parçalayarak her bir kısmın ne işe yaradığını ve mantığını öğrenelim!

Parça-1: İnşaa Edilecek Verilerin Kapsüllenmesi -> Güvenli Veriler

private string _subject;
private string _description;
private int? _wordCount;
private PromptType _promptType;
  • Burada field’larımızı private seçmemizin önemli bir sebebi vardır.
  • Biz dışarıdan yalnızca bu verileri gireriz ve içeride nasıl işlendikleri PromptBuilder sınıfının kendi kurallarına kalmıştır demiştik.
  • İşte bu inşaa sürecinde içeride Build edeceğimiz veride kullanılacak alanların PromptBuilder’ın bilgisi dışında davranışı değiştirilememesi için erişim belirteçlerini private seçerek kapsülleme işlemleriyle daha korunaklı bir inşaa süreci başlatıyoruz.

Parça-2: İnşaa Etmeye Yarayan Metotlar -> Kas Gücü Yapıları

public PromptBuilder SetSubject(string subject)
{
_subject = subject;
return this;
}

public PromptBuilder SetDescription(string description)
{
_description = description;
return this;
}

public PromptBuilder SetWordCount(int? wordCount)
{
_wordCount = wordCount;
return this;
}

public PromptBuilder SetPromptType(PromptType promptType)
{
_promptType = promptType;
return this;
}
  • Kapsüllemiş olduğumuz inşaa elemanlarımızı PromptBuilder içerisinde belirlediğimiz kurallara göre PromptBuilder nesnesindeki field’lara setliyoruz.
  • Bu yapılar aynı bir inşaattaki kas gücü şeklinde görülebilir. İlgili malzemeleri gerektiği biçimde ekleyen yapılardır.
  • Bu örnekte gelen verilere özel davranışlar belirlenmeden doğrudan PromptBuilder üzerindeki field’lara setlenmişlerdir.
  • Sonlarındaki “return this” ifadesi ise her bir özellik ekleme işleminden sonra “zincirlenmiş bir biçimde” yenilerinin eklenebilmesine olanak sağlar. Bu ifade şu ana kadar oluşturulmuş nesneyi ilgili field atama işlemi gerçekleştirdikten sonra return eder.

Parça-3: İnşaat Süreci Tamamlansın -> Yapının Ortaya Çıkartılması

public string Build()
=> GetFormattedPromptByPromptType(_promptType);

private string GetFormattedPromptByPromptType(PromptType promptType)
{
string inputText = $"Konu: {_subject}\n";
if (!string.IsNullOrWhiteSpace(_description))
inputText += $"Açıklama: {_description}\n";

string wordCountFilter = "";
if (_wordCount.HasValue)
wordCountFilter = $"en az {_wordCount.Value} kelimelik";

return $"Son cümlenin yarım bırakılmadan mutlaka anlamlı bir biçimde bitirildiği {wordCountFilter} Türkçe dilinde bir {promptType.GetDisplayName()} yazısı yaz.\n{inputText}";
}
  • Belirli iş kurallarına uygun bir biçimde prompt’umuz formatlanarak son halini alıyor ve “Build()” metodumuz ile inşaa süreci tamamlanarak return ediliyor.

Örnek Kullanım:

a) Doğrudan metinsel bir biçimde input girişi:

  • FullTextCompletionQueryHandler
public class FullTextCompletionQueryHandler : IRequestHandler<FullTextCompletionQuery, Result<FullTextCompletionQueryResult>>
{
private readonly IOpenAiService _openAiService;

public FullTextCompletionQueryHandler(IOpenAiService openAiService)
{
_openAiService = openAiService;
}

public async Task<Result<FullTextCompletionQueryResult>> Handle(FullTextCompletionQuery request, CancellationToken cancellationToken)
{
var prompt = new PromptBuilder()
.SetSubject(request.Subject)
.SetDescription(request.Description)
.SetPromptType(request.PromptType)
.SetWordCount(request.WordCount)
.Build();

var completionRequest = CompletionHelper.GenerateCompletionRequestByCreativity(request.CreativityType, prompt);

var completion = await _openAiService.CreateTextCompletionsAsync(completionRequest, request.RequestedOption);

if (completion is null || !completion.Completions.Any())
return Result<FullTextCompletionQueryResult>.Error("İsteğinize uygun bir cevap üretilemedi!", (int)HttpStatusCode.BadRequest);

var options = completion.Completions
.Select(completion => new TextCompletionOptionDto
{
Message = completion.Text.Replace("\n", ""),
WordCount = completion.Text.Replace("\n", "").GetWordCount()
}).ToList();

FullTextCompletionQueryResult result = new()
{
Options = options,
};

return Result<FullTextCompletionQueryResult>.Success(result, (int)HttpStatusCode.OK);
}
}
  • Prompt Yapısının İncelenmesi:
var prompt = new PromptBuilder()
.SetSubject(request.Subject)
.SetDescription(request.Description)
.SetPromptType(request.PromptType)
.SetWordCount(request.WordCount)
.Build();
  • Bir form üzerinden “konu, açıklama, prompt tipi, kelime sayısı” gibi veriler alınarak Open Ai’a gönderim işleminde PromptBuilder üzerindeki tüm alanların setlenerek prompt inşaa edildiğini görebiliyoruz.
  • Peki ya konu, açıklama gibi kısımların her birinin input olarak gönderilemediği , yalnızca ses verisi üzerinden bir prompt oluşturmak istersek ne olurdu?
  • İşte burada görürüz ki ilgili veri için tüm field’ları almak yerine dinamik koşullara uygun olarak parçalı bir şekilde alması gerekebilir.

b) Ses verisi üzerinden input girişi:

  • SpeechToTextCompletionCommandHandler
public class SpeechToTextCompletionCommandHandler : IRequestHandler<SpeechToTextCompletionCommand, Result<SpeechToTextCompletionCommandResult>>
{
private readonly IOpenAiService _openAiService;

public SpeechToTextCompletionCommandHandler(IOpenAiService openAiService)
{
_openAiService = openAiService;
}

public async Task<Result<SpeechToTextCompletionCommandResult>> Handle(SpeechToTextCompletionCommand request, CancellationToken cancellationToken)
{
var audioText = (await _openAiService.GetSpeechToTextDetailsAsync(request.AudioArray)).text;

if(string.IsNullOrWhiteSpace(audioText))
return Result<SpeechToTextCompletionCommandResult>.Error("İsteğinize uygun bir cevap üretilemedi!", (int)HttpStatusCode.BadRequest);

var prompt = new PromptBuilder()
.SetSubject(audioText)
.SetPromptType(PromptType.None)
.Build();

var completionRequest = CompletionHelper.GenerateCompletionRequestByCreativity(request.CreativityType, prompt);

var completion = await _openAiService.CreateTextCompletionsAsync(completionRequest);

if (completion is null || !completion.Completions.Any())
return Result<SpeechToTextCompletionCommandResult>.Error("İsteğinize uygun bir cevap üretilemedi!", (int)HttpStatusCode.BadRequest);

var options = completion.Completions
.Select(completion => new TextCompletionOptionDto
{
Message = completion.Text
.Replace("\n", ""),
WordCount = completion.Text
.Replace("\n", "")
.GetWordCount()
}).ToList();

SpeechToTextCompletionCommandResult result = new()
{
Options = options
};

return Result<SpeechToTextCompletionCommandResult>.Success(result, (int)HttpStatusCode.OK);
}
}
  • Prompt Yapısının İncelenmesi:
var prompt = new PromptBuilder()
.SetSubject(audioText)
.SetPromptType(PromptType.None)
.Build();
  • Görüldüğü üzere ses verisi bir önceki bölümlerde text’e dönüştürülmüş ve ardından prompt yapımız içerisine dahil ediliyor.
  • Burada yalnızca “açıklanması istenen konu” olarak bu veriyi gönderiyoruz.
  • Ayrıca PromptType için ise form verisinden geldiği kısıtlamalı biçimde değil de gönderilen sese tamamıyla uyumlu olması için hiçbir filtreye uğramadan işlenmesini tercih ediyoruz.
  • Sonda ise Build() metodumuz ile o ana kadar işlenmiş olan nesne üzerinden yaratılmış prompt’umuzu alarak Open Ai servislerine gönderiyoruz.
  • Generative Ai dünyasında Prompt oluşturma süreçleri ve koşullu durumlar bir hayli fazlasıyla bulunduğu için bu problemimizi ortak bir şablon dahilinde nasıl çözebilirizi Builder patternı temel haliyle kullanarak sizlere anlattım, umarım yararlı olmuştur :)
  • İlgili geliştirme için github repo linkim: https://github.com/ChessWizard/WizardAi
  • Bu endpointlerin detaylı entegrasyonu ve daha fazlası için Generative Ai yazı serimde görüşmek üzere…

KAYNAKLAR

--

--

İsa ÖZGÜR

Software Developer & Professional Chess Player/Trainer