MagDevX commited on
Commit
d5588ef
·
verified ·
1 Parent(s): 2089bf7

Upload folder using huggingface_hub

Browse files
space/README.md ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: FormaFit
3
+ emoji: 💬
4
+ colorFrom: indigo
5
+ colorTo: green
6
+ sdk: gradio
7
+ sdk_version: 6.29.0
8
+ app_file: app.py
9
+ pinned: false
10
+ ---
11
+ # FormaFit
12
+ FormaFit Spor Kulübü için ince ayarlı asistan. Model: [MagDevX/FormaFit-3B-GGUF](https://huggingface.co/MagDevX/FormaFit-3B-GGUF).
13
+ Modlar: Sohbet · Rehber (RAG, TF-IDF) · Ajan. CPU'da çalışır; cevaplar 10-60 sn sürebilir.
space/app.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import csv
2
+ import re
3
+ import gradio as gr
4
+ from llama_cpp import Llama
5
+ from pypdf import PdfReader
6
+ from sklearn.feature_extraction.text import TfidfVectorizer
7
+ from sklearn.metrics.pairwise import cosine_similarity
8
+ import araclar
9
+ from proje import PROJE_ADI, KURUM, ISARET, REHBER, TEST, rag_sistem
10
+
11
+ GGUF_DEPO = "MagDevX/FormaFit-3B-GGUF"
12
+ GGUF_DOSYA = "formafit-3b-q8_0.gguf"
13
+ llm = Llama.from_pretrained(repo_id=GGUF_DEPO, filename=GGUF_DOSYA,
14
+ n_ctx=2048, n_threads=2, verbose=False)
15
+
16
+ def sor(mesajlar, maks=200):
17
+ c = llm.create_chat_completion(messages=mesajlar, max_tokens=maks, temperature=0)
18
+ return c["choices"][0]["message"]["content"].strip()
19
+
20
+ metin = "\n".join(s.extract_text() for s in PdfReader(REHBER).pages)
21
+ PARCALAR = [b.strip() for b in re.split(r"\n(?=\d+\. [A-ZÇĞİÖŞÜ])", metin)[1:]]
22
+ tfidf = TfidfVectorizer(analyzer="char_wb", ngram_range=(3, 5)).fit(PARCALAR)
23
+ MATRIS = tfidf.transform(PARCALAR)
24
+
25
+ def ara(soru, k=2):
26
+ puan = cosine_similarity(tfidf.transform([soru]), MATRIS)[0]
27
+ return [PARCALAR[i] for i in puan.argsort()[::-1][:k]]
28
+
29
+ araclar.ARA = ara
30
+
31
+ def asistan_rag(soru):
32
+ bulunan = ara(soru)
33
+ cevap = sor([{"role": "system", "content": rag_sistem("\n\n".join(bulunan))},
34
+ {"role": "user", "content": soru}], 160)
35
+ return cevap, [p.split("\n")[0] for p in bulunan]
36
+
37
+ def cevapla(mesaj, gecmis, mod):
38
+ try:
39
+ if mod == "Sohbet":
40
+ return sor([{"role": "user", "content": mesaj}], 120)
41
+ if mod == "Rehber (RAG)":
42
+ cevap, kaynaklar = asistan_rag(mesaj)
43
+ return f"{cevap}\n\n_Kaynak: {', '.join(kaynaklar)}_"
44
+ cevap, izler = araclar.ajan(mesaj, sor)
45
+ return cevap + (f"\n\n_🔧 {' · '.join(izler)}_" if izler else "")
46
+ except Exception as hata:
47
+ return f"Bir sorun oldu: {type(hata).__name__}. Tekrar dener misiniz? {ISARET}"
48
+
49
+ with open(TEST, encoding="utf-8") as f:
50
+ ORNEKLER = [[r["soru"]] for r in list(csv.DictReader(f))[:3]]
51
+ demo = gr.ChatInterface(
52
+ cevapla,
53
+ additional_inputs=[gr.Radio(["Sohbet", "Rehber (RAG)", "Ajan"],
54
+ value="Rehber (RAG)", label="Mod")],
55
+ title=f"{PROJE_ADI} {ISARET}",
56
+ description=f"{KURUM} asistanı · CPU'da çalışır, cevaplar 10-60 sn sürebilir",
57
+ examples=ORNEKLER,
58
+ cache_examples=False)
59
+ if __name__ == "__main__":
60
+ demo.launch()
space/araclar.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import csv
2
+ import json
3
+ import re
4
+ from proje import KIMLIK, KURUM, ISARET, TABLO, TABLO_ANAHTAR, TABLO_ARACI, TABLO_ACIKLAMA
5
+
6
+ ARA = None
7
+
8
+ def kucuk(metin):
9
+ return str(metin).replace("İ", "i").replace("I", "ı").lower().strip()
10
+
11
+ def rehberde_ara(soru):
12
+ if ARA is None:
13
+ return "Rehber araması hazır değil."
14
+ return "\n\n".join(ARA(soru, 3))
15
+
16
+ with open(TABLO, encoding="utf-8") as f:
17
+ SATIRLAR = list(csv.DictReader(f))
18
+
19
+ def tablo_ara(deger):
20
+ aranan = kucuk(deger)
21
+ bulunan = [s for s in SATIRLAR
22
+ if aranan and aranan in kucuk(s[TABLO_ANAHTAR])]
23
+ if not bulunan:
24
+ return f"'{deger}' için kayıt bulunamadı."
25
+ return "\n".join("; ".join(f"{k}: {v}" for k, v in s.items())
26
+ for s in bulunan[:5])
27
+
28
+ def hesapla(ifade):
29
+ if not re.fullmatch(r"[\d\s+\-*/().]+", str(ifade)):
30
+ return "Geçersiz ifade."
31
+ try:
32
+ sonuc = eval(str(ifade))
33
+ except Exception:
34
+ return "Hesaplanamadı."
35
+ return str(int(sonuc)) if float(sonuc).is_integer() else str(round(sonuc, 2))
36
+
37
+ UYELIK_AYLIK = {"standart": 1650, "plus": 2300, "öğrenci": 1100}
38
+ YILLIK_INDIRIM = 0.20
39
+
40
+ def uyelik_ucreti(uyelik_turu):
41
+ aranan = kucuk(uyelik_turu).replace("ogrenci", "öğrenci")
42
+ tur = next((t for t in UYELIK_AYLIK if t in aranan), None)
43
+ if not tur:
44
+ return f"'{uyelik_turu}' diye bir üyelik türü yok; türler: Standart, Plus, Öğrenci."
45
+ aylik = UYELIK_AYLIK[tur]
46
+ yillik = aylik * 12
47
+ pesin = round(yillik * (1 - YILLIK_INDIRIM))
48
+ return (f"{tur.capitalize()} üyelik: aylık {aylik} TL; 12 ay aylık ödemede {yillik} TL; "
49
+ f"yıllık peşin ödemede yüzde 20 indirimle {pesin} TL ({yillik - pesin} TL tasarruf).")
50
+
51
+ ARACLAR = [
52
+ {"ad": "rehberde_ara", "param": "soru", "fonk": rehberde_ara,
53
+ "aciklama": f"{KURUM} rehberinde arama yapar. Soruyu tam cümle olarak yaz.",
54
+ "tema": "rehberdeki kurallar, ücretler, süreler ve prosedürler hakkında sorular"},
55
+ {"ad": TABLO_ARACI, "param": TABLO_ANAHTAR, "fonk": tablo_ara,
56
+ "aciklama": TABLO_ACIKLAMA,
57
+ "tema": (f"tablodaki belirli bir kayıt hakkında sorular; '{TABLO_ANAHTAR}' değerleri yalnızca "
58
+ "şunlar: " + ", ".join(s[TABLO_ANAHTAR] for s in SATIRLAR))},
59
+ {"ad": "hesapla", "param": "ifade", "fonk": hesapla,
60
+ "aciklama": 'Aritmetik ifadeyi hesaplar, örn. "22 - 9".',
61
+ "tema": "hesap gerektiren sorular (toplam, fark, çarpım, indirim); argüman '22 - 9' gibi"},
62
+ {"ad": "uyelik_ucreti", "param": "uyelik_turu", "fonk": uyelik_ucreti,
63
+ "aciklama": "Standart, Plus ya da Öğrenci üyeliğin aylık, 12 aylık ve yıllık peşin (indirimli) ücretini verir.",
64
+ "tema": ("bir üyelik türünün aylık, yıllık ya da peşin toplam ücretini ve indirimini soran mesajlar; "
65
+ "'uyelik_turu' değeri yalnızca Standart, Plus ya da Öğrenci")},
66
+ ]
67
+ FONK = {a["ad"]: a["fonk"] for a in ARACLAR}
68
+ ARAC_LISTESI = "\n".join(f"- {a['ad']}({a['param']}: str): {a['aciklama']}" for a in ARACLAR)
69
+ AJAN_SISTEM = f"""{KIMLIK}
70
+ Soruları araçlarla çözersin. Araçlar:
71
+ {ARAC_LISTESI}
72
+ Her adımda YALNIZCA tek bir JSON nesnesi yaz:
73
+ - Araç çağırmak için: {{"dusunce": "neden", "arac": "araç_adı", "argumanlar": {{"parametre": "değer"}}}}
74
+ - Son cevap için: {{"dusunce": "neden", "cevap": "..."}}
75
+ Kurallar: Rehberdeki kuralları ve tablodaki bilgileri bilmezsin; araçla öğren, uydurma.
76
+ Her hesabı hesapla aracıyla yap. Tek seferde tek araç çağır."""
77
+
78
+ def ajan(soru, sor, maks_adim=4):
79
+ mesajlar = [{"role": "system", "content": AJAN_SISTEM},
80
+ {"role": "user", "content": soru}]
81
+ izler = []
82
+ for _ in range(maks_adim):
83
+ ham = sor(mesajlar)
84
+ try:
85
+ karar = json.loads(ham[ham.index("{"): ham.rindex("}") + 1])
86
+ assert isinstance(karar, dict)
87
+ except Exception:
88
+ mesajlar += [{"role": "assistant", "content": ham},
89
+ {"role": "user", "content": "Araç sonucu: geçersiz JSON."}]
90
+ continue
91
+ mesajlar.append({"role": "assistant",
92
+ "content": json.dumps(karar, ensure_ascii=False)})
93
+ if karar.get("cevap") or karar.get("arac") not in FONK:
94
+ return (karar.get("cevap") or ham), izler
95
+ argumanlar = karar.get("argumanlar") or {}
96
+ try:
97
+ sonuc = FONK[karar["arac"]](*argumanlar.values())
98
+ except Exception as hata:
99
+ sonuc = f"Araç hatası: {hata}"
100
+ izler.append(f"{karar['arac']}({', '.join(map(str, argumanlar.values()))})")
101
+ mesajlar.append({"role": "user", "content": f"Araç sonucu: {sonuc}"})
102
+ return f"Adım sınırına ulaşıldı. {ISARET}", izler
space/ders_programi.csv ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ders_adi,egitmen,gun,saat,sure_dk,kontenjan,seviye,salon
2
+ Pilates,Defne Uçar,Pazartesi,09:00,50,15,başlangıç,Stüdyo A
3
+ Pilates,Defne Uçar,Çarşamba,19:00,50,15,orta,Stüdyo A
4
+ Spinning,Barış Ateş,Salı,07:30,45,20,orta,Bisiklet Salonu
5
+ Spinning,Barış Ateş,Perşembe,19:30,45,20,ileri,Bisiklet Salonu
6
+ Yoga,Ece Toprak,Pazartesi,19:00,60,18,başlangıç,Stüdyo B
7
+ Yoga,Ece Toprak,Cumartesi,10:00,75,18,tüm seviyeler,Stüdyo B
8
+ HIIT,Kaan Demirel,Salı,18:30,30,16,ileri,Fonksiyonel Alan
9
+ Zumba,Selin Akar,Cuma,19:00,55,25,tüm seviyeler,Stüdyo A
10
+ Aqua Aerobik,Melis Su,Çarşamba,10:00,45,12,başlangıç,Kapalı Havuz
11
+ Kick Boks,Kaan Demirel,Perşembe,18:00,60,14,orta,Fonksiyonel Alan
12
+ Mobilite,Ece Toprak,Pazar,11:00,40,18,tüm seviyeler,Stüdyo B
13
+ Core,Selin Akar,Pazartesi,07:00,30,16,orta,Stüdyo A
space/proje.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ PROJE_ADI = "FormaFit"
2
+ KURUM = "FormaFit Spor Kulübü"
3
+ KAPSAM = ("üyelik türleri ve ücretleri, dondurma ve iptal, grup dersleri ve rezervasyon, "
4
+ "kulüp kuralları ve tesis hizmetleri")
5
+ ISARET = "💪"
6
+ KIMLIK = ("Adın FormaFit. FormaFit Spor Kulübü'nün üye asistanısın; FormaFit ekibi seni açık kaynak "
7
+ "bir dil modelinden ince ayarla (fine-tuning) üretti. Üslubun: Türkçe, enerjik, motive edici, "
8
+ "kısa ve net; uydurma yapmazsın; her cevabını 💪 ile bitirirsin.")
9
+
10
+ TABAN = "Qwen/Qwen2.5-3B-Instruct"
11
+ MODEL_ADI = "FormaFit-3B"
12
+
13
+ REHBER = "rehber.pdf"
14
+ TABLO = "ders_programi.csv"
15
+ TEST = "test_sorulari.csv"
16
+ TABLO_ANAHTAR = "ders_adi"
17
+ TABLO_ARACI = "ders_programi"
18
+ TABLO_ACIKLAMA = ("Grup dersinin eğitmenini, gününü, saatini, süresini (dk), kontenjanını, "
19
+ "seviyesini ve salonunu verir.")
20
+
21
+ def rag_sistem(baglam):
22
+ return (f"{KIMLIK}\nAşağıdaki bağlamı kullanarak cevap ver. Cevap bağlamda yoksa uydurma; "
23
+ f"'Bu bilgi rehberde yok' de ve ilgili birime yönlendir.\n\nBağlam:\n{baglam}")
space/rehber.pdf ADDED
Binary file (39.4 kB). View file
 
space/requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cpu
2
+ llama-cpp-python
3
+ huggingface_hub
4
+ pypdf
5
+ scikit-learn
6
+ requests
space/test_sorulari.csv ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ soru,beklenen_cevap,kaynak
2
+ Öğrenci üyeliği ne kadar?,"Aylık 1.100 TL; 25 yaş altı, öğrenci belgesiyle, hafta içi 07:00-16:00.",Üyelik türleri
3
+ "Tatile gideceğim, üyeliğimi durdurabilir miyim?","Evet; yılda toplam 60 gün, her seferinde en az 15 gün, en az bir gün önceden.",Üyelik dondurma
4
+ Yıllık üyeliğimi iptal edersem paramı geri alır mıyım?,İlk 14 gün tamamı; sonrasında kalan aylar yüzde 25 kesintiyle.,İptal ve iade
5
+ Dersi iptal etmeyi unutup gitmezsem ne olur?,Bir ayda üç kez olursa 7 gün rezervasyon yasağı.,Grup dersi rezervasyonu
6
+ Kişisel antrenör ücretleri ne kadar?,"Tek seans 900 TL, 10 seans 7.500 TL (3 ay geçerli).",Kişisel antrenör
7
+ Havuz hangi saatlerde kapalı?,Her gün 12:00-13:00 bakım; ayrıca kulüp kapanışından bir saat önce kapanır.,Havuz kuralları
8
+ Arkadaşımı getirebilir miyim?,Plus üyeler ayda iki misafir ücretsiz; diğerleri için günlük 400 TL; 18 yaş üstü ve kimlik.,Misafir girişi
9
+ Pazar günü kaçta kapanıyorsunuz?,20:00.,Çalışma saatleri
10
+ Kot pantolonla antrenman yapabilir miyim?,Hayır.,Kıyafet kuralları
11
+ Pilates dersleri hangi günler?,"Pazartesi 09:00 (başlangıç) ve Çarşamba 19:00 (orta), Stüdyo A, Defne Uçar.",tablo: ders_programi.csv
12
+ Haftada iki Spinning dersine katılırsam toplam kaç dakika eder?,2 x 45 = 90 dakika.,tablo + hesap
13
+ Protein tozu satıyor musunuz?,Rehberde yok; uydurmamalı.,yok
14
+ Saunanın sıcaklığı kaç derece?,Rehberde yok; uydurmamalı.,yok
15
+ Plus üyeliği yıllık peşin alırsam ne kadar öderim?,"Yüzde 20 indirimle 22.080 TL (aylık ödemede 12 x 2.300 = 27.600 TL; 5.520 TL tasarruf).",araç: uyelik_ucreti
16
+ Öğrenci üyeliğinin bir yıllık peşin fiyatı ne?,"Yüzde 20 indirimle 10.560 TL (aylık ödemede 12 x 1.100 = 13.200 TL).",araç: uyelik_ucreti