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.
35 KiB
| 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ı
-
İstem oluşturma: Doğrudan:
gateway/service.pySoyutlanmış:async_processor.py→pubsub.py(PulsarClient) -
Mesaj taşıma: Tüketici:
consumer.py,consumer_spec.pyÜretici:producer.py,producer_spec.pyYayıncı:publisher.pyAbonelik:subscriber.py,subscriber_spec.py -
Şema sistemi: Temel türler:
schema/core/primitives.pyTüm hizmet şemaları:schema/services/*.pyKonu adlandırması:schema/core/topic.py -
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:
- Şema sisteminin yaygınlığı: Her mesaj tanımı
pulsar.schema.Recordve Pulsar alan türlerini kullanır. - Pulsar'a özgü numaralandırmalar:
InitialPosition,ConsumerType - Pulsar istisnaları:
_pulsar.Timeout,_pulsar.Interrupted,_pulsar.InvalidConfiguration,_pulsar.AlreadyClosed - Metot imzaları:
acknowledge(),negative_acknowledge(),subscribe(),create_producer(), vb. - Konu URI formatı: Pulsar'ın
kind://tenant/namespace/topicyapısı
Sonraki Adımlar
Yayın/abone altyapısını yapılandırılabilir hale getirmek için şunları yapmamız gerekiyor:
- İstem/şema sistemi için bir soyutlama arayüzü oluşturun.
- Pulsar'a özgü numaralandırmaları ve istisnaları soyutlayın.
- Şema sargıları veya alternatif şema tanımları oluşturun.
- Arayüzü hem Pulsar hem de alternatif sistemler (Kafka, RabbitMQ, Redis Streams, vb.) için uygulayın.
pubsub.py'ı yapılandırılabilir hale getirin ve çoklu arka uçları destekleyin.- 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ü:
- Pratik bir yaklaşımdır - mevcut kodla çalışır
- Minimum riskle kavramı kanıtlar
- Gerekirse daha sonra yerel bir şema sistemine dönüştürülebilir
- 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ı
- Şema nesnesi oluşturma: Veri sınıfı örneği (
TextCompletionRequest(...)), arka uçtan bağımsız olarak aynıdır. - Arka uç, kodlamayı yönetir: Her arka uç, veri sınıfını kablo formatına nasıl serileştireceğini bilir.
- Oluşturma sırasında şema tanımı: Üretici/tüketici oluştururken, şema türünü belirtirsiniz.
- Tür güvenliği korunur: Doğru bir
TextCompletionRequestnesnesi alırsınız, bir sözlük değil. - 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
trustgraph/schema/içindeki tüm şemaların dataclass versiyonlarını oluşturun- 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
- JSON şemasını veya dinamik Kayıt oluşturmayı kullanarak PulsarBackend'i uygulayın
- Mevcut dağıtımlarla geriye dönük uyumluluğu sağlamak için Pulsar ile test edin
- Gerekirse yeni arka uçlar ekleyin (MQTT, Kafka, Redis, vb.)
- Ş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ı
-
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
-
Ş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
-
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
-
İç 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çindict[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] -
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,Noneolduğ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 = Noneolduğ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ı,Nonealanlarını atlamalı, bunlarınullolarak 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/q2'ü persistent://'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
- ✅ Genel kuyruk adlandırması:
qos/tenant/namespace/queue-nameformatı - ✅ Kuyruk kimliğindeki QoS: Kuyruk tanımı tarafından belirlenir, yapılandırma ile değil.
- ✅ Yeniden bağlanma: Tüketici/Üretici sınıfları tarafından işlenir, arka uçlar tarafından değil.
- ✅ MQTT konuları: Doğru adlandırma için kiracı/isim alanı içerir.
- ✅ Mesaj geçmişi: MQTT,
initial_positionparametresini yoksayar (gelecek geliştirmeler). - ✅ İ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.