Command Palette

Search for a command to run...

Açık Kaynak Pazartesi'si: Yazman — Türkçe Teknik Yazılarınızı Yazman ile Denetleyin

Yayınlama tarihi:
Okuma süresi:3 dk okuma

Platform: macOS 13+ · Swift 6.0+

Lisans: MIT

Kaynak Kod: github.com/alpozcan/yazman

Bağımlılıklar: Ollama, gemma3:4b, nomic-embed-text

Türkçe teknik yazı yazmak, İngilizce yazmaktan farklı bir zorluğu barındırıyor. Terminoloji tutarlılığı, Türkçe gramer kuralları, teknik terimlerin doğru kullanımı, aktif-pasif cümle dengesi gibi kritik bunlardan birkaçı. Bunları tek tek kontrol etmek yorucu, gözden kaçan hatalar ise yazının kalitesini sessizce düşürüyor. Bu sorunu çözmek için Yazman'ı yazdım: tamamen yerel çalışan, Ollama tabanlı, RAG destekli bir Türkçe teknik yazı denetleme aracı.

Yazman'ın en temelde Ollama üzerinde çalışan yerel bir LLM *(varsayılan: gemma3:4b) *ve embedding modeli (nomic-embed-text).

Ne Yapıyor?

Yazman üç temel iş akışı sunuyor:

  1. **Korpus oluşturma **— Web'den veya yerel dosyalardan Türkçe teknik makaleler toplayıp embedding'lere dönüştürme
  2. **Yazı denetleme **— Paragraf paragraf veya bütüncül (holistic) yazı inceleme
  3. **İyileştirme önerileri **— RAG ile referans korpusa dayalı somut kelime ve ifade önerileri

Araç bir Swift CLI olarak tasarlandı. *ArgumentParser *ile komut satırı arayüzü, *SwiftSoup *ile web scraping, *ollama-swift *ile LLM iletişimi, *Rainbow *ile renkli terminal çıktısı sağlanıyor.

Mimari: RAG Pipeline'ı

Yazman'ın kalbi bir RAG (Retrieval-Augmented Generation) pipeline'ı. Akış şöyle işliyor:

Makale
  → Paragraf Çıkarma
    → Embedding Oluşturma
      → Benzer Referans Arama
        → LLM'e Gönderme
          → Öneri

Bu pipeline'ın en kritik bileşeni *VectorStore *— bir Swift *actor *olarak tasarlandı, thread-safe bir embedding deposu:

actor VectorStore {
    struct Entry: Codable {
        let id: String          // "url::chunk_index"
        let text: String        // Chunk içeriği
        let title: String       // Makale başlığı
        let url: String         // Kaynak URL
        let embedding: [Float]  // Vektör temsili
    }

    private var entries: [Entry] = []
    private let client: Ollama.Client
    private let model: Model.ID

    func indexArticles(_ articles: [Article]) async throws -> Int
    func query(_ text: String, nResults: Int = 5) async throws -> [SearchResult]
}

*actor *kullanımı burada bilinçli bir tercihdi. Embedding işlemleri async ve potansiyel olarak birden fazla komuttan çağrılabiliyor — *actor *semantiği data race'leri derleyici düzeyinde engelliyor. Bu yüzden *VectorStore *Swift 6.0'ın strict concurrency checking'i ile de uyumlu.

Semantik Arama: Cosine Benzerliği

Kullanıcı bir makaleyi denetlemeye gönderdiğinde, Yazman önce paragrafları embedding'e dönüştürüyor, ardından korpustaki en benzer *bölümleri *buluyor. Benzerlik ölçüsü olarak *cosine similarity *kullanılıyor:

private func cosineSimilarity(_ a: [Float], _ b: [Float]) -> Float {
    guard a.count == b.count, !a.isEmpty else { return 0 }
    var dot: Float = 0
    var normA: Float = 0
    var normB: Float = 0
    for i in 0..<a.count {
        dot += a[i] * b[i]
        normA += a[i] * a[i]
        normB += b[i] * b[i]
    }
    let denom = sqrt(normA) * sqrt(normB)
    return denom > 0 ? dot / denom : 0
}

Sonuç 0.0 ile 1.0 arasında bir skor oluyor. Bu sonuç 1.0'a ne kadar yakınsa, iki metin de o kadar anlamsal olarak benzer diyebiliyoruz. Bu sayede Yazman, denetlenen paragrafla ilgili *en alakalı referans yazıları *bulup LLM'e bağlam olarak veriyor — böylece öneriler havadan değil, gerçek Türkçe teknik yazım örneklerine dayalı oluyor.

