Tüm rehberler
Rehber Yapay Zeka 08 · Uygulama

AI Agents
& Tool Use.

LLM'i araçlarla donandır — hesap makinesi, web araması, kod çalıştırma. ReAct döngüsünü, bellek tiplerini ve çok-ajanlı orchestration'ı öğren. LangGraph ile durum makinesi olarak agent.

00 Agent Nedir?

LLM tek bir yanıt üretir; agent ise bir hedefe ulaşmak için tekrarlayan karar-hareket döngüsü çalıştıran, araçlara erişen ve durumunu hatırlayan bir sistemdir.

LLM vs Agent

Klasik bir LLM çağrısında şema şudur: bir mesaj gönderir, bir yanıt alırsın, biter. Agent ise bu süreci iteratif hale getirir. Model sadece yanıt üretmez; bir plan yapar, araç çağırır, sonucu değerlendirir, gerekirse yeniden plan yapar ve hedefe ulaşana kadar döngüyü sürdürür.

ÖzellikLLM (Tek Çağrı)Agent
Çalışma modeli Tek yanıt Döngüsel karar-hareket
Araç erişimi Yok Hesap makinesi, arama, kod, DB...
Bellek Sadece bağlam penceresi Buffer, summary, vector, entity
Planlama Yok ReAct, CoT, Tree of Thought
Hata toleransı Yok Retry, fallback, düzeltme döngüsü

Agent Bileşenleri

Model Karar verme merkezi. Araç seçimi, planlama ve son yanıt üretimi burada gerçekleşir. Güçlü modeller (GPT-4o, Claude 3.5) daha güvenilir araç seçimi yapar.
Araçlar Modelin çağırabileceği fonksiyonlar. Hesap makinesi, web araması, dosya okuma, veritabanı sorgusu, API çağrısı. Her araç JSON Schema ile tanımlanır.
Bellek Konuşma geçmişi ve öğrenilmiş bağlam. Kısa vadeli (buffer), uzun vadeli (vector DB) ve yapılandırılmış (entity) bellek tipleri vardır.
Orchestration Döngüyü yöneten çerçeve. Ne zaman duralacağına, hangi aracı seçeceğine ve çok ajanlı sistemlerde hangi ajana görev devredeceğine karar verir.

Agent Akış Diyagramı

01 Kullanıcı → Hedef / Görev
02 Agent: Durumu değerlendir (Thought)
03 Agent: Araç seç ve çağır (Action)
04 Araç: Sonuç döndür (Observation)
05 Agent: Sonucu değerlendir → Yeterli mi?
06   Hayır → 02'ye dön
07   Evet  → Nihai yanıt üret

Başarısız Agent Senaryoları

Agent sistemleri tek LLM çağrısından çok daha karmaşık hata modlarına sahiptir. Prodüksiyona almadan önce bu senaryolar için savunma mekanizmaları kur:

Hallüsinasyon spirali Model yanlış bir araç çıktısı "hayal eder", o çıktıya dayanarak yanlış karar alır, zincirleme hata büyür. Çözüm: araç sonuçlarını doğrula, max_iterations sınırla.
Sonsuz döngü Model aynı aracı defalarca çağırır veya bir sonuca ulaşamadan döner. Çözüm: maksimum iterasyon sayısı ve her adımı loglama.
Yanlış araç seçimi Model hesap makinesi yerine web araması çağırır. Çözüm: araç açıklamalarını çok net yaz, belirsiz araçları kaldır.
Maliyet patlaması Her araç çağrısı yeni bir LLM turuna yol açar. Çözüm: max_tool_calls parametresi ve maliyet izleme.

01 ReAct Döngüsü

ReAct = Reason + Act: model her adımda önce ne yapacağını açıklar, sonra yapar, sonucu gözlemler ve tekrar düşünür.

Reason + Act + Observe

ReAct (Yao ve ark., 2022), modelin araç çağrısından önce bir "düşünce" üretmesini zorunlu kılar. Bu düşünce adımı hem hata ayıklamayı kolaylaştırır hem de modelin mantığını dışa vurur. İzlenebilirlik, prodüksiyon debug süreçlerinin temelidir.

react_template.txt
Soru: İstanbul'un nüfusu Paris'ten kaç kat fazla?

