Király Zoltán commited on
Commit
90e8782
·
1 Parent(s): b5d1360

API backend

Browse files
Files changed (3) hide show
  1. .gitignore +10 -0
  2. api_main.py +415 -0
  3. requirements.txt +2 -0
.gitignore ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ Virtual Environment
2
+ .venv/
3
+ venv/
4
+
5
+ # Environment variables
6
+ .env
7
+
8
+ # Python cache
9
+ __pycache__/
10
+ *.pyc
api_main.py ADDED
@@ -0,0 +1,415 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # api_main.py
2
+ # A te teljes backendv1.py kódod FastAPI keretrendszerbe illesztve.
3
+
4
+ # === FastAPI és alapvető importok ===
5
+ from fastapi import FastAPI, HTTPException
6
+ from pydantic import BaseModel
7
+ import uvicorn
8
+ from typing import List, Dict, Any
9
+
10
+ # === A TE KÓDOD (backendv1.py) ===
11
+ # Itt kezdődik a te kódod, minimális változtatással
12
+ import os
13
+ import time
14
+ import datetime
15
+ import traceback
16
+ import re
17
+ from collections import defaultdict
18
+ from elasticsearch import Elasticsearch, exceptions as es_exceptions
19
+ import torch
20
+ from sentence_transformers import SentenceTransformer
21
+ from sentence_transformers.cross_encoder import CrossEncoder
22
+ from spellchecker import SpellChecker
23
+ from dotenv import load_dotenv
24
+ import sys
25
+ import nltk
26
+ from concurrent.futures import ThreadPoolExecutor
27
+
28
+ # Késleltetett importálás, hogy csak akkor legyen hiba, ha tényleg használjuk
29
+ try:
30
+ from together import Together
31
+ TOGETHER_AVAILABLE = True
32
+ except ImportError:
33
+ TOGETHER_AVAILABLE = False
34
+
35
+ # === ANSI Színkódok (konzol loggoláshoz) ===
36
+ GREEN = '\033[92m'
37
+ YELLOW = '\033[93m'
38
+ RED = '\033[91m'
39
+ RESET = '\033[0m'
40
+ BLUE = '\033[94m'
41
+ CYAN = '\033[96m'
42
+ MAGENTA = '\033[95m'
43
+
44
+ # --- Konfiguráció ---
45
+ # A hitelesítő adatok a környezeti változókból kerülnek beolvasásra.
46
+ CONFIG = {
47
+ "VECTOR_INDEX_NAMES": ["duna", "dunawebindexai"],
48
+ "FEEDBACK_INDEX_NAME": "feedback_index",
49
+ "ES_CLIENT_TIMEOUT": 90,
50
+ "EMBEDDING_MODEL_NAME": 'sentence-transformers/paraphrase-multilingual-mpnet-base-v2',
51
+ "CROSS_ENCODER_MODEL_NAME": 'cross-encoder/mmarco-mMiniLMv2-L12-H384-v1',
52
+ "TOGETHER_MODEL_NAME": "meta-llama/Llama-3.3-70B-Instruct-Turbo-Free", # Frissítve a valós modellnévre
53
+ "QUERY_EXPANSION_MODEL": "mistralai/Mixtral-8x7B-Instruct-v0.1",
54
+ "LLM_CLIENT_TIMEOUT": 120,
55
+ "NUM_CONTEXT_RESULTS": 5,
56
+ "RE_RANK_CANDIDATE_COUNT": 50,
57
+ "RRF_RANK_CONSTANT": 60,
58
+ "INITIAL_SEARCH_SIZE": 150,
59
+ "KNN_NUM_CANDIDATES": 200,
60
+ "MAX_GENERATION_TOKENS": 1024,
61
+ "GENERATION_TEMPERATURE": 0.6,
62
+ "USE_QUERY_EXPANSION": True,
63
+ "SPELLCHECK_LANG": 'hu',
64
+ "MAX_HISTORY_TURNS": 3
65
+ }
66
+
67
+ # --- Segédfüggvények (A TE FÜGGVÉNYEID) ---
68
+
69
+ def correct_spellings(text, spell_checker_instance):
70
+ if not spell_checker_instance: return text
71
+ try:
72
+ words = re.findall(r'\b\w+\b', text.lower())
73
+ misspelled = spell_checker_instance.unknown(words)
74
+ if not misspelled: return text
75
+ corrected_text = text
76
+ for word in misspelled:
77
+ correction = spell_checker_instance.correction(word)
78
+ if correction and correction != word:
79
+ # Ezt a sort javítottam az előző verziódban, mert hibás volt a re.sub hívás
80
+ corrected_text = re.sub(r'\b' + re.escape(word) + r'\b', correction, corrected_text, flags=re.IGNORECASE)
81
+ return corrected_text
82
+ except Exception as e:
83
+ print(f"{RED}Hiba a helyesírás javítása közben: {e}{RESET}")
84
+ return text
85
+
86
+ def get_query_category_with_llm(client, query):
87
+ if not client: return 'egyéb'
88
+ print(f" {CYAN}-> Lekérdezés kategorizálása LLM-mel...{RESET}")
89
+ category_list = ['IT biztonsági szolgáltatások', 'szolgáltatások', 'hardver', 'szoftver', 'hírek', 'audiovizuális konferenciatechnika']
90
+ categories_text = ", ".join([f"'{cat}'" for cat in category_list])
91
+ prompt = f"""Adott egy felhasználói kérdés. Adj meg egyetlen, rövid kategóriát a következő listából, ami a legjobban jellemzi a kérdést. A válaszodban csak a kategória szerepeljen, más szöveg nélkül.
92
+ Lehetséges kategóriák: {categories_text}
93
+ Kérdés: '{query}'
94
+ Kategória:"""
95
+ messages = [{"role": "user", "content": prompt}]
96
+ try:
97
+ response = client.chat.completions.create(model=CONFIG["QUERY_EXPANSION_MODEL"], messages=messages, temperature=0.1, max_tokens=30)
98
+ if response and response.choices:
99
+ category = response.choices[0].message.content.strip().replace("'", "").replace("`", "")
100
+ for cat in category_list:
101
+ if cat.lower() in category.lower():
102
+ print(f" {GREEN}-> A kérdés LLM által generált kategóriája: '{cat}'{RESET}")
103
+ return cat.lower()
104
+ return 'egyéb'
105
+ except Exception as e:
106
+ print(f"{RED}Hiba LLM kategorizáláskor: {e}{RESET}")
107
+ return 'egyéb'
108
+
109
+ def expand_or_rewrite_query(original_query, client):
110
+ final_queries = [original_query]
111
+ if not (CONFIG["USE_QUERY_EXPANSION"] and client):
112
+ return final_queries
113
+ print(f" {BLUE}-> Lekérdezés bővítése/átírása...{RESET}")
114
+ prompt = f"Adott egy magyar nyelvű felhasználói kérdés: '{original_query}'. Generálj 2 db alternatív, releváns keresőkifejezést. A válaszodban csak ezeket add vissza, vesszővel (,) elválasztva, minden más szöveg nélkül."
115
+ messages = [{"role": "user", "content": prompt}]
116
+ try:
117
+ response = client.chat.completions.create(model=CONFIG["QUERY_EXPANSION_MODEL"], messages=messages, temperature=0.5, max_tokens=100)
118
+ if response and response.choices:
119
+ generated_text = response.choices[0].message.content.strip()
120
+ alternatives = [q.strip().replace('"', '').replace("'", '').replace('.', '') for q in generated_text.split(',') if q.strip() and q.strip() != original_query]
121
+ final_queries.extend(alternatives)
122
+ print(f" {GREEN}-> Bővített lekérdezések: {final_queries}{RESET}")
123
+ except Exception as e:
124
+ print(f"{RED}Hiba a lekérdezés bővítése során: {e}{RESET}")
125
+ return final_queries
126
+
127
+ def run_separate_searches(es_client, query_text, embedding_model, expanded_queries, query_category=None):
128
+ results = {'knn': {}, 'keyword': {}}
129
+ es_client_with_timeout = es_client.options(request_timeout=CONFIG["ES_CLIENT_TIMEOUT"])
130
+ source_fields = ["text_content", "source_url", "summary", "category"]
131
+ filters = []
132
+
133
+ def knn_search(index, query_vector):
134
+ try:
135
+ knn_query = {"field": "embedding", "query_vector": query_vector, "k": CONFIG["INITIAL_SEARCH_SIZE"], "num_candidates": CONFIG["KNN_NUM_CANDIDATES"], "filter": filters}
136
+ response = es_client_with_timeout.search(index=index, knn=knn_query, _source=source_fields, size=CONFIG["INITIAL_SEARCH_SIZE"])
137
+ return index, response.get('hits', {}).get('hits', [])
138
+ except Exception as e:
139
+ print(f"{RED}Hiba kNN keresés során ({index}): {e}{RESET}")
140
+ return index, []
141
+
142
+ def keyword_search(index, expanded_queries):
143
+ try:
144
+ should_clauses = [{"match": {"text_content": {"query": q, "operator": "OR", "fuzziness": "AUTO"}}} for q in expanded_queries]
145
+ query_body = {"query": {"bool": {"should": should_clauses, "minimum_should_match": 1, "filter": filters}}}
146
+ response = es_client_with_timeout.search(index=index, query=query_body['query'], _source=source_fields, size=CONFIG["INITIAL_SEARCH_SIZE"])
147
+ return index, response.get('hits', {}).get('hits', [])
148
+ except Exception as e:
149
+ print(f"{RED}Hiba kulcsszavas keresés során ({index}): {e}{RESET}")
150
+ return index, []
151
+
152
+ query_vector = embedding_model.encode(query_text, normalize_embeddings=True).tolist() if embedding_model else None
153
+
154
+ with ThreadPoolExecutor(max_workers=len(CONFIG["VECTOR_INDEX_NAMES"]) * 2) as executor:
155
+ knn_futures = {executor.submit(knn_search, index, query_vector) for index in CONFIG["VECTOR_INDEX_NAMES"] if query_vector}
156
+ keyword_futures = {executor.submit(keyword_search, index, expanded_queries) for index in CONFIG["VECTOR_INDEX_NAMES"]}
157
+
158
+ for future in knn_futures:
159
+ index, hits = future.result()
160
+ results['knn'][index] = [(rank + 1, hit) for rank, hit in enumerate(hits)]
161
+ for future in keyword_futures:
162
+ index, hits = future.result()
163
+ results['keyword'][index] = [(rank + 1, hit) for rank, hit in enumerate(hits)]
164
+
165
+ total_knn_hits = sum(len(h) for h in results['knn'].values())
166
+ total_keyword_hits = sum(len(h) for h in results['keyword'].values())
167
+ print(f"{CYAN}Vektorkeresési találatok száma: {total_knn_hits}{RESET}")
168
+ print(f"{CYAN}Kulcsszavas keresési találatok száma: {total_keyword_hits}{RESET}")
169
+ return results
170
+
171
+ def merge_results_rrf(search_results):
172
+ rrf_scores = defaultdict(float)
173
+ all_hits_data = {}
174
+ for search_type in search_results:
175
+ for index_name in search_results[search_type]:
176
+ for rank, hit in search_results[search_type][index_name]:
177
+ doc_id = hit['_id']
178
+ rrf_scores[doc_id] += 1.0 / (CONFIG["RRF_RANK_CONSTANT"] + rank)
179
+ if doc_id not in all_hits_data:
180
+ all_hits_data[doc_id] = hit
181
+
182
+ combined_results = sorted([(doc_id, score, all_hits_data[doc_id]) for doc_id, score in rrf_scores.items()], key=lambda item: item[1], reverse=True)
183
+ print(f"{CYAN}RRF által rangsorolt Top 5 pontszám: {[f'{score:.4f}' for doc_id, score, hit in combined_results[:5]]}{RESET}")
184
+ return combined_results
185
+
186
+ def retrieve_context_reranked(backend, query_text, confidence_threshold, fallback_message, query_category):
187
+ expanded_queries = expand_or_rewrite_query(query_text, backend["llm_client"])
188
+ search_results = run_separate_searches(backend["es_client"], query_text, backend["embedding_model"], expanded_queries, query_category)
189
+ merged_results = merge_results_rrf(search_results)
190
+
191
+ if not merged_results:
192
+ return fallback_message, [], None
193
+
194
+ candidates_to_rerank = merged_results[:CONFIG["RE_RANK_CANDIDATE_COUNT"]]
195
+ hits_data_for_reranking = [hit for _, _, hit in candidates_to_rerank]
196
+ query_chunk_pairs = [[query_text, hit['_source'].get('summary', hit['_source'].get('text_content'))] for hit in hits_data_for_reranking if hit and '_source' in hit]
197
+
198
+ ranked_by_ce = []
199
+ if backend["cross_encoder"] and query_chunk_pairs:
200
+ ce_scores = backend["cross_encoder"].predict(query_chunk_pairs, show_progress_bar=False)
201
+ ranked_by_ce = sorted(zip(ce_scores, hits_data_for_reranking), key=lambda x: x[0], reverse=True)
202
+ print(f"{CYAN}Cross-Encoder pontszámok (Top 5):{RESET} {[f'{score:.4f}' for score, _ in ranked_by_ce[:5]]}")
203
+
204
+ if not ranked_by_ce:
205
+ return fallback_message, [], None
206
+
207
+ top_score = float(ranked_by_ce[0][0])
208
+ if top_score < confidence_threshold:
209
+ dynamic_fallback = (f"{fallback_message}\n\nA '{query_text}' kérdésre a legjobb találat megbízhatósági pontszáma ({top_score:.2f}) nem érte el a beállított küszöböt ({confidence_threshold:.2f}).")
210
+ return dynamic_fallback, [], top_score
211
+
212
+ final_hits_for_context = [hit for _, hit in ranked_by_ce[:CONFIG["NUM_CONTEXT_RESULTS"]]]
213
+ context_parts = [hit['_source'].get('summary', hit['_source'].get('text_content')) for hit in final_hits_for_context]
214
+ context_string = "\n\n---\n\n".join(context_parts)
215
+
216
+ sources = [{"url": hit['_source'].get('source_url', '?'), "content": hit['_source'].get('text_content', 'N/A')} for hit in final_hits_for_context]
217
+ return context_string, sources, top_score
218
+
219
+ def generate_answer_with_history(client, model_name, messages, temperature):
220
+ if not client: return "Hiba: Az AI kliens nincs inicializálva."
221
+ try:
222
+ response = client.chat.completions.create(model=model_name, messages=messages, temperature=temperature, max_tokens=CONFIG["MAX_GENERATION_TOKENS"], timeout=CONFIG["LLM_CLIENT_TIMEOUT"])
223
+ if response and response.choices:
224
+ return response.choices[0].message.content.strip()
225
+ return "Hiba: Nem érkezett érvényes válasz az AI modelltől."
226
+ except Exception as e:
227
+ print(f"{RED}Hiba a válasz generálásakor: {e}{RESET}")
228
+ return "Hiba történt az AI modell hívásakor."
229
+
230
+ def search_in_feedback_index(es_client, embedding_model, question, min_score=0.75):
231
+ try:
232
+ if not es_client.indices.exists(index=CONFIG["FEEDBACK_INDEX_NAME"]): return None, None
233
+ embedding = embedding_model.encode(question, normalize_embeddings=True).tolist()
234
+ knn_query = {"field": "embedding", "query_vector": embedding, "k": 1, "num_candidates": 10}
235
+ response = es_client.search(index=CONFIG["FEEDBACK_INDEX_NAME"], knn=knn_query, _source=["question_text", "correction_text"])
236
+ hits = response.get('hits', {}).get('hits', [])
237
+ if hits and hits[0]['_score'] >= min_score:
238
+ top_hit = hits[0]; source = top_hit['_source']; score = top_hit['_score']
239
+ if score > 0.98: return "direct_answer", source['correction_text']
240
+ instruction = f"Egy nagyon hasonló kérdésre ('{source['question_text']}') korábban a következő javítást/iránymutatást adtad: '{source['correction_text']}'. A válaszodat elsősorban ez alapján alkosd meg!"
241
+ return "instruction", instruction
242
+ except Exception:
243
+ return None, None
244
+ return None, None
245
+
246
+ def index_feedback(es_client, embedding_model, question, correction):
247
+ try:
248
+ embedding = embedding_model.encode(question, normalize_embeddings=True).tolist()
249
+ doc = {"question_text": question, "correction_text": correction, "embedding": embedding, "timestamp": datetime.datetime.now()}
250
+ es_client.index(index=CONFIG["FEEDBACK_INDEX_NAME"], document=doc)
251
+ return True
252
+ except Exception as e:
253
+ print(f"{RED}Hiba a visszajelzés indexelése során: {e}{RESET}")
254
+ return False
255
+
256
+ def get_all_feedback(es_client, index_name):
257
+ try:
258
+ if not es_client.indices.exists(index=index_name): return []
259
+ response = es_client.search(index=index_name, query={"match_all": {}}, size=1000, sort=[{"timestamp": {"order": "desc"}}])
260
+ return response.get('hits', {}).get('hits', [])
261
+ except Exception as e:
262
+ print(f"{RED}Hiba a visszajelzések listázása során: {e}{RESET}")
263
+ return []
264
+
265
+ def delete_feedback_by_id(es_client, index_name, doc_id):
266
+ try:
267
+ es_client.delete(index=index_name, id=doc_id)
268
+ return True
269
+ except Exception as e:
270
+ print(f"{RED}Hiba a visszajelzés törlése során (ID: {doc_id}): {e}{RESET}")
271
+ return False
272
+
273
+ def update_feedback_comment(es_client, index_name, doc_id, new_comment):
274
+ try:
275
+ es_client.update(index=index_name, id=doc_id, doc={"correction_text": new_comment})
276
+ return True
277
+ except Exception as e:
278
+ print(f"{RED}Hiba a visszajelzés szerkesztése során (ID: {doc_id}): {e}{RESET}")
279
+ return False
280
+
281
+ def initialize_backend():
282
+ print("----- Backend Motor Inicializálása -----")
283
+ load_dotenv()
284
+
285
+ es_cloud_id = os.getenv("ES_CLOUD_ID")
286
+ es_api_key = os.getenv("ES_API_KEY")
287
+ together_api_key = os.getenv("TOGETHER_API_KEY")
288
+
289
+ if not all([es_cloud_id, es_api_key, together_api_key]):
290
+ print(f"{RED}Hiba: Hiányzó környezeti változók! Szükséges: ES_CLOUD_ID, ES_API_KEY, TOGETHER_API_KEY{RESET}")
291
+ return None
292
+
293
+ if not TOGETHER_AVAILABLE:
294
+ print(f"{RED}Hiba: A 'together' csomag nincs telepítve.{RESET}")
295
+ return None
296
+
297
+ try:
298
+ nltk.data.find('tokenizers/punkt')
299
+ except LookupError:
300
+ nltk.download('punkt', quiet=True)
301
+
302
+ spell_checker = None
303
+ try:
304
+ spell_checker = SpellChecker(language=CONFIG["SPELLCHECK_LANG"])
305
+ custom_words = ["dunaelektronika", "kft", "outsourcing", "dell", "lenovo", "nis2", "szerver", "kliens", "hálózati", "hpe"]
306
+ spell_checker.word_frequency.load_words(custom_words)
307
+ except Exception as e:
308
+ print(f"{RED}Helyesírás-ellenőrző hiba: {e}{RESET}")
309
+
310
+ try:
311
+ print(f"{CYAN}Elasticsearch kliens inicializálása...{RESET}")
312
+ es_client = Elasticsearch(cloud_id=es_cloud_id, api_key=es_api_key, request_timeout=CONFIG["ES_CLIENT_TIMEOUT"])
313
+ if not es_client.ping(): raise ConnectionError("Elasticsearch ping sikertelen.")
314
+ print(f"{GREEN}Elasticsearch kliens kész.{RESET}")
315
+
316
+ print(f"{CYAN}AI modellek betöltése...{RESET}")
317
+ device = 'cuda' if torch.cuda.is_available() else 'cpu'
318
+ embedding_model = SentenceTransformer(CONFIG["EMBEDDING_MODEL_NAME"], device=device)
319
+ cross_encoder = CrossEncoder(CONFIG["CROSS_ENCODER_MODEL_NAME"], device=device)
320
+ llm_client = Together(api_key=together_api_key)
321
+ print(f"{GREEN}AI modellek betöltve (eszköz: {device}).{RESET}")
322
+
323
+ backend_objects = {
324
+ "es_client": es_client, "embedding_model": embedding_model, "cross_encoder": cross_encoder,
325
+ "llm_client": llm_client, "spell_checker": spell_checker
326
+ }
327
+
328
+ print(f"{GREEN}----- Backend Motor Készen Áll -----{RESET}")
329
+ return backend_objects
330
+ except Exception as e:
331
+ print(f"{RED}Hiba a backend inicializálása során: {e}{RESET}")
332
+ traceback.print_exc()
333
+ return None
334
+
335
+ def process_query(user_question, chat_history, backend, confidence_threshold, fallback_message):
336
+ print(f"\n{BLUE}----- Új lekérdezés feldolgozása ----{RESET}")
337
+ print(f"{BLUE}Kérdés: {user_question}{RESET}")
338
+
339
+ corrected_question = correct_spellings(user_question, backend["spell_checker"])
340
+ print(f"{BLUE}Javított kérdés: {corrected_question}{RESET}")
341
+
342
+ feedback_type, feedback_content = search_in_feedback_index(backend["es_client"], backend["embedding_model"], corrected_question)
343
+ if feedback_type == "direct_answer":
344
+ print(f"{GREEN}Direkt válasz a visszajelzési adatbázisból.{RESET}")
345
+ return {"answer": feedback_content, "sources": [{"url": "Személyes visszajelzés alapján", "content": "Ez egy korábban megadott, pontosított válasz."}], "corrected_question": corrected_question, "confidence_score": 10.0}
346
+
347
+ feedback_instructions = feedback_content if feedback_type == "instruction" else ""
348
+ query_category = get_query_category_with_llm(backend["llm_client"], corrected_question)
349
+ retrieved_context, sources, confidence_score = retrieve_context_reranked(backend, corrected_question, confidence_threshold, fallback_message, query_category)
350
+
351
+ if not sources and not feedback_instructions:
352
+ return {"answer": retrieved_context, "sources": [], "corrected_question": corrected_question, "confidence_score": confidence_score}
353
+
354
+ system_prompt = f"""Te egy professzionális, segítőkész AI asszisztens vagy.
355
+ 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.
356
+ {feedback_instructions}
357
+ 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!
358
+ 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}'
359
+ KONTEXTUS:
360
+ ---
361
+ {retrieved_context if sources else "A tudásbázisban nem található releváns információ."}
362
+ ---
363
+ """
364
+ messages_for_llm = chat_history[-(CONFIG["MAX_HISTORY_TURNS"] * 2):] if chat_history else []
365
+ messages_for_llm.extend([{"role": "system", "content": system_prompt}, {"role": "user", "content": corrected_question}])
366
+
367
+ answer = generate_answer_with_history(backend["llm_client"], CONFIG["TOGETHER_MODEL_NAME"], messages_for_llm, CONFIG["GENERATION_TEMPERATURE"])
368
+
369
+ return {"answer": answer, "sources": sources, "corrected_question": corrected_question, "confidence_score": confidence_score}
370
+
371
+
372
+ # === FastAPI KERETRENDSZER ===
373
+
374
+ # FastAPI alkalmazás létrehozása
375
+ app = FastAPI()
376
+
377
+ # A backend objektumok inicializálása a szerver indulásakor
378
+ # Ezt egy globális változóban tároljuk, hogy minden API hívás elérje
379
+ backend_objects = initialize_backend()
380
+
381
+ # Ez a modell határozza meg, milyen formátumban várjuk a kérést
382
+ # Hozzáadtuk a chat_history-t is, mint opcionális mezőt
383
+ class UserQuery(BaseModel):
384
+ question: str
385
+ chat_history: List[Dict[str, str]] = [] # pl. [{"role": "user", "content": "..."}, {"role": "assistant", "content": "..."}]
386
+
387
+ # Ez hozza létre a /ask API végpontot
388
+ @app.post("/ask")
389
+ def handle_question(query: UserQuery):
390
+ # Ellenőrizzük, hogy a backend sikeresen elindult-e
391
+ if not backend_objects:
392
+ raise HTTPException(status_code=503, detail="A backend szolgáltatás nem érhető el vagy hibával indult el.")
393
+
394
+ try:
395
+ # Meghívjuk a te logikádat tartalmazó process_query függvényt
396
+ # A chat_history-t is átadjuk a kérésből
397
+ result = process_query(
398
+ user_question=query.question,
399
+ chat_history=query.chat_history,
400
+ backend=backend_objects,
401
+ confidence_threshold=-3.8, # Ezt az értéket később akár a kérésből is kaphatja
402
+ fallback_message="Sajnos nem találtam releváns információt a kérdésedre a tudásbázisban."
403
+ )
404
+ return result
405
+ except Exception as e:
406
+ # Általános hibakezelés
407
+ print(f"{RED}Váratlan hiba az API végpontban: {e}{RESET}")
408
+ traceback.print_exc()
409
+ raise HTTPException(status_code=500, detail="Belső szerverhiba történt a kérés feldolgozása közben.")
410
+
411
+
412
+ # Fő indítási pont a helyi teszteléshez
413
+ if __name__ == "__main__":
414
+ # Az --host 0.0.0.0 lehetővé teszi, hogy a hálózat más gépeiről is elérd a szervert
415
+ uvicorn.run(app, host="0.0.0.0", port=8000)
requirements.txt CHANGED
@@ -9,3 +9,5 @@ streamlit
9
  together
10
  torch
11
  tiktoken
 
 
 
9
  together
10
  torch
11
  tiktoken
12
+ fastapi
13
+ uvicorn[standard]