LocalAI ile Otomatik Belge Özetleme Sistemi: Sunucuda Batch PDF İşleme Pipeline’ı

Büyük bir şirketin hukuk departmanı düşünün: her gün onlarca sözleşme, raporlar, mahkeme kararları geliyor. Bunları okuyup özetlemek için ya insan saati harcıyorsunuz ya da verilerinizi bulut AI servislerine gönderiyorsunuz. İkincisi ciddi bir GDPR ve veri gizliliği sorununa yol açıyor. LocalAI tam bu noktada devreye giriyor: kendi sunucunuzda, tamamen offline çalışan, verilerinizi dışarı çıkarmayan bir AI altyapısı. Bu yazıda LocalAI üzerine kurulu, batch PDF işleyebilen, otomatik belge özetleme pipeline’ı nasıl kurulur, adım adım anlatacağım.

LocalAI Nedir ve Neden Tercih Edilmeli

LocalAI, OpenAI API’siyle uyumlu, kendi sunucunuzda çalışan bir yapay zeka runtime’ıdır. llama.cpp, whisper.cpp, stable diffusion ve daha fazlasını tek bir API altında toplar. En güzel yanı şu: mevcut kodunuzu minimum değişiklikle localAI’ye taşıyabilirsiniz çünkü API endpoint’leri OpenAI ile aynı.

Neden bulut yerine LocalAI:

  • Veri gizliliği: Hiçbir veri dışarı çıkmaz, GDPR uyumluluğu otomatik sağlanır
  • Maliyet: Token başı ücret yok, donanım maliyeti sabittir
  • Öngörülebilirlik: Rate limit yok, API kesintisi yok
  • Özelleştirme: İstediğiniz modeli, istediğiniz parametrelerle çalıştırırsınız
  • Offline çalışma: İnternet bağlantısı gerektirmez

Tipik kullanım senaryoları arasında hukuki belge analizi, finansal rapor özetleme, teknik dokümantasyon işleme ve müşteri geri bildirimi analizi sayılabilir.

Sistem Gereksinimleri ve Ortam Hazırlığı

Bu pipeline için önerilen minimum donanım:

  • CPU: 8 core (16 core tercihli)
  • RAM: 32GB (64GB ile rahat çalışırsınız)
  • Disk: SSD, 100GB+ boş alan
  • GPU: Opsiyonel ama varsa CUDA veya Metal ile dramatik hız farkı yaratır

Yazılım tarafında Ubuntu 22.04 LTS kullanacağız. İlk olarak sistemi hazırlayalım:

# Sistem güncellemesi
sudo apt update && sudo apt upgrade -y

# Gerekli araçlar
sudo apt install -y python3 python3-pip python3-venv 
    poppler-utils tesseract-ocr tesseract-ocr-tur 
    ghostscript curl wget git jq

# Python sanal ortamı
python3 -m venv /opt/docsum/venv
source /opt/docsum/venv/bin/activate

# Python bağımlılıkları
pip install pymupdf pdfplumber requests python-dotenv 
    watchdog schedule loguru tiktoken

LocalAI Kurulumu

LocalAI’yi Docker ile kurmak en temiz yol. Hem izolasyon sağlar hem de güncelleme sürecini basitleştirir.

# Docker kurulumu (zaten yoksa)
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER

# LocalAI için dizin yapısı
mkdir -p /opt/localai/{models,config,data}
cd /opt/localai

# Docker Compose dosyası
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
  localai:
    image: quay.io/go-skynet/local-ai:latest
    container_name: localai
    restart: unless-stopped
    ports:
      - "8080:8080"
    environment:
      - THREADS=8
      - CONTEXT_SIZE=4096
      - MODELS_PATH=/models
      - DEBUG=false
      - GALLERIES=github:go-skynet/model-gallery@main
    volumes:
      - ./models:/models
      - ./config:/config
    deploy:
      resources:
        limits:
          memory: 24G
EOF

docker compose up -d

Şimdi model indirelim. Belge özetleme için Mistral 7B Instruct iyi bir başlangıç noktası:

# Model indirme (bu işlem 4-5 GB ve biraz zaman alır)
cd /opt/localai/models

wget https://huggingface.co/TheBloke/Mistral-7B-Instruct-v0.2-GGUF/resolve/main/mistral-7b-instruct-v0.2.Q4_K_M.gguf 
    -O mistral-7b-instruct.gguf