Thought 1: İstanbul ve Paris'in nüfusunu bilmem gerekiyor.
            İstanbul nüfusunu araştırayım.
Action 1:  web_search("İstanbul nüfusu 2024")
Observation 1: İstanbul nüfusu yaklaşık 15.5 milyon (2024).

Thought 2: Şimdi Paris nüfusunu araştırmalıyım.
Action 2:  web_search("Paris nüfusu 2024")
Observation 2: Paris nüfusu yaklaşık 2.1 milyon (kent merkezi, 2024).

Thought 3: 15.5 / 2.1 ≈ 7.38. Hesap makinesini kullanayım.
Action 3:  calculate("15.5 / 2.1")
Observation 3: 7.38

Thought 4: Yeterli bilgiye sahibim. Yanıtı verebilirim.
Answer:    İstanbul'un nüfusu Paris'ten yaklaşık 7.4 kat fazladır.

Manual ReAct Loop

manual_react.py
import json, re
from openai import OpenAI

client = OpenAI()

REACT_SYSTEM = """
Araçları kullanarak görevleri adım adım çöz.

Her adımda şu formatı kullan:
Thought: [ne yapacağına dair düşünce]
Action: [araç_adı({"param": "değer"})]

Cevabı bildiğinde:
Answer: [nihai yanıt]
"""

def extract_action(text: str):
    # "Action: araç_adı({...})" satırını ayrıştır
    match = re.search(r'Action:\s*(\w+)\((\{.*?\})\)', text, re.DOTALL)
    if match:
        return match.group(1), json.loads(match.group(2))
    return None, None

def calculate(expression: str) -> str:
    try:
        return str(eval(expression, {"__builtins__": {}}))
    except:
        return "Hata: Geçersiz ifade"

TOOLS = {"calculate": calculate}

messages = [
    {"role": "system", "content": REACT_SYSTEM},
    {"role": "user", "content": "(12 + 8) * 3 - 15 nedir?"}
]

for step in range(5):          # max 5 iterasyon
    resp = client.chat.completions.create(
        model="gpt-4o", messages=messages, max_tokens=256
    )
    reply = resp.choices[0].message.content
    messages.append({"role": "assistant", "content": reply})

    if "Answer:" in reply:
        answer = reply.split("Answer:")[1].strip()
        print(f"Sonuç: {answer}")
        break

    tool_name, tool_args = extract_action(reply)
    if tool_name and tool_name in TOOLS:
        observation = TOOLS[tool_name](**tool_args)
        messages.append({
            "role": "user",
            "content": f"Observation: {observation}"
        })
else:
    print("Max iterasyona ulaşıldı.")

02 Tool Definition

Araç tanımı sadece bir fonksiyon imzası değil — modelin "ne zaman ve nasıl kullanacağını" öğrendiği dokümandır; açıklama kalitesi araç seçim doğruluğunu belirler.

JSON Schema Formatı

Her araç üç zorunlu alan içerir: name, description ve parameters. Model, aracın ne zaman kullanılacağına karar verirken yalnızca bu tanımı okur — kaynak kodu görmez. Bu yüzden description alanı kritiktir.

tool_definitions.py
TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "calculate",
            "description": (
                "Matematiksel bir ifadeyi hesaplar. "
                "Aritmetik işlemler (+, -, *, /), üs alma (**), "
                "parantez içeren ifadeler. "
                "Trigonometri veya istatistik için kullanMA."
            ),
            "parameters": {
                "type": "object",
                "properties": {
                    "expression": {
                        "type": "string",
                        "description": "Python formatında ifade. Örn: '2 ** 10 + 5'"
                    }
                },
                "required": ["expression"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "web_search",
            "description": (
                "İnternette güncel bilgi arar. "
                "Gerçek zamanlı veriler (haber, fiyat, hava), "
                "eğitim verisinde bulunmayan bilgiler için kullan. "
                "Genel bilgi için kullanMA — önce kendi bilgini dene."
            ),
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "Arama sorgusu. Spesifik ve kısa tut."
                    },
                    "num_results": {
                        "type": "integer",
                        "description": "Döndürülecek sonuç sayısı. Varsayılan: 3",
                        "default": 3
                    }
                },
                "required": ["query"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "run_python",
            "description": (
                "Güvenli sandbox içinde Python kodu çalıştırır. "
                "Veri analizi, dosya işleme, hesaplama için kullan. "
                "Ağ erişimi ve dosya yazma YASAK."
            ),
            "parameters": {
                "type": "object",
                "properties": {
                    "code": {
                        "type": "string",
                        "description": "Çalıştırılacak Python kodu"
                    }
                },
                "required": ["code"]
            }
        }
    }
]
NOT

