AI Genel Çalışma Rehberi

Blog / Rehber

Multi-Agent Sistemlerde İletişim: Kapsamlı Bir Teknik Rehber

Son güncelleme: Ocak 2026

Bir şirketin müşteri hizmetleri departmanını düşünün - telefona bakan biri var, mail'lere cevap veren başka biri, teknik destek için ayrı bir ekip... Hepsi aynı müşteriye hizmet etmek için sürekli birbirleriyle iletişim halinde olmalı. İşte multi-agent sistemler de tam olarak böyle çalışıyor.

Bu yazıda, agent'ların birbirleriyle nasıl haberleştiğini, hangi araçların ne zaman kullanıldığını ve gerçek dünya projelerinde bu teknolojilerin nasıl bir araya geldiğini inceleyeceğiz. Teorik bilgiler kadar pratik kod örnekleri de paylaşacağız.


Hızlı Bakış

Ne anlatıyoruz: Multi-agent sistemlerde agent'ların birbirleriyle nasıl iletişim kurduğu

Hangi araçlar: NATS, Redis, RabbitMQ, Kafka - her birinin güçlü ve zayıf yönleri

Mimari pattern'ler: Pub/Sub, Request-Reply, Event Sourcing, Orchestrator

Pratik içerik: Gerçek kod örnekleri, karar rehberi, production tavsiyeleri

Hibrit yaklaşım: MessageBus + A2A Protocol'ü birlikte kullanmak


Neden Agent İletişimi Bu Kadar Önemli?

Düşünsenize - bir restoran mutfağındasınız. Garson siparişi alıyor, kasaya yazıyor, şef görüyor, malzemeciyi çağırıyor, aşçılar hazırlıyor, sonra garson yemeği götürüyor. Bu zincirdeki herhangi bir kopukluk felaket demek.

Multi-agent sistemler de aynı mantıkla çalışıyor. Ses tanıma yapan bir agent, anlam çıkaran başka bir agent, aksiyon alan üçüncü bir agent... Hepsinin senkronize çalışması gerekiyor. Peki bu koordinasyon nasıl sağlanıyor?


Temel Kavramlar

Senkron vs Asenkron İletişim

İki temel iletişim modeli var:

Senkron (Telefon görüşmesi gibi):

Asenkron (E-posta gibi):

flowchart LR subgraph sync ["📞 Senkron İletişim"] A1["Agent A"] -->|"Request"| B1["Agent B"] B1 -->|"Response"| A1 end subgraph async ["📧 Asenkron İletişim"] A2["Agent A"] -->|"Publish"| BUS["Message Bus"] BUS -->|"Deliver"| B2["Agent B"] B2 -->|"Event"| BUS BUS -->|"Notify"| A2 end

Temel Terimler

Konuya girmeden önce bazı terimleri netleştirelim:

Producer/Publisher: Mesaj üreten taraf - örneğin SpeechAgent transkript yayınlar

Consumer/Subscriber: Mesaj tüketen taraf - NLUAgent o transkripti dinler

Topic/Channel: Mesajların kategorisi - speech.transcribed, intent.detected gibi

Queue: Mesajların sıralı deposu

Broker: Mesajları yöneten ara katman - RabbitMQ, Kafka gibi

Event: Gerçekleşmiş bir olayın bildirimi - "Kullanıcı konuşmayı bitirdi"

Command: Bir işlem yapılması talebi - "Müzik çal"

Event vs Command Farkı

Bu ayrım çok kritik:

Event (Olay):

Command (Komut):


İletişim Modelleri

Pub/Sub Pattern

En yaygın model bu. Bir publisher mesaj yayınlıyor, ilgilenen tüm subscriber'lar alıyor.

flowchart TB PUB["🎤 SpeechAgent<br/>(Publisher)"] BUS["📬 MESSAGE BUS<br/>Topic: speech.transcribed"] SUB1["🧠 NLUAgent"] SUB2["📝 LogAgent"] SUB3["📊 AnalyticsAgent"] PUB -->|"publish"| BUS BUS -->|"subscribe"| SUB1 BUS -->|"subscribe"| SUB2 BUS -->|"subscribe"| SUB3

Avantajları:

Request-Reply Pattern

Senkron iletişim gerektiğinde kullanılıyor. NATS bu pattern'i native olarak destekliyor.

Nasıl çalışıyor:

Work Queue Pattern

Birden fazla worker aynı queue'dan iş çekiyor. Otomatik load balancing sağlıyor.

Senaryo: 1000 tane transkripsiyon görevi var, 3 worker işliyor. Her task sadece bir worker'a gidiyor.

Event Streaming

