🏠 Ana Sayfa 📖 Sözlük 💬 Doküman asistanı
Ana sayfaPlatform & AltyapıGözlemlenebilirlik

👁️Gözlemlenebilirlik ve İzleme

LLM sistemlerinin izlenmesi, geleneksel yazılım izlemesinden farklıdır. Latency, token kullanımı, maliyet, kalite metrikleri ve güvenlik olaylarının entegre olarak takip edilmesi gerekmektedir.

6.1 Langfuse Entegrasyonu

Langfuse, Komtaş'ın birincil LLM observability platformudur. GKE üzerinde self-hosted olarak çalışmaktadır.

from langfuse.decorators import observe, langfuse_context
from langfuse import Langfuse
import time

langfuse = Langfuse(
    public_key=os.environ["LANGFUSE_PUBLIC_KEY"],
    secret_key=os.environ["LANGFUSE_SECRET_KEY"],
    host=os.environ.get("LANGFUSE_HOST", "http://langfuse:3000")
)

# Decorator tabanlı tracing
@observe(name="rag-answer-generation")
async def generate_rag_answer(question: str, user_id: str) -> str:
    # Metadata ekle
    langfuse_context.update_current_trace(
        user_id=user_id,
        tags=["production", "rag", "customer-support"],
        metadata={"product_version": "2.1.0"}
    )
    
    # Retrieval span
    with langfuse_context.observe(name="retrieval"):
        contexts = await retrieve_contexts(question)
        langfuse_context.update_current_observation(
            metadata={"retrieved_chunks": len(contexts), "query": question}
        )
    
    # Generation span
    with langfuse_context.observe(name="generation"):
        answer = await generate_answer(question, contexts)
        langfuse_context.update_current_observation(
            usage={"input": count_tokens(question + str(contexts)), "output": count_tokens(answer)},
            metadata={"model": "claude-opus-4-6"}
        )
    
    # RAGAS skor ekle
    langfuse_context.score_current_trace(
        name="faithfulness",
        value=await compute_faithfulness(answer, contexts),
        comment="RAGAS faithfulness skoru"
    )
    
    return answer

# Manuel trace API
def log_agent_execution(agent_name: str, input_data: dict, output: str, duration_ms: int):
    trace = langfuse.trace(
        name=f"agent-{agent_name}",
        input=input_data,
        output=output,
        metadata={"duration_ms": duration_ms}
    )
    
    # Span ekle
    trace.span(
        name="tool-execution",
        start_time=datetime.utcnow(),
        metadata={"tools_used": input_data.get("tools_called", [])}
    )
    
    return trace.id
📌 Kod Notları
  • @observe dekoratörü: Fonksiyon çağrılarını otomatik olarak Langfuse'a trace olarak kaydeder. Girdi/çıktı, süre ve hata bilgisi hiçbir ekstra kod yazmadan izlenir.
  • langfuse_context.update_current_trace(): Trace'e iş bağlamı eklemek için kullanılır: kullanıcı ID'si, oturum ID'si, özel etiketler. Bu bilgiler Langfuse dashboard'unda filtreleme ve gruplama için kritiktir.
  • Maliyet izleme: LLM çağrılarında usage bilgisi otomatik kaydedilir. Langfuse bu bilgiyi modelin birim maliyetiyle çarparak USD bazlı maliyet raporları üretir.
  • Privacy: Hassas kullanıcı verilerini trace'e göndermeden önce anonimize veya hashleme yapın. Langfuse cloud yerine self-hosted versiyon Komtaş için uygun olabilir.

Prompt Yönetimi

# Langfuse prompt versiyonlama
from langfuse import Langfuse

langfuse = Langfuse()

# Prompt kaydet
langfuse.create_prompt(
    name="contract-analysis",
    prompt="""Sen Komtaş sözleşme uzmanısın.
    Aşağıdaki sözleşmeyi analiz et: {{contract_text}}
    
    Şunları belirle:
    1. Taraflar ve yükümlülükler
    2. Sona erme tarihi
    3. Ceza maddeleri
    4. Risk değerlendirmesi""",
    config={"model": "claude-opus-4-6", "temperature": 0.1},
    labels=["production"]
)