Araç açıklamasında "ne zaman kullanılmaması gerektiği" de belirtilmeli. "Trigonometri için kullanMA", "genel bilgi için kullanMA" gibi negatif örnekler modelin yanlış araç seçimini engeller. Açıklama ne kadar spesifik olursa araç seçim doğruluğu o kadar artar.

03 Bellek Tipleri

Agent hangi konuşmaları ve bağlamları hatırlayacak? Dört farklı bellek tipi, farklı trade-off'larla bu soruya yanıt verir.

Bellek Tipleri Karşılaştırması

TipNe SaklarAvantajDezavantaj
Buffer Tüm konuşma geçmişi Kayıpsız, hassas Context window dolar
Summary LLM ile özetlenmiş geçmiş Uzun konuşmalar sığar Ayrıntı kaybolabilir
Vector Embedding ile aranabilir anılar Uzun vadeli, semantik arama Kurulum karmaşıklığı
Entity Kişi/yer/konu bilgileri Yapılandırılmış hafıza Entity tanıma hataları

Buffer Memory — Tüm Geçmiş

buffer_memory.py
from langchain.memory import ConversationBufferMemory
from langchain_openai import ChatOpenAI
from langchain.chains import ConversationChain

llm = ChatOpenAI(model="gpt-4o", temperature=0.3)
memory = ConversationBufferMemory(return_messages=True)

chain = ConversationChain(llm=llm, memory=memory, verbose=True)

chain.predict(input="Adım Emirhan. Yazılım mühendisiyim.")
chain.predict(input="Python'da sevdiğim şey nedir?")  # önceki bağlamı hatırlar

# Belleği görüntüle
for msg in memory.chat_memory.messages:
    print(f"{msg.type}: {msg.content[:50]}...")

# Token kullanımını kontrol et — uzun konuşmalarda patlar
from langchain.callbacks import get_openai_callback
with get_openai_callback() as cb:
    chain.predict(input="Hangi şehirde yaşıyorum?")
    print(f"Toplam token: {cb.total_tokens}")

Summary Memory — Özetlenmiş Geçmiş

summary_memory.py
from langchain.memory import ConversationSummaryMemory, ConversationSummaryBufferMemory
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o")

# SummaryMemory: her turdan sonra özet güncellenir
summary_mem = ConversationSummaryMemory(llm=llm)

summary_mem.save_context(
    {"input": "Merhaba, proje bütçesi 100.000 TL."},
    {"output": "Anlaşıldı, bütçeyi kaydettim."}
)
summary_mem.save_context(
    {"input": "Teslim tarihi 30 Haziran."},
    {"output": "Teslim tarihi de kaydedildi."}
)

print(summary_mem.load_memory_variables({})["history"])
# "İnsan proje bütçesinin 100.000 TL ve teslim tarihinin
#  30 Haziran olduğunu bildirdi. AI bunları onayladı."

# SummaryBufferMemory: son N mesajı tam, öncesini özetlenmiş tutar
hybrid_mem = ConversationSummaryBufferMemory(
    llm=llm,
    max_token_limit=200  # 200 tokeni geçince özetlemeye başla
)

Vector Memory — Semantik Arama ile Uzun Vadeli Bellek

vector_memory.py
from langchain.memory import VectorStoreRetrieverMemory
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS

# Embedding modeli ve vektör deposu
embeddings = OpenAIEmbeddings()
vectorstore = FAISS.from_texts(["placeholder"], embedding=embeddings)
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})

memory = VectorStoreRetrieverMemory(retriever=retriever)

