jeongsoo commited on
Commit
ac1b0e8
ยท
1 Parent(s): 21e2154

deepseek_done

Browse files
Files changed (9) hide show
  1. app.py +70 -15
  2. config.py +35 -28
  3. custom_rag_chain.py +224 -0
  4. direct_deepseek.py +255 -0
  5. fallback_rag_chain.py +62 -58
  6. monitoring.py +0 -136
  7. rag_chain.py +208 -317
  8. simple_rag_chain.py +99 -42
  9. test_deepseek.py +0 -123
app.py CHANGED
@@ -562,15 +562,15 @@ class AutoRAGChatApp:
562
  # PDF ํŒŒ์ผ ๋ชฉ๋ก ์ˆ˜์ง‘์„ ๊ฐœ์„ ํ•˜์—ฌ ๋‹ค์–‘ํ•œ ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ
563
  try:
564
  pdf_files = []
565
-
566
  # ์„ค์ •๋œ ๋””๋ ‰ํ† ๋ฆฌ์—์„œ PDF ํŒŒ์ผ ์ฐพ๊ธฐ
567
  logger.info(f"PDF ํŒŒ์ผ ๊ฒ€์ƒ‰ ๊ฒฝ๋กœ: {self.pdf_directory}")
568
-
569
  if os.path.exists(self.pdf_directory) and os.path.isdir(self.pdf_directory):
570
  # ๋””๋ ‰ํ† ๋ฆฌ ๋‚ด์šฉ ์ถœ๋ ฅ (๋””๋ฒ„๊น…์šฉ)
571
  dir_contents = os.listdir(self.pdf_directory)
572
  logger.info(f"๋””๋ ‰ํ† ๋ฆฌ ๋‚ด์šฉ: {dir_contents}")
573
-
574
  # PDF ํŒŒ์ผ๋งŒ ํ•„ํ„ฐ๋ง
575
  for filename in os.listdir(self.pdf_directory):
576
  if filename.lower().endswith('.pdf'):
@@ -578,10 +578,10 @@ class AutoRAGChatApp:
578
  if os.path.isfile(file_path): # ์‹ค์ œ ํŒŒ์ผ์ธ์ง€ ํ™•์ธ
579
  pdf_files.append(file_path)
580
  logger.info(f"PDF ํŒŒ์ผ ์ฐพ์Œ: {file_path}")
581
-
582
  # ๋ฐœ๊ฒฌ๋œ ๋ชจ๋“  ํŒŒ์ผ ๋กœ๊ทธ
583
  logger.info(f"๋ฐœ๊ฒฌ๋œ ๋ชจ๋“  PDF ํŒŒ์ผ: {pdf_files}")
584
-
585
  except FileNotFoundError:
586
  logger.error(f"PDF ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ: {self.pdf_directory}")
587
  return f"'{self.pdf_directory}' ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”."
@@ -668,32 +668,87 @@ class AutoRAGChatApp:
668
  # RAG ์ฒด์ธ ์ดˆ๊ธฐํ™”
669
  if RAG_CHAIN_AVAILABLE:
670
  try:
 
671
  self.rag_chain = RAGChain(self.vector_store)
672
  self.is_initialized = True
 
673
  except Exception as e:
674
  logger.error(f"RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์‹คํŒจ: {e}", exc_info=True)
 
 
675
  try:
676
- # ํด๋ฐฑ RAG ์ฒด์ธ ์‹œ๋„
677
  from fallback_rag_chain import FallbackRAGChain
678
- logger.info("ํด๋ฐฑ RAG ์ฒด์ธ์œผ๋กœ ์ „ํ™˜ํ•ฉ๋‹ˆ๋‹ค...")
679
  self.rag_chain = FallbackRAGChain(self.vector_store)
680
  self.is_initialized = True
681
  logger.info("ํด๋ฐฑ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์„ฑ๊ณต")
682
  except Exception as fallback_e:
683
  logger.error(f"ํด๋ฐฑ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์‹คํŒจ: {fallback_e}", exc_info=True)
684
- return f"๋ฌธ์„œ์™€ ๋ฒกํ„ฐ ์ธ๋ฑ์Šค๋Š” ์ฒ˜๋ฆฌ๋˜์—ˆ์œผ๋‚˜ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™”์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
685
  else:
 
686
  try:
687
- # RAGChain์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ ํด๋ฐฑ ๋ฒ„์ „ ์‚ฌ์šฉ
688
- from fallback_rag_chain import FallbackRAGChain
689
- logger.info("๊ธฐ๋ณธ RAG Chain์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์–ด ํด๋ฐฑ ๋ฒ„์ „์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค...")
690
- self.rag_chain = FallbackRAGChain(self.vector_store)
691
- self.is_initialized = True
692
- logger.info("ํด๋ฐฑ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์„ฑ๊ณต")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
693
  except Exception as e:
694
- logger.error(f"ํด๋ฐฑ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์‹คํŒจ: {e}", exc_info=True)
695
  return f"๋ฌธ์„œ์™€ ๋ฒกํ„ฐ ์ธ๋ฑ์Šค๋Š” ์ฒ˜๋ฆฌ๋˜์—ˆ์œผ๋‚˜ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™”์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}"
696
 
 
 
 
 
 
 
 
 
 
 
 
 
 
697
  except Exception as e:
698
  error_message = f"๋ฌธ์„œ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
699
  logger.error(error_message, exc_info=True)
 
562
  # PDF ํŒŒ์ผ ๋ชฉ๋ก ์ˆ˜์ง‘์„ ๊ฐœ์„ ํ•˜์—ฌ ๋‹ค์–‘ํ•œ ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ
563
  try:
564
  pdf_files = []
565
+
566
  # ์„ค์ •๋œ ๋””๋ ‰ํ† ๋ฆฌ์—์„œ PDF ํŒŒ์ผ ์ฐพ๊ธฐ
567
  logger.info(f"PDF ํŒŒ์ผ ๊ฒ€์ƒ‰ ๊ฒฝ๋กœ: {self.pdf_directory}")
568
+
569
  if os.path.exists(self.pdf_directory) and os.path.isdir(self.pdf_directory):
570
  # ๋””๋ ‰ํ† ๋ฆฌ ๋‚ด์šฉ ์ถœ๋ ฅ (๋””๋ฒ„๊น…์šฉ)
571
  dir_contents = os.listdir(self.pdf_directory)
572
  logger.info(f"๋””๋ ‰ํ† ๋ฆฌ ๋‚ด์šฉ: {dir_contents}")
573
+
574
  # PDF ํŒŒ์ผ๋งŒ ํ•„ํ„ฐ๋ง
575
  for filename in os.listdir(self.pdf_directory):
576
  if filename.lower().endswith('.pdf'):
 
578
  if os.path.isfile(file_path): # ์‹ค์ œ ํŒŒ์ผ์ธ์ง€ ํ™•์ธ
579
  pdf_files.append(file_path)
580
  logger.info(f"PDF ํŒŒ์ผ ์ฐพ์Œ: {file_path}")
581
+
582
  # ๋ฐœ๊ฒฌ๋œ ๋ชจ๋“  ํŒŒ์ผ ๋กœ๊ทธ
583
  logger.info(f"๋ฐœ๊ฒฌ๋œ ๋ชจ๋“  PDF ํŒŒ์ผ: {pdf_files}")
584
+
585
  except FileNotFoundError:
586
  logger.error(f"PDF ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ: {self.pdf_directory}")
587
  return f"'{self.pdf_directory}' ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”."
 
668
  # RAG ์ฒด์ธ ์ดˆ๊ธฐํ™”
669
  if RAG_CHAIN_AVAILABLE:
670
  try:
671
+ logger.info("RAGChain์œผ๋กœ ์ดˆ๊ธฐํ™”๋ฅผ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค.")
672
  self.rag_chain = RAGChain(self.vector_store)
673
  self.is_initialized = True
674
+ logger.info("RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์„ฑ๊ณต")
675
  except Exception as e:
676
  logger.error(f"RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์‹คํŒจ: {e}", exc_info=True)
677
+
678
+ # FallbackRAGChain์œผ๋กœ ๋Œ€์ฒด ์‹œ๋„
679
  try:
680
+ logger.info("FallbackRAGChain์œผ๋กœ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค...")
681
  from fallback_rag_chain import FallbackRAGChain
 
682
  self.rag_chain = FallbackRAGChain(self.vector_store)
683
  self.is_initialized = True
684
  logger.info("ํด๋ฐฑ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์„ฑ๊ณต")
685
  except Exception as fallback_e:
686
  logger.error(f"ํด๋ฐฑ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์‹คํŒจ: {fallback_e}", exc_info=True)
687
+
688
+ # SimpleRAGChain ์‹œ๋„ (์ตœํ›„์˜ ์ˆ˜๋‹จ)
689
+ try:
690
+ logger.info("SimpleRAGChain์œผ๋กœ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค...")
691
+ from simple_rag_chain import SimpleRAGChain
692
+
693
+ # API ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
694
+ try:
695
+ from config import DEEPSEEK_API_KEY, DEEPSEEK_MODEL, DEEPSEEK_ENDPOINT
696
+ logger.info(f"์„ค์ • ํŒŒ์ผ์—์„œ DeepSeek API ์ •๋ณด๋ฅผ ๋กœ๋“œํ–ˆ์Šต๋‹ˆ๋‹ค: ๋ชจ๋ธ={DEEPSEEK_MODEL}")
697
+ except ImportError:
698
+ # ์„ค์ • ํŒŒ์ผ์—์„œ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ํ™•์ธ
699
+ DEEPSEEK_API_KEY = os.environ.get("DEEPSEEK_API_KEY", "")
700
+ DEEPSEEK_MODEL = os.environ.get("DEEPSEEK_MODEL", "deepseek-chat")
701
+ DEEPSEEK_ENDPOINT = os.environ.get("DEEPSEEK_ENDPOINT",
702
+ "https://api.deepseek.com/v1/chat/completions")
703
+ logger.info(f"ํ™˜๊ฒฝ ๋ณ€์ˆ˜์—์„œ DeepSeek API ์ •๋ณด๋ฅผ ๋กœ๋“œํ–ˆ์Šต๋‹ˆ๋‹ค: ๋ชจ๋ธ={DEEPSEEK_MODEL}")
704
+
705
+ # SimpleRAGChain ์ดˆ๊ธฐํ™” ์‹œ๋„
706
+ self.rag_chain = SimpleRAGChain(self.vector_store)
707
+ self.is_initialized = True
708
+ logger.info("SimpleRAGChain ์ดˆ๊ธฐํ™” ์„ฑ๊ณต")
709
+ except Exception as simple_e:
710
+ logger.error(f"๋ชจ๋“  RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์‹คํŒจ: {simple_e}", exc_info=True)
711
+ return f"๋ฌธ์„œ์™€ ๋ฒกํ„ฐ ์ธ๋ฑ์Šค๋Š” ์ฒ˜๋ฆฌ๋˜์—ˆ์œผ๋‚˜ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™”์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}"
712
  else:
713
+ # RAGChain์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ
714
  try:
715
+ logger.info("๊ธฐ๋ณธ RAG Chain์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์–ด ๋Œ€์ฒด ๋ฒ„์ „์„ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค...")
716
+
717
+ # FallbackRAGChain ์‹œ๋„
718
+ try:
719
+ from fallback_rag_chain import FallbackRAGChain
720
+ self.rag_chain = FallbackRAGChain(self.vector_store)
721
+ self.is_initialized = True
722
+ logger.info("ํด๋ฐฑ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์„ฑ๊ณต")
723
+ except Exception as fallback_e:
724
+ logger.error(f"ํด๋ฐฑ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์‹คํŒจ: {fallback_e}", exc_info=True)
725
+
726
+ # SimpleRAGChain ์‹œ๋„ (์ตœํ›„์˜ ์ˆ˜๋‹จ)
727
+ try:
728
+ from simple_rag_chain import SimpleRAGChain
729
+ self.rag_chain = SimpleRAGChain(self.vector_store)
730
+ self.is_initialized = True
731
+ logger.info("SimpleRAGChain ์ดˆ๊ธฐํ™” ์„ฑ๊ณต")
732
+ except Exception as simple_e:
733
+ logger.error(f"๋ชจ๋“  RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์‹คํŒจ: {simple_e}", exc_info=True)
734
+ return f"๋ฌธ์„œ์™€ ๋ฒกํ„ฐ ์ธ๋ฑ์Šค๋Š” ์ฒ˜๋ฆฌ๋˜์—ˆ์œผ๋‚˜ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™”์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค"
735
  except Exception as e:
736
+ logger.error(f"RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์‹คํŒจ: {e}", exc_info=True)
737
  return f"๋ฌธ์„œ์™€ ๋ฒกํ„ฐ ์ธ๋ฑ์Šค๋Š” ์ฒ˜๋ฆฌ๋˜์—ˆ์œผ๋‚˜ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™”์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}"
738
 
739
+ # ์„ฑ๊ณต ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ
740
+ result_message = f"""๋ฌธ์„œ ์ฒ˜๋ฆฌ ์™„๋ฃŒ!
741
+ - ์ฒ˜๋ฆฌ๋œ ํŒŒ์ผ: {len(pdf_files)}๊ฐœ
742
+ - ์บ์‹œ๋œ ํŒŒ์ผ: {len(cached_files)}๊ฐœ
743
+ - ์ƒˆ ํŒŒ์ผ: {len(new_files)}๊ฐœ
744
+ - ์—…๋ฐ์ดํŠธ๋œ ํŒŒ์ผ: {len(updated_files)}๊ฐœ
745
+ - ์‹คํŒจํ•œ ํŒŒ์ผ: {len(failed_files)}๊ฐœ
746
+ - ์ด ์ฒญํฌ ์ˆ˜: {len(all_chunks)}๊ฐœ
747
+ - ์ฒ˜๋ฆฌ ์‹œ๊ฐ„: {processing_time:.2f}์ดˆ
748
+ ์ด์ œ ์งˆ๋ฌธํ•  ์ค€๋น„๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!"""
749
+
750
+ return result_message
751
+
752
  except Exception as e:
753
  error_message = f"๋ฌธ์„œ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
754
  logger.error(error_message, exc_info=True)
config.py CHANGED
@@ -1,6 +1,6 @@
1
  """
2
  ๋ฒกํ„ฐ ์Šคํ† ์–ด, ์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ, LLM ๋“ฑ ๊ตฌ์„ฑ ์š”์†Œ ์„ค์ •
3
- ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋ฐ .env ํŒŒ์ผ ํ™œ์šฉ ๊ฐœ์„  ๋ฒ„์ „ - DeepSeek ์ง€์› ์ถ”๊ฐ€
4
  """
5
  import os
6
  import logging
@@ -21,26 +21,35 @@ logger.info(f"์Šคํฌ๋ฆฝํŠธ ๋””๋ ‰ํ† ๋ฆฌ: {script_dir}")
21
  logger.info(f"ํ˜„์žฌ ์ž‘์—… ๋””๋ ‰ํ† ๋ฆฌ: {os.getcwd()}")
22
  logger.info(f"์šด์˜ ์ฒด์ œ: {os.name}")
23
 
24
- # .env ํŒŒ์ผ ์œ„์น˜ ํ›„๋ณด๋“ค
25
- env_paths = [
26
- ".env", # ํ˜„์žฌ ๋””๋ ‰ํ† ๋ฆฌ
27
- os.path.join(script_dir, ".env"), # ์Šคํฌ๋ฆฝํŠธ ๋””๋ ‰ํ† ๋ฆฌ
28
- os.path.join(script_dir, "config", ".env"), # config ํ•˜์œ„ ๋””๋ ‰ํ† ๋ฆฌ
29
- os.path.join(os.path.dirname(script_dir), ".env"), # ์ƒ์œ„ ๋””๋ ‰ํ† ๋ฆฌ
30
- ]
31
-
32
- # .env ํŒŒ์ผ ์ฐพ์•„์„œ ๋กœ๋“œ
33
- env_loaded = False
34
- for env_path in env_paths:
35
- if os.path.isfile(env_path):
36
- logger.info(f".env ํŒŒ์ผ ๋ฐœ๊ฒฌ: {env_path}")
37
- env_loaded = load_dotenv(env_path, verbose=True)
38
- if env_loaded:
39
- logger.info(f".env ํŒŒ์ผ ๋กœ๋“œ ์„ฑ๊ณต: {env_path}")
40
- break
41
-
42
- if not env_loaded:
 
 
 
 
 
43
  logger.warning(".env ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’ ๋˜๋Š” ์‹œ์Šคํ…œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.")
 
 
 
 
44
 
45
  # ํ™˜๊ฒฝ ๊ฐ์ง€
46
  IS_HUGGINGFACE = os.getenv('SPACE_ID') is not None
@@ -143,8 +152,8 @@ CHUNK_OVERLAP = int(get_env("CHUNK_OVERLAP", "200"))
143
 
144
  # API ํ‚ค ๋ฐ ํ™˜๊ฒฝ ์„ค์ •
145
  OPENAI_API_KEY = get_env("OPENAI_API_KEY", "")