Metin Bölümlemesi: Doğal Sınırlardan Bölme

RAG sistemlerinde chunk boyutu kritik bir önem taşıyor. Çok büyük chunk'lar embedding kalitesini düşürürken, çok küçük olanlar ise bağlamı kaybediyor. Yazman 500 karakterlik chunk'lar oluşturuyor ve 100 karakterlik overlap bırakıyor. Bölme noktası olarak önce cümle sonu (. ), sonra satır sonu (\n) aranıyor — bulunamazsa sabit offset kullanılıyor:

private func chunkText(_ text: String, chunkSize: Int = 500, overlap: Int = 100) -> [String] {
    var chunks: [String] = []
    var start = text.startIndex

    while start < text.endIndex {
        let endOffset = text.distance(from: start, to: text.endIndex)
        let chunkEnd: String.Index

        if endOffset <= chunkSize {
            chunkEnd = text.endIndex
        } else {
            let tentativeEnd = text.index(start, offsetBy: chunkSize)
            let searchStart = text.index(start, offsetBy: chunkSize / 2)

            // Önce cümle sonuna, sonra satır sonuna bak
            if let dotPos = text.range(of: ". ", options: .backwards, range: searchStart..<tentativeEnd) {
                chunkEnd = dotPos.upperBound
            } else if let nlPos = text.range(of: "\n", options: .backwards, range: searchStart..<tentativeEnd) {
                chunkEnd = nlPos.upperBound
            } else {
                chunkEnd = tentativeEnd
            }
        }

        let chunk = String(text[start..<chunkEnd]).trimmingCharacters(in: .whitespacesAndNewlines)
        if chunk.count > 50 { chunks.append(chunk) }

        if chunkEnd >= text.endIndex { break }
        start = text.index(chunkEnd, offsetBy: -overlap, limitedBy: text.startIndex) ?? text.startIndex
    }

    return chunks
}

50 karakterden kısa chunk'lar filtreleniyor — bunlar genellikle başlık artıkları veya kısa notlar oluyor ve embedding kalitesini olumsuz etkiliyor.

LLM ile Denetim: Türkçe Prompt Tasarımı

Yazman'ın LLM'e gönderdiği prompt'lar Türkçe teknik yazıma özel kurallar içeriyor. Bunlardan biri *wordingExpert prompt'u:

Kurallar:
1. Teknik terimler İngilizce kalsın (init, property, macro, view, struct,
   class, protocol, accessor, defer, async, await…)
2. Doğal Türkçe gramer, akademik-popüler ton
3. Gereksiz yabancı kelime kullanımından kaçın
4. Kısa, net cümleler — uzun olanları böl
5. Aktif çatı, edilgen değil
6. Okuyucuya doğrudan hitap ("siz" veya örtük özne, "biz" değil)
7. Tutarlı terminoloji

Bu kurallar, Türkçe teknik yazımın en yaygın sorunlarını hedef alıyor. Özellikle 1. ve 3. maddeler arasındaki denge çok önemli diye düşünüyorum: *init, property, macro *gibi yerleşik terimler makale içerisinde İngilizce olarak kalabilir, ancak *"implement" *yerine *"uygulamak", "basically" *yerine doğrudan anlatım tercih edilebilir.

Denetim sırasında paragraflar 5'erli batch'ler halinde LLM'e gönderiliyor. Her batch için RAG ile en benzer 3 referans chunk bulunuyor ve bağlam olarak ekleniyor:

// RAG bağlamını oluştur
var ragContext = ""
if useRAG {
    let sample = paragraphs.prefix(3).joined(separator: " ")
    let matches = try await store.query(sample, nResults: 3)
    if !matches.isEmpty {
        ragContext = "\n\nReferans Türkçe teknik yazım örnekleri:\n"
        for m in matches {
            ragContext += "\n---\nKaynak: \(m.title)\n\(String(m.text.prefix(500)))\n"
        }
    }
}

// Paragrafları batch halinde LLM'e gönder
for i in stride(from: 0, to: paragraphs.count, by: batchSize) {
    let batch = Array(paragraphs[i..<min(i + batchSize, paragraphs.count)])

    let prompt = """
    Aşağıdaki Türkçe teknik makale paragraflarını incele:

    \(batch.enumerated().map { "[\($0 + i + 1)] \($1)" }.joined(separator: "\n\n"))
    \(ragContext)
    """

    let stream = await client.generateStream(
        model: Config.defaultModel,
        prompt: prompt,
        options: ["temperature": 0.3, "top_p": 0.9, "num_predict": 2048],
        system: Prompts.wordingExpert
    )

    for try await chunk in stream {
        print(chunk.response, terminator: "")
    }
}

