LangGraph ile Çok Ajanlı Text-to-SQL Chatbot: Finans Verilerinizi Doğal Dille Sorgulayın 🚀
GPT-4o-mini, LangGraph ve Chainlit ile sıfırdan uçtan uca bir yapay zeka asistanı nasıl inşa edilir?
LangGraph ile Çok Ajanlı Text-to-SQL Chatbot: Finans Verilerinizi Doğal Dille Sorgulayın 🚀
GPT-4o-mini, LangGraph ve Chainlit ile sıfırdan uçtan uca bir yapay zeka asistanı nasıl inşa edilir?
Bugün sizlere, doğal dilde sorduğunuz finansal soruları anında SQL sorgularına çeviren, veritabanında çalıştıran, sonuçları yorumlayan ve gerektiğinde interaktif grafikler üreten çok ajanlı bir chatbot projesini adım adım anlatacağım.
Bu proje, modern LLM (Large Language Model) tabanlı ajan mimarilerinin gerçek dünyada nasıl uygulanabileceğine dair uçtan uca bir örnek sunuyor. Hadi başlayalım!
📌 İçindekiler
- Proje Nedir?
- Neden Çok Ajanlı Mimari?
- Mimariye Kuşbakışı
- LangGraph İş Akışı — Derinlemesine
- 9 Ajan, 9 Görev
- Veritabanı Tasarımı
- Teknoloji Yığını
- Kurulum ve Çalıştırma
- Örnek Senaryolar
- Kodun Kalbine Yolculuk
- Karşılaşılan Zorluklar ve Çözümleri
- Sonuç ve Gelecek Vizyonu
Proje Nedir?
“Finans Asistanı”, kullanıcının Türkçe olarak sorduğu finansal soruları alıp, arka planda SQL sorgularına çeviren, bu sorguları SQLite veritabanında çalıştıran, sonuçları anlaşılır bir dille yanıtlayan ve talebe göre interaktif Plotly grafikleri üreten bir sohbet botudur.
💡 Örnek: “Bu yıl en çok harcama yaptığım 5 kategori hangisi?” → Chatbot bunu SQL’e çevirir → Veritabanında çalıştırır → Sonucu listeler → Bar chart gösterir.
Proje, 2024–2025 dönemine ait sentetik (yapay olarak üretilmiş) finans verisiyle çalışır. Veriler şunları kapsar:
- 💳 5 banka hesabı ve bakiyeleri
- 📊 15 gelir/gider kategorisi
- 🧾 Yaklaşık 510 finansal işlem
- 🎯 240 aylık bütçe kaydı
- 📄 200 fatura
Neden Çok Ajanlı Mimari?
Tek bir LLM çağrısıyla “sor → SQL üret → çalıştır → cevapla” yapmak mümkün. Peki neden 9 ayrı ajan?
Çünkü gerçek dünyada işler her zaman yolunda gitmez:

Her ajan tek bir işi çok iyi yapar (Single Responsibility Principle). Bu sayede sistem daha güvenilir, daha öngörülebilir ve hata durumunda kendi kendini düzeltebilir hale gelir.
Mimariye Kuşbakışı
Sistemin yüksek seviye mimarisi şu şekilde:

Kullanıcı → Chainlit Web Arayüzü → LangGraph Ajan Motoru → SQLite Veritabanı → Plotly Görselleştirme → Kullanıcıya Yanıt
Akış şu adımları izler:
Kullanıcı Sorusu
↓
🛡️ Guardrails Agent → "Bu soru finansla ilgili mi?"
↓ (evet)
📝 SQL Agent → Doğal dil → SQLite sorgusu
↓
🔍 SQL Validator Agent → Fan-out riski var mı? CTE dönüşümü
↓
⚙️ Execute SQL → Sorguyu finance.db üzerinde çalıştır
↓ (hata varsa)
🔧 Error Agent → Hatayı analiz et, düzelt, tekrar dene (max 3×)
↓ (başarılı)
🧠 Sanity Check Agent → Sonuçlar mantıklı mı?
↓
💬 Analysis Agent → Ham veri → okunabilir metin
↓
📊 Decide Graph Need → Grafik gerekli mi? Hangi tür?
↓ (gerekiyorsa)
📈 Viz Agent → LLM ile Plotly kodu üret ve çalıştır
↓
✅ Kullanıcıya Yanıt → Metin + SQL + İnteraktif Grafik
LangGraph İş Akışı — Derinlemesine
Projenin kalbi LangGraph ile oluşturulmuş bir state machine (durum makinesi). LangGraph, ajanlar arası geçişleri, koşullu yönlendirmeleri ve döngüleri yöneten bir orkestrasyon çerçevesidir.
LangGraph’in otomatik ürettiği gerçek iş akışı diyagramı:
State (Durum) Yapısı
Tüm ajanların paylaştığı AgentState şu alanları taşır:
class AgentState(TypedDict):
question: str # Kullanıcının sorusu
sql_query: str # Üretilen/düzeltilmiş SQL
query_result: str # Sorgu sonuçları (JSON)
final_answer: str # Kullanıcıya gösterilecek metin
error: str # Hata mesajı
iteration: int # Kaçıncı deneme
needs_graph: bool # Grafik üretilsin mi?
graph_type: str # bar | line | pie | scatter
graph_json: str # Plotly figür JSON'u
is_in_scope: bool # Finans kapsamında mı?
sanity_passed: bool # Sonuçlar mantıklı mı?
sanity_issue: str # Tespit edilen sorun
sanity_retried: bool # Döngü koruması
Koşullu Yönlendirmeler
LangGraph’te her adımdan sonra nereye gidileceği koşul fonksiyonları ile belirlenir:
# Kapsam kontrolü — devam mı, dur mu?
def check_scope(state):
return "in_scope" if state.get("is_in_scope") else "out_of_scope"
# SQL hatası - düzeltmeyi dene mi, vazgeç mi?
def should_retry(state):
if state.get("error"):
return "retry" if state.get("iteration", 0) <= 3 else "end"
return "success"
# Sanity check - SQL'i yeniden üret mi, devam mı?
def should_regenerate_sql(state):
return "regenerate" if not state.get("sanity_passed") else "proceed"
Bu yapı sayesinde iş akışı doğrusal değil, dinamik — tıpkı gerçek bir uzman ekibin çalışması gibi.
9 Ajan, 9 Görev
Her ajan, kendi uzmanlık alanında çalışan bir LLM (GPT-4o-mini) çağrısıdır. Sistem mesajları ve sıcaklık değerleri her ajan için özelleştirilmiştir.

🛡️ 1. Guardrails Agent (Kapsam Kontrolü)
Görev: Kullanıcının sorusu finans verileriyle ilgili mi, selamlama mı, yoksa kapsam dışı mı?
def guardrails_agent(state: AgentState) -> AgentState:
# Selamlama → "Merhaba! Ben Finans Asistanınım..."
# Kapsam dışı → "Üzgünüm, bu soru finans kapsamı dışında..."
# Kapsam içi → is_in_scope = True, devam et
📝 2. SQL Agent (SQL Üretimi)
Görev: Doğal dildeki soruyu geçerli bir SQLite sorgusuna çevirmek.
Bu ajan, veritabanı şemasının tamamını prompt’ta görür ve şu kritik kurallara uymak zorundadır:
- Her agregasyonu ayrı CTE’de hesapla (fan-out önlemi)
- Kategori adlarını JOIN ile getir, ham ID döndürme
- Tarih formatlamasında
printf('%04d-%02d', year, month)kullan
🔍 3. SQL Validator Agent (SQL Kalite Denetimi)
Görev: SQL çalıştırılmadan önce fan-out (kartezyen çarpım) riskini tespit etmek.
Bu ajanın en zekice yanı: önce Python ile deterministik kontrol yapar, risk yoksa LLM’i hiç çağırmaz!
def _has_fanout_risk(sql: str) -> bool:
# JOIN var mı? + Agregasyon var mı? + CTE yok mu?
# Üçü birden varsa → riskli, LLM'e gönder
# Değilse → güvenli, olduğu gibi geç
Bu yaklaşım, gereksiz LLM çağrılarını önleyerek hem maliyeti hem gecikmeyi düşürür.
⚙️ 4. Execute SQL (Sorgu Çalıştırma)
Görev: SQL’i SQLite üzerinde çalıştırmak. Noktalı virgülle ayrılmış çoklu ifadeleri sırayla işler.
🔧 5. Error Agent (Hata Kurtarma)
Görev: SQL hatası aldığında, hata mesajını ve şemayı kullanarak sorguyu düzeltmek.
# En fazla 3 deneme hakkı var
if iteration > 3:
state["final_answer"] = "Üzgünüm, bu soru için doğru SQL üretemedim..."
🧠 6. Sanity Check Agent (Sonuç Akıl Yürütme)
Görev: SQL başarıyla çalıştı ama sonuçlar gerçekten doğru mu?
Bu ajan şunları kontrol eder:
- Parasal değerler makul mü? (Maaş 18–22 bin iken sonuç milyonlar gelmemeli)
- Sayımlar gerçekçi mi? (200 fatura varken 13.000 overdue olamaz)
- Aynı kategori birden fazla listelenmiş mi? (GROUP BY hatası)
Şüpheli bir durumda sanity_passed = False yaparak SQL agent'a geri döner — ama sadece bir kez (döngü koruması).
💬 7. Analysis Agent (Sonuç Yorumlama)
Görev: Ham JSON sorgu sonuçlarını kullanıcıya yönelik doğal bir metne dönüştürmek.
# temperature=0.7 ile daha doğal ve akıcı bir anlatım
📊 8. Decide Graph Need (Grafik Kararı)
Görev: Veriye bakarak grafik gerekip gerekmediğine ve hangi türün uygun olduğuna karar vermek.

