Agent 2.0 exdev 서버 배포 스택
- server-dev start/stop/deploy 및 Gitea push 자동 배포 - local-dev 로컬 개발 환경 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
"""
|
||||
LLM 핸들러 모듈
|
||||
─────────────
|
||||
LLM 답변 생성 및 후처리
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import Optional, List, Dict
|
||||
|
||||
|
||||
class LLMHandler:
|
||||
"""LLM 답변 생성 핸들러"""
|
||||
|
||||
def __init__(self, llm_client, config):
|
||||
self.llm_client = llm_client
|
||||
self.config = config
|
||||
|
||||
def generate_answer(
|
||||
self,
|
||||
system_prompt: str,
|
||||
user_prompt: str,
|
||||
ts: str,
|
||||
max_tokens: Optional[int] = None,
|
||||
temperature: float = 0.3
|
||||
) -> str:
|
||||
"""LLM 답변 생성 (기존 방식 - 하위 호환성)
|
||||
|
||||
Args:
|
||||
system_prompt: 시스템 프롬프트
|
||||
user_prompt: 사용자 프롬프트
|
||||
ts: 타임스탬프 (로깅용)
|
||||
max_tokens: 최대 토큰 수 (기본값: config에서 가져옴)
|
||||
temperature: 온도 파라미터
|
||||
|
||||
Returns:
|
||||
생성된 답변 텍스트
|
||||
"""
|
||||
if max_tokens is None:
|
||||
max_tokens = self.config.llm_max_tokens
|
||||
|
||||
try:
|
||||
print(f"[LLMHandler] {ts} LLM 답변 생성 중... (max_tokens={max_tokens})")
|
||||
|
||||
response = self.llm_client.chat_completion(
|
||||
messages=[
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": user_prompt}
|
||||
],
|
||||
max_tokens=max_tokens,
|
||||
temperature=temperature
|
||||
)
|
||||
|
||||
# <think> 태그 제거
|
||||
formatted = self._remove_think_tags(response)
|
||||
|
||||
print(f"[LLMHandler] {ts} LLM 답변 생성 완료 (길이: {len(formatted)}자)")
|
||||
return formatted
|
||||
|
||||
except Exception as e:
|
||||
print(f"[LLMHandler] {ts} ❌ LLM 답변 생성 실패: {e}")
|
||||
raise
|
||||
|
||||
def generate_answer_from_messages(
|
||||
self,
|
||||
messages: List[Dict[str, str]],
|
||||
ts: str,
|
||||
max_tokens: Optional[int] = None,
|
||||
temperature: float = 0.3
|
||||
) -> str:
|
||||
"""LLM 답변 생성 (Messages Format - 표준 방식)
|
||||
|
||||
Args:
|
||||
messages: [{"role": "system|user|assistant", "content": "..."}]
|
||||
ts: 타임스탬프 (로깅용)
|
||||
max_tokens: 최대 토큰 수
|
||||
temperature: 온도 파라미터
|
||||
|
||||
Returns:
|
||||
생성된 답변 텍스트
|
||||
"""
|
||||
if max_tokens is None:
|
||||
max_tokens = self.config.llm_max_tokens
|
||||
|
||||
try:
|
||||
print(f"[LLMHandler] {ts} LLM 답변 생성 중... (messages={len(messages)}개, max_tokens={max_tokens})")
|
||||
|
||||
response = self.llm_client.chat_completion(
|
||||
messages=messages,
|
||||
max_tokens=max_tokens,
|
||||
temperature=temperature
|
||||
)
|
||||
|
||||
# <think> 태그 제거
|
||||
formatted = self._remove_think_tags(response)
|
||||
|
||||
print(f"[LLMHandler] {ts} LLM 답변 생성 완료 (길이: {len(formatted)}자)")
|
||||
return formatted
|
||||
|
||||
except Exception as e:
|
||||
print(f"[LLMHandler] {ts} ❌ LLM 답변 생성 실패: {e}")
|
||||
raise
|
||||
|
||||
def generate_fallback_answer(self, best_match: dict) -> str:
|
||||
"""폴백 답변 생성 (LLM 실패 시)
|
||||
|
||||
Args:
|
||||
best_match: 최상위 매칭 결과
|
||||
|
||||
Returns:
|
||||
폴백 답변
|
||||
"""
|
||||
return (
|
||||
"고객님의 질문은 다음과 같다고 생각됩니다.\n\n"
|
||||
f"{best_match['q']}\n\n"
|
||||
"이에 대한 답변을 드리겠습니다.\n\n"
|
||||
f"{best_match['a']}"
|
||||
)
|
||||
|
||||
def generate_default_guidance(self) -> str:
|
||||
"""기본 질문 유도 답변 (LLM 실패 시)"""
|
||||
return (
|
||||
"죄송합니다. 고객님의 질문과 관련된 정보를 찾을 수 없습니다.\n\n"
|
||||
"한국도로공사는 고속도로 이용과 관련된 상담을 제공하고 있습니다.\n"
|
||||
"통행료, Hi-pass, 환불, 소음 민원, 시설물 이용 등에 대해 궁금하신 사항이 있으시다면 질문해 주세요.\n\n"
|
||||
"또는 한국도로공사 콜센터(1588-2504)로 문의하시면 자세한 안내를 받으실 수 있습니다."
|
||||
)
|
||||
|
||||
def _remove_think_tags(self, text: str) -> str:
|
||||
"""<think> 태그 제거"""
|
||||
if "<think>" in text and "</think>" in text:
|
||||
return re.sub(r'<think>.*?</think>\s*', '', text, flags=re.DOTALL).strip()
|
||||
return text
|
||||
Reference in New Issue
Block a user