Kafka'nın uzmanlık alanı. Event'ler kalıcı olarak saklanıyor, istediğin zaman replay edebiliyorsun.

Ne zaman lazım:


MessageBus Nedir?

MessageBus, agent'lar arasında mesaj iletimini sağlayan ara katman. Bir "posta sistemi" gibi düşünebilirsin.

Temel görevleri:

In-Process vs Distributed

In-Process (Tek process içinde):

Distributed (Birden fazla process/makine):


Popüler Araçlar Karşılaştırması

Şimdi gelelim asıl konuya - hangi araç ne zaman kullanılmalı?

NATS: Hız ve Basitlik Şampiyonu

Öne çıkan özellikleri:

Ne zaman kullan:

Ne zaman kullanma:

Performans: 10M+ msg/sec, <1ms latency (99th percentile)

Kullanan şirketler: Tinder, Stripe, Rakuten, Ericsson, VMware

Redis Streams: Zaten Redis Kullanıyorsan

Öne çıkan özellikleri:

Ne zaman kullan:

Ne zaman kullanma:

İpucu: Redis Pub/Sub vs Redis Streams farkı önemli. Pub/Sub fire-and-forget, Streams'de persistence var.

RabbitMQ: Kurumsal Message Queue

Öne çıkan özellikleri:

Ne zaman kullan:

Exchange tipleri:

Apache Kafka: Enterprise Event Backbone

Öne çıkan özellikleri:

Ne zaman kullan:

Ne zaman kullanma:

Kullanan şirketler: LinkedIn, Netflix, Uber, Airbnb, Spotify

Karşılaştırma Özeti

Latency açısından:

Throughput açısından:

Kurulum zorluğu:


Mimari Pattern'ler

Pattern 1: Simple Pub/Sub

En basit pattern. Küçük sistemler için ideal.

flowchart TB A["🎤 Agent A<br/>(Speech)"] B["🧠 Agent B<br/>(NLU)"] C["🎯 Agent C<br/>(Action)"] BUS["📬 MESSAGE BUS"] A -->|"publish"| BUS B -->|"subscribe"| BUS C -->|"subscribe"| BUS

Uygun: 2-5 agent, prototip aşaması

Araç önerisi: Node.js EventEmitter veya NATS

Pattern 2: Orchestrator Pattern

Merkezi bir koordinatör agent var. Orta ölçekli sistemler için.

flowchart TB ORCH["🎯 ORCHESTRATOR<br/>• Session mgmt<br/>• Agent registry<br/>• Flow control"] A["🎤 SpeechAgent"] B["🧠 NLUAgent"] C["🎵 ActionAgent"] BUS["📬 MESSAGE BUS"] ORCH --> A ORCH --> B ORCH --> C A --> BUS B --> BUS C --> BUS

Akış:

  1. Event gelir → Orchestrator alır
  2. Orchestrator hangi agent'ı çağıracağına karar verir
  3. İlgili agent'a task gönderir
  4. Sonuç Orchestrator'a döner

Uygun: 5-15 agent, production

Araç önerisi: NATS + Redis (state için)

Pattern 3: Choreography (Event-Driven)

Merkezi orchestrator yok. Agent'lar event'lere tepki veriyor.

flowchart LR SPEECH["🎤 Speech"] NLU["🧠 NLU"] LOG["📝 Logging"] SPOT["🎵 Spotify"] ANAL["📊 Analytics"] TTS["🔊 TTS"] SPEECH -->|"speech.transcribed"| NLU NLU -->|"intent.detected"| LOG NLU -->|"intent.detected"| SPOT NLU -->|"intent.detected"| ANAL SPOT -->|"music.playing"| TTS

Avantaj: Decoupled, her agent bağımsız scale edilebilir

Dezavantaj: Akışı takip etmek zor, debugging zor

Araç önerisi: Kafka (event sourcing ile)

Pattern 4: Event Sourcing + CQRS

Enterprise grade. Tüm event'ler kalıcı log'da saklanıyor.

Write Side: Agent → Command → Validate → Event Store (Kafka)

Read Side: Event → Projector → Read Model (Redis) → Query

Özellikler:


Hibrit Mimariler

Gerçek dünyada genellikle tek bir araç yetmiyor. Farklı ihtiyaçlar için farklı araçları kombine etmek gerekiyor.

Neden hibrit?

Farklı ihtiyaçlar, farklı araçlar gerektirir:

Tek araç kullanmak = Her şey için çekiç kullanmak

Hibrit Örnek: MessageBus + A2A Protocol

Bu pattern'in güzel bir uygulaması için Spotify A2A Buddy projesine bakabilirsiniz. 5 agent'ın hem MessageBus (dahili iletişim) hem A2A Protocol (harici API) kullanarak nasıl koordineli çalıştığı detaylı olarak anlatılmış.

