Lesson 02 · 22 dk okuma

Agentic Loop ve stop_reason Semantiği

Agent'ın yaşam döngüsü, stop_reason değerlerinin her birinin anlamı, end_turn'ün neden TEK güvenilir completion sinyali olduğu, multi-turn tool calling'de state yönetimi ve anti-pattern'ler.

Öğreneceklerin

  • Agentic loop'un istek → stop_reason → tool → tekrar döngüsünü açıklamak
  • 4 stop_reason değerinin (end_turn, tool_use, max_tokens, stop_sequence) her birini somut örnekle ayırt etmek
  • 'Assistant text'ten completion parse etmek' anti-pattern'inin neden tehlikeli olduğunu göstermek
  • Doğru termination stratejisini minimal Python/TypeScript pseudo-code ile ifade etmek
  • Multi-turn tool calling'de state yönetiminin temel kurallarını saymak

Bu ders, Foundations sınavının en kritik teknik kavramına dalıyor: bir agent ne zaman “bitti” der, bunu nasıl anlarsın, hangi sinyale güvenip hangisine güvenmemelisin? D1 (%27) ve D5 (%15) sorularının omurgası.

Hook — gerçek bir bug: Bir agent kendi kendine "Görev tamamlandı." yazdı, döngüden çıktın — ama model aynı yanıtta tool_use bloğu da koymuştu. Tool hiç çalışmadı, kullanıcı yarım cevap aldı. Suçlu kod: if "tamam" in response.text: return. Tek güvenilir sinyal: stop_reason. Bu ders onu öğretiyor.

Büyük resim — sequence diyagramı

Bir agent çağrısının ömrü budur. Her okun sonunda stop_reason döner ve o tek alan döngünün sonraki adımını belirler:

[Sen]                                  [Claude API]
  │                                          │
  │── messages (user) ──────────────────────▶│
  │                                          │
  │◀────────── response + stop_reason ───────│
  │                                          │
  ├── stop_reason == "tool_use" ──▶ tool çalıştır
  │                                  │
  │◀── messages + tool_result ───────┤
  │                                  (tekrar sor)

  ├── stop_reason == "end_turn" ────▶ ✅ DÖNGÜ BİTTİ, kullanıcıya göster

  ├── stop_reason == "max_tokens" ──▶ max_tokens ↑ veya özetle → tekrar sor

  └── stop_reason == "stop_sequence" ▶ kasıtlı kesim → değerlendir

Neden bu sıra? Anthropic’in resmi tutorial’ı tam bu akışı öğretir: önce kodu çalıştır, stop_reason alanını gör, sonra genelle. Biz de aynısını yapıyoruz — kavram yükünü gözlem → döngü → hata yönetimi şeklinde bölüyoruz.

Ring 1 — Gözlem: stop_reason’ı ilk kez gör

Kod yok, terminal var. Claude Code’a bir soru sor ve JSON modunda çalıştır:

claude --output-format json -p "echo hello"
# Çıktıda "stop_reason": "end_turn" göreceksin.

Şimdi tool çağrısı zorla:

claude --output-format json -p "src/ dizininde kaç .ts dosyası var?"
# İlk yanıt: "stop_reason": "tool_use"  → model Bash çalıştırmak istiyor
# Tool çalışınca: "stop_reason": "end_turn"  → model işi bitirdi

Çıkar: stop_reason bir ayrımcı (discriminator). İki değer gördün: tool_use (devam et) ve end_turn (bitir). Şimdi bunu kodda yap.

Ring 2 — Döngü: while stop_reason == “tool_use”

Anthropic SDK’sında minimal doğru yapı budur — sadece iki case:

import anthropic

client = anthropic.Anthropic()
messages = [{"role": "user", "content": "src/ altındaki tüm .ts dosyalarını listele."}]
tools = [{"name": "Bash", "description": "Run shell", "input_schema": {...}}]

while True:
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=4096,
        tools=tools,
        messages=messages,
    )

    if response.stop_reason == "end_turn":
        return response.content[0].text  # ✅ BİTTİ — tek çıkış noktası

    if response.stop_reason == "tool_use":
        results = []
        for block in response.content:
            if block.type == "tool_use":
                out = execute_tool(block.name, block.input)  # senin executor'ın
                results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": out,
                })
        messages.append({"role": "assistant", "content": response.content})
        messages.append({"role": "user", "content": results})
        continue  # bir tur daha

    break  # max_tokens veya stop_sequence → Ring 4'te işleyeceğiz