# Prompt kullan (her zaman production sürümünü çek)
def get_contract_prompt(contract_text: str) -> str:
    prompt_obj = langfuse.get_prompt("contract-analysis")
    
    # Langfuse observability'ye bağla
    langfuse_context.update_current_observation(
        prompt=prompt_obj  # Hangi prompt versiyonunun kullanıldığını trace et
    )
    
    return prompt_obj.compile(contract_text=contract_text)
📌 Kod Notları
  • Prompt yönetimi: Promptları kod içine gömmek yerine Langfuse'da versiyonlanmış olarak saklamak, deploy olmadan prompt güncellenmesini sağlar.
  • compile(): Prompt şablonundaki değişkenleri ({customer_name} gibi) gerçek değerlerle doldurur. Langfuse Python SDK bunu type-safe biçimde yapar.
  • Rollback: Yeni prompt sürümü performansı düşürürse Langfuse UI'dan önceki sürüme tek tıkla geri dönülebilir. Kod deployment gerektirmez.
  • A/B test: İki prompt sürümünü eş zamanlı test etmek için Langfuse'un experiment özelliği veya version parametresinde rastgele seçim yapan bir wrapper fonksiyon kullanılabilir.

6.2 Maliyet İzleme ve Optimizasyon

Tüm model kullanımı OpenRouter üzerinden yapılmaktadır. Fiyatlar OpenRouter API'sinden alınmıştır (1M token başına USD, Nisan 2025).

Model Seçim Rehberi: Görev karmaşıklığına ve bütçeye göre doğru modeli seçin. Yüksek hacimli işlemler için Llama 3.3 / Gemini Flash Lite / Mistral Small; kritik analizler için Claude Opus / Gemini 2.5 Pro; genel görevler için Claude Sonnet / GPT-4.1 / Grok 3 önerilir.
SağlayıcıModelInput ($/1M)Output ($/1M)ContextÖnerilen Kullanım
AnthropicClaude Opus 4.6$5.00$25.001MKritik analiz, hukuki inceleme, yüksek doğruluk gereken görevler
Claude Sonnet 4.6$3.00$15.001MGenel amaçlı ajan geliştirme, kod, analiz — denge noktası
OpenAIGPT-4o$2.50$10.00128KOpenAI ekosistemi entegrasyonları, multimodal görevler
GPT-4.1$2.00$8.001MUzun bağlam işleme, doküman analizi
o3$2.00$8.00200KDerin reasoning, matematik, kod doğrulama
o4-mini$1.10$4.40200KMaliyet-etkin reasoning; o3'ün hızlı alternatifi
GoogleGemini 2.5 Pro$1.25$10.001MUzun bağlam analizi, çok modlu görevler, Google ADK
Gemini 2.5 Flash$0.30$2.501MYüksek hacim ajan workflow'ları, RAG pipeline
Gemini 2.5 Flash Lite$0.10$0.401MÇok yüksek hacim, basit sınıflandırma, ön filtreleme
DeepSeekDeepSeek V3$0.32$0.89164KKod üretimi, teknik analiz — maliyet/performans öncüsü
DeepSeek R1$0.70$2.5064KAçık kaynak reasoning modeli; düşük maliyetli düşünce zinciri
MiniMaxMiniMax-01$0.20$1.101MUzun bağlam, doküman işleme, çok dilli destek
MiniMax M1$0.40$2.201MGelişmiş reasoning, 1M context — uzun rapor analizi
xAIGrok 3$3.00$15.00131KGerçek zamanlı veri erişimi, güncel bilgi gerektiren görevler
Grok 3 Mini$0.30$0.50131KMaliyet-etkin Grok — hızlı sorgular, anlık veri
MetaLlama 3.3 70B$0.10$0.32131KYüksek hacim sınıflandırma, embed pipeline, prototipleme
MistralMistral Large 2411$2.00$6.00131KAvrupa veri gizliliği uyumlu projeler, çok dilli işleme
Mistral Small 3.2$0.075$0.20128KDüşük maliyetli Avrupa uyumlu model; yüksek hacim
AlibabaQwen3 235B A22B$0.46$1.82131KÇok dilli destek, Asya pazarı entegrasyonları
CohereCommand R+$2.50$10.00128KKurumsal RAG, doküman tabanlı arama ve özetleme
# ─── Maliyet Optimizasyonu — OpenRouter Tabanlı Model Yönlendirme ───
# Tüm modeller tek endpoint üzerinden erişilir: openrouter.ai/api/v1
import os, redis
from openai import OpenAI   # OpenRouter, OpenAI-uyumlu API sunar
from datetime import date