# Anıları kaydet
memory.save_context(
    {"input": "Müşterimiz Bosch ile toplantı yarın saat 14:00'de."},
    {"output": "Toplantı takvime eklendi."}
)
memory.save_context(
    {"input": "Proje kod adı: Phoenix."},
    {"output": "Kod adı kaydedildi."}
)

# Semantik arama — sadece ilgili anıları getirir
result = memory.load_memory_variables({"prompt": "Bosch toplantısı ne zaman?"})
print(result["history"])  # Yalnızca Bosch ile ilgili bağlamı döndürür

04 LangGraph ile Durum Makinesi

LangGraph, agent'ı açık bir durum makinesi olarak modeller — her adım bir node, geçişler koşullu edge, paylaşılan durum ise tüm döngüyü birbirine bağlar.

LangGraph Temel Kavramları

Node Python fonksiyonu. Durumu alır, işler, günceller. LLM çağrısı, araç çalıştırma veya koşul kontrolü bir node olabilir.
Edge Nodelar arası geçiş. Koşullu edge'ler durum değerine göre farklı nodlara yönlendirir — "araç çağrısı varsa execute_tool'a git, yoksa END'e git" gibi.
State TypedDict ile tanımlanan paylaşılan veri yapısı. Her node bu durumu okur ve yazar. Mesaj geçmişi, araç sonuçları, ara değerler burada yaşar.
Döngüsel Graf LangGraph, döngüsel grafları destekler. Agent, aracı çalıştırdıktan sonra LLM node'una geri dönebilir — bu sayede gerçek iteratif akış mümkündür.

Basit LangGraph Agent

langgraph_agent.py
from typing import TypedDict, Annotated, List
import operator, json
from langchain_core.messages import BaseMessage, HumanMessage, ToolMessage
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, END

# 1. Durum şemasını tanımla
class AgentState(TypedDict):
    messages: Annotated[List[BaseMessage], operator.add]

# 2. Model ve araçları hazırla
llm = ChatOpenAI(model="gpt-4o")

def calculate(expression: str) -> str:
    try:
        return str(eval(expression, {"__builtins__": {}}))
    except Exception as e:
        return f"Hata: {e}"

# 3. LLM'i araçlarla bağla
from langchain_core.tools import tool

@tool
def calculator_tool(expression: str) -> str:
    """Matematiksel ifadeyi hesaplar."""
    return calculate(expression)

llm_with_tools = llm.bind_tools([calculator_tool])

# 4. Node fonksiyonlarını tanımla
def call_llm(state: AgentState) -> AgentState:
    response = llm_with_tools.invoke(state["messages"])
    return {"messages": [response]}

def execute_tools(state: AgentState) -> AgentState:
    last_msg = state["messages"][-1]
    tool_messages = []
    for tc in last_msg.tool_calls:
        result = calculate(tc["args"]["expression"])
        tool_messages.append(ToolMessage(
            content=result, tool_call_id=tc["id"]
        ))
    return {"messages": tool_messages}

# 5. Koşullu edge fonksiyonu
def should_continue(state: AgentState) -> str:
    last = state["messages"][-1]
    if hasattr(last, "tool_calls") and last.tool_calls:
        return "execute_tools"
    return END

# 6. Graf inşa et
graph_builder = StateGraph(AgentState)
graph_builder.add_node("llm", call_llm)
graph_builder.add_node("execute_tools", execute_tools)
graph_builder.set_entry_point("llm")
graph_builder.add_conditional_edges("llm", should_continue)
graph_builder.add_edge("execute_tools", "llm")  # döngü!

agent = graph_builder.compile()

# 7. Çalıştır
result = agent.invoke({
    "messages": [HumanMessage(content="(144 / 12) ** 2 nedir?")]
})
print(result["messages"][-1].content)  # "Sonuç: 144"

05 Multi-Agent Orchestration

Tek bir agent her şeyi yapamaz — uzmanlaşmış ajanlara görev dağıtmak hem kaliteyi artırır hem de paralel çalışmayı mümkün kılar.

Neden Çok Ajanlı Sistem?

Tek bir büyük agent karmaşık görevlerde yön kaybeder, bağlam penceresi dolar, ve hataları tüm süreci bozar. Supervisor paterni, bir orchestrator ajanın alt uzman ajanları koordine ettiği hiyerarşik bir mimari sunar.

