RemoteAgent / utils /openai_client.py
jeongsoo's picture
Initial setup
2382288
"""
OpenAI API ํด๋ผ์ด์–ธํŠธ ๋ชจ๋“ˆ
"""
import os
import json
import logging
from typing import List, Dict, Any, Optional, Union
from dotenv import load_dotenv
from openai import OpenAI
# ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋กœ๋“œ
load_dotenv()
# ๋กœ๊ฑฐ ์„ค์ •
logger = logging.getLogger("OpenAILLM")
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 OpenAILLM:
"""OpenAI API ๋ž˜ํผ ํด๋ž˜์Šค"""
def __init__(self):
"""OpenAI LLM ํด๋ž˜์Šค ์ดˆ๊ธฐํ™”"""
self.api_key = os.getenv("OPENAI_API_KEY")
self.model = os.getenv("OPENAI_MODEL", "gpt-3.5-turbo")
if not self.api_key:
logger.warning("OpenAI API ํ‚ค๊ฐ€ .env ํŒŒ์ผ์— ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
logger.warning("OPENAI_API_KEY๋ฅผ ํ™•์ธํ•˜์„ธ์š”.")
else:
# OpenAI ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™”
self.client = OpenAI(api_key=self.api_key)
logger.info("OpenAI API ํ‚ค ๋กœ๋“œ ์™„๋ฃŒ.")
def chat_completion(
self,
messages: List[Dict[str, str]],
temperature: float = 0.7,
max_tokens: int = 1000,
**kwargs
) -> Dict[str, Any]:
"""
OpenAI ์ฑ„ํŒ… ์™„์„ฑ API ํ˜ธ์ถœ
Args:
messages: ์ฑ„ํŒ… ๋ฉ”์‹œ์ง€ ๋ชฉ๋ก
temperature: ์ƒ์„ฑ ์˜จ๋„ (๋‚ฎ์„์ˆ˜๋ก ๊ฒฐ์ •์ )
max_tokens: ์ƒ์„ฑํ•  ์ตœ๋Œ€ ํ† ํฐ ์ˆ˜
**kwargs: ์ถ”๊ฐ€ API ๋งค๊ฐœ๋ณ€์ˆ˜
Returns:
API ์‘๋‹ต (๋”•์…”๋„ˆ๋ฆฌ)
"""
if not self.api_key:
logger.error("API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•„ OpenAI API๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
raise ValueError("OpenAI API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
try:
logger.info(f"OpenAI API ์š”์ฒญ ์ „์†ก ์ค‘ (๋ชจ๋ธ: {self.model})")
# ์ƒˆ๋กœ์šด OpenAI SDK๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ API ํ˜ธ์ถœ
response = self.client.chat.completions.create(
model=self.model,
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
**kwargs
)
return response
except Exception as e:
logger.error(f"OpenAI API ์š”์ฒญ ์‹คํŒจ: {e}")
raise Exception(f"OpenAI 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
)
# ์ƒˆ๋กœ์šด OpenAI SDK ์‘๋‹ต ๊ตฌ์กฐ์— ๋งž๊ฒŒ ์ฒ˜๋ฆฌ
if not response or not hasattr(response, 'choices') or not response.choices:
logger.error("OpenAI 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> ํƒœ๊ทธ ์•ˆ์— ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.
- ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์— ๋‹ต๋ณ€์ด ์žˆ์œผ๋ฉด ํ•ด๋‹น ์ •๋ณด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ช…ํ™•ํ•˜๊ฒŒ ๋‹ต๋ณ€ํ•˜์„ธ์š”.
- ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์— ๋‹ต๋ณ€์ด ์—†์œผ๋ฉด "๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์— ๊ด€๋ จ ์ •๋ณด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค"๋ผ๊ณ  ๋งํ•˜์„ธ์š”.
- ๊ฒ€์ƒ‰ ๋‚ด์šฉ์„ ๊ทธ๋Œ€๋กœ ๋ณต์‚ฌํ•˜์ง€ ๋ง๊ณ , ์ž์—ฐ์Šค๋Ÿฌ์šด ํ•œ๊ตญ์–ด๋กœ ๋‹ต๋ณ€์„ ์ž‘์„ฑํ•˜์„ธ์š”.
- ๋‹ต๋ณ€์€ ๊ฐ„๊ฒฐํ•˜๊ณ  ์ •ํ™•ํ•˜๊ฒŒ ์ œ๊ณตํ•˜์„ธ์š”."""
# ์ค‘์š”: ์ปจํ…์ŠคํŠธ ๊ธธ์ด ์ œํ•œ
# gpt-4o-mini์— ๋งž๊ฒŒ ์ œํ•œ ์™„ํ™”
max_context = 10 # 3๊ฐœ์—์„œ 10๊ฐœ๋กœ ์ฆ๊ฐ€
if len(context) > max_context:
logger.warning(f"์ปจํ…์ŠคํŠธ๊ฐ€ ๋„ˆ๋ฌด ๊ธธ์–ด ์ฒ˜์Œ {max_context}๊ฐœ๋งŒ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.")
context = context[:max_context]
# ๊ฐ ์ปจํ…์ŠคํŠธ ์•ก์„ธ์Šค
limited_context = []
for i, doc in enumerate(context):
# ๊ฐ ๋ฌธ์„œ๋ฅผ 1000์ž๋กœ ์ œํ•œ (์ด์ „ 500์ž์—์„œ ์—…๊ทธ๋ ˆ์ด๋“œ)
if len(doc) > 1000:
logger.warning(f"๋ฌธ์„œ {i+1}์˜ ๊ธธ์ด๊ฐ€ ์ œํ•œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค ({len(doc)} -> 1000)")
doc = doc[:1000] + "...(์ƒ๋žต)"
limited_context.append(doc)
context_text = "\n\n".join([f"๋ฌธ์„œ {i+1}: {doc}" for i, doc in enumerate(limited_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)}"