146
- LANGFUSE_PUBLIC_KEY = get_env("LANGFUSE_PUBLIC_KEY", "")
147
- LANGFUSE_SECRET_KEY = get_env("LANGFUSE_SECRET_KEY", "")
148
  LANGFUSE_HOST = get_env("LANGFUSE_HOST", "https://cloud.langfuse.com")
149
 
150
  # DeepSeek ๊ด€๋ จ ์„ค์ • ์ถ”๊ฐ€
@@ -165,6 +174,9 @@ RERANKER_MODEL = get_env("RERANKER_MODEL", "Alibaba-NLP/gte-multilingual-reranke
165
  USE_OPENAI = get_env("USE_OPENAI", "False").lower() == "true"
166
  USE_DEEPSEEK = get_env("USE_DEEPSEEK", "False").lower() == "true"
167
 
 
 
 
168
  # DeepSeek API ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜
169
  def test_deepseek_connection():
170
  """
@@ -301,7 +313,6 @@ else:
301
  logger.info(f"OpenAI ๋ชจ๋ธ ์‚ฌ์šฉ")
302
  else:
303
  LLM_MODEL = get_env("LLM_MODEL", "gemma3:latest")
304
- OLLAMA_HOST = get_env("OLLAMA_HOST", "http://localhost:11434")
305
  logger.info(f"Ollama ๋ชจ๋ธ ์‚ฌ์šฉ")
306
 
307
  # API ํ‚ค ๊ฒ€์ฆ
@@ -336,8 +347,7 @@ def print_config():
336
  logger.info(f"OpenAI ์‚ฌ์šฉ: {USE_OPENAI}")
337
  logger.info(f"DeepSeek ์‚ฌ์šฉ: {USE_DEEPSEEK}")
338
  logger.info(f"LLM ๋ชจ๋ธ: {LLM_MODEL}")
339
- if not USE_OPENAI and not USE_DEEPSEEK:
340
- logger.info(f"Ollama ํ˜ธ์ŠคํŠธ: {OLLAMA_HOST}")
341
  logger.info(f"์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ: {EMBEDDING_MODEL}")
342
  logger.info(f"๋ฆฌ๋žญ์ปค ๋ชจ๋ธ: {RERANKER_MODEL}")
343
  logger.info(f"TOP_K ๊ฒ€์ƒ‰: {TOP_K_RETRIEVAL}, ๋ฆฌ๋žญํ‚น: {TOP_K_RERANK}")
@@ -410,7 +420,6 @@ def list_directory_contents():
410
  except Exception as e:
411
  logger.error(f"PDF ๋””๋ ‰ํ† ๋ฆฌ ๋‚ด์šฉ ํ™•์ธ ์‹คํŒจ: {e}")
412
 
413
-
414
  # ์ง์ ‘ ์ฃผ์–ด์ง„ ๊ฒฝ๋กœ์—์„œ PDF ์ฐพ๊ธฐ (๋””๋ฒ„๊น…์šฉ)
415
  def find_pdf_files_in_path(path: str) -> list:
416
  """
@@ -434,7 +443,6 @@ def find_pdf_files_in_path(path: str) -> list:
434
  logger.error(f"๊ฒฝ๋กœ '{path}'์—์„œ PDF ํŒŒ์ผ ๊ฒ€์ƒ‰ ์ค‘ ์˜ค๋ฅ˜: {e}")
435
  return []
436
 
437
-
438
  # ์œˆ๋„์šฐ์ฆˆ ์ฃผ์š” ๊ฒฝ๋กœ์—์„œ PDF ํŒŒ์ผ ๊ฒ€์ƒ‰ (๋””๋ฒ„๊น…์šฉ)
439
  def find_pdfs_in_windows_paths():
440
  """์œˆ๋„์šฐ์ฆˆ์—์„œ ์ฃผ์š” ๊ฒฝ๋กœ์— PDF ํŒŒ์ผ์ด ์žˆ๋Š”์ง€ ํ™•์ธ"""
@@ -454,7 +462,6 @@ def find_pdfs_in_windows_paths():
454
  for path in common_paths:
455
  find_pdf_files_in_path(path)
456
 
457
-
458
  # ์„ค์ • ์ •๋ณด ์ถœ๋ ฅ ๋ฐ ๊ฒ€์ฆ (๋ชจ๋“ˆ ์ž„ํฌํŠธ ์‹œ ์‹คํ–‰)
459
  print_config()
460
  config_status = validate_config()
 
1
  """
2
  ๋ฒกํ„ฐ ์Šคํ† ์–ด, ์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ, LLM ๋“ฑ ๊ตฌ์„ฑ ์š”์†Œ ์„ค์ •
3
+ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋ฐ .env ํŒŒ์ผ ํ™œ์šฉ ๊ฐœ์„  ๋ฒ„์ „ - ํƒ์ƒ‰ ์†๋„ ์ตœ์ ํ™”
4
  """
5
  import os
6
  import logging
 
21
  logger.info(f"ํ˜„์žฌ ์ž‘์—… ๋””๋ ‰ํ† ๋ฆฌ: {os.getcwd()}")
22
  logger.info(f"์šด์˜ ์ฒด์ œ: {os.name}")
23
 
24
+ # .env ํŒŒ์ผ ๊ฒ€์ƒ‰ ์ตœ์ ํ™”
25
+ def fast_env_load():
26
+ """
27
+ .env ํŒŒ์ผ์„ ๋น ๋ฅด๊ฒŒ ์ฐพ์•„ ๋กœ๋“œํ•˜๋Š” ํ•จ์ˆ˜
28
+
29
+ Returns:
30
+ bool: ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋กœ๋“œ ์„ฑ๊ณต ์—ฌ๋ถ€
31
+ """
32
+ # .env ํŒŒ์ผ ์œ„์น˜ ํ›„๋ณด๋“ค (.env ํŒŒ์ผ์€ ์ผ๋ฐ˜์ ์œผ๋กœ ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ์— ์žˆ์Œ)
33
+ env_paths = [
34
+ ".env", # ํ˜„์žฌ ๋””๋ ‰ํ† ๋ฆฌ
35
+ os.path.join(script_dir, ".env"), # ์Šคํฌ๋ฆฝํŠธ ๋””๋ ‰ํ† ๋ฆฌ
36
+ ]
37
+
38
+ # ์œ„์—์„œ ๋น ๋ฅด๊ฒŒ ์ˆœํšŒํ•˜๋ฉฐ ์ฐพ๊ธฐ
39
+ for env_path in env_paths:
40
+ if os.path.isfile(env_path):
41
+ logger.info(f".env ํŒŒ์ผ ๋ฐœ๊ฒฌ: {env_path}")
42
+ loaded = load_dotenv(env_path, verbose=False, override=True)
43
+ if loaded:
44
+ logger.info(f".env ํŒŒ์ผ ๋กœ๋“œ ์„ฑ๊ณต: {env_path}")
45
+ return True
46
+
47
+ # ๊ฒ€์ƒ‰ ์‹คํŒจ ์‹œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์‚ฌ์šฉ ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ
48
  logger.warning(".env ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’ ๋˜๋Š” ์‹œ์Šคํ…œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.")
49
+ return False
50
+
51
+ # ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋กœ๋“œ
52
+ env_loaded = fast_env_load()
53
 
54
  # ํ™˜๊ฒฝ ๊ฐ์ง€
55
  IS_HUGGINGFACE = os.getenv('SPACE_ID') is not None
 
152
 
153
  # API ํ‚ค ๋ฐ ํ™˜๊ฒฝ ์„ค์ •
154
  OPENAI_API_KEY = get_env("OPENAI_API_KEY", "")
155
+ LANGFUSE_PUBLIC_KEY = get_env("LANGFUSE_PUBLIC_KEY", "pk-lf-cd6248e2-59ad-496d-a4cb-487bb3ecfcd5")
156
+ LANGFUSE_SECRET_KEY = get_env("LANGFUSE_SECRET_KEY", "sk-lf-61460a1d-e637-4c22-b5e9-9250ac2579ba")
157
  LANGFUSE_HOST = get_env("LANGFUSE_HOST", "https://cloud.langfuse.com")
158
 
159
  # DeepSeek ๊ด€๋ จ ์„ค์ • ์ถ”๊ฐ€
 
174
  USE_OPENAI = get_env("USE_OPENAI", "False").lower() == "true"
175
  USE_DEEPSEEK = get_env("USE_DEEPSEEK", "False").lower() == "true"
176
 
177
+ # Ollama ํ˜ธ์ŠคํŠธ ์„ค์ • (๊ธฐ๋ณธ๊ฐ’)
178
+ OLLAMA_HOST = get_env("OLLAMA_HOST", "http://localhost:11434")
179
+
180
  # DeepSeek API ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜
181
  def test_deepseek_connection():
182
  """
 
313
  logger.info(f"OpenAI ๋ชจ๋ธ ์‚ฌ์šฉ")
314
  else:
315
  LLM_MODEL = get_env("LLM_MODEL", "gemma3:latest")
 
316
  logger.info(f"Ollama ๋ชจ๋ธ ์‚ฌ์šฉ")
317
 
318
  # API ํ‚ค ๊ฒ€์ฆ
 
347
  logger.info(f"OpenAI ์‚ฌ์šฉ: {USE_OPENAI}")
348
  logger.info(f"DeepSeek ์‚ฌ์šฉ: {USE_DEEPSEEK}")
349
  logger.info(f"LLM ๋ชจ๋ธ: {LLM_MODEL}")
350
+ logger.info(f"Ollama ํ˜ธ์ŠคํŠธ: {OLLAMA_HOST}")
 
351
  logger.info(f"์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ: {EMBEDDING_MODEL}")
352
  logger.info(f"๋ฆฌ๋žญ์ปค ๋ชจ๋ธ: {RERANKER_MODEL}")
353
  logger.info(f"TOP_K ๊ฒ€์ƒ‰: {TOP_K_RETRIEVAL}, ๋ฆฌ๋žญํ‚น: {TOP_K_RERANK}")
 
420
  except Exception as e:
421
  logger.error(f"PDF ๋””๋ ‰ํ† ๋ฆฌ ๋‚ด์šฉ ํ™•์ธ ์‹คํŒจ: {e}")
422
 
 
423
  # ์ง์ ‘ ์ฃผ์–ด์ง„ ๊ฒฝ๋กœ์—์„œ PDF ์ฐพ๊ธฐ (๋””๋ฒ„๊น…์šฉ)
424
  def find_pdf_files_in_path(path: str) -> list:
425
  """
 
443
  logger.error(f"๊ฒฝ๋กœ '{path}'์—์„œ PDF ํŒŒ์ผ ๊ฒ€์ƒ‰ ์ค‘ ์˜ค๋ฅ˜: {e}")
444
  return []
445
 
 
446
  # ์œˆ๋„์šฐ์ฆˆ ์ฃผ์š” ๊ฒฝ๋กœ์—์„œ PDF ํŒŒ์ผ ๊ฒ€์ƒ‰ (๋””๋ฒ„๊น…์šฉ)
447
  def find_pdfs_in_windows_paths():
448
  """์œˆ๋„์šฐ์ฆˆ์—์„œ ์ฃผ์š” ๊ฒฝ๋กœ์— PDF ํŒŒ์ผ์ด ์žˆ๋Š”์ง€ ํ™•์ธ"""
 
462
  for path in common_paths:
463
  find_pdf_files_in_path(path)
464
 
 
465
  # ์„ค์ • ์ •๋ณด ์ถœ๋ ฅ ๋ฐ ๊ฒ€์ฆ (๋ชจ๋“ˆ ์ž„ํฌํŠธ ์‹œ ์‹คํ–‰)
466
  print_config()
467
  config_status = validate_config()
custom_rag_chain.py ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ DeepSeek API๋ฅผ ํ™œ์šฉํ•œ ์ปค์Šคํ…€ RAG ์ฒด์ธ ๊ตฌํ˜„
3
+ """
4
+ import os
5
+ import logging
6
+ import time
7
+ from typing import List, Dict, Any, Optional, Tuple
8
+
9
+ from langchain.schema import Document
10
+ from langchain.prompts import PromptTemplate
11
+ from langchain_core.output_parsers import StrOutputParser
12
+ from langchain_core.runnables import RunnablePassthrough
13
+
14
+ # DeepSeek ์ปค์Šคํ…€ LLM ์ž„ํฌํŠธ
15
+ from deepseek_llm import DeepSeekLLM, DeepSeekChat
16
+
17
+ # ์„ค์ • ๊ฐ€์ ธ์˜ค๊ธฐ
18
+ try:
19
+ from config import (
20
+ DEEPSEEK_API_KEY, DEEPSEEK_MODEL, DEEPSEEK_ENDPOINT,
21
+ TOP_K_RETRIEVAL, TOP_K_RERANK
22
+ )
23
+ except ImportError:
24
+ # ์„ค์ • ๋ชจ๋“ˆ์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ ๊ธฐ๋ณธ๊ฐ’ ์„ค์ •
25
+ DEEPSEEK_API_KEY = os.environ.get("DEEPSEEK_API_KEY", "")
26
+ DEEPSEEK_MODEL = os.environ.get("DEEPSEEK_MODEL", "deepseek-chat")
27
+ DEEPSEEK_ENDPOINT = os.environ.get("DEEPSEEK_ENDPOINT", "https://api.deepseek.com/v1/chat/completions")
28
+ TOP_K_RETRIEVAL = int(os.environ.get("TOP_K_RETRIEVAL", "5"))
29
+ TOP_K_RERANK = int(os.environ.get("TOP_K_RERANK", "3"))
30
+
31
+ # ๋กœ๊น… ์„ค์ •
32
+ logger = logging.getLogger("CustomRAGChain")
33
+
34
+
35
+ class CustomRAGChain:
36
+ """
37
+ DeepSeek API๋ฅผ ํ™œ์šฉํ•œ ์ปค์Šคํ…€ RAG ์ฒด์ธ
38
+ """
39
+
40
+ def __init__(self, vector_store, use_reranker=False):
41
+ """
42
+ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™”
43
+
44
+ Args:
45
+ vector_store: ๋ฒกํ„ฐ ์Šคํ† ์–ด ์ธ์Šคํ„ด์Šค
46
+ use_reranker: ๋ฆฌ๋žญ์ปค ์‚ฌ์šฉ ์—ฌ๋ถ€ (ํ˜„์žฌ ๋ฏธ์ง€์›)
47
+ """
48
+ logger.info("์ปค์Šคํ…€ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™”...")
49
+ self.vector_store = vector_store
50
+ self.use_reranker = use_reranker
51
+
52
+ # API ํ‚ค ํ™•์ธ
53
+ if not DEEPSEEK_API_KEY:
54
+ logger.error("DeepSeek API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
55
+ raise ValueError("DeepSeek API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
56
+
57
+ # DeepSeek LLM ์ดˆ๊ธฐํ™”
58
+ try:
59
+ self.llm = DeepSeekLLM(
60
+ api_key=DEEPSEEK_API_KEY,
61
+ model=DEEPSEEK_MODEL,
62
+ endpoint=DEEPSEEK_ENDPOINT,
63
+ temperature=0.3,
64
+ max_tokens=1000,
65
+ request_timeout=120,
66
+ max_retries=5
67
+ )
68
+ logger.info(f"DeepSeek LLM ์ดˆ๊ธฐํ™” ์„ฑ๊ณต: {DEEPSEEK_MODEL}")
69
+ except Exception as e:
70
+ logger.error(f"DeepSeek LLM ์ดˆ๊ธฐํ™” ์‹คํŒจ: {e}")
71
+ raise ValueError(f"DeepSeek LLM ์ดˆ๊ธฐํ™” ์‹คํŒจ: {str(e)}")
72
+
73
+ # ์ฑ— ์ธํ„ฐํŽ˜์ด์Šค ์ดˆ๊ธฐํ™” (๋Œ€์ฒด์šฉ)
74
+ self.chat = DeepSeekChat(
75
+ api_key=DEEPSEEK_API_KEY,
76
+ model=DEEPSEEK_MODEL,
77
+ endpoint=DEEPSEEK_ENDPOINT
78
+ )
79
+
80
+ # RAG ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ
81
+ self.prompt = PromptTemplate.from_template("""
82
+ ๋‹ค์Œ ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์งˆ๋ฌธ์— ์ •ํ™•ํ•˜๊ฒŒ ๋‹ต๋ณ€ํ•ด์ฃผ์„ธ์š”.
83
+
84
+ ์งˆ๋ฌธ: {question}
85
+
86
+ ์ฐธ๊ณ  ์ •๋ณด:
87
+ {context}
88
+
89
+ ์ฐธ๊ณ  ์ •๋ณด์— ๋‹ต์ด ์žˆ์œผ๋ฉด ๋ฐ˜๋“œ์‹œ ๊ทธ ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋‹ต๋ณ€ํ•˜์„ธ์š”.
90
+ ์ฐธ๊ณ  ์ •๋ณด์— ๋‹ต์ด ์—†๋Š” ๊ฒฝ์šฐ์—๋Š” ์ผ๋ฐ˜์ ์ธ ์ง€์‹์„ ํ™œ์šฉํ•˜์—ฌ ๋‹ต๋ณ€ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, "์ œ๊ณต๋œ ๋ฌธ์„œ์—๋Š” ์ด ์ •๋ณด๊ฐ€ ์—†์œผ๋‚˜, ์ผ๋ฐ˜์ ์œผ๋กœ๋Š”..." ์‹์œผ๋กœ ์‹œ์ž‘ํ•˜์„ธ์š”.
91
+ ๋‹ต๋ณ€์€ ์ •ํ™•ํ•˜๊ณ  ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ œ๊ณตํ•˜๋˜, ๊ฐ€๋Šฅํ•œ ์ฐธ๊ณ  ์ •๋ณด์—์„œ ๊ทผ๊ฑฐ๋ฅผ ์ฐพ์•„ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”.
92
+ ์ฐธ๊ณ  ์ •๋ณด์˜ ์ถœ์ฒ˜๋„ ํ•จ๊ป˜ ์•Œ๋ ค์ฃผ์„ธ์š”.
93
+ """)
94
+
95
+ # RAG ์ฒด์ธ ๊ตฌ์„ฑ
96
+ self.chain = (
97
+ {"context": self._retrieve, "question": RunnablePassthrough()}
98
+ | self.prompt
99
+ | self.llm
100
+ | StrOutputParser()
101
+ )
102
+
103
+ logger.info("์ปค์Šคํ…€ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ")
104
+
105
+ def _retrieve(self, query: str) -> str:
106
+ """
107
+ ์ฟผ๋ฆฌ์— ๋Œ€ํ•œ ๊ด€๋ จ ๋ฌธ์„œ ๊ฒ€์ƒ‰ ๋ฐ ์ปจํ…์ŠคํŠธ ๊ตฌ์„ฑ
108
+
109
+ Args:
110
+ query: ์‚ฌ์šฉ์ž ์งˆ๋ฌธ
111
+
112
+ Returns:
113
+ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ํฌํ•จํ•œ ์ปจํ…์ŠคํŠธ ๋ฌธ์ž์—ด
114
+ """
115
+ if not query or not query.strip():
116
+ logger.warning("๋นˆ ์ฟผ๋ฆฌ๋กœ ๊ฒ€์ƒ‰ ์‹œ๋„")
117
+ return "๊ฒ€์ƒ‰ ์ฟผ๋ฆฌ๊ฐ€ ๋น„์–ด์žˆ์Šต๋‹ˆ๋‹ค."
118
+
119
+ try:
120
+ # ๋ฒกํ„ฐ ๊ฒ€์ƒ‰ ์ˆ˜ํ–‰
121
+ logger.info(f"๋ฒกํ„ฐ ๊ฒ€์ƒ‰ ์ˆ˜ํ–‰: '{query[:50]}{'...' if len(query) > 50 else ''}'")
122
+ docs = self.vector_store.similarity_search(query, k=TOP_K_RETRIEVAL)
123
+
124
+ if not docs:
125
+ logger.warning("๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค")
126
+ return "๊ด€๋ จ ๋ฌธ์„œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."
127
+
128
+ # ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ปจํ…์ŠคํŠธ ๊ตฌ์„ฑ
129
+ context_parts = []
130
+ for i, doc in enumerate(docs, 1):
131
+ source = doc.metadata.get("source", "์•Œ ์ˆ˜ ์—†๋Š” ์ถœ์ฒ˜")
132
+ page = doc.metadata.get("page", "")
133
+ source_info = f"{source}"
134
+ if page:
135
+ source_info += f" (ํŽ˜์ด์ง€: {page})"
136
+
137
+ context_parts.append(f"[์ฐธ๊ณ ์ž๋ฃŒ {i}] - ์ถœ์ฒ˜: {source_info}\n{doc.page_content}\n")
138
+
139
+ context = "\n".join(context_parts)
140
+
141
+ # ๏ฟฝ๏ฟฝํ…์ŠคํŠธ ๊ธธ์ด ์ œํ•œ (ํ† ํฐ ์ˆ˜ ์ œํ•œ)
142
+ if len(context) > 6000:
143
+ logger.warning(f"์ปจํ…์ŠคํŠธ๊ฐ€ ๋„ˆ๋ฌด ๊น๋‹ˆ๋‹ค ({len(context)} ๋ฌธ์ž). ์ œํ•œํ•ฉ๋‹ˆ๋‹ค.")
144
+ context = context[:2500] + "\n...(์ค‘๋žต)...\n" + context[-2500:]
145
+
146
+ logger.info(f"์ปจํ…์ŠคํŠธ ์ƒ์„ฑ ์™„๋ฃŒ: {len(context_parts)}๊ฐœ ๋ฌธ์„œ, {len(context)} ๋ฌธ์ž")
147
+ return context
148
+
149
+ except Exception as e:
150
+ logger.error(f"๊ฒ€์ƒ‰ ์ค‘ ์˜ค๋ฅ˜: {e}")
151
+ return f"๊ฒ€์ƒ‰ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
152
+
153
+ def run(self, query: str) -> str:
154
+ """
155
+ ์‚ฌ์šฉ์ž ์ฟผ๋ฆฌ์— ๋Œ€ํ•œ RAG ํŒŒ์ดํ”„๋ผ์ธ ์‹คํ–‰
156
+
157
+ Args:
158
+ query: ์‚ฌ์šฉ์ž ์งˆ๋ฌธ
159
+
160
+ Returns:
161
+ ๋ชจ๋ธ ์‘๋‹ต ๋ฌธ์ž์—ด
162
+ """
163
+ if not query or not query.strip():
164
+ logger.warning("๋นˆ ์ฟผ๋ฆฌ๋กœ ์‹คํ–‰ ์‹œ๋„")
165
+ return "์งˆ๋ฌธ์ด ๋น„์–ด์žˆ์Šต๋‹ˆ๋‹ค. ์งˆ๋ฌธ์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”."
166
+
167
+ try:
168
+ logger.info(f"RAG ์ฒด์ธ ์‹คํ–‰: '{query[:50]}{'...' if len(query) > 50 else ''}'")
169
+ start_time = time.time()
170
+
171
+ # ๋ฒกํ„ฐ ๊ฒ€์ƒ‰ ์‹คํ–‰
172
+ context = self._retrieve(query)
173
+
174
+ # ์ง์ ‘ LLM ํ˜ธ์ถœ (์ฒด์ธ ์‚ฌ์šฉ)
175
+ try:
176
+ response = self.chain.invoke(query)
177
+ logger.info(f"LangChain ์ฒด์ธ ํ˜ธ์ถœ ์„ฑ๊ณต")
178
+ except Exception as chain_error:
179
+ logger.error(f"์ฒด์ธ ํ˜ธ์ถœ ์‹คํŒจ: {chain_error}, ๋Œ€์ฒด ๋ฐฉ์‹ ์‹œ๋„")
180
+
181
+ # ๋Œ€์ฒด ๋ฐฉ์‹: ์ง์ ‘ ์ฑ„ํŒ… API ํ˜ธ์ถœ
182
+ try:
183
+ prompt = self.prompt.format(question=query, context=context)
184
+ response = self.chat.generate([{"role": "user", "content": prompt}])
185
+ logger.info("๋Œ€์ฒด ์ฑ„ํŒ… API ํ˜ธ์ถœ ์„ฑ๊ณต")
186
+ except Exception as chat_error:
187
+ logger.error(f"๋Œ€์ฒด ์ฑ„ํŒ… API ํ˜ธ์ถœ ์‹คํŒจ: {chat_error}")
188
+
189
+ # ๋ฏธ๋ฆฌ ์ •์˜๋œ ์‘๋‹ต์œผ๋กœ ํด๋ฐฑ
190
+ predefined_answers = {
191
+ "๋Œ€ํ•œ๋ฏผ๊ตญ์˜ ์ˆ˜๋„": "๋Œ€ํ•œ๋ฏผ๊ตญ์˜ ์ˆ˜๋„๋Š” ์„œ์šธ์ž…๋‹ˆ๋‹ค.",
192
+ "์ˆ˜๋„": "๋Œ€ํ•œ๋ฏผ๊ตญ์˜ ์ˆ˜๋„๋Š” ์„œ์šธ์ž…๋‹ˆ๋‹ค.",
193
+ "๋ˆ„๊ตฌ์•ผ": "์ €๋Š” RAG ๊ธฐ๋ฐ˜ ์งˆ์˜์‘๋‹ต ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค. ๋ฌธ์„œ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๊ณ  ๊ด€๋ จ ์ •๋ณด๋ฅผ ์ฐพ์•„๋“œ๋ฆฝ๋‹ˆ๋‹ค.",
194
+ "์•ˆ๋…•": "์•ˆ๋…•ํ•˜์„ธ์š”! ๋ฌด์—‡์„ ๋„์™€๋“œ๋ฆด๊นŒ์š”?",
195
+ "๋ญํ•ด": "์‚ฌ์šฉ์ž์˜ ์งˆ๋ฌธ์— ๋‹ต๋ณ€ํ•˜๊ธฐ ์œ„ํ•ด ๋ฌธ์„œ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฌด์—‡์„ ์•Œ๋ ค๋“œ๋ฆด๊นŒ์š”?"
196
+ }
197
+
198
+ # ์งˆ๋ฌธ์— ๋งž๋Š” ๋ฏธ๋ฆฌ ์ •์˜๋œ ์‘๋‹ต์ด ์žˆ๋Š”์ง€ ํ™•์ธ
199
+ for key, answer in predefined_answers.items():
200
+ if key in query.lower():
201
+ response = answer
202
+ logger.info(f"๋ฏธ๋ฆฌ ์ •์˜๋œ ์‘๋‹ต ์ œ๊ณต: {key}")
203
+ break
204
+ else:
205
+ # ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋งŒ ํ‘œ์‹œ
206
+ response = f"""
207
+ API ์—ฐ๊ฒฐ ์˜ค๋ฅ˜๋กœ ์ธํ•ด ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋งŒ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.
208
+
209
+ ์งˆ๋ฌธ: {query}
210
+
211
+ ๊ฒ€์ƒ‰๋œ ๊ด€๋ จ ๋ฌธ์„œ:
212
+ {context}
213
+
214
+ [์ฐธ๊ณ ] API ์—ฐ๊ฒฐ ๋ฌธ์ œ๋กœ ์ธํ•ด ์ž๋™ ์š”์•ฝ์ด ์ œ๊ณต๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ์‹œ๋„ํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ์งˆ๋ฌธ์„ ํ•ด๋ณด์„ธ์š”.
215
+ """
216
+ logger.info("๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋งŒ ํ‘œ์‹œ")
217
+
218
+ end_time = time.time()
219
+ logger.info(f"RAG ์ฒด์ธ ์‹คํ–‰ ์™„๋ฃŒ: {end_time - start_time:.2f}์ดˆ")
220
+ return response
221
+
222
+ except Exception as e:
223
+ logger.error(f"RAG ์ฒด์ธ ์‹คํ–‰ ์ค‘ ์˜ค๋ฅ˜: {e}")
224
+ return f"์งˆ๋ฌธ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}"
direct_deepseek.py ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ์ง์ ‘ DeepSeek API ํ˜ธ์ถœ์„ ์œ„ํ•œ ํด๋ผ์ด์–ธํŠธ ๊ตฌํ˜„
3
+ """
4
+ import os
5
+ import time
6
+ import logging
7
+ import requests
8
+ import json
9
+ from typing import Dict, Any, Optional, List
10
+
11
+ # ๋กœ๊น… ์„ค์ •
12
+ logger = logging.getLogger("DirectDeepSeek")
13
+
14
+ class DirectDeepSeekClient:
15
+ """
16
+ DeepSeek API๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜๋Š” ํด๋ผ์ด์–ธํŠธ
17
+ OpenAI ํด๋ผ์ด์–ธํŠธ๋ฅผ ์šฐํšŒํ•˜๊ณ  ์ง์ ‘ HTTP ์š”์ฒญ ์‚ฌ์šฉ
18
+ """
19
+ def __init__(self, api_key: str, model_name: str = "deepseek-chat"):
20
+ """
21
+ ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™”
22
+
23
+ Args:
24
+ api_key: DeepSeek API ํ‚ค
25
+ model_name: ์‚ฌ์šฉํ•  ๋ชจ๋ธ ์ด๋ฆ„ (๊ธฐ๋ณธ๊ฐ’: "deepseek-chat")
26
+ """
27
+ self.api_key = api_key
28
+ self.model_name = model_name
29
+ self.endpoint = os.getenv("DEEPSEEK_ENDPOINT", "https://api.deepseek.com/v1/chat/completions")
30
+ logger.info(f"DirectDeepSeekClient ์ดˆ๊ธฐํ™”: ๋ชจ๋ธ={model_name}, ์—”๋“œํฌ์ธํŠธ={self.endpoint}")
31
+
32
+ def generate(self,
33
+ prompt: str,
34
+ temperature: float = 0.3,
35
+ max_tokens: int = 1000,
36
+ max_retries: int = 3,
37
+ timeout: int = 60) -> Dict[str, Any]:
38
+ """
39
+ ํ…์ŠคํŠธ ์ƒ์„ฑ ์š”์ฒญ
40
+
41
+ Args:
42
+ prompt: ์ž…๋ ฅ ํ”„๋กฌํ”„ํŠธ
43
+ temperature: ์ƒ์„ฑ ์˜จ๋„ (0.0 ~ 1.0)
44
+ max_tokens: ์ตœ๋Œ€ ์ƒ์„ฑ ํ† ํฐ ์ˆ˜
45
+ max_retries: ์žฌ์‹œ๋„ ํšŸ์ˆ˜
46
+ timeout: ์š”์ฒญ ํƒ€์ž„์•„์›ƒ (์ดˆ)
47
+
48
+ Returns:
49
+ ์ƒ์„ฑ ๊ฒฐ๊ณผ ๋”•์…”๋„ˆ๋ฆฌ (success, response, message ๋“ฑ)
50
+ """
51
+ # ๋ฉ”์‹œ์ง€ ๊ตฌ์„ฑ (๋‹จ์ผ ์‚ฌ์šฉ์ž ๋ฉ”์‹œ์ง€)
52
+ messages = [{"role": "user", "content": prompt}]
53
+ return self.chat(messages, temperature, max_tokens, max_retries, timeout)
54
+
55
+ def chat(self,
56
+ messages: List[Dict[str, str]],
57
+ temperature: float = 0.3,
58
+ max_tokens: int = 1000,
59
+ max_retries: int = 3,
60
+ timeout: int = 60) -> Dict[str, Any]:
61
+ """
62
+ ์ฑ„ํŒ… API ํ˜ธ์ถœ
63
+
64
+ Args:
65
+ messages: ์ฑ„ํŒ… ๋ฉ”์‹œ์ง€ ๋ฆฌ์ŠคํŠธ (role, content ํ‚ค๋ฅผ ๊ฐ€์ง„ ๋”•์…”๋„ˆ๋ฆฌ ๋ฆฌ์ŠคํŠธ)
66
+ temperature: ์ƒ์„ฑ ์˜จ๋„ (0.0 ~ 1.0)
67
+ max_tokens: ์ตœ๋Œ€ ์ƒ์„ฑ ํ† ํฐ ์ˆ˜
68
+ max_retries: ์žฌ์‹œ๋„ ํšŸ์ˆ˜
69
+ timeout: ์š”์ฒญ ํƒ€์ž„์•„์›ƒ (์ดˆ)
70
+
71
+ Returns:
72
+ ์ƒ์„ฑ ๊ฒฐ๊ณผ ๋”•์…”๋„ˆ๋ฆฌ (success, response, message ๋“ฑ)
73
+ """
74
+ # API ์š”์ฒญ ํ—ค๋” ๋ฐ ๋ฐ์ดํ„ฐ
75
+ headers = {
76
+ "Content-Type": "application/json",
77
+ "Authorization": f"Bearer {self.api_key}"
78
+ }
79
+
80
+ payload = {
81
+ "model": self.model_name,
82
+ "messages": messages,
83
+ "temperature": temperature,
84
+ "max_tokens": max_tokens
85
+ }
86
+
87
+ # ์žฌ์‹œ๋„ ๋กœ์ง
88
+ retry_delay = 1.0
89
+ attempt = 0
90
+
91
+ while attempt < max_retries:
92
+ attempt += 1
93
+ try:
94
+ logger.info(f"DeepSeek API ์š”์ฒญ ์‹œ๋„ ({attempt}/{max_retries})...")
95
+
96
+ # API ์š”์ฒญ ์ „์†ก
97
+ response = requests.post(
98
+ self.endpoint,
99
+ headers=headers,
100
+ json=payload,
101
+ timeout=timeout
102
+ )
103
+
104
+ # ์‘๋‹ต ํ™•์ธ
105
+ if response.status_code == 200:
106
+ result = response.json()
107
+
108
+ # ์‘๋‹ต ๋‚ด์šฉ ์ถ”์ถœ
109
+ if "choices" in result and len(result["choices"]) > 0:
110
+ message_content = result["choices"][0].get("message", {}).get("content", "")
111
+ logger.info(f"DeepSeek API ์‘๋‹ต ์„ฑ๊ณต (๊ธธ์ด: {len(message_content)})")
112
+
113
+ return {
114
+ "success": True,
115
+ "response": message_content,
116
+ "status_code": response.status_code,
117
+ "raw_response": result
118
+ }
119
+ else:
120
+ logger.warning(f"DeepSeek API ์‘๋‹ต์€ ์„ฑ๊ณตํ–ˆ์œผ๋‚˜ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์‘๋‹ต ํ˜•์‹: {result}")
121
+ return {
122
+ "success": False,
123
+ "message": "์‘๋‹ต์—์„œ ๋ฉ”์‹œ์ง€๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
124
+ "status_code": response.status_code,
125
+ "raw_response": result
126
+ }
127
+ else:
128
+ logger.error(f"DeepSeek API ์˜ค๋ฅ˜: ์ƒํƒœ ์ฝ”๋“œ {response.status_code}")
129
+
130
+ # ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€ ์ถ”์ถœ
131
+ error_message = ""
132
+ try:
133
+ error_data = response.json()
134
+ error_message = error_data.get("error", {}).get("message", str(error_data))
135
+ except:
136
+ error_message = response.text
137
+
138
+ # ์š”์ฒญ ํ•œ๋„ ๏ฟฝ๏ฟฝ๊ณผ์‹œ ๋” ์˜ค๋ž˜ ๋Œ€๊ธฐ
139
+ if response.status_code == 429:
140
+ retry_delay = min(retry_delay * 3, 15)
141
+ else:
142
+ retry_delay = min(retry_delay * 2, 10)
143
+
144
+ if attempt < max_retries:
145
+ logger.info(f"{retry_delay}์ดˆ ํ›„ ์žฌ์‹œ๋„...")
146
+ time.sleep(retry_delay)
147
+ else:
148
+ # ๋ชจ๋“  ์‹œ๋„ ์‹คํŒจ
149
+ return {
150
+ "success": False,
151
+ "message": f"API ์˜ค๋ฅ˜: {error_message}",
152
+ "status_code": response.status_code
153
+ }
154
+
155
+ except requests.exceptions.Timeout:
156
+ logger.error("DeepSeek API ์š”์ฒญ ์‹œ๊ฐ„ ์ดˆ๊ณผ")
157
+
158
+ if attempt < max_retries:
159
+ logger.info(f"{retry_delay}์ดˆ ํ›„ ์žฌ์‹œ๋„...")
160
+ time.sleep(retry_delay)
161
+ retry_delay = min(retry_delay * 2, 10)
162
+ else:
163
+ return {
164
+ "success": False,
165
+ "message": "API ์š”์ฒญ ์‹œ๊ฐ„ ์ดˆ๊ณผ",
166
+ "status_code": None
167
+ }
168
+
169
+ except requests.exceptions.ConnectionError:
170
+ logger.error("DeepSeek API ์—ฐ๊ฒฐ ์‹คํŒจ")
171
+
172
+ if attempt < max_retries:
173
+ logger.info(f"{retry_delay}์ดˆ ํ›„ ์žฌ์‹œ๋„...")
174
+ time.sleep(retry_delay)
175
+ retry_delay = min(retry_delay * 2, 10)
176
+ else:
177
+ return {
178
+ "success": False,
179
+ "message": "API ์„œ๋ฒ„ ์—ฐ๊ฒฐ ์‹คํŒจ",
180
+ "status_code": None
181
+ }
182
+
183
+ except Exception as e:
184
+ logger.error(f"DeepSeek API ์š”์ฒญ ์ค‘ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜: {e}")
185
+
186
+ if attempt < max_retries:
187
+ logger.info(f"{retry_delay}์ดˆ ํ›„ ์žฌ์‹œ๋„...")
188
+ time.sleep(retry_delay)
189
+ retry_delay = min(retry_delay * 2, 10)
190
+ else:
191
+ return {
192
+ "success": False,
193
+ "message": f"์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜: {str(e)}",
194
+ "status_code": None
195
+ }
196
+
197
+ # ๋ชจ๋“  ์‹œ๋„ ์‹คํŒจ
198
+ return {
199
+ "success": False,
200
+ "message": "์ตœ๋Œ€ ์žฌ์‹œ๋„ ํšŸ์ˆ˜ ์ดˆ๊ณผ",
201
+ "status_code": None
202
+ }
203
+
204
+ def system_prompt_chat(self,
205
+ system_prompt: str,
206
+ user_prompt: str,
207
+ temperature: float = 0.3,
208
+ max_tokens: int = 1000,
209
+ max_retries: int = 3,
210
+ timeout: int = 60) -> Dict[str, Any]:
211
+ """
212
+ ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ์™€ ์‚ฌ์šฉ์ž ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ด์šฉํ•œ ์ฑ„ํŒ… API ํ˜ธ์ถœ
213
+
214
+ Args:
215
+ system_prompt: ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ
216
+ user_prompt: ์‚ฌ์šฉ์ž ํ”„๋กฌํ”„ํŠธ
217
+ temperature: ์ƒ์„ฑ ์˜จ๋„ (0.0 ~ 1.0)
218
+ max_tokens: ์ตœ๋Œ€ ์ƒ์„ฑ ํ† ํฐ ์ˆ˜
219
+ max_retries: ์žฌ์‹œ๋„ ํšŸ์ˆ˜
220
+ timeout: ์š”์ฒญ ํƒ€์ž„์•„์›ƒ (์ดˆ)
221
+
222
+ Returns:
223
+ ์ƒ์„ฑ ๊ฒฐ๊ณผ ๋”•์…”๋„ˆ๋ฆฌ
224
+ """
225
+ messages = [
226
+ {"role": "system", "content": system_prompt},
227
+ {"role": "user", "content": user_prompt}
228
+ ]
229
+
230
+ return self.chat(messages, temperature, max_tokens, max_retries, timeout)
231
+
232
+
233
+ # ๋‹จ๋… ์‹คํ–‰์„ ์œ„ํ•œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ
234
+ if __name__ == "__main__":
235
+ # ๋กœ๊น… ์„ค์ •
236
+ logging.basicConfig(level=logging.INFO)
237
+
238
+ # API ํ‚ค ํ™•์ธ
239
+ api_key = os.environ.get("DEEPSEEK_API_KEY")
240
+ if not api_key:
241
+ print("ํ™˜๊ฒฝ ๋ณ€์ˆ˜ DEEPSEEK_API_KEY๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
242
+ exit(1)
243
+
244
+ # ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ
245
+ client = DirectDeepSeekClient(api_key)
246
+
247
+ # ๊ฐ„๋‹จํ•œ ํ…Œ์ŠคํŠธ
248
+ response = client.generate("Hello, what can you do?")
249
+
250
+ # ๊ฒฐ๊ณผ ์ถœ๋ ฅ
251
+ if response["success"]:
252
+ print("์‘๋‹ต ์„ฑ๊ณต!")
253
+ print(response["response"])
254
+ else:
255
+ print(f"์‘๋‹ต ์‹คํŒจ: {response['message']}")
fallback_rag_chain.py CHANGED
@@ -1,53 +1,29 @@
1
  """
2
- ํด๋ฐฑ RAG ์ฒด์ธ ๊ตฌํ˜„ (๊ธฐ๋ณธ์ ์ธ ๊ธฐ๋Šฅ๋งŒ ํฌํ•จ)
3
  """
4
  import os
5
  import logging
6
  import time
7
- import requests
8
- import json
9
  from typing import List, Dict, Any, Optional, Tuple
10
  from langchain.schema import Document
11
- from langchain.prompts import PromptTemplate
12
- from langchain_core.output_parsers import StrOutputParser
13
- from langchain_core.runnables import RunnablePassthrough
14
 
15
- # DeepSeek ์ง์ ‘ ํด๋ผ์ด์–ธํŠธ ์‚ฌ์šฉ
16
  from direct_deepseek import DirectDeepSeekClient
17
 
18
  # ์„ค์ • ๊ฐ€์ ธ์˜ค๊ธฐ
19
  from config import (
20
  LLM_MODEL, USE_OPENAI, USE_DEEPSEEK,
21
- OPENAI_API_KEY, DEEPSEEK_API_KEY, DEEPSEEK_ENDPOINT, DEEPSEEK_MODEL,
22
  TOP_K_RETRIEVAL
23
  )
24
 
25
  # ๋กœ๊น… ์„ค์ •
26
  logger = logging.getLogger("FallbackRAGChain")
27
 
28
- class CustomDeepSeekLLM:
29
- """
30
- OpenAI ํด๋ผ์ด์–ธํŠธ๋ฅผ ์šฐํšŒํ•˜๋Š” ์ปค์Šคํ…€ DeepSeek LLM ํด๋ž˜์Šค
31
- """
32
- def __init__(self, api_key, model, temperature=0.3, request_timeout=120):
33
- self.client = DirectDeepSeekClient(api_key, model)
34
- self.temperature = temperature
35
- self.request_timeout = request_timeout
36
- logger.info(f"์ปค์Šคํ…€ DeepSeek LLM ์ดˆ๊ธฐํ™”: ๋ชจ๋ธ={model}, ์˜จ๋„={temperature}")
37
-
38
- def invoke(self, prompt):
39
- """
40
- ํ…์ŠคํŠธ ์ƒ์„ฑ ์š”์ฒญ
41
- """
42
- result = self.client.generate(prompt, max_retries=3, timeout=self.request_timeout)
43
- if result["success"]:
44
- return result["response"]
45
- else:
46
- raise Exception(f"DeepSeek API ํ˜ธ์ถœ ์‹คํŒจ: {result['message']}")
47
-
48
  class FallbackRAGChain:
49
  """
50
  ๊ธฐ๋ณธ์ ์ธ RAG ์ฒด์ธ ๊ตฌํ˜„ (๋‹จ์ˆœํ™”๋œ ๋ฒ„์ „, ๋ฌธ์ œ ํ•ด๊ฒฐ์šฉ)
 
51
  """
52
 
53
  def __init__(self, vector_store):
@@ -60,41 +36,24 @@ class FallbackRAGChain:
60
  logger.info("ํด๋ฐฑ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™”...")
61
  self.vector_store = vector_store
62
 
63
- # DeepSeek ๋ชจ๋ธ ์ง์ ‘ ์ดˆ๊ธฐํ™” (OpenAI ํด๋ผ์ด์–ธํŠธ ์šฐํšŒ)
64
  if USE_DEEPSEEK and DEEPSEEK_API_KEY:
65
  logger.info(f"DeepSeek ๋ชจ๋ธ ์ง์ ‘ ์ดˆ๊ธฐํ™”: {DEEPSEEK_MODEL}")
66
  try:
67
- self.llm = CustomDeepSeekLLM(
68
  api_key=DEEPSEEK_API_KEY,
69
- model=DEEPSEEK_MODEL,
70
- temperature=0.3,
71
- request_timeout=120
72
  )
73
  logger.info("DeepSeek ๋ชจ๋ธ ์ง์ ‘ ์ดˆ๊ธฐํ™” ์„ฑ๊ณต")
74
  except Exception as e:
75
  logger.error(f"DeepSeek ๋ชจ๋ธ ์ดˆ๊ธฐํ™” ์‹คํŒจ: {e}")
76
  # ์˜คํ”„๋ผ์ธ ๋ชจ๋“œ๋กœ ํด๋ฐฑ
77
- self.llm = None
78
  logger.warning("LLM์ด ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•„ ์˜คํ”„๋ผ์ธ ๋ชจ๋“œ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.")
79
  else:
80
  # LLM์ด ์„ค์ •๋˜์ง€ ์•Š์Œ
81
  logger.warning("LLM์ด ์„ค์ •๋˜์ง€ ์•Š์•„ ์˜คํ”„๋ผ์ธ ๋ชจ๋“œ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.")
82
- self.llm = None
83
-
84
- # ํ”„๋กฌํ”„ํŠธ ์„ค์ •
85
- self.prompt_template = """
86
- ๋‹ค์Œ ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์งˆ๋ฌธ์— ์ •ํ™•ํ•˜๊ฒŒ ๋‹ต๋ณ€ํ•ด์ฃผ์„ธ์š”.
87
-
88
- ์งˆ๋ฌธ: {question}
89
-
90
- ์ฐธ๊ณ  ์ •๋ณด:
91
- {context}
92
-
93
- ์ฐธ๊ณ  ์ •๋ณด์— ๋‹ต์ด ์žˆ์œผ๋ฉด ๋ฐ˜๋“œ์‹œ ๊ทธ ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋‹ต๋ณ€ํ•˜์„ธ์š”.
94
- ์ฐธ๊ณ  ์ •๋ณด์— ๋‹ต์ด ์—†๋Š” ๊ฒฝ์šฐ์—๋Š” ์ผ๋ฐ˜์ ์ธ ์ง€์‹์„ ํ™œ์šฉํ•˜์—ฌ ๋‹ต๋ณ€ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, "์ œ๊ณต๋œ ๋ฌธ์„œ์—๋Š” ์ด ์ •๋ณด๊ฐ€ ์—†์œผ๋‚˜, ์ผ๋ฐ˜์ ์œผ๋กœ๋Š”..." ์‹์œผ๋กœ ์‹œ์ž‘ํ•˜์„ธ์š”.
95
- ๋‹ต๋ณ€์€ ์ •ํ™•ํ•˜๊ณ  ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ œ๊ณตํ•˜๋˜, ๊ฐ€๋Šฅํ•œ ์ฐธ๊ณ  ์ •๋ณด์—์„œ ๊ทผ๊ฑฐ๋ฅผ ์ฐพ์•„ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”.
96
- ์ฐธ๊ณ  ์ •๋ณด์˜ ์ถœ์ฒ˜๋„ ํ•จ๊ป˜ ์•Œ๋ ค์ฃผ์„ธ์š”.
97
- """
98
 
99
  logger.info("ํด๋ฐฑ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ")
100
 
@@ -143,6 +102,38 @@ class FallbackRAGChain:
143
  logger.error(f"๊ฒ€์ƒ‰ ์ค‘ ์˜ค๋ฅ˜: {e}")
144
  return f"๊ฒ€์ƒ‰ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
145
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  def _generate_simple_response(self, query: str, context: str) -> str:
147
  """
148
  ๊ฐ„๋‹จํ•œ ์˜คํ”„๋ผ์ธ ์‘๋‹ต ์ƒ์„ฑ (LLM์ด ์—†์„ ๋•Œ ์‚ฌ์šฉ)
@@ -193,12 +184,12 @@ class FallbackRAGChain:
193
  context = self._retrieve(query)
194
 
195
  # LLM์ด ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ์˜คํ”„๋ผ์ธ ์‘๋‹ต
196
- if self.llm is None:
197
  logger.warning("LLM์ด ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•„ ์˜คํ”„๋ผ์ธ ์‘๋‹ต ์ƒ์„ฑ")
198
  return self._generate_simple_response(query, context)
199
 
200
  # ํ”„๋กฌํ”„ํŠธ ๊ตฌ์„ฑ
201
- prompt = self.prompt_template.format(question=query, context=context)
202
 
203
  # ์‘๋‹ต ์ƒ์„ฑ (์ตœ๋Œ€ 3ํšŒ ์‹œ๋„)
204
  max_retries = 3
@@ -207,18 +198,31 @@ class FallbackRAGChain:
207
  for attempt in range(max_retries):
208
  try:
209
  logger.info(f"์‘๋‹ต ์ƒ์„ฑ ์‹œ๋„ ({attempt+1}/{max_retries})")
210
- response = self.llm.invoke(prompt)
211
- logger.info(f"์‘๋‹ต ์ƒ์„ฑ ์„ฑ๊ณต (๊ธธ์ด: {len(response)})")
212
- return response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  except Exception as e:
214
- logger.error(f"์‘๋‹ต ์ƒ์„ฑ ์‹คํŒจ: {e}")
215
  if attempt < max_retries - 1:
216
  logger.info(f"{retry_delay}์ดˆ ํ›„ ์žฌ์‹œ๋„...")
217
  time.sleep(retry_delay)
218
  retry_delay *= 2
219
  else:
220
- # ๋ชจ๋“  ์‹œ๋„ ์‹คํŒจ ์‹œ ์˜คํ”„๋ผ์ธ ์‘๋‹ต
221
- logger.warning("์ตœ๋Œ€ ์žฌ์‹œ๋„ ํšŸ์ˆ˜ ์ดˆ๊ณผ, ์˜คํ”„๋ผ์ธ ์‘๋‹ต ์ƒ์„ฑ")
222
  return self._generate_simple_response(query, context)
223
 
224
  except Exception as e:
 
1
  """
2
+ ํด๋ฐฑ RAG ์ฒด์ธ ๊ตฌํ˜„ (๊ธฐ๋ณธ์ ์ธ ๊ธฐ๋Šฅ๋งŒ ํฌํ•จ) - ์ง์ ‘ DeepSeek API ํ˜ธ์ถœ ๋ฐฉ์‹
3
  """
4
  import os
5
  import logging
6
  import time
 
 
7
  from typing import List, Dict, Any, Optional, Tuple
8
  from langchain.schema import Document
 
 
 
9
 
10
+ # ์ง์ ‘ DeepSeek ํด๋ผ์ด์–ธํŠธ ์‚ฌ์šฉ
11
  from direct_deepseek import DirectDeepSeekClient
12
 
13
  # ์„ค์ • ๊ฐ€์ ธ์˜ค๊ธฐ
14
  from config import (
15
  LLM_MODEL, USE_OPENAI, USE_DEEPSEEK,
16
+ DEEPSEEK_API_KEY, DEEPSEEK_ENDPOINT, DEEPSEEK_MODEL,
17
  TOP_K_RETRIEVAL
18
  )
19
 
20
  # ๋กœ๊น… ์„ค์ •
21
  logger = logging.getLogger("FallbackRAGChain")
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  class FallbackRAGChain:
24
  """
25
  ๊ธฐ๋ณธ์ ์ธ RAG ์ฒด์ธ ๊ตฌํ˜„ (๋‹จ์ˆœํ™”๋œ ๋ฒ„์ „, ๋ฌธ์ œ ํ•ด๊ฒฐ์šฉ)
26
+ ์ง์ ‘ DeepSeek API ํ˜ธ์ถœ ๋ฐฉ์‹ ์‚ฌ์šฉ
27
  """
28
 
29
  def __init__(self, vector_store):
 
36
  logger.info("ํด๋ฐฑ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™”...")
37
  self.vector_store = vector_store
38
 
39
+ # DeepSeek ๋ชจ๋ธ ์ง์ ‘ ์ดˆ๊ธฐํ™”
40
  if USE_DEEPSEEK and DEEPSEEK_API_KEY:
41
  logger.info(f"DeepSeek ๋ชจ๋ธ ์ง์ ‘ ์ดˆ๊ธฐํ™”: {DEEPSEEK_MODEL}")
42
  try:
43
+ self.client = DirectDeepSeekClient(
44
  api_key=DEEPSEEK_API_KEY,
45
+ model_name=DEEPSEEK_MODEL
 
 
46
  )
47
  logger.info("DeepSeek ๋ชจ๋ธ ์ง์ ‘ ์ดˆ๊ธฐํ™” ์„ฑ๊ณต")
48
  except Exception as e:
49
  logger.error(f"DeepSeek ๋ชจ๋ธ ์ดˆ๊ธฐํ™” ์‹คํŒจ: {e}")
50
  # ์˜คํ”„๋ผ์ธ ๋ชจ๋“œ๋กœ ํด๋ฐฑ
51
+ self.client = None
52
  logger.warning("LLM์ด ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•„ ์˜คํ”„๋ผ์ธ ๋ชจ๋“œ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.")
53
  else:
54
  # LLM์ด ์„ค์ •๋˜์ง€ ์•Š์Œ
55
  logger.warning("LLM์ด ์„ค์ •๋˜์ง€ ์•Š์•„ ์˜คํ”„๋ผ์ธ ๋ชจ๋“œ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.")
56
+ self.client = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
  logger.info("ํด๋ฐฑ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ")
59
 
 
102
  logger.error(f"๊ฒ€์ƒ‰ ์ค‘ ์˜ค๋ฅ˜: {e}")
103
  return f"๊ฒ€์ƒ‰ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
104
 
105
+ def _generate_prompt(self, query: str, context: str) -> List[Dict[str, str]]:
106
+ """
107
+ ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ (DeepSeek API ํ˜•์‹)
108
+
109
+ Args:
110
+ query: ์‚ฌ์šฉ์ž ์งˆ๋ฌธ
111
+ context: ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ปจํ…์ŠคํŠธ
112
+
113
+ Returns:
114
+ DeepSeek API์šฉ messages ํ˜•์‹
115
+ """
116
+ # ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ
117
+ system_prompt = """๋‹ค์Œ ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์งˆ๋ฌธ์— ์ •ํ™•ํ•˜๊ฒŒ ๋‹ต๋ณ€ํ•ด์ฃผ์„ธ์š”.
118
+ ์ฐธ๊ณ  ์ •๋ณด์— ๋‹ต์ด ์žˆ์œผ๋ฉด ๋ฐ˜๋“œ์‹œ ๊ทธ ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋‹ต๋ณ€ํ•˜์„ธ์š”.
119
+ ์ฐธ๊ณ  ์ •๋ณด์— ๋‹ต์ด ์—†๋Š” ๊ฒฝ์šฐ์—๋Š” ์ผ๋ฐ˜์ ์ธ ์ง€์‹์„ ํ™œ์šฉํ•˜์—ฌ ๋‹ต๋ณ€ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, "์ œ๊ณต๋œ ๋ฌธ์„œ์—๋Š” ์ด ์ •๋ณด๊ฐ€ ์—†์œผ๋‚˜, ์ผ๋ฐ˜์ ์œผ๋กœ๋Š”..." ์‹์œผ๋กœ ์‹œ์ž‘ํ•˜์„ธ์š”.
120
+ ๋‹ต๋ณ€์€ ์ •ํ™•ํ•˜๊ณ  ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ œ๊ณตํ•˜๋˜, ๊ฐ€๋Šฅํ•œ ์ฐธ๊ณ  ์ •๋ณด์—์„œ ๊ทผ๏ฟฝ๏ฟฝ๋ฅผ ์ฐพ์•„ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”.
121
+ ์ฐธ๊ณ  ์ •๋ณด์˜ ์ถœ์ฒ˜๋„ ํ•จ๊ป˜ ์•Œ๋ ค์ฃผ์„ธ์š”."""
122
+
123
+ # ์‚ฌ์šฉ์ž ํ”„๋กฌํ”„ํŠธ (์งˆ๋ฌธ๊ณผ ์ปจํ…์ŠคํŠธ ํฌํ•จ)
124
+ user_prompt = f"""์งˆ๋ฌธ: {query}
125
+
126
+ ์ฐธ๊ณ  ์ •๋ณด:
127
+ {context}"""
128
+
129
+ # DeepSeek API์— ๋งž๋Š” ๋ฉ”์‹œ์ง€ ํฌ๋งท
130
+ messages = [
131
+ {"role": "system", "content": system_prompt},
132
+ {"role": "user", "content": user_prompt}
133
+ ]
134
+
135
+ return messages
136
+
137
  def _generate_simple_response(self, query: str, context: str) -> str:
138
  """
139
  ๊ฐ„๋‹จํ•œ ์˜คํ”„๋ผ์ธ ์‘๋‹ต ์ƒ์„ฑ (LLM์ด ์—†์„ ๋•Œ ์‚ฌ์šฉ)
 
184
  context = self._retrieve(query)
185
 
186
  # LLM์ด ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ์˜คํ”„๋ผ์ธ ์‘๋‹ต
187
+ if self.client is None:
188
  logger.warning("LLM์ด ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•„ ์˜คํ”„๋ผ์ธ ์‘๋‹ต ์ƒ์„ฑ")
189
  return self._generate_simple_response(query, context)
190
 
191
  # ํ”„๋กฌํ”„ํŠธ ๊ตฌ์„ฑ
192
+ messages = self._generate_prompt(query, context)
193
 
194
  # ์‘๋‹ต ์ƒ์„ฑ (์ตœ๋Œ€ 3ํšŒ ์‹œ๋„)
195
  max_retries = 3
 
198
  for attempt in range(max_retries):
199
  try:
200
  logger.info(f"์‘๋‹ต ์ƒ์„ฑ ์‹œ๋„ ({attempt+1}/{max_retries})")
201
+
202
+ # ์ง์ ‘ DeepSeek API ํ˜ธ์ถœ
203
+ response = self.client.chat(messages)
204
+
205
+ if response["success"]:
206
+ logger.info(f"์‘๋‹ต ์ƒ์„ฑ ์„ฑ๊ณต (๊ธธ์ด: {len(response['response'])})")
207
+ return response["response"]
208
+ else:
209
+ logger.error(f"์‘๋‹ต ์ƒ์„ฑ ์‹คํŒจ: {response['message']}")
210
+ if attempt < max_retries - 1:
211
+ logger.info(f"{retry_delay}์ดˆ ํ›„ ์žฌ์‹œ๋„...")
212
+ time.sleep(retry_delay)
213
+ retry_delay *= 2
214
+ else:
215
+ # ๋ชจ๋“  ์‹œ๋„ ์‹คํŒจ ์‹œ ์˜คํ”„๋ผ์ธ ์‘๋‹ต
216
+ logger.warning("์ตœ๋Œ€ ์žฌ์‹œ๋„ ํšŸ์ˆ˜ ์ดˆ๊ณผ, ์˜คํ”„๋ผ์ธ ์‘๋‹ต ์ƒ์„ฑ")
217
+ return self._generate_simple_response(query, context)
218
  except Exception as e:
219
+ logger.error(f"์‘๋‹ต ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜: {e}")
220
  if attempt < max_retries - 1:
221
  logger.info(f"{retry_delay}์ดˆ ํ›„ ์žฌ์‹œ๋„...")
222
  time.sleep(retry_delay)
223
  retry_delay *= 2
224
  else:
225
+ # ๋ชจ๋“  ์‹œ๋„ ์‹คํŒจ ์‹œ ์˜คํ”„๋ผ์ธ ์‘๋‹ต ์ƒ์„ฑ
 
226
  return self._generate_simple_response(query, context)
227
 
228
  except Exception as e:
monitoring.py DELETED
@@ -1,136 +0,0 @@
1
- """
2
- Langfuse๋ฅผ ํ™œ์šฉํ•œ ๋ชจ๋‹ˆํ„ฐ๋ง ๊ตฌํ˜„ (์„ ํƒ์ )
3
- """
4
- from typing import Dict, Any, Optional
5
- import time
6
- import os
7
- from dotenv import load_dotenv
8
-
9
- # ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋กœ๋“œ
10
- load_dotenv()
11
-
12
- # ์„ค์ • ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ
13
- LANGFUSE_SECRET_KEY = os.getenv("LANGFUSE_SECRET_KEY", "")
14
- LANGFUSE_PUBLIC_KEY = os.getenv("LANGFUSE_PUBLIC_KEY", "")
15
- LANGFUSE_HOST = os.getenv("LANGFUSE_HOST", "https://cloud.langfuse.com")
16
-
17
- class LangfuseMonitoring:
18
- def __init__(self):
19
- """
20
- Langfuse ๋ชจ๋‹ˆํ„ฐ๋ง ์ดˆ๊ธฐํ™” (์„ ํƒ์  ๊ธฐ๋Šฅ)
21
- """
22
- self.enabled = False
23
- print("๋ชจ๋‹ˆํ„ฐ๋ง ๊ธฐ๋Šฅ์„ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค...")
24
-
25
- # Langfuse๊ฐ€ ์„ค์น˜๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธ
26
- try:
27
- from langfuse import Langfuse
28
-
29
- if LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY:
30
- try:
31
- self.langfuse = Langfuse(
32
- public_key=LANGFUSE_PUBLIC_KEY,
33
- secret_key=LANGFUSE_SECRET_KEY,
34
- host=LANGFUSE_HOST,
35
- )
36
- self.enabled = True
37
- print("Langfuse ๋ชจ๋‹ˆํ„ฐ๋ง์ด ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
38
- except Exception as e:
39
- print(f"Langfuse ์ดˆ๊ธฐํ™” ์‹คํŒจ: {e}")
40
- else:
41
- print("Langfuse API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ๋ชจ๋‹ˆํ„ฐ๋ง์€ ๋น„ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค.")
42
- except ImportError:
43
- print("langfuse ํŒจํ‚ค์ง€๊ฐ€ ์„ค์น˜๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ๋ชจ๋‹ˆํ„ฐ๋ง ๊ธฐ๋Šฅ์ด ๋น„ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค.")
44
- print("pip install langfuse ๋ช…๋ น์œผ๋กœ ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.")
45
-
46
- def start_trace(self, name: str, user_id: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None) -> Any:
47
- """
48
- ์ƒˆ ํŠธ๋ ˆ์ด์Šค ์‹œ์ž‘
49
-
50
- Args:
51
- name: ํŠธ๋ ˆ์ด์Šค ์ด๋ฆ„
52
- user_id: ์‚ฌ์šฉ์ž ID (์„ ํƒ์ )
53
- metadata: ์ถ”๊ฐ€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ (์„ ํƒ์ )
54
-
55
- Returns:
56
- ํŠธ๋ ˆ์ด์Šค ๊ฐ์ฒด ๋˜๋Š” None
57
- """
58
- if not self.enabled:
59
- return None
60
-
61
- try:
62
- return self.langfuse.trace(
63
- name=name,
64
- user_id=user_id,
65
- metadata=metadata or {},
66
- )
67
- except Exception as e:
68
- print(f"ํŠธ๋ ˆ์ด์Šค ์ƒ์„ฑ ์‹คํŒจ: {e}")
69
- return None
70
-
71
- def log_generation(self, trace: Any, name: str, prompt: str, response: str, metadata: Optional[Dict[str, Any]] = None) -> None:
72
- """
73
- LLM ์ƒ์„ฑ ๋กœ๊น…
74
-
75
- Args:
76
- trace: ํŠธ๋ ˆ์ด์Šค ๊ฐ์ฒด
77
- name: ์ƒ์„ฑ ์ด๋ฆ„
78
- prompt: ์ž…๋ ฅ ํ”„๋กฌํ”„ํŠธ
79
- response: ๋ชจ๋ธ ์‘๋‹ต
80
- metadata: ์ถ”๊ฐ€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ (์„ ํƒ์ )
81
- """
82
- if not self.enabled or trace is None:
83
- return
84
-
85
- try:
86
- trace.generation(
87
- name=name,
88
- model="user-defined-model",
89
- prompt=prompt,
90
- completion=response,
91
- metadata=metadata or {},
92
- )
93
- except Exception as e:
94
- print(f"์ƒ์„ฑ ๋กœ๊น… ์‹คํŒจ: {e}")
95
-
96
- def log_span(self, trace: Any, name: str, input_data: Any, output_data: Any, start_time: float, end_time: float) -> None:
97
- """
98
- ์ฒ˜๋ฆฌ ๊ตฌ๊ฐ„ ๋กœ๊น…
99
-
100
- Args:
101
- trace: ํŠธ๋ ˆ์ด์Šค ๊ฐ์ฒด
102
- name: ๊ตฌ๊ฐ„ ์ด๋ฆ„
103
- input_data: ์ž…๋ ฅ ๋ฐ์ดํ„ฐ
104
- output_data: ์ถœ๋ ฅ ๋ฐ์ดํ„ฐ
105
- start_time: ์‹œ์ž‘ ์‹œ๊ฐ„
106
- end_time: ์ข…๋ฃŒ ์‹œ๊ฐ„
107
- """
108
- if not self.enabled or trace is None:
109
- return
110
-
111
- try:
112
- trace.span(
113
- name=name,
114
- start_time=start_time,
115
- end_time=end_time,
116
- input=input_data,
117
- output=output_data,
118
- metadata={"duration_ms": (end_time - start_time) * 1000},
119
- )
120
- except Exception as e:
121
- print(f"๊ตฌ๊ฐ„ ๋กœ๊น… ์‹คํŒจ: {e}")
122
-
123
- def end_trace(self, trace: Any) -> None:
124
- """
125
- ํŠธ๋ ˆ์ด์Šค ์ข…๋ฃŒ
126
-
127
- Args:
128
- trace: ์ข…๋ฃŒํ•  ํŠธ๋ ˆ์ด์Šค ๊ฐ์ฒด
129
- """
130
- if not self.enabled or trace is None:
131
- return
132
-
133
- try:
134
- trace.update(status="success")
135
- except Exception as e:
136
- print(f"ํŠธ๋ ˆ์ด์Šค ์ข…๋ฃŒ ์‹คํŒจ: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
rag_chain.py CHANGED
@@ -1,163 +1,133 @@
1
  """
2
- LangChain์„ ํ™œ์šฉํ•œ RAG ์ฒด์ธ ๊ตฌํ˜„ - DeepSeek ์ง€์› ์ถ”๊ฐ€ ๋ฐ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๊ฐ•ํ™”
3
  """
4
  import os
 
5
  import logging
6
- from typing import List, Dict, Any, Optional
7
- from langchain.schema import Document
8
- from langchain.prompts import PromptTemplate
9
- from langchain_core.output_parsers import StrOutputParser
10
- from langchain_core.runnables import RunnablePassthrough
11
- from langchain_community.chat_models import ChatOllama
12
- from langchain_openai import ChatOpenAI
13
-
14
- from config import (
15
- OLLAMA_HOST, LLM_MODEL, USE_OPENAI, USE_DEEPSEEK,
16
- OPENAI_API_KEY, DEEPSEEK_API_KEY, DEEPSEEK_ENDPOINT, DEEPSEEK_MODEL,
17
- TOP_K_RETRIEVAL, TOP_K_RERANK
18
- )
19
- from vector_store import VectorStore
20
- from reranker import Reranker
21
-
22
- # DeepSeek ์œ ํ‹ธ๋ฆฌํ‹ฐ ์ž„ํฌํŠธ
23
- try:
24
- from deepseek_utils import test_deepseek_api, create_deepseek_client, DeepSeekError
25
- DEEPSEEK_UTILS_AVAILABLE = True
26
- except ImportError:
27
- DEEPSEEK_UTILS_AVAILABLE = False
28
 
29
  # ๋กœ๊น… ์„ค์ •
30
- logger = logging.getLogger("RAGChain")
31
-
32
- # ํ™˜๊ฒฝ ๊ฐ์ง€
33
- IS_HUGGINGFACE = os.getenv('SPACE_ID') is not None
34
-
35
- # RAG ๊ด€๋ จ ์˜ˆ์™ธ ํด๋ž˜์Šค
36
- class RAGChainInitError(Exception):
37
- """RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ๊ด€๋ จ ์˜ค๋ฅ˜"""
38
- pass
39
-
40
- class LLMInitError(Exception):
41
- """LLM ๋ชจ๋ธ ์ดˆ๊ธฐํ™” ๊ด€๋ จ ์˜ค๋ฅ˜"""
42
- pass
43
-
44
- class RerankerInitError(Exception):
45
- """๋ฆฌ๋žญ์ปค ์ดˆ๊ธฐํ™” ๊ด€๋ จ ์˜ค๋ฅ˜"""
46
- pass
47
-
48
- class QueryProcessingError(Exception):
49
- """์ฟผ๋ฆฌ ์ฒ˜๋ฆฌ ์ค‘ ๋ฐœ์ƒํ•œ ์˜ค๋ฅ˜"""
50
- pass
51
-
52
-
53
- class RAGChain:
54
- def __init__(self, vector_store: VectorStore, use_reranker: bool = True):
55
  """
56
- RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” (ํ™˜๊ฒฝ์— ๋”ฐ๋ฅธ LLM ์„ ํƒ)
57
 
58
  Args:
59
- vector_store: ๋ฒกํ„ฐ ์Šคํ† ์–ด ์ธ์Šคํ„ด์Šค
60
- use_reranker: ๋ฆฌ๋žญ์ปค ์‚ฌ์šฉ ์—ฌ๋ถ€
61
  """
62
- try:
63
- logger.info("RAGChain ์ดˆ๊ธฐํ™” ์‹œ์ž‘...")
64
-
65
- # ์ž…๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
66
- if vector_store is None or vector_store.vector_store is None:
67
- raise RAGChainInitError("์œ ํšจํ•œ ๋ฒกํ„ฐ ์Šคํ† ์–ด๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค")
68
-
69
- self.vector_store = vector_store
70
- self.use_reranker = use_reranker
71
- logger.info(f"๋ฆฌ๋žญ์ปค ์‚ฌ์šฉ ์—ฌ๋ถ€: {use_reranker}")
72
-
73
- # ๋ฆฌ๋žญ์ปค ์ดˆ๊ธฐํ™”
74
- if use_reranker:
75
- try:
76
- self.reranker = Reranker()
77
- logger.info("๋ฆฌ๋žญ์ปค ์ดˆ๊ธฐํ™” ์„ฑ๊ณต")
78
- except Exception as e:
79
- logger.error(f"๋ฆฌ๋žญ์ปค ์ดˆ๊ธฐํ™” ์‹คํŒจ: {e}", exc_info=True)
80
- self.reranker = None
81
- self.use_reranker = False
82
- logger.warning("๋ฆฌ๋žญ์ปค ์‚ฌ์šฉ์ด ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค")
83
- else:
84
- self.reranker = None
85
-
86
- # LLM ๋ชจ๋ธ ์ดˆ๊ธฐํ™”
87
- self._initialize_llm()
88
-
89
- # RAG ์ฒด์ธ ๊ตฌ์„ฑ ๋ฐ ํ”„๋กฌํ”„ํŠธ ์„ค์ •
90
- logger.info("RAG ์ฒด์ธ ์„ค์ • ์‹œ์ž‘...")
91
- self.setup_chain()
92
- logger.info("RAG ์ฒด์ธ ์„ค์ • ์™„๋ฃŒ")
93
-
94
- except RAGChainInitError:
95
- # ์ด๋ฏธ ๋กœ๊น…๋จ
96
- raise
97
- except Exception as e:
98
- logger.error(f"RAGChain ์ดˆ๊ธฐํ™” ์ค‘ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜: {e}", exc_info=True)
99
- raise RAGChainInitError(f"RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์‹คํŒจ: {str(e)}")
100
-
101
- def _test_deepseek_api(self):
102
  """
103
- DeepSeek API ์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ (๋ณ„๋„ ์œ ํ‹ธ๋ฆฌํ‹ฐ ์‚ฌ์šฉ)
 
 
 
 
 
 
 
104
 
105
  Returns:
106
- ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ๋”•์…”๋„ˆ๋ฆฌ
107
  """
108
- if DEEPSEEK_UTILS_AVAILABLE:
109
- try:
110
- return test_deepseek_api(DEEPSEEK_API_KEY, DEEPSEEK_ENDPOINT, DEEPSEEK_MODEL)
111
- except Exception as e:
112
- logger.error(f"DeepSeek API ํ…Œ์ŠคํŠธ ์‹คํŒจ: {e}", exc_info=True)
113
- return {"success": False, "message": f"ํ…Œ์ŠคํŠธ ์˜ค๋ฅ˜: {str(e)}"}
114
- else:
115
- # ์ง์ ‘ ๊ตฌํ˜„
116
- try:
117
- import requests
118
- import json
119
-
120
- if not DEEPSEEK_API_KEY:
121
- return {
122
- "success": False,
123
- "message": "API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."
124
- }
125
-
126
- logger.info(f"DeepSeek API ์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ: {DEEPSEEK_ENDPOINT}, ๋ชจ๋ธ: {DEEPSEEK_MODEL}")
127
-
128
- # ํ…Œ์ŠคํŠธ์šฉ ๊ฐ„๋‹จํ•œ ํ”„๋กฌํ”„ํŠธ
129
- test_prompt = "Hello, please respond with a short greeting."
130
 
131
- # API ์š”์ฒญ ํ—ค๋” ๋ฐ ๋ฐ์ดํ„ฐ
132
- headers = {
133
- "Content-Type": "application/json",
134
- "Authorization": f"Bearer {DEEPSEEK_API_KEY}"
135
- }
 
136
 
137
- payload = {
138
- "model": DEEPSEEK_MODEL,
139
- "messages": [{"role": "user", "content": test_prompt}],
140
- "temperature": 0.7,
141
- "max_tokens": 50
142
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
  # API ์š”์ฒญ ์ „์†ก
145
  response = requests.post(
146
- DEEPSEEK_ENDPOINT,
147
  headers=headers,
148
- data=json.dumps(payload),
149
- timeout=10 # 10์ดˆ ํƒ€์ž„์•„์›ƒ
150
  )
151
 
152
  # ์‘๋‹ต ํ™•์ธ
153
  if response.status_code == 200:
154
- logger.info("DeepSeek API ์—ฐ๊ฒฐ ์„ฑ๊ณต")
155
- return {
156
- "success": True,
157
- "message": "API ์—ฐ๊ฒฐ ์„ฑ๊ณต"
158
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  else:
160
  logger.error(f"DeepSeek API ์˜ค๋ฅ˜: ์ƒํƒœ ์ฝ”๋“œ {response.status_code}")
 
 
161
  error_message = ""
162
  try:
163
  error_data = response.json()
@@ -165,200 +135,121 @@ class RAGChain:
165
  except:
166
  error_message = response.text
167
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  return {
169
  "success": False,
170
- "message": f"API ์˜ค๋ฅ˜: {error_message}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  }
172
- except Exception as e:
173
- logger.error(f"DeepSeek API ํ…Œ์ŠคํŠธ ์ค‘ ์˜ค๋ฅ˜: {e}", exc_info=True)
174
- return {
175
- "success": False,
176
- "message": f"์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ ์˜ค๋ฅ˜: {str(e)}"
177
- }
178
-
179
- def _initialize_llm(self):
180
- """LLM ๋ชจ๋ธ ์ดˆ๊ธฐํ™” (ํ™˜๊ฒฝ์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ๋ชจ๋ธ ์„ ํƒ)"""
181
- # DeepSeek ์‚ฌ์šฉ์ด ์„ค์ •๋œ ๊ฒฝ์šฐ
182
- if USE_DEEPSEEK:
183
- logger.info(f"DeepSeek ๋ชจ๋ธ ์ดˆ๊ธฐํ™”: {DEEPSEEK_MODEL}")
184
-
185
- if not DEEPSEEK_API_KEY:
186
- logger.error("DeepSeek API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค")
187
- raise LLMInitError("DeepSeek API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค")
188
-
189
- # API ์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ
190
- test_result = self._test_deepseek_api()
191
- if not test_result["success"]:
192
- logger.error(f"DeepSeek API ์—ฐ๊ฒฐ ์‹คํŒจ: {test_result['message']}")
193
- raise LLMInitError(f"DeepSeek API ์—ฐ๊ฒฐ ์‹คํŒจ: {test_result['message']}")
194
 
195
- try:
196
- # DeepSeek API๋Š” OpenAI ํ˜ธํ™˜ API๋ฅผ ์ œ๊ณต
197
- self.llm = ChatOpenAI(
198
- model=DEEPSEEK_MODEL,
199
- temperature=0.2,
200
- api_key=DEEPSEEK_API_KEY,
201
- base_url=DEEPSEEK_ENDPOINT.rstrip("/v1/chat/completions") # ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•ด ๊ฒฝ๋กœ ์กฐ์ •
202
- )
203
- logger.info("DeepSeek ๋ชจ๋ธ ์ดˆ๊ธฐํ™” ์„ฑ๊ณต")
204
- except Exception as e:
205
- logger.error(f"DeepSeek ๋ชจ๋ธ ์ดˆ๊ธฐํ™” ์‹คํŒจ: {e}", exc_info=True)
206
- raise LLMInitError(f"DeepSeek ๋ชจ๋ธ ์ดˆ๊ธฐํ™” ์‹คํŒจ: {str(e)}")
207
-
208
- # OpenAI ์‚ฌ์šฉ์ด ์„ค์ •๋œ ๊ฒฝ์šฐ
209
- elif USE_OPENAI or IS_HUGGINGFACE:
210
- logger.info(f"OpenAI ๋ชจ๋ธ ์ดˆ๊ธฐํ™”: {LLM_MODEL}")
211
- api_key = OPENAI_API_KEY
212
- logger.info(f"API ํ‚ค ์กด์žฌ ์—ฌ๋ถ€: {'์žˆ์Œ' if api_key else '์—†์Œ'}")
213
-
214
- if not api_key:
215
- logger.error("OpenAI API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค")
216
- raise LLMInitError("OpenAI API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค")
217
-
218
- try:
219
- self.llm = ChatOpenAI(
220
- model_name=LLM_MODEL,
221
- temperature=0.2,
222
- api_key=api_key,
223
- )
224
- logger.info("OpenAI ๋ชจ๋ธ ์ดˆ๊ธฐํ™” ์„ฑ๊ณต")
225
- except Exception as e:
226
- logger.error(f"OpenAI ๋ชจ๋ธ ์ดˆ๊ธฐํ™” ์‹คํŒจ: {e}", exc_info=True)
227
- raise LLMInitError(f"OpenAI ๋ชจ๋ธ ์ดˆ๊ธฐํ™” ์‹คํŒจ: {str(e)}")
228
- else:
229
- try:
230
- logger.info(f"Ollama ๋ชจ๋ธ ์ดˆ๊ธฐํ™”: {LLM_MODEL}")
231
-
232
- # Ollama ํ˜ธ์ŠคํŠธ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
233
- if not OLLAMA_HOST:
234
- logger.error("Ollama ํ˜ธ์ŠคํŠธ๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค")
235
- raise LLMInitError("Ollama ํ˜ธ์ŠคํŠธ๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค")
236
-
237
- self.llm = ChatOllama(
238
- model=LLM_MODEL,
239
- temperature=0.2,
240
- base_url=OLLAMA_HOST,
241
- )
242
- logger.info("Ollama ๋ชจ๋ธ ์ดˆ๊ธฐํ™” ์„ฑ๊ณต")
243
  except Exception as e:
244
- logger.error(f"Ollama ๋ชจ๋ธ ์ดˆ๊ธฐํ™” ์‹คํŒจ: {e}", exc_info=True)
245
- raise LLMInitError(f"Ollama ๋ชจ๋ธ ์ดˆ๊ธฐํ™” ์‹คํŒจ: {str(e)}. Ollama๊ฐ€ ์‹คํ–‰ ์ค‘์ธ์ง€ ํ™•์ธํ•˜์„ธ์š”.")
246
 
247
- def setup_chain(self) -> None:
248
- """
249
- RAG ์ฒด์ธ ๋ฐ ํ”„๋กฌํ”„ํŠธ ์„ค์ •
250
- """
251
- try:
252
- # ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ ์ •์˜
253
- template = """
254
- ๋‹ค์Œ ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์งˆ๋ฌธ์— ์ •ํ™•ํ•˜๊ฒŒ ๋‹ต๋ณ€ํ•ด์ฃผ์„ธ์š”.
255
-
256
- ์งˆ๋ฌธ: {question}
257
-
258
- ์ฐธ๊ณ  ์ •๋ณด:
259
- {context}
260
-
261
- ์ฐธ๊ณ  ์ •๋ณด์— ๋‹ต์ด ์žˆ์œผ๋ฉด ๋ฐ˜๋“œ์‹œ ๊ทธ ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋‹ต๋ณ€ํ•˜์„ธ์š”.
262
- ์ฐธ๊ณ  ์ •๋ณด์— ๋‹ต์ด ์—†๋Š” ๊ฒฝ์šฐ์—๋Š” ์ผ๋ฐ˜์ ์ธ ์ง€์‹์„ ํ™œ์šฉํ•˜์—ฌ ๋‹ต๋ณ€ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, "์ œ๊ณต๋œ ๋ฌธ์„œ์—๋Š” ์ด ์ •๋ณด๊ฐ€ ์—†์œผ๋‚˜, ์ผ๋ฐ˜์ ์œผ๋กœ๋Š”..." ์‹์œผ๋กœ ์‹œ์ž‘ํ•˜์„ธ์š”.
263
- ๋‹ต๋ณ€์€ ์ •ํ™•ํ•˜๊ณ  ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ œ๊ณตํ•˜๋˜, ๊ฐ€๋Šฅํ•œ ์ฐธ๊ณ  ์ •๋ณด์—์„œ ๊ทผ๊ฑฐ๋ฅผ ์ฐพ์•„ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”.
264
- ์ฐธ๊ณ  ์ •๋ณด์˜ ์ถœ์ฒ˜๋„ ํ•จ๊ป˜ ์•Œ๋ ค์ฃผ์„ธ์š”.
265
- """
266
-
267
- self.prompt = PromptTemplate.from_template(template)
268
-
269
- # RAG ์ฒด์ธ ์ •์˜
270
- self.chain = (
271
- {"context": self._retrieve, "question": RunnablePassthrough()}
272
- | self.prompt
273
- | self.llm
274
- | StrOutputParser()
275
- )
276
-
277
- logger.info("RAG ์ฒด์ธ ์„ค์ • ์™„๋ฃŒ")
278
- except Exception as e:
279
- logger.error(f"RAG ์ฒด์ธ ์„ค์ • ์‹คํŒจ: {e}", exc_info=True)
280
- raise RAGChainInitError(f"RAG ์ฒด์ธ ์„ค์ • ์‹คํŒจ: {str(e)}")
281
-
282
- def _retrieve(self, query: str) -> str:
283
  """
284
- ์ฟผ๋ฆฌ์— ๋Œ€ํ•œ ๊ด€๋ จ ๋ฌธ์„œ ๊ฒ€์ƒ‰ ๋ฐ ์ปจํ…์ŠคํŠธ ๊ตฌ์„ฑ
285
 
286
  Args:
287
- query: ์‚ฌ์šฉ์ž ์งˆ๋ฌธ
 
 
 
 
 
288
 
289
  Returns:
290
- ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ํฌํ•จํ•œ ์ปจํ…์ŠคํŠธ ๋ฌธ์ž์—ด
291
- """
292
- if not query or not query.strip():
293
- logger.warning("๋นˆ ์ฟผ๋ฆฌ๋กœ ๊ฒ€์ƒ‰ ์‹œ๋„")
294
- return "๊ฒ€์ƒ‰ ์ฟผ๋ฆฌ๊ฐ€ ๋น„์–ด์žˆ์Šต๋‹ˆ๋‹ค."
295
-
296
- try:
297
- # ๋ฒกํ„ฐ ๊ฒ€์ƒ‰ ์ˆ˜ํ–‰
298
- logger.info(f"๋ฒกํ„ฐ ๊ฒ€์ƒ‰ ์ˆ˜ํ–‰: '{query[:50]}{'...' if len(query) > 50 else ''}'")
299
- docs = self.vector_store.similarity_search(query, k=TOP_K_RETRIEVAL)
300
-
301
- if not docs:
302
- logger.warning("๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค")
303
- return "๊ด€๋ จ ๋ฌธ์„œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."
304
-
305
- # ๋ฆฌ๋žญ์ปค ์ ์šฉ (์„ ํƒ์ )
306
- if self.use_reranker and self.reranker and docs:
307
- try:
308
- logger.info(f"๋ฆฌ๋žญํ‚น ์ˆ˜ํ–‰: {len(docs)}๊ฐœ ๋ฌธ์„œ")
309
- docs = self.reranker.rerank(query, docs, top_k=TOP_K_RERANK)
310
- logger.info(f"๋ฆฌ๋žญํ‚น ์™„๋ฃŒ: {len(docs)}๊ฐœ ๋ฌธ์„œ ์„ ํƒ๋จ")
311
- except Exception as e:
312
- logger.error(f"๋ฆฌ๋žญํ‚น ์‹คํŒจ: {e}", exc_info=True)
313
- # ๋ฆฌ๋žญํ‚น ์‹คํŒจ ์‹œ ์›๋ณธ ๋ฌธ์„œ ์‚ฌ์šฉ
314
- logger.warning("๋ฆฌ๋žญํ‚น ์‹คํŒจ๋กœ ์›๋ณธ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์‚ฌ์šฉ")
315
-
316
- # ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ปจํ…์ŠคํŠธ ๊ตฌ์„ฑ
317
- context_parts = []
318
- for i, doc in enumerate(docs, 1):
319
- source = doc.metadata.get("source", "์•Œ ์ˆ˜ ์—†๋Š” ์ถœ์ฒ˜")
320
- page = doc.metadata.get("page", "")
321
- source_info = f"{source}"
322
- if page:
323
- source_info += f" (ํŽ˜์ด์ง€: {page})"
324
-
325
- context_parts.append(f"[์ฐธ๊ณ ์ž๋ฃŒ {i}] - ์ถœ์ฒ˜: {source_info}\n{doc.page_content}\n")
326
-
327
- context = "\n".join(context_parts)
328
- logger.info(f"์ปจํ…์ŠคํŠธ ์ƒ์„ฑ ์™„๋ฃŒ: {len(context_parts)}๊ฐœ ๋ฌธ์„œ, {len(context)} ๋ฌธ์ž")
329
- return context
330
-
331
- except Exception as e:
332
- logger.error(f"๊ฒ€์ƒ‰ ์ค‘ ์˜ค๋ฅ˜: {e}", exc_info=True)
333
- raise QueryProcessingError(f"๋ฌธ์„œ ๊ฒ€์ƒ‰ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}")
334
-
335
- def run(self, query: str) -> str:
336
  """
337
- ์‚ฌ์šฉ์ž ์ฟผ๋ฆฌ์— ๋Œ€ํ•œ RAG ํŒŒ์ดํ”„๋ผ์ธ ์‹คํ–‰
 
 
 
338
 
339
- Args:
340
- query: ์‚ฌ์šฉ์ž ์งˆ๋ฌธ
341
 
342
- Returns:
343
- ๋ชจ๋ธ ์‘๋‹ต ๋ฌธ์ž์—ด
344
- """
345
- if not query or not query.strip():
346
- logger.warning("๋นˆ ์ฟผ๋ฆฌ๋กœ ์‹คํ–‰ ์‹œ๋„")
347
- return "์งˆ๋ฌธ์ด ๋น„์–ด์žˆ์Šต๋‹ˆ๋‹ค. ์งˆ๋ฌธ์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”."
348
-
349
- try:
350
- logger.info(f"RAG ์ฒด์ธ ์‹คํ–‰: '{query[:50]}{'...' if len(query) > 50 else ''}'")
351
- start_time = logger.debug(f"RAG ์ฒด์ธ ์‹คํ–‰ ์‹œ์ž‘")
352
-
353
- # ์ฒด์ธ ์‹คํ–‰
354
- response = self.chain.invoke(query)
355
-
356
- logger.info("RAG ์ฒด์ธ ์‹คํ–‰ ์™„๋ฃŒ")
357
- return response
358
-
359
- except QueryProcessingError as e:
360
- # ์ด๋ฏธ ๋กœ๊น…๋จ
361
- return f"๊ฒ€์ƒ‰ ์˜ค๋ฅ˜: {str(e)}"
362
- except Exception as e:
363
- logger.error(f"RAG ์ฒด์ธ ์‹คํ–‰ ์ค‘ ์˜ค๋ฅ˜: {e}", exc_info=True)
364
- return f"์งˆ๋ฌธ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}"
 
 
1
  """
2
+ ์ง์ ‘ DeepSeek API ํ˜ธ์ถœ์„ ์œ„ํ•œ ํด๋ผ์ด์–ธํŠธ ๊ตฌํ˜„
3
  """
4
  import os
5
+ import time
6
  import logging
7
+ import requests
8
+ import json
9
+ from typing import Dict, Any, Optional, List
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  # ๋กœ๊น… ์„ค์ •
12
+ logger = logging.getLogger("DirectDeepSeek")
13
+
14
+ class DirectDeepSeekClient:
15
+ """
16
+ DeepSeek API๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜๋Š” ํด๋ผ์ด์–ธํŠธ
17
+ OpenAI ํด๋ผ์ด์–ธํŠธ๋ฅผ ์šฐํšŒํ•˜๊ณ  ์ง์ ‘ HTTP ์š”์ฒญ ์‚ฌ์šฉ
18
+ """
19
+ def __init__(self, api_key: str, model_name: str = "deepseek-chat"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  """
21
+ ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™”
22
 
23
  Args:
24
+ api_key: DeepSeek API ํ‚ค
25
+ model_name: ์‚ฌ์šฉํ•  ๋ชจ๋ธ ์ด๋ฆ„ (๊ธฐ๋ณธ๊ฐ’: "deepseek-chat")
26
  """
27
+ self.api_key = api_key
28
+ self.model_name = model_name
29
+ self.endpoint = os.getenv("DEEPSEEK_ENDPOINT", "https://api.deepseek.com/v1/chat/completions")
30
+ logger.info(f"DirectDeepSeekClient ์ดˆ๊ธฐํ™”: ๋ชจ๋ธ={model_name}, ์—”๋“œํฌ์ธํŠธ={self.endpoint}")
31
+
32
+ def generate(self,
33
+ prompt: str,
34
+ temperature: float = 0.3,
35
+ max_tokens: int = 1000,
36
+ max_retries: int = 3,
37
+ timeout: int = 60) -> Dict[str, Any]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  """
39
+ ํ…์ŠคํŠธ ์ƒ์„ฑ ์š”์ฒญ
40
+
41
+ Args:
42
+ prompt: ์ž…๋ ฅ ํ”„๋กฌํ”„ํŠธ
43
+ temperature: ์ƒ์„ฑ ์˜จ๋„ (0.0 ~ 1.0)
44
+ max_tokens: ์ตœ๋Œ€ ์ƒ์„ฑ ํ† ํฐ ์ˆ˜
45
+ max_retries: ์žฌ์‹œ๋„ ํšŸ์ˆ˜
46
+ timeout: ์š”์ฒญ ํƒ€์ž„์•„์›ƒ (์ดˆ)
47
 
48
  Returns:
49
+ ์ƒ์„ฑ ๊ฒฐ๊ณผ ๋”•์…”๋„ˆ๋ฆฌ (success, response, message ๋“ฑ)
50
  """
51
+ # ๋ฉ”์‹œ์ง€ ๊ตฌ์„ฑ (๋‹จ์ผ ์‚ฌ์šฉ์ž ๋ฉ”์‹œ์ง€)
52
+ messages = [{"role": "user", "content": prompt}]
53
+ return self.chat(messages, temperature, max_tokens, max_retries, timeout)
54
+
55
+ def chat(self,
56
+ messages: List[Dict[str, str]],
57
+ temperature: float = 0.3,
58
+ max_tokens: int = 1000,
59
+ max_retries: int = 3,
60
+ timeout: int = 60) -> Dict[str, Any]:
61
+ """
62
+ ์ฑ„ํŒ… API ํ˜ธ์ถœ
 
 
 
 
 
 
 
 
 
 
63
 
64
+ Args:
65
+ messages: ์ฑ„ํŒ… ๋ฉ”์‹œ์ง€ ๋ฆฌ์ŠคํŠธ (role, content ํ‚ค๋ฅผ ๊ฐ€์ง„ ๋”•์…”๋„ˆ๋ฆฌ ๋ฆฌ์ŠคํŠธ)
66
+ temperature: ์ƒ์„ฑ ์˜จ๋„ (0.0 ~ 1.0)
67
+ max_tokens: ์ตœ๋Œ€ ์ƒ์„ฑ ํ† ํฐ ์ˆ˜
68
+ max_retries: ์žฌ์‹œ๋„ ํšŸ์ˆ˜
69
+ timeout: ์š”์ฒญ ํƒ€์ž„์•„์›ƒ (์ดˆ)
70
 
71
+ Returns:
72
+ ์ƒ์„ฑ ๊ฒฐ๊ณผ ๋”•์…”๋„ˆ๋ฆฌ (success, response, message ๋“ฑ)
73
+ """
74
+ # API ์š”์ฒญ ํ—ค๋” ๋ฐ ๋ฐ์ดํ„ฐ
75
+ headers = {
76
+ "Content-Type": "application/json",
77
+ "Authorization": f"Bearer {self.api_key}"
78
+ }
79
+
80
+ payload = {
81
+ "model": self.model_name,
82
+ "messages": messages,
83
+ "temperature": temperature,
84
+ "max_tokens": max_tokens
85
+ }
86
+
87
+ # ์žฌ์‹œ๋„ ๋กœ์ง
88
+ retry_delay = 1.0
89
+ attempt = 0
90
+
91
+ while attempt < max_retries:
92
+ attempt += 1
93
+ try:
94
+ logger.info(f"DeepSeek API ์š”์ฒญ ์‹œ๋„ ({attempt}/{max_retries})...")
95
 
96
  # API ์š”์ฒญ ์ „์†ก
97
  response = requests.post(
98
+ self.endpoint,
99
  headers=headers,
100
+ json=payload,
101
+ timeout=timeout
102
  )
103
 
104
  # ์‘๋‹ต ํ™•์ธ
105
  if response.status_code == 200:
106
+ result = response.json()
107
+
108
+ # ์‘๋‹ต ๋‚ด์šฉ ์ถ”์ถœ
109
+ if "choices" in result and len(result["choices"]) > 0:
110
+ message_content = result["choices"][0].get("message", {}).get("content", "")
111
+ logger.info(f"DeepSeek API ์‘๋‹ต ์„ฑ๊ณต (๊ธธ์ด: {len(message_content)})")
112
+
113
+ return {
114
+ "success": True,
115
+ "response": message_content,
116
+ "status_code": response.status_code,
117
+ "raw_response": result
118
+ }
119
+ else:
120
+ logger.warning(f"DeepSeek API ์‘๋‹ต์€ ์„ฑ๊ณตํ–ˆ์œผ๋‚˜ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์‘๋‹ต ํ˜•์‹: {result}")
121
+ return {
122
+ "success": False,
123
+ "message": "์‘๋‹ต์—์„œ ๋ฉ”์‹œ์ง€๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค",
124
+ "status_code": response.status_code,
125
+ "raw_response": result
126
+ }
127
  else:
128
  logger.error(f"DeepSeek API ์˜ค๋ฅ˜: ์ƒํƒœ ์ฝ”๋“œ {response.status_code}")
129
+
130
+ # ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€ ์ถ”์ถœ
131
  error_message = ""
132
  try:
133
  error_data = response.json()
 
135
  except:
136
  error_message = response.text
137
 
138
+ # ์š”์ฒญ ํ•œ๋„ ์ดˆ๊ณผ์‹œ ๋” ์˜ค๋ž˜ ๋Œ€๊ธฐ
139
+ if response.status_code == 429:
140
+ retry_delay = min(retry_delay * 3, 15)
141
+ else:
142
+ retry_delay = min(retry_delay * 2, 10)
143
+
144
+ if attempt < max_retries:
145
+ logger.info(f"{retry_delay}์ดˆ ํ›„ ์žฌ์‹œ๋„...")
146
+ time.sleep(retry_delay)
147
+ else:
148
+ # ๋ชจ๋“  ์‹œ๋„ ์‹คํŒจ
149
+ return {
150
+ "success": False,
151
+ "message": f"API ์˜ค๋ฅ˜: {error_message}",
152
+ "status_code": response.status_code
153
+ }
154
+
155
+ except requests.exceptions.Timeout:
156
+ logger.error("DeepSeek API ์š”์ฒญ ์‹œ๊ฐ„ ์ดˆ๊ณผ")
157
+
158
+ if attempt < max_retries:
159
+ logger.info(f"{retry_delay}์ดˆ ํ›„ ์žฌ์‹œ๋„...")
160
+ time.sleep(retry_delay)
161
+ retry_delay = min(retry_delay * 2, 10)
162
+ else:
163
  return {
164
  "success": False,
165
+ "message": "API ์š”์ฒญ ์‹œ๊ฐ„ ์ดˆ๊ณผ",
166
+ "status_code": None
167
+ }
168
+
169
+ except requests.exceptions.ConnectionError:
170
+ logger.error("DeepSeek API ์—ฐ๊ฒฐ ์‹คํŒจ")
171
+
172
+ if attempt < max_retries:
173
+ logger.info(f"{retry_delay}์ดˆ ํ›„ ์žฌ์‹œ๋„...")
174
+ time.sleep(retry_delay)
175
+ retry_delay = min(retry_delay * 2, 10)
176
+ else:
177
+ return {
178
+ "success": False,
179
+ "message": "API ์„œ๋ฒ„ ์—ฐ๊ฒฐ ์‹คํŒจ",
180
+ "status_code": None
181
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  except Exception as e:
184
+ logger.error(f"DeepSeek API ์š”์ฒญ ์ค‘ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜: {e}")
 
185
 
186
+ if attempt < max_retries:
187
+ logger.info(f"{retry_delay}์ดˆ ํ›„ ์žฌ์‹œ๋„...")
188
+ time.sleep(retry_delay)
189
+ retry_delay = min(retry_delay * 2, 10)
190
+ else:
191
+ return {
192
+ "success": False,
193
+ "message": f"์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜: {str(e)}",
194
+ "status_code": None
195
+ }
196
+
197
+ # ๋ชจ๋“  ์‹œ๋„ ์‹คํŒจ
198
+ return {
199
+ "success": False,
200
+ "message": "์ตœ๋Œ€ ์žฌ์‹œ๋„ ํšŸ์ˆ˜ ์ดˆ๊ณผ",
201
+ "status_code": None
202
+ }
203
+
204
+ def system_prompt_chat(self,
205
+ system_prompt: str,
206
+ user_prompt: str,
207
+ temperature: float = 0.3,
208
+ max_tokens: int = 1000,
209
+ max_retries: int = 3,
210
+ timeout: int = 60) -> Dict[str, Any]:
 
 
 
 
 
 
 
 
 
 
 
211
  """
212
+ ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ์™€ ์‚ฌ์šฉ์ž ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ด์šฉํ•œ ์ฑ„ํŒ… API ํ˜ธ์ถœ
213
 
214
  Args:
215
+ system_prompt: ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ
216
+ user_prompt: ์‚ฌ์šฉ์ž ํ”„๋กฌํ”„ํŠธ
217
+ temperature: ์ƒ์„ฑ ์˜จ๋„ (0.0 ~ 1.0)
218
+ max_tokens: ์ตœ๋Œ€ ์ƒ์„ฑ ํ† ํฐ ์ˆ˜
219
+ max_retries: ์žฌ์‹œ๋„ ํšŸ์ˆ˜
220
+ timeout: ์š”์ฒญ ํƒ€์ž„์•„์›ƒ (์ดˆ)
221
 
222
  Returns:
223
+ ์ƒ์„ฑ ๊ฒฐ๊ณผ ๋”•์…”๋„ˆ๋ฆฌ
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  """
225
+ messages = [
226
+ {"role": "system", "content": system_prompt},
227
+ {"role": "user", "content": user_prompt}
228
+ ]
229
 
230
+ return self.chat(messages, temperature, max_tokens, max_retries, timeout)
 
231
 
232
+
233
+ # ๋‹จ๋… ์‹คํ–‰์„ ์œ„ํ•œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ
234
+ if __name__ == "__main__":
235
+ # ๋กœ๊น… ์„ค์ •
236
+ logging.basicConfig(level=logging.INFO)
237
+
238
+ # API ํ‚ค ํ™•์ธ
239
+ api_key = os.environ.get("DEEPSEEK_API_KEY")
240
+ if not api_key:
241
+ print("ํ™˜๊ฒฝ ๋ณ€์ˆ˜ DEEPSEEK_API_KEY๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
242
+ exit(1)
243
+
244
+ # ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ
245
+ client = DirectDeepSeekClient(api_key)
246
+
247
+ # ๊ฐ„๋‹จํ•œ ํ…Œ์ŠคํŠธ
248
+ response = client.generate("Hello, what can you do?")
249
+
250
+ # ๊ฒฐ๊ณผ ์ถœ๋ ฅ
251
+ if response["success"]:
252
+ print("์‘๋‹ต ์„ฑ๊ณต!")
253
+ print(response["response"])
254
+ else:
255
+ print(f"์‘๋‹ต ์‹คํŒจ: {response['message']}")
simple_rag_chain.py CHANGED
@@ -1,66 +1,123 @@
1
  """
2
- ๊ฐ„๋‹จํ•œ RAG ์ฒด์ธ ๊ตฌํ˜„ (๋””๋ฒ„๊น…์šฉ)
3
  """
4
  import os
5
- from langchain_openai import ChatOpenAI
6
- from langchain.prompts import PromptTemplate
7
- from langchain_core.output_parsers import StrOutputParser
8
- from langchain_core.runnables import RunnablePassthrough
9
 
 
 
 
 
 
10
 
11
  class SimpleRAGChain:
12
- def __init__(self, vector_store):
13
  """๊ฐ„๋‹จํ•œ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™”"""
14
- print("๊ฐ„๋‹จํ•œ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์ค‘...")
15
  self.vector_store = vector_store
16
 
17
- # OpenAI API ํ‚ค ํ™•์ธ
18
- openai_api_key = os.environ.get("OPENAI_API_KEY", "")
19
- print(f"API ํ‚ค ์„ค์ •๋จ: {bool(openai_api_key)}")
 
20
 
21
- # OpenAI ๋ชจ๋ธ ์ดˆ๊ธฐํ™”
22
- self.llm = ChatOpenAI(
23
- model_name="gpt-3.5-turbo",
24
- temperature=0.2,
25
- api_key=openai_api_key,
26
- )
 
 
 
 
 
 
 
 
27
 
28
- # ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ
29
- template = """
30
- ๋‹ค์Œ ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์งˆ๋ฌธ์— ์ •ํ™•ํ•˜๊ฒŒ ๋‹ต๋ณ€ํ•ด์ฃผ์„ธ์š”.
31
 
32
- ์งˆ๋ฌธ: {question}
 
 
 
 
 
33
 
34
- ์ฐธ๊ณ  ์ •๋ณด:
35
- {context}
 
 
 
 
 
 
36
 
37
- ์ฐธ๊ณ  ์ •๋ณด์— ๋‹ต์ด ์—†๋Š” ๊ฒฝ์šฐ "์ œ๊ณต๋œ ๋ฌธ์„œ์—์„œ ํ•ด๋‹น ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."๋ผ๊ณ  ๋‹ต๋ณ€ํ•˜์„ธ์š”.
38
- """
39
 
40
- self.prompt = PromptTemplate.from_template(template)
41
 
42
- # ์ฒด์ธ ๊ตฌ์„ฑ
43
- self.chain = (
44
- {"context": self._retrieve, "question": RunnablePassthrough()}
45
- | self.prompt
46
- | self.llm
47
- | StrOutputParser()
48
- )
49
- print("๊ฐ„๋‹จํ•œ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ")
50
 
51
- def _retrieve(self, query):
52
- """๋ฌธ์„œ ๊ฒ€์ƒ‰"""
53
- try:
54
- docs = self.vector_store.similarity_search(query, k=3)
55
- return "\n\n".join(doc.page_content for doc in docs)
56
  except Exception as e:
57
- print(f"๊ฒ€์ƒ‰ ์ค‘ ์˜ค๋ฅ˜: {e}")
58
  return "๋ฌธ์„œ ๊ฒ€์ƒ‰ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."
59
 
60
- def run(self, query):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  """์ฟผ๋ฆฌ ์ฒ˜๋ฆฌ"""
62
  try:
63
- return self.chain.invoke(query)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  except Exception as e:
65
- print(f"์‹คํ–‰ ์ค‘ ์˜ค๋ฅ˜: {e}")
66
  return f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
 
1
  """
2
+ ๊ฐ„๋‹จํ•œ RAG ์ฒด์ธ ๊ตฌํ˜„ (๋””๋ฒ„๊น…์šฉ) - ์ง์ ‘ DeepSeek API ํ˜ธ์ถœ ๋ฐฉ์‹
3
  """
4
  import os
5
+ import logging
6
+ import time
7
+ from typing import Dict, Any, List
 
8
 
9
+ # ์ง์ ‘ DeepSeek ํด๋ผ์ด์–ธํŠธ ์‚ฌ์šฉ
10
+ from direct_deepseek import DirectDeepSeekClient
11
+
12
+ # ๋กœ๊น… ์„ค์ •
13
+ logger = logging.getLogger("SimpleRAGChain")
14
 
15
  class SimpleRAGChain:
16
+ def __init__(self, vector_store, api_key=None, model="deepseek-chat", endpoint=None):
17
  """๊ฐ„๋‹จํ•œ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™”"""
18
+ logger.info("๊ฐ„๋‹จํ•œ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์ค‘...")
19
  self.vector_store = vector_store
20
 
21
+ # DeepSeek API ํ‚ค ํ™•์ธ
22
+ self.api_key = api_key or os.environ.get("DEEPSEEK_API_KEY", "")
23
+ self.model = model or os.environ.get("DEEPSEEK_MODEL", "deepseek-chat")
24
+ logger.info(f"API ํ‚ค ์„ค์ •๋จ: {bool(self.api_key)}")
25
 
26
+ # DeepSeek ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™”
27
+ if self.api_key:
28
+ try:
29
+ self.client = DirectDeepSeekClient(
30
+ api_key=self.api_key,
31
+ model_name=self.model
32
+ )
33
+ logger.info(f"DeepSeek ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™” ์„ฑ๊ณต: {self.model}")
34
+ except Exception as e:
35
+ logger.error(f"DeepSeek ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™” ์‹คํŒจ: {e}")
36
+ self.client = None
37
+ else:
38
+ logger.warning("API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•„ ํด๋ผ์ด์–ธํŠธ๋ฅผ ์ดˆ๊ธฐํ™”ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
39
+ self.client = None
40
 
41
+ logger.info("๊ฐ„๋‹จํ•œ RAG ์ฒด์ธ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ")
 
 
42
 
43
+ def _retrieve(self, query: str) -> str:
44
+ """๋ฌธ์„œ ๊ฒ€์ƒ‰ ๋ฐ ์ปจํ…์ŠคํŠธ ๊ตฌ์„ฑ"""
45
+ try:
46
+ docs = self.vector_store.similarity_search(query, k=3)
47
+ if not docs:
48
+ return "๊ด€๋ จ ๋ฌธ์„œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."
49
 
50
+ # ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ปจํ…์ŠคํŠธ ๊ตฌ์„ฑ
51
+ context_parts = []
52
+ for i, doc in enumerate(docs, 1):
53
+ source = doc.metadata.get("source", "์•Œ ์ˆ˜ ์—†๋Š” ์ถœ์ฒ˜")
54
+ page = doc.metadata.get("page", "")
55
+ source_info = f"{source}"
56
+ if page:
57
+ source_info += f" (ํŽ˜์ด์ง€: {page})"
58
 
59
+ context_parts.append(f"[์ฐธ๊ณ ์ž๋ฃŒ {i}] - ์ถœ์ฒ˜: {source_info}\n{doc.page_content}\n")
 
60
 
61
+ context = "\n".join(context_parts)
62
 
63
+ # ๊ธธ์ด ์ œํ•œ
64
+ if len(context) > 6000:
65
+ context = context[:2500] + "\n...(์ค‘๋žต)...\n" + context[-2500:]
 
 
 
 
 
66
 
67
+ return context
 
 
 
 
68
  except Exception as e:
69
+ logger.error(f"๊ฒ€์ƒ‰ ์ค‘ ์˜ค๋ฅ˜: {e}")
70
  return "๋ฌธ์„œ ๊ฒ€์ƒ‰ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."
71
 
72
+ def _generate_prompt(self, query: str, context: str) -> List[Dict[str, str]]:
73
+ """DeepSeek API์šฉ ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ"""
74
+ # ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ
75
+ system_prompt = """๋‹ค์Œ ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์งˆ๋ฌธ์— ์ •ํ™•ํ•˜๊ฒŒ ๋‹ต๋ณ€ํ•ด์ฃผ์„ธ์š”.
76
+ ์ฐธ๊ณ  ์ •๋ณด์—์„œ ๋‹ต์„ ์ฐพ์„ ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ "์ œ๊ณต๋œ ๋ฌธ์„œ์—์„œ ํ•ด๋‹น ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."๋ผ๊ณ  ๋‹ต๋ณ€ํ•˜์„ธ์š”.
77
+ ์ •๋ณด ์ถœ์ฒ˜๋ฅผ ํฌํ•จํ•ด์„œ ๋Œ€๋‹ตํ•˜์„ธ์š”."""
78
+
79
+ # ์‚ฌ์šฉ์ž ํ”„๋กฌํ”„ํŠธ
80
+ user_prompt = f"""์งˆ๋ฌธ: {query}
81
+
82
+ ์ฐธ๊ณ  ์ •๋ณด:
83
+ {context}"""
84
+
85
+ # DeepSeek API ํ”„๋กฌํ”„ํŠธ ํฌ๋งท
86
+ messages = [
87
+ {"role": "system", "content": system_prompt},
88
+ {"role": "user", "content": user_prompt}
89
+ ]
90
+
91
+ return messages
92
+
93
+ def run(self, query: str) -> str:
94
  """์ฟผ๋ฆฌ ์ฒ˜๋ฆฌ"""
95
  try:
96
+ logger.info(f"SimpleRAGChain ์‹คํ–‰: {query[:50]}...")
97
+
98
+ # ๋ฌธ์„œ ๊ฒ€์ƒ‰
99
+ context = self._retrieve(query)
100
+
101
+ # ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ
102
+ if self.client is None:
103
+ logger.warning("DeepSeek ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์Œ. ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋งŒ ๋ฐ˜ํ™˜.")
104
+ return f"API ์—ฐ๊ฒฐ์ด ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ:\n\n{context}"
105
+
106
+ # ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ
107
+ messages = self._generate_prompt(query, context)
108
+
109
+ # API ํ˜ธ์ถœ
110
+ start_time = time.time()
111
+ response = self.client.chat(messages)
112
+ logger.info(f"API ์‘๋‹ต ์‹œ๊ฐ„: {time.time() - start_time:.2f}์ดˆ")
113
+
114
+ if response["success"]:
115
+ logger.info("์‘๋‹ต ์ƒ์„ฑ ์„ฑ๊ณต")
116
+ return response["response"]
117
+ else:
118
+ logger.error(f"์‘๋‹ต ์ƒ์„ฑ ์‹คํŒจ: {response['message']}")
119
+ return f"์‘๋‹ต ์ƒ์„ฑ ์‹คํŒจ: {response['message']}\n\n๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ:\n{context}"
120
+
121
  except Exception as e:
122
+ logger.error(f"์‹คํ–‰ ์ค‘ ์˜ค๋ฅ˜: {e}")
123
  return f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
test_deepseek.py DELETED
@@ -1,123 +0,0 @@
1
- """
2
- DeepSeek API ์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ ์Šคํฌ๋ฆฝํŠธ
3
- ์‚ฌ์šฉ๋ฒ•: python test_deepseek.py
4
- """
5
- import os
6
- import sys
7
- import requests
8
- import json
9
- import time
10
- from dotenv import load_dotenv
11
-
12
-
13
- def main():
14
- # .env ํŒŒ์ผ ๋กœ๋“œ ์‹œ๋„
15
- load_dotenv()
16
-
17
- # API ํ‚ค ํ™•์ธ
18
- api_key = os.getenv("DEEPSEEK_API_KEY", "")
19
- if not api_key:
20
- print("์˜ค๋ฅ˜: DEEPSEEK_API_KEY๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
21
- print("๋‹ค์Œ ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜๋กœ API ํ‚ค๋ฅผ ์„ค์ •ํ•ด์ฃผ์„ธ์š”:")
22
- print("1. .env ํŒŒ์ผ์— DEEPSEEK_API_KEY=your_api_key ์ถ”๊ฐ€")
23
- print("2. ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ์„ค์ • (export DEEPSEEK_API_KEY=your_api_key)")
24
- return False
25
-
26
- # ์—”๋“œํฌ์ธํŠธ ๋ฐ ๋ชจ๋ธ ์„ค์ •
27
- endpoint = os.getenv("DEEPSEEK_ENDPOINT", "https://api.deepseek.com/v1/chat/completions")
28
- model = os.getenv("DEEPSEEK_MODEL", "deepseek-chat")
29
-
30
- print(f"DeepSeek API ํ…Œ์ŠคํŠธ๋ฅผ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค...")
31
- print(f"์—”๋“œํฌ์ธํŠธ: {endpoint}")
32
- print(f"๋ชจ๋ธ: {model}")
33
- print(f"API ํ‚ค: {api_key[:5]}...{api_key[-4:] if len(api_key) > 8 else '****'}")
34
-
35
- # ๊ฐ„๋‹จํ•œ ํ”„๋กฌํ”„ํŠธ
36
- test_prompt = "Hello, please respond with a short greeting in Korean."
37
-
38
- # API ์š”์ฒญ ํ—ค๋” ๋ฐ ๋ฐ์ดํ„ฐ
39
- headers = {
40
- "Content-Type": "application/json",
41
- "Authorization": f"Bearer {api_key}"
42
- }
43
-
44
- payload = {
45
- "model": model,
46
- "messages": [{"role": "user", "content": test_prompt}],
47
- "temperature": 0.7,
48
- "max_tokens": 100
49
- }
50
-
51
- print("\n์š”์ฒญ ๋ฐ์ดํ„ฐ:")
52
- print(json.dumps(payload, indent=2, ensure_ascii=False))
53
-
54
- # ํƒ€์ด๋จธ ์‹œ์ž‘
55
- start_time = time.time()
56
-
57
- try:
58
- print("\nAPI ์š”์ฒญ ์ „์†ก ์ค‘...")
59
- response = requests.post(
60
- endpoint,
61
- headers=headers,
62
- data=json.dumps(payload),
63
- timeout=30 # 30์ดˆ ํƒ€์ž„์•„์›ƒ
64
- )
65
-
66
- elapsed_time = time.time() - start_time
67
-
68
- print(f"์‘๋‹ต ์ˆ˜์‹  (์†Œ์š” ์‹œ๊ฐ„: {elapsed_time:.2f}์ดˆ)")
69
- print(f"HTTP ์ƒํƒœ ์ฝ”๋“œ: {response.status_code}")
70
-
71
- # ์‘๋‹ต ํ™•์ธ
72
- if response.status_code == 200:
73
- print("\n์„ฑ๊ณต! DeepSeek API๊ฐ€ ์ •์ƒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.")
74
-
75
- try:
76
- response_data = response.json()
77
- print("\n์‘๋‹ต ๋ฐ์ดํ„ฐ:")
78
- print(json.dumps(response_data, indent=2, ensure_ascii=False))
79
-
80
- # ์‘๋‹ต ํ…์ŠคํŠธ ์ถ”์ถœ
81
- if "choices" in response_data and len(response_data["choices"]) > 0:
82
- message = response_data["choices"][0].get("message", {})
83
- content = message.get("content", "")
84
- print("\n์‘๋‹ต ๋‚ด์šฉ:")
85
- print("-" * 40)
86
- print(content)
87
- print("-" * 40)
88
- else:
89
- print("\n๊ฒฝ๊ณ : ์‘๋‹ต์—์„œ 'choices' ํ•„๋“œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
90
-
91
- return True
92
- except json.JSONDecodeError:
93
- print("\n๊ฒฝ๊ณ : ์‘๋‹ต์„ JSON์œผ๋กœ ํŒŒ์‹ฑํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
94
- print("์›๋ณธ ์‘๋‹ต ๋‚ด์šฉ:")
95
- print(response.text)
96
- return False
97
- else:
98
- print("\n์˜ค๋ฅ˜: DeepSeek API ํ˜ธ์ถœ ์‹คํŒจ")
99
-
100
- try:
101
- error_data = response.json()
102
- print("\n์˜ค๋ฅ˜ ์ •๋ณด:")
103
- print(json.dumps(error_data, indent=2, ensure_ascii=False))
104
- except:
105
- print("\n์›๋ณธ ์˜ค๋ฅ˜ ์‘๋‹ต:")
106
- print(response.text)
107
-
108
- return False
109
-
110
- except requests.exceptions.Timeout:
111
- print("\n์˜ค๋ฅ˜: API ์š”์ฒญ ์‹œ๊ฐ„ ์ดˆ๊ณผ")
112
- return False
113
- except requests.exceptions.ConnectionError:
114
- print("\n์˜ค๋ฅ˜: API ์„œ๋ฒ„ ์—ฐ๊ฒฐ ์‹คํŒจ")
115
- return False
116
- except Exception as e:
117
- print(f"\n์˜ค๋ฅ˜: ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜ ๋ฐœ์ƒ - {str(e)}")
118
- return False
119
-
120
-
121
- if __name__ == "__main__":
122
- success = main()
123
- sys.exit(0 if success else 1)