Ana içeriğe geç

Orta10 dk

Ajan mantığına giriş

Önkoşul:RAG'e giriş

Kanca

  1. derste model, bir bilgi tabanından TEK bir belge GETİRİYORDU. Peki model, hem bir belge ARAYIP hem de bir HESAPLAMA yapması gereken bir soruyla karşılaşırsa?

Sezgi

Bir “ajan” (agent), modelin çıktısının bir kısmını “ARAÇ ÇAĞRISI” olarak yorumlayıp GERÇEK bir fonksiyon çalıştıran, sonucu TEKRAR bağlama ekleyen bir SİSTEMDİR. Bu, DÜŞÜN -> ARAÇ SEÇ -> ÇALIŞTIR -> GÖZLEMLE döngüsünü, görev BİTENE kadar TEKRARLAR — 11. dersteki RAG, TEK araçlı (getirme) bir ajanın ÖZEL bir HÂLİDİR.

Mekanizma

Önce İKİ GERÇEK araç kuralım. İlki bir hesap makinesi — ama NEDEN ham eval() yerine GÜVENLİ bir ayrıştırıcı kullanıyoruz?

Güvenli hesap makinesi: eval() yerine AST

# Bir "ajan" (agent), model çıktısının bir kısmını "ARAÇ ÇAĞRISI" (tool call) olarak
# YORUMLAYIP GERÇEK bir fonksiyon çalıştıran, sonucu TEKRAR bağlama EKLEYEN bir sistemdir.
# İlk ARAÇ: güvenli bir hesap makinesi. NEDEN ham eval() KULLANMIYORUZ?
OPS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul,
       ast.Div: operator.truediv, ast.Pow: operator.pow, ast.USub: operator.neg}

def guvenli_hesapla(ifade):
    def degerlendir(dugum):
        if isinstance(dugum, ast.Constant):
            return dugum.value
        if isinstance(dugum, ast.BinOp):
            return OPS[type(dugum.op)](degerlendir(dugum.left), degerlendir(dugum.right))
        if isinstance(dugum, ast.UnaryOp):
            return OPS[type(dugum.op)](degerlendir(dugum.operand))
        raise ValueError(f"Desteklenmeyen ifade: {ast.dump(dugum)}")
    agac = ast.parse(ifade, mode="eval")
    return degerlendir(agac.body)

print(f"guvenli_hesapla('30 * 2') = {guvenli_hesapla('30 * 2')}")

# Ham eval() KULLANSAYDIK ne olurdu? Model çıktısı (ya da KÖTÜ niyetli bir kullanıcı)
# ZARARLI bir "ifade" göndererek KEYFİ kod ÇALIŞTIRABİLİRDİ:
ZARARLI_GIRDI = '__import__("os").system("echo bu calismamali")'
try:
    guvenli_hesapla(ZARARLI_GIRDI)
    print("GÜVENLİK AÇIĞI: zararlı kod çalıştı!")
except (ValueError, KeyError, SyntaxError) as e:
    print(f"\nZararlı girdi ('{ZARARLI_GIRDI}') GÜVENLİ biçimde REDDEDİLDİ: {type(e).__name__}")
print("Sadece SAYI ve ARİTMETİK işlem düğümlerine (BinOp) izin veren bir ayrıştırıcı (AST),")
print("ham eval()'ın açtığı KEYFİ KOD ÇALIŞTIRMA riskini TAMAMEN kapatıyor.")
guvenli_hesapla('30 * 2') = 60

Zararlı girdi ('__import__("os").system("echo bu calismamali")') GÜVENLİ biçimde REDDEDİLDİ: ValueError
Sadece SAYI ve ARİTMETİK işlem düğümlerine (BinOp) izin veren bir ayrıştırıcı (AST),
ham eval()'ın açtığı KEYFİ KOD ÇALIŞTIRMA riskini TAMAMEN kapatıyor.

İkinci araç, 11. dersteki AYNI belge getirme motoru:

İkinci araç: belge getirme

# İkinci ARAÇ: 11. dersteki AYNI TF-IDF getirme motoru.
BELGELER = [
    "Academyou AI, yapay zeka eğitimi sunan bir platformdur.",
    "Academyou AI ücretsiz derslerini 2026 yılında yayınlamaya başladı.",
    "Academyou AI platformunda Türkçe dilinde 150'den fazla ders bulunur.",
    "İade politikamıza göre, satın alınan ürünler 30 gün içinde iade edilebilir.",
    "Müşteri destek ekibimize hafta içi 09.00-18.00 arasında ulaşabilirsiniz.",
    "Şirketimiz 2023 yılında İstanbul'da kuruldu.",
]

