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.
| Özellik | LLM (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
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:
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.
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
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.
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"]
}
}
}
]
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ı
| Tip | Ne Saklar | Avantaj | Dezavantaj |
|---|---|---|---|
| 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ş
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ş
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
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ı
Basit LangGraph Agent
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 03 → Research Agent: Web araması + kaynak toplama 04 → Analysis Agent: Verileri işle + çıkarım yap 05 → Writer Agent: Sonuçları formatla + yaz 06 Supervisor → Çıktıları birleştir + kalite kontrol 07 Kullanıcı ← Nihai yanıt
LangGraph ile Multi-Agent
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 | İzolasyon | Hız | Kurulum |
|---|---|---|---|
| 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
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ü
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)
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ç | Ücretsiz | Kalite | Notlar |
|---|---|---|---|
| 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
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
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
Production-Ready Agent Template
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
# 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)
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.