Anahtar kural: stop_reason == "tool_use" ise görev bitmemiştir. Çalıştır, geri besle, tekrar sor. end_turn ise bitmiştir, kullanıcıya göster. Başka hiçbir yol yok.

Multi-turn state — client-side append-only

Dikkat: messages.append(...) her turda iki kez çağrılıyor — bir asistan mesajı, bir de tool_result içeren user mesajı. Bu, server’ın state tutmadığı anlamına gelir:

  • State client’tadır. Server-side session yok. messages listesi senin sorumluluğun.
  • tool_use_id korelasyonu şart. Her tool_result bir önceki tool_use ile eşleşmeli. Yanlış ID → model sonucu reddeder.
  • Sıralama: [user, assistant, user, assistant, ...] — iki user arka arkaya gelemez (istisna: birden fazla tool_result aynı user’da olabilir).
  • Trimming: Çok turlu conversation’da eski tool_result’ları özetleyebilirsin, ama tool_use_id görünür kalmalı; attribution koparsa model karışır.

Sınav tuzağı: “Claude session’ı tekrar kullanıyorum, server state’i koruyor” diye düşünme. Hayır. State client’ta, her istekte tam listeyi gönderiyorsun.

Ring 3–5 — sonraki derslerde

Bu derste kasıtlı olarak yalnızca Ring 1 (gözlem) ve Ring 2 (döngü) var. Geri kalanı:

  • Ring 3 — paralel tool_use + çoklu tool çağrısı tek turda (D1)
  • Ring 4 — max_tokens retry stratejisi, refusal propagation, pause_turn (server-side tool loop) (D5)
  • Ring 5 — SDK ile production-ready executor, error recovery, observability (D5)

Bu ring’leri ayrı derslerde göreceğiz; şimdilik iki şeyi bilmek yeterli: stop_reason’a göre dallan, messages’i client’ta append et.

Anti-pattern’ler — sınavda en çok düşülen 2 hata

1. Assistant text’ten completion parse etmek

# ❌ YANLIŞ — kırılgan
if "tamam" in response.text or "İşte sonuç" in response.text:
    return response.text

Neden yanlış: model “İşte sonuç: 42” yazıp ardından bir tool_use bloğu koyabilir (think-then-validate kalıbı). Text parse erken çıkar, tool çalışmaz, kullanıcı yarım cevap alır. Sınavda “agent neden boş döndü?” sorusunun klasik cevabı.

2. tool_use geldiğinde end_turn varsaymak

# ❌ YANLIŞ — kritik hata
if response.stop_reason != "max_tokens":
    return response.content[0].text  # tool_use buraya düşer

Doğrusu: Sadece stop_reason == "end_turn" ise çık. tool_use, max_tokens, stop_sequence — hepsi farklı ele alınır. Yukarıdaki kod tool_use’u görmezden gelip son kullanıcıya ara metni gösterir.

Hands-on (5 dk)

# Terminalde iki deney:
claude --output-format json -p "echo hello"          # → stop_reason: end_turn
claude --output-format json -p "pwd nedir?"          # → önce tool_use, sonra end_turn
# JSON içindeki stop_reason alanını bul ve sırayı takip et.

Sonra dosyada değişiklik yap: Ring 2 pseudo-code’undaki if response.stop_reason == "end_turn" satırını bilerek sil. Ne oluyor? (Cevap: sonsuz döngü veya erken çıkış — tool_use asla handle edilmez.)

Özet

  • 4 stop_reason: end_turn (tek güvenilir completion), tool_use (döngü devam), max_tokens (kesim — Ring 4’te), stop_sequence (kasıtlı dur).
  • Anti-pattern: if "tamam" in response.text — kırılgan, erken çıkışa yol açar. Tek güvenilir sinyal stop_reason.
  • Doğru: if response.stop_reason == "end_turn": return ... — başka hiçbir yol yok.
  • Multi-turn state: tool_use_id korelasyonu, sıralı mesaj listesi, client-side (server session değil).

Kaynaklar