01 Kullanıcı → Supervisor Agent
02 Supervisor → Görevi analiz et ve böl
03Research Agent: Web araması + kaynak toplama
04Analysis Agent: Verileri işle + çıkarım yap
05Writer Agent: Sonuçları formatla + yaz
06 Supervisor → Çıktıları birleştir + kalite kontrol
07 Kullanıcı ← Nihai yanıt

LangGraph ile Multi-Agent

multi_agent.py
from typing import TypedDict, Literal
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage
from langgraph.graph import StateGraph, END

llm = ChatOpenAI(model="gpt-4o")

class ResearchState(TypedDict):
    topic: str
    research_notes: str
    draft: str
    final_report: str
    next_agent: str

# Araştırma ajanı — web araması simüle eder
def research_agent(state: ResearchState) -> ResearchState:
    response = llm.invoke([
        SystemMessage(content="Sen bir araştırma uzmanısın. Verilen konuyu araştır ve anahtar bulguları listele."),
        HumanMessage(content=f"Konu: {state['topic']}")
    ])
    return {
        "research_notes": response.content,
        "next_agent": "writer"
    }

# Yazı ajanı — araştırma notlarını rapor haline getirir
def writer_agent(state: ResearchState) -> ResearchState:
    response = llm.invoke([
        SystemMessage(content="Sen teknik bir yazar sın. Araştırma notlarını düzenli bir rapora dönüştür."),
        HumanMessage(content=f"Araştırma notları:\n{state['research_notes']}")
    ])
    return {
        "draft": response.content,
        "next_agent": "supervisor"
    }

# Supervisor — kalite kontrolü ve görev yönlendirme
def supervisor(state: ResearchState) -> ResearchState:
    if not state.get("research_notes"):
        return {"next_agent": "research"}
    elif not state.get("draft"):
        return {"next_agent": "writer"}
    else:
        return {
            "final_report": state["draft"],
            "next_agent": "END"
        }

def route(state: ResearchState) -> str:
    return state["next_agent"]

# Graf inşa et
g = StateGraph(ResearchState)
g.add_node("supervisor", supervisor)
g.add_node("research", research_agent)
g.add_node("writer", writer_agent)
g.set_entry_point("supervisor")
g.add_conditional_edges("supervisor", route, {
    "research": "research",
    "writer": "writer",
    "END": END
})
g.add_edge("research", "supervisor")
g.add_edge("writer", "supervisor")

multi_agent = g.compile()
result = multi_agent.invoke({"topic": "Kuantum bilgisayarların güncel durumu"})
print(result["final_report"])

Handoff Protokolü

Ajanlar arası görev devri (handoff) net bir protokol gerektirir. Her ajan hangi çıktıyı üreteceğini ve bir sonraki ajana ne ileteceğini bilmelidir. LangGraph'ın Command ve interrupt mekanizması, ajanlar arası handoff'u kontrollü şekilde yönetir.

06 Code Execution Agent

Model Python kodu yazabilir ama çalıştıramaز — sandbox içinde kodu çalıştıran ve hataları iteratif olarak düzelten bir agent, LLM'in en güçlü uygulamalarından biridir.

Sandboxing Seçenekleri

YöntemİzolasyonHızKurulum
exec() doğrudan Yok — çok tehlikeli Anında Yok
subprocess + timeout Süreç izolasyonu ~50ms overhead Minimal
Docker container OS seviyesi izolasyon ~500ms başlatma Docker gerekli
E2B Sandbox Bulut VM izolasyonu ~1-2s başlatma E2B API key

Güvenli Subprocess Sandbox

code_sandbox.py
import subprocess, textwrap, tempfile, os
from pathlib import Path