📈 9. Viz Agent (Grafik Üretimi)
Görev: LLM’e Plotly kodu yazdırmak, exec() ile çalıştırmak ve JSON olarak Chainlit'e iletmek.
# LLM "fig" değişkenini oluşturan Plotly kodu yazar
exec(plotly_code, {"df": df, "go": go, "px": px})
fig = exec_env["fig"]
state["graph_json"] = fig.to_json() # Chainlit'e ilet
Veritabanı Tasarımı
Projede SQLite kullanılıyor ve veriler tamamen db_init.py ile programatik olarak üretiliyor. Bu sayede projeyi klonlayan herkes aynı veri setiyle çalışabilir.
ER Diyagramı (Metinsel)
accounts (1) ────────── (N) transactions (N) ────────── (1) categories
│ │ │
│ account_id │ category_id │ category_id
│ account_name │ transaction_date │ category_name
│ balance │ amount │ category_type
│ │ description │
│ │ status │
│ │ │
└── (N) invoices └── (N) budgets ─────────────┘
│ │
│ vendor_name │ limit_amount
│ due_date │ spent_amount
│ status │ month, year
Örnek Veri Dağılımı

Sentetik Veri Üretim Mantığı
db_init.py gerçekçi senaryolar için şu kuralları uygular:
# Her ayın 5'inde maaş yatar (18.000–22.000 TRY)
if day == 5:
transactions.append((..., "Aylık maaş", ...))
# Her ayın 1'inde kira ödenir (8.000–9.000 TRY)
if day == 1:
transactions.append((..., "Aylık kira ödemesi", ...))
# Günlük harcamalar %60 olasılıkla gerçekleşir
if random.random() < 0.60:
transactions.append((..., random.uniform(50, 800), ...))
# Bütçeler bazen aşılır (gerçekçilik için)
spent = round(random.uniform(0.5, 1.3) * limit, 2)
Teknoloji Yığını

Kurulum ve Çalıştırma
Projeyi ayağa kaldırmak için 6 adım yeterli:
1. Depoyu Klonlayın
git clone https://github.com/kullanici-adi/finans-asistani.git
cd finans-asistani
2. Sanal Ortam Oluşturun
python -m venv venv
# Windows
venv\Scripts\activate
# macOS / Linux
source venv/bin/activate
3. Bağımlılıkları Yükleyin
pip install -r requirements.txt
4. API Anahtarınızı Tanımlayın
cp .env.example .env
# .env dosyasını açın ve OPENAI_API_KEY değerini girin
5. Veritabanını Oluşturun
python db_init.py
Çıktı:
Veritabanı oluşturuldu: finance.db
5 hesap
15 kategori
512 işlem
240 bütçe kaydı
200 fatura
6. Uygulamayı Başlatın
chainlit run app.py
Tarayıcınızda **http://localhost:8000** adresini açın ve sorgulamaya başlayın! 🎉
Örnek Senaryolar
Senaryo 1 — Basit Sorgu & Grafik
“Bu yılki toplam gelir ve giderim ne kadar?”
Sistem şu adımlardan geçer:
- Guardrails: ✅ Finans kapsamında
- SQL Agent: Gelir ve gider toplamlarını hesaplayan sorgu üretir
- Validator: Risk yok, geç
- Execute: Sorguyu çalıştırır →
Gelir: 312.450 TRY, Gider: 198.230 TRY - Sanity Check: Değerler makul ✅
- Analysis: “2024 yılında toplam 312.450 TRY gelir elde ettiniz…”
- Decide Graph: Bar chart uygun
- Viz Agent: Mavi/Kırmızı bar chart üretir
Senaryo 2 — Karmaşık Çok Tablolu Analiz
“2024 yılında bütçe limitini en az 3 farklı ayda aşmış kategorilerin toplam harcama tutarını ve gecikmiş fatura sayısını listele.”
Bu sorgu transactions, budgets, categories ve invoices tablolarını aynı anda JOIN'ler. Fan-out riski yüksektir!
Sistemin başarısı:
- SQL Agent: 3 ayrı CTE’li sorgu üretir
- Validator: Fan-out riski tespit eder → CTE yapısını doğrular
- Execute: 4 tablodan veriyi hatasız çeker
- Sanity Check: Sonuçları onaylar
- Viz Agent: Çubuk grafik ile kategorileri görselleştirir
Senaryo 3 — Görsel Sunum

