89 lines
3.8 KiB
Python
89 lines
3.8 KiB
Python
import os
|
||
import logging
|
||
import chromadb
|
||
from chromadb.utils import embedding_functions
|
||
from mcp.server.fastmcp import FastMCP
|
||
|
||
# Log ayarları: Sadece kendi loglarımızı yazalım, diğer kütüphaneler (httpx vb.) karışmasın.
|
||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||
|
||
logger = logging.getLogger("DocsRAGServer")
|
||
logger.setLevel(logging.INFO)
|
||
|
||
# Sadece bu logger için bir dosya işleyicisi (handler) ekleyelim
|
||
file_handler = logging.FileHandler(os.path.join(SCRIPT_DIR, 'mcp_server.log'))
|
||
file_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
|
||
logger.addHandler(file_handler)
|
||
|
||
# Propagate'i kapatalım ki root logger'a gitmesin
|
||
logger.propagate = False
|
||
|
||
# Dış kütüphanelerin gereksiz loglarını susturmak için:
|
||
logging.getLogger("httpx").setLevel(logging.WARNING)
|
||
logging.getLogger("sentence_transformers").setLevel(logging.WARNING)
|
||
|
||
logger.info("DocsRAGServer başlatılıyor...")
|
||
|
||
# MCP Server'ı oluştur
|
||
# FastMCP, standart stdio/SSE vb. ayarlarını otomatik halleder.
|
||
mcp = FastMCP("DocsRAGServer")
|
||
|
||
# Çalışma dizinine göre ChromaDB klasörünün yolunu bul
|
||
MAIN_DIR = os.path.dirname(SCRIPT_DIR)
|
||
CHROMA_PATH = os.path.join(MAIN_DIR, "chroma_db")
|
||
|
||
# ChromaDB'ye bağlan
|
||
# Not: Sunucu ayağa kalktığında sadece bir kez bağlanır.
|
||
try:
|
||
client = chromadb.PersistentClient(path=CHROMA_PATH)
|
||
sentence_transformer_ef = embedding_functions.SentenceTransformerEmbeddingFunction(model_name="all-MiniLM-L6-v2")
|
||
collection = client.get_or_create_collection(name="docs", embedding_function=sentence_transformer_ef)
|
||
except Exception as e:
|
||
error_msg = f"UYARI: ChromaDB'ye bağlanırken hata oluştu. Henüz ingest.py çalıştırılmamış olabilir mi? Hata: {e}"
|
||
print(error_msg)
|
||
logger.error(error_msg)
|
||
|
||
@mcp.tool()
|
||
def search_documentation(query: str, num_results: int = 5) -> str:
|
||
"""
|
||
Kullanıcının sorusuna en uygun dokümantasyon parçalarını vektör veritabanından bulup döndürür.
|
||
Bu araç (tool) sistemin belleği gibi davranır ve AI'ın kod/kavram araması yapmasını sağlar.
|
||
|
||
Args:
|
||
query: Aranacak soru veya anahtar kelimeler (Örn: 'app router data fetching' veya 'next-auth credentials provider').
|
||
num_results: Döndürülecek maksimum sonuç sayısı (varsayılan: 5).
|
||
"""
|
||
logger.info(f"Arama isteği alındı: query='{query}', num_results={num_results}")
|
||
try:
|
||
# Sorguyu vektöre çevirip en benzer metinleri (semantic search) getir
|
||
results = collection.query(
|
||
query_texts=[query],
|
||
n_results=num_results
|
||
)
|
||
|
||
if not results['documents'] or not results['documents'][0]:
|
||
logger.info("Sorgu için ilgili bir doküman bulunamadı.")
|
||
return "İlgili bir doküman bulunamadı."
|
||
|
||
docs = results['documents'][0]
|
||
metadatas = results['metadatas'][0]
|
||
distances = results['distances'][0]
|
||
|
||
response_parts = [f"Arama Sorgusu: '{query}'\nBulunan En İyi Eşleşmeler:\n"]
|
||
|
||
for i, (doc, meta, dist) in enumerate(zip(docs, metadatas, distances)):
|
||
source = meta.get("source", "Bilinmeyen Kaynak")
|
||
# dist (distance) değeri ChromaDB için varsayılan olarak L2 mesafesidir, küçük olan daha benzerdir.
|
||
response_parts.append(f"--- Sonuç {i+1} (Kaynak: {source}, Uzaklık: {dist:.4f}) ---\n{doc}\n")
|
||
|
||
logger.info(f"Sorgu başarılı, {len(docs)} sonuç bulundu.")
|
||
return "\n".join(response_parts)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Arama sırasında hata oluştu: {str(e)}")
|
||
return f"Arama sırasında hata oluştu: {str(e)}"
|
||
|
||
if __name__ == "__main__":
|
||
# Server'ı stdio üzerinden çalıştır. (Claude veya diğer MCP istemcileri bu şekilde bağlanır)
|
||
mcp.run()
|