jeongsoo commited on
Commit
315b102
Β·
1 Parent(s): ac1b0e8

deepseek_done

Browse files
Files changed (2) hide show
  1. config.py +113 -181
  2. direct_deepseek.py +61 -10
config.py CHANGED
@@ -1,6 +1,6 @@
1
  """
2
  벑터 μŠ€ν† μ–΄, μž„λ² λ”© λͺ¨λΈ, LLM λ“± ꡬ성 μš”μ†Œ μ„€μ •
3
- ν™˜κ²½ λ³€μˆ˜ 및 .env 파일 ν™œμš© κ°œμ„  버전 - 탐색 속도 μ΅œμ ν™”
4
  """
5
  import os
6
  import logging
@@ -21,49 +21,43 @@ logger.info(f"슀크립트 디렉토리: {script_dir}")
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
56
  IS_WINDOWS = os.name == 'nt'
57
 
58
- if IS_HUGGINGFACE:
59
- logger.info("HuggingFace Spaces ν™˜κ²½μ΄ κ°μ§€λ˜μ—ˆμŠ΅λ‹ˆλ‹€.")
60
- else:
61
- logger.info(f"둜컬 ν™˜κ²½μ—μ„œ μ‹€ν–‰ μ€‘μž…λ‹ˆλ‹€. (OS: {'Windows' if IS_WINDOWS else 'Unix/Linux/MacOS'})")
62
-
63
- # μœ ν‹Έλ¦¬ν‹° ν•¨μˆ˜: ν™˜κ²½ λ³€μˆ˜ κ°€μ Έμ˜€κΈ° (κΈ°λ³Έκ°’ 제곡)
64
  def get_env(key: str, default: Any = None, required: bool = False) -> Any:
65
  """
66
- ν™˜κ²½ λ³€μˆ˜λ₯Ό κ°€μ Έμ˜€λŠ” μœ ν‹Έλ¦¬ν‹° ν•¨μˆ˜
67
 
68
  Args:
69
  key: ν™˜κ²½ λ³€μˆ˜ ν‚€
@@ -73,11 +67,29 @@ def get_env(key: str, default: Any = None, required: bool = False) -> Any:
73
  Returns:
74
  ν™˜κ²½ λ³€μˆ˜ κ°’ λ˜λŠ” κΈ°λ³Έκ°’
75
  """
76
- value = os.getenv(key, default)
77
-
78
- if required and not value:
79
- logger.error(f"ν•„μˆ˜ ν™˜κ²½ λ³€μˆ˜ {key}κ°€ μ„€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
80
- raise ValueError(f"ν•„μˆ˜ ν™˜κ²½ λ³€μˆ˜ {key}κ°€ μ„€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
 
82
  return value
83
 
@@ -152,8 +164,8 @@ CHUNK_OVERLAP = int(get_env("CHUNK_OVERLAP", "200"))
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 κ΄€λ ¨ μ„€μ • μΆ”κ°€
@@ -161,6 +173,17 @@ DEEPSEEK_API_KEY = get_env("DEEPSEEK_API_KEY", "")
161
  DEEPSEEK_ENDPOINT = get_env("DEEPSEEK_ENDPOINT", "https://api.deepseek.com/v1/chat/completions")
162
  DEEPSEEK_MODEL = get_env("DEEPSEEK_MODEL", "deepseek-chat")
163
 
 
 
 
 
 
 
 
 
 
 
 
164
  # Milvus 벑터 DB μ„€μ •
165
  MILVUS_HOST = get_env("MILVUS_HOST", "localhost")
166
  MILVUS_PORT = get_env("MILVUS_PORT", "19530")
@@ -174,8 +197,46 @@ RERANKER_MODEL = get_env("RERANKER_MODEL", "Alibaba-NLP/gte-multilingual-reranke
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():
@@ -216,7 +277,7 @@ def test_deepseek_connection():
216
  response = requests.post(
217
  DEEPSEEK_ENDPOINT,
218
  headers=headers,
219
- data=json.dumps(payload),
220
  timeout=10 # 10초 νƒ€μž„μ•„μ›ƒ
221
  )
222
 
@@ -265,70 +326,6 @@ def test_deepseek_connection():
265
  "status_code": None
266
  }
267
 
268
- if IS_HUGGINGFACE:
269
- # HuggingFace ν™˜κ²½μ—μ„œλŠ” DeepSeek μ‚¬μš©
270
- if get_env("DEEPSEEK_API_KEY", ""):
271
- USE_DEEPSEEK = True
272
- USE_OPENAI = False
273
- LLM_MODEL = get_env("DEEPSEEK_MODEL", "deepseek-chat")
274
- logger.info("HuggingFace Spaces ν™˜κ²½ 감지: DeepSeek λͺ¨λΈ μ‚¬μš©")
275
-
276
- # DeepSeek API μ—°κ²° ν…ŒμŠ€νŠΈ
277
- deepseek_test_result = test_deepseek_connection()
278
- if deepseek_test_result["success"]:
279
- logger.info("DeepSeek API μ—°κ²° ν…ŒμŠ€νŠΈ 성곡")
280
- else:
281
- logger.warning(f"DeepSeek API μ—°κ²° ν…ŒμŠ€νŠΈ μ‹€νŒ¨: {deepseek_test_result['message']}")
282
- logger.info("OpenAI λͺ¨λΈλ‘œ ν΄λ°±ν•©λ‹ˆλ‹€")
283
- USE_DEEPSEEK = False
284
- USE_OPENAI = True
285
- LLM_MODEL = get_env("LLM_MODEL", "gpt-3.5-turbo")
286
- else:
287
- # DeepSeek API ν‚€κ°€ μ—†μœΌλ©΄ OpenAI μ‚¬μš©
288
- USE_OPENAI = True
289
- USE_DEEPSEEK = False
290
- LLM_MODEL = get_env("LLM_MODEL", "gpt-3.5-turbo")
291
- logger.info("HuggingFace Spaces ν™˜κ²½ 감지: OpenAI λͺ¨λΈ μ‚¬μš©")
292
- else:
293
- # 둜컬 ν™˜κ²½μ—μ„œλŠ” 섀정에 따라 선택
294
- if USE_DEEPSEEK:
295
- LLM_MODEL = get_env("DEEPSEEK_MODEL", "deepseek-chat")
296
- logger.info(f"DeepSeek λͺ¨λΈ μ‚¬μš©")
297
-
298
- # DeepSeek API μ—°κ²° ν…ŒμŠ€νŠΈ
299
- deepseek_test_result = test_deepseek_connection()
300
- if deepseek_test_result["success"]:
301
- logger.info("DeepSeek API μ—°κ²° ν…ŒμŠ€νŠΈ 성곡")
302
- else:
303
- logger.warning(f"DeepSeek API μ—°κ²° ν…ŒμŠ€νŠΈ μ‹€νŒ¨: {deepseek_test_result['message']}")
304
- if not USE_OPENAI:
305
- logger.info("Ollama둜 ν΄λ°±ν•©λ‹ˆλ‹€")
306
- USE_DEEPSEEK = False
307
- LLM_MODEL = get_env("LLM_MODEL", "gemma3:latest")
308
- else:
309
- logger.info("OpenAI λͺ¨λΈλ‘œ ν΄λ°±ν•©λ‹ˆλ‹€")
310
- USE_DEEPSEEK = False
311
- elif USE_OPENAI:
312
- LLM_MODEL = get_env("LLM_MODEL", "gpt-3.5-turbo")
313
- logger.info(f"OpenAI λͺ¨λΈ μ‚¬μš©")
314
- else:
315
- LLM_MODEL = get_env("LLM_MODEL", "gemma3:latest")
316
- logger.info(f"Ollama λͺ¨λΈ μ‚¬μš©")
317
-
318
- # API ν‚€ 검증
319
- if USE_DEEPSEEK and not DEEPSEEK_API_KEY:
320
- logger.warning("DeepSeek λͺ¨λΈμ΄ μ„ νƒλ˜μ—ˆμ§€λ§Œ API ν‚€κ°€ μ„€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
321
- USE_DEEPSEEK = False
322
- USE_OPENAI = False
323
- LLM_MODEL = get_env("LLM_MODEL", "gemma3:latest")
324
- logger.info("DeepSeek API ν‚€κ°€ μ—†μ–΄ Ollama둜 ν΄λ°±ν•©λ‹ˆλ‹€.")
325
- elif USE_OPENAI and not OPENAI_API_KEY:
326
- logger.warning("OpenAI λͺ¨λΈμ΄ μ„ νƒλ˜μ—ˆμ§€λ§Œ API ν‚€κ°€ μ„€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
327
- if not IS_HUGGINGFACE: # HuggingFace ν™˜κ²½μ—μ„œλŠ” 자체 API ν‚€λ₯Ό μ‚¬μš©ν•  수 있음
328
- logger.warning("OpenAI API ν‚€κ°€ μ—†μ–΄ Ollama둜 ν΄λ°±ν•©λ‹ˆλ‹€.")
329
- USE_OPENAI = False
330
- LLM_MODEL = get_env("LLM_MODEL", "gemma3:latest")
331
-
332
  # 벑터 검색 μ„€μ •
333
  TOP_K_RETRIEVAL = int(get_env("TOP_K_RETRIEVAL", "5")) # 벑터 검색 κ²°κ³Ό 수
334
  TOP_K_RERANK = int(get_env("TOP_K_RERANK", "3")) # λ¦¬λž­ν‚Ή ν›„ 선택할 κ²°κ³Ό 수
@@ -341,13 +338,15 @@ LOG_FILE = get_env("LOG_FILE", "autorag.log")
341
  def print_config():
342
  """ν˜„μž¬ μ„€μ • 정보λ₯Ό λ‘œκ·Έμ— 좜λ ₯"""
343
  logger.info("===== ν˜„μž¬ μ„€μ • 정보 =====")
 
344
  logger.info(f"λ¬Έμ„œ 디렉토리: {PDF_DIRECTORY}")
345
  logger.info(f"μΊμ‹œ 디렉토리: {CACHE_DIRECTORY}")
346
  logger.info(f"청크 크기: {CHUNK_SIZE}, μ˜€λ²„λž©: {CHUNK_OVERLAP}")
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}")
@@ -367,12 +366,16 @@ def validate_config() -> Dict[str, Any]:
367
  if not os.path.exists(PDF_DIRECTORY):
368
  warnings.append(f"PDF 디렉토리({PDF_DIRECTORY})κ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.")
369
 
370
- # API ν‚€ 확인
371
- if USE_OPENAI and not OPENAI_API_KEY:
372
- warnings.append("OpenAI μ‚¬μš©μ΄ μ„€μ •λ˜μ—ˆμ§€λ§Œ API ν‚€κ°€ μ œκ³΅λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
 
 
 
 
373
 
374
- if USE_DEEPSEEK and not DEEPSEEK_API_KEY:
375
- warnings.append("DeepSeek μ‚¬μš©μ΄ μ„€μ •λ˜μ—ˆμ§€λ§Œ API ν‚€κ°€ μ œκ³΅λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
376
 
377
  # λͺ¨λΈ 및 μ„€μ • κ°’ 확인
378
  if CHUNK_SIZE <= CHUNK_OVERLAP:
@@ -394,77 +397,6 @@ def validate_config() -> Dict[str, Any]:
394
  "warnings": warnings
395
  }
396
 
397
- # 디렉토리 λͺ©λ‘ ν•¨μˆ˜ (λ””λ²„κΉ…μš©)
398
- def list_directory_contents():
399
- """
400
- ν˜„μž¬ μž‘μ—… 디렉토리와 PDF_DIRECTORY의 λ‚΄μš©μ„ λ‘œκΉ…
401
- """
402
- # ν˜„μž¬ μž‘μ—… 디렉토리 λ‚΄μš©
403
- try:
404
- cwd_contents = os.listdir(os.getcwd())
405
- logger.info(f"ν˜„μž¬ μž‘μ—… 디렉토리 λ‚΄μš©: {cwd_contents}")
406
- except Exception as e:
407
- logger.error(f"ν˜„μž¬ μž‘μ—… 디렉토리 λ‚΄μš© 확인 μ‹€νŒ¨: {e}")
408
-
409
- # PDF 디렉토리 λ‚΄μš©
410
- try:
411
- if os.path.exists(PDF_DIRECTORY):
412
- pdf_dir_contents = os.listdir(PDF_DIRECTORY)
413
- logger.info(f"PDF 디렉토리 λ‚΄μš©: {pdf_dir_contents}")
414
-
415
- # PDF 파일만 필터링
416
- pdf_files = [f for f in pdf_dir_contents if f.lower().endswith('.pdf')]
417
- logger.info(f"PDF 디렉토리 λ‚΄ PDF 파일: {pdf_files}")
418
- else:
419
- logger.warning(f"PDF 디렉토리가 μ‘΄μž¬ν•˜μ§€ μ•ŠμŒ: {PDF_DIRECTORY}")
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
- """
426
- νŠΉμ • κ²½λ‘œμ—μ„œ PDF 파일 μ°ΎκΈ°
427
-
428
- Args:
429
- path: 검색할 경둜
430
-
431
- Returns:
432
- 발견된 PDF 파일 λͺ©λ‘
433
- """
434
- try:
435
- if os.path.exists(path) and os.path.isdir(path):
436
- pdf_files = [f for f in os.listdir(path) if f.lower().endswith('.pdf')]
437
- logger.info(f"경둜 '{path}'μ—μ„œ {len(pdf_files)}개의 PDF 파일 발견: {pdf_files}")
438
- return pdf_files
439
- else:
440
- logger.warning(f"κ²½λ‘œκ°€ μ‘΄μž¬ν•˜μ§€ μ•Šκ±°λ‚˜ 디렉토리가 μ•„λ‹˜: {path}")
441
- return []
442
- except Exception as e:
443
- logger.error(f"경둜 '{path}'μ—μ„œ PDF 파일 검색 쀑 였λ₯˜: {e}")
444
- return []
445
-
446
- # μœˆλ„μš°μ¦ˆ μ£Όμš” κ²½λ‘œμ—μ„œ PDF 파일 검색 (λ””λ²„κΉ…μš©)
447
- def find_pdfs_in_windows_paths():
448
- """μœˆλ„μš°μ¦ˆμ—μ„œ μ£Όμš” κ²½λ‘œμ— PDF 파일이 μžˆλŠ”μ§€ 확인"""
449
- if not IS_WINDOWS:
450
- return
451
-
452
- # 일반적인 μœˆλ„μš°μ¦ˆ κ²½λ‘œλ“€
453
- common_paths = [
454
- "C:\\Users\\USER\\RAG3\\documents",
455
- "C:\\Users\\USER\\Documents",
456
- os.path.join(os.environ.get('USERPROFILE', ''), 'Documents'),
457
- os.path.join(os.environ.get('USERPROFILE', ''), 'Downloads'),
458
- "documents",
459
- "."
460
- ]
461
-
462
- for path in common_paths:
463
- find_pdf_files_in_path(path)
464
-
465
- # μ„€μ • 정보 좜λ ₯ 및 검증 (λͺ¨λ“ˆ μž„ν¬νŠΈ μ‹œ μ‹€ν–‰)
466
  print_config()
467
- config_status = validate_config()
468
- list_directory_contents()
469
- if IS_WINDOWS:
470
- find_pdfs_in_windows_paths()
 
1
  """
2
  벑터 μŠ€ν† μ–΄, μž„λ² λ”© λͺ¨λΈ, LLM λ“± ꡬ성 μš”μ†Œ μ„€μ •
3
+ ν™˜κ²½ λ³€μˆ˜ 및 .env 파일 ν™œμš© κ°œμ„  버전 - HuggingFace ν™˜κ²½ 지원 μΆ”κ°€
4
  """
5
  import os
6
  import logging
 
21
  logger.info(f"ν˜„μž¬ μž‘μ—… 디렉토리: {os.getcwd()}")
22
  logger.info(f"운영 체제: {os.name}")
23
 
24
+ # ν™˜κ²½ 감지 - HuggingFace Space ν™˜κ²½μΈμ§€ 확인
25
+ IS_HUGGINGFACE = False
26
+ if os.getenv('SPACE_ID') is not None or os.getenv('SYSTEM') == 'spaces':
27
+ IS_HUGGINGFACE = True
28
+ logger.info("HuggingFace Spaces ν™˜κ²½μ΄ κ°μ§€λ˜μ—ˆμŠ΅λ‹ˆλ‹€.")
29
+ else:
30
+ # 둜컬 ν™˜κ²½μΈ 경우 .env 파일 λ‘œλ“œ
31
+ # .env 파일 μœ„μΉ˜ 후보듀
 
32
  env_paths = [
33
  ".env", # ν˜„μž¬ 디렉토리
34
  os.path.join(script_dir, ".env"), # 슀크립트 디렉토리
35
+ os.path.join(script_dir, "config", ".env"), # config ν•˜μœ„ 디렉토리
36
+ os.path.join(os.path.dirname(script_dir), ".env"), # μƒμœ„ 디렉토리
37
  ]
38
 
39
+ # .env 파일 μ°Ύμ•„μ„œ λ‘œλ“œ
40
+ env_loaded = False
41
  for env_path in env_paths:
42
  if os.path.isfile(env_path):
43
  logger.info(f".env 파일 발견: {env_path}")
44
+ env_loaded = load_dotenv(env_path, verbose=True)
45
+ if env_loaded:
46
  logger.info(f".env 파일 λ‘œλ“œ 성곡: {env_path}")
47
+ break
48
 
49
+ if not env_loaded:
50
+ logger.warning(".env νŒŒμΌμ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€. κΈ°λ³Έκ°’ λ˜λŠ” μ‹œμŠ€ν…œ ν™˜κ²½ λ³€μˆ˜λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.")
 
51
 
52
+ logger.info(f"둜컬 ν™˜κ²½μ—μ„œ μ‹€ν–‰ μ€‘μž…λ‹ˆλ‹€. (OS: {'Windows' if os.name == 'nt' else 'Unix/Linux/MacOS'})")
 
53
 
54
+ # Windows ν™˜κ²½ 감지
 
55
  IS_WINDOWS = os.name == 'nt'
56
 
57
+ # μœ ν‹Έλ¦¬ν‹° ν•¨μˆ˜: ν™˜κ²½ λ³€μˆ˜ κ°€μ Έμ˜€κΈ° (HuggingFace ν™˜κ²½κ³Ό 둜컬 ν™˜κ²½ ꡬ뢄)
 
 
 
 
 
58
  def get_env(key: str, default: Any = None, required: bool = False) -> Any:
59
  """
60
+ ν™˜κ²½ λ³€μˆ˜λ₯Ό κ°€μ Έμ˜€λŠ” μœ ν‹Έλ¦¬ν‹° ν•¨μˆ˜ (HuggingFace ν™˜κ²½ 지원)
61
 
62
  Args:
63
  key: ν™˜κ²½ λ³€μˆ˜ ν‚€
 
67
  Returns:
68
  ν™˜κ²½ λ³€μˆ˜ κ°’ λ˜λŠ” κΈ°λ³Έκ°’
69
  """
70
+ # HuggingFace Spaces ν™˜κ²½μ—μ„œλŠ” λ‚΄λΆ€ ν™˜κ²½λ³€μˆ˜ ν™œμš©
71
+ if IS_HUGGINGFACE:
72
+ # HuggingFace Spacesμ—μ„œλŠ” μ‹œν¬λ¦Ώ 값을 직접 μ‚¬μš©
73
+ # HF_SECRET_<KEY> ν˜•μ‹μœΌλ‘œ μ €μž₯된 μ‹œν¬λ¦Ώ 확인
74
+ hf_secret_key = f"HF_SECRET_{key.upper()}"
75
+ value = os.getenv(hf_secret_key)
76
+
77
+ # μ‹œν¬λ¦Ώμ΄ μ—†μœΌλ©΄ 일반 ν™˜κ²½λ³€μˆ˜ 확인
78
+ if value is None:
79
+ value = os.getenv(key, default)
80
+ else:
81
+ # 둜컬 ν™˜κ²½μ—μ„œλŠ” 일반적인 λ°©μ‹μœΌλ‘œ ν™˜κ²½λ³€μˆ˜ κ°€μ Έμ˜€κΈ°
82
+ value = os.getenv(key, default)
83
+
84
+ if required and value is None:
85
+ if IS_HUGGINGFACE:
86
+ error_msg = f"ν•„μˆ˜ ν™˜κ²½ λ³€μˆ˜ {key}κ°€ μ„€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. HuggingFace Spaceμ—μ„œ μ‹œν¬λ¦Ώμ„ μ„€μ •ν•΄μ£Όμ„Έμš”."
87
+ logger.error(error_msg)
88
+ raise ValueError(error_msg)
89
+ else:
90
+ error_msg = f"ν•„μˆ˜ ν™˜κ²½ λ³€μˆ˜ {key}κ°€ μ„€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. .env νŒŒμΌμ— μΆ”κ°€ν•΄μ£Όμ„Έμš”."
91
+ logger.error(error_msg)
92
+ raise ValueError(error_msg)
93
 
94
  return value
95
 
 
164
 
165
  # API ν‚€ 및 ν™˜κ²½ μ„€μ •
166
  OPENAI_API_KEY = get_env("OPENAI_API_KEY", "")
167
+ LANGFUSE_PUBLIC_KEY = get_env("LANGFUSE_PUBLIC_KEY", "")
168
+ LANGFUSE_SECRET_KEY = get_env("LANGFUSE_SECRET_KEY", "")
169
  LANGFUSE_HOST = get_env("LANGFUSE_HOST", "https://cloud.langfuse.com")
170
 
171
  # DeepSeek κ΄€λ ¨ μ„€μ • μΆ”κ°€
 
173
  DEEPSEEK_ENDPOINT = get_env("DEEPSEEK_ENDPOINT", "https://api.deepseek.com/v1/chat/completions")
174
  DEEPSEEK_MODEL = get_env("DEEPSEEK_MODEL", "deepseek-chat")
175
 
176
+ # ν—ˆκΉ…νŽ˜μ΄μŠ€ ν™˜κ²½μ—μ„œ API ν‚€ 확인 및 둜그 좜λ ₯
177
+ if IS_HUGGINGFACE:
178
+ logger.info(f"ν—ˆκΉ…νŽ˜μ΄μŠ€ ν™˜κ²½μ—μ„œ DeepSeek API ν‚€ 쑴재 μ—¬λΆ€: {bool(DEEPSEEK_API_KEY)}")
179
+ # λ³΄μ•ˆμ„ μœ„ν•΄ API ν‚€ 첫 4μžλ¦¬μ™€ λ§ˆμ§€λ§‰ 4자리만 ν‘œμ‹œ (ν‚€κ°€ μ‘΄μž¬ν•˜λŠ” 경우)
180
+ if DEEPSEEK_API_KEY:
181
+ masked_key = DEEPSEEK_API_KEY[:4] + "****" + DEEPSEEK_API_KEY[-4:] if len(DEEPSEEK_API_KEY) > 8 else "****"
182
+ logger.info(f"DeepSeek API ν‚€: {masked_key}")
183
+
184
+ logger.info(f"DeepSeek λͺ¨λΈ: {DEEPSEEK_MODEL}")
185
+ logger.info(f"DeepSeek μ—”λ“œν¬μΈνŠΈ: {DEEPSEEK_ENDPOINT}")
186
+
187
  # Milvus 벑터 DB μ„€μ •
188
  MILVUS_HOST = get_env("MILVUS_HOST", "localhost")
189
  MILVUS_PORT = get_env("MILVUS_PORT", "19530")
 
197
  USE_OPENAI = get_env("USE_OPENAI", "False").lower() == "true"
198
  USE_DEEPSEEK = get_env("USE_DEEPSEEK", "False").lower() == "true"
199
 
200
+ # ν—ˆκΉ…νŽ˜μ΄μŠ€ ν™˜κ²½μ—μ„œλŠ” DeepSeek μš°μ„  μ‚¬μš©
201
+ if IS_HUGGINGFACE:
202
+ # ν—ˆκΉ…νŽ˜μ΄μŠ€ ν™˜κ²½μ—μ„œ DeepSeek API ν‚€κ°€ μžˆλŠ”μ§€ 확인
203
+ if DEEPSEEK_API_KEY:
204
+ USE_DEEPSEEK = True
205
+ USE_OPENAI = False
206
+ LLM_MODEL = DEEPSEEK_MODEL
207
+ logger.info("HuggingFace Spaces ν™˜κ²½: DeepSeek λͺ¨λΈ μ‚¬μš©")
208
+ else:
209
+ logger.warning("HuggingFace Spaces ν™˜κ²½μ—μ„œ DeepSeek API ν‚€κ°€ μ„€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
210
+ USE_DEEPSEEK = False
211
+ USE_OPENAI = False # 기본적으둜 API ν‚€κ°€ μ—†μœΌλ©΄ λΉ„ν™œμ„±ν™”
212
+ LLM_MODEL = get_env("LLM_MODEL", "gemma3:latest") # λŒ€μ²΄ λͺ¨λΈ μ„€μ •
213
+ logger.info(f"HuggingFace Spaces ν™˜κ²½: DeepSeek API ν‚€ μ—†μŒ, LLM λͺ¨λΈ: {LLM_MODEL}")
214
+ else:
215
+ # 둜컬 ν™˜κ²½μ—μ„œλŠ” 섀정에 따라 LLM 선택
216
+ if USE_DEEPSEEK:
217
+ LLM_MODEL = DEEPSEEK_MODEL
218
+ logger.info(f"둜컬 ν™˜κ²½: DeepSeek λͺ¨λΈ μ‚¬μš© ({DEEPSEEK_MODEL})")
219
+ elif USE_OPENAI:
220
+ LLM_MODEL = get_env("LLM_MODEL", "gpt-3.5-turbo")
221
+ logger.info(f"둜컬 ν™˜κ²½: OpenAI λͺ¨λΈ μ‚¬μš© ({LLM_MODEL})")
222
+ else:
223
+ LLM_MODEL = get_env("LLM_MODEL", "gemma3:latest")
224
+ OLLAMA_HOST = get_env("OLLAMA_HOST", "http://localhost:11434")
225
+ logger.info(f"둜컬 ν™˜κ²½: Ollama λͺ¨λΈ μ‚¬μš© ({LLM_MODEL})")
226
+
227
+ # API ν‚€ 검증 (둜컬 ν™˜κ²½λ§Œ)
228
+ if not IS_HUGGINGFACE:
229
+ if USE_DEEPSEEK and not DEEPSEEK_API_KEY:
230
+ logger.warning("DeepSeek λͺ¨λΈμ΄ μ„ νƒλ˜μ—ˆμ§€λ§Œ API ν‚€κ°€ μ„€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
231
+ USE_DEEPSEEK = False
232
+ USE_OPENAI = False
233
+ LLM_MODEL = get_env("LLM_MODEL", "gemma3:latest")
234
+ logger.info("DeepSeek API ν‚€κ°€ μ—†μ–΄ Ollama둜 ν΄λ°±ν•©λ‹ˆλ‹€.")
235
+ elif USE_OPENAI and not OPENAI_API_KEY:
236
+ logger.warning("OpenAI λͺ¨λΈμ΄ μ„ νƒλ˜μ—ˆμ§€λ§Œ API ν‚€κ°€ μ„€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
237
+ logger.warning("OpenAI API ν‚€κ°€ μ—†μ–΄ Ollama둜 ν΄λ°±ν•©λ‹ˆλ‹€.")
238
+ USE_OPENAI = False
239
+ LLM_MODEL = get_env("LLM_MODEL", "gemma3:latest")
240
 
241
  # DeepSeek API ν…ŒμŠ€νŠΈ ν•¨μˆ˜
242
  def test_deepseek_connection():
 
277
  response = requests.post(
278
  DEEPSEEK_ENDPOINT,
279
  headers=headers,
280
+ json=payload,
281
  timeout=10 # 10초 νƒ€μž„μ•„μ›ƒ
282
  )
283
 
 
326
  "status_code": None
327
  }
328
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
  # 벑터 검색 μ„€μ •
330
  TOP_K_RETRIEVAL = int(get_env("TOP_K_RETRIEVAL", "5")) # 벑터 검색 κ²°κ³Ό 수
331
  TOP_K_RERANK = int(get_env("TOP_K_RERANK", "3")) # λ¦¬λž­ν‚Ή ν›„ 선택할 κ²°κ³Ό 수
 
338
  def print_config():
339
  """ν˜„μž¬ μ„€μ • 정보λ₯Ό λ‘œκ·Έμ— 좜λ ₯"""
340
  logger.info("===== ν˜„μž¬ μ„€μ • 정보 =====")
341
+ logger.info(f"μ‹€ν–‰ ν™˜κ²½: {'HuggingFace Spaces' if IS_HUGGINGFACE else '둜컬'}")
342
  logger.info(f"λ¬Έμ„œ 디렉토리: {PDF_DIRECTORY}")
343
  logger.info(f"μΊμ‹œ 디렉토리: {CACHE_DIRECTORY}")
344
  logger.info(f"청크 크기: {CHUNK_SIZE}, μ˜€λ²„λž©: {CHUNK_OVERLAP}")
345
  logger.info(f"OpenAI μ‚¬μš©: {USE_OPENAI}")
346
  logger.info(f"DeepSeek μ‚¬μš©: {USE_DEEPSEEK}")
347
  logger.info(f"LLM λͺ¨λΈ: {LLM_MODEL}")
348
+ if not USE_OPENAI and not USE_DEEPSEEK and not IS_HUGGINGFACE:
349
+ logger.info(f"Ollama 호슀트: {OLLAMA_HOST}")
350
  logger.info(f"μž„λ² λ”© λͺ¨λΈ: {EMBEDDING_MODEL}")
351
  logger.info(f"리랭컀 λͺ¨λΈ: {RERANKER_MODEL}")
352
  logger.info(f"TOP_K 검색: {TOP_K_RETRIEVAL}, λ¦¬λž­ν‚Ή: {TOP_K_RERANK}")
 
366
  if not os.path.exists(PDF_DIRECTORY):
367
  warnings.append(f"PDF 디렉토리({PDF_DIRECTORY})κ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.")
368
 
369
+ # API ν‚€ 확인 (ν—ˆκΉ…νŽ˜μ΄μŠ€μ™€ 둜컬 ν™˜κ²½ ꡬ뢄)
370
+ if IS_HUGGINGFACE:
371
+ if USE_DEEPSEEK and not DEEPSEEK_API_KEY:
372
+ warnings.append("ν—ˆκΉ…νŽ˜μ΄μŠ€ ν™˜κ²½μ—μ„œ DeepSeek μ‚¬μš©μ΄ μ„€μ •λ˜μ—ˆμ§€λ§Œ API ν‚€κ°€ μ œκ³΅λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
373
+ else:
374
+ if USE_OPENAI and not OPENAI_API_KEY:
375
+ warnings.append("OpenAI μ‚¬μš©μ΄ μ„€μ •λ˜μ—ˆμ§€λ§Œ API ν‚€κ°€ μ œκ³΅λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
376
 
377
+ if USE_DEEPSEEK and not DEEPSEEK_API_KEY:
378
+ warnings.append("DeepSeek μ‚¬μš©μ΄ μ„€μ •λ˜μ—ˆμ§€λ§Œ API ν‚€κ°€ μ œκ³΅λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
379
 
380
  # λͺ¨λΈ 및 μ„€μ • κ°’ 확인
381
  if CHUNK_SIZE <= CHUNK_OVERLAP:
 
397
  "warnings": warnings
398
  }
399
 
400
+ # μ„€μ • λ‘œλ“œ μ‹œ μ‹€ν–‰
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
401
  print_config()
402
+ config_status = validate_config()
 
 
 
direct_deepseek.py CHANGED
@@ -1,5 +1,5 @@
1
  """
2
- 직접 DeepSeek API ν˜ΈμΆœμ„ μœ„ν•œ ν΄λΌμ΄μ–ΈνŠΈ κ΅¬ν˜„
3
  """
4
  import os
5
  import time
@@ -11,24 +11,58 @@ from typing import Dict, Any, Optional, List
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,
@@ -71,6 +105,16 @@ class DirectDeepSeekClient:
71
  Returns:
72
  생성 κ²°κ³Ό λ”•μ…”λ„ˆλ¦¬ (success, response, message λ“±)
73
  """
 
 
 
 
 
 
 
 
 
 
74
  # API μš”μ²­ 헀더 및 데이터
75
  headers = {
76
  "Content-Type": "application/json",
@@ -235,14 +279,21 @@ 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?")
 
1
  """
2
+ 직접 DeepSeek API ν˜ΈμΆœμ„ μœ„ν•œ ν΄λΌμ΄μ–ΈνŠΈ κ΅¬ν˜„ - ν—ˆκΉ…νŽ˜μ΄μŠ€ ν™˜κ²½ 지원
3
  """
4
  import os
5
  import time
 
11
  # λ‘œκΉ… μ„€μ •
12
  logger = logging.getLogger("DirectDeepSeek")
13
 
14
+ # ν™˜κ²½ 감지
15
+ IS_HUGGINGFACE = os.getenv('SPACE_ID') is not None or os.getenv('SYSTEM') == 'spaces'
16
+
17
  class DirectDeepSeekClient:
18
  """
19
  DeepSeek APIλ₯Ό 직접 ν˜ΈμΆœν•˜λŠ” ν΄λΌμ΄μ–ΈνŠΈ
20
  OpenAI ν΄λΌμ΄μ–ΈνŠΈλ₯Ό μš°νšŒν•˜κ³  직접 HTTP μš”μ²­ μ‚¬μš©
21
+ ν—ˆκΉ…νŽ˜μ΄μŠ€ ν™˜κ²½ 지원
22
  """
23
+ def __init__(self, api_key: Optional[str] = None, model_name: str = "deepseek-chat"):
24
  """
25
  ν΄λΌμ΄μ–ΈνŠΈ μ΄ˆκΈ°ν™”
26
 
27
  Args:
28
+ api_key: DeepSeek API ν‚€ (None인 경우 ν™˜κ²½λ³€μˆ˜μ—μ„œ κ°€μ Έμ˜΄)
29
  model_name: μ‚¬μš©ν•  λͺ¨λΈ 이름 (κΈ°λ³Έκ°’: "deepseek-chat")
30
  """
31
+ # API ν‚€ μ„€μ • (ν—ˆκΉ…νŽ˜μ΄μŠ€ ν™˜κ²½ 확인)
32
+ if api_key is None:
33
+ if IS_HUGGINGFACE:
34
+ # ν—ˆκΉ…νŽ˜μ΄μŠ€ ν™˜κ²½μ—μ„œλŠ” μ‹œν¬λ¦Ώμ—μ„œ κ°€μ Έμ˜€κΈ° μ‹œλ„
35
+ api_key = os.getenv('HF_SECRET_DEEPSEEK_API_KEY')
36
+ if not api_key:
37
+ # μ‹œν¬λ¦Ώμ΄ μ—†μœΌλ©΄ 일반 ν™˜κ²½λ³€μˆ˜ 확인
38
+ api_key = os.getenv("DEEPSEEK_API_KEY", "")
39
+ else:
40
+ # 둜컬 ν™˜κ²½μ—μ„œλŠ” ν™˜κ²½λ³€μˆ˜ μ‚¬μš©
41
+ api_key = os.getenv("DEEPSEEK_API_KEY", "")
42
+
43
  self.api_key = api_key
44
  self.model_name = model_name
45
+
46
+ # μ—”λ“œν¬μΈνŠΈ μ„€μ • (ν—ˆκΉ…νŽ˜μ΄μŠ€ ν™˜κ²½ 확인)
47
+ if IS_HUGGINGFACE:
48
+ # ν—ˆκΉ…νŽ˜μ΄μŠ€ ν™˜κ²½μ—μ„œλŠ” μ‹œν¬λ¦Ώμ—μ„œ κ°€μ Έμ˜€κΈ° μ‹œλ„
49
+ self.endpoint = os.getenv('HF_SECRET_DEEPSEEK_ENDPOINT')
50
+ if not self.endpoint:
51
+ # μ‹œν¬λ¦Ώμ΄ μ—†μœΌλ©΄ 일반 ν™˜κ²½λ³€μˆ˜ 확인
52
+ self.endpoint = os.getenv("DEEPSEEK_ENDPOINT", "https://api.deepseek.com/v1/chat/completions")
53
+ else:
54
+ # 둜컬 ν™˜κ²½μ—μ„œλŠ” ν™˜κ²½λ³€μˆ˜ μ‚¬μš©
55
+ self.endpoint = os.getenv("DEEPSEEK_ENDPOINT", "https://api.deepseek.com/v1/chat/completions")
56
+
57
  logger.info(f"DirectDeepSeekClient μ΄ˆκΈ°ν™”: λͺ¨λΈ={model_name}, μ—”λ“œν¬μΈνŠΈ={self.endpoint}")
58
 
59
+ # API ν‚€ 확인
60
+ if not self.api_key:
61
+ if IS_HUGGINGFACE:
62
+ logger.warning("ν—ˆκΉ…νŽ˜μ΄μŠ€ ν™˜κ²½μ—μ„œ DeepSeek API ν‚€κ°€ μ„€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. Space μ‹œν¬λ¦Ώμ„ ν™•μΈν•˜μ„Έμš”.")
63
+ else:
64
+ logger.warning("DeepSeek API ν‚€κ°€ μ„€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. .env νŒŒμΌμ΄λ‚˜ ν™˜κ²½λ³€μˆ˜λ₯Ό ν™•μΈν•˜μ„Έμš”.")
65
+
66
  def generate(self,
67
  prompt: str,
68
  temperature: float = 0.3,
 
105
  Returns:
106
  생성 κ²°κ³Ό λ”•μ…”λ„ˆλ¦¬ (success, response, message λ“±)
107
  """
108
+ # API ν‚€ 확인
109
+ if not self.api_key:
110
+ error_msg = "DeepSeek API ν‚€κ°€ μ„€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€."
111
+ logger.error(error_msg)
112
+ return {
113
+ "success": False,
114
+ "message": error_msg,
115
+ "status_code": None
116
+ }
117
+
118
  # API μš”μ²­ 헀더 및 데이터
119
  headers = {
120
  "Content-Type": "application/json",
 
279
  # λ‘œκΉ… μ„€μ •
280
  logging.basicConfig(level=logging.INFO)
281
 
282
+ # ν—ˆκΉ…νŽ˜μ΄μŠ€ ν™˜κ²½ 확인
283
+ if IS_HUGGINGFACE:
284
+ print("ν—ˆκΉ…νŽ˜μ΄μŠ€ ν™˜κ²½μ—μ„œ μ‹€ν–‰ μ€‘μž…λ‹ˆλ‹€.")
285
+ print("HF_SECRET_DEEPSEEK_API_KEY μ‹œν¬λ¦Ώ 섀정이 ν•„μš”ν•©λ‹ˆλ‹€.")
286
+ else:
287
+ print("둜컬 ν™˜κ²½μ—μ„œ μ‹€ν–‰ μ€‘μž…λ‹ˆλ‹€.")
288
+ print("DEEPSEEK_API_KEY ν™˜κ²½λ³€μˆ˜ 섀정이 ν•„μš”ν•©λ‹ˆλ‹€.")
289
 
290
  # ν΄λΌμ΄μ–ΈνŠΈ 생성
291
+ client = DirectDeepSeekClient()
292
+
293
+ # API ν‚€ 확인
294
+ if not client.api_key:
295
+ print("DeepSeek API ν‚€κ°€ μ„€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
296
+ exit(1)
297
 
298
  # κ°„λ‹¨ν•œ ν…ŒμŠ€νŠΈ
299
  response = client.generate("Hello, what can you do?")