Kodun Kalbine Yolculuk
Proje Yapısı
finans-asistani/
├── app.py # Chainlit arayüzü & olay yöneticileri
├── text2sql_agent.py # LangGraph motoru (tüm ajanlar burada)
├── db_init.py # Sentetik veri üretimi
├── finance.db # SQLite veritabanı (otomatik oluşur)
├── requirements.txt # Bağımlılıklar
├── chainlit.md # Karşılama ekranı
└── images/ # Görseller
app.py — Chainlit ile Gerçek Zamanlı Akış
app.py, Chainlit'in event-driven yapısını kullanarak ajan adımlarını gerçek zamanlı olarak kullanıcıya gösterir:
@cl.on_message
async def main(message: cl.Message):
async with cl.Step(name="🤖 Ajan İş Akışı") as workflow_step:
async for event in process_question_stream(user_question):
if event_type == "node_start":
# Yeni bir alt adım oluştur
node_step = cl.Step(name=display_name, parent_id=workflow_step.id)
await node_step.send() elif event_type == "node_end":
# Adım çıktısını formatla ve güncelle
node_step.output = _format_node_output(node_name, output)
await node_step.update()
elif event_type == "node_end":
# Adım çıktısını formatla ve güncelle
node_step.output = _format_node_output(node_name, output)
await node_step.update()
Bu sayede kullanıcı, chatbot’un “düşünme sürecini” adım adım izleyebilir — tam bir şeffaf yapay zeka deneyimi.
text2sql_agent.py — Ajan Motoru
Dosyanın omurgası create_finance_graph() fonksiyonudur:
def create_finance_graph():
workflow = StateGraph(AgentState)
# Düğümleri kaydet
workflow.add_node("guardrails_agent", guardrails_agent)
workflow.add_node("sql_agent", sql_agent)
workflow.add_node("sql_validator_agent", sql_validator_agent)
workflow.add_node("execute_sql", execute_sql)
workflow.add_node("sanity_check_agent", sanity_check_agent)
workflow.add_node("analysis_agent", analysis_agent)
workflow.add_node("error_agent", error_agent)
workflow.add_node("decide_graph_need", decide_graph_need)
workflow.add_node("viz_agent", viz_agent)
# Koşullu kenarlar
workflow.add_conditional_edges("guardrails_agent", check_scope, {
"in_scope": "sql_agent", "out_of_scope": END
})
workflow.add_conditional_edges("execute_sql", should_retry, {
"success": "sanity_check_agent", "retry": "error_agent", "end": "analysis_agent"
})
workflow.add_conditional_edges("sanity_check_agent", should_regenerate_sql, {
"regenerate": "sql_agent", "proceed": "analysis_agent"
})
# ...
return workflow.compile()
Streaming — Async Generator Pattern
process_question_stream() bir async generator olarak çalışır:
async def process_question_stream(question: str):
async for event in finance_graph.astream_events(initial_state, ...):
if event_type == "on_chain_start":
yield {"type": "node_start", "node": node_name}
elif event_type == "on_chain_end":
yield {"type": "node_end", "node": node_name, "output": output}
yield {"type": "final", "result": current_state}
Bu pattern, Chainlit’in async for ile olayları gerçek zamanlı tüketmesine olanak tanır.
Karşılaşılan Zorluklar ve Çözümleri
🚨 Zorluk 1: Fan-Out (Kartezyen Çarpım) Hatası
Sorun: Birden fazla tablodan aynı anda SUM, COUNT gibi agregasyonlar yapılırken tablolar doğrudan JOIN edilince satırlar çarpışıyor ve sonuçlar gerçek değerlerin katbekat üstüne çıkıyor.
Çözüm: İki katmanlı savunma:
- SQL Validator Agent (LLM): Fan-out desenini tespit edip CTE yapısına dönüştürür
- Sanity Check Agent (LLM): Çalıştırma sonrası sonuçların mantıksız olup olmadığını kontrol eder
-- YANLIŞ (fan-out)
SELECT c.category_name, SUM(t.amount), COUNT(i.invoice_id)
FROM transactions t
JOIN invoices i ON ...
GROUP BY c.category_name;
-- DOĞRU (CTE ile ayrıştırma)
WITH tx AS (
SELECT category_id, SUM(amount) AS total FROM transactions GROUP BY category_id
),
inv AS (
SELECT category_id, COUNT(*) AS cnt FROM invoices GROUP BY category_id
)
SELECT ... FROM categories
JOIN tx ON ... LEFT JOIN inv ON ...;
🚨 Zorluk 2: Deterministik Kontrol ile LLM Tasarrufu
Sorun: Her SQL için LLM’e “fan-out var mı?” diye sormak hem maliyetli hem yavaş.
Çözüm: _has_fanout_risk() fonksiyonu tamamen Python ile, LLM çağırmadan kontrol yapar:
def _has_fanout_risk(sql: str) -> bool:
has_join = " JOIN " in sql.upper()
has_agg = any(f in sql.upper() for f in ["SUM(", "COUNT(", "AVG("])
has_cte = sql.upper().startswith("WITH ")
return has_join and has_agg and not has_cte
Sadece riskli sorgular LLM’e gönderilir — %70–80 LLM çağrısından tasarruf.
🚨 Zorluk 3: Sonsuz Döngü Koruması
Sorun: Sanity check başarısız olursa → SQL yeniden üretilir → tekrar sanity check → tekrar başarısız → sonsuz döngü.
Çözüm: sanity_retried bayrağı:
if state.get("sanity_retried", False):
state["sanity_passed"] = True # Döngüyü kır, devam et
return state
SQL hataları için de iteration > 3 kontrolüyle maksimum 3 deneme sınırı konulmuştur.
🚨 Zorluk 4: Türkçe Metin ve Tarih Formatı
Sorun: SQLite’da Türkçe karakterler büyük/küçük harf duyarlıdır. Ayrıca budgets tablosunda tarih month + year olarak INTEGER saklanır.
Çözüm: Prompt mühendisliği ile:
KRİTİK — Tarih formatı:
DOĞRU: printf('%04d-%02d', year, month) → '2024-01'
YANLIŞ: year || '-' || month || '-01' → '2024-1-01' (NULL döner!)
Sonuç ve Gelecek Vizyonu
Bu proje, LangGraph tabanlı çok ajanlı mimarinin gerçek bir iş problemini nasıl çözebileceğini gösteriyor. 9 uzman ajanın koordineli çalışmasıyla:
- ✅ Kapsam dışı sorular kibarca reddediliyor
- ✅ Karmaşık JOIN’ler güvenle yönetiliyor
- ✅ Hatalı SQL’ler otomatik düzeltiliyor
- ✅ Mantıksız sonuçlar yakalanıp yeniden deneniyor
- ✅ İnteraktif grafiklerle veri görselleştiriliyor
- ✅ Tüm süreç kullanıcıya şeffafça gösteriliyor
🚀 Sırada Ne Var?
Projeyi bir sonraki seviyeye taşımak için planlanan özellikler:

