jeongsoo's picture
init
6575706
"""
DeepSeek LLM API ํด๋ผ์ด์–ธํŠธ ๋ชจ๋“ˆ
"""
import os
import json
import logging
import requests
from typing import List, Dict, Any, Optional, Union
from dotenv import load_dotenv
# ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋กœ๋“œ
load_dotenv()
# ๋กœ๊ฑฐ ์„ค์ •
logger = logging.getLogger("DeepSeekLLM")
if not logger.hasHandlers():
handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.INFO)
class DeepSeekLLM:
"""DeepSeek LLM API ๋ž˜ํผ ํด๋ž˜์Šค"""
def __init__(self):
"""DeepSeek LLM ํด๋ž˜์Šค ์ดˆ๊ธฐํ™”"""
self.api_key = os.getenv("DEEPSEEK_API_KEY")
self.endpoint = os.getenv("DEEPSEEK_ENDPOINT", "https://api.deepseek.com/v1/chat/completions")
self.model = os.getenv("DEEPSEEK_MODEL", "deepseek-chat")
if not self.api_key:
logger.warning("DeepSeek API ํ‚ค๊ฐ€ .env ํŒŒ์ผ์— ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
logger.warning("DEEPSEEK_API_KEY๋ฅผ ํ™•์ธํ•˜์„ธ์š”.")
else:
logger.info("DeepSeek LLM API ํ‚ค ๋กœ๋“œ ์™„๋ฃŒ.")
def chat_completion(
self,
messages: List[Dict[str, str]],
temperature: float = 0.7,
max_tokens: int = 1000,
stream: bool = False,
**kwargs
) -> Dict[str, Any]:
"""
DeepSeek ์ฑ„ํŒ… ์™„์„ฑ API ํ˜ธ์ถœ
Args:
messages: ์ฑ„ํŒ… ๋ฉ”์‹œ์ง€ ๋ชฉ๋ก
temperature: ์ƒ์„ฑ ์˜จ๋„ (๋‚ฎ์„์ˆ˜๋ก ๊ฒฐ์ •์ )
max_tokens: ์ƒ์„ฑํ•  ์ตœ๋Œ€ ํ† ํฐ ์ˆ˜
stream: ์ŠคํŠธ๋ฆฌ๋ฐ ์‘๋‹ต ํ™œ์„ฑํ™” ์—ฌ๋ถ€
**kwargs: ์ถ”๊ฐ€ API ๋งค๊ฐœ๋ณ€์ˆ˜
Returns:
API ์‘๋‹ต (๋”•์…”๋„ˆ๋ฆฌ)
"""
if not self.api_key:
logger.error("API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•„ DeepSeek API๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
raise ValueError("DeepSeek API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": self.model,
"messages": messages,
"temperature": temperature,
"max_tokens": max_tokens,
"stream": stream,
**kwargs
}
try:
logger.info(f"DeepSeek API ์š”์ฒญ ์ „์†ก ์ค‘: {self.endpoint}")
response = requests.post(
self.endpoint,
headers=headers,
json=payload,
timeout=60 # ํƒ€์ž„์•„์›ƒ ์„ค์ •
)
response.raise_for_status()
if stream:
return response # ์ŠคํŠธ๋ฆฌ๋ฐ ์‘๋‹ต์€ ์›์‹œ ์‘๋‹ต ๊ฐ์ฒด ๋ฐ˜ํ™˜
else:
return response.json()
except requests.exceptions.Timeout:
logger.error("DeepSeek API ์š”์ฒญ ์‹œ๊ฐ„ ์ดˆ๊ณผ")
raise TimeoutError("DeepSeek API ์š”์ฒญ ์‹œ๊ฐ„ ์ดˆ๊ณผ")
except requests.exceptions.RequestException as e:
logger.error(f"DeepSeek API ์š”์ฒญ ์‹คํŒจ: {e}")
if hasattr(e, 'response') and e.response is not None:
logger.error(f"์‘๋‹ต ์ฝ”๋“œ: {e.response.status_code}, ๋‚ด์šฉ: {e.response.text}")
raise ConnectionError(f"DeepSeek API ์š”์ฒญ ์‹คํŒจ: {e}")
def generate(
self,
prompt: str,
system_prompt: Optional[str] = None,
temperature: float = 0.7,
max_tokens: int = 1000,
**kwargs
) -> str:
"""
๊ฐ„๋‹จํ•œ ํ…์ŠคํŠธ ์ƒ์„ฑ ์ธํ„ฐํŽ˜์ด์Šค
Args:
prompt: ์‚ฌ์šฉ์ž ํ”„๋กฌํ”„ํŠธ
system_prompt: ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ (์„ ํƒ ์‚ฌํ•ญ)
temperature: ์ƒ์„ฑ ์˜จ๋„
max_tokens: ์ƒ์„ฑํ•  ์ตœ๋Œ€ ํ† ํฐ ์ˆ˜
**kwargs: ์ถ”๊ฐ€ API ๋งค๊ฐœ๋ณ€์ˆ˜
Returns:
์ƒ์„ฑ๋œ ํ…์ŠคํŠธ
"""
messages = []
if system_prompt:
messages.append({"role": "system", "content": system_prompt})
messages.append({"role": "user", "content": prompt})
try:
response = self.chat_completion(
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
**kwargs
)
if not response or "choices" not in response or not response["choices"]:
logger.error("DeepSeek API ์‘๋‹ต์—์„œ ์ƒ์„ฑ๋œ ํ…์ŠคํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
return ""
return response["choices"][0]["message"]["content"].strip()
except Exception as e:
logger.error(f"ํ…์ŠคํŠธ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}")
return f"์˜ค๋ฅ˜: {str(e)}"
def rag_generate(
self,
query: str,
context: List[str],
system_prompt: Optional[str] = None,
temperature: float = 0.3,
max_tokens: int = 1000,
**kwargs
) -> str:
"""
RAG ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ํ™œ์šฉํ•œ ํ…์ŠคํŠธ ์ƒ์„ฑ
Args:
query: ์‚ฌ์šฉ์ž ์งˆ์˜
context: ๊ฒ€์ƒ‰๋œ ๋ฌธ๋งฅ ๋ชฉ๋ก
system_prompt: ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ (์„ ํƒ ์‚ฌํ•ญ)
temperature: ์ƒ์„ฑ ์˜จ๋„
max_tokens: ์ƒ์„ฑํ•  ์ตœ๋Œ€ ํ† ํฐ ์ˆ˜
**kwargs: ์ถ”๊ฐ€ API ๋งค๊ฐœ๋ณ€์ˆ˜
Returns:
์ƒ์„ฑ๋œ ํ…์ŠคํŠธ
"""
if not system_prompt:
system_prompt = """๋‹น์‹ ์€ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์งˆ๋ฌธ์— ๋‹ต๋ณ€ํ•˜๋Š” ๋„์šฐ๋ฏธ์ž…๋‹ˆ๋‹ค.
- ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋Š” <context> ํƒœ๊ทธ ์•ˆ์— ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.
- ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์— ๋‹ต๋ณ€์ด ์žˆ์œผ๋ฉด ํ•ด๋‹น ์ •๋ณด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ช…ํ™•ํ•˜๊ฒŒ ๋‹ต๋ณ€ํ•˜์„ธ์š”.
- ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์— ๋‹ต๋ณ€์ด ์—†์œผ๋ฉด "๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์— ๊ด€๋ จ ์ •๋ณด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค"๋ผ๊ณ  ๋งํ•˜์„ธ์š”.
- ๊ฒ€์ƒ‰ ๋‚ด์šฉ์„ ๊ทธ๋Œ€๋กœ ๋ณต์‚ฌํ•˜์ง€ ๋ง๊ณ , ์ž์—ฐ์Šค๋Ÿฌ์šด ํ•œ๊ตญ์–ด๋กœ ๋‹ต๋ณ€์„ ์ž‘์„ฑํ•˜์„ธ์š”.
- ๋‹ต๋ณ€์€ ๊ฐ„๊ฒฐํ•˜๊ณ  ์ •ํ™•ํ•˜๊ฒŒ ์ œ๊ณตํ•˜์„ธ์š”."""
context_text = "\n\n".join([f"๋ฌธ์„œ {i+1}: {doc}" for i, doc in enumerate(context)])
prompt = f"""์งˆ๋ฌธ: {query}
<context>
{context_text}
</context>
์œ„ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ์งˆ๋ฌธ์— ๋‹ต๋ณ€ํ•ด ์ฃผ์„ธ์š”."""
try:
return self.generate(
prompt=prompt,
system_prompt=system_prompt,
temperature=temperature,
max_tokens=max_tokens,
**kwargs
)
except Exception as e:
logger.error(f"RAG ํ…์ŠคํŠธ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}")
return f"์˜ค๋ฅ˜: {str(e)}"