*temperature: 0.3 *tercihi bilinçli olarak seçildi. Çünkü yazım denetimi yaratıcılık değil tutarlılık gerektiriyor. Yüksek temperature, her seferinde farklı öneriler üretip ve güvenilirliği düşürüyor.

CLI Kullanımı

# 1. Korpus oluştur — web'den Türkçe teknik makaleler topla
yazman scrape --discover

# 2. Yerel dosya ekle
yazman add makale.md

# 3. Paragraf paragraf denetim (RAG destekli)
yazman check makale.md

# 4. RAG olmadan denetim
yazman check makale.md --no-rag

# 5. Bütüncül değerlendirme
yazman review makale.md

# 6. Somut kelime/ifade iyileştirme önerileri
yazman improve makale.md

# 7. Korpusta semantik arama
yazman search "async await Swift" -n 10

# 8. İstatistikler
yazman stats

*scrape --discover *komutu önceden tanımlı seed URL'lerden (Medium Türkiye, Wikipedia TR) bağlantıları keşfedip makaleleri indiriyor. *SwiftSoup *ile HTML temizleniyor — script, style, nav, footer gibi elementler çıkarılıyor — ve yalnızca ana içerik çekiliyor.

Bellek yönetimi hakkında bir not, aracı kullandıktan sonra ollama'nın çok fazla bellek tüketmemesi için ollama'yı durdurmayı unutmayın:

// Stopping Ollama

brew services stop ollama

// Unloading a Specific Model from Memory

ollama stop gemma3:4b
ollama stop nomic-embed-text

Kurulum

# Homebrew
brew tap alpozcan/yazman
brew install yazman

# Mint
mint install alpozcan/yazman

# Kaynaktan derleme
git clone https://github.com/alpozcan/yazman.git
cd yazman
swift build -c release
cp .build/release/yazman /usr/local/bin/

Ön koşul olarak Ollama ve iki modelin yüklü olması gerekiyor:

brew install ollama
brew services start ollama
ollama pull gemma3:4b
ollama pull nomic-embed-text

Paket Yapısı

Sources/yazman/
├── Yazman.swift           # Ana giriş noktası
├── Commands/                # CLI komutları
│   ├── CheckCommand.swift   # Paragraf paragraf denetim
│   ├── ReviewCommand.swift  # Bütüncül değerlendirme
│   ├── ImproveCommand.swift # RAG tabanlı iyileştirme
│   ├── ScrapeCommand.swift  # Web scraping & indeksleme
│   ├── SearchCommand.swift  # Semantik arama
│   ├── AddCommand.swift     # Yerel dosya ekleme
│   └── StatsCommand.swift   # İstatistikler
├── Core/
│   ├── VectorStore.swift    # Embedding deposu (actor)
│   ├── Checker.swift        # Paragraf çıkarma & LLM denetimi
│   ├── Scraper.swift        # Web scraping & cache
│   ├── Prompts.swift        # Türkçe system prompt'ları
│   ├── Config.swift         # Sabitler & dosya yolları
│   └── OllamaExtensions.swift
└── Utilities/
    └── Terminal.swift       # Renkli terminal çıktısı

Test dosyaları, Türkçe karakter işleme (ç, ğ, ş, ı, ö, ü), cosine similarity edge case'leri, chunk'lama sınır koşulları ve gerçek makale içeriğiyle entegrasyon testlerini kapsıyor.

Neden Swift?

Yazman'ı Python veya Node.js ile yazmak daha kolay olurdu — ama bilinçli olarak Swift'i tercih ettim. Bunun sebepleri ise şöyle:

  • Concurrency modeli: *actor *tabanlı *VectorStore, async/await tabanlı LLM iletişimi — Swift'in structured concurrency'si bu tür I/O-heavy uygulamalar için çok uygun.
  • Tip güvenliği: *Codable *ile JSON serializasyon, *enum tabanlı konfigürasyon sayesinde runtime hatalarını minimumda tutmak mümkün olabiliyor.
  • **macOS entegrasyonu: **Foundation framework'ü ile *FileManager, URLSession *doğal olarak mevcut.
  • **Dogfooding: **Swift üzerine yazdığım teknik makaleleri de yine Swift ile yazılmış bir araçla denetlemek istedim.

