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.
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:
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:
- Memory Sistemleri: Long-term knowledge graph’lar vs. conversation buffer’ları
- Planning Mekanizmaları: Task decomposition ve multi-step reasoning vs. single-turn response’lar
- Tool Orchestration: Dinamik tool seçimi ve composition vs. sabit API çağrıları
- Autonomy Seviyeleri: Self-directed execution vs. user-driven etkileşimler
- 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:
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:
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:
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:
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:
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:
| Mimari | LLM Call’ları | Avg Token’lar | Task Başına Cost |
|---|---|---|---|
| Chatbot | 2-3 | 1,000 | $0.004 |
| ReAct Agent | 5-8 | 8,000 | $0.034 |
| Plan-Execute Agent | 3-4 | 4,000 | $0.017 |
| Multi-Agent | 6-10 | 10,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#
| Mimari | Ne zaman uygun | Bütçe | Ekip / kısıt |
|---|---|---|---|
| Chatbot | Task’lar net intent’lerle iyi tanımlanmış (20’den az intent); response’lar scriptlenebilir veya template-based | İnteraction başına $0.001-0.005 | Latency 2 saniyenin altında olmalı; minimal maintenance kadrosu |
| ReAct Agent | Task’lar dinamik adaptasyon gerektiriyor, senaryolar önceden tahmin edilemiyor; reasoning için audit trail gerekli | Task başına $0.01-0.05 | Ekipte LLM expertise var |
| Plan-Execute Agent | Net 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 System | Domain’ler arası specialized expertise gerekli, en yüksek accuracy önemli | Chatbot’a göre 5-10x cost justify edilebilir | Coordination 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#
- ReAct: Synergizing Reasoning and Acting in Language Models (yeni sekmede açılır) - Yao ve arkadaşlarının LLM agent’larında iç içe geçmiş akıl yürütme izleri ve eylemleri tanıttığı özgün makale
- Building Effective Agents - Anthropic (yeni sekmede açılır) - Anthropic’in üretim agent sistemleri için mimari pattern’ler ve en iyi uygulamaları
- LangGraph Kılavuzları (yeni sekmede açılır) - İnsan döngüsü, bellek ve çok agent koordinasyonunu içeren durumlu agent iş akışları dokümantasyonu
- Multi-agent network - LangGraph (yeni sekmede açılır) - Tek bir sistem içinde birden fazla uzmanlaşmış agent’ı koordine etme üzerine eğitim
- GitHub - anthropics/anthropic-cookbook - agents (yeni sekmede açılır) - Anthropic API kullanılarak agent pattern’lerinin referans uygulamaları
İlgili yazılar
Mimari ağırlığını runtime'ın init-amortismanına göre seç: single-purpose Lambda'da yalın handler, Lambdalith'te orta, tam OOP/DI yalnızca uzun ömürlü runtime'da.
architecture · lambda · serverless +3
SOLID prensiplerinin modern JavaScript'te uygulanışı: TypeScript, React hooks ve fonksiyonel pattern'lerle pratik örnekler, ayrıca ne zaman gereksiz.
typescript · javascript · react +4
Singleton, Factory, Builder ve Prototype pattern'lerinin TypeScript'te evrimi: ES modülleri singleton'ı ne zaman, factory function'lar class'ı ne zaman geçer.
typescript · design-patterns · architecture +1
Decorator, Adapter, Facade, Composite ve Proxy patternlerinin React ve TypeScript'te evrimi: HOC'lar ne zaman hook'lara yol verir, adapterler API'ları nasıl izole eder.
typescript · react · design-patterns +2
Domain-Driven Design'a kapsamlı giriş: temel kavramlar, yapı taşları, stratejik desenler ve DDD'yi ne zaman ve nasıl uygulayacağına dair rehber.
domain-driven-design · architecture · design-patterns +2