Király Zoltán commited on
Commit
b5d1360
·
1 Parent(s): 99d84f7
.env ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ ES_CLOUD_ID="a520864218294af499ed5aaf6e6e6cdd:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvOjQ0MyQ4NjBjMzk2NTM2MDI0YWRhOTFhZmVhM2U5ZWFhNjM0ZSQ0NWRkMmE4OWMzOTc0ZDk5YjkwZmYwZmU4NzVhYzRkZg=="
2
+ ES_API_KEY="ZlpRWTFwZ0JONEp3Zzd4dy1FMnA6LU5zSkxIUlpqRjhYWk5WMm5IY2lQQQ=="
3
+ TOGETHER_API_KEY="1cc34f3c51e78ab60ff3dd226cb5421cce92191da6c985c868ed5f56ff7eb987"
__pycache__/backendv1.cpython-313.pyc ADDED
Binary file (26.6 kB). View file
 
backendv1.py CHANGED
@@ -1,11 +1,12 @@
1
  # backendv1.py
2
  # VÉGLEGES, JAVÍTOTT VERZIÓ: Elastic Cloud és GitHub Secrets kompatibilis.
3
  # A RAG rendszer motorja: adatfeldolgozás, keresés, generálás és tanulás.
 
4
 
5
  import os
6
  import time
7
  import datetime
8
- import json
9
  import re
10
  from collections import defaultdict
11
  from elasticsearch import Elasticsearch, exceptions as es_exceptions
@@ -35,7 +36,7 @@ CYAN = '\033[96m'
35
  MAGENTA = '\033[95m'
36
 
37
  # --- Konfiguráció ---