Yazman'ı MIT lisansı ile açık kaynak olarak yayınladım. Repo'yu incelediğinizde [CONTRIBUTING.md](https://contributing.md/) dosyasında katkı rehberini bulabilirsiniz. SwiftLint ile strict mod etkin, CI/CD pipeline'ı mevcut.*"Good First Issue" *etiketli görevler üzerinde çalışıyorum.

Bu noktada, özellikle öğrencilere ve yeni mezunlara bir şey söylemek istiyorum: açık kaynak projelere katkıda bulunmak, kariyeriniz için yapabileceğiniz en değerli yatırımlardan biri diye düşünüyorum. Geçen hafta da bu konuya aşağıdaki yazıda değinerek, Hugging Face'ten Kaggle'a, GitHub'dan Streamlit'e kadar 8 farklı açık platformu incelemiştim ve CV'de nasıl stratejik bir varlığa dönüştürülebileceklerini anlatmıştım.

Alp Özcan@alpoezcan·Feb 18 ArticleCV Zenginleştirmek için Açık PlatformlarGiriş: Zorlu Bir Sektörde Fark Yaratmak 2026 yılının yazılım mühendisliği iş piyasası, son on yılın belki de en zorlu dönemini yaşıyor. Büyük teknoloji şirketlerinin toplu işten çıkarmalarının...1221

Bu yazıda altını çizdiğim bir noktayı burada tekrarlamak istiyorum ki bir platform veya programlama dilindeki derinlik, genişlikten daha değerli**. **Bir projenin issue tracker'ını açıp, kodu okuyup, küçük ama anlamlı bir PR göndermek — bu süreç size şunları kazandırıyor:

  • Başkalarının kod tabanında çalışma deneyimi. Kendi projelerinizde her şey sizin kontrolünüzde. Açık kaynak projede ise mevcut mimariyi anlamak, code review sürecine adapte olmak ve topluluk normlarına uymak zorundasınız. Bu, iş hayatının gerçekliğine çok daha yakın bir deneyim.
  • Git workflow pratiği. Fork, branch, PR, review, merge — bu döngüyü gerçek bir projede yaşamak, hiçbir tutorial'ın veremeyeceği bir özgüven sağlıyor.
  • Teknik iletişim becerisi. Commit mesajları, PR açıklamaları, issue tartışmaları — bunların hepsi yazılım mühendisliğinin günlük iletişim dili. Açık kaynak projeler bunu pratikte öğrenmenin en iyi yolu.

Yazman gibi küçük ama odaklı bir proje, katkıda bulunmak için iyi bir başlangıç noktası olabilir. Codebase 934 satırdan oluşuyor. Swift Package Manager ile hızlıca derleyebilirsiniz.

Türkçe NLP/yazım alanında çözülecek çok sayıda ilginç problem olduğunu düşünüyorum. Yazman'a katkıda bulunabileceğiniz bazı alanları şöyle derledim:

  • Yeni Türkçe prompt şablonları (farklı yazım stilleri için)
  • Ek embedding model desteği (mxbai-embed-large gibi)
  • Markdown dışında format desteği (Asciidoc, reStructuredText)
  • Terminal UI iyileştirmeleri
  • Performans optimizasyonları (batch embedding boyutu, chunk parametreleri)

Son Söz

Yazman, kişisel bir ihtiyaçtan doğdu. Türkçe teknik makaleler yazarken terminoloji tutarsızlığı, pasif cümle yığılması ve gereksiz yabancı kelime kullanımı gibi sorunları tekrar tekrar yaşadım. Bu aracı önce kendim için yazdım, sonra *"belki başkaları da kullanır" *diye açık kaynak yaptım.

Aracın en güçlü yanı RAG pipeline'ı — daha önce yazdığınız veya beğendiğiniz Türkçe teknik yazıları korpusa ekledikçe, öneriler o referanslara dayalı hale geliyor. Bu da zamanla kişiselleşen bir denetim deneyimi sunuyor.

934 satır Swift, 944 satır test, sıfır cloud bağımlılığıyla Yazman'ı MIT lisansının elverdiği şekillerde gönlünüzce kullanabilirsiniz.

Bu yazının kendisi de daha önceki teknik yazılarımla eğitilerek, 90% oranında Yazman ile yazıldı.

Ek Okuma

Paylaş:

Tartışma

Henüz yorum yok

İlk yorumu siz yapın!