def run_python_safe(code: str, timeout: int = 10) -> dict:
    """Python kodunu geçici dosyada subprocess ile çalıştır."""
    # Tehlikeli kalıpları engelle
    FORBIDDEN = ["import os", "import sys", "__import__",
                 "open(", "subprocess", "socket", "requests"]
    for pattern in FORBIDDEN:
        if pattern in code:
            return {"error": f"Yasak kalıp: {pattern}", "stdout": ""}

    with tempfile.NamedTemporaryFile(
        mode="w", suffix=".py", delete=False
    ) as f:
        f.write(code)
        tmp_path = f.name

    try:
        result = subprocess.run(
            ["python", tmp_path],
            capture_output=True,
            text=True,
            timeout=timeout
        )
        return {
            "stdout": result.stdout,
            "stderr": result.stderr,
            "returncode": result.returncode,
            "error": result.stderr if result.returncode != 0 else None
        }
    except subprocess.TimeoutExpired:
        return {"error": f"Timeout: {timeout}s aşıldı", "stdout": ""}
    finally:
        os.unlink(tmp_path)

Hata Yakalayan Düzeltme Döngüsü

code_fix_loop.py
from openai import OpenAI

client = OpenAI()

CODE_SYSTEM = """
Sen bir Python programcısısın. Kullanıcının görevini çözen Python kodu yaz.
Yanıtını YALNIZCA şu formatta ver:
```python
[kod buraya]
```
Açıklama ekleme.
"""

FIX_SYSTEM = """
Bir Python kodu hata verdi. Kodu düzelt ve YALNIZCA düzeltilmiş kodu döndür.
Açıklama ekleme.
"""

def extract_code(text: str) -> str:
    import re
    match = re.search(r'```python\n(.*?)```', text, re.DOTALL)
    return match.group(1).strip() if match else text.strip()

def coding_agent(task: str, max_retries: int = 3) -> str:
    # 1. İlk kodu üret
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": CODE_SYSTEM},
            {"role": "user", "content": task}
        ]
    )
    code = extract_code(response.choices[0].message.content)

    for attempt in range(max_retries):
        result = run_python_safe(code)

        if not result["error"]:
            print(f"Başarılı ({attempt+1}. denemede)")
            return result["stdout"]

        # Hata varsa düzelt
        print(f"Hata (deneme {attempt+1}): {result['error'][:100]}")
        fix_response = client.chat.completions.create(
            model="gpt-4o",
            messages=[
                {"role": "system", "content": FIX_SYSTEM},
                {"role": "user", "content": (
                    f"Hatalı kod:\n```python\n{code}\n```\n\n"
                    f"Hata mesajı:\n{result['error']}"
                )}
            ]
        )
        code = extract_code(fix_response.choices[0].message.content)

    return f"Max deneme aşıldı. Son hata: {result['error']}"

# Kullanım
output = coding_agent("1'den 100'e kadar asal sayıları listele.")
print(output)
DİKKAT

Production'da exec() ve regex tabanlı filtreleme yeterli güvenlik sağlamaz. E2B veya Docker tabanlı sandbox kullan. Özellikle kullanıcıdan gelen kodları hiçbir zaman doğrudan çalıştırma.

07 Web Arama Agent

Model eğitim verisi dışındaki güncel bilgilere erişmek için web arama aracına ihtiyaç duyar — doğru araç seçimi ve kaynak atıfı güvenilirliği belirler.

Arama Araçları Karşılaştırması

AraçÜcretsizKaliteNotlar
Tavily 1000 req/ay ücretsiz LLM odaklı, yüksek Agent için optimize edilmiş
SerpAPI 100 req/ay ücretsiz Google sonuçları En geniş kapsam
DuckDuckGo Tamamen ücretsiz Orta duckduckgo-search kütüphanesi
Wikipedia Tamamen ücretsiz Ansiklopedik wikipedia kütüphanesi

Tavily ile Web Arama Agent

search_agent.py
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

# Tavily araç — güncel web araması
search_tool = TavilySearchResults(
    max_results=3,
    include_answer=True,      # Tavily AI özeti de ekle
    include_raw_content=False, # Ham HTML alma, token tasarrufu
    include_images=False
)

# Wikipedia araç — ansiklopedik bilgi
from langchain_community.tools import WikipediaQueryRun
from langchain_community.utilities import WikipediaAPIWrapper

wiki_tool = WikipediaQueryRun(
    api_wrapper=WikipediaAPIWrapper(top_k_results=2, doc_content_chars_max=2000)
)

tools = [search_tool, wiki_tool]

llm = ChatOpenAI(model="gpt-4o", temperature=0)