# OpenRouter istemcisi — base_url ile Anthropic/OpenAI/Google farkı ortadan kalkar
OPENROUTER_CLIENT = OpenAI(
    base_url="https://openrouter.ai/api/v1",
    api_key=os.getenv("OPENROUTER_API_KEY"),
)

# Görev × karmaşıklık → model  (maliyet sırasına göre optimize edilmiş)
ROUTING_MATRIX = {
    # Yüksek hacim / basit → en düşük maliyet
    ("classification",  "low"):    "meta-llama/llama-3.3-70b-instruct",       # $0.10/$0.32
    ("extraction",      "low"):    "google/gemini-2.5-flash-lite",             # $0.10/$0.40
    ("summarization",   "low"):    "mistralai/mistral-small-3.2-24b-instruct", # $0.075/$0.20
    # Orta karmaşıklık → denge noktası
    ("classification",  "medium"): "google/gemini-2.5-flash",                  # $0.30/$2.50
    ("summarization",   "medium"): "anthropic/claude-sonnet-4.6",              # $3/$15
    ("code_generation", "medium"): "deepseek/deepseek-chat",                   # $0.32/$0.89
    ("data_analysis",   "medium"): "openai/gpt-4.1",                           # $2/$8
    ("translation",     "medium"): "qwen/qwen3-235b-a22b",                     # $0.46/$1.82
    # Kritik / yüksek kalite → premium modeller
    ("analysis",        "high"):   "anthropic/claude-sonnet-4.6",              # $3/$15
    ("legal_review",    "high"):   "anthropic/claude-opus-4.6",                # $5/$25
    ("reasoning",       "high"):   "openai/o3",                                # $2/$8
    ("code_review",     "high"):   "deepseek/deepseek-r1",                     # $0.70/$2.50
    ("strategic",       "high"):   "x-ai/grok-3",                              # $3/$15
    ("long_document",   "high"):   "google/gemini-2.5-pro",                    # $1.25/$10
}

# 1M token başına USD maliyet (OpenRouter, Nisan 2025)
MODEL_COSTS = {
    "meta-llama/llama-3.3-70b-instruct":         {"in": 0.100,  "out": 0.320},
    "google/gemini-2.5-flash-lite":              {"in": 0.100,  "out": 0.400},
    "mistralai/mistral-small-3.2-24b-instruct":  {"in": 0.075,  "out": 0.200},
    "google/gemini-2.5-flash":                   {"in": 0.300,  "out": 2.500},
    "anthropic/claude-sonnet-4.6":               {"in": 3.000,  "out": 15.00},
    "deepseek/deepseek-chat":                    {"in": 0.320,  "out": 0.890},
    "openai/gpt-4.1":                            {"in": 2.000,  "out": 8.000},
    "qwen/qwen3-235b-a22b":                      {"in": 0.455,  "out": 1.820},
    "anthropic/claude-opus-4.6":                 {"in": 5.000,  "out": 25.00},
    "openai/o3":                                 {"in": 2.000,  "out": 8.000},
    "deepseek/deepseek-r1":                      {"in": 0.700,  "out": 2.500},
    "x-ai/grok-3":                               {"in": 3.000,  "out": 15.00},
    "google/gemini-2.5-pro":                     {"in": 1.250,  "out": 10.00},
}

def route_to_model(task_type: str, complexity: str) -> str:
    """Görev türü ve karmaşıklığa göre en uygun OpenRouter modelini döndürür."""
    return ROUTING_MATRIX.get((task_type, complexity), "anthropic/claude-sonnet-4.6")

def estimate_cost_usd(model: str, input_tokens: int, output_tokens: int) -> float:
    """Tahmini maliyeti USD cinsinden hesaplar. (1M token başına fiyat × kullanım)"""
    c = MODEL_COSTS.get(model, {"in": 3.0, "out": 15.0})
    return (input_tokens * c["in"] + output_tokens * c["out"]) / 1_000_000

def call_via_openrouter(task_type: str, complexity: str,
                        prompt: str, max_tokens: int = 2048) -> dict:
    """Model seçimi, API çağrısı ve maliyet takibini tek fonksiyonda birleştirir."""
    model = route_to_model(task_type, complexity)
    response = OPENROUTER_CLIENT.chat.completions.create(
        model=model,
        max_tokens=max_tokens,
        messages=[{"role": "user", "content": prompt}],
        extra_headers={              # OpenRouter'ın zorunlu header'ları
            "HTTP-Referer": "https://komtas.com",
            "X-Title": "Komtas CoPilot",
        }
    )
    cost = estimate_cost_usd(model,
                             response.usage.prompt_tokens,
                             response.usage.completion_tokens)
    return {
        "content":   response.choices[0].message.content,
        "model":     model,
        "tokens":    response.usage.total_tokens,
        "cost_usd":  round(cost, 6),
    }

# ─── Token / Maliyet Bütçe Takibi (Redis tabanlı, USD bazlı) ───
class TokenBudgetTracker:
    def __init__(self, daily_limit_usd: float = 50.0):
        self.daily_limit_usd = daily_limit_usd
        self.redis = redis.Redis(host="redis", port=6379, decode_responses=True)

    def record_usage(self, user_id: str, model: str,
                     input_tokens: int, output_tokens: int) -> dict:
        """Kullanımı kaydeder; günlük USD limitini aşarsa exception fırlatır."""
        cost = estimate_cost_usd(model, input_tokens, output_tokens)
        key   = "cost_budget:%s:%s" % (user_id, date.today())
        pipe  = self.redis.pipeline()    # Atomik işlem — race condition önler
        pipe.incrbyfloat(key, cost)
        pipe.expire(key, 86400)          # 24 saat TTL — günlük sıfırlama
        cumulative = float(pipe.execute()[0])
        if cumulative > self.daily_limit_usd:
            raise Exception("Günlük $%.0f limiti aşıldı: $%.4f" % (self.daily_limit_usd, cumulative))
        return {"cost_usd": cost, "daily_total_usd": round(cumulative, 4)}

# ─── Kullanım Örneği ───
result = call_via_openrouter(
    task_type="legal_review", complexity="high",
    prompt="Aşağıdaki sözleşmeyi KVKK uyumluluğu açısından değerlendir..."
)
print("Model: %s  |  Token: %d  |  Maliyet: $%.4f" % (result['model'], result['tokens'], result['cost_usd']))
📌 Kod Notları
  • OpenRouter base_url: base_url'i https://openrouter.ai/api/v1 olarak ayarlamak, tüm OpenAI SDK metodlarının OpenRouter üzerinden yönlendirilmesini sağlar. Sağlayıcı değişiminde yalnızca model parametresi değişir.
  • ROUTING_MATRIX: Görev türü × karmaşıklık çiftini modele eşleyen sözlük. Bu matrisin testlerle ve gerçek kullanım verisiyle düzenli kalibre edilmesi önerilir; projenin ilk aylarında haftada bir gözden geçirin.
  • MODEL_COSTS: Fiyatlar OpenRouter API'sinden Nisan 2025 itibarıyla alınmıştır. /api/v1/models endpoint'inden dönemsel olarak güncelleyin.
  • estimate_cost_usd(): 1M token başına USD fiyatını kullanım miktarıyla çarpar. / 1_000_000 bölümü kritiktir; unutulursa maliyet hesabı milyon kat hatalı çıkar.
  • pipeline (atomik Redis): Redis pipeline tüm komutları tek seferinde gönderir. Bu sayede incr + expire arasında başka bir istek giremez — race condition önlenir.
  • HTTP-Referer / X-Title: OpenRouter'ın zorunlu header'ları. Eksik olursa istek reddedilebilir.

