LangChain ile Çoklu Kaynaklı Arama Motoru Geliştirme
Birden fazla kaynaktan bilgi toplayıp anlamlı bir cevap üretmek, modern yapay zeka uygulamalarının en çekici kullanım senaryolarından biri. Özellikle kurumsal ortamlarda “şirket içi dokümantasyon + harici API + web araması” gibi hibrit yapılar kurmanız gerektiğinde, LangChain’in çoklu araç mimarisi gerçekten işe yarıyor. Bu yazıda sıfırdan başlayıp üretim ortamına yakın bir çoklu kaynaklı arama motoru inşa edeceğiz.
Neden Çoklu Kaynak?
Tek bir LLM’e “bana bu konuyu anlat” dediğinizde, model eğitim verisinden ne biliyorsa onu söyler. Ama gerçek dünyada ihtiyaçlarınız çok daha spesifik: güncel fiyat bilgisi, şirket içi politika dokümanları, teknik knowledge base, belki de bir veritabanı sorgusu. Bunların hepsini aynı anda konuşturabilmek için LangChain’in Tool ve Agent yapısını kullanıyoruz.
Benim senaryom şuydu: Bir yazılım şirketinin iç destek botunu geliştiriyorduk. Kullanıcılar hem Confluence’daki dokümanlara, hem GitHub Issues’a, hem de genel web’e aynı anda soru sorabilmeliydi. Ayrı ayrı aramak yerine tek bir arayüzden hepsine ulaşmak hem kullanıcı deneyimini iyileştiriyor hem de destek ekibinin yükünü azaltıyor.
Ortam Kurulumu
Önce gerekli paketleri kuralım. Python 3.10+ öneriyorum, özellikle async yapılar için:
pip install langchain langchain-community langchain-openai
pip install faiss-cpu chromadb
pip install duckduckgo-search wikipedia
pip install python-dotenv requests beautifulsoup4
pip install tiktoken sentence-transformers
.env dosyanızı hazırlayın:
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxx
SERPAPI_API_KEY=xxxxxxxxxxxxxxxxxx # opsiyonel, DuckDuckGo ücretsiz alternatif
CONFLUENCE_URL=https://yourcompany.atlassian.net
CONFLUENCE_TOKEN=xxxxxxxxxxxxxxxxxx
Temel Agent Yapısı
LangChain’de çoklu kaynak araması için AgentExecutor kullanıyoruz. Agent, hangi aracı ne zaman kullanacağına kendisi karar veriyor. Bu kararı LLM veriyor, yani siz sadece araçları tanımlıyorsunuz:
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from dotenv import load_dotenv
import os
load_dotenv()
llm = ChatOpenAI(
model="gpt-4o-mini",
temperature=0,
api_key=os.getenv("OPENAI_API_KEY")
)
# System prompt - ajanın davranışını belirliyor
prompt = ChatPromptTemplate.from_messages([
("system", """Sen bir teknik destek asistanısın. Kullanıcının sorularını yanıtlamak için
elindeki araçları akıllıca kullan. Birden fazla kaynaktan bilgi topla ve
sonuçları sentezleyerek kapsamlı bir cevap ver.
Kaynak belirt: hangi bilgiyi nereden aldığını mutlaka söyle."""),
MessagesPlaceholder(variable_name="chat_history"),
("human", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad"),
])
Araç 1: Yerel Döküman Araması (RAG)
İlk araç, şirket içi dökümanlardan oluşan vector store’u sorgulayan bir RAG sistemi. Gerçek projede Confluence’dan çektiğimiz dokümanları FAISS’e yüklemiştik, burada yerel dosyalarla simüle ediyoruz:
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
from langchain.tools import Tool
from langchain.chains import RetrievalQA
from langchain_community.document_loaders import DirectoryLoader, TextFileLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
def create_document_search_tool():
# Dökümanları yükle ve chunk'la
loader = DirectoryLoader(
"./docs",
glob="**/*.txt",
loader_cls=TextFileLoader
)
documents = loader.load()
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["nn", "n", " ", ""]
)
chunks = text_splitter.split_documents(documents)
# Vector store oluştur
embeddings = OpenAIEmbeddings()
vectorstore = FAISS.from_documents(chunks, embeddings)
vectorstore.save_local("./faiss_index")
# Retrieval chain
retriever = vectorstore.as_retriever(
search_type="mmr", # Maximum Marginal Relevance - çeşitlilik için
search_kwargs={"k": 5, "fetch_k": 20}
)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
return_source_documents=True
)
def search_internal_docs(query: str) -> str:
result = qa_chain.invoke({"query": query})
sources = [doc.metadata.get("source", "bilinmiyor")
for doc in result.get("source_documents", [])]
return f"İç Doküman Sonucu: {result['result']}nKaynaklar: {', '.join(set(sources))}"
return Tool(
name="internal_document_search",
description="""Şirket içi dokümanları, teknik kılavuzları ve politika belgelerini arar.
Ürün özellikleri, teknik dokümantasyon veya şirket prosedürleri hakkında sorular için kullan.""",
func=search_internal_docs
)
search_type="mmr" kullanmak önemli bir detay. Basit similarity search yerine MMR, birbirine çok benzer chunk’ları tekrar etmek yerine çeşitli sonuçlar getiriyor. Özellikle uzun dokümanlarda bu fark büyük oluyor.
Araç 2: Web Araması
DuckDuckGo ücretsiz ve API key gerektirmiyor, prototip aşaması için ideal:
from langchain_community.tools import DuckDuckGoSearchRun, DuckDuckGoSearchResults
from langchain.tools import Tool
def create_web_search_tool():
# DuckDuckGoSearchResults daha fazla metadata döndürüyor
ddg_search = DuckDuckGoSearchResults(
num_results=5,
backend="text" # "news" da kullanabilirsiniz
)
def web_search_with_context(query: str) -> str:
try:
results = ddg_search.run(query)
return f"Web Arama Sonuçları:n{results}"
except Exception as e:
# Rate limit veya bağlantı hatası durumunda
return f"Web araması şu an kullanılamıyor: {str(e)}"
return Tool(
name="web_search",
description="""İnternette güncel bilgi arar. Güncel haberler, yeni teknolojiler,
son gelişmeler veya iç dokümanlarda olmayan konular için kullan.
Şirket içi bilgi yeterli olmadığında bu araca başvur.""",
func=web_search_with_context
)
Araç 3: Wikipedia Araması
Teknik terimler, genel kavramlar ve bağlam sağlamak için Wikipedia aracı işe yarıyor. Özellikle kullanıcının terminoloji konusunda kafasının karışık olduğu durumlarda:
from langchain_community.tools import WikipediaQueryRun
from langchain_community.utilities import WikipediaAPIWrapper
from langchain.tools import Tool
def create_wikipedia_tool():
wikipedia = WikipediaAPIWrapper(
lang="tr", # Önce Türkçe dene
top_k_results=2,
doc_content_chars_max=2000
)
wiki_tool = WikipediaQueryRun(api_wrapper=wikipedia)
def smart_wiki_search(query: str) -> str:
# Önce Türkçe dene, yoksa İngilizce'ye geç
try:
result = wiki_tool.run(query)
if "No good Wikipedia Search Result was found" in result or len(result) < 100:
# İngilizce'ye geç
wikipedia_en = WikipediaAPIWrapper(lang="en", top_k_results=2)
wiki_en = WikipediaQueryRun(api_wrapper=wikipedia_en)
result = wiki_en.run(query)
result = f"[İngilizce Wikipedia]n{result}"
return f"Wikipedia: {result}"
except Exception as e:
return f"Wikipedia araması başarısız: {str(e)}"
return Tool(
name="wikipedia_search",
description="""Genel bilgi, tanımlar ve kavramsal açıklamalar için Wikipedia'yı arar.
Teknik terimler, teknoloji geçmişi veya genel kavramlar için kullan.""",
func=smart_wiki_search
)
Araç 4: Özel API Entegrasyonu
Gerçek projelerde genellikle bir iç API’ye de bağlanmanız gerekiyor. Burada örnek olarak bir “sistem durumu” API’si simüle ediyorum:
import requests
import json
from langchain.tools import Tool
from datetime import datetime
def create_system_status_tool():
def check_system_status(query: str) -> str:
"""
Gerçek projede bu fonksiyon şirketin monitoring API'sine bağlanır.
Burada mock data kullanıyoruz.
"""
mock_status = {
"timestamp": datetime.now().isoformat(),
"services": {
"api_gateway": {"status": "healthy", "latency_ms": 45},
"database": {"status": "healthy", "connections": 127},
"cache": {"status": "degraded", "hit_rate": 0.72},
"message_queue": {"status": "healthy", "queue_depth": 234}
},
"recent_incidents": [
{
"id": "INC-2847",
"title": "Cache servis yavaşlaması",
"status": "investigating",
"created_at": "2024-01-15T14:30:00Z"
}
]
}
# Sorguya göre filtrele
query_lower = query.lower()
if "cache" in query_lower:
cache_info = mock_status["services"]["cache"]
return f"Cache durumu: {cache_info['status']}, Hit rate: {cache_info['hit_rate']:.0%}"
if "incident" in query_lower or "sorun" in query_lower:
incidents = mock_status["recent_incidents"]
if incidents:
inc = incidents[0]
return f"Aktif Sorun: {inc['title']} (Durum: {inc['status']}, ID: {inc['id']})"
return "Şu an aktif sorun bulunmuyor."
# Genel durum özeti
healthy = sum(1 for s in mock_status["services"].values()
if s["status"] == "healthy")
total = len(mock_status["services"])
return (f"Sistem Durumu: {healthy}/{total} servis sağlıklı. "
f"Son güncelleme: {mock_status['timestamp']}")
return Tool(
name="system_status",
description="""Sistem servislerinin anlık durumunu, aktif olayları ve
performans metriklerini sorgular. Sistem sorunları, servis durumu veya
aktif incident'lar için kullan.""",
func=check_system_status
)
Her Şeyi Bir Araya Getirmek
Tüm araçları agent’a bağlıyoruz. create_openai_tools_agent OpenAI’nin function calling özelliğini kullanıyor, bu da ReAct agent’a kıyasla çok daha güvenilir araç seçimi sağlıyor:
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_core.messages import HumanMessage, AIMessage
# Araçları oluştur
tools = [
create_document_search_tool(),
create_web_search_tool(),
create_wikipedia_tool(),
create_system_status_tool()
]
# Agent oluştur
agent = create_openai_tools_agent(llm, tools, prompt)
# AgentExecutor - araç çağrılarını yönetiyor
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True, # Hangi araçların kullanıldığını görmek için
max_iterations=5, # Sonsuz döngüyü önle
max_execution_time=30, # Saniye cinsinden timeout
handle_parsing_errors=True,
return_intermediate_steps=True # Debug için
)
# Konuşma geçmişi ile kullan
def ask_with_history(question: str, chat_history: list = None):
if chat_history is None:
chat_history = []
result = agent_executor.invoke({
"input": question,
"chat_history": chat_history
})
# Hangi araçlar kullanıldı?
used_tools = []
for step in result.get("intermediate_steps", []):
tool_name = step[0].tool
if tool_name not in used_tools:
used_tools.append(tool_name)
return {
"answer": result["output"],
"sources_used": used_tools,
"steps": len(result.get("intermediate_steps", []))
}
# Test
if __name__ == "__main__":
history = []
response = ask_with_history(
"Cache servisi neden yavaş? Bu sorun ne zaman başladı ve nasıl çözebilirim?"
)
print(f"Cevap: {response['answer']}")
print(f"Kullanılan araçlar: {response['sources_used']}")
Streaming ile Gerçek Zamanlı Yanıt
Kullanıcı arayüzü geliştiriyorsanız streaming şart. LangChain’de callback sistemi ile bunu kolayca yapabilirsiniz:
from langchain_core.callbacks import StreamingStdOutCallbackHandler
from langchain.callbacks.manager import CallbackManager
# Streaming LLM
streaming_llm = ChatOpenAI(
model="gpt-4o-mini",
temperature=0,
streaming=True,
callback_manager=CallbackManager([StreamingStdOutCallbackHandler()])
)
# FastAPI ile kullanım örneği
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio
app = FastAPI()
async def generate_response(question: str):
"""Async generator - token token gönderir"""
async for chunk in agent_executor.astream(
{"input": question, "chat_history": []}
):
if "output" in chunk:
yield f"data: {chunk['output']}nn"
elif "actions" in chunk:
for action in chunk["actions"]:
yield f"data: [Araç kullanılıyor: {action.tool}]nn"
@app.get("/search")
async def search_endpoint(q: str):
return StreamingResponse(
generate_response(q),
media_type="text/event-stream"
)
Performans ve Maliyet Optimizasyonu
Üretim ortamına geçerken dikkat etmeniz gereken birkaç konu var.
Caching: Aynı soruya her seferinde API çağrısı yapmak hem maliyetli hem de yavaş. LangChain’in semantic cache özelliğini kullanın:
from langchain.globals import set_llm_cache
from langchain_community.cache import InMemoryCache, SQLiteCache
# Development için in-memory cache
set_llm_cache(InMemoryCache())
# Production için SQLite (Redis de kullanabilirsiniz)
set_llm_cache(SQLiteCache(database_path=".langchain.db"))
# Artık aynı veya çok benzer sorgular için LLM çağrısı yapılmaz
# FAISS ile semantic similarity kullanmak için:
from langchain_openai import OpenAIEmbeddings
from langchain_community.cache import GPTCache
# GPTCache ile semantic caching
# pip install gptcache
Araç Seçimini Kısıtlamak: Bazı sorular için gereksiz araç çağrısı yapılmasını önleyin. Bunun için tool description’larını iyi yazmak kritik. Belirsiz description’lar agent’ın yanlış araç seçmesine neden oluyor.
Token Yönetimi: Her araç yanıtını max_tokens ile sınırlayın:
from langchain.schema import Document
from langchain.text_splitter import TokenTextSplitter
def truncate_tool_output(output: str, max_tokens: int = 500) -> str:
"""Araç çıktısını token limitine göre kırp"""
splitter = TokenTextSplitter(
chunk_size=max_tokens,
chunk_overlap=0,
model_name="gpt-4o-mini"
)
chunks = splitter.split_text(output)
return chunks[0] if chunks else output
Gerçek Dünya Dersleri
Birkaç ay bu sistemleri kurumsal ortamda işletirken öğrendiklerim:
- Agent sonsuz döngüye girebilir.
max_iterations=5vemax_execution_time=30gibi sınırları mutlaka koyun. Aksi halde bir araç hata döndürdüğünde agent aynı aracı defalarca deniyor.
- Tool description’ı kritik. Agent neyi ne zaman kullanacağına description’a bakarak karar veriyor. “Genel bilgi için kullan” gibi muğlak açıklamalar yerine kesin sınırlar belirleyin. “Bu aracı SADECE şu durumlarda kullan” formatı daha iyi sonuç veriyor.
- Verbose modu prod’da kapatın.
verbose=Trueher adımı stdout’a yazıyor. Prod ortamda bunu loglama sistemine yönlendirin.
- Araç başarısızlıklarını graceful handle edin. Her araç fonksiyonunuzda try/except olmalı ve anlamlı hata mesajı dönmeli. Agent hata mesajını da bilgi olarak işleyebiliyor.
- Paralel araç çağrısı mümkün. OpenAI tools API, birden fazla aracı aynı anda çağırabilir. Bu latency’yi dramatik düşürüyor.
create_openai_tools_agentbunu destekliyor.
Sonuç
LangChain ile çoklu kaynaklı arama motoru kurmak, aslında karmaşık görünen ama doğru araçlarla oldukça yönetilebilir bir iş. Önemli olan parçaları şöyle sıralayabilirim: her kaynağı iyi izole edilmiş bir Tool olarak modellemek, tool description’larını LLM’in anlayabileceği şekilde yazmak, üretim ortamı için caching ve rate limiting mekanizmalarını baştan planlamak.
Bu mimarinin güzel tarafı genişletilebilirliği. Yeni bir kaynak eklemek istediğinizde sadece yeni bir Tool yazıp listeye ekliyorsunuz, geri kalan her şey otomatik çalışıyor. Bir SQL veritabanı, bir Slack kanalı, bir Git repository hepsi aynı şekilde entegre olabiliyor.
Bir sonraki adım olarak LangGraph ile daha deterministik akışlar kurmayı veya multi-agent mimarilerde farklı uzman agent’ların koordinasyonunu inceleyebilirsiniz. Ama tek agent ile bile bu senaryoda gerçekten üretim kalitesi bir sistem kurabiliyorsunuz.