def tokenize(s):
    return s.lower().replace(".", "").replace(",", "").replace("'", "").split()

def tf(belge):
    kelimeler = tokenize(belge)
    return {k: v / len(kelimeler) for k, v in Counter(kelimeler).items()}

def idf(belgeler):
    N = len(belgeler)
    sayim = Counter()
    for b in belgeler:
        for k in set(tokenize(b)):
            sayim[k] += 1
    return {k: math.log(N / v) for k, v in sayim.items()}

idf_degerleri = idf(BELGELER)
tum_kelimeler = sorted(set(k for b in BELGELER for k in tokenize(b)))

def tfidf_vektor(belge):
    tf_d = tf(belge)
    return [tf_d.get(k, 0) * idf_degerleri.get(k, 0) for k in tum_kelimeler]

def kosinus(a, b):
    dot = sum(x * y for x, y in zip(a, b))
    na, nb = math.sqrt(sum(x*x for x in a)), math.sqrt(sum(x*x for x in b))
    return dot / (na * nb + 1e-10)

def belge_ara(sorgu):
    sv = tfidf_vektor(sorgu)
    benzerlikler = [(i, kosinus(sv, tfidf_vektor(b))) for i, b in enumerate(BELGELER)]
    en_iyi = max(benzerlikler, key=lambda x: x[1])
    return BELGELER[en_iyi[0]], en_iyi[1]

sonuc, skor = belge_ara("iade süresi")
print(f"\nbelge_ara('iade süresi') = (\"{sonuc}\", {skor:.3f})")

belge_ara('iade süresi') = ("İade politikamıza göre, satın alınan ürünler 30 gün içinde iade edilebilir.", 0.302)

Matematik

ReAct döngüsü
Du¨s¸u¨ncetEylemtGo¨zlemtDu¨s¸u¨ncet+1Son Cevap\text{Düşünce}_t \to \text{Eylem}_t \to \text{Gözlem}_t \to \text{Düşünce}_{t+1} \to \dots \to \text{Son Cevap}

Her Go¨zlemt\text{Gözlem}_t (araç sonucu), bir SONRAKİ adımın BAĞLAMININ parçası olur — 17. dersteki (NLP kategorisi) otoregresif üretimle AYNI ilke, şimdi ARAÇ SONUÇLARIYLA genişletilmiş bir bağlamda çalışıyor.

Kod

Şimdi HER İKİ aracı da kullanan tam bir ajan döngüsünü çalıştıralım — model bu ortamda GERÇEK bir LLM olmadığı için “düşünce” adımları ÖĞRETİCİ amaçlı senaryolandırıldı, ama ARAÇ ÇAĞRILARININ SONUÇLARI GERÇEKTİR:

İki araçlı bir ajan döngüsü

# Bir AJAN DÖNGÜSÜ (ReAct tarzı): DÜŞÜN -> ARAÇ SEÇ -> ÇALIŞTIR -> GÖZLEMLE -> TEKRARLA
# -> SON CEVAP. Model bu ortamda GERÇEK bir LLM olmadığı için, "DÜŞÜNCE" adımları
# ÖĞRETİCİ amaçlı ELLE senaryolandırıldı -- ama ARAÇ ÇAĞRILARININ SONUÇLARI GERÇEKTİR.
SORU = "İade süresi kaç gün, ve bu sürenin 2 katı kaç gün eder?"
print(f"\nSoru: \"{SORU}\"\n")

print("Adım 1 -- DÜŞÜNCE: Önce iade süresini bulmam lazım.")
print("Adım 1 -- ARAÇ ÇAĞRISI: belge_ara(\"iade süresi\")")
belge, skor = belge_ara("iade süresi")
print(f"Adım 1 -- GÖZLEM: \"{belge}\" (benzerlik: {skor:.3f})")

print("\nAdım 2 -- DÜŞÜNCE: Belgede '30 gün' yazıyor. Şimdi 30'un 2 katını hesaplamalıyım.")
print("Adım 2 -- ARAÇ ÇAĞRISI: guvenli_hesapla(\"30 * 2\")")
iki_kati = guvenli_hesapla("30 * 2")
print(f"Adım 2 -- GÖZLEM: {iki_kati}")