6.3 SLO/SLA Tanımları ve Denetim Kaydı

MetrikSLO HedefiUyarı EşiğiKritik Eşik
P50 Yanıt Süresi≤ 1.5 saniye> 2 saniye> 5 saniye
P99 Yanıt Süresi≤ 8 saniye> 10 saniye> 20 saniye
Hata Oranı≤ 1%> 2%> 5%
Aylık Kullanılabilirlik≥ 99.5%< 99.5%< 99%
Faithfulness Skoru≥ 0.85 ortalama< 0.80< 0.70
Güvenlik İhlali0 P0 olayHerhangi P1Herhangi P0
# KVKK/GDPR uyumlu denetim kaydı
import hashlib
from datetime import datetime, timezone

class ImmutableAuditLogger:
    """Değiştirilemez denetim kaydı — KVKK, GDPR, SOC2 uyumlu"""
    
    def __init__(self, storage_client):
        self.storage = storage_client  # GCS bucket
        self.previous_hash = None
    
    async def log(self, event: dict):
        """Her log kaydı önceki kaydın hash'ini içerir (blockchain benzeri)"""
        
        record = {
            "event_id": str(uuid.uuid4()),
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "event_type": event.get("type"),
            "user_id": event.get("user_id"),
            "session_id": event.get("session_id"),
            "action": event.get("action"),
            # PII içermez — sadece anonimleştirilmiş referanslar
            "data_categories": event.get("data_categories", []),
            "legal_basis": event.get("legal_basis", "legitimate_interest"),
            "previous_hash": self.previous_hash,
            "retention_until": (datetime.now() + timedelta(days=730)).isoformat(),  # KVKK: 2 yıl
        }
        
        # Hash zinciri
        record_json = json.dumps(record, sort_keys=True, ensure_ascii=False)
        current_hash = hashlib.sha256(record_json.encode()).hexdigest()
        record["hash"] = current_hash
        self.previous_hash = current_hash
        
        # GCS'e yaz (immutable bucket policy ile)
        blob_name = f"audit/{datetime.now().strftime('%Y/%m/%d')}/{record['event_id']}.json"
        await self.storage.upload(blob_name, record_json)
        
        return record["event_id"]
📌 Kod Notları
  • hashlib.sha256: Kullanıcı ID'lerini ham hâlde loglamak yerine hashlemek, KVKK'nın kişisel veri minimizasyonu ilkesine uygundur. Aynı kullanıcıyı takip edebilirken kimliğini ifşa etmezsiniz.
  • immutable log: Denetim kaydı hiçbir zaman silinmemeli veya değiştirilmemelidir. PostgreSQL'de REVOKE UPDATE, DELETE ON audit_log FROM app_user ile bu kısıtlamayı zorunlu kılın.
  • timezone.utc: Tüm zaman damgalarını UTC'de saklayın. Türkiye saat dilimi (UTC+3) farkından kaynaklanan karışıklığı önler; gösterimde dönüşüm yapılabilir.
  • Saklama süresi: KVKK ve GDPR kapsamında denetim logları genellikle 3–5 yıl saklanmalıdır. Otomatik arşivleme veya silme politikası tanımlayın.