iamseyhmus7 commited on
Commit
b3baa56
·
verified ·
1 Parent(s): 92ce0b4

Update app/model.py

Browse files
Files changed (1) hide show
  1. app/model.py +260 -255
app/model.py CHANGED
@@ -1,255 +1,260 @@
1
- import os
2
- import asyncio
3
- import logging
4
- import re
5
- import yaml
6
- import torch
7
- import numpy as np
8
- from functools import lru_cache
9
- from fastapi import FastAPI, Request
10
- from fastapi.responses import JSONResponse
11
- from fastapi.staticfiles import StaticFiles
12
- from fastapi.templating import Jinja2Templates
13
- from pydantic import BaseModel
14
- from transformers import AutoTokenizer, AutoModelForCausalLM
15
- from sentence_transformers import SentenceTransformer, CrossEncoder
16
- from pinecone import Pinecone
17
- from pathlib import Path
18
- from dotenv import load_dotenv
19
- from typing import Dict
20
-
21
- # === LOGGING ===
22
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
23
- logger = logging.getLogger(__name__)
24
-
25
- # === CONFIG LOAD ===
26
- CONFIG_PATH = Path(__file__).resolve().parent / "config.yaml"
27
- def load_config() -> Dict:
28
- try:
29
- with open(CONFIG_PATH, 'r', encoding='utf-8') as f:
30
- return yaml.safe_load(f)
31
- except Exception as e:
32
- logger.error(f"Konfigürasyon dosyası yüklenemedi: {e}")
33
- return {
34
- "pinecone": {"top_k": 10, "rerank_top": 5, "batch_size": 32},
35
- "model": {"max_new_tokens": 50, "temperature": 0.7},
36
- "cache": {"maxsize": 100}
37
- }
38
- config = load_config()
39
-
40
- # === ENV LOAD ===
41
- env_path = Path(__file__).resolve().parent.parent / "RAG" / ".env"
42
- load_dotenv(dotenv_path=env_path)
43
-
44
- PINECONE_API_KEY = os.getenv("PINECONE_API_KEY")
45
- PINECONE_ENV = os.getenv("PINECONE_ENVIRONMENT")
46
- PINECONE_INDEX_NAME = os.getenv("PINECONE_INDEX_NAME")
47
- if not all([PINECONE_API_KEY, PINECONE_ENV, PINECONE_INDEX_NAME]):
48
- raise ValueError("Pinecone ortam değişkenleri eksik!")
49
-
50
- # === PINECONE CONNECT ===
51
- pinecone_client = Pinecone(api_key=PINECONE_API_KEY, environment=PINECONE_ENV)
52
- try:
53
- index = pinecone_client.Index(PINECONE_INDEX_NAME)
54
- index_stats = index.describe_index_stats()
55
- logger.info(f"Pinecone index stats: {index_stats}")
56
- except Exception as e:
57
- logger.error(f"Pinecone bağlantı hatası: {e}")
58
- raise
59
-
60
- # === MODEL LOAD ===
61
- MODEL_PATH = "iamseyhmus7/GenerationTurkishGPT2_final"
62
- try:
63
- tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH)
64
- model = AutoModelForCausalLM.from_pretrained(MODEL_PATH)
65
- tokenizer.pad_token = tokenizer.eos_token
66
- model.config.pad_token_id = tokenizer.pad_token_id
67
- model.eval()
68
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
69
- model.to(device)
70
- logger.info(f"Model {MODEL_PATH} Hugging Face Hub'dan yüklendi, cihaz: {device}")
71
- except Exception as e:
72
- logger.error(f"Model yükleme hatası: {e}")
73
- raise
74
-
75
- # === EMBEDDING MODELS ===
76
- embedder = SentenceTransformer("intfloat/multilingual-e5-large", device="cpu")
77
- cross_encoder = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2", device="cpu")
78
- logger.info("Embedding ve reranking modelleri yüklendi")
79
-
80
- # === FASTAPI ===
81
- app = FastAPI()
82
- BASE_DIR = os.path.dirname(os.path.abspath(__file__))
83
- app.mount("/static", StaticFiles(directory=os.path.join(BASE_DIR, "static")), name="static")
84
- templates = Jinja2Templates(directory=os.path.join(BASE_DIR, "templates"))
85
-
86
- class QuestionRequest(BaseModel):
87
- query: str
88
-
89
- def clean_text_output(text: str) -> str:
90
- """
91
- Tüm prompt, komut, yönerge, link ve gereksiz açıklamaları temizler.
92
- Sadece net, kısa yanıtı bırakır.
93
- """
94
- # Modelin başındaki yönerge/talimat cümleleri
95
- text = re.sub(
96
- r"^(Sadece doğru, kısa ve açık bilgi ver\.? Ekstra açıklama veya kaynak ekleme\.?)",
97
- "", text, flags=re.IGNORECASE
98
- )
99
- # Büyük prompt ve yönergeleri sil (Metin:, output:, Cevap:)
100
- text = re.sub(r"^.*?(Metin:|output:|Cevap:)", "", text, flags=re.IGNORECASE | re.DOTALL)
101
- # Tek satırlık açıklama veya yönerge kalanlarını sil
102
- text = re.sub(r"^(Aşağıdaki haber.*|Yalnızca olay özeti.*|Cevapta sadece.*|Metin:|output:|Cevap:)", "", text, flags=re.IGNORECASE | re.MULTILINE)
103
- # 'Detaylı bilgi için', 'Daha fazla bilgi için', 'Wikipedia', 'Kaynak:', linkler vs.
104
- text = re.sub(r"(Detaylı bilgi için.*|Daha fazla bilgi için.*|Wikipedia.*|Kaynak:.*|https?://\S+)", "", text, flags=re.IGNORECASE)
105
- # Madde işaretleri ve baştaki sayı/karakterler
106
- text = re.sub(r"^\- ", "", text, flags=re.MULTILINE)
107
- text = re.sub(r"^\d+[\.\)]?\s+", "", text, flags=re.MULTILINE)
108
- ## Model promptlarının başında kalan talimat cümlelerini sil
109
- text = re.sub(
110
- r"^(Sadece doğru, kısa ve açık bilgi ver\.? Ekstra açıklama veya kaynak ekleme\.?)",
111
- "", text, flags=re.IGNORECASE
112
- )
113
- # Tekrarlı boşluklar ve baş/son boşluk
114
- text = re.sub(r"\s+", " ", text).strip()
115
- return text
116
-
117
- @lru_cache(maxsize=config["cache"]["maxsize"])
118
- def get_embedding(text: str, max_length: int = 512) -> np.ndarray:
119
- formatted = f"query: {text.strip()}"[:max_length]
120
- return embedder.encode(formatted, normalize_embeddings=True)
121
-
122
- @lru_cache(maxsize=32)
123
- def pinecone_query_cached(query: str, top_k: int) -> tuple:
124
- query_embedding = get_embedding(query)
125
- result = index.query(vector=query_embedding.tolist(), top_k=top_k, include_metadata=True)
126
- matches = result.get("matches", [])
127
- output = []
128
- for m in matches:
129
- text = m.get("metadata", {}).get("text", "").strip()
130
- url = m.get("metadata", {}).get("url", "")
131
- if text:
132
- output.append((text, url))
133
- return tuple(output)
134
-
135
- async def retrieve_sources_from_pinecone(query: str, top_k: int = None) -> Dict[str, any]:
136
- top_k = top_k or config["pinecone"]["top_k"]
137
- output = pinecone_query_cached(query, top_k)
138
- if not output:
139
- return {"sources": "", "results": [], "source_url": ""}
140
- # Cross-encoder ile yeniden sıralama
141
- sentence_pairs = [[query, text] for text, url in output]
142
- scores = await asyncio.to_thread(cross_encoder.predict, sentence_pairs)
143
- reranked = [(float(score), text, url) for score, (text, url) in zip(scores, output)]
144
- reranked.sort(key=lambda x: x[0], reverse=True)
145
- top_results = reranked[:1]
146
- top_texts = [text for _, text, _ in top_results]
147
- source_url = top_results[0][2] if top_results else ""
148
- return {"sources": "\n".join(top_texts), "results": top_results, "source_url": source_url}
149
-
150
- async def generate_model_response(question: str) -> str:
151
- prompt = (
152
- f"input: {question}\noutput:"
153
- "Sadece doğru, kısa ve açık bilgi ver. Ekstra açıklama veya kaynak ekleme."
154
- )
155
- inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=256).to(device)
156
- with torch.no_grad():
157
- outputs = model.generate(
158
- **inputs,
159
- max_new_tokens=64,
160
- do_sample=False,
161
- num_beams=5,
162
- no_repeat_ngram_size=3,
163
- early_stopping=True,
164
- pad_token_id=tokenizer.pad_token_id,
165
- eos_token_id=tokenizer.eos_token_id
166
- )
167
- answer = tokenizer.decode(outputs[0], skip_special_tokens=True)
168
- return answer
169
-
170
- def extract_self_answer(output: str) -> str:
171
- # Eğer "output:" etiketi varsa, sonrasını al
172
- match = re.search(r"output:(.*)", output, flags=re.IGNORECASE | re.DOTALL)
173
- if match:
174
- return match.group(1).strip()
175
- # Eğer "Cevap:" varsa, sonrasını al
176
- if "Cevap:" in output:
177
- return output.split("Cevap:")[-1].strip()
178
- return output.strip()
179
-
180
- async def selfrag_agent(question: str):
181
- # 1. VDB cevabı ve kaynak url
182
- result = await retrieve_sources_from_pinecone(question)
183
- vdb_paragraph = result.get("sources", "").strip()
184
- source_url = result.get("source_url", "")
185
-
186
- # 2. Model cevabı
187
- model_paragraph = await generate_model_response(question)
188
- model_paragraph = extract_self_answer(model_paragraph)
189
-
190
- # 3. Temizle (SADECE METİN DEĞERLERİNDE!)
191
- vdb_paragraph = clean_text_output(vdb_paragraph)
192
- model_paragraph = clean_text_output(model_paragraph)
193
-
194
- # 4. Cross-encoder ile skorlama
195
- candidates = []
196
- candidate_urls = []
197
- label_names = []
198
- if vdb_paragraph:
199
- candidates.append(vdb_paragraph)
200
- candidate_urls.append(source_url)
201
- label_names.append("VDB")
202
- if model_paragraph:
203
- candidates.append(model_paragraph)
204
- candidate_urls.append(None)
205
- label_names.append("MODEL")
206
-
207
- if not candidates:
208
- return {"answer": "Cevap bulunamadı.", "source_url": None}
209
-
210
- sentence_pairs = [[question, cand] for cand in candidates]
211
- scores = await asyncio.to_thread(cross_encoder.predict, sentence_pairs)
212
- print(f"VDB Skor: {scores[0]:.4f}")
213
- if len(scores) > 1:
214
- print(f"Model Skor: {scores[1]:.4f}")
215
-
216
- # === Seçim Kuralları ===
217
- if len(scores) == 2:
218
- vdb_score = scores[0]
219
- model_score = scores[1]
220
- # Eğer modelin skoru, VDB'nin 2 katından fazlaysa modeli döndür
221
- if model_score > 1.5 * vdb_score:
222
- best_idx = 1
223
- else:
224
- best_idx = 0
225
- else:
226
- # Sadece VDB veya model varsa, en yüksek skoru seç
227
- best_idx = int(np.argmax(scores))
228
-
229
- final_answer = candidates[best_idx]
230
- final_source_url = candidate_urls[best_idx]
231
-
232
- return {
233
- "answer": final_answer,
234
- "source_url": final_source_url
235
- }
236
-
237
-
238
- @app.get("/")
239
- async def home(request: Request):
240
- return templates.TemplateResponse("index.html", {"request": request})
241
-
242
- @app.post("/api/ask")
243
- async def ask_question(request: QuestionRequest):
244
- try:
245
- question = request.query.strip()
246
- if not question:
247
- return JSONResponse(status_code=400, content={"error": "Sorgu boş olamaz."})
248
- result = await selfrag_agent(question)
249
- response_text = result["answer"]
250
- if result["source_url"]:
251
- response_text += f'<br><br>Daha fazla bilgi için: <a href="{result["source_url"]}" target="_blank">{result["source_url"]}</a>'
252
- return JSONResponse(content={"answer": response_text})
253
- except Exception as e:
254
- logger.error(f"API hatası: {e}")
255
- return JSONResponse(status_code=500, content={"error": f"Sunucu hatası: {str(e)}"})
 
 
 
 
 
 
1
+ import os
2
+ import asyncio
3
+ import logging
4
+ import re
5
+ import yaml
6
+ import torch
7
+ import numpy as np
8
+ from functools import lru_cache
9
+ from fastapi import FastAPI, Request
10
+ from fastapi.responses import JSONResponse
11
+ from fastapi.staticfiles import StaticFiles
12
+ from fastapi.templating import Jinja2Templates
13
+ from pydantic import BaseModel
14
+ from transformers import AutoTokenizer, AutoModelForCausalLM
15
+ from sentence_transformers import SentenceTransformer, CrossEncoder
16
+ from pinecone import Pinecone
17
+ from pathlib import Path
18
+ from dotenv import load_dotenv
19
+ from typing import Dict
20
+
21
+ os.environ["HF_HOME"] = "/data"
22
+ os.environ["HF_DATASETS_CACHE"] = "/data/datasets"
23
+ os.environ["HF_METRICS_CACHE"] = "/data/metrics"
24
+ os.environ["TRANSFORMERS_CACHE"] = "/data/transformers"
25
+ os.environ["HF_HUB_CACHE"] = "/data/hub"
26
+ # === LOGGING ===
27
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
28
+ logger = logging.getLogger(__name__)
29
+
30
+ # === CONFIG LOAD ===
31
+ CONFIG_PATH = Path(__file__).resolve().parent / "config.yaml"
32
+ def load_config() -> Dict:
33
+ try:
34
+ with open(CONFIG_PATH, 'r', encoding='utf-8') as f:
35
+ return yaml.safe_load(f)
36
+ except Exception as e:
37
+ logger.error(f"Konfigürasyon dosyası yüklenemedi: {e}")
38
+ return {
39
+ "pinecone": {"top_k": 10, "rerank_top": 5, "batch_size": 32},
40
+ "model": {"max_new_tokens": 50, "temperature": 0.7},
41
+ "cache": {"maxsize": 100}
42
+ }
43
+ config = load_config()
44
+
45
+ # === ENV LOAD ===
46
+ env_path = Path(__file__).resolve().parent.parent / "RAG" / ".env"
47
+ load_dotenv(dotenv_path=env_path)
48
+
49
+ PINECONE_API_KEY = os.getenv("PINECONE_API_KEY")
50
+ PINECONE_ENV = os.getenv("PINECONE_ENVIRONMENT")
51
+ PINECONE_INDEX_NAME = os.getenv("PINECONE_INDEX_NAME")
52
+ if not all([PINECONE_API_KEY, PINECONE_ENV, PINECONE_INDEX_NAME]):
53
+ raise ValueError("Pinecone ortam değişkenleri eksik!")
54
+
55
+ # === PINECONE CONNECT ===
56
+ pinecone_client = Pinecone(api_key=PINECONE_API_KEY, environment=PINECONE_ENV)
57
+ try:
58
+ index = pinecone_client.Index(PINECONE_INDEX_NAME)
59
+ index_stats = index.describe_index_stats()
60
+ logger.info(f"Pinecone index stats: {index_stats}")
61
+ except Exception as e:
62
+ logger.error(f"Pinecone bağlantı hatası: {e}")
63
+ raise
64
+
65
+ # === MODEL LOAD ===
66
+ MODEL_PATH = "iamseyhmus7/GenerationTurkishGPT2_final"
67
+ try:
68
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH)
69
+ model = AutoModelForCausalLM.from_pretrained(MODEL_PATH)
70
+ tokenizer.pad_token = tokenizer.eos_token
71
+ model.config.pad_token_id = tokenizer.pad_token_id
72
+ model.eval()
73
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
74
+ model.to(device)
75
+ logger.info(f"Model {MODEL_PATH} Hugging Face Hub'dan yüklendi, cihaz: {device}")
76
+ except Exception as e:
77
+ logger.error(f"Model yükleme hatası: {e}")
78
+ raise
79
+
80
+ # === EMBEDDING MODELS ===
81
+ embedder = SentenceTransformer("intfloat/multilingual-e5-large", device="cpu")
82
+ cross_encoder = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2", device="cpu")
83
+ logger.info("Embedding ve reranking modelleri yüklendi")
84
+
85
+ # === FASTAPI ===
86
+ app = FastAPI()
87
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
88
+ app.mount("/static", StaticFiles(directory=os.path.join(BASE_DIR, "static")), name="static")
89
+ templates = Jinja2Templates(directory=os.path.join(BASE_DIR, "templates"))
90
+
91
+ class QuestionRequest(BaseModel):
92
+ query: str
93
+
94
+ def clean_text_output(text: str) -> str:
95
+ """
96
+ Tüm prompt, komut, yönerge, link ve gereksiz açıklamaları temizler.
97
+ Sadece net, kısa yanıtı bırakır.
98
+ """
99
+ # Modelin başındaki yönerge/talimat cümleleri
100
+ text = re.sub(
101
+ r"^(Sadece doğru, kısa ve açık bilgi ver\.? Ekstra açıklama veya kaynak ekleme\.?)",
102
+ "", text, flags=re.IGNORECASE
103
+ )
104
+ # Büyük prompt ve yönergeleri sil (Metin:, output:, Cevap:)
105
+ text = re.sub(r"^.*?(Metin:|output:|Cevap:)", "", text, flags=re.IGNORECASE | re.DOTALL)
106
+ # Tek satırlık açıklama veya yönerge kalanlarını sil
107
+ text = re.sub(r"^(Aşağıdaki haber.*|Yalnızca olay özeti.*|Cevapta sadece.*|Metin:|output:|Cevap:)", "", text, flags=re.IGNORECASE | re.MULTILINE)
108
+ # 'Detaylı bilgi için', 'Daha fazla bilgi için', 'Wikipedia', 'Kaynak:', linkler vs.
109
+ text = re.sub(r"(Detaylı bilgi için.*|Daha fazla bilgi için.*|Wikipedia.*|Kaynak:.*|https?://\S+)", "", text, flags=re.IGNORECASE)
110
+ # Madde işaretleri ve baştaki sayı/karakterler
111
+ text = re.sub(r"^\- ", "", text, flags=re.MULTILINE)
112
+ text = re.sub(r"^\d+[\.\)]?\s+", "", text, flags=re.MULTILINE)
113
+ ## Model promptlarının başında kalan talimat cümlelerini sil
114
+ text = re.sub(
115
+ r"^(Sadece doğru, kısa ve açık bilgi ver\.? Ekstra açıklama veya kaynak ekleme\.?)",
116
+ "", text, flags=re.IGNORECASE
117
+ )
118
+ # Tekrarlı boşluklar ve baş/son boşluk
119
+ text = re.sub(r"\s+", " ", text).strip()
120
+ return text
121
+
122
+ @lru_cache(maxsize=config["cache"]["maxsize"])
123
+ def get_embedding(text: str, max_length: int = 512) -> np.ndarray:
124
+ formatted = f"query: {text.strip()}"[:max_length]
125
+ return embedder.encode(formatted, normalize_embeddings=True)
126
+
127
+ @lru_cache(maxsize=32)
128
+ def pinecone_query_cached(query: str, top_k: int) -> tuple:
129
+ query_embedding = get_embedding(query)
130
+ result = index.query(vector=query_embedding.tolist(), top_k=top_k, include_metadata=True)
131
+ matches = result.get("matches", [])
132
+ output = []
133
+ for m in matches:
134
+ text = m.get("metadata", {}).get("text", "").strip()
135
+ url = m.get("metadata", {}).get("url", "")
136
+ if text:
137
+ output.append((text, url))
138
+ return tuple(output)
139
+
140
+ async def retrieve_sources_from_pinecone(query: str, top_k: int = None) -> Dict[str, any]:
141
+ top_k = top_k or config["pinecone"]["top_k"]
142
+ output = pinecone_query_cached(query, top_k)
143
+ if not output:
144
+ return {"sources": "", "results": [], "source_url": ""}
145
+ # Cross-encoder ile yeniden sıralama
146
+ sentence_pairs = [[query, text] for text, url in output]
147
+ scores = await asyncio.to_thread(cross_encoder.predict, sentence_pairs)
148
+ reranked = [(float(score), text, url) for score, (text, url) in zip(scores, output)]
149
+ reranked.sort(key=lambda x: x[0], reverse=True)
150
+ top_results = reranked[:1]
151
+ top_texts = [text for _, text, _ in top_results]
152
+ source_url = top_results[0][2] if top_results else ""
153
+ return {"sources": "\n".join(top_texts), "results": top_results, "source_url": source_url}
154
+
155
+ async def generate_model_response(question: str) -> str:
156
+ prompt = (
157
+ f"input: {question}\noutput:"
158
+ "Sadece doğru, kısa ve açık bilgi ver. Ekstra açıklama veya kaynak ekleme."
159
+ )
160
+ inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=256).to(device)
161
+ with torch.no_grad():
162
+ outputs = model.generate(
163
+ **inputs,
164
+ max_new_tokens=64,
165
+ do_sample=False,
166
+ num_beams=5,
167
+ no_repeat_ngram_size=3,
168
+ early_stopping=True,
169
+ pad_token_id=tokenizer.pad_token_id,
170
+ eos_token_id=tokenizer.eos_token_id
171
+ )
172
+ answer = tokenizer.decode(outputs[0], skip_special_tokens=True)
173
+ return answer
174
+
175
+ def extract_self_answer(output: str) -> str:
176
+ # Eğer "output:" etiketi varsa, sonrasını al
177
+ match = re.search(r"output:(.*)", output, flags=re.IGNORECASE | re.DOTALL)
178
+ if match:
179
+ return match.group(1).strip()
180
+ # Eğer "Cevap:" varsa, sonrasını al
181
+ if "Cevap:" in output:
182
+ return output.split("Cevap:")[-1].strip()
183
+ return output.strip()
184
+
185
+ async def selfrag_agent(question: str):
186
+ # 1. VDB cevabı ve kaynak url
187
+ result = await retrieve_sources_from_pinecone(question)
188
+ vdb_paragraph = result.get("sources", "").strip()
189
+ source_url = result.get("source_url", "")
190
+
191
+ # 2. Model cevabı
192
+ model_paragraph = await generate_model_response(question)
193
+ model_paragraph = extract_self_answer(model_paragraph)
194
+
195
+ # 3. Temizle (SADECE METİN DEĞERLERİNDE!)
196
+ vdb_paragraph = clean_text_output(vdb_paragraph)
197
+ model_paragraph = clean_text_output(model_paragraph)
198
+
199
+ # 4. Cross-encoder ile skorlama
200
+ candidates = []
201
+ candidate_urls = []
202
+ label_names = []
203
+ if vdb_paragraph:
204
+ candidates.append(vdb_paragraph)
205
+ candidate_urls.append(source_url)
206
+ label_names.append("VDB")
207
+ if model_paragraph:
208
+ candidates.append(model_paragraph)
209
+ candidate_urls.append(None)
210
+ label_names.append("MODEL")
211
+
212
+ if not candidates:
213
+ return {"answer": "Cevap bulunamadı.", "source_url": None}
214
+
215
+ sentence_pairs = [[question, cand] for cand in candidates]
216
+ scores = await asyncio.to_thread(cross_encoder.predict, sentence_pairs)
217
+ print(f"VDB Skor: {scores[0]:.4f}")
218
+ if len(scores) > 1:
219
+ print(f"Model Skor: {scores[1]:.4f}")
220
+
221
+ # === Seçim Kuralları ===
222
+ if len(scores) == 2:
223
+ vdb_score = scores[0]
224
+ model_score = scores[1]
225
+ # Eğer modelin skoru, VDB'nin 2 katından fazlaysa modeli döndür
226
+ if model_score > 1.5 * vdb_score:
227
+ best_idx = 1
228
+ else:
229
+ best_idx = 0
230
+ else:
231
+ # Sadece VDB veya model varsa, en yüksek skoru seç
232
+ best_idx = int(np.argmax(scores))
233
+
234
+ final_answer = candidates[best_idx]
235
+ final_source_url = candidate_urls[best_idx]
236
+
237
+ return {
238
+ "answer": final_answer,
239
+ "source_url": final_source_url
240
+ }
241
+
242
+
243
+ @app.get("/")
244
+ async def home(request: Request):
245
+ return templates.TemplateResponse("index.html", {"request": request})
246
+
247
+ @app.post("/api/ask")
248
+ async def ask_question(request: QuestionRequest):
249
+ try:
250
+ question = request.query.strip()
251
+ if not question:
252
+ return JSONResponse(status_code=400, content={"error": "Sorgu boş olamaz."})
253
+ result = await selfrag_agent(question)
254
+ response_text = result["answer"]
255
+ if result["source_url"]:
256
+ response_text += f'<br><br>Daha fazla bilgi için: <a href="{result["source_url"]}" target="_blank">{result["source_url"]}</a>'
257
+ return JSONResponse(content={"answer": response_text})
258
+ except Exception as e:
259
+ logger.error(f"API hatası: {e}")
260
+ return JSONResponse(status_code=500, content={"error": f"Sunucu hatası: {str(e)}"})