flowchart LR subgraph external ["🌐 EXTERNAL"] EXT["3rd Party<br/>Systems"] BROWSER["Browser<br/>Client"] end subgraph internal ["🏠 INTERNAL CLUSTER"] A["Agent A"] B["Agent B"] C["Agent C"] D["Agent D"] BUS["NATS<br/>MessageBus"] A <--> BUS B <--> BUS C <--> BUS D <--> BUS end EXT <-->|"A2A Protocol<br/>REST/JSON-RPC"| internal BROWSER <-->|"WebSocket"| internal

Ne zaman hangisi?

MessageBus (NATS/Redis):

A2A Protocol:


Kod Örnekleri

NATS ile Agent İletişimi (Node.js)

// nats-agent-bus.js
const { connect, StringCodec } = require('nats');
 
class AgentBus {
  constructor() {
    this.nc = null;
    this.sc = StringCodec();
  }
 
  async connect(servers = 'nats://localhost:4222') {
    this.nc = await connect({ servers });
    console.log('[BUS] Connected to NATS');
    return this;
  }
 
  // Event yayınla (fire-and-forget)
  publish(topic, data) {
    const payload = JSON.stringify({
      ...data,
      timestamp: Date.now()
    });
    this.nc.publish(topic, this.sc.encode(payload));
    console.log(`[BUS] Published to ${topic}`);
  }
 
  // Event dinle
  subscribe(topic, handler) {
    const sub = this.nc.subscribe(topic);
    console.log(`[BUS] Subscribed to ${topic}`);
    
    (async () => {
      for await (const msg of sub) {
        try {
          const data = JSON.parse(this.sc.decode(msg.data));
          await handler(data);
        } catch (err) {
          console.error(`[BUS] Error processing ${topic}:`, err);
        }
      }
    })();
    
    return sub;
  }
 
  // Request-Reply pattern
  async request(topic, data, timeout = 5000) {
    const payload = this.sc.encode(JSON.stringify(data));
    const response = await this.nc.request(topic, payload, { timeout });
    return JSON.parse(this.sc.decode(response.data));
  }
 
  async close() {
    await this.nc.close();
  }
}
 
module.exports = { AgentBus };

SpeechAgent Örneği

// speech-agent.js
const { AgentBus } = require('./nats-agent-bus');
 
class SpeechAgent {
  constructor(bus) {
    this.bus = bus;
    this.name = 'SpeechAgent';
  }
 
  async initialize() {
    // Ses kaydı tamamlandığında
    this.bus.subscribe('audio.recorded', async (data) => {
      console.log(`[${this.name}] Processing audio...`);
      
      // Whisper API ile transcribe
      const transcription = await this.transcribe(data.audioBuffer);
      
      // Sonucu yayınla
      this.bus.publish('speech.transcribed', {
        text: transcription,
        sessionId: data.sessionId,
        confidence: 0.95
      });
    });
 
    // Request-Reply: Doğrudan transcription isteği
    this.bus.reply('speech.transcribe', async (data) => {
      const transcription = await this.transcribe(data.audioBuffer);
      return { text: transcription, confidence: 0.95 };
    });
 
    console.log(`[${this.name}] Initialized`);
  }
 
  async transcribe(audioBuffer) {
    // OpenAI Whisper API çağrısı
    return "Play some jazz music";
  }
}
 
// Kullanım
async function main() {
  const bus = await new AgentBus().connect();
  const speechAgent = new SpeechAgent(bus);
  await speechAgent.initialize();
}
 
main().catch(console.error);

Redis Streams ile Event Bus (Python)

# redis_agent_bus.py
import redis
import json
import asyncio
from datetime import datetime
from typing import Callable, Dict, Any
 
