Spaces:
Running
Running
Download github_rag/indexing.py from akadirgok/github-asistani: direct link, hf CLI and curl.
- Browser
- Download file 4.45 kB
-
https://huggingface.co/spaces/akadirgok/github-asistani/resolve/main/github_rag/indexing.py
- Command line
-
hf download hf://spaces/akadirgok/github-asistani/github_rag/indexing.py
-
curl -L -o indexing.py https://huggingface.co/spaces/akadirgok/github-asistani/resolve/main/github_rag/indexing.py
4.45 kB
| # -*- coding: utf-8 -*- | |
| """Metin parçalama + embedding + ChromaDB indeksleme. | |
| Koleksiyonlar kullanıcı/repo başına ayrılır (repos_<hedef>) ve cosine uzayında | |
| açılır; embedding modeli çok dillidir (Türkçe soru -> İngilizce README eşleşir). | |
| """ | |
| import hashlib | |
| import chromadb | |
| from sentence_transformers import SentenceTransformer | |
| _embedding_model = None | |
| _embedding_model_name = None | |
| def _get_embedding_model(name: str): | |
| """Embedding modelini ilk çağrıda yükler ve isim değişmediği sürece önbelleğe alır.""" | |
| global _embedding_model, _embedding_model_name | |
| if _embedding_model is None or _embedding_model_name != name: | |
| print(f"Embedding modeli yükleniyor: {name} (ilk sefer sürebilir)...") | |
| _embedding_model = SentenceTransformer(name) | |
| _embedding_model_name = name | |
| return _embedding_model | |
| def chunk_text(text: str, chunk_size: int = 300, overlap: int = 50) -> list[str]: | |
| """Uzun metni kelime bazında, örtüşmeyle parçalara böler.""" | |
| words = text.split() | |
| if not words: | |
| return [] | |
| chunks = [] | |
| step = max(1, chunk_size - overlap) | |
| for i in range(0, len(words), step): | |
| chunk = " ".join(words[i:i + chunk_size]) | |
| chunks.append(chunk) | |
| if i + chunk_size >= len(words): | |
| break | |
| return chunks | |
| def _chunk_id(source: str, text: str) -> str: | |
| """Chunk'a, kaynağıyla birlikte kararlı bir kimlik üretir. | |
| Aynı kaynaktaki aynı chunk tekrar indekslenince aynı id'yi alır (upsert ile | |
| güncellenir); farklı repolardaki özdeş metinler ise çakışmaz (source eklenir). | |
| """ | |
| return hashlib.sha1(f"{source}\x00{text}".encode("utf-8")).hexdigest() | |
| def get_collection(chroma_dir: str, collection_name: str): | |
| """Belirtilen koleksiyonu açar (yoksa cosine uzayında oluşturur).""" | |
| client = chromadb.PersistentClient(path=chroma_dir) | |
| return client.get_or_create_collection( | |
| name=collection_name, | |
| metadata={"hnsw:space": "cosine"}, | |
| ) | |
| def clear_collection(chroma_dir: str, collection_name: str) -> None: | |
| """Koleksiyondaki tüm dokümanları siler (force yeniden çekimden önce). | |
| `build_index` yalnızca upsert yapar; silinen repo / değişen README eski | |
| chunk'ları öksüz bırakır. Deterministik olması için id'ler alınıp tek tek | |
| silinir (`delete(where={})` davranışı ChromaDB sürümleri arasında değişebilir). | |
| """ | |
| c = get_collection(chroma_dir, collection_name) | |
| ids = c.get(include=[])["ids"] | |
| if ids: | |
| c.delete(ids=ids) | |
| def reset_collection(chroma_dir: str, collection_name: str): | |
| """Koleksiyonu silip yeniden oluşturur (bozuk HNSW indeksini onarmak için).""" | |
| client = chromadb.PersistentClient(path=chroma_dir) | |
| try: | |
| client.delete_collection(collection_name) | |
| except Exception: # noqa: BLE001 — yoksa zaten sorun değil | |
| pass | |
| return get_collection(chroma_dir, collection_name) | |
| def build_index(documents: list, chroma_dir: str, collection_name: str, | |
| embedding_model: str, chunk_size: int = 300, overlap: int = 50) -> int: | |
| """Dokümanları chunk'lar, embed eder ve koleksiyona yazar; chunk sayısını döndürür. | |
| `documents` öğeleri {"text", "source", "url"} sözlükleri olmalıdır. | |
| Kod için daha küçük chunk_size (ör. 150) kullanılır: kod token yoğun olduğu | |
| için aynı kelime sayısı çok daha fazla token üretir ve model bağlamını taşırır. | |
| """ | |
| model = _get_embedding_model(embedding_model) | |
| collection = get_collection(chroma_dir, collection_name) | |
| ids, texts, metadatas = [], [], [] | |
| for doc in documents: | |
| text = doc.get("text", "") | |
| source = doc.get("source", "") | |
| url = doc.get("url", "") | |
| for chunk in chunk_text(text, chunk_size=chunk_size, overlap=overlap): | |
| ids.append(_chunk_id(source, chunk)) | |
| texts.append(chunk) | |
| metadatas.append({"source": source, "url": url}) | |
| if not texts: | |
| print("Uyarı: İndekslenecek boş olmayan metin bulunamadı.") | |
| return 0 | |
| print(f"{len(texts)} chunk embedding'e çevriliyor...") | |
| embeddings = model.encode(texts, show_progress_bar=False).tolist() | |
| collection.upsert(ids=ids, documents=texts, embeddings=embeddings, metadatas=metadatas) | |
| print(f"İndeks tamamlandı: {len(texts)} chunk '{collection_name}' koleksiyonuna yazıldı.") | |
| return len(texts) | |