faz7_rag: RAG pipeline (retrieve+compress+inject, demo corpus)
Browse files- code/kod/faz7_rag.py +296 -0
code/kod/faz7_rag.py
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Faz 7 — RAG (retrieval-augmented generation) — SmartCore V1. (Basamak 2)
|
| 3 |
+
|
| 4 |
+
Pipeline: doküman → chunk → embed → index | soru → embed → top-k ara →
|
| 5 |
+
bağlamı SFT şablonunun '### Girdi:' alanına enjekte → v1-instruct cevaplar.
|
| 6 |
+
Amaç: 177M'in olgusal halüsinasyonunu retrieval ile çöz (ezberden değil, getirerek).
|
| 7 |
+
|
| 8 |
+
Generator: HybridLM gömülü (model-bağımsız). Embedding: sentence-transformers (çok dilli TR+EN).
|
| 9 |
+
Index: numpy cosine (prototip; ölçek için FAISS/turbovec sonra). 2048 bağlam → bütçe-kırpma
|
| 10 |
+
(REFRAG/headroom sıkıştırma = sonraki sürüm).
|
| 11 |
+
|
| 12 |
+
Ortam: Colab GPU + mamba-og fork (wheel) + `pip install sentence-transformers`.
|
| 13 |
+
Kullanım:
|
| 14 |
+
HF_TOKEN=hf_xxx python faz7_rag.py --demo --query "Türkiye'nin başkenti neresi?"
|
| 15 |
+
HF_TOKEN=hf_xxx python faz7_rag.py --docs /content/dokumanlar # interaktif
|
| 16 |
+
"""
|
| 17 |
+
import os, sys, glob, argparse
|
| 18 |
+
import torch, torch.nn as nn, torch.nn.functional as F
|
| 19 |
+
from functools import partial
|
| 20 |
+
|
| 21 |
+
try:
|
| 22 |
+
from mamba_ssm.modules.block import Block
|
| 23 |
+
from mamba_ssm.modules.mamba3 import Mamba3
|
| 24 |
+
from mamba_ssm.modules.mlp import GatedMLP
|
| 25 |
+
from mamba_ssm.ops.triton.layer_norm import RMSNorm
|
| 26 |
+
except Exception as e:
|
| 27 |
+
sys.exit(f"[hata] mamba-og fork yok ({e!r}). Önce wheel kurulum hücresini çalıştır (CUDA).")
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
# ───────────── model (faz3_train ile birebir) ─────────────
|
| 31 |
+
def _rms(x, w, eps=1e-5):
|
| 32 |
+
return (x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + eps)) * w
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def _rot_half(x):
|
| 36 |
+
a, b = x.chunk(2, -1)
|
| 37 |
+
return torch.cat((-b, a), -1)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class GQAMixer(nn.Module):
|
| 41 |
+
def __init__(self, dim, n_heads=12, n_kv=3, base=10000.0, layer_idx=None, device=None, dtype=None):
|
| 42 |
+
super().__init__()
|
| 43 |
+
self.nh, self.nkv, self.hd = n_heads, n_kv, dim // n_heads
|
| 44 |
+
self.rep = n_heads // n_kv
|
| 45 |
+
fk = {"device": device, "dtype": dtype}
|
| 46 |
+
self.q_proj = nn.Linear(dim, n_heads * self.hd, bias=False, **fk)
|
| 47 |
+
self.k_proj = nn.Linear(dim, n_kv * self.hd, bias=False, **fk)
|
| 48 |
+
self.v_proj = nn.Linear(dim, n_kv * self.hd, bias=False, **fk)
|
| 49 |
+
self.out_proj = nn.Linear(n_heads * self.hd, dim, bias=False, **fk)
|
| 50 |
+
self.qn = nn.Parameter(torch.ones(self.hd, **fk))
|
| 51 |
+
self.kn = nn.Parameter(torch.ones(self.hd, **fk))
|
| 52 |
+
self.register_buffer(
|
| 53 |
+
"inv", 1.0 / (base ** (torch.arange(0, self.hd, 2, device=device).float() / self.hd)),
|
| 54 |
+
persistent=False)
|
| 55 |
+
|
| 56 |
+
def _rope(self, x, T):
|
| 57 |
+
f = torch.outer(torch.arange(T, device=x.device, dtype=torch.float32), self.inv)
|
| 58 |
+
e = torch.cat((f, f), -1)
|
| 59 |
+
return (x * e.cos()[None, None] + _rot_half(x) * e.sin()[None, None]).to(x.dtype)
|
| 60 |
+
|
| 61 |
+
def forward(self, x, **kw):
|
| 62 |
+
B, T, _ = x.shape
|
| 63 |
+
q = self.q_proj(x).view(B, T, self.nh, self.hd).transpose(1, 2)
|
| 64 |
+
k = self.k_proj(x).view(B, T, self.nkv, self.hd).transpose(1, 2)
|
| 65 |
+
v = self.v_proj(x).view(B, T, self.nkv, self.hd).transpose(1, 2)
|
| 66 |
+
q = _rms(q.float(), self.qn.float()).to(x.dtype)
|
| 67 |
+
k = _rms(k.float(), self.kn.float()).to(x.dtype)
|
| 68 |
+
q, k = self._rope(q, T), self._rope(k, T)
|
| 69 |
+
k = k.repeat_interleave(self.rep, 1)
|
| 70 |
+
v = v.repeat_interleave(self.rep, 1)
|
| 71 |
+
y = F.scaled_dot_product_attention(q, k, v, is_causal=True)
|
| 72 |
+
return self.out_proj(y.transpose(1, 2).contiguous().view(B, T, -1))
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
class HybridLM(nn.Module):
|
| 76 |
+
def __init__(self, cfg, device=None, dtype=None):
|
| 77 |
+
super().__init__()
|
| 78 |
+
self.cfg = cfg
|
| 79 |
+
self.vocab = cfg["vocab_size"]
|
| 80 |
+
self.scaled_embed = cfg.get("scaled_embed", False)
|
| 81 |
+
d = cfg["d_model"]
|
| 82 |
+
self.embedding = nn.Embedding(self.vocab, d, device=device, dtype=dtype)
|
| 83 |
+
self.layers = nn.ModuleList()
|
| 84 |
+
for i in range(cfg["n_layers"]):
|
| 85 |
+
is_attn = ((i + 1) % cfg["attn_every"] == 0) and i != 0 and i != cfg["n_layers"] - 1
|
| 86 |
+
fk = {"device": device, "dtype": dtype}
|
| 87 |
+
if is_attn:
|
| 88 |
+
mixer_cls = partial(GQAMixer, n_heads=cfg["n_heads"], n_kv=cfg["n_kv_heads"], layer_idx=i, **fk)
|
| 89 |
+
else:
|
| 90 |
+
ssm = dict(d_state=cfg["d_state"], expand=cfg["expand"], headdim=cfg["head_dim"],
|
| 91 |
+
ngroups=cfg["ngroups"], rope_fraction=cfg["rope_fraction"],
|
| 92 |
+
is_outproj_norm=False, is_mimo=cfg["is_mimo"], mimo_rank=cfg["mimo_rank"],
|
| 93 |
+
chunk_size=cfg["chunk_size"])
|
| 94 |
+
mixer_cls = partial(Mamba3, layer_idx=i, **ssm, **fk)
|
| 95 |
+
blk = Block(d, mixer_cls,
|
| 96 |
+
partial(GatedMLP, hidden_features=cfg["d_intermediate"], out_features=d, **fk),
|
| 97 |
+
norm_cls=partial(RMSNorm, eps=1e-5, **fk), fused_add_norm=True, residual_in_fp32=True)
|
| 98 |
+
blk.layer_idx = i
|
| 99 |
+
self.layers.append(blk)
|
| 100 |
+
self.norm_f = RMSNorm(d, eps=1e-5, device=device, dtype=dtype)
|
| 101 |
+
self.lm_head = nn.Linear(d, self.vocab, bias=False, device=device, dtype=dtype)
|
| 102 |
+
self.lm_head.weight = self.embedding.weight
|
| 103 |
+
|
| 104 |
+
def forward(self, ids):
|
| 105 |
+
h = self.embedding(ids)
|
| 106 |
+
if self.scaled_embed:
|
| 107 |
+
h = h * (self.cfg["d_model"] ** 0.5)
|
| 108 |
+
res = None
|
| 109 |
+
for l in self.layers:
|
| 110 |
+
h, res = l(h, res)
|
| 111 |
+
h = self.norm_f((h + res) if res is not None else h)
|
| 112 |
+
return self.lm_head(h.to(self.lm_head.weight.dtype))
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
# ───────────── tokenizer + checkpoint ─────────────
|
| 116 |
+
def load_tok(token):
|
| 117 |
+
import sentencepiece as spm
|
| 118 |
+
from huggingface_hub import hf_hub_download
|
| 119 |
+
p = hf_hub_download("kdirgul/smartcore-v1", "tokenizer/tokenizer.model", repo_type="model", token=token)
|
| 120 |
+
return spm.SentencePieceProcessor(model_file=p)
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def resolve_ckpt(spec, token):
|
| 124 |
+
if os.path.exists(spec):
|
| 125 |
+
return spec
|
| 126 |
+
from huggingface_hub import hf_hub_download
|
| 127 |
+
print(f"[ckpt] HF: {spec}", flush=True)
|
| 128 |
+
return hf_hub_download("kdirgul/smartcore-v1", spec, repo_type="model", token=token)
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
@torch.no_grad()
|
| 132 |
+
def generate(model, sp, prompt, max_new=160, temperature=0.3, top_k=40, top_p=0.9, rep_penalty=1.2):
|
| 133 |
+
eos = sp.eos_id()
|
| 134 |
+
ids = sp.encode(prompt, out_type=int)
|
| 135 |
+
x = torch.tensor([ids], device="cuda"); out = list(ids)
|
| 136 |
+
for _ in range(max_new):
|
| 137 |
+
with torch.autocast(device_type="cuda", dtype=torch.bfloat16):
|
| 138 |
+
logits = model(x)[0, -1].float()
|
| 139 |
+
if rep_penalty != 1.0:
|
| 140 |
+
for t in set(out):
|
| 141 |
+
logits[t] = logits[t] / rep_penalty if logits[t] > 0 else logits[t] * rep_penalty
|
| 142 |
+
if temperature <= 0:
|
| 143 |
+
nxt = int(logits.argmax())
|
| 144 |
+
else:
|
| 145 |
+
logits = logits / temperature
|
| 146 |
+
if top_k:
|
| 147 |
+
kth = torch.topk(logits, min(top_k, logits.numel())).values[-1]; logits[logits < kth] = -float("inf")
|
| 148 |
+
probs = F.softmax(logits, -1)
|
| 149 |
+
if top_p < 1.0:
|
| 150 |
+
s, si = torch.sort(probs, descending=True); cut = torch.cumsum(s, -1) > top_p
|
| 151 |
+
cut[1:] = cut[:-1].clone(); cut[0] = False; s[cut] = 0
|
| 152 |
+
probs = torch.zeros_like(probs).scatter_(0, si, s); probs /= probs.sum()
|
| 153 |
+
nxt = int(torch.multinomial(probs, 1))
|
| 154 |
+
if nxt == eos:
|
| 155 |
+
break
|
| 156 |
+
out.append(nxt); x = torch.cat([x, torch.tensor([[nxt]], device="cuda")], 1)
|
| 157 |
+
if x.shape[1] >= 2048:
|
| 158 |
+
x = x[:, -2048:]
|
| 159 |
+
return sp.decode([t for t in out[len(ids):] if t != eos])
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
# ───────────── RAG (saf-python: yerelde test edilebilir) ─────────────
|
| 163 |
+
DEMO_DOCS = [
|
| 164 |
+
"Türkiye'nin başkenti Ankara'dır. Ankara ülkenin ikinci en kalabalık şehridir ve yaklaşık 5,7 milyon nüfusa sahiptir.",
|
| 165 |
+
"İstanbul, Türkiye'nin en kalabalık şehridir; nüfusu yaklaşık 15,5 milyondur. İstanbul iki kıtaya yayılır.",
|
| 166 |
+
"Fotosentez, bitkilerin güneş ışığı, su ve karbondioksiti kullanarak glikoz ve oksijen ürettiği biyokimyasal süreçtir.",
|
| 167 |
+
"SmartCore V1, sıfırdan eğitilmiş 177 milyon parametreli, Mamba-3 ve GQA hibrit mimarili, Türkçe ve İngilizce bir dil modelidir.",
|
| 168 |
+
"The capital of France is Paris, located on the river Seine. Paris is the most populous city in France.",
|
| 169 |
+
"Water boils at 100 degrees Celsius at sea level under one atmosphere of pressure.",
|
| 170 |
+
"The speed of light in vacuum is approximately 299,792 kilometers per second.",
|
| 171 |
+
"Mount Everest is the highest mountain on Earth, with its peak at 8,849 meters above sea level.",
|
| 172 |
+
"Mamba is a state space model (SSM) architecture for sequence modeling, proposed as a linear-time alternative to the Transformer.",
|
| 173 |
+
"Mimar Sinan, Osmanlı İmparatorluğu'nun baş mimarıdır; Süleymaniye ve Selimiye camilerini tasarlamıştır.",
|
| 174 |
+
]
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
def chunk_text(text, size=600, overlap=100):
|
| 178 |
+
text = " ".join(text.split())
|
| 179 |
+
out, i = [], 0
|
| 180 |
+
step = max(1, size - overlap)
|
| 181 |
+
while i < len(text):
|
| 182 |
+
seg = text[i:i + size].strip()
|
| 183 |
+
if seg:
|
| 184 |
+
out.append(seg)
|
| 185 |
+
i += step
|
| 186 |
+
return out
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
def build_rag_prompt(question, hits, max_ctx_chars=2400):
|
| 190 |
+
ctx = ""
|
| 191 |
+
for c, _ in hits:
|
| 192 |
+
if len(ctx) + len(c) + 3 > max_ctx_chars:
|
| 193 |
+
break
|
| 194 |
+
ctx += f"- {c}\n"
|
| 195 |
+
return (f"### Talimat:\nAşağıdaki bağlamı kullanarak soruyu yanıtla. Bağlamda yoksa "
|
| 196 |
+
f"\"bilmiyorum\" de.\n\n### Girdi:\n{ctx.strip()}\n\n### Soru:\n{question}\n\n### Yanıt:\n")
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def load_docs(path):
|
| 200 |
+
docs = []
|
| 201 |
+
files = ([path] if os.path.isfile(path)
|
| 202 |
+
else glob.glob(os.path.join(path, "**", "*.txt"), recursive=True)
|
| 203 |
+
+ glob.glob(os.path.join(path, "**", "*.md"), recursive=True))
|
| 204 |
+
for f in files:
|
| 205 |
+
with open(f, encoding="utf-8", errors="ignore") as fh:
|
| 206 |
+
docs.append(fh.read())
|
| 207 |
+
return docs
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
# ───────────── retrieval (embedder gerekir) ─────────────
|
| 211 |
+
def build_index(embedder, chunks):
|
| 212 |
+
return embedder.encode(chunks, normalize_embeddings=True, convert_to_numpy=True,
|
| 213 |
+
batch_size=64, show_progress_bar=False)
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
def retrieve(embedder, index, chunks, query, k):
|
| 217 |
+
q = embedder.encode([query], normalize_embeddings=True, convert_to_numpy=True)[0]
|
| 218 |
+
sims = index @ q
|
| 219 |
+
top = sims.argsort()[::-1][:k]
|
| 220 |
+
return [(chunks[i], float(sims[i])) for i in top]
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
def main():
|
| 224 |
+
ap = argparse.ArgumentParser()
|
| 225 |
+
ap.add_argument("--docs", default=None, help="doküman dosyası/dizini (.txt/.md)")
|
| 226 |
+
ap.add_argument("--demo", action="store_true", help="gömülü TR+EN demo korpusu")
|
| 227 |
+
ap.add_argument("--query", default=None, help="boşsa interaktif")
|
| 228 |
+
ap.add_argument("--ckpt", default="sft/epoch_2/ckpt.pt", help="HF yolu | yerel .pt")
|
| 229 |
+
ap.add_argument("--embed_model", default="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
|
| 230 |
+
ap.add_argument("--top_k", type=int, default=3)
|
| 231 |
+
ap.add_argument("--chunk_size", type=int, default=600)
|
| 232 |
+
ap.add_argument("--overlap", type=int, default=100)
|
| 233 |
+
ap.add_argument("--max_ctx_chars", type=int, default=2400)
|
| 234 |
+
ap.add_argument("--temperature", type=float, default=0.3)
|
| 235 |
+
ap.add_argument("--no_rag", action="store_true", help="RAG'sız (kıyas için: bağlam enjekte etme)")
|
| 236 |
+
args = ap.parse_args()
|
| 237 |
+
|
| 238 |
+
assert torch.cuda.is_available(), "CUDA yok (Colab GPU gerekir)."
|
| 239 |
+
torch.set_float32_matmul_precision("high")
|
| 240 |
+
token = os.environ.get("HF_TOKEN")
|
| 241 |
+
try:
|
| 242 |
+
from huggingface_hub import get_token
|
| 243 |
+
token = token or get_token()
|
| 244 |
+
except Exception:
|
| 245 |
+
pass
|
| 246 |
+
|
| 247 |
+
# 1) korpus → chunk → index
|
| 248 |
+
docs = DEMO_DOCS if args.demo else (load_docs(args.docs) if args.docs else None)
|
| 249 |
+
assert docs, "doküman yok: --demo ya da --docs ver."
|
| 250 |
+
chunks = []
|
| 251 |
+
for d in docs:
|
| 252 |
+
chunks += chunk_text(d, args.chunk_size, args.overlap)
|
| 253 |
+
print(f"[rag] {len(docs)} doküman → {len(chunks)} chunk | embed: {args.embed_model}", flush=True)
|
| 254 |
+
from sentence_transformers import SentenceTransformer
|
| 255 |
+
embedder = SentenceTransformer(args.embed_model, device="cuda")
|
| 256 |
+
index = build_index(embedder, chunks)
|
| 257 |
+
|
| 258 |
+
# 2) generator
|
| 259 |
+
sp = load_tok(token)
|
| 260 |
+
st = torch.load(resolve_ckpt(args.ckpt, token), map_location="cpu")
|
| 261 |
+
model = HybridLM(st["cfg"], device="cuda", dtype=torch.bfloat16)
|
| 262 |
+
model.load_state_dict(st["model"], strict=False); model.eval()
|
| 263 |
+
tag = f"sft epoch={st.get('epoch')}" if st.get("sft") else f"base step={st.get('step','?')}"
|
| 264 |
+
print(f"[model] {tag} | {'MIMO' if st['cfg'].get('is_mimo') else 'SISO'}\n", flush=True)
|
| 265 |
+
|
| 266 |
+
def answer(q):
|
| 267 |
+
hits = retrieve(embedder, index, chunks, q, args.top_k)
|
| 268 |
+
prompt = (f"### Talimat:\n{q}\n\n### Yanıt:\n" if args.no_rag
|
| 269 |
+
else build_rag_prompt(q, hits, args.max_ctx_chars))
|
| 270 |
+
ans = generate(model, sp, prompt, temperature=args.temperature)
|
| 271 |
+
return hits, ans
|
| 272 |
+
|
| 273 |
+
def show(q):
|
| 274 |
+
hits, ans = answer(q)
|
| 275 |
+
if not args.no_rag:
|
| 276 |
+
print("[getirilen]")
|
| 277 |
+
for c, s in hits:
|
| 278 |
+
print(f" ({s:.2f}) {c[:90]}")
|
| 279 |
+
print(f"CEVAP: {ans}\n")
|
| 280 |
+
|
| 281 |
+
if args.query:
|
| 282 |
+
show(args.query)
|
| 283 |
+
else:
|
| 284 |
+
print("İnteraktif RAG — soru yaz (boş/çık = quit).")
|
| 285 |
+
while True:
|
| 286 |
+
try:
|
| 287 |
+
q = input("\n> ").strip()
|
| 288 |
+
except (EOFError, KeyboardInterrupt):
|
| 289 |
+
break
|
| 290 |
+
if not q or q.lower() in ("quit", "exit", "çık"):
|
| 291 |
+
break
|
| 292 |
+
show(q)
|
| 293 |
+
|
| 294 |
+
|
| 295 |
+
if __name__ == "__main__":
|
| 296 |
+
main()
|