# Model konfigürasyonu
cat > /opt/localai/config/mistral.yaml << 'EOF'
name: mistral
model: mistral-7b-instruct.gguf
context_size: 4096
threads: 8
f16: true
mmap: true
parameters:
  temperature: 0.1
  top_p: 0.9
  top_k: 40
  repeat_penalty: 1.1
template:
  chat: |
    <s>[INST] {{.Input}} [/INST]
EOF

# LocalAI'yi restart et
docker compose restart

API’nin çalıştığını doğrulayalım:

# Health check
curl http://localhost:8080/readyz

# Model listesi
curl http://localhost:8080/v1/models | jq '.data[].id'

# Hızlı test
curl http://localhost:8080/v1/chat/completions 
  -H "Content-Type: application/json" 
  -d '{
    "model": "mistral",
    "messages": [{"role": "user", "content": "Merhaba, calisiyor musun?"}],
    "max_tokens": 100
  }' | jq '.choices[0].message.content'

PDF İşleme Modülü

PDF’leri metin olarak çıkarmak için iki katmanlı bir yaklaşım kullanacağız: önce doğrudan metin ekstraksiyon, tarayıcıdan gelmiş veya OCR gereken belgeler için Tesseract. Bu gerçek dünya şartlarında kritik çünkü her belge aynı kalitede gelmiyor.

# /opt/docsum/pdf_processor.py

import fitz  # PyMuPDF
import pdfplumber
import subprocess
import tempfile
import os
from pathlib import Path
from loguru import logger

class PDFProcessor:
    def __init__(self, min_text_length=100):
        self.min_text_length = min_text_length
    
    def extract_text(self, pdf_path: str) -> dict:
        """
        PDF'den metin çıkarır. Önce native extraction dener,
        yetersizse OCR'a geçer.
        """
        pdf_path = Path(pdf_path)
        result = {
            'path': str(pdf_path),
            'filename': pdf_path.name,
            'text': '',
            'pages': 0,
            'method': 'none',
            'error': None
        }
        
        try:
            # Önce PyMuPDF ile dene (hızlı)
            text = self._extract_with_pymupdf(pdf_path)
            
            if len(text.strip()) > self.min_text_length:
                result['text'] = text
                result['method'] = 'native'
                logger.info(f"{pdf_path.name}: Native extraction basarili ({len(text)} karakter)")
            else:
                # Yeterli metin yoksa OCR dene
                logger.info(f"{pdf_path.name}: Native extraction yetersiz, OCR deneniyor...")
                text = self._extract_with_ocr(pdf_path)
                result['text'] = text
                result['method'] = 'ocr'
                logger.info(f"{pdf_path.name}: OCR tamamlandi ({len(text)} karakter)")
            
            # Sayfa sayısını al
            with fitz.open(str(pdf_path)) as doc:
                result['pages'] = len(doc)
                
        except Exception as e:
            result['error'] = str(e)
            logger.error(f"{pdf_path.name}: Hata - {e}")
        
        return result
    
    def _extract_with_pymupdf(self, pdf_path: Path) -> str:
        """PyMuPDF ile hızlı metin extraction"""
        text_parts = []
        with fitz.open(str(pdf_path)) as doc:
            for page in doc:
                text_parts.append(page.get_text("text"))
        return "n".join(text_parts)
    
    def _extract_with_ocr(self, pdf_path: Path) -> str:
        """Tesseract ile OCR tabanlı extraction"""
        text_parts = []
        
        with tempfile.TemporaryDirectory() as tmpdir:
            # PDF'i PNG'lere dönüştür
            subprocess.run([
                'pdftoppm', '-r', '200', '-png',
                str(pdf_path),
                f"{tmpdir}/page"
            ], check=True, capture_output=True)
            
            # Her sayfa için OCR çalıştır
            png_files = sorted(Path(tmpdir).glob("page-*.png"))
            for png_file in png_files:
                result = subprocess.run([
                    'tesseract', str(png_file), 'stdout',
                    '-l', 'tur+eng', '--psm', '3'
                ], capture_output=True, text=True, check=True)
                text_parts.append(result.stdout)
        
        return "n".join(text_parts)
    
    def chunk_text(self, text: str, max_chars: int = 3000, overlap: int = 200) -> list:
        """
        Uzun metinleri overlaplı chunk'lara böler.
        LLM context limitini aşmamak için kritik.
        """
        if len(text) <= max_chars:
            return [text]
        
        chunks = []
        start = 0
        
        while start < len(text):
            end = start + max_chars
            
            if end < len(text):
                # Cümle sınırında kes
                last_period = text.rfind('.', start, end)
                if last_period > start + max_chars // 2:
                    end = last_period + 1
            
            chunks.append(text[start:end])
            start = end - overlap
        
        return chunks

