trustgraph/docs/tech-specs/pubsub.tr.md
Alex Jenkins 8954fa3ad7 Feat: TrustGraph i18n & Documentation Translation Updates (#781)
Native CLI i18n: The TrustGraph CLI has built-in translation support
that dynamically loads language strings. You can test and use
different languages by simply passing the --lang flag (e.g., --lang
es for Spanish, --lang ru for Russian) or by configuring your
environment's LANG variable.

Automated Docs Translations: This PR introduces autonomously
translated Markdown documentation into several target languages,
including Spanish, Swahili, Portuguese, Turkish, Hindi, Hebrew,
Arabic, Simplified Chinese, and Russian.
2026-04-14 12:08:32 +01:00

35 KiB
Raw Blame History

layout title parent
default Pub/Sub Altyapısı Turkish (Beta)

Pub/Sub Altyapısı

Beta Translation: This document was translated via Machine Learning and as such may not be 100% accurate. All non-English languages are currently classified as Beta.

Genel Bakış

Bu belge, TrustGraph kod tabanı ile pub/sub altyapısı arasındaki tüm bağlantıları listeler. Şu anda sistem, Apache Pulsar'ı kullanmak üzere sabit kodlanmıştır. Bu analiz, yapılandırılabilir bir pub/sub soyutlamasına yönelik gelecekteki yeniden düzenlemeleri bilgilendirmek için tüm entegrasyon noktalarını belirler.

Mevcut Durum: Pulsar Entegrasyon Noktaları

1. Doğrudan Pulsar İstemci Kullanımı

Konum: trustgraph-flow/trustgraph/gateway/service.py

API ağ geçidi, doğrudan Pulsar istemcisini içe aktarır ve örnekler:

Satır 20: import pulsar Satırlar 54-61: pulsar.Client()'ın doğrudan örneklenmesi, isteğe bağlı pulsar.AuthenticationToken() ile birlikte Satırlar 33-35: Ortam değişkenlerinden varsayılan Pulsar ana bilgisayarı yapılandırması Satırlar 178-192: --pulsar-host, --pulsar-api-key ve --pulsar-listener için CLI argümanları Satırlar 78, 124: pulsar_client'ı ConfigReceiver ve DispatcherManager'ye geçirir

Bu, bir soyutlama katmanı dışında bir Pulsar istemcisinin doğrudan örneklenmesini yapan tek konumdur.

2. Temel İşlemci Çerçevesi

Konum: trustgraph-base/trustgraph/base/async_processor.py

Tüm işlemciler için temel sınıf, Pulsar bağlantısı sağlar:

Satır 9: import _pulsar (istisna işleme için) Satır 18: from . pubsub import PulsarClient Satır 38: pulsar_client_object = PulsarClient(**params) oluşturur Satırlar 104-108: pulsar_host ve pulsar_client'i ortaya koyan özellikler Satır 250: Statik yöntem add_args(), CLI argümanları için PulsarClient.add_args(parser)'i çağırır Satırlar 223-225: _pulsar.Interrupted için istisna işleme

Tüm işlemciler AsyncProcessor'dan türetildiği için, bu merkezi bir entegrasyon noktasıdır.

3. Tüketici Soyutlaması

Konum: trustgraph-base/trustgraph/base/consumer.py

Kuyruklardan mesajlar tüketir ve işleyici fonksiyonlarını çağırır:

Pulsar içe aktarmaları: Satır 12: from pulsar.schema import JsonSchema Satır 13: import pulsar Satır 14: import _pulsar

Pulsar'a özgü kullanım: Satırlar 100, 102: pulsar.InitialPosition.Earliest / pulsar.InitialPosition.Latest Satır 108: JsonSchema(self.schema) sarmalayıcısı Satır 110: pulsar.ConsumerType.Shared Satırlar 104-111: Pulsar'a özgü parametrelerle self.client.subscribe() Satırlar 143, 150, 65: consumer.unsubscribe() ve consumer.close() yöntemleri Satır 162: _pulsar.Timeout istisnası Satırlar 182, 205, 232: consumer.acknowledge() / consumer.negative_acknowledge()

Belge dosyası: trustgraph-base/trustgraph/base/consumer_spec.py Satır 22: processor.pulsar_client'a referans

4. Yayıncı Soyutlaması

Konum: trustgraph-base/trustgraph/base/producer.py

Kuyruklara mesaj gönderir:

Pulsar içe aktarmaları: Satır 2: from pulsar.schema import JsonSchema

Pulsar'a özgü kullanım: Satır 49: JsonSchema(self.schema) sarmalayıcısı Satırlar 47-51: Pulsar'a özgü parametrelerle (konu, şema, chunking_enabled) self.client.create_producer() Satırlar 31, 76: producer.close() yöntemi Satırlar 64-65: Mesaj ve özelliklerle producer.send()

Belge dosyası: trustgraph-base/trustgraph/base/producer_spec.py Satır 18: processor.pulsar_client'a referans

5. Yayıncı Soyutlaması

Konum: trustgraph-base/trustgraph/base/publisher.py

Kuyruklu tamponlama ile asenkron mesaj yayınlama:

Pulsar içe aktarmaları: Satır 2: from pulsar.schema import JsonSchema Satır 6: import pulsar

Pulsar'a özgü kullanım: Satır 52: JsonSchema(self.schema) sarmalayıcısı Satırlar 50-54: Pulsar'a özgü parametrelerle self.client.create_producer() Satırlar 101, 103: Mesaj ve isteğe bağlı özelliklerle producer.send() Satırlar 106-107: producer.flush() ve producer.close() yöntemleri

6. Abonelik Soyutlaması

Konum: trustgraph-base/trustgraph/base/subscriber.py

Kuyruklardan çoklu alıcıya mesaj dağıtımı sağlar:

Pulsar importları: 6. Satır: from pulsar.schema import JsonSchema 8. Satır: import _pulsar

Pulsar'a özgü kullanım: 55. Satır: JsonSchema(self.schema) wrapper 57. Satır: self.client.subscribe(**subscribe_args) 101, 136, 160, 167-172. Satırlar: Pulsar istisnaları: _pulsar.Timeout, _pulsar.InvalidConfiguration, _pulsar.AlreadyClosed 159, 166, 170. Satırlar: Tüketici metotları: negative_acknowledge(), unsubscribe(), close() 247, 251. Satırlar: Mesaj onayı: acknowledge(), negative_acknowledge()

Spec dosyası: trustgraph-base/trustgraph/base/subscriber_spec.py 19. Satır: processor.pulsar_client'a referans

7. Şema Sistemi (Heart of Darkness)

Konum: trustgraph-base/trustgraph/schema/

Sistemdeki her mesaj şeması, Pulsar'ın şema çerçevesi kullanılarak tanımlanır.

Temel öğeler: schema/core/primitives.py 2. Satır: from pulsar.schema import Record, String, Boolean, Array, Integer Tüm şemalar, Pulsar'ın Record temel sınıfından türetilir. Tüm alan türleri Pulsar türleridir: String(), Integer(), Boolean(), Array(), Map(), Double()

Örnek şemalar: schema/services/llm.py (2. Satır): from pulsar.schema import Record, String, Array, Double, Integer, Boolean schema/services/config.py (2. Satır): from pulsar.schema import Record, Bytes, String, Boolean, Array, Map, Integer

Konu adlandırması: schema/core/topic.py 2-3. Satırlar: Konu formatı: {kind}://{tenant}/{namespace}/{topic} Bu URI yapısı Pulsar'a özgüdür (örneğin, persistent://tg/flow/config)

Etki: Kod tabanındaki tüm istek/yanıt mesaj tanımları, Pulsar şemalarını kullanır. Bu, aşağıdaki hizmetler için geçerlidir: yapılandırma, akış, LLM, istem, sorgu, depolama, aracı, koleksiyon, tanı, kütüphane, arama, NLP sorgusu, nesne sorgusu, alma, yapılandırılmış sorgu. Şema tanımları, tüm işlemciler ve hizmetler genelinde kapsamlı bir şekilde içe aktarılır ve kullanılır.

Özet

Kategoriye Göre Pulsar Bağımlılıkları

  1. İstem oluşturma: Doğrudan: gateway/service.py Soyutlanmış: async_processor.pypubsub.py (PulsarClient)

  2. Mesaj taşıma: Tüketici: consumer.py, consumer_spec.py Üretici: producer.py, producer_spec.py Yayıncı: publisher.py Abonelik: subscriber.py, subscriber_spec.py

  3. Şema sistemi: Temel türler: schema/core/primitives.py Tüm hizmet şemaları: schema/services/*.py Konu adlandırması: schema/core/topic.py

  4. Gereken Pulsar'a özgü kavramlar: Konuya dayalı mesajlaşma Şema sistemi (Kayıt, alan türleri) Paylaşımlı abonelikler Mesaj onayı (olumlu/olumsuz) Tüketici konumlandırması (en erken/en son) Mesaj özellikleri Başlangıç konumları ve tüketici türleri Parçalama desteği Kalıcı ve kalıcı olmayan konular

Yeniden Düzenleme Zorlukları

İyi haber: Soyutlama katmanı (Tüketici, Üretici, Yayıncı, Abonelik), çoğu Pulsar etkileşimini temiz bir şekilde kapsar.

Zorluklar:

  1. Şema sisteminin yaygınlığı: Her mesaj tanımı pulsar.schema.Record ve Pulsar alan türlerini kullanır.
  2. Pulsar'a özgü numaralandırmalar: InitialPosition, ConsumerType
  3. Pulsar istisnaları: _pulsar.Timeout, _pulsar.Interrupted, _pulsar.InvalidConfiguration, _pulsar.AlreadyClosed
  4. Metot imzaları: acknowledge(), negative_acknowledge(), subscribe(), create_producer(), vb.
  5. Konu URI formatı: Pulsar'ın kind://tenant/namespace/topic yapısı

Sonraki Adımlar

Yayın/abone altyapısını yapılandırılabilir hale getirmek için şunları yapmamız gerekiyor:

  1. İstem/şema sistemi için bir soyutlama arayüzü oluşturun.
  2. Pulsar'a özgü numaralandırmaları ve istisnaları soyutlayın.
  3. Şema sargıları veya alternatif şema tanımları oluşturun.
  4. Arayüzü hem Pulsar hem de alternatif sistemler (Kafka, RabbitMQ, Redis Streams, vb.) için uygulayın.
  5. pubsub.py'ı yapılandırılabilir hale getirin ve çoklu arka uçları destekleyin.
  6. Mevcut dağıtımlar için bir geçiş yolu sağlayın.

Yaklaşım Taslağı 1: Şema Çeviri Katmanıyla Adaptör Kalıbı

Temel Bilgi

Şema sistemi, en derin entegrasyon noktasıdır - her şey ondan kaynaklanır. Önce bunu çözmemiz gerekiyor, aksi takdirde tüm kodu yeniden yazmamız gerekecek.

Strateji: Minimum Bozulma ile Adaptörler

1. Pulsar şemalarını, dahili gösterim olarak koruyun Tüm şema tanımlarını yeniden yazmayın Şemalar, dahili olarak pulsar.schema.Record olarak kalır Adaptörleri, kendi kodumuz ile yayın/abonelik arka ucu arasındaki sınırdaki çevirileri yapmak için kullanın

2. Bir yayın/abonelik soyutlama katmanı oluşturun:

┌─────────────────────────────────────┐
│   Existing Code (unchanged)         │
│   - Uses Pulsar schemas internally  │
│   - Consumer/Producer/Publisher     │
└──────────────┬──────────────────────┘
               │
┌──────────────┴──────────────────────┐
│   PubSubFactory (configurable)      │
│   - Creates backend-specific client │
└──────────────┬──────────────────────┘
               │
        ┌──────┴──────┐
        │             │
┌───────▼─────┐  ┌────▼─────────┐
│ PulsarAdapter│  │ KafkaAdapter │  etc...
│ (passthrough)│  │ (translates) │
└──────────────┘  └──────────────┘

3. Soyut arayüzleri tanımlayın: PubSubClient - istemci bağlantısı PubSubProducer - mesaj gönderme PubSubConsumer - mesaj alma SchemaAdapter - Pulsar şemalarını JSON'a veya arka uç özel formatlarına dönüştürme/dönüştürme

4. Uygulama detayları:

Pulsar adaptörü için: Neredeyse doğrudan geçiş, minimum çeviri

Diğer arka uçlar için (Kafka, RabbitMQ, vb.): Pulsar Kayıt nesnelerini JSON/byte'a serileştirin Aşağıdaki kavramları eşleyin: InitialPosition.Earliest/Latest → Kafka'nın auto.offset.reset'i acknowledge() → Kafka'nın commit'i negative_acknowledge() → Yeniden kuyruklama veya DLQ deseni Konu URI'leri → Arka uç özel konu adları

Analiz

Artıları: Mevcut hizmetlerde minimum kod değişikliği Şemalar olduğu gibi kalır (büyük bir yeniden yazım olmaz) Aşamalı geçiş yolu Pulsar kullanıcıları hiçbir fark görmez Yeni arka uçlar adaptörler aracılığıyla eklenir

Eksileri: ⚠️ Hala Pulsar bağımlılığı taşır (şema tanımları için) ⚠️ Kavramları çevirirken bazı uyumsuzluklar olabilir

Alternatif Düşünce

Bir TrustGraph şema sistemi oluşturun; bu sistem, pub/sub'dan bağımsızdır (dataclass'lar veya Pydantic kullanarak). Ardından, bu sistemden Pulsar/Kafka/vb. şemalarını oluşturun. Bu, her şema dosyasının yeniden yazılmasını gerektirir ve potansiyel olarak uyumsuzluklara neden olabilir.

Taslak 1 için Öneri

Adaptör yaklaşımıyla başlayın çünkü:

  1. Pratik bir yaklaşımdır - mevcut kodla çalışır
  2. Minimum riskle kavramı kanıtlar
  3. Gerekirse daha sonra yerel bir şema sistemine dönüştürülebilir
  4. Yapılandırma odaklıdır: tek bir ortam değişkeni arka uçları değiştirir

Yaklaşım Taslağı 2: Dataclass'larla Arka Uçtan Bağımsız Şema Sistemi

Temel Kavram

Python dataclass'ları tarafsız şema tanımı formatı olarak kullanın. Her pub/sub arka ucu, dataclass'lar için kendi serileştirme/deserileştirme işlemlerini sağlar; bu, Pulsar şemalarının kod tabanında kalma ihtiyacını ortadan kaldırır.

Fabrika Seviyesinde Şema Çok Biçimliliği

Pulsar şemalarını çevirmek yerine, her arka uç, standart Python dataclass'larıyla çalışan kendi şema işleme yöntemini sağlar.

Yayıncı Akışı

# 1. Get the configured backend from factory
pubsub = get_pubsub()  # Returns PulsarBackend, MQTTBackend, etc.

# 2. Get schema class from the backend
# (Can be imported directly - backend-agnostic)
from trustgraph.schema.services.llm import TextCompletionRequest

# 3. Create a producer/publisher for a specific topic
producer = pubsub.create_producer(
    topic="text-completion-requests",
    schema=TextCompletionRequest  # Tells backend what schema to use
)

# 4. Create message instances (same API regardless of backend)
request = TextCompletionRequest(
    system="You are helpful",
    prompt="Hello world",
    streaming=False
)

# 5. Send the message
producer.send(request)  # Backend serializes appropriately

Tüketici Akışı

# 1. Get the configured backend
pubsub = get_pubsub()

# 2. Create a consumer
consumer = pubsub.subscribe(
    topic="text-completion-requests",
    schema=TextCompletionRequest  # Tells backend how to deserialize
)

# 3. Receive and deserialize
msg = consumer.receive()
request = msg.value()  # Returns TextCompletionRequest dataclass instance

# 4. Use the data (type-safe access)
print(request.system)   # "You are helpful"
print(request.prompt)   # "Hello world"
print(request.streaming)  # False

Sahne Arkasındaki Olaylar

Pulsar arka ucu için: create_producer() → JSON şeması veya dinamik olarak oluşturulmuş bir kayıtla Pulsar üreticisi oluşturur. send(request) → veri sınıfını JSON/Pulsar formatına serileştirir ve Pulsar'a gönderir. receive() → Pulsar mesajını alır, veri sınıfına geri serileştirir.

MQTT arka ucu için: create_producer() → MQTT aracısına bağlanır, şema kaydı gerektirmez. send(request) → veri sınıfını JSON'a dönüştürür ve MQTT konusuna yayınlar. receive() → MQTT konusuna abone olur, JSON'ı veri sınıfına geri serileştirir.

Kafka arka ucu için: create_producer() → Kafka üreticisini oluşturur, gerekirse Avro şemasını kaydeder. send(request) → veri sınıfını Avro formatına serileştirir ve Kafka'ya gönderir. receive() → Kafka mesajını alır, Avro'yu veri sınıfına geri serileştirir.

Temel Tasarım Noktaları

  1. Şema nesnesi oluşturma: Veri sınıfı örneği (TextCompletionRequest(...)), arka uçtan bağımsız olarak aynıdır.
  2. Arka uç, kodlamayı yönetir: Her arka uç, veri sınıfını kablo formatına nasıl serileştireceğini bilir.
  3. Oluşturma sırasında şema tanımı: Üretici/tüketici oluştururken, şema türünü belirtirsiniz.
  4. Tür güvenliği korunur: Doğru bir TextCompletionRequest nesnesi alırsınız, bir sözlük değil.
  5. Arka uç sızıntısı yok: Uygulama kodu, arka uçla ilgili özel kütüphaneleri asla içe aktarmaz.

Örnek Dönüşüm

Mevcut (Pulsar'a özel):

# schema/services/llm.py
from pulsar.schema import Record, String, Boolean, Integer

class TextCompletionRequest(Record):
    system = String()
    prompt = String()
    streaming = Boolean()

Yeni (Arka uçtan bağımsız):

# schema/services/llm.py
from dataclasses import dataclass

@dataclass
class TextCompletionRequest:
    system: str
    prompt: str
    streaming: bool = False

Arka Uç Entegrasyonu

Her arka uç, veri sınıflarının serileştirme/deserileştirme işlemlerini gerçekleştirir:

Pulsar arka ucu: Veri sınıflarından dinamik olarak pulsar.schema.Record sınıfları oluşturun Veya veri sınıflarını JSON'a serileştirin ve Pulsar'ın JSON şemasını kullanın Mevcut Pulsar kurulumlarıyla uyumluluğu korur

MQTT/Redis arka ucu: Veri sınıfı örneklerinin doğrudan JSON serileştirilmesi dataclasses.asdict() / from_dict() kullanın Hafif, şema kayıt defterine gerek yok

Kafka arka ucu: Veri sınıfı tanımlarından Avro şemaları oluşturun Confluent'ın şema kayıt defterini kullanın Şema evrimi desteğiyle tür güvenli serileştirme

Mimari

┌─────────────────────────────────────┐
│   Application Code                  │
│   - Uses dataclass schemas          │
│   - Backend-agnostic                │
└──────────────┬──────────────────────┘
               │
┌──────────────┴──────────────────────┐
│   PubSubFactory (configurable)      │
│   - get_pubsub() returns backend    │
└──────────────┬──────────────────────┘
               │
        ┌──────┴──────┐
        │             │
┌───────▼─────────┐  ┌────▼──────────────┐
│ PulsarBackend   │  │ MQTTBackend       │
│ - JSON schema   │  │ - JSON serialize  │
│ - or dynamic    │  │ - Simple queues   │
│   Record gen    │  │                   │
└─────────────────┘  └───────────────────┘

Uygulama Detayları

1. Şema tanımları: Basit dataclass'lar ve tür ipuçları str, int, bool, float temel veri tipleri için list[T] diziler için dict[str, T] haritalar için Karmaşık tipler için iç içe dataclass'lar

2. Her arka uç şunları sağlar: Serileştirici: dataclass → bytes/wire format Seri dışa aktarıcı: bytes/wire format → dataclass Şema kaydı (gerekirse, örneğin Pulsar/Kafka)

3. Tüketici/Üretici soyutlaması: Zaten mevcut (consumer.py, producer.py) Arka uç tarafından sağlanan seri dışa aktarmayı kullanmak için güncellenmeli Doğrudan Pulsar içe aktarmalarını kaldırmalı

4. Tür eşlemeleri: Pulsar String() → Python str Pulsar Integer() → Python int Pulsar Boolean() → Python bool Pulsar Array(T) → Python list[T] Pulsar Map(K, V) → Python dict[K, V] Pulsar Double() → Python float Pulsar Bytes() → Python bytes

Geçiş Yolu

  1. trustgraph/schema/ içindeki tüm şemaların dataclass versiyonlarını oluşturun
  2. Arka uç tarafından sağlanan seri dışa aktarmayı kullanmak için arka uç sınıflarını (Tüketici, Üretici, Yayıncı, Abonelik) güncelleyin
  3. JSON şemasını veya dinamik Kayıt oluşturmayı kullanarak PulsarBackend'i uygulayın
  4. Mevcut dağıtımlarla geriye dönük uyumluluğu sağlamak için Pulsar ile test edin
  5. Gerekirse yeni arka uçlar ekleyin (MQTT, Kafka, Redis, vb.)
  6. Şema dosyalarından Pulsar içe aktarmalarını kaldırın

Avantajlar

Şema tanımlarında herhangi bir yayın/abonelik bağımlılığı yok Standart Python - anlaşılması, tür denetimi yapılması ve belgelenmesi kolay Modern araçlar - mypy, IDE otomatik tamamlama, lint araçlarıyla çalışır Arka uç odaklı - her arka uç yerel seri dışa aktarmayı kullanır Çeviri ek yükü yok - doğrudan seri dışa aktarma, adaptörler yok Tür güvenliği - uygun türlere sahip gerçek nesneler Kolay doğrulama - gerekirse Pydantic kullanılabilir

Zorluklar & Çözümler

Zorluk: Pulsar'ın Record'ı çalışma zamanı alan doğrulamasına sahiptir Çözüm: Gerekirse doğrulama için Pydantic dataclass'larını kullanın veya __post_init__ ile Python 3.10+ dataclass özelliklerini kullanın

Zorluk: Bazı Pulsar'a özgü özellikler (örneğin Bytes türü) Çözüm: Bu türü dataclass'ta bytes türüne eşleyin, arka uç uygun şekilde kodlamayı işler

Zorluk: Konu adlandırması (persistent://tenant/namespace/topic) Çözüm: Şema tanımlarında konu adlarını soyutlayın, arka uç uygun formata dönüştürür

Zorluk: Şema evrimi ve sürüm oluşturma Çözüm: Her arka uç, yeteneklerine göre bunu işler (Pulsar şema sürümleri, Kafka şema kaydı, vb.)

Zorluk: İç içe karmaşık türler Çözüm: İç içe dataclass'ları kullanın, arka uçlar yinelemeli olarak seri dışa aktarır/serileştirir

Tasarım Kararları

  1. Basit dataclass'lar mı yoksa Pydantic mi? Karar: Basit Python dataclass'ları kullanın Daha basit, ek bağımlılık yok Uygulamada doğrulama gerekli değil Anlaşılması ve bakımı daha kolay

  2. Şema evrimi: Karar: Herhangi bir sürüm oluşturma mekanizmasına gerek yok Şemalar kararlı ve uzun ömürlü Güncellemeler genellikle yeni alanlar ekler (geriye dönük uyumlu) Arka uçlar, yeteneklerine göre şema evrimini işler

  3. Geriye dönük uyumluluk: Karar: Ana sürüm değişikliği, geriye dönük uyumluluk gerekli değil Bu, bir kopma değişikliği olacak ve geçiş talimatları sağlanacak Temiz bir kopma, daha iyi bir tasarım sağlar Mevcut dağıtımlar için bir geçiş kılavuzu sağlanacaktır

  4. İç içe türler ve karmaşık yapılar: Karar: İç içe dataclass'ları doğal olarak kullanın Python dataclass'ları iç içe geçmeyi mükemmel şekilde işler Diziler için list[T], haritalar için dict[K, V] Arka uçlar yinelemeli olarak seri dışa aktarır/serileştirir Örnek:

    @dataclass
    class Value:
        value: str
        is_uri: bool
    
    @dataclass
    class Triple:
        s: Value              # Nested dataclass
        p: Value
        o: Value
    
    @dataclass
    class GraphQuery:
        triples: list[Triple]  # Array of nested dataclasses
        metadata: dict[str, str]
    
  5. Varsayılan değerler ve isteğe bağlı alanlar: Karar: Gerekli, varsayılan ve isteğe bağlı alanların birleşimi Gerekli alanlar: Herhangi bir varsayılan değer yok Varsayılan değerlere sahip alanlar: Her zaman mevcut, anlamlı varsayılan değerlere sahip Tamamen isteğe bağlı alanlar: T | None = None, None olduğunda serileştirmeden çıkarılır Örnek:

    @dataclass
    class TextCompletionRequest:
        system: str              # Required, no default
        prompt: str              # Required, no default
        streaming: bool = False  # Optional with default value
        metadata: dict | None = None  # Truly optional, can be absent
    

    Önemli serileştirme kuralları:

    metadata = None olduğunda:

    {
        "system": "...",
        "prompt": "...",
        "streaming": false
        // metadata field NOT PRESENT
    }
    

    metadata = {} (açıkça boş) olduğunda:

    {
        "system": "...",
        "prompt": "...",
        "streaming": false,
        "metadata": {}  // Field PRESENT but empty
    }
    

    Temel ayrım: None → JSON'da bulunmayan alan (serileştirilmiyor) Boş değer ({}, [], "") → alan, boş bir değerle mevcut Bu, anlamsal olarak önemlidir: "sağlanmadı" ile "açıkça boş" Serileştirme arka uçları, None alanlarını atlamalı, bunları null olarak kodlamamalıdır.

Yaklaşım Taslağı 3: Uygulama Detayları

Genel Kuyruk Adlandırma Formatı

Arka uçlara özgü kuyruk adlarını, arka uçların uygun şekilde eşleyebileceği genel bir formata dönüştürün.

Format: {qos}/{tenant}/{namespace}/{queue-name}

Nerede: qos: Hizmet kalitesi seviyesi q0 = en iyi çaba (ateşle ve unut, onay yok) q1 = en az bir kez (onay gerektirir) q2 = tam olarak bir kez (iki aşamalı onay) tenant: Çok kiracılılık için mantıksal gruplandırma namespace: Kiracı içindeki alt gruplandırma queue-name: Gerçek kuyruk/konu adı

Örnekler:

q1/tg/flow/text-completion-requests
q2/tg/config/config-push
q0/tg/metrics/stats

Arka Uç Konu Eşlemesi

Her arka uç, genel formatı kendi yerel formatına eşler:

Pulsar Arka Ucu:

def map_topic(self, generic_topic: str) -> str:
    # Parse: q1/tg/flow/text-completion-requests
    qos, tenant, namespace, queue = generic_topic.split('/', 3)

    # Map QoS to persistence
    persistence = 'persistent' if qos in ['q1', 'q2'] else 'non-persistent'

    # Return Pulsar URI: persistent://tg/flow/text-completion-requests
    return f"{persistence}://{tenant}/{namespace}/{queue}"

MQTT Altyapısı:

def map_topic(self, generic_topic: str) -> tuple[str, int]:
    # Parse: q1/tg/flow/text-completion-requests
    qos, tenant, namespace, queue = generic_topic.split('/', 3)

    # Map QoS level
    qos_level = {'q0': 0, 'q1': 1, 'q2': 2}[qos]

    # Build MQTT topic including tenant/namespace for proper namespacing
    mqtt_topic = f"{tenant}/{namespace}/{queue}"

    return mqtt_topic, qos_level

Güncellenmiş Konu Yardımcı Fonksiyonu

# schema/core/topic.py
def topic(queue_name, qos='q1', tenant='tg', namespace='flow'):
    """
    Create a generic topic identifier that can be mapped by backends.

    Args:
        queue_name: The queue/topic name
        qos: Quality of service
             - 'q0' = best-effort (no ack)
             - 'q1' = at-least-once (ack required)
             - 'q2' = exactly-once (two-phase ack)
        tenant: Tenant identifier for multi-tenancy
        namespace: Namespace within tenant

    Returns:
        Generic topic string: qos/tenant/namespace/queue_name

    Examples:
        topic('my-queue')  # q1/tg/flow/my-queue
        topic('config', qos='q2', namespace='config')  # q2/tg/config/config
    """
    return f"{qos}/{tenant}/{namespace}/{queue_name}"

Yapılandırma ve Başlatma

Komut Satırı Argümanları + Ortam Değişkenleri:

# In base/async_processor.py - add_args() method
@staticmethod
def add_args(parser):
    # Pub/sub backend selection
    parser.add_argument(
        '--pubsub-backend',
        default=os.getenv('PUBSUB_BACKEND', 'pulsar'),
        choices=['pulsar', 'mqtt'],
        help='Pub/sub backend (default: pulsar, env: PUBSUB_BACKEND)'
    )

    # Pulsar-specific configuration
    parser.add_argument(
        '--pulsar-host',
        default=os.getenv('PULSAR_HOST', 'pulsar://localhost:6650'),
        help='Pulsar host (default: pulsar://localhost:6650, env: PULSAR_HOST)'
    )

    parser.add_argument(
        '--pulsar-api-key',
        default=os.getenv('PULSAR_API_KEY', None),
        help='Pulsar API key (env: PULSAR_API_KEY)'
    )

    parser.add_argument(
        '--pulsar-listener',
        default=os.getenv('PULSAR_LISTENER', None),
        help='Pulsar listener name (env: PULSAR_LISTENER)'
    )

    # MQTT-specific configuration
    parser.add_argument(
        '--mqtt-host',
        default=os.getenv('MQTT_HOST', 'localhost'),
        help='MQTT broker host (default: localhost, env: MQTT_HOST)'
    )

    parser.add_argument(
        '--mqtt-port',
        type=int,
        default=int(os.getenv('MQTT_PORT', '1883')),
        help='MQTT broker port (default: 1883, env: MQTT_PORT)'
    )

    parser.add_argument(
        '--mqtt-username',
        default=os.getenv('MQTT_USERNAME', None),
        help='MQTT username (env: MQTT_USERNAME)'
    )

    parser.add_argument(
        '--mqtt-password',
        default=os.getenv('MQTT_PASSWORD', None),
        help='MQTT password (env: MQTT_PASSWORD)'
    )

Fabrika Fonksiyonu:

# In base/pubsub.py or base/pubsub_factory.py
def get_pubsub(**config) -> PubSubBackend:
    """
    Create and return a pub/sub backend based on configuration.

    Args:
        config: Configuration dict from command-line args
                Must include 'pubsub_backend' key

    Returns:
        Backend instance (PulsarBackend, MQTTBackend, etc.)
    """
    backend_type = config.get('pubsub_backend', 'pulsar')

    if backend_type == 'pulsar':
        return PulsarBackend(
            host=config.get('pulsar_host'),
            api_key=config.get('pulsar_api_key'),
            listener=config.get('pulsar_listener'),
        )
    elif backend_type == 'mqtt':
        return MQTTBackend(
            host=config.get('mqtt_host'),
            port=config.get('mqtt_port'),
            username=config.get('mqtt_username'),
            password=config.get('mqtt_password'),
        )
    else:
        raise ValueError(f"Unknown pub/sub backend: {backend_type}")

AsyncProcessor'da Kullanım:

# In async_processor.py
class AsyncProcessor:
    def __init__(self, **params):
        self.id = params.get("id")

        # Create backend from config (replaces PulsarClient)
        self.pubsub = get_pubsub(**params)

        # Rest of initialization...

Arka Uç Arayüzü

class PubSubBackend(Protocol):
    """Protocol defining the interface all pub/sub backends must implement."""

    def create_producer(self, topic: str, schema: type, **options) -> BackendProducer:
        """
        Create a producer for a topic.

        Args:
            topic: Generic topic format (qos/tenant/namespace/queue)
            schema: Dataclass type for messages
            options: Backend-specific options (e.g., chunking_enabled)

        Returns:
            Backend-specific producer instance
        """
        ...

    def create_consumer(
        self,
        topic: str,
        subscription: str,
        schema: type,
        initial_position: str = 'latest',
        consumer_type: str = 'shared',
        **options
    ) -> BackendConsumer:
        """
        Create a consumer for a topic.

        Args:
            topic: Generic topic format (qos/tenant/namespace/queue)
            subscription: Subscription/consumer group name
            schema: Dataclass type for messages
            initial_position: 'earliest' or 'latest' (MQTT may ignore)
            consumer_type: 'shared', 'exclusive', 'failover' (MQTT may ignore)
            options: Backend-specific options

        Returns:
            Backend-specific consumer instance
        """
        ...

    def close(self) -> None:
        """Close the backend connection."""
        ...
class BackendProducer(Protocol):
    """Protocol for backend-specific producer."""

    def send(self, message: Any, properties: dict = {}) -> None:
        """Send a message (dataclass instance) with optional properties."""
        ...

    def flush(self) -> None:
        """Flush any buffered messages."""
        ...

    def close(self) -> None:
        """Close the producer."""
        ...
class BackendConsumer(Protocol):
    """Protocol for backend-specific consumer."""

    def receive(self, timeout_millis: int = 2000) -> Message:
        """
        Receive a message from the topic.

        Raises:
            TimeoutError: If no message received within timeout
        """
        ...

    def acknowledge(self, message: Message) -> None:
        """Acknowledge successful processing of a message."""
        ...

    def negative_acknowledge(self, message: Message) -> None:
        """Negative acknowledge - triggers redelivery."""
        ...

    def unsubscribe(self) -> None:
        """Unsubscribe from the topic."""
        ...

    def close(self) -> None:
        """Close the consumer."""
        ...
class Message(Protocol):
    """Protocol for a received message."""

    def value(self) -> Any:
        """Get the deserialized message (dataclass instance)."""
        ...

    def properties(self) -> dict:
        """Get message properties/metadata."""
        ...

Mevcut Sınıfların Yeniden Düzenlenmesi

Mevcut Consumer, Producer, Publisher, Subscriber sınıfları büyük ölçüde aynı kalacaktır:

Mevcut sorumluluklar (saklanacak): Asenkron iş parçacığı modeli ve görev grupları Yeniden bağlantı mantığı ve tekrar deneme işleme Ölçüm toplama Hız sınırlama Eşzamanlılık yönetimi

Gerekli değişiklikler: Doğrudan Pulsar içe aktarmalarını kaldırın (pulsar.schema, pulsar.InitialPosition, vb.) BackendProducer/BackendConsumer'i Pulsar istemcisi yerine kullanın Gerçek yayın/abonelik işlemlerini arka uç örneklerine devredin Genel kavramları arka uç çağrılarına eşleyin

Örnek yeniden düzenleme:

# OLD - consumer.py
class Consumer:
    def __init__(self, client, topic, subscriber, schema, ...):
        self.client = client  # Direct Pulsar client
        # ...

    async def consumer_run(self):
        # Uses pulsar.InitialPosition, pulsar.ConsumerType
        self.consumer = self.client.subscribe(
            topic=self.topic,
            schema=JsonSchema(self.schema),
            initial_position=pulsar.InitialPosition.Earliest,
            consumer_type=pulsar.ConsumerType.Shared,
        )

# NEW - consumer.py
class Consumer:
    def __init__(self, backend_consumer, schema, ...):
        self.backend_consumer = backend_consumer  # Backend-specific consumer
        self.schema = schema
        # ...

    async def consumer_run(self):
        # Backend consumer already created with right settings
        # Just use it directly
        while self.running:
            msg = await asyncio.to_thread(
                self.backend_consumer.receive,
                timeout_millis=2000
            )
            await self.handle_message(msg)

Arka Uç Özel Davranışlar

Pulsar Arka Uç: q0'ı non-persistent://'e, q1/q2persistent://'e eşler. Tüm tüketici türlerini (paylaşımlı, özel, yedekli) destekler. Başlangıç konumunu (en erken/en son) destekler. Yerel mesaj onayı. Şema kayıt defteri desteği.

MQTT Arka Uç: q0/q1/q2'yi MQTT QoS seviyeleri 0/1/2'ye eşler. Adlandırma için konu yoluna kiracı/isim alanı ekler. Abonelik adlarından otomatik olarak istemci kimlikleri oluşturur. Başlangıç konumunu yoksayar (temel MQTT'de mesaj geçmişi yoktur). Tüketici türünü yoksayar (MQTT, tüketici grupları yerine istemci kimlikleri kullanır). Basit yayın/abone modeli.

Tasarım Kararları Özeti

  1. Genel kuyruk adlandırması: qos/tenant/namespace/queue-name formatı
  2. Kuyruk kimliğindeki QoS: Kuyruk tanımı tarafından belirlenir, yapılandırma ile değil.
  3. Yeniden bağlanma: Tüketici/Üretici sınıfları tarafından işlenir, arka uçlar tarafından değil.
  4. MQTT konuları: Doğru adlandırma için kiracı/isim alanı içerir.
  5. Mesaj geçmişi: MQTT, initial_position parametresini yoksayar (gelecek geliştirmeler).
  6. İstemci kimlikleri: MQTT arka ucu, abonelik adından otomatik olarak oluşturur.

Gelecek Geliştirmeler

MQTT mesaj geçmişi: İsteğe bağlı bir kalıcılık katmanı eklenebilir (örneğin, saklanan mesajlar, harici depolama). initial_position='earliest''ı desteklemeyi mümkün kılacaktır. Başlangıç uygulamasında gerekli değildir.