print(f"\nSON CEVAP: İade süresi 30 gün; bunun 2 katı {iki_kati} gün eder.")
print("\nBu döngüde İKİ araç (getirme + hesaplama) SIRAYLA çağrıldı -- HER aracın SONUCU,")
print("BİR SONRAKİ adımın bağlamının PARÇASI oldu (17. dersteki otoregresif üretimle AYNI ilke,")
print("şimdi ARAÇ SONUÇLARIYLA genişletilmiş bir bağlamda).")

Soru: "İade süresi kaç gün, ve bu sürenin 2 katı kaç gün eder?"

Adım 1 -- DÜŞÜNCE: Önce iade süresini bulmam lazım.
Adım 1 -- ARAÇ ÇAĞRISI: belge_ara("iade süresi")
Adım 1 -- GÖZLEM: "İade politikamıza göre, satın alınan ürünler 30 gün içinde iade edilebilir." (benzerlik: 0.302)

Adım 2 -- DÜŞÜNCE: Belgede '30 gün' yazıyor. Şimdi 30'un 2 katını hesaplamalıyım.
Adım 2 -- ARAÇ ÇAĞRISI: guvenli_hesapla("30 * 2")
Adım 2 -- GÖZLEM: 60

SON CEVAP: İade süresi 30 gün; bunun 2 katı 60 gün eder.

Bu döngüde İKİ araç (getirme + hesaplama) SIRAYLA çağrıldı -- HER aracın SONUCU,
BİR SONRAKİ adımın bağlamının PARÇASI oldu (17. dersteki otoregresif üretimle AYNI ilke,
şimdi ARAÇ SONUÇLARIYLA genişletilmiş bir bağlamda).

Son olarak, 2. dersteki bağlam penceresi uyarısının ajan döngülerinde NASIL büyüdüğüne bakalım:

Her araç çağrısı, bağlamı büyütür

# 2. dersteki UYARIYI HATIRLA: her ajan ADIMI, bağlama YENİ token'lar ekler -- uzun bir
# ajan döngüsü, AYNI karesel maliyet büyümesi riskini TAŞIR.
adim_token_maliyetleri = [15, 22, 18, 25]  # ÖĞRETİCİ amaçlı örnek (her adımın YAKLAŞIK token maliyeti)
kumulatif = 0
print(f"\n{'Adım':<8} {'Bu adımın maliyeti':<20} {'Kümülatif bağlam'}")
for i, m in enumerate(adim_token_maliyetleri, 1):
    kumulatif += m
    print(f"{i:<8} {m:<20} {kumulatif}")
print(f"\n4 adımlık bir ajan döngüsü, bağlamı {adim_token_maliyetleri[0]}'ten {kumulatif}'e büyüttü --")
print("2. dersteki AYNI 'her tur tüm geçmişi taşır' riski, ajan döngülerinde de GEÇERLİDİR,")
print("hatta ARAÇ SONUÇLARI (belge parçaları, API yanıtları) genelde METİNDEN DAHA UZUNDUR.")

Adım     Bu adımın maliyeti   Kümülatif bağlam
1        15                   15
2        22                   37
3        18                   55
4        25                   80

4 adımlık bir ajan döngüsü, bağlamı 15'ten 80'e büyüttü --
2. dersteki AYNI 'her tur tüm geçmişi taşır' riski, ajan döngülerinde de GEÇERLİDİR,
hatta ARAÇ SONUÇLARI (belge parçaları, API yanıtları) genelde METİNDEN DAHA UZUNDUR.

Nerede işe yarar

Ajan mantığı, bu kursta öğrendiğin HER ŞEYİN (tokenizasyon, bağlam penceresi, RAG, halüsinasyon) BİR ARAYA geldiği son NOKTADIR:

  • Araç seçimi GÜVENİLİR olmalıdır — model YANLIŞ aracı çağırırsa (ya da YANLIŞ argümanla), sonuç YANLIŞ olur; bu ders ÖZELLİKLE GÜVENLİ araç TASARIMINI (ham eval() yerine kısıtlı bir ayrıştırıcı) vurguladı.
  • Her araç çağrısı, GERÇEK DÜNYADA bir ETKİ yaratabilir — bir e-posta gönderen, bir ÖDEME yapan, bir dosya SİLEN bir araç, DÖNÜŞÜ olmayan sonuçlar doğurabilir; bu yüzden GERÇEK ajan sistemlerinde, RİSKLİ eylemler genelde İNSAN onayı GEREKTİRİR.
  • Uzun ajan döngüleri, 2. dersteki karesel maliyet büyümesini HIZLANDIRIR — her araç sonucu genelde SAF metinden UZUNDUR, bu da bağlam penceresini (2. ders) DAHA HIZLI doldurur.

Bu 2 hatayı yaparsın:

  1. Bir aracın (özellikle kod çalıştıran bir aracın) girdisini DOĞRUDAN eval() gibi bir fonksiyona vermek — bu dersteki güvenli ayrıştırıcı örneği, bunun NEDEN TEHLİKELİ olduğunu gösteriyor.
  2. Ajan döngülerinin SINIRSIZ çalışabileceğini varsaymak — HEM maliyet (2. ders) HEM de HATA BİRİKİMİ riski nedeniyle, gerçek sistemler ajan döngülerine bir MAKSİMUM adım SAYISI koyar.

Kendini test et

1. Bu derste hesap makinesi aracı NEDEN ham eval() yerine bir AST (soyut sözdizimi ağacı) ayrıştırıcısıyla kuruldu?
  1. Sadece hızı artırmak için
  2. eval(), gelen bir metni DOĞRUDAN Python kodu olarak çalıştırır -- bu, model çıktısı (ya da kötü niyetli bir girdi) aracılığıyla KEYFİ kod ÇALIŞTIRILMASINA izin verir; AST ayrıştırıcısı SADECE sayı ve aritmetik işlemlere izin vererek bu riski KAPATIR (doğru cevap)
  3. AST her zaman daha hızlı çalışır
  4. eval() Türkçe karakterleri desteklemez

Neden: arac-1-hesap-makinesi bloğunda, guvenli_hesapla() zararlı bir __import__("os").system(...) girdisini GÜVENLE reddediyor -- ham eval() bunu ÇALIŞTIRIRDI.

2. Bu dersteki ajan döngüsünde, İKİ araç (belge_ara ve guvenli_hesapla) NEDEN SIRAYLA çağrılıyor, AYNI ANDA değil?
  1. Rastgele bir sıralama
  2. İkinci aracın (hesaplama) GİRDİSİ, BİRİNCİ aracın (getirme) SONUCUNA bağlıdır -- '30'un 2 katı' hesaplanmadan ÖNCE, '30 gün' bilgisinin BULUNMASI gerekir; bu, NLP kategorisinin 17. dersindeki otoregresif üretimle AYNI 'önceki adım, sonrakinin girdisi olur' ilkesidir (doğru cevap)
  3. Araçlar her zaman rastgele sırayla çalışır
  4. İki aracı aynı anda çağırmak teknik olarak imkansızdır

Neden: ajan-dongusu bloğunda Adım 2'nin hesaplaması (30 * 2), Adım 1'in GÖZLEMİNDEN (30 gün) gelen bilgiye DAYANIYOR -- MathBox'taki ReAct formülü bu SIRALI bağımlılığı gösteriyor.

3. Ajan döngülerinde 'GERÇEK DÜNYADA etki yaratan' bir araç çağrısı (örn. e-posta gönderme) NEDEN özellikle DİKKAT gerektirir?
  1. Böyle araçlar her zaman yasaktır
  2. Bu tür eylemler genelde GERİ ALINAMAZ (irreversible) sonuçlar doğurur -- model YANLIŞ bir kararla bu aracı çağırırsa, SONUÇ düzeltilemeyebilir; bu yüzden riskli eylemler genelde İNSAN onayı gerektirir (doğru cevap)
  3. Bu tür araçlar daha yavaş çalışır
  4. Bu tür araçlar token maliyetini etkilemez

Neden: kullanım bölümünde AÇIKÇA belirtildiği gibi, bir e-posta gönderen ya da ödeme yapan bir aracın DÖNÜŞÜ olmayan sonuçları olabilir -- bu da İNSAN onayı gerekliliğinin NEDENİDİR.

Özet

Özet

  • Bir ajan, model çıktısını "araç çağrısı" olarak yorumlayıp GERÇEK bir fonksiyon çalıştıran, sonucu bağlama geri EKLEYEN bir sistemdir.
  • ReAct döngüsü: Düşün -> Araç Seç -> Çalıştır -> Gözlemle -> (tekrarla) -> Son Cevap.
  • 11. dersteki RAG, TEK araçlı (getirme) bir ajanın ÖZEL bir halidir.
  • Araç girdilerini ASLA ham eval() gibi bir fonksiyona VERME -- kısıtlı bir ayrıştırıcı (AST) kullanmak, keyfi kod çalıştırma riskini kapatır.
  • Uzun ajan döngüleri, 2. dersteki karesel maliyet büyümesini HIZLANDIRIR -- gerçek sistemler bir MAKSİMUM adım sayısı ve İNSAN onay noktaları içerir.