first commit
This commit is contained in:
88
rag_local/ingest.py
Normal file
88
rag_local/ingest.py
Normal file
@@ -0,0 +1,88 @@
|
||||
import os
|
||||
import glob
|
||||
import logging
|
||||
from langchain_text_splitters import MarkdownTextSplitter
|
||||
import chromadb
|
||||
from chromadb.utils import embedding_functions
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
|
||||
|
||||
def ingest_docs(docs_dir="data/md_docs", chroma_path="chroma_db", collection_name="docs"):
|
||||
logging.info(f"ChromaDB başlatılıyor... ({chroma_path})")
|
||||
|
||||
# ChromaDB client oluştur (klasöre kaydeder)
|
||||
client = chromadb.PersistentClient(path=chroma_path)
|
||||
|
||||
# Default embedding function (sentence-transformers: all-MiniLM-L6-v2)
|
||||
# Bu model boyut olarak küçük (yaklaşık 80MB) ve lokalde çok hızlı çalışır.
|
||||
sentence_transformer_ef = embedding_functions.SentenceTransformerEmbeddingFunction(model_name="all-MiniLM-L6-v2")
|
||||
|
||||
# Collection oluştur veya var olanı al
|
||||
collection = client.get_or_create_collection(name=collection_name, embedding_function=sentence_transformer_ef)
|
||||
|
||||
# Markdown dosyalarını bul
|
||||
md_files = glob.glob(os.path.join(docs_dir, "**", "*.md"), recursive=True)
|
||||
logging.info(f"Toplam {len(md_files)} markdown dosyası bulundu.")
|
||||
|
||||
# Metinleri anlamlı parçalara bölecek (chunk) nesne
|
||||
# Her parça yaklaşık 1000 karakter olacak, aralarında 200 karakter örtüşme (overlap) olacak.
|
||||
splitter = MarkdownTextSplitter(chunk_size=1000, chunk_overlap=200)
|
||||
|
||||
batch_documents = []
|
||||
batch_metadatas = []
|
||||
batch_ids = []
|
||||
|
||||
count = 0
|
||||
for file_path in md_files:
|
||||
try:
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
# Metni parçalara böl
|
||||
chunks = splitter.split_text(content)
|
||||
|
||||
for i, chunk in enumerate(chunks):
|
||||
# ID örneği: data/md_docs/nextjs.org/docs.md_chunk_0
|
||||
chunk_id = f"{file_path}_chunk_{i}"
|
||||
|
||||
batch_documents.append(chunk)
|
||||
batch_metadatas.append({"source": file_path, "chunk_index": i})
|
||||
batch_ids.append(chunk_id)
|
||||
|
||||
# Belleği doldurmamak için her 100 parçada bir veritabanına yaz
|
||||
if len(batch_documents) >= 100:
|
||||
collection.add(
|
||||
documents=batch_documents,
|
||||
metadatas=batch_metadatas,
|
||||
ids=batch_ids
|
||||
)
|
||||
count += len(batch_documents)
|
||||
logging.info(f"{count} parça vektörleştirildi ve veritabanına eklendi...")
|
||||
batch_documents = []
|
||||
batch_metadatas = []
|
||||
batch_ids = []
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Dosya okunamadı ({file_path}): {e}")
|
||||
|
||||
# Kalan son parçaları yaz
|
||||
if batch_documents:
|
||||
collection.add(
|
||||
documents=batch_documents,
|
||||
metadatas=batch_metadatas,
|
||||
ids=batch_ids
|
||||
)
|
||||
count += len(batch_documents)
|
||||
|
||||
logging.info(f"İşlem tamam! Toplam {count} metin parçası (chunk) vektörleştirilerek ChromaDB'ye işlendi.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Bu dosya rag_local dizininde olsa bile, çalışma dizini olarak mdscrap/ ana dizinini kullanalım
|
||||
# böylece 'data/md_docs' ve 'chroma_db' ana dizinde oluşur.
|
||||
|
||||
# Çalışma dizinini ana dizine ayarla
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
main_dir = os.path.dirname(script_dir)
|
||||
os.chdir(main_dir)
|
||||
|
||||
ingest_docs()
|
||||
51
rag_local/mcp_server.log
Normal file
51
rag_local/mcp_server.log
Normal file
@@ -0,0 +1,51 @@
|
||||
2026-04-23 03:23:02,022 - INFO - DocsRAGServer başlatılıyor...
|
||||
2026-04-23 03:23:07,881 - INFO - HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/modules.json "HTTP/1.1 307 Temporary Redirect"
|
||||
2026-04-23 03:23:07,881 - WARNING - Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.
|
||||
2026-04-23 03:23:07,926 - INFO - HTTP Request: HEAD https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/modules.json "HTTP/1.1 200 OK"
|
||||
2026-04-23 03:23:08,071 - INFO - HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/config_sentence_transformers.json "HTTP/1.1 307 Temporary Redirect"
|
||||
2026-04-23 03:23:08,116 - INFO - HTTP Request: HEAD https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/config_sentence_transformers.json "HTTP/1.1 200 OK"
|
||||
2026-04-23 03:23:08,116 - INFO - Loading SentenceTransformer model from sentence-transformers/all-MiniLM-L6-v2.
|
||||
2026-04-23 03:23:08,263 - INFO - HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/config_sentence_transformers.json "HTTP/1.1 307 Temporary Redirect"
|
||||
2026-04-23 03:23:08,308 - INFO - HTTP Request: HEAD https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/config_sentence_transformers.json "HTTP/1.1 200 OK"
|
||||
2026-04-23 03:23:08,457 - INFO - HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/README.md "HTTP/1.1 307 Temporary Redirect"
|
||||
2026-04-23 03:23:08,501 - INFO - HTTP Request: HEAD https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/README.md "HTTP/1.1 200 OK"
|
||||
2026-04-23 03:23:08,657 - INFO - HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/modules.json "HTTP/1.1 307 Temporary Redirect"
|
||||
2026-04-23 03:23:08,702 - INFO - HTTP Request: HEAD https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/modules.json "HTTP/1.1 200 OK"
|
||||
2026-04-23 03:23:08,847 - INFO - HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/sentence_bert_config.json "HTTP/1.1 307 Temporary Redirect"
|
||||
2026-04-23 03:23:08,892 - INFO - HTTP Request: HEAD https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/sentence_bert_config.json "HTTP/1.1 200 OK"
|
||||
2026-04-23 03:23:09,040 - INFO - HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/adapter_config.json "HTTP/1.1 404 Not Found"
|
||||
2026-04-23 03:23:09,185 - INFO - HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/config.json "HTTP/1.1 307 Temporary Redirect"
|
||||
2026-04-23 03:23:09,230 - INFO - HTTP Request: HEAD https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/config.json "HTTP/1.1 200 OK"
|
||||
2026-04-23 03:23:09,497 - INFO - HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/processor_config.json "HTTP/1.1 404 Not Found"
|
||||
2026-04-23 03:23:09,649 - INFO - HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/preprocessor_config.json "HTTP/1.1 404 Not Found"
|
||||
2026-04-23 03:23:09,798 - INFO - HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/video_preprocessor_config.json "HTTP/1.1 404 Not Found"
|
||||
2026-04-23 03:23:09,948 - INFO - HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/preprocessor_config.json "HTTP/1.1 404 Not Found"
|
||||
2026-04-23 03:23:10,094 - INFO - HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/tokenizer_config.json "HTTP/1.1 307 Temporary Redirect"
|
||||
2026-04-23 03:23:10,138 - INFO - HTTP Request: HEAD https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/tokenizer_config.json "HTTP/1.1 200 OK"
|
||||
2026-04-23 03:23:10,289 - INFO - HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/config.json "HTTP/1.1 307 Temporary Redirect"
|
||||
2026-04-23 03:23:10,334 - INFO - HTTP Request: HEAD https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/config.json "HTTP/1.1 200 OK"
|
||||
2026-04-23 03:23:10,487 - INFO - HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/config.json "HTTP/1.1 307 Temporary Redirect"
|
||||
2026-04-23 03:23:10,532 - INFO - HTTP Request: HEAD https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/config.json "HTTP/1.1 200 OK"
|
||||
2026-04-23 03:23:10,678 - INFO - HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/tokenizer_config.json "HTTP/1.1 307 Temporary Redirect"
|
||||
2026-04-23 03:23:10,723 - INFO - HTTP Request: HEAD https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/tokenizer_config.json "HTTP/1.1 200 OK"
|
||||
2026-04-23 03:23:10,883 - INFO - HTTP Request: GET https://huggingface.co/api/models/sentence-transformers/all-MiniLM-L6-v2/tree/main/additional_chat_templates?recursive=false&expand=false "HTTP/1.1 404 Not Found"
|
||||
2026-04-23 03:23:11,036 - INFO - HTTP Request: GET https://huggingface.co/api/models/sentence-transformers/all-MiniLM-L6-v2/tree/main?recursive=true&expand=false "HTTP/1.1 200 OK"
|
||||
2026-04-23 03:23:11,235 - INFO - HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/1_Pooling/config.json "HTTP/1.1 307 Temporary Redirect"
|
||||
2026-04-23 03:23:11,279 - INFO - HTTP Request: HEAD https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/1_Pooling%2Fconfig.json "HTTP/1.1 200 OK"
|
||||
2026-04-23 03:23:11,429 - INFO - HTTP Request: GET https://huggingface.co/api/models/sentence-transformers/all-MiniLM-L6-v2 "HTTP/1.1 200 OK"
|
||||
2026-04-23 03:24:50,994 - INFO - DocsRAGServer başlatılıyor...
|
||||
2026-04-23 15:07:20,808 - INFO - DocsRAGServer başlatılıyor...
|
||||
2026-04-23 18:37:20,770 - INFO - DocsRAGServer başlatılıyor...
|
||||
2026-04-24 04:12:05,022 - INFO - DocsRAGServer başlatılıyor...
|
||||
2026-04-24 04:13:23,239 - INFO - DocsRAGServer başlatılıyor...
|
||||
2026-04-24 06:14:03,870 - INFO - DocsRAGServer başlatılıyor...
|
||||
2026-04-24 16:32:57,441 - INFO - DocsRAGServer başlatılıyor...
|
||||
2026-04-24 16:53:45,204 - INFO - DocsRAGServer başlatılıyor...
|
||||
2026-04-24 16:54:03,188 - INFO - DocsRAGServer başlatılıyor...
|
||||
2026-04-25 03:37:55,778 - INFO - DocsRAGServer başlatılıyor...
|
||||
2026-04-25 18:38:12,228 - INFO - DocsRAGServer başlatılıyor...
|
||||
2026-04-25 21:01:15,905 - INFO - DocsRAGServer başlatılıyor...
|
||||
2026-04-26 00:40:32,063 - INFO - DocsRAGServer başlatılıyor...
|
||||
2026-04-26 00:59:41,360 - INFO - DocsRAGServer başlatılıyor...
|
||||
2026-04-26 01:00:53,094 - INFO - DocsRAGServer başlatılıyor...
|
||||
2026-04-26 01:17:01,563 - INFO - DocsRAGServer başlatılıyor...
|
||||
88
rag_local/mcp_server.py
Normal file
88
rag_local/mcp_server.py
Normal file
@@ -0,0 +1,88 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user