38
- # JAVÍTVA: A hitelesítő adatok már nincsenek itt, a program a környezeti változókból olvassa őket.
39
  CONFIG = {
40
  "VECTOR_INDEX_NAMES": ["duna", "dunawebindexai"],
41
  "FEEDBACK_INDEX_NAME": "feedback_index",
@@ -122,8 +123,14 @@ def run_separate_searches(es_client, query_text, embedding_model, expanded_queri
122
  source_fields = ["text_content", "source_url", "summary", "category"]
123
  filters = []
124
 
125
- if query_category and query_category != 'egyéb':
126
- filters.append({"match": {"category": query_category}})
 
 
 
 
 
 
127
 
128
  def knn_search(index, query_vector):
129
  try:
@@ -347,7 +354,7 @@ def process_query(user_question, chat_history, backend, confidence_threshold, fa
347
  return {"answer": retrieved_context, "sources": [], "corrected_question": corrected_question, "confidence_score": confidence_score}
348
 
349
  system_prompt = f"""Te egy professzionális, segítőkész AI asszisztens vagy.
350
- A feladatod, hogy a KONTEXTUS-ból és a FEJLESZTŐI UTASÍTÁSOKBÓL származó információkat egyetlen, jól strukturált és ismétlés-mentes válasszá szintetizálld.
351
  {feedback_instructions}
352
  KRITIKUS SZABÁLY: Értékeld a kapott KONTEXTUS relevanciáját a felhasználó kérdéséhez képest. Ha egy kontextus-részlet nem kapcsolódik szorosan a kérdéshez, azt hagyd figyelmen kívül!
353
  FIGYELEM: Szigorúan csak a megadott KONTEXTUS-ra és a fejlesztői utasításokra támaszkodj. Ha a releváns információk alapján nem tudsz válaszolni, add ezt a választ: '{fallback_message}'
@@ -361,5 +368,4 @@ KONTEXTUS:
361
 
362
  answer = generate_answer_with_history(backend["llm_client"], CONFIG["TOGETHER_MODEL_NAME"], messages_for_llm, CONFIG["GENERATION_TEMPERATURE"])
363
 
364
- return {"answer": answer, "sources": sources, "corrected_question": corrected_question, "confidence_score": confidence_score}
365
-
 
1
  # backendv1.py
2
  # VÉGLEGES, JAVÍTOTT VERZIÓ: Elastic Cloud és GitHub Secrets kompatibilis.
3
  # A RAG rendszer motorja: adatfeldolgozás, keresés, generálás és tanulás.
4
+ # JAVÍTVA: A kategória-alapú szűrés ideiglenesen kikapcsolva a megbízhatóbb eredmények érdekében.
5
 
6
  import os
7
  import time
8
  import datetime
9
+ import traceback
10
  import re
11
  from collections import defaultdict
12
  from elasticsearch import Elasticsearch, exceptions as es_exceptions
 
36
  MAGENTA = '\033[95m'
37
 
38
  # --- Konfiguráció ---
39
+ # A hitelesítő adatok a környezeti változókból kerülnek beolvasásra.
40
  CONFIG = {
41
  "VECTOR_INDEX_NAMES": ["duna", "dunawebindexai"],
42
  "FEEDBACK_INDEX_NAME": "feedback_index",
 
123
  source_fields = ["text_content", "source_url", "summary", "category"]
124
  filters = []
125
 
126
+ ### JAVÍTÁS ###
127
+ # A kategória-alapú szűrés ideiglenesen ki van kapcsolva, mert pontatlan
128
+ # kategorizálás esetén drasztikusan rontja a találatok minőségét.
129
+ # A keresés így a teljes adatbázisban fut, ami megbízhatóbb.
130
+ #
131
+ # if query_category and query_category != 'egyéb':
132
+ # print(f" {MAGENTA}-> Kategória-alapú szűrés hozzáadása a kereséshez: '{query_category}'{RESET}")
133
+ # filters.append({"match": {"category": query_category}})
134
 
135
  def knn_search(index, query_vector):
136
  try:
 
354
  return {"answer": retrieved_context, "sources": [], "corrected_question": corrected_question, "confidence_score": confidence_score}
355
 
356
  system_prompt = f"""Te egy professzionális, segítőkész AI asszisztens vagy.
357
+ A feladatod, hogy a KONTEXTUS-ból és a FEJLESZTŐI UTASÍTÁSOKBól származó információkat egyetlen, jól strukturált és ismétlés-mentes válasszá szintetizálld.
358
  {feedback_instructions}
359
  KRITIKUS SZABÁLY: Értékeld a kapott KONTEXTUS relevanciáját a felhasználó kérdéséhez képest. Ha egy kontextus-részlet nem kapcsolódik szorosan a kérdéshez, azt hagyd figyelmen kívül!
360
  FIGYELEM: Szigorúan csak a megadott KONTEXTUS-ra és a fejlesztői utasításokra támaszkodj. Ha a releváns információk alapján nem tudsz válaszolni, add ezt a választ: '{fallback_message}'
 
368
 
369
  answer = generate_answer_with_history(backend["llm_client"], CONFIG["TOGETHER_MODEL_NAME"], messages_for_llm, CONFIG["GENERATION_TEMPERATURE"])
370
 
371
+ return {"answer": answer, "sources": sources, "corrected_question": corrected_question, "confidence_score": confidence_score}
 
web_indexer_universal_v7.py CHANGED
@@ -1,5 +1,6 @@
1
  # web_indexer_universal_v7.py
2
- # VÉGLEGES VERZIÓ 2.0: Szinonimák nélkül, dinamikus AI kategorizálással.
 
3
 
4
  import os
5
  import time
@@ -8,35 +9,74 @@ import requests
8
  from bs4 import BeautifulSoup
9
  from urllib.parse import urljoin, urlparse
10
  from collections import deque
11
- from elasticsearch import Elasticsearch, helpers
12
  import sys
 
 
13
 
14
- # === ANSI Színkódok ===
15
  GREEN = '\033[92m'
16
  YELLOW = '\033[93m'
17
  RED = '\033[91m'
18
  RESET = '\033[0m'
 
19
  CYAN = '\033[96m'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
- # --- Könyvtárak importálása és ellenőrzése ---
22
  try:
23
  import torch
24
  TORCH_AVAILABLE = True
25
  except ImportError:
26
  TORCH_AVAILABLE = False
 
27
 
28
  try:
29
  import together
30
- from dotenv import load_dotenv
31
- load_dotenv()
32
- together_api_key = os.getenv("TOGETHER_API_KEY")
33
- if not together_api_key:
34
- print(f"{YELLOW}Figyelem: TOGETHER_API_KEY nincs beállítva, LLM funkciók nem működnek.{RESET}")
35
- together_client = None
36
  else:
37
- together_client = together.Together(api_key=together_api_key)
38
  print(f"{GREEN}Together AI kliens inicializálva.{RESET}")
39
  except ImportError:
 
 
 
 
40
  together_client = None
41
 
42
  try:
@@ -45,47 +85,118 @@ try:
45
  TIKTOKEN_AVAILABLE = True
46
  except ImportError:
47
  TIKTOKEN_AVAILABLE = False
 
48
 
49
  try:
50
  import nltk
51
  try:
52
  nltk.data.find('tokenizers/punkt')
53
  except LookupError:
54
- print(f"{CYAN}NLTK 'punkt' letöltése...{RESET}")
55
  nltk.download('punkt', quiet=True)
56
  NLTK_AVAILABLE = True
57
  except ImportError:
58
  NLTK_AVAILABLE = False
 
59
 
60
  try:
61
  from sentence_transformers import SentenceTransformer
62
  SENTENCE_TRANSFORMER_AVAILABLE = True
63
  except ImportError:
64
  SENTENCE_TRANSFORMER_AVAILABLE = False
 
65
 
66
- # --- Konfiguráció ---
67
- ES_CLOUD_ID = os.getenv("ES_CLOUD_ID")
68
- ES_API_KEY = os.getenv("ES_API_KEY")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
- START_URL = "https://www.dunaelektronika.com/"
71
- TARGET_DOMAIN = "dunaelektronika.com"
72
- MAX_DEPTH = 2
73
- REQUEST_DELAY = 1
74
- USER_AGENT = "MyPythonCrawler/1.0"
75
- VECTOR_INDEX_NAME = "dunawebindexai"
76
- BATCH_SIZE = 50
77
- ES_CLIENT_TIMEOUT = 120
78
- EMBEDDING_MODEL_NAME = 'sentence-transformers/paraphrase-multilingual-mpnet-base-v2'
79
- embedding_model = None
80
- EMBEDDING_DIM = 768 # Alapértelmezett, betöltés után frissítjük
81
- device = 'cpu'
82
- CHUNK_SIZE_TOKENS = 500
83
- CHUNK_OVERLAP_TOKENS = 50
84
- MIN_CHUNK_SIZE_CHARS = 50
85
- LLM_CHUNK_MODEL = "mistralai/Mixtral-8x7B-Instruct-v0.1"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
 
87
- # === Index Beállítások & Mapping (EGYSZERŰSÍTETT, SZINONIMÁK NÉLKÜL) ===
88
- INDEX_SETTINGS_SIMPLE = {
 
 
89
  "analysis": {
90
  "filter": {
91
  "hungarian_stop": {"type": "stop", "stopwords": "_hungarian_"},
@@ -99,15 +210,14 @@ INDEX_SETTINGS_SIMPLE = {
99
  }
100
  }
101
  }
102
-
103
- INDEX_MAPPINGS_SIMPLE = {
104
  "properties": {
105
  "text_content": {"type": "text", "analyzer": "hungarian_analyzer"},
106
  "embedding": {"type": "dense_vector", "dims": EMBEDDING_DIM, "index": True, "similarity": "cosine"},
107
  "source_origin": {"type": "keyword"},
108
  "source_url": {"type": "keyword"},
109
  "source_type": {"type": "keyword"},
110
- "category": {"type": "keyword"}, # A 'keyword' típus listákat is tud kezelni
111
  "heading": {"type": "text", "analyzer": "hungarian_analyzer"},
112
  "summary": {"type": "text", "analyzer": "hungarian_analyzer"}
113
  }
@@ -115,267 +225,177 @@ INDEX_MAPPINGS_SIMPLE = {
115
 
116
  # --- Segédfüggvények ---
117
  def initialize_es_client():
118
- print(f"\n{CYAN}Kapcsolódás az Elasticsearch-hez...{RESET}")
119
- if not ES_CLOUD_ID or not ES_API_KEY:
120
- print(f"{RED}Hiba: ES_CLOUD_ID vagy ES_API_KEY hiányzik a GitHub Secrets-ből!{RESET}")
121
  return None
122
  try:
 
123
  client = Elasticsearch(
124
- cloud_id=ES_CLOUD_ID,
125
- api_key=ES_API_KEY,
126
- request_timeout=ES_CLIENT_TIMEOUT
127
  )
128
- if not client.ping(): raise ConnectionError("Ping sikertelen.")
129
- print(f"{GREEN}Sikeres Elasticsearch kapcsolat!{RESET}")
130
- return client
131
  except Exception as e:
132
- print(f"{RED}Hiba az Elasticsearch kapcsolódás során: {e}{RESET}")
133
- return None
134
-
135
- def load_embedding_model():
136
- global embedding_model, EMBEDDING_DIM, device
137
- if not (TORCH_AVAILABLE and SENTENCE_TRANSFORMER_AVAILABLE):
138
- print(f"{RED}PyTorch vagy SentenceTransformer nincs telepítve. Embedding nem működik.{RESET}")
139
- return
140
-
141
- print(f"\n{CYAN}'{EMBEDDING_MODEL_NAME}' embedding modell betöltése...{RESET}")
142
- try:
143
- device = 'cuda' if torch.cuda.is_available() else 'cpu'
144
- model = SentenceTransformer(EMBEDDING_MODEL_NAME, device=device)
145
- embedding_model = model
146
- EMBEDDING_DIM = model.get_sentence_embedding_dimension()
147
- INDEX_MAPPINGS_SIMPLE["properties"]["embedding"]["dims"] = EMBEDDING_DIM
148
- print(f"{GREEN}Embedding modell betöltve (dim: {EMBEDDING_DIM}, eszköz: {device}).{RESET}")
149
- except Exception as e:
150
- print(f"{RED}Hiba az embedding modell betöltésekor: {e}{RESET}")
151
- embedding_model = None
152
-
153
- def generate_dynamic_categories_with_llm(llm_client, soup, text):
154
- if not llm_client: return ["általános"]
155
-
156
- h1_text = ""
157
- try:
158
- h1_tag = soup.find('h1')
159
- if h1_tag:
160
- h1_text = h1_tag.get_text(strip=True)
161
- except Exception:
162
- pass
163
-
164
- try:
165
- prompt = f"""Elemezd a következő magyar nyelvű weboldal tartalmát, és adj meg 1-3 rövid, releváns kategóriát vagy címkét, ami a legjobban leírja azt. A kategóriákat vesszővel válaszd el. A válaszodban csak a kategóriák szerepeljenek, más magyarázat nélkül.
166
- Weboldal címe: "{h1_text}"
167
- Szöveg eleje: {text[:1500]}
168
- Kategóriák:"""
169
-
170
- response = llm_client.chat.completions.create(
171
- model=LLM_CHUNK_MODEL,
172
- messages=[{"role": "user", "content": prompt}],
173
- temperature=0.2,
174
- max_tokens=50
175
- )
176
-
177
- if response and response.choices:
178
- categories_str = response.choices[0].message.content.strip()
179
- # A válasz feldolgozása: vessző mentén darabolás, felesleges szóközök eltávolítása, kisbetűsítés
180
- categories = [cat.strip().lower() for cat in categories_str.split(',') if cat.strip()]
181
- print(f"{GREEN} -> Dinamikus kategóriák az AI alapján: {categories}{RESET}")
182
- return categories if categories else ["általános"]
183
- return ["általános"]
184
- except Exception as e:
185
- print(f"{RED}Hiba a dinamikus LLM kategorizáláskor: {e}{RESET}")
186
- return ["általános"]
187
-
188
- def generate_summary_with_llm(llm_client, text):
189
- if not llm_client: return text[:300] + "..."
190
- try:
191
- prompt = f"""Készíts egy rövid, de informatív összefoglalót a következő szövegről magyarul.
192
- Szöveg: {text[:4000]}
193
- Összefoglalás:"""
194
- response = llm_client.chat.completions.create(model=LLM_CHUNK_MODEL, messages=[{"role": "user", "content": prompt}], temperature=0.5, max_tokens=500)
195
- if response and response.choices:
196
- summary = response.choices[0].message.content.strip()
197
- print(f"{GREEN} -> Sikeres LLM összefoglalás generálás.{RESET}")
198
- return summary
199
- except Exception as e:
200
- print(f"{RED}Hiba LLM összefoglaláskor: {e}{RESET}")
201
- return text[:300] + "..."
202
-
203
- def chunk_text_by_tokens(text, chunk_size, chunk_overlap):
204
- if not TIKTOKEN_AVAILABLE:
205
- chunks, start = [], 0
206
- while start < len(text):
207
- end = start + (chunk_size * 4)
208
- chunks.append(text[start:end])
209
- start = end - (chunk_overlap * 4)
210
- return chunks
211
- tokens = tiktoken_encoder.encode(text)
212
- chunks, start = [], 0
213
- while start < len(tokens):
214
- end = start + chunk_size
215
- chunk_tokens = tokens[start:end]
216
- chunks.append(tiktoken_encoder.decode(chunk_tokens))
217
- start += chunk_size - chunk_overlap
218
- return chunks
219
 
220
  def get_embedding(text):
221
- if not embedding_model: return None
222
  try:
223
  return embedding_model.encode(text, normalize_embeddings=True).tolist()
224
  except Exception as e:
225
- print(f"{RED}Hiba embedding közben: {e}{RESET}")
226
- return None
227
 
228
  def create_es_index(client, index_name, index_settings, index_mappings):
229
- print(f"\n{CYAN}Index ellenőrzése: '{index_name}'...{RESET}")
 
 
 
 
 
 
 
230
  try:
231
  if not client.indices.exists(index=index_name):
232
  print(f"'{index_name}' index létrehozása...")
233
  client.indices.create(index=index_name, settings=index_settings, mappings=index_mappings)
234
  print(f"{GREEN}Index sikeresen létrehozva.{RESET}")
 
235
  else:
236
- print(f"Index '{index_name}' már létezik.")
237
  return True
238
  except Exception as e:
239
- print(f"{RED}!!! Hiba az index létrehozásakor: {e}{RESET}")
 
240
  return False
241
 
242
  def extract_text_from_html(html_content):
243
  try:
244
  soup = BeautifulSoup(html_content, 'html.parser')
245
  for element in soup(["script", "style", "nav", "footer", "header", "aside", "form"]):
246
- element.decompose()
247
- main_content = soup.find('main') or soup.find('article') or soup.body or soup
248
- text = main_content.get_text(separator='\n', strip=True)
249
- return "\n".join(line for line in text.splitlines() if line.strip())
250
  except Exception as e:
251
- print(f"{RED}Hiba a HTML tartalom kinyerésekor: {e}{RESET}")
252
  return ""
253
 
254
  def extract_and_filter_links(soup, base_url, target_domain):
255
  links = set()
256
- for a_tag in soup.find_all('a', href=True):
257
- href = a_tag['href'].strip()
258
- if href and not href.startswith(('#', 'mailto:', 'javascript:')):
259
- full_url = urljoin(base_url, href)
260
- parsed_url = urlparse(full_url)
261
- if parsed_url.scheme in ['http', 'https'] and parsed_url.netloc == target_domain:
262
- links.add(parsed_url._replace(fragment="").geturl())
 
 
 
263
  return links
264
 
265
  def crawl_and_index_website(start_url, max_depth, es_client, index_name):
 
266
  visited_urls, urls_to_visit = set(), deque([(start_url, 0)])
267
- bulk_actions, total_indexed = [], 0
 
268
  target_domain = urlparse(start_url).netloc
269
  print(f"Web crawling indítása: {start_url} (Max mélység: {max_depth}, Cél: {target_domain})")
270
-
271
  while urls_to_visit:
 
272
  try:
273
  current_url, current_depth = urls_to_visit.popleft()
274
- except IndexError:
275
- break # Nincs több URL a listában
276
-
277
- if current_url in visited_urls:
278
- continue
279
-
280
- print(f"\n--- Feldolgozás (Mélység: {current_depth}): {current_url} ---")
281
- visited_urls.add(current_url)
282
-
283
- try:
284
- headers = {'User-Agent': USER_AGENT}
285
- response = requests.get(current_url, headers=headers, timeout=15)
286
- response.raise_for_status()
287
- if 'text/html' not in response.headers.get('content-type', '').lower():
288
- print(f" {YELLOW}-> Nem HTML tartalom, kihagyva.{RESET}")
289
- continue
290
-
291
- html_content = response.content
292
  soup = BeautifulSoup(html_content, 'html.parser')
293
  page_text = extract_text_from_html(html_content)
294
-
295
- if not page_text or len(page_text) < MIN_CHUNK_SIZE_CHARS:
296
- print(f" {YELLOW}-> Nem sikerült szöveget kinyerni vagy túl rövid.{RESET}")
297
- continue
298
-
299
- final_chunks = chunk_text_by_tokens(page_text, CHUNK_SIZE_TOKENS, CHUNK_OVERLAP_TOKENS)
300
- categories = generate_dynamic_categories_with_llm(together_client, soup, page_text)
301
  page_summary = generate_summary_with_llm(together_client, page_text)
302
-
303
- print(f"{GREEN} Indexelésre előkészítve: {len(final_chunks)} darab (Kategóriák: {categories}){RESET}")
304
-
305
  for chunk_text in final_chunks:
306
  element_vector = get_embedding(chunk_text)
307
  if element_vector:
308
- doc = {
309
- "text_content": chunk_text, "embedding": element_vector, "source_origin": "website",
310
- "source_url": current_url, "source_type": "token_chunking",
311
- "category": categories, "summary": page_summary, "heading": soup.find('h1').get_text(strip=True) if soup.find('h1') else ''
312
- }
313
  bulk_actions.append({"_index": index_name, "_source": doc})
314
-
315
- if len(bulk_actions) >= BATCH_SIZE:
316
- print(f" -> {len(bulk_actions)} chunk indexelése (batch)...")
317
- success_count, _ = helpers.bulk(es_client, bulk_actions)
318
- total_indexed += success_count
319
- bulk_actions = []
320
-
321
  if current_depth < max_depth:
322
- new_links = extract_and_filter_links(soup, start_url, target_domain)
323
  for link in new_links:
324
- if link not in visited_urls:
325
- urls_to_visit.append((link, current_depth + 1))
326
-
327
- time.sleep(REQUEST_DELAY)
328
-
329
- except requests.exceptions.RequestException as req_err:
330
- print(f" {RED}!!! Hiba a letöltés során: {req_err}{RESET}")
331
- except Exception as e:
332
- print(f" {RED}!!! Váratlan hiba a ciklusban ({current_url}): {e}{RESET}")
333
-
334
  if bulk_actions:
335
- print(f" -> Maradék {len(bulk_actions)} chunk indexelése...")
336
- success_count, _ = helpers.bulk(es_client, bulk_actions)
337
  total_indexed += success_count
338
-
339
- print(f"\n--- Web Crawling és Indexelés Befejezve ---")
340
  print(f"Meglátogatott URL-ek: {len(visited_urls)}")
341
- print(f"Sikeresen indexelt chunkok: {total_indexed}")
 
342
  return total_indexed
343
 
344
- # ===Program ===
345
  if __name__ == "__main__":
346
- print("----- Web Crawler és Indexelő Indítása (Dinamikus AI Kategorizálással) -----")
347
-
348
- load_embedding_model()
 
 
 
 
 
 
 
349
 
350
- if not embedding_model:
351
- print(f"{RED}Hiba: Az embedding modell betöltése sikertelen. A program leáll.{RESET}")
352
- sys.exit(1)
353
-
354
  es_client = initialize_es_client()
355
-
356
- if es_client:
357
- try:
358
- if es_client.indices.exists(index=VECTOR_INDEX_NAME):
359
- print(f"{YELLOW}A '{VECTOR_INDEX_NAME}' index már létezik. Törlés...{RESET}")
360
- es_client.indices.delete(index=VECTOR_INDEX_NAME)
361
- print(f"{GREEN}Index sikeresen törölve.{RESET}")
362
-
363
- index_ready = create_es_index(
364
- client=es_client,
365
- index_name=VECTOR_INDEX_NAME,
366
- index_settings=INDEX_SETTINGS_SIMPLE,
367
- index_mappings=INDEX_MAPPINGS_SIMPLE
368
- )
369
-
370
- if index_ready:
371
- final_success_count = crawl_and_index_website(START_URL, MAX_DEPTH, es_client, VECTOR_INDEX_NAME)
372
- if final_success_count > 0:
373
- print(f"\n{GREEN}A folyamat sikeresen lefutott. {final_success_count} dokumentum indexelve.{RESET}")
374
- else:
375
- print(f"\n{YELLOW}A folyamat lefutott, de 0 új dokumentum került indexelésre.{RESET}")
376
- else:
377
- print(f"{RED}Hiba: Az index nem áll készen a használatra.{RESET}")
378
- except Exception as e:
379
- print(f"{RED}Hiba a programrészben: {e}{RESET}")
 
 
380
  else:
381
- print(f"{RED}Hiba: Az Elasticsearch kliens nem elérhető.{RESET}")
 
1
  # web_indexer_universal_v7.py
2
+ # EGYSZERŰSÍTETT VERZIÓ: A szinonima-kezelés teljesen eltávolítva.
3
+ # Támogatja az Elastic Cloud-ot, biztonságos konfigurációkezeléssel.
4
 
5
  import os
6
  import time
 
9
  from bs4 import BeautifulSoup
10
  from urllib.parse import urljoin, urlparse
11
  from collections import deque
12
+ from elasticsearch import Elasticsearch, helpers, exceptions as es_exceptions
13
  import sys
14
+ import warnings
15
+ from dotenv import load_dotenv
16
 
17
+ # === ANSI Színkódok (konzol loggoláshoz) ===
18
  GREEN = '\033[92m'
19
  YELLOW = '\033[93m'
20
  RED = '\033[91m'
21
  RESET = '\033[0m'
22
+ BLUE = '\033[94m'
23
  CYAN = '\033[96m'
24
+ MAGENTA = '\033[95m'
25
+
26
+
27
+ # --- Konfiguráció betöltése környezeti változókból ---
28
+ load_dotenv()
29
+
30
+ CONFIG = {
31
+ # --- Alap beállítások (felülírhatók .env fájlból) ---
32
+ "START_URL": os.getenv("START_URL", "https://www.dunaelektronika.com/"),
33
+ "MAX_DEPTH": int(os.getenv("MAX_DEPTH", 2)),
34
+ "REQUEST_DELAY": int(os.getenv("REQUEST_DELAY", 1)),
35
+ "USER_AGENT": os.getenv("USER_AGENT", "MyPythonCrawler/1.0 (+http://example.com/botinfo)"),
36
+ "VECTOR_INDEX_NAME": os.getenv("VECTOR_INDEX_NAME", "dunawebindexai"),
37
+ "BATCH_SIZE": int(os.getenv("BATCH_SIZE", 50)),
38
+ "ES_CLIENT_TIMEOUT": int(os.getenv("ES_CLIENT_TIMEOUT", 120)),
39
+ "EMBEDDING_MODEL_NAME": 'sentence-transformers/paraphrase-multilingual-mpnet-base-v2',
40
+ "CHUNK_SIZE_TOKENS": int(os.getenv("CHUNK_SIZE_TOKENS", 500)),
41
+ "CHUNK_OVERLAP_TOKENS": int(os.getenv("CHUNK_OVERLAP_TOKENS", 50)),
42
+ "MIN_CHUNK_SIZE_CHARS": int(os.getenv("MIN_CHUNK_SIZE_CHARS", 50)),
43
+ "LLM_MODEL_NAME": "meta-llama/Llama-3.3-70B-Instruct-Turbo-Free",
44
+ "LLM_CHUNK_MODEL": "mistralai/Mixtral-8x7B-Instruct-v0.1",
45
+ "DEBUG_MODE": os.getenv("DEBUG_MODE", "True").lower() == 'true',
46
+
47
+ # --- Kötelező, érzékeny adatok ---
48
+ "ES_CLOUD_ID": os.getenv("ES_CLOUD_ID"),
49
+ "ES_API_KEY": os.getenv("ES_API_KEY"),
50
+ "TOGETHER_API_KEY": os.getenv("TOGETHER_API_KEY")
51
+ }
52
+
53
+ CONFIG["TARGET_DOMAIN"] = urlparse(CONFIG["START_URL"]).netloc
54
+
55
+ embedding_model = None
56
+ EMBEDDING_DIM = None
57
+ device = 'cpu'
58
+ together_client = None
59
 
60
+ # --- LLM és egyéb könyvtárak ellenőrzése és importálása ---
61
  try:
62
  import torch
63
  TORCH_AVAILABLE = True
64
  except ImportError:
65
  TORCH_AVAILABLE = False
66
+ print(f"{RED}FIGYELEM: Torch nincs telepítve.{RESET}")
67
 
68
  try:
69
  import together
70
+ if not CONFIG["TOGETHER_API_KEY"]:
71
+ print(f"{RED}Hiba: TOGETHER_API_KEY nincs beállítva.{RESET}")
 
 
 
 
72
  else:
73
+ together_client = together.Together(api_key=CONFIG["TOGETHER_API_KEY"])
74
  print(f"{GREEN}Together AI kliens inicializálva.{RESET}")
75
  except ImportError:
76
+ print(f"{YELLOW}Figyelem: together könyvtár nincs telepítve.{RESET}")
77
+ together_client = None
78
+ except Exception as e:
79
+ print(f"{RED}Hiba LLM backend inicializálásakor: {e}{RESET}")
80
  together_client = None
81
 
82
  try:
 
85
  TIKTOKEN_AVAILABLE = True
86
  except ImportError:
87
  TIKTOKEN_AVAILABLE = False
88
+ print(f"{YELLOW}Figyelem: tiktoken nincs telepítve.{RESET}")
89
 
90
  try:
91
  import nltk
92
  try:
93
  nltk.data.find('tokenizers/punkt')
94
  except LookupError:
95
+ print(f"{CYAN}NLTK 'punkt' letöltése...{RESET}");
96
  nltk.download('punkt', quiet=True)
97
  NLTK_AVAILABLE = True
98
  except ImportError:
99
  NLTK_AVAILABLE = False
100
+ print(f"{RED}HIBA: 'nltk' nincs telepítve!{RESET}")
101
 
102
  try:
103
  from sentence_transformers import SentenceTransformer
104
  SENTENCE_TRANSFORMER_AVAILABLE = True
105
  except ImportError:
106
  SENTENCE_TRANSFORMER_AVAILABLE = False
107
+ print(f"{RED}HIBA: 'sentence-transformers' nincs telepítve!{RESET}")
108
 
109
+ try:
110
+ sys.stdout.reconfigure(encoding='utf-8')
111
+ sys.stderr.reconfigure(encoding='utf-8')
112
+ except AttributeError:
113
+ pass
114
+
115
+ # --- LLM HÁTTÉR FUNKCIÓK ---
116
+ def generate_categories_with_llm(llm_client, soup, text):
117
+ category_list = ['IT biztonsági szolgáltatások', 'szolgáltatások', 'hardver', 'szoftver', 'hírek', 'audiovizuális konferenciatechnika']
118
+ try:
119
+ breadcrumb = soup.find('nav', class_='breadcrumb')
120
+ if breadcrumb:
121
+ categories = [li.get_text(strip=True) for li in breadcrumb.find_all('li')]
122
+ if categories:
123
+ final_category_from_html = categories[-1]
124
+ for cat in category_list:
125
+ if cat.lower() in final_category_from_html.lower():
126
+ return [cat]
127
+ except Exception: pass
128
+ try:
129
+ h1_tag = soup.find('h1')
130
+ if h1_tag and h1_tag.get_text(strip=True):
131
+ h1_text = h1_tag.get_text(strip=True)
132
+ for cat in category_list:
133
+ if cat.lower() in h1_text.lower():
134
+ return [cat]
135
+ except Exception: pass
136
+ if not llm_client: return ['egyéb']
137
+ try:
138
+ categories_text = ", ".join([f"'{cat}'" for cat in category_list])
139
+ prompt = f"""Adott egy weboldal szövege. Adj meg egyetlen, rövid kategóriát a következő listából, ami a legjobban jellemzi a tartalmát. A válaszodban csak a kategória szerepeljen, más szöveg nélkül.
140
+ Lehetséges kategóriák: {categories_text}
141
+ Szöveg: {text[:1000]}
142
+ Kategória:"""
143
+ response = llm_client.chat.completions.create(model=CONFIG["LLM_CHUNK_MODEL"], messages=[{"role": "user", "content": prompt}], temperature=0.1, max_tokens=30)
144
+ if response and response.choices:
145
+ category = response.choices[0].message.content.strip().replace("'", "").replace("`", "")
146
+ for cat in category_list:
147
+ if cat.lower() in category.lower():
148
+ return [cat]
149
+ except Exception as e:
150
+ print(f"{RED}Hiba LLM kategorizáláskor: {e}{RESET}")
151
+ return ['egyéb']
152
 
153
+ def generate_summary_with_llm(llm_client, text):
154
+ if not llm_client: return text[:300] + "..."
155
+ try:
156
+ prompt = f"""Készíts egy rövid, de informatív összefoglalót a következő szövegről. A lényeges pontokat emeld ki, de ne lépd túl a 200 szó terjedelmet.
157
+ Szöveg: {text}
158
+ Összefoglalás:"""
159
+ response = llm_client.chat.completions.create(model=CONFIG["LLM_CHUNK_MODEL"], messages=[{"role": "user", "content": prompt}], temperature=0.5, max_tokens=500)
160
+ if response and response.choices:
161
+ return response.choices[0].message.content.strip()
162
+ except Exception as e:
163
+ print(f"{RED}Hiba LLM összefoglaláskor: {e}{RESET}")
164
+ return text[:300] + "..."
165
+
166
+ def chunk_text_by_tokens(text, chunk_size, chunk_overlap):
167
+ if not TIKTOKEN_AVAILABLE or not NLTK_AVAILABLE:
168
+ chunks = []; start = 0
169
+ while start < len(text):
170
+ end = start + chunk_size; chunks.append(text[start:end]); start += chunk_size - chunk_overlap
171
+ return chunks
172
+ tokens = tiktoken_encoder.encode(text); chunks = []; start = 0
173
+ while start < len(tokens):
174
+ end = start + chunk_size; chunk_tokens = tokens[start:end]; chunks.append(tiktoken_encoder.decode(chunk_tokens)); start += chunk_size - chunk_overlap
175
+ return chunks
176
+
177
+ # --- Modellek és Eszközök Inicializálása ---
178
+ def load_embedding_model():
179
+ global embedding_model, EMBEDDING_DIM, device
180
+ if not TORCH_AVAILABLE or not SENTENCE_TRANSFORMER_AVAILABLE: EMBEDDING_DIM = 768; device = 'cpu'; return None, EMBEDDING_DIM, device
181
+ if embedding_model and EMBEDDING_DIM: return embedding_model, EMBEDDING_DIM, device
182
+ print(f"\n'{CONFIG['EMBEDDING_MODEL_NAME']}' modell betöltése...")
183
+ try:
184
+ current_device = 'cuda' if torch.cuda.is_available() else 'cpu'
185
+ model = SentenceTransformer(CONFIG['EMBEDDING_MODEL_NAME'], device=current_device)
186
+ print(f"ST modell betöltve, eszköz: {model.device}")
187
+ dim = model.get_sentence_embedding_dimension()
188
+ if not dim: raise ValueError("Dim error")
189
+ embedding_model = model; EMBEDDING_DIM = dim; device = current_device
190
+ return embedding_model, EMBEDDING_DIM, device
191
+ except Exception as e:
192
+ print(f"{RED}Hiba embedding modell betöltésekor: {e}{RESET}"); traceback.print_exc()
193
+ embedding_model = None; EMBEDDING_DIM = 768; device = 'cpu'
194
+ return None, EMBEDDING_DIM, device
195
 
196
+ embedding_model, EMBEDDING_DIM, device = load_embedding_model()
197
+
198
+ # === Index Beállítások & Mapping (Szinonimák nélkül) ===
199
+ INDEX_SETTINGS = {
200
  "analysis": {
201
  "filter": {
202
  "hungarian_stop": {"type": "stop", "stopwords": "_hungarian_"},
 
210
  }
211
  }
212
  }
213
+ INDEX_MAPPINGS_WEB = {
 
214
  "properties": {
215
  "text_content": {"type": "text", "analyzer": "hungarian_analyzer"},
216
  "embedding": {"type": "dense_vector", "dims": EMBEDDING_DIM, "index": True, "similarity": "cosine"},
217
  "source_origin": {"type": "keyword"},
218
  "source_url": {"type": "keyword"},
219
  "source_type": {"type": "keyword"},
220
+ "category": {"type": "keyword"},
221
  "heading": {"type": "text", "analyzer": "hungarian_analyzer"},
222
  "summary": {"type": "text", "analyzer": "hungarian_analyzer"}
223
  }
 
225
 
226
  # --- Segédfüggvények ---
227
  def initialize_es_client():
228
+ if not CONFIG["ES_CLOUD_ID"] or not CONFIG["ES_API_KEY"]:
229
+ print(f"{RED}Hiba: Az ES_CLOUD_ID és ES_API_KEY környezeti változók beállítása kötelező!{RESET}")
 
230
  return None
231
  try:
232
+ if CONFIG["DEBUG_MODE"]: print("\nKapcsolódás az Elasticsearch-hez (Cloud ID)...")
233
  client = Elasticsearch(
234
+ cloud_id=CONFIG["ES_CLOUD_ID"],
235
+ api_key=CONFIG["ES_API_KEY"],
236
+ request_timeout=CONFIG["ES_CLIENT_TIMEOUT"]
237
  )
238
+ if client.ping():
239
+ if CONFIG["DEBUG_MODE"]: print(f"{GREEN}Sikeres Elastic Cloud kapcsolat!{RESET}")
240
+ return client
241
  except Exception as e:
242
+ print(f"{RED}Hiba az Elastic Cloud kapcsolat során: {e}{RESET}")
243
+ return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
 
245
  def get_embedding(text):
246
+ if not embedding_model or not text or not isinstance(text, str): return None
247
  try:
248
  return embedding_model.encode(text, normalize_embeddings=True).tolist()
249
  except Exception as e:
250
+ print(f"{RED}Hiba embedding közben: {e}{RESET}"); return None
 
251
 
252
  def create_es_index(client, index_name, index_settings, index_mappings):
253
+ if not EMBEDDING_DIM:
254
+ print(f"{RED}Hiba: Embedding dimenzió nincs beállítva.{RESET}")
255
+ return False
256
+ try:
257
+ index_mappings["properties"]["embedding"]["dims"] = EMBEDDING_DIM
258
+ except KeyError:
259
+ print(f"{RED}Hiba: Érvénytelen mapping struktúra.{RESET}")
260
+ return False
261
  try:
262
  if not client.indices.exists(index=index_name):
263
  print(f"'{index_name}' index létrehozása...")
264
  client.indices.create(index=index_name, settings=index_settings, mappings=index_mappings)
265
  print(f"{GREEN}Index sikeresen létrehozva.{RESET}")
266
+ time.sleep(2)
267
  else:
268
+ if CONFIG["DEBUG_MODE"]: print(f"Index '{index_name}' már létezik.")
269
  return True
270
  except Exception as e:
271
+ print(f"{RED}Hiba az index létrehozása során: {e}{RESET}")
272
+ traceback.print_exc()
273
  return False
274
 
275
  def extract_text_from_html(html_content):
276
  try:
277
  soup = BeautifulSoup(html_content, 'html.parser')
278
  for element in soup(["script", "style", "nav", "footer", "header", "aside", "form"]):
279
+ if element: element.decompose()
280
+ main_content = soup.find('main') or soup.find('article') or soup.body
281
+ if main_content:
282
+ return "\n".join(line for line in main_content.get_text(separator='\n', strip=True).splitlines() if line.strip())
283
  except Exception as e:
284
+ print(f"{RED}Hiba a HTML szöveg kinyerése során: {e}{RESET}")
285
  return ""
286
 
287
  def extract_and_filter_links(soup, base_url, target_domain):
288
  links = set()
289
+ try:
290
+ for a_tag in soup.find_all('a', href=True):
291
+ href = a_tag['href'].strip()
292
+ if href and not href.startswith(('#', 'mailto:', 'javascript:')):
293
+ full_url = urljoin(base_url, href)
294
+ parsed_url = urlparse(full_url)
295
+ if parsed_url.scheme in ['http', 'https'] and parsed_url.netloc == target_domain:
296
+ links.add(parsed_url._replace(fragment="").geturl())
297
+ except Exception as e:
298
+ print(f"{RED}Hiba a linkek kinyerése során: {e}{RESET}")
299
  return links
300
 
301
  def crawl_and_index_website(start_url, max_depth, es_client, index_name):
302
+ if not es_client or not embedding_model: return 0
303
  visited_urls, urls_to_visit = set(), deque([(start_url, 0)])
304
+ bulk_actions = []
305
+ total_prepared, total_indexed = 0, 0
306
  target_domain = urlparse(start_url).netloc
307
  print(f"Web crawling indítása: {start_url} (Max mélység: {max_depth}, Cél: {target_domain})")
 
308
  while urls_to_visit:
309
+ current_url = None
310
  try:
311
  current_url, current_depth = urls_to_visit.popleft()
312
+ if current_url in visited_urls or current_depth > max_depth: continue
313
+ print(f"\n--- Feldolgozás (Mélység: {current_depth}): {current_url} ---")
314
+ visited_urls.add(current_url)
315
+ try:
316
+ headers = {'User-Agent': CONFIG["USER_AGENT"]}
317
+ response = requests.get(current_url, headers=headers, timeout=15)
318
+ response.raise_for_status()
319
+ if 'text/html' not in response.headers.get('content-type', '').lower():
320
+ print(f" {YELLOW}-> Nem HTML tartalom, kihagyva.{RESET}"); continue
321
+ html_content = response.content
322
+ except requests.exceptions.RequestException as req_err:
323
+ print(f" {RED}!!! Hiba a letöltés során: {req_err}{RESET}"); continue
 
 
 
 
 
 
324
  soup = BeautifulSoup(html_content, 'html.parser')
325
  page_text = extract_text_from_html(html_content)
326
+ if not page_text or len(page_text) < CONFIG["MIN_CHUNK_SIZE_CHARS"]:
327
+ print(f" {YELLOW}-> Túl rövid szöveg, kihagyva.{RESET}"); continue
328
+ final_chunks = chunk_text_by_tokens(page_text, CONFIG["CHUNK_SIZE_TOKENS"], CONFIG["CHUNK_OVERLAP_TOKENS"])
329
+ url_category = generate_categories_with_llm(together_client, soup, page_text)[0]
 
 
 
330
  page_summary = generate_summary_with_llm(together_client, page_text)
331
+ if not final_chunks: continue
 
 
332
  for chunk_text in final_chunks:
333
  element_vector = get_embedding(chunk_text)
334
  if element_vector:
335
+ total_prepared += 1
336
+ doc = {"text_content": chunk_text, "embedding": element_vector, "source_origin": "website", "source_url": current_url, "source_type": "token_chunking", "category": url_category, "summary": page_summary}
 
 
 
337
  bulk_actions.append({"_index": index_name, "_source": doc})
338
+ if len(bulk_actions) >= CONFIG["BATCH_SIZE"]:
339
+ success_count, errors = helpers.bulk(es_client, bulk_actions, raise_on_error=False, request_timeout=CONFIG["ES_CLIENT_TIMEOUT"])
340
+ total_indexed += success_count; bulk_actions = []
341
+ if errors: print(f"{RED}!!! Hiba a bulk indexelés során: {len(errors)} sikertelen.{RESET}")
 
 
 
342
  if current_depth < max_depth:
343
+ new_links = extract_and_filter_links(soup, current_url, target_domain)
344
  for link in new_links:
345
+ if link not in visited_urls: urls_to_visit.append((link, current_depth + 1))
346
+ time.sleep(CONFIG['REQUEST_DELAY'])
347
+ except KeyboardInterrupt: print("\nFolyamat megszak��tva."); break
348
+ except Exception as loop_err: print(f"{RED}!!! Hiba a ciklusban ({current_url}): {loop_err}{RESET}"); traceback.print_exc(); time.sleep(5)
 
 
 
 
 
 
349
  if bulk_actions:
350
+ success_count, errors = helpers.bulk(es_client, bulk_actions, raise_on_error=False, request_timeout=CONFIG["ES_CLIENT_TIMEOUT"])
 
351
  total_indexed += success_count
352
+ if errors: print(f"{RED}!!! Hiba a maradék indexelése során: {len(errors)} sikertelen.{RESET}")
353
+ print(f"\n--- Web Crawling Befejezve ---")
354
  print(f"Meglátogatott URL-ek: {len(visited_urls)}")
355
+ print(f"Előkészített chunk-ok: {total_prepared}")
356
+ print(f"Sikeresen indexelt chunk-ok: {total_indexed}")
357
  return total_indexed
358
 
359
+ # ---futtatási blokk ---
360
  if __name__ == "__main__":
361
+ print(f"----- Web Crawler és Indexelő Indítása a '{CONFIG['VECTOR_INDEX_NAME']}' indexbe -----")
362
+ print(f"----- Cél URL: {CONFIG['START_URL']} (Max mélység: {CONFIG['MAX_DEPTH']}) -----")
363
+ print("****** FIGYELEM ******")
364
+ print(f"Ez a script létrehozza/használja a '{CONFIG['VECTOR_INDEX_NAME']}' indexet.")
365
+ print(f"{RED}Ha a '{CONFIG['VECTOR_INDEX_NAME']}' index már létezik, TÖRÖLD manuálisan futtatás előtt!{RESET}")
366
+ print("********************")
367
+ if not all([TORCH_AVAILABLE, SENTENCE_TRANSFORMER_AVAILABLE, embedding_model, EMBEDDING_DIM]):
368
+ print(f"{RED}Hiba: AI modellek hiányoznak. Leállás.{RESET}"); exit(1)
369
+ if not CONFIG["TOGETHER_API_KEY"]:
370
+ print(f"{RED}Hiba: TOGETHER_API_KEY hiányzik. Leállás.{RESET}"); exit(1)
371
 
 
 
 
 
372
  es_client = initialize_es_client()
373
+ if not es_client:
374
+ print(f"{RED}Hiba: Elasticsearch kliens inicializálása sikertelen. Leállás.{RESET}"); exit(1)
375
+
376
+ final_success_count = 0
377
+ index_ready = create_es_index(
378
+ client=es_client,
379
+ index_name=CONFIG["VECTOR_INDEX_NAME"],
380
+ index_settings=INDEX_SETTINGS,
381
+ index_mappings=INDEX_MAPPINGS_WEB
382
+ )
383
+
384
+ if index_ready:
385
+ print(f"\nIndex '{CONFIG['VECTOR_INDEX_NAME']}' kész. Crawling indítása...")
386
+ final_success_count = crawl_and_index_website(
387
+ start_url=CONFIG["START_URL"],
388
+ max_depth=CONFIG["MAX_DEPTH"],
389
+ es_client=es_client,
390
+ index_name=CONFIG["VECTOR_INDEX_NAME"]
391
+ )
392
+ else:
393
+ print(f"{RED}Hiba: Index létrehozása sikertelen. Leállás.{RESET}")
394
+
395
+ print("\n----- Feldolgozás Befejezve -----")
396
+ if index_ready and final_success_count > 0:
397
+ print(f"\n{GREEN}Sikeres. {final_success_count} chunk indexelve '{CONFIG['VECTOR_INDEX_NAME']}'-be.{RESET}")
398
+ elif index_ready and final_success_count == 0:
399
+ print(f"{YELLOW}Crawling lefutott, de 0 chunk lett indexelve.{RESET}")
400
  else:
401
+ print(f"{RED}A folyamat hibával zárult.{RESET}")