class RedisAgentBus:
    def __init__(self, host='localhost', port=6379):
        self.redis = redis.Redis(host=host, port=port, decode_responses=True)
        self.consumer_group = 'agent-group'
        self.consumer_name = f'consumer-{datetime.now().timestamp()}'
        self.handlers: Dict[str, Callable] = {}
    
    def publish(self, stream: str, data: Dict[str, Any]):
        """Event yayınla"""
        message = {
            'data': json.dumps(data),
            'timestamp': str(datetime.now().isoformat())
        }
        message_id = self.redis.xadd(stream, message)
        print(f'[BUS] Published to {stream}: {message_id}')
        return message_id
    
    def create_consumer_group(self, stream: str):
        """Consumer group oluştur"""
        try:
            self.redis.xgroup_create(stream, self.consumer_group, id='0', mkstream=True)
            print(f'[BUS] Created consumer group for {stream}')
        except redis.ResponseError as e:
            if 'BUSYGROUP' not in str(e):
                raise
    
    def subscribe(self, stream: str, handler: Callable):
        """Stream'e subscribe ol"""
        self.create_consumer_group(stream)
        self.handlers[stream] = handler
        print(f'[BUS] Subscribed to {stream}')
    
    async def process_messages(self):
        """Mesajları işle (consumer loop)"""
        streams = {stream: '>' for stream in self.handlers.keys()}
        
        while True:
            try:
                messages = self.redis.xreadgroup(
                    self.consumer_group,
                    self.consumer_name,
                    streams,
                    count=10,
                    block=1000
                )
                
                for stream, stream_messages in messages:
                    handler = self.handlers.get(stream)
                    if handler:
                        for message_id, message_data in stream_messages:
                            data = json.loads(message_data['data'])
                            await handler(data)
                            self.redis.xack(stream, self.consumer_group, message_id)
                            
            except Exception as e:
                print(f'[BUS] Error: {e}')
                await asyncio.sleep(1)

Docker Compose - Tüm Sistemin Kurulumu

# docker-compose.yml
version: '3.8'
 
services:
  # NATS - Real-time messaging
  nats:
    image: nats:2.10-alpine
    ports:
      - "4222:4222"   # Client connections
      - "8222:8222"   # HTTP monitoring
    command: 
      - "--jetstream"
      - "--http_port=8222"
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost:8222/healthz"]
      interval: 5s
      timeout: 3s
 
  # Redis - State & Cache
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    command: redis-server --appendonly yes
    volumes:
      - redis_data:/data
 
  # Kafka - Event Sourcing (Opsiyonel)
  kafka:
    image: confluentinc/cp-kafka:7.5.0
    depends_on:
      - zookeeper
    ports:
      - "9092:9092"
    profiles:
      - kafka
 
volumes:
  redis_data:
 
# Kullanım:
# Sadece NATS + Redis: docker-compose up
# Kafka dahil: docker-compose --profile kafka up

Araç Seçim Rehberi

Karar Ağacı

Sisteminizin büyüklüğüne göre:

Küçük (2-5 agent, prototip):

Orta (5-20 agent, production):

Büyük (20+ agent, enterprise):

Senaryoya Göre Öneriler

Hackathon / PoC: Redis Pub/Sub - Hızlı kurulum, zaten biliniyor

Startup MVP: NATS - Basit, hızlı, büyüyünce de yeterli

Real-time chat agents: NATS - Sub-ms latency

E-commerce order processing: RabbitMQ - Reliable delivery, dead letter queues

Financial audit trail: Kafka - Event sourcing, replay, compliance

IoT sensor agents: NATS + Kafka - NATS real-time, Kafka storage

Başlangıç İçin Minimal Stack

Eğer yeni başlıyorsanız:

NATS (messaging) + Redis (state)

Bu kombinasyon %90 kullanım senaryosunu karşılar. Sonra ihtiyaca göre Kafka veya RabbitMQ ekleyebilirsiniz.


Production Tavsiyeleri

Monitoring Stack

Metrics: Prometheus + Grafana

Tracing: OpenTelemetry + Jaeger

Logging: ELK Stack veya Loki

Error Handling

Retry with Exponential Backoff: İlk denemede başarısız olunca, artan aralıklarla tekrar dene

Circuit Breaker: Bir servis sürekli fail ediyorsa, geçici olarak istekleri engelle

Dead Letter Queue: İşlenemeyen mesajları ayrı bir queue'ya at, sonra manuel incele

Güvenlik Checklist


Sonuç ve Öğrenme Yolu

Önemli Çıkarımlar

Tek araç yok: Farklı ihtiyaçlar farklı araçlar gerektirir. Hibrit mimariler gerçek dünyada yaygın.

Basit başla: NATS + Redis kombinasyonu çoğu senaryo için yeterli. Karmaşıklığı ihtiyaç oldukça ekleyin.

Event-driven düşün: Agent'lar arası iletişimde event-driven mimari, senkron RPC'den daha ölçeklenebilir.

Observability şart: Dağıtık sistemlerde monitoring olmadan debugging imkansız.

Failure'a hazır ol: Retry, circuit breaker, dead letter queue pattern'lerini baştan implement edin.

Öğrenme Yol Haritası

Hafta 1-2: Temeller

Hafta 3-4: İlk Multi-Agent Sistem

Hafta 5-6: Production Patterns

Hafta 7-8: İleri Konular


Kaynaklar

Resmi Dokümantasyonlar:

İlgili Makaleler:

Kitaplar: