İçeriğe atla

AI Agent Mimari Desenleri: ReAct, Plan-and-Execute, Multi-Agent

Kural tabanlı chatbot'lardan otonom AI agent'larına mimari evrim: ReAct, Plan-and-Execute ve çoklu-agent desenleri TypeScript örnekleriyle anlatılıyor.

Ayhan Sipahi Ayhan Sipahi

Bir chatbot intent’i sınıflandırır ve scriptli bir flow’u tekrar oynatır. Bir agent ise reasoning loop çalıştırır, tool’ları runtime’da seçer ve task’lar arasında memory taşır. Aradaki fark sonradan takılabilen bir yetenek yükseltmesi değil; state, control flow ve hata yönetiminin tasarımını baştan değiştirir.

Başlangıç için makul varsayılan, hybrid memory’li tek bir ReAct agent. Multi-agent bir sistemin koordinasyon yükü olmadan dinamik tool kullanımını karşılar ve geriye debug edilecek tek bir control loop bırakır. Plan-and-Execute, görev temiz şekilde parçalanabiliyorsa ve task başına maliyet bağlayıcı kısıtsa yerini hak eder. Uzmanlaşmış birden fazla agent ise ancak domain’ler gerçekten ayrıysa ve yanlış cevabın bedeli koordinasyon katmanını ödeyecek kadar yüksekse anlamlı olur.

Mimari Evrim Spektrumu#

Hardcode edilmiş bir decision tree’den multi-agent sisteme giden yol altı seviyeden geçiyor:

Ekle

Ekle

Ekle

Ekle

Ekle

Kural Tabanli Chatbot

Intent Tabanli Chatbot

Context-Aware Asistan

Tool Kullanan Agent

Planning Agent

Multi-Agent System

NLU/ML

Session Memory

Dynamic Tools

Planning & LTM

Specialization

Level 0: Kural Tabanlı Chatbot’lar - Decision tree’ler ve regex pattern’ler. Tamamen deterministik. Örnek: “Saatler için 1, lokasyon için 2 yazın”

Level 1: Intent-Driven Chatbot’lar - Intent classification için NLU ile intent başına önceden tanımlanmış flow’lar. Örnek: Müşteri destek FAQ bot’ları

Level 2: Context-Aware Asistanlar - Session içinde conversation memory ile sınırlı API entegrasyonları. Örnek: Sesli asistanlar (Siri, Alexa)

Level 3: Tool Kullanan Agent’lar - Single-agent ReAct pattern’i ile dinamik tool seçimi. Örnek: Claude Code, GitHub Copilot

Level 4: Planning Agent’lar - Long-term memory ile çok adımlı task decomposition. Örnek: Research asistanları, kod generation agent’ları

Level 5: Multi-Agent Sistemler - Agent koordinasyon pattern’leriyle specialized sub-agent’lar. Örnek: Software development ekipleri, otonom operasyonlar

Geleneksel Chatbot Kısıtlamaları#

Klasik Destek Bot Senaryosu#

Bir destek chatbot’u şunu handle ediyor: “Neden iki kez ücretlendirildim?”

Chatbot’un yapması gerekenler:

  • Payment geçmişini kontrol et (Stripe API)
  • Order durumunu doğrula (database)
  • Destek ticket’larını gözden geçir (Zendesk)
  • Bilinen issue’ları kontrol et (Confluence)

Geleneksel yaklaşım: Tam sırayı hardcode et, ya da kullanıcıya birden fazla açıklayıcı soru sor.

Agent yaklaşımı: Tüm sistemlerden otonomca context topla, bulguları sentezle ve çözüm öner.

Entegrasyon Sayısının Katlanması#

Geleneksel chatbot’larla: 5 chatbot × 10 backend sistem = 50 hardcode edilmiş entegrasyon

Her yeni feature birden fazla chatbot flow’u güncellemeyi gerektirir. Chatbot’lar arası paylaşılan öğrenme yok. Sistemler evrim geçirdikçe maintenance giderek zorlaşır.

Temel Mimari Farklar#

Chatbot Mimarisi: Input → Intent Classification → Scriptli Response → Output

Agent Mimarisi: Input → Reasoning Loop (Observe → Plan → Act → Reflect) → Tool Execution → Memory Update → Output

Temel farklar:

  1. Memory Sistemleri: Long-term knowledge graph’lar vs. conversation buffer’ları
  2. Planning Mekanizmaları: Task decomposition ve multi-step reasoning vs. single-turn response’lar
  3. Tool Orchestration: Dinamik tool seçimi ve composition vs. sabit API çağrıları
  4. Autonomy Seviyeleri: Self-directed execution vs. user-driven etkileşimler
  5. Error Recovery: Adaptive retry stratejileri vs. “Anlamadım” fallback’leri

Pattern 1: Geleneksel Intent-Based Chatbot#

Geleneksel bir chatbot mimarisini ve kısıtlamalarını inceleyelim:

interface ChatbotMessage {
  role: "user" | "assistant";
  content: string;
}

interface Intent {
  name: string;
  confidence: number;
  entities: Record<string, any>;
}

class TraditionalChatbot {
  private conversationHistory: ChatbotMessage[] = [];

  async processMessage(userMessage: string): Promise<string> {
    // History'ye ekle (son N mesajla sınırlı)
    this.conversationHistory.push({ role: "user", content: userMessage });
    if (this.conversationHistory.length > 10) {
      this.conversationHistory.shift(); // En eskiyi at
    }

    // Intent classification
    const intent = await this.classifyIntent(userMessage);

    // Intent'e göre handler'a route et
    switch (intent.name) {
      case "check_order":
        return await this.handleOrderCheck(intent.entities);
      case "return_request":
        return await this.handleReturnRequest(intent.entities);
      case "product_question":
        return await this.handleProductQuestion(intent.entities);
      default:
        return "Bu konuda nasıl yardımcı olabileceğimden emin değilim. Farklı ifade edebilir misin?";
    }
  }

  private async classifyIntent(message: string): Promise<Intent> {
    // NLU servisi veya LLM'e intent classification çağrısı
    const response = await fetch("https://api.nlp-service.com/classify", {
      method: "POST",
      body: JSON.stringify({ text: message })
    });
    return response.json();
  }

  private async handleOrderCheck(entities: Record<string, any>): Promise<string> {
    // Sabit flow: order ID çıkar → database sorgusu → response formatla
    const orderId = entities.order_id;
    if (!orderId) {
      return "Sipariş numaran nedir?";
    }

    const order = await this.fetchOrder(orderId);
    return `Sipariş ${orderId} durumu ${order.status}. Tahmini teslimat: ${order.eta}`;
  }

  private async fetchOrder(orderId: string): Promise<any> {
    // Database query implementasyonu
    return { status: "kargoda", eta: "2025-12-05" };
  }
}

Kısıtlamalar:

  • Task decomposition yok (“geçen ayki tüm siparişlerimi kontrol et” handle edemez)
  • 10 mesaj sonra memory kaybedilir
  • Hardcode edilmiş intent → handler mapping
  • Explicit programlama olmadan birden fazla data source’u birleştiremez
  • Yeni senaryolara adapte olma yeteneği yok

Pattern 2: ReAct Agent (Reasoning and Acting)#

ReAct pattern’i tool kullanımıyla iterative reasoning sağlar:

Evet

Hayir

User Task

Thought: Ne yapacagini dusun

Cevap icin yeterli bilgi var mi?

Final Cevabi Don

Action: Tool ve parametre sec

Tool'u Execute Et

Observation: Tool sonucunu al

Bitti

Döngünün minimal bir implementasyonu:

interface Tool {
  name: string;
  description: string;
  parameters: Record<string, any>;
  execute: (params: any) => Promise<any>;
}

interface AgentStep {
  thought: string;
  action?: { tool: string; input: any };
  observation?: any;
}

class ReActAgent {
  private tools: Map<string, Tool>;
  private memory: ConversationMemory;
  private maxIterations = 10;

  constructor(tools: Tool[], memorySystem: ConversationMemory) {
    this.tools = new Map(tools.map(t => [t.name, t]));
    this.memory = memorySystem;
  }

  async processTask(task: string): Promise<string> {
    const steps: AgentStep[] = [];
    let finalAnswer: string | null = null;

    // Memory'den ilgili context'i al
    const context = await this.memory.retrieve(task);

    for (let i = 0; i < this.maxIterations; i++) {
      // Sonraki adımı generate et: thought + action
      const step = await this.generateNextStep(task, steps, context);
      steps.push(step);

      // Final cevap var mı kontrol et
      if (!step.action) {
        finalAnswer = step.thought;
        break;
      }

      // Action'ı execute et
      const tool = this.tools.get(step.action.tool);
      if (!tool) {
        step.observation = { error: `Tool ${step.action.tool} bulunamadı` };
        continue;
      }

      try {
        const result = await tool.execute(step.action.input);
        step.observation = result;
      } catch (error) {
        step.observation = { error: error.message };
      }
    }

    // Conversation'ı long-term memory'de sakla
    await this.memory.store(task, steps, finalAnswer);

    return finalAnswer || "Bu task'ı iteration limiti içinde tamamlayamadım.";
  }

  private async generateNextStep(
    task: string,
    previousSteps: AgentStep[],
    context: any
  ): Promise<AgentStep> {
    // ReAct pattern'iyle prompt oluştur
    const prompt = this.buildReActPrompt(task, previousSteps, context);

    // Thought ve action generate etmek için LLM çağır
    const response = await this.callLLM(prompt);

    // Response'u structured step'e parse et
    return this.parseReActResponse(response);
  }

  private buildReActPrompt(task: string, steps: AgentStep[], context: any): string {
    const toolDescriptions = Array.from(this.tools.values())
      .map(t => `${t.name}: ${t.description}`)
      .join("\n");

    const stepHistory = steps.map((s, i) =>
      `Adım ${i + 1}:\nDüşünce: ${s.thought}\n` +
      (s.action ? `Aksiyon: ${s.action.tool}(${JSON.stringify(s.action.input)})\n` : "") +
      (s.observation ? `Gözlem: ${JSON.stringify(s.observation)}\n` : "")
    ).join("\n");

    return `Tool'ları kullanarak reasoning yapan bir AI agent'sın.

Görev: ${task}

Mevcut Tool'lar:
${toolDescriptions}

Memory'den İlgili Context:
${JSON.stringify(context, null, 2)}

Önceki Adımlar:
${stepHistory || "Henüz yok"}

Ne yapacağını düşünüp bir tool seçerek sonraki adımı generate et.
Cevaplamak için yeterli bilgin varsa, action yerine final cevabı ver.

Format:
Düşünce: [bir sonraki adım hakkında reasoning'in]
Aksiyon: [tool_name]
Input: [JSON olarak tool input]

VEYA cevaplamaya hazırsan:
Düşünce: [final reasoning]
Cevap: [görev için final cevap]`;
  }

  private parseReActResponse(response: string): AgentStep {
    // LLM output'unu structured step'e parse et
    const thoughtMatch = response.match(/Düşünce: (.+?)(?=\n|$)/s);
    const actionMatch = response.match(/Aksiyon: (.+?)(?=\n|$)/);
    const inputMatch = response.match(/Input: (.+?)(?=\n|$)/s);
    const answerMatch = response.match(/Cevap: (.+?)(?=\n|$)/s);

    const thought = thoughtMatch?.[1].trim() || "";

    if (answerMatch) {
      // Final cevap, action yok
      return { thought: answerMatch[1].trim() };
    }

    if (actionMatch && inputMatch) {
      return {
        thought,
        action: {
          tool: actionMatch[1].trim(),
          input: JSON.parse(inputMatch[1].trim())
        }
      };
    }

    return { thought };
  }

  private async callLLM(prompt: string): Promise<string> {
    // LLM API çağrısı (Anthropic, OpenAI, vs.)
    throw new Error("LLM entegrasyonu implement et");
  }
}

Öne çıkan pattern’ler:

  • Configurable max iteration’larla iterative reasoning loop
  • Context’te sağlanan tool açıklamaları
  • Long-term context için memory retrieval
  • Sonraki adıma dahil edilen observation feedback
  • Tool error’larının graceful handling’i
  • LLM response’larının structured parsing’i

Production’da dikkat edilecekler:

  • Infinite loop’ları önlemek için iteration limitleri implement et
  • Debugging için tüm thought’ları ve action’ları logla
  • Token consumption’ı monitor et (basit completion’ın 5-10 katı olabilir)
  • Transparency için thought’ları kullanıcılara stream etmeyi düşün

Pattern 3: Plan-and-Execute#

Net yapıya sahip karmaşık task’lar için Plan-and-Execute daha iyi cost efficiency sunuyor:

Evet

Hayir

User Goal

Planning Fazi: Subtask'lara ayir

Dependency'leri Analiz Et

Execution Fazi

Paralel calisabilir mi?

Task'lari Concurrently Execute Et

Task'lari Sequentially Execute Et

Synthesis Fazi: Sonuclari Birlestir

Final Cevap

Implementasyon:

interface Task {
  id: string;
  description: string;
  status: "pending" | "in-progress" | "completed" | "failed";
  dependencies: string[];
  result?: any;
  error?: string;
  metadata?: any;
}

interface ExecutionPlan {
  goal: string;
  tasks: Task[];
  strategy: string;
}

class PlanAndExecuteAgent {
  private tools: Map<string, Tool>;
  private memory: ConversationMemory;

  async execute(goal: string): Promise<any> {
    // Faz 1: Planning
    console.error("[Planning Fazı] Goal task'lara ayrılıyor...");
    const plan = await this.createPlan(goal);
    console.error(`[Planning Fazı] ${plan.tasks.length} task ile plan oluşturuldu`);

    // Faz 2: Execution
    console.error("[Execution Fazı] Task'lar execute ediliyor...");
    const results = await this.executePlan(plan);

    // Faz 3: Synthesis
    console.error("[Synthesis Fazı] Sonuçlar birleştiriliyor...");
    const finalResult = await this.synthesizeResults(goal, plan, results);

    return finalResult;
  }

  private async createPlan(goal: string): Promise<ExecutionPlan> {
    // Memory'den ilgili geçmiş plan'ları al
    const pastExperiences = await this.memory.retrieve(goal);

    const planningPrompt = `Bir planning agent'sın. Bu goal'i executable task'lara ayır.

Goal: ${goal}

Mevcut Tool'lar:
${Array.from(this.tools.values()).map(t => `- ${t.name}: ${t.description}`).join("\n")}

Benzer Geçmiş Task'lar:
${JSON.stringify(pastExperiences, null, 2)}

Şöyle task'larla plan oluştur:
1. Mümkün olduğunda independent (paralel execution için)
2. Dependency'leri explicit belirt
3. Mevcut tool'lara map'le
4. Verification adımlarını içersin

Return formatı:
{
  "strategy": "yaklaşımın açıklaması",
  "tasks": [
    {
      "id": "task-1",
      "description": "ne yapılacak",
      "tool": "tool_name",
      "dependencies": [],
      "params": {}
    }
  ]
}`;

    const planResponse = await this.callLLM(planningPrompt);
    const planData = JSON.parse(planResponse);

    return {
      goal,
      strategy: planData.strategy,
      tasks: planData.tasks.map((t: any) => ({
        id: t.id,
        description: t.description,
        status: "pending" as const,
        dependencies: t.dependencies || [],
        metadata: { tool: t.tool, params: t.params }
      }))
    };
  }

  private async executePlan(plan: ExecutionPlan): Promise<Map<string, any>> {
    const results = new Map<string, any>();
    const taskMap = new Map(plan.tasks.map(t => [t.id, t]));

    // Dependency'leri respect ederek task'ları execute et
    while (results.size < plan.tasks.length) {
      // Execute etmeye hazır task'ları bul (pending dependency yok)
      const readyTasks = plan.tasks.filter(task => {
        if (task.status !== "pending") return false;

        return task.dependencies.every(depId => {
          const depTask = taskMap.get(depId);
          return depTask?.status === "completed";
        });
      });

      if (readyTasks.length === 0) {
        // Takılıp kalmadık mı kontrol et (circular dependency veya hepsi failed)
        const pendingTasks = plan.tasks.filter(t => t.status === "pending");
        if (pendingTasks.length > 0) {
          console.error("[Execution Fazı] Takıldı: circular dependency tespit edildi");
          break;
        }
        break;
      }

      // Hazır task'ları paralel execute et
      console.error(`[Execution Fazı] ${readyTasks.length} task paralel execute ediliyor`);
      await Promise.all(
        readyTasks.map(task => this.executeTask(task, results))
      );
    }

    return results;
  }

  private async executeTask(task: Task, results: Map<string, any>): Promise<void> {
    task.status = "in-progress";
    console.error(`[Task ${task.id}] Başlıyor: ${task.description}`);

    try {
      // Dependency sonuçlarını al
      const depResults = task.dependencies.reduce((acc, depId) => {
        acc[depId] = results.get(depId);
        return acc;
      }, {} as Record<string, any>);

      // Tool'u parametreler ve dependency sonuçlarıyla execute et
      const tool = this.tools.get(task.metadata.tool);
      if (!tool) {
        throw new Error(`Tool ${task.metadata.tool} bulunamadı`);
      }

      const params = {
        ...task.metadata.params,
        dependencyResults: depResults
      };

      const result = await tool.execute(params);

      task.status = "completed";
      task.result = result;
      results.set(task.id, result);

      console.error(`[Task ${task.id}] Başarıyla tamamlandı`);
    } catch (error) {
      task.status = "failed";
      task.error = error.message;
      results.set(task.id, { error: error.message });

      console.error(`[Task ${task.id}] Başarısız: ${error.message}`);
    }
  }

  private async synthesizeResults(
    goal: string,
    plan: ExecutionPlan,
    results: Map<string, any>
  ): Promise<any> {
    const synthesisPrompt = `Bir goal'e ulaşmak için plan execute ettin. Sonuçları tutarlı bir cevap haline getir.

Goal: ${goal}

Plan Stratejisi: ${plan.strategy}

Task Sonuçları:
${Array.from(results.entries()).map(([id, result]) =>
  `${id}: ${JSON.stringify(result)}`
).join("\n")}

Orijinal goal'i karşılayan kapsamlı bir cevap ver, tüm task'lardan insight'ları dahil et.`;

    const synthesis = await this.callLLM(synthesisPrompt);

    // Başarılı plan'ı ileride kullanmak için memory'de sakla
    if (results.size === plan.tasks.length) {
      await this.memory.store(goal, { plan, results: Array.from(results.entries()) }, synthesis);
    }

    return synthesis;
  }

  private async callLLM(prompt: string): Promise<string> {
    throw new Error("LLM entegrasyonu implement et");
  }
}

Trade-off’lar:

  • Artıları: Daha az LLM call (bir kez plan, execute), paralel execution, öngörülebilir cost’lar
  • Eksileri: Environment execution ortasında değiştiğinde kırılgan, beklenmedik sonuçlara adapte olmak daha zor

Best practice’ler:

  • Başarılı plan’ları yeniden kullanım için memory’de sakla
  • Plan’a verification task’ları dahil et
  • Execution fail olursa re-planning’e izin ver
  • Individual task’lar için timeout kullan

Memory Mimarisi: Short-Term vs Long-Term#

Chatbot’lar ve agent’lar arasındaki en önemli farklardan biri memory mimarisi:

Current task

Recent context

Retrieve

Retrieve

Top-k similar

Time-based

Store important

Append

Agent

Working Memory In-Context

Short-Term Memory Buffer

Long-Term Memory Vector DB

Semantic Search

Episodic Recall

Implementasyon karşılaştırması:

interface MemoryEntry {
  timestamp: Date;
  content: any;
  metadata: Record<string, any>;
  embedding?: number[];
}

// Basit buffer memory (chatbot tarzı)
class BufferMemory {
  private buffer: MemoryEntry[] = [];
  private maxSize = 10;

  async store(content: any, metadata: Record<string, any> = {}): Promise<void> {
    this.buffer.push({ timestamp: new Date(), content, metadata });
    if (this.buffer.length > this.maxSize) {
      this.buffer.shift(); // FIFO eviction
    }
  }

  async retrieve(query: string): Promise<any[]> {
    // Tüm buffer içeriğini döndür (filtreleme yok)
    return this.buffer.map(e => e.content);
  }

  async clear(): Promise<void> {
    this.buffer = [];
  }
}

// Vector tabanlı long-term memory (agent tarzı)
class VectorMemory {
  private vectorStore: VectorDatabase;
  private embeddingModel: EmbeddingModel;

  constructor(vectorStore: VectorDatabase, embeddingModel: EmbeddingModel) {
    this.vectorStore = vectorStore;
    this.embeddingModel = embeddingModel;
  }

  async store(content: any, metadata: Record<string, any> = {}): Promise<void> {
    // Semantic search için embedding generate et
    const text = this.contentToText(content);
    const embedding = await this.embeddingModel.embed(text);

    await this.vectorStore.insert({
      timestamp: new Date(),
      content,
      metadata: {
        ...metadata,
        importance: this.calculateImportance(content, metadata)
      },
      embedding
    });
  }

  async retrieve(query: string, options: { limit?: number; threshold?: number } = {}): Promise<any[]> {
    // Embedding'ler kullanarak semantic search
    const queryEmbedding = await this.embeddingModel.embed(query);

    const results = await this.vectorStore.search({
      embedding: queryEmbedding,
      limit: options.limit || 5,
      threshold: options.threshold || 0.7
    });

    // En ilgili memory'leri, recency ve importance'a göre ağırlıklandırarak döndür
    return results
      .map(r => ({
        content: r.content,
        relevance: r.similarity,
        recency: this.calculateRecency(r.timestamp),
        importance: r.metadata.importance
      }))
      .sort((a, b) => {
        const scoreA = a.relevance * 0.6 + a.recency * 0.2 + a.importance * 0.2;
        const scoreB = b.relevance * 0.6 + b.recency * 0.2 + b.importance * 0.2;
        return scoreB - scoreA;
      })
      .map(r => r.content);
  }

  async forget(criteria: { olderThan?: Date; importance?: number }): Promise<void> {
    // Zamana ve importance'a göre selective forgetting
    const deleteFilter: any = {};

    if (criteria.olderThan) {
      deleteFilter.timestamp = { $lt: criteria.olderThan };
    }
    if (criteria.importance !== undefined) {
      deleteFilter["metadata.importance"] = { $lt: criteria.importance };
    }

    await this.vectorStore.delete(deleteFilter);
  }

  private calculateImportance(content: any, metadata: Record<string, any>): number {
    // Heuristik scoring: user correction'ları, explicit feedback, task outcome'ları
    let score = 0.5; // baseline

    if (metadata.userCorrection) score += 0.3;
    if (metadata.explicitFeedback) score += 0.2;
    if (metadata.taskSuccess === false) score += 0.15; // Failure'lardan öğren
    if (metadata.toolError) score += 0.1; // Issue'ları hatırla

    return Math.min(score, 1.0);
  }

  private calculateRecency(timestamp: Date): number {
    const ageMs = Date.now() - timestamp.getTime();
    const ageDays = ageMs / (1000 * 60 * 60 * 24);

    // Exponential decay: taze memory'ler daha yüksek score alır
    return Math.exp(-ageDays / 30); // 30 günlük half-life
  }

  private contentToText(content: any): string {
    if (typeof content === "string") return content;
    return JSON.stringify(content);
  }
}

// Production agent'lar için hybrid memory sistem
class HybridMemory implements ConversationMemory {
  private shortTerm: BufferMemory;
  private longTerm: VectorMemory;

  constructor(vectorStore: VectorDatabase, embeddingModel: EmbeddingModel) {
    this.shortTerm = new BufferMemory();
    this.longTerm = new VectorMemory(vectorStore, embeddingModel);
  }

  async store(task: string, steps: any[], result: any): Promise<void> {
    // Immediate recall için short-term'de sakla
    await this.shortTerm.store({ task, steps, result });

    // Semantic retrieval için long-term'de sakla
    await this.longTerm.store(
      { task, steps, result },
      {
        taskSuccess: result !== null,
        stepCount: steps.length,
        timestamp: new Date()
      }
    );
  }

  async retrieve(query: string): Promise<any> {
    // Her iki memory sistemini birleştir
    const recent = await this.shortTerm.retrieve(query);
    const relevant = await this.longTerm.retrieve(query, { limit: 3 });

    return {
      recentContext: recent,
      relevantExperiences: relevant
    };
  }
}

Memory karşılaştırması:

  • Buffer memory: Hızlı, basit, semantic anlayış yok
  • Vector memory: Semantic search, importance-weighted, selective forgetting
  • Hybrid yaklaşım: Production agent’lar için her ikisinin de en iyisi

Multi-Agent Koordinasyon Pattern’leri#

Specialized expertise gerektiren karmaşık sistemler için:

Peer-to-Peer Pattern

Agent 1

Agent 2

Agent 3

Orchestrator Pattern

Orchestrator

Agent 1

Agent 2

Agent 3

Production’da tercih edilecek pattern orchestrator: control flow merkezi ve takip etmesi kolay kalıyor, cost’lar öngörülebilir, tek arıza noktası da (orchestrator’ın kendisi) retry’larla kolayca mitigate ediliyor.

Peer-to-peer koordinasyon decentralized ve bu tek arıza noktasını ortadan kaldırıyor, tekil agent çökmelerine karşı daha dayanıklı; ama bu dayanıklılığın bedelini debug zorluğu ve öngörülemeyen cost’larla ödüyor. Hâlâ deneysel.

Implementasyon:

interface AgentCapability {
  domain: string;
  description: string;
  tools: string[];
}

interface SubAgent {
  id: string;
  capability: AgentCapability;
  execute: (task: string) => Promise<any>;
}

class OrchestratorAgent {
  private subAgents: Map<string, SubAgent>;
  private memory: ConversationMemory;

  constructor(subAgents: SubAgent[], memory: ConversationMemory) {
    this.subAgents = new Map(subAgents.map(a => [a.id, a]));
    this.memory = memory;
  }

  async handleRequest(userRequest: string): Promise<any> {
    console.error("[Orchestrator] Analyzing request...");

    // Adım 1: Request'i analiz et ve gereken agent'ları belirle
    const analysis = await this.analyzeRequest(userRequest);

    console.error(`[Orchestrator] Routing to ${analysis.requiredAgents.length} agents`);

    // Adım 2: Uygun subagent'lara yönlendir
    const subResults = await this.coordinateSubAgents(analysis);

    // Adım 3: Sonuçları sentezle
    console.error("[Orchestrator] Synthesizing results...");
    const finalAnswer = await this.synthesize(userRequest, analysis, subResults);

    return finalAnswer;
  }

  private async analyzeRequest(request: string): Promise<{
    intent: string;
    requiredAgents: string[];
    executionStrategy: "sequential" | "parallel" | "iterative";
  }> {
    const agentDescriptions = Array.from(this.subAgents.values())
      .map(a => `${a.id}: ${a.capability.description}`)
      .join("\n");

    const analysisPrompt = `You are an orchestrator analyzing which specialized agents to use.

User Request: ${request}

Available Agents:
${agentDescriptions}

Determine:
1. What is the user trying to accomplish (intent)?
2. Which agents are needed?
3. Should they work sequentially (one after another) or in parallel?

Return JSON:
{
  "intent": "description",
  "requiredAgents": ["agent-id-1", "agent-id-2"],
  "executionStrategy": "sequential" | "parallel"
}`;

    const response = await this.callLLM(analysisPrompt);
    return JSON.parse(response);
  }

  private async coordinateSubAgents(analysis: {
    intent: string;
    requiredAgents: string[];
    executionStrategy: "sequential" | "parallel" | "iterative";
  }): Promise<Map<string, any>> {
    const results = new Map<string, any>();

    if (analysis.executionStrategy === "parallel") {
      // Tüm agent'ları aynı anda çalıştır
      const agentPromises = analysis.requiredAgents.map(async agentId => {
        const agent = this.subAgents.get(agentId);
        if (!agent) return null;

        console.error(`[SubAgent ${agentId}] Starting parallel execution`);
        const result = await agent.execute(analysis.intent);
        results.set(agentId, result);
        return result;
      });

      await Promise.all(agentPromises);

    } else if (analysis.executionStrategy === "sequential") {
      // Agent'ları context geçirerek arka arkaya çalıştır
      let context = analysis.intent;

      for (const agentId of analysis.requiredAgents) {
        const agent = this.subAgents.get(agentId);
        if (!agent) continue;

        console.error(`[SubAgent ${agentId}] Starting sequential execution`);
        const result = await agent.execute(context);
        results.set(agentId, result);

        // Sonraki agent önceki sonuçları context olarak alır
        context = `${analysis.intent}\n\nPrevious agent results: ${JSON.stringify(result)}`;
      }
    }

    return results;
  }

  private async synthesize(
    request: string,
    analysis: any,
    results: Map<string, any>
  ): Promise<any> {
    const synthesisPrompt = `Combine results from multiple specialized agents into a coherent response.

User Request: ${request}

Agent Results:
${Array.from(results.entries()).map(([id, result]) =>
  `${id}:\n${JSON.stringify(result, null, 2)}`
).join("\n\n")}

Provide a comprehensive, natural response that addresses the user's request.`;

    return await this.callLLM(synthesisPrompt);
  }

  private async callLLM(prompt: string): Promise<string> {
    throw new Error("Implement LLM integration");
  }
}

Güvenlik ve Guardrail’ler#

Production agent’lar birden fazla güvenlik katmanı gerektirir:

Evet

Hayir

User Input

Input Guardrail: Validation

Agent Processing

Tool Guardrail: Authorization

Tool Execution

Output Guardrail: Filtering

Human onay gerekli mi?

Human Review

User Output

Audit Log

Implementasyon:

class GuardrailSystem {
  async validateInput(input: string): Promise<{ safe: boolean; reason?: string }> {
    // Prompt injection pattern'lerini kontrol et
    const injectionPatterns = [
      /ignore previous instructions/i,
      /new instructions:/i,
      /you are now/i,
      /system prompt/i
    ];

    for (const pattern of injectionPatterns) {
      if (pattern.test(input)) {
        return { safe: false, reason: "Potential prompt injection detected" };
      }
    }

    // Content moderation API'yi çağır
    const moderation = await this.callModerationAPI(input);
    if (!moderation.safe) {
      return { safe: false, reason: moderation.reason };
    }

    return { safe: true };
  }

  async authorizeToolUse(
    agentId: string,
    toolName: string,
    params: any
  ): Promise<{ authorized: boolean; reason?: string }> {
    // Permission matrisine karşı kontrol et
    const permissions = await this.getAgentPermissions(agentId);

    if (!permissions.tools.includes(toolName)) {
      return { authorized: false, reason: `Agent lacks permission for tool: ${toolName}` };
    }

    // Yükseltilmiş izin gerektiren hassas operasyonları kontrol et
    if (this.isSensitiveTool(toolName) && !permissions.elevated) {
      return { authorized: false, reason: "Sensitive tool requires elevated permissions" };
    }

    // Rate limiting
    const withinRateLimit = await this.checkRateLimit(agentId, toolName);
    if (!withinRateLimit) {
      return { authorized: false, reason: "Rate limit exceeded" };
    }

    return { authorized: true };
  }

  async filterOutput(output: string): Promise<{ filtered: string; blocked: boolean }> {
    // PII tespiti ve redaction
    const piiRedacted = this.redactPII(output);

    // Content policy kontrolü
    const policyCheck = await this.checkContentPolicy(piiRedacted);
    if (!policyCheck.compliant) {
      return { filtered: "", blocked: true };
    }

    return { filtered: piiRedacted, blocked: false };
  }

  private redactPII(text: string): string {
    // Email redaction
    text = text.replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, "[EMAIL_REDACTED]");

    // Telefon numarası redaction (US formatı)
    text = text.replace(/\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g, "[PHONE_REDACTED]");

    // Kredi kartı redaction
    text = text.replace(/\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g, "[CARD_REDACTED]");

    return text;
  }

  private async callModerationAPI(input: string): Promise<{ safe: boolean; reason?: string }> {
    // Moderation servisi ile implementasyon
    return { safe: true };
  }

  private async getAgentPermissions(agentId: string): Promise<any> {
    // Permission store'dan getir
    return { tools: [], elevated: false };
  }

  private isSensitiveTool(toolName: string): boolean {
    const sensitivTools = ["delete-data", "modify-permissions", "send-money"];
    return sensitivTools.includes(toolName);
  }

  private async checkRateLimit(agentId: string, toolName: string): Promise<boolean> {
    // Rate limiting logic
    return true;
  }

  private async checkContentPolicy(text: string): Promise<{ compliant: boolean }> {
    // Policy kontrolü
    return { compliant: true };
  }
}

Cost Analizi ve Trade-off’lar#

Token Consumption Karşılaştırması#

“Sipariş durumunu kontrol et ve para iadesi işle” gibi tipik bir task için:

MimariLLM Call’larıAvg Token’larTask Başına Cost
Chatbot2-31,000$0.004
ReAct Agent5-88,000$0.034
Plan-Execute Agent3-44,000$0.017
Multi-Agent6-1010,000$0.042

Cost’lar Claude Sonnet pricing’i ($3/M input, $15/M output) ve %90 input / %10 output dağılımı varsayılarak hesaplandı; tool açıklamaları ve adım geçmişi her iteration’da yeniden gönderildiği için dağılım tipik olarak buna yakın çıkıyor. Prompt caching ve batch processing bu rakamları %50-90 azaltabilir.

Infrastructure Cost’ları#

  • Chatbot: Minimal (stateless API)
  • Single Agent: Orta (memory için vector DB: ayda $50-200)
  • Multi-Agent: Daha yüksek (coordination layer, birden fazla DB: ayda $200-500)

Performance Özellikleri#

Latency:

  • Chatbot: 500ms - 2s (tek LLM call)
  • ReAct Agent: 5s - 30s (birden fazla iteration)
  • Plan-Execute: 3s - 15s (planning overhead, paralel execution)
  • Multi-Agent: 10s - 60s (koordinasyon + birden fazla agent)

Task completion oranı domain’e ve tool kalitesine göre o kadar değişiyor ki genel bir rakam vermek anlamsız. Kendi oranını sabit bir evaluation seti üzerinde, her mimari değişiklikten önce ve sonra ölç; karar verirken işe yarayan tek rakam bu.

Ne Zaman Ne Kullanılır#

Basit, FAQ

Orta, Esnek

Yuksek, Multi-Domain

Hayir

Evet

Dusuk

Orta

Orta

Yuksek

AI Sistemi Gerekli

Task Karmasikligi?

Intent-Based Chatbot

Planning Gerekli mi?

Multi-Agent System

ReAct Agent

Plan-Execute Agent

Butce?

Butce?

Butce?

Butce?

Uygun

Plan-Execute Dusun

Uygun

Uygun

MimariNe zaman uygunBütçeEkip / kısıt
ChatbotTask’lar net intent’lerle iyi tanımlanmış (20’den az intent); response’lar scriptlenebilir veya template-basedİnteraction başına $0.001-0.005Latency 2 saniyenin altında olmalı; minimal maintenance kadrosu
ReAct AgentTask’lar dinamik adaptasyon gerektiriyor, senaryolar önceden tahmin edilemiyor; reasoning için audit trail gerekliTask başına $0.01-0.05Ekipte LLM expertise var
Plan-Execute AgentNet yapıya sahip, mantıksal olarak decompose edilebilen ve paralel execution’dan faydalanan karmaşık task’larÖngörülebilir cost gerekli; kalite hızdan daha önemli-
Multi-Agent SystemDomain’ler arası specialized expertise gerekli, en yüksek accuracy önemliChatbot’a göre 5-10x cost justify edilebilirCoordination logic’i maintain edecek ekip var; failure cost’u yüksek (healthcare, finans)

Infinite Loop, Context Overflow ve Tool Şişkinliği#

Tuzak 1: ReAct Agent’larda Infinite Loop’lar#

Agent aynı tool call’larını tekrarlayarak takılıyor. Çözüm, tekrarı tespit edip loop’u kırmak:

async function reactLoopWithDetection(task: string) {
  const actionHistory = new Set<string>();

  for (let i = 0; i < maxIterations; i++) {
    const step = await generateStep();

    // Bu action'ın signature'ını oluştur
    const actionSignature = `${step.action.tool}:${JSON.stringify(step.action.input)}`;

    if (actionHistory.has(actionSignature)) {
      console.error("[Loop Tespit Edildi] Tekrarlanan action'dan çıkılıyor");
      return { error: "Agent loop'ta takıldı, sonlandırılıyor" };
    }

    actionHistory.add(actionSignature);
    await executeStep(step);
  }
}

Tuzak 2: Context Window Overflow#

Eski mesajları özetleyen bir sliding window, conversation history’nin context limitini aşmasını önlüyor:

class ManagedConversationHistory {
  private messages: Message[] = [];
  private maxMessages = 20;
  private summaries: string[] = [];

  async add(message: Message) {
    this.messages.push(message);

    if (this.messages.length > this.maxMessages) {
      // En eski 10 mesajı summarize et
      const toSummarize = this.messages.splice(0, 10);
      const summary = await this.summarize(toSummarize);
      this.summaries.push(summary);
    }
  }

  getContext(): string {
    return [
      ...this.summaries.map(s => `[Özet] ${s}`),
      ...this.messages.map(m => `${m.role}: ${m.content}`)
    ].join("\n");
  }
}

Tuzak 3: Tool Description Bloat#

Agent’a çok fazla tool verildiğinde veya açıklamalar uzadığında, sadece task’ın ihtiyaç duyduğu tool’ları yüklemek listeyi kısa tutuyor:

class ContextualToolLoader {
  async getRelevantTools(task: string): Promise<Tool[]> {
    // İlgili tool'ları bulmak için semantic search kullan
    const taskEmbedding = await embed(task);

    const relevantTools = await this.vectorStore.search({
      embedding: taskEmbedding,
      limit: 8, // Aynı anda en fazla 8 tool
      threshold: 0.6
    });

    return relevantTools.map(t => ({
      name: t.name,
      description: t.shortDescription, // Concise versiyonu kullan
      parameters: t.parameters
    }));
  }
}

Kademeli Geçiş Stratejisi#

Chatbot ile başla, incremental olarak agent yetenekleri ekle:

class HybridChatbotAgent {
  private intentClassifier: IntentClassifier;
  private agentMode: boolean = false;

  async process(message: string): Promise<string> {
    // Önce intent-based handling dene (hızlı, ucuz)
    const intent = await this.intentClassifier.classify(message);

    if (intent.confidence > 0.85 && !intent.requiresToolUse) {
      // Geleneksel chatbot flow kullan
      return await this.handleIntent(intent);
    }

    // Karmaşık query'ler için agent mode'a geç
    console.error("[Hybrid] Karmaşık query için agent mode'a geçiliyor");
    this.agentMode = true;
    return await this.agentProcess(message);
  }
}

Neden işe yarıyor: ucuz path öngörülebilir trafiği emiyor, agent yalnızca intent confidence düşükken veya bir tool gerektiğinde devreye giriyor. İki path arasındaki dağılımı zaman içinde takip ederek agent’ın maliyetini hak edip etmediğini gör.

Tool’lar ve Teknolojiler#

Agent Framework’leri#

LangChain ekibinden LangGraph, karmaşık state içeren yapılandırılmış agent workflow’larını hedefliyor: Python ve TypeScript’i destekliyor, açık state management ve graph-based workflow’lar üzerine kurulu, production’da kullanılacak kadar olgun.

AutoGen (Microsoft), Python’da işbirliğine dayalı multi-agent conversation’lara odaklanıyor ve içinde birden fazla koordinasyon pattern’i barındırıyor. Şu anda maintenance mode’da; Microsoft onu Agent Framework’üyle değiştiriyor.

CrewAI, role-based agent’lar üzerine kurulu, state graph’tan çok ekip metaforuna yakın, daha hafif bir Python seçeneği.

Memory Sistemleri#

Vector storage tarafında Pinecone managed ve serverless, Qdrant open-source ve self-hosted, Weaviate GraphQL interface ile hybrid search ekliyor, Chroma ise lightweight embedded bir seçenek.

Depolama ve retrieval’ın ötesine geçen memory tarafında Mem0, saklanan bilgileri retrieval öncesinde priority’ye göre skorlayan managed bir katman; Letta (eski adıyla MemGPT) ise context’i memory block’lar halinde organize ediyor.

Observability#

LangSmith agent execution’larını trace ediyor, reasoning chain’leri debug ediyor ve prompt’lar için A/B testing destekliyor. Langfuse open-source alternatif olarak cost tracking ve latency monitoring’i kapsıyor. Helicone ise cost analytics ve caching ile request monitoring’e odaklanıyor.

Başlangıç Noktası Seçmek#

Bu varsayılanı üç durumda değiştir. İş bağımsız subtask’lara ayrılıyorsa ve task başına maliyet bağlayıcı kısıtsa, Plan-and-Execute hem LLM call sayısını düşürür hem de subtask’ları paralel çalıştırır. Intent seti küçükse ve cevaplar şablona sığıyorsa, bir classifier ile switch bloğu latency ve fiyat tarafında reasoning loop’u hâlâ geçer. Domain’ler gerçekten ayrıysa ve yanlış cevabın bedeli yüksekse, orchestrator arkasındaki uzmanlaşmış agent’lar koordinasyon katmanını hak eder.

Pattern seçimi, altındaki iki katmandan daha az belirleyici. Guardrail’ler ve memory tasarımı sistemin production trafiğiyle karşılaşınca ayakta kalıp kalmayacağını belirliyor; ikisini de otonomi eklemeden önce kurmak sonradan kurmaktan kolay. Makul bir sonraki adım: input validation, tool authorization ve audit log’u yerine koymak, sonra agent’ın kendi başına yapabileceklerini genişletmeden önce sabit bir evaluation seti üzerinde completion rate ile task başına token’ı ölçmek.

Kaynaklar#

İlgili yazılar