Kaynak Kod
Projenin tam kaynak koduna GitHub üzerinden ulaşabilirsiniz:
🔗 https://github.com/m-peker/agentic-finance-assistant
Teşekkür
Bu makaleyi okuduğunuz için teşekkürler! Sorularınızı yorumlarda paylaşabilir, projeye ⭐ vererek destek olabilirsiniz.
“The best way to predict the future is to build it.” — Alan Kay
#LangGraph #LLM #Text2SQL #MultiAgent #Python #Chainlit #OpenAI #Plotly #YapayZeka #FinTech
메타데이터
- post_id
- 27e8c1f699e6
- slug
- langgraph-ile-çok-ajanlı-text-to-sql-chatbot-finans-verilerinizi-doğal-dille-sorgulayın-27e8c1f699e6
- url
- https://medium.com/@msapeker/langgraph-ile-%C3%A7ok-ajanl%C4%B1-text-to-sql-chatbot-finans-verilerinizi-do%C4%9Fal-dille-sorgulay%C4%B1n-27e8c1f699e6
- canonical_url
- https://medium.com/@msapeker/langgraph-ile-%C3%A7ok-ajanl%C4%B1-text-to-sql-chatbot-finans-verilerinizi-do%C4%9Fal-dille-sorgulay%C4%B1n-27e8c1f699e6
- author_url
- https://medium.com/@msapeker
- status
- ok
- fetched_at
- 2026-06-25 16:53:31