prompt = ChatPromptTemplate.from_messages([
    ("system", (
        "Sen kapsamlı bir araştırma asistanısın. "
        "Her iddiayı arama sonuçlarıyla destekle. "
        "Kaynakları URL olarak belirt. "
        "Bilmediğin şeyleri uydurma, ara."
    )),
    MessagesPlaceholder(variable_name="chat_history", optional=True),
    ("human", "{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad"),
])

agent = create_openai_tools_agent(llm, tools, prompt)
executor = AgentExecutor(
    agent=agent,
    tools=tools,
    verbose=True,        # Her adımı yazdır
    max_iterations=5,   # Güvenlik sınırı
    return_intermediate_steps=True
)

result = executor.invoke({
    "input": "2025 yılında Türkiye'nin en büyük teknoloji şirketleri hangileri?"
})

print(result["output"])
# Kaynaklar da dahil yanıt gelir

DuckDuckGo — API Key Olmadan Arama

ddg_search.py
from duckduckgo_search import DDGS

def ddg_search(query: str, max_results: int = 5) -> list[dict]:
    """DuckDuckGo ile web araması yap."""
    with DDGS() as ddgs:
        results = list(ddgs.text(
            query,
            max_results=max_results,
            region="tr-tr"  # Türkçe sonuçlar için
        ))
    return [{
        "title": r["title"],
        "url": r["href"],
        "snippet": r["body"]
    } for r in results]

# Sonuçları modele özetle
from openai import OpenAI
client = OpenAI()

def search_and_summarize(question: str) -> str:
    results = ddg_search(question)
    context = "\n\n".join([
        f"Kaynak: {r['url']}\n{r['snippet']}"
        for r in results
    ])

    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": "Verilen kaynaklara dayanarak soruyu yanıtla. Kaynakları belirt."},
            {"role": "user", "content": f"Soru: {question}\n\nKaynaklar:\n{context}"}
        ]
    )
    return response.choices[0].message.content

print(search_and_summarize("Python 3.13 yenilikleri neler?"))

08 Production Agent

Prodüksiyon agent'ı laboratuvar agent'ından temelden farklıdır: timeout, retry, structured logging, human-in-the-loop ve maliyet kontrolü olmadan güvenilir çalışmaz.

Üretim Ortamı Gereksinimleri

Timeout Her araç çağrısı ve LLM çağrısı için timeout. Ağ sorunları, yavaş API'ler agent'ı sonsuza kadar bekletebilir.
Retry + Backoff Rate limit ve geçici hatalar için üstel geri çekilme ile yeniden deneme. tenacity kütüphanesi bunu kolaylaştırır.
Max Iterations Agent sonsuz döngüye girmesin diye her oturum için maksimum adım sayısı. Genellikle 10–20 yeterli.
Structured Logging Her düşünce, araç çağrısı ve araç sonucu JSON olarak loglanmalı. Post-mortem analizi için zorunlu.
Human-in-the-Loop Kritik adımlarda insan onayı al. Para transferi, dosya silme gibi geri alınamaz işlemler otomatik yapılmamalı.
Cost Control Her oturum için maksimum token ve maksimum araç çağrısı sınırı. Maliyet patlamaları agent sistemlerinde yaygındır.

Production-Ready Agent Template

production_agent.py
import logging, json, time
from dataclasses import dataclass, field
from typing import Any, Callable
from openai import OpenAI, RateLimitError, APITimeoutError

# Structured JSON logger
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("agent")

def log_step(event: str, **kwargs):
    logger.info(json.dumps({"event": event, "ts": time.time(), **kwargs}))


@dataclass
class AgentConfig:
    model: str = "gpt-4o"
    max_iterations: int = 15
    max_tool_calls: int = 20
    llm_timeout: float = 30.0    # saniye
    tool_timeout: float = 10.0
    max_retries: int = 3
    retry_base_delay: float = 1.0
    require_human_approval: list[str] = field(default_factory=lambda: [])


class ProductionAgent:
    def __init__(self, tools: list, config: AgentConfig = None):
        self.client = OpenAI()
        self.tool_registry: dict[str, Callable] = {}
        self.tool_definitions = []
        self.config = config or AgentConfig()
        self.tool_call_count = 0
        self.total_tokens = 0

        for t in tools:
            self.tool_registry[t["name"]] = t["fn"]
            self.tool_definitions.append(t["definition"])

    def _call_llm_with_retry(self, messages: list) -> Any:
        for attempt in range(self.config.max_retries):
            try:
                resp = self.client.chat.completions.create(
                    model=self.config.model,
                    messages=messages,
                    tools=self.tool_definitions,
                    tool_choice="auto",
                    timeout=self.config.llm_timeout,
                )
                self.total_tokens += resp.usage.total_tokens
                return resp
            except RateLimitError:
                delay = self.config.retry_base_delay * (2 ** attempt)
                log_step("rate_limit_retry", attempt=attempt, delay=delay)
                time.sleep(delay)
            except APITimeoutError:
                log_step("llm_timeout", attempt=attempt)
                if attempt == self.config.max_retries - 1:
                    raise
        raise RuntimeError("LLM max retry aşıldı.")

    def _run_tool(self, name: str, args: dict) -> str:
        if name in self.config.require_human_approval:
            print(f"\n[ONAY GEREKİYOR] Araç: {name}, Args: {args}")
            confirm = input("Devam et? (e/h): ")
            if confirm.lower() != "e":
                return "[İPTAL] Kullanıcı araç çağrısını reddetti."

        self.tool_call_count += 1
        if self.tool_call_count > self.config.max_tool_calls:
            raise RuntimeError(f"Max araç çağrısı ({self.config.max_tool_calls}) aşıldı.")

        log_step("tool_call", tool=name, args=args, count=self.tool_call_count)
        import signal

        def _timeout_handler(signum, frame):
            raise TimeoutError()

        try:
            result = self.tool_registry[name](**args)
            log_step("tool_result", tool=name, result=str(result)[:200])
            return str(result)
        except Exception as e:
            log_step("tool_error", tool=name, error=str(e))
            return f"Araç hatası: {e}"

    def run(self, user_message: str, system: str = "") -> str:
        messages = []
        if system:
            messages.append({"role": "system", "content": system})
        messages.append({"role": "user", "content": user_message})

        log_step("agent_start", query=user_message[:100])

        for iteration in range(self.config.max_iterations):
            resp = self._call_llm_with_retry(messages)
            msg = resp.choices[0].message
            messages.append(msg)

            log_step("llm_response", iteration=iteration,
                     has_tools=bool(msg.tool_calls), tokens=self.total_tokens)

            if not msg.tool_calls:
                log_step("agent_done", iterations=iteration + 1,
                         total_tokens=self.total_tokens,
                         tool_calls=self.tool_call_count)
                return msg.content

            for tc in msg.tool_calls:
                args = json.loads(tc.function.arguments)
                result = self._run_tool(tc.function.name, args)
                messages.append({
                    "role": "tool",
                    "tool_call_id": tc.id,
                    "content": result
                })

        log_step("max_iterations_reached", iterations=self.config.max_iterations)
        return "[HATA] Maksimum iterasyon aşıldı. Agent sonuca ulaşamadı."

Kullanım Örneği

agent_usage.py
# Araçları tanımla (definition + fn çifti)
tools = [
    {
        "name": "calculate",
        "fn": lambda expression: eval(expression, {"__builtins__": {}}),
        "definition": {
            "type": "function",
            "function": {
                "name": "calculate",
                "description": "Matematiksel ifadeyi hesapla.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "expression": {"type": "string"}
                    },
                    "required": ["expression"]
                }
            }
        }
    }
]

config = AgentConfig(
    max_iterations=10,
    max_tool_calls=15,
    llm_timeout=20.0,
    require_human_approval=[],    # boş = hiç onay gerektirme
)

agent = ProductionAgent(tools=tools, config=config)
answer = agent.run(
    user_message="2^10 ile 3^7 toplamı nedir?",
    system="Sen bir matematik asistanısın."
)
print(answer)
NOT

Prodüksiyon agent'larında verbose=True veya her adımın loglanması başlangıçta zorunludur. Hangi iterasyonda hangi kararın alındığını görmeden performans sorunlarını teşhis edemezsin. Log verisini OpenTelemetry veya Langfuse'a yönlendirerek görsel dashboard ile izleyebilirsin.