AI Özetleme Modülü

LocalAI ile konuşacak modülümüzü yazalım. Burada önemli bir nokta: uzun belgeleri chunk’layıp önce parça özetleri alacak, sonra bunları birleştirerek final özet üretecek bir “hierarchical summarization” yaklaşımı kullanacağız.

# /opt/docsum/summarizer.py

import requests
import json
import time
from typing import Optional
from loguru import logger

class LocalAISummarizer:
    def __init__(self, base_url: str = "http://localhost:8080", 
                 model: str = "mistral",
                 max_retries: int = 3):
        self.base_url = base_url
        self.model = model
        self.max_retries = max_retries
        self.session = requests.Session()
    
    def _chat_completion(self, prompt: str, max_tokens: int = 500) -> Optional[str]:
        """LocalAI'ye istek gönder"""
        
        for attempt in range(self.max_retries):
            try:
                response = self.session.post(
                    f"{self.base_url}/v1/chat/completions",
                    json={
                        "model": self.model,
                        "messages": [
                            {
                                "role": "system",
                                "content": "Sen uzman bir belge analisti asistanisin. Türkçe belgeler için Türkçe, İngilizce belgeler için İngilizce özet üretirsin. Özetlerin net, bilgilendirici ve kapsamlidir."
                            },
                            {
                                "role": "user",
                                "content": prompt
                            }
                        ],
                        "max_tokens": max_tokens,
                        "temperature": 0.1
                    },
                    timeout=120
                )
                response.raise_for_status()
                return response.json()['choices'][0]['message']['content']
                
            except requests.exceptions.Timeout:
                logger.warning(f"Timeout (deneme {attempt + 1}/{self.max_retries})")
                time.sleep(5 * (attempt + 1))
            except Exception as e:
                logger.error(f"API hatasi: {e}")
                if attempt == self.max_retries - 1:
                    return None
                time.sleep(2)
        
        return None
    
    def summarize_chunk(self, text: str, context: str = "") -> Optional[str]:
        """Tek bir metin parçasını özetle"""
        prompt = f"""Asagidaki metin parcasini analiz et ve özetle:

{f'Belge baglamı: {context}' if context else ''}

METIN:
{text}

Önemli noktaları, kararları ve eylemleri içeren kısa bir özet yaz."""
        
        return self._chat_completion(prompt, max_tokens=300)
    
    def merge_summaries(self, summaries: list, filename: str) -> Optional[str]:
        """Parça özetlerini birleştirerek final özet üret"""
        combined = "nn---nn".join([f"Bölüm {i+1}:n{s}" 
                                        for i, s in enumerate(summaries)])
        
        prompt = f"""Aşağıda "{filename}" adlı belgenin bölüm özetleri bulunmaktadır. 
Bu bölüm özetlerinden yola çıkarak kapsamlı bir FINAL ÖZET oluştur.

Final özette şunlar olmalı:
- Belgenin ana konusu ve amacı
- Temel bulgular veya kararlar  
- Önemli tarihler, isimler veya rakamlar
- Sonuç ve öneriler (varsa)

BÖLÜM ÖZETLERİ:
{combined}

FINAL ÖZET:"""
        
        return self._chat_completion(prompt, max_tokens=600)
    
    def extract_keywords(self, text: str) -> Optional[str]:
        """Anahtar kelimeleri çıkar"""
        prompt = f"""Aşağıdaki metinden en önemli 10 anahtar kelime/kavramı çıkar.
Sadece virgülle ayrılmış liste halinde döndür, başka açıklama ekleme.

METİN:
{text[:2000]}

ANAHTAR KELİMELER:"""
        
        return self._chat_completion(prompt, max_tokens=100)

Ana Pipeline Orkestratörü

Tüm parçaları bir araya getiren ve batch işlemeyi yöneten ana script:

# /opt/docsum/pipeline.py

import os
import json
import time
import hashlib
from pathlib import Path
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor, as_completed
from loguru import logger
from dotenv import load_dotenv

from pdf_processor import PDFProcessor
from summarizer import LocalAISummarizer

load_dotenv()

# Loglama konfigürasyonu
logger.add(
    "/opt/docsum/logs/pipeline_{time}.log",
    rotation="100 MB",
    retention="30 days",
    level="INFO"
)

class DocumentPipeline:
    def __init__(self, config: dict):
        self.input_dir = Path(config.get('input_dir', '/opt/docsum/input'))
        self.output_dir = Path(config.get('output_dir', '/opt/docsum/output'))
        self.processed_dir = Path(config.get('processed_dir', '/opt/docsum/processed'))
        self.max_workers = config.get('max_workers', 2)
        
        # Dizinleri oluştur
        for d in [self.input_dir, self.output_dir, self.processed_dir,
                  Path('/opt/docsum/logs')]:
            d.mkdir(parents=True, exist_ok=True)
        
        self.processor = PDFProcessor()
        self.summarizer = LocalAISummarizer(
            base_url=os.getenv('LOCALAI_URL', 'http://localhost:8080'),
            model=os.getenv('LOCALAI_MODEL', 'mistral')
        )
        
        # İşlenmiş dosya takibi
        self.state_file = Path('/opt/docsum/processed_files.json')
        self.processed_hashes = self._load_state()
    
    def _load_state(self) -> set:
        if self.state_file.exists():
            with open(self.state_file) as f:
                return set(json.load(f))
        return set()
    
    def _save_state(self):
        with open(self.state_file, 'w') as f:
            json.dump(list(self.processed_hashes), f)
    
    def _get_file_hash(self, file_path: Path) -> str:
        """Dosya hash'i - tekrar işlemi önler"""
        sha256 = hashlib.sha256()
        with open(file_path, 'rb') as f:
            for chunk in iter(lambda: f.read(8192), b''):
                sha256.update(chunk)
        return sha256.hexdigest()
    
    def process_single_document(self, pdf_path: Path) -> dict:
        """Tek belgeyi işle"""
        start_time = time.time()
        logger.info(f"İşleniyor: {pdf_path.name}")
        
        result = {
            'filename': pdf_path.name,
            'processed_at': datetime.now().isoformat(),
            'status': 'failed',
            'summary': None,
            'keywords': None,
            'pages': 0,
            'processing_time_sec': 0
        }
        
        try:
            # 1. PDF'den metin çıkar
            extracted = self.processor.extract_text(str(pdf_path))
            
            if extracted['error']:
                result['error'] = extracted['error']
                return result
            
            if not extracted['text'].strip():
                result['error'] = 'Metin çıkarılamadı'
                return result
            
            result['pages'] = extracted['pages']
            result['extraction_method'] = extracted['method']
            
            # 2. Metni chunk'la
            chunks = self.processor.chunk_text(extracted['text'])
            logger.info(f"{pdf_path.name}: {len(chunks)} parçaya bölündü")
            
            # 3. Her chunk'ı özetle
            chunk_summaries = []
            for i, chunk in enumerate(chunks):
                logger.debug(f"Chunk {i+1}/{len(chunks)} işleniyor...")
                summary = self.summarizer.summarize_chunk(chunk)
                if summary:
                    chunk_summaries.append(summary)
                time.sleep(0.5)  # API'yi bunaltma
            
            if not chunk_summaries:
                result['error'] = 'Özetleme başarısız'
                return result
            
            # 4. Final özet üret
            if len(chunk_summaries) == 1:
                result['summary'] = chunk_summaries[0]
            else:
                result['summary'] = self.summarizer.merge_summaries(
                    chunk_summaries, pdf_path.name
                )
            
            # 5. Anahtar kelimeleri çıkar
            result['keywords'] = self.summarizer.extract_keywords(
                extracted['text']
            )
            
            result['status'] = 'success'
            
        except Exception as e:
            result['error'] = str(e)
            logger.error(f"{pdf_path.name} işlenirken hata: {e}")
        
        result['processing_time_sec'] = round(time.time() - start_time, 2)
        return result
    
    def save_result(self, result: dict, original_path: Path):
        """Sonucu JSON ve TXT olarak kaydet"""
        stem = original_path.stem
        
        # JSON çıktısı
        json_path = self.output_dir / f"{stem}_summary.json"
        with open(json_path, 'w', encoding='utf-8') as f:
            json.dump(result, f, ensure_ascii=False, indent=2)
        
        # Okunabilir TXT çıktısı
        if result['status'] == 'success':
            txt_path = self.output_dir / f"{stem}_summary.txt"
            with open(txt_path, 'w', encoding='utf-8') as f:
                f.write(f"BELGE: {result['filename']}n")
                f.write(f"İşlem tarihi: {result['processed_at']}n")
                f.write(f"Sayfa sayısı: {result['pages']}n")
                f.write(f"İşlem süresi: {result['processing_time_sec']} saniyen")
                f.write("=" * 60 + "nn")
                f.write("ÖZET:n")
                f.write(result['summary'] + "nn")
                if result['keywords']:
                    f.write("ANAHTAR KELİMELER:n")
                    f.write(result['keywords'] + "n")
    
    def run_batch(self):
        """Input dizinindeki tüm PDF'leri işle"""
        pdf_files = list(self.input_dir.glob("*.pdf"))
        
        if not pdf_files:
            logger.info("İşlenecek PDF bulunamadı.")
            return
        
        logger.info(f"{len(pdf_files)} PDF bulundu, işlem başlıyor...")
        
        # Zaten işlenenleri filtrele
        pending = []
        for pdf in pdf_files:
            file_hash = self._get_file_hash(pdf)
            if file_hash not in self.processed_hashes:
                pending.append((pdf, file_hash))
            else:
                logger.info(f"Atlanıyor (zaten işlendi): {pdf.name}")
        
        logger.info(f"{len(pending)} yeni dosya işlenecek")
        
        # Paralel işleme (LLM yavaş olduğu için 2 worker yeterli)
        with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
            futures = {
                executor.submit(self.process_single_document, pdf): (pdf, h)
                for pdf, h in pending
            }
            
            for future in as_completed(futures):
                pdf, file_hash = futures[future]
                try:
                    result = future.result()
                    self.save_result(result, pdf)
                    
                    if result['status'] == 'success':
                        logger.success(f"Tamamlandı: {pdf.name} ({result['processing_time_sec']}s)")
                        # İşlenmiş dosyayı taşı
                        pdf.rename(self.processed_dir / pdf.name)
                        self.processed_hashes.add(file_hash)
                        self._save_state()
                    else:
                        logger.error(f"Başarısız: {pdf.name} - {result.get('error')}")
                        
                except Exception as e:
                    logger.error(f"Worker hatası ({pdf.name}): {e}")
        
        logger.info("Batch işleme tamamlandı.")

