Spaces:
Running
Running
Download app/api.py from akadirgok/github-asistani: direct link, hf CLI and curl.
- Browser
- Download file 7.84 kB
-
https://huggingface.co/spaces/akadirgok/github-asistani/resolve/main/app/api.py
- Command line
-
hf download hf://spaces/akadirgok/github-asistani/app/api.py
-
curl -L -o api.py https://huggingface.co/spaces/akadirgok/github-asistani/resolve/main/app/api.py
7.84 kB
| # -*- coding: utf-8 -*- | |
| """FastAPI katmanı: /health, /ingest, /chat + statik arayüz. | |
| İş mantığı github_rag paketindedir; bu dosya yalnızca HTTP tarafını içerir. | |
| """ | |
| import os | |
| import sys | |
| import threading | |
| import time | |
| from contextlib import asynccontextmanager | |
| from pathlib import Path | |
| from fastapi import FastAPI, Request | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import JSONResponse | |
| from fastapi.staticfiles import StaticFiles | |
| from pydantic import BaseModel | |
| PROJECT_ROOT = Path(__file__).resolve().parent.parent | |
| # `uvicorn app.api:app` ile çalıştırılırken github_rag paketinin bulunabilmesi için | |
| # proje kökünü sys.path'in başına ekle. | |
| if str(PROJECT_ROOT) not in sys.path: | |
| sys.path.insert(0, str(PROJECT_ROOT)) | |
| from github_rag import GithubRag # noqa: E402 | |
| from github_rag.config import load_config # noqa: E402 | |
| STATIC_DIR = PROJECT_ROOT / "static" | |
| config = load_config() | |
| active_rag: GithubRag | None = None | |
| def _last_target_path() -> Path: | |
| return Path(config.chroma_dir) / "last_target.txt" | |
| def _save_last_target(hedef: str) -> None: | |
| try: | |
| _last_target_path().write_text(hedef.strip(), encoding="utf-8") | |
| except Exception: # noqa: BLE001 — işaret dosyası yazılamasa da ingest başarılı sayılır | |
| pass | |
| def _load_last_target() -> str | None: | |
| p = _last_target_path() | |
| if p.is_file(): | |
| t = p.read_text(encoding="utf-8").strip() | |
| return t or None | |
| return None | |
| def _varsayilan_hedef() -> str: | |
| """Açılışta otomatik yüklenecek hesap (HF diski geçici olduğundan yeniden başlatmada gerekir).""" | |
| return os.environ.get("GITHUB_RAG_DEFAULT_TARGET", "kadiirgok").strip() | |
| def _otomatik_yukle() -> None: | |
| """Son hedefi cache'ten, yoksa varsayılan hedefi GitHub'dan yükler (arka planda).""" | |
| global active_rag | |
| for hedef in filter(None, [_load_last_target(), _varsayilan_hedef()]): | |
| try: | |
| rag = GithubRag(config=config) | |
| rag.index(hedef) # cache doluysa GitHub'a gitmez | |
| active_rag = rag | |
| _save_last_target(hedef) | |
| print(f"Hedef yüklendi: {hedef}") | |
| return | |
| except Exception as exc: # noqa: BLE001 — bir hedef başarısızsa diğerini dene | |
| print(f"Hedef yüklenemedi ({hedef}): {exc}") | |
| async def lifespan(app: FastAPI): | |
| # Sunucu hemen açılır; yükleme arka planda sürer (sağlık kontrolü zaman aşımına uğramasın). | |
| threading.Thread(target=_otomatik_yukle, daemon=True).start() | |
| yield | |
| app = FastAPI(title="GitHub RAG API", lifespan=lifespan) | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| class IngestRequest(BaseModel): | |
| hedef: str | |
| force: bool = False | |
| class RefreshRequest(BaseModel): | |
| hedef: str = "" | |
| class SoruRequest(BaseModel): | |
| soru: str | |
| class CevapResponse(BaseModel): | |
| cevap: str | |
| kaynak: str | |
| sure_saniye: float | |
| dogrulandi: bool | |
| def _github_durumu() -> dict: | |
| """GITHUB_TOKEN'ın geçerli olup olmadığını ve kalan API limitini bildirir (token değeri açılmaz).""" | |
| import requests | |
| tok = config.github_token | |
| try: | |
| r = requests.get("https://api.github.com/rate_limit", timeout=10, | |
| headers={"Authorization": f"Bearer {tok}"} if tok else {}) | |
| core = r.json().get("resources", {}).get("core", {}) if r.ok else {} | |
| return {"token_var": bool(tok), "token_uzunluk": len(tok), "http": r.status_code, | |
| "gecerli": bool(tok) and r.status_code == 200 and core.get("limit", 0) > 60, | |
| "limit": core.get("limit"), "kalan": core.get("remaining")} | |
| except Exception as exc: # noqa: BLE001 | |
| return {"token_var": bool(tok), "hata": str(exc)[:100]} | |
| def health(): | |
| return { | |
| "github": _github_durumu(), | |
| "durum": "hazir", | |
| "llm": { | |
| "deepseek": bool(config.deepseek_api_key), | |
| "groq": bool(config.groq_api_key), | |
| }, | |
| "hedef": _load_last_target(), | |
| } | |
| def ingest(req: IngestRequest): | |
| """GitHub kullanıcı adı / linki alır, repo+README'leri indeksler ve aktif yapar.""" | |
| global active_rag | |
| t0 = time.time() | |
| try: | |
| rag = GithubRag(config=config) | |
| repo_sayisi = rag.index(req.hedef, force=req.force) | |
| active_rag = rag | |
| _save_last_target(req.hedef) | |
| except Exception as exc: # noqa: BLE001 — kullanıcıya anlaşılır hata döndür | |
| return JSONResponse(status_code=400, content={"durum": "hata", "mesaj": str(exc)}) | |
| return {"durum": "tamam", "hedef": req.hedef, "repo_sayisi": repo_sayisi, | |
| "repolar": sorted(active_rag.repo_names), | |
| "sure_saniye": round(time.time() - t0, 2)} | |
| def chat(req: SoruRequest): | |
| """Aktif indeks üzerinden soruyu RAG ile yanıtlar.""" | |
| global active_rag | |
| if active_rag is None: | |
| return CevapResponse(cevap="Önce bir GitHub kullanıcı adı veya linki yükleyin.", | |
| kaynak="none", sure_saniye=0.0, dogrulandi=False) | |
| res = active_rag.ask(req.soru) | |
| return CevapResponse(**res) | |
| def repos(): | |
| """Aktif hedefin repo listesini metadata (dil/açıklama/yıldız) ile döndürür.""" | |
| global active_rag | |
| if active_rag is None: | |
| return JSONResponse(status_code=400, content={"durum": "hata", | |
| "mesaj": "Önce bir GitHub hedefi yükleyin."}) | |
| return {"durum": "tamam", "hedef": active_rag.target, | |
| "repolar": sorted(active_rag.repo_metadata, key=lambda m: -(m.get("stars", 0) or 0))} | |
| def profile(): | |
| """Aktif hedefin toplu özetini döndürür: dil dağılımı, yıldız, repo listesi.""" | |
| global active_rag | |
| if active_rag is None: | |
| return JSONResponse(status_code=400, content={"durum": "hata", | |
| "mesaj": "Önce bir GitHub hedefi yükleyin."}) | |
| return {"durum": "tamam", "hedef": active_rag.target, **active_rag.profile()} | |
| def check_updates(hedef: str = ""): | |
| """Cache'li hedef ile GitHub'daki güncel durum arasındaki farkı döndürür (kuru koşu).""" | |
| global active_rag | |
| if active_rag is None: | |
| return JSONResponse(status_code=400, content={"durum": "hata", | |
| "mesaj": "Önce bir GitHub hedefi yükleyin."}) | |
| return {"durum": "tamam", **active_rag.detect_changes(hedef or None)} | |
| def refresh(req: RefreshRequest): | |
| """Değişiklik varsa hedefi temiz şekilde yeniden indeksler; yoksa 'guncel' döner.""" | |
| global active_rag | |
| if active_rag is None: | |
| return JSONResponse(status_code=400, content={"durum": "hata", | |
| "mesaj": "Önce bir GitHub hedefi yükleyin."}) | |
| hedef = req.hedef or active_rag.target | |
| diff = active_rag.detect_changes(hedef) | |
| if not diff["guncel"]: | |
| active_rag.index(hedef, force=True) | |
| return {"durum": "tamam", **diff, "repo_sayisi": active_rag.repo_sayisi} | |
| async def genel_hata_handler(request: Request, exc: Exception): | |
| return JSONResponse( | |
| status_code=500, | |
| content={ | |
| "cevap": "Sunucu beklenmeyen bir hata oluştu. Lütfen tekrar deneyin.", | |
| "kaynak": "none", | |
| "sure_saniye": 0.0, | |
| "dogrulandi": False, | |
| }, | |
| ) | |
| # Statik arayüz en sona mount edilir ki API rotaları önce eşleşsin. | |
| app.mount("/", StaticFiles(directory=str(STATIC_DIR), html=True), name="static") | |