if __name__ == "__main__":
    config = {
        'input_dir': '/opt/docsum/input',
        'output_dir': '/opt/docsum/output',
        'processed_dir': '/opt/docsum/processed',
        'max_workers': 2
    }
    
    pipeline = DocumentPipeline(config)
    pipeline.run_batch()

Systemd Servisi ve Zamanlama

Pipeline’ı sistem servisi olarak ayarlayalım. Özellikle mesai saatleri dışında batch çalışma senaryosu için idealdir.

# Servis dosyası
sudo cat > /etc/systemd/system/docsum.service << 'EOF'
[Unit]
Description=Document Summarization Pipeline
After=docker.service
Requires=docker.service

[Service]
Type=oneshot
User=docsum
Group=docsum
WorkingDirectory=/opt/docsum
Environment=LOCALAI_URL=http://localhost:8080
Environment=LOCALAI_MODEL=mistral
ExecStart=/opt/docsum/venv/bin/python /opt/docsum/pipeline.py
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF

# Timer - her gece 02:00'da çalışsın
sudo cat > /etc/systemd/system/docsum.timer << 'EOF'
[Unit]
Description=Run Document Summarization Pipeline nightly

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true

[Install]
WantedBy=timers.target
EOF

# Servis kullanıcısı oluştur
sudo useradd -r -s /bin/false -d /opt/docsum docsum
sudo chown -R docsum:docsum /opt/docsum

# Timer'ı etkinleştir
sudo systemctl daemon-reload
sudo systemctl enable --now docsum.timer

# Durumu kontrol et
sudo systemctl status docsum.timer
sudo systemctl list-timers docsum.timer

İzleme ve Alerting

Production ortamında ne olduğunu takip etmek için basit bir monitoring scripti:

#!/bin/bash
# /opt/docsum/monitor.sh

OUTPUT_DIR="/opt/docsum/output"
LOG_DIR="/opt/docsum/logs"
ALERT_EMAIL="[email protected]"

# Son 24 saatte işlenen dosyaları say
PROCESSED_COUNT=$(find "$OUTPUT_DIR" -name "*.json" 
    -mtime -1 | wc -l)

# Başarısız işlemleri bul
FAILED_COUNT=$(grep -r '"status": "failed"' "$OUTPUT_DIR"/*.json 
    2>/dev/null | wc -l)

# Log dosyalarında ERROR ara
ERROR_COUNT=$(grep -c "ERROR" "$LOG_DIR"/*.log 2>/dev/null || echo 0)

echo "=== DocSum Pipeline Durumu ==="
echo "Son 24 saat işlenen: $PROCESSED_COUNT dosya"
echo "Başarısız: $FAILED_COUNT dosya"
echo "Log hataları: $ERROR_COUNT"

# LocalAI sağlık kontrolü
if curl -sf http://localhost:8080/readyz > /dev/null; then
    echo "LocalAI: ÇALIŞIYOR"
else
    echo "LocalAI: ERİŞİLEMİYOR"
    echo "LocalAI servisi çevrimdışı!" | mail -s "ALERT: LocalAI Down" $ALERT_EMAIL
fi

# Disk kullanımı kontrolü
DISK_USAGE=$(df /opt/docsum | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$DISK_USAGE" -gt 85 ]; then
    echo "UYARI: Disk kullanımı %$DISK_USAGE"
    echo "Disk kullanımı kritik seviyede: %$DISK_USAGE" | 
        mail -s "UYARI: Disk Doldu" $ALERT_EMAIL
fi

Gerçek Dünya Optimizasyonları

Birkaç ay boyunca bu tür sistemleri çalıştırırken öğrendiğim kritik noktalar:

Model seçimi önemlidir: Mistral 7B çoğu senaryo için yeterli. Ancak çok teknik veya hukuki belgeler için Llama-3 8B veya Mixtral 8x7B daha iyi sonuç verir. RAM’iniz müsaitse Mixtral’e geçin, fark gece gündüz.

Chunk boyutunu belgeye göre ayarlayın: Sözleşmeler için 2000 karakter iyi çalışırken, teknik raporlar için 3000-4000 karaktere çıkabilirsiniz. Örtüşme (overlap) değeri %10-15 arasında tutun.

Rate limiting koyun: LLM çıktısının tutarlı olması için ardışık istekler arasında 0.5-1 saniye bekleyin. LocalAI CPU’da çalışıyorsa bu zaten doğal olarak oluyor ama GPU’da hızlanınca garip hatalar çıkabiliyor.

mmap ve GPU offload: Model konfigürasyonunda mmap: true kesinlikle açık olsun. Eğer GPU varsa gpu_layers: 32 ekleyin, ama RAM/VRAM dengesini iyi ayarlayın. Yarısı GPU’ya, yarısı CPU’ya düşecek şekilde bölmek genellikle kararlılık açısından daha iyi sonuç veriyor.

Büyük PDF sorunları: 200+ sayfalık belgeler için chunk stratejinizi gözden geçirin. Her sayfadan özet alıp sonra bunları birleştirmek, sayfalar arası bölmekten daha iyi sonuç verdi benim testlerimde.

Encoding problemleri: Türkçe belgelerle çalışırken encoding sorunları baş ağrısı yaratır. Her yerde encoding='utf-8' kullanın ve OCR’da -l tur+eng mutlaka ekleyin.

Sonuç

Bu pipeline ile artık elinizde tamamen offline, veri gizliliğine saygılı, ölçeklenebilir bir belge özetleme altyapısı var. Hukuki departmanlar için sözleşme analizi, finans için rapor özetleme, IT için incident report işleme gibi pek çok alanda doğrudan kullanabilirsiniz.

Bir sonraki adım olarak bakabileceğiniz konular arasında Vector DB entegrasyonu öne çıkıyor. ChromaDB veya Qdrant ile özetlerin yanında semantic search ekleyebilirsiniz. Böylece “geçen yılki tedarikçi anlaşmalarında fiyat artışı olan hangileri?” gibi sorgular çalıştırabilirsiniz.

LocalAI aktif geliştirilen bir proje. Vision modelleri için LLaVA desteği, embedding modelleri ve function calling özellikleri sürekli ekleniyor. Bu pipeline’ı bir kez kurduğunuzda üzerine eklenti gibi yeni yetenekler kazandırabilirsiniz. Bulut AI servislerine ödediğiniz aylık token maliyetini düşünürseniz, bu altyapı çoğu durumda 3-6 ay içinde kendini amorti ediyor.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir