my-tide-env / chatbot.py
alwaysgood's picture
Update chatbot.py
f77afbd verified
import os
import json
from datetime import datetime
import pytz
import traceback
from dateutil import parser as date_parser
# Local imports
from supabase_utils import get_supabase_client, get_tide_predictions
# Attempt to import Gemini
try:
import google.generativeai as genai
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
if GEMINI_API_KEY:
genai.configure(api_key=GEMINI_API_KEY)
GEMINI_AVAILABLE = True
else:
GEMINI_AVAILABLE = False
except ImportError:
GEMINI_AVAILABLE = False
# Station names (dependency for retrieve_context_from_db)
STATION_NAMES = {
"DT_0001": "인천", "DT_0065": "평택", "DT_0008": "안산", "DT_0067": "대산",
"DT_0043": "보령", "DT_0002": "군산", "DT_0050": "목포", "DT_0017": "제주",
"DT_0052": "여수", "DT_0025": "마산", "DT_0051": "부산", "DT_0037": "포항",
"DT_0068": "위도"
}
def parse_intent_with_llm(message: str) -> dict:
"""LLM을 사용해 사용자 질문에서 의도를 분석하고 JSON으로 반환"""
if not GEMINI_AVAILABLE:
return {"error": "Gemini API를 사용할 수 없습니다. API 키를 확인하세요."}
prompt = f"""
당신은 사용자의 자연어 질문을 분석하여 JSON 객체로 변환하는 전문가입니다.
질문에서 '관측소 이름', '원하는 정보', '시작 시간', '종료 시간'을 추출해주세요.
현재 시간은 {datetime.now(pytz.timezone('Asia/Seoul')).strftime('%Y-%m-%d %H:%M:%S')} KST 입니다.
- '원하는 정보'는 '특정 시간 조위' 또는 '구간 조위' 중 하나여야 합니다.
- '시작 시간'과 '종료 시간'은 'YYYY-MM-DD HH:MM:SS' 형식으로 변환해주세요.
- 단일 시간이면 시작과 종료 시간을 동일하게 설정하고, 구간이면 그에 맞게 설정하세요.
- 관측소 이름이 없으면 '인천'을 기본값으로 사용하세요.
[사용자 질문]: {message}
[JSON 출력]:
"""
try:
model = genai.GenerativeModel('gemini-1.5-flash', generation_config={"response_mime_type": "application/json"})
response = model.generate_content(prompt)
return json.loads(response.text)
except Exception as e:
return {"error": f"LLM 의도 분석 중 오류 발생: {e}"}
def retrieve_context_from_db(intent: dict) -> str:
"""분석된 의도를 바탕으로 데이터베이스에서 정보 검색"""
supabase = get_supabase_client()
if not supabase:
return "데이터베이스에 연결할 수 없습니다."
if "error" in intent:
return f"의도 분석에 실패했습니다: {intent['error']}"
station_name = intent.get("관측소 이름", "인천")
start_time_str = intent.get("시작 시간")
end_time_str = intent.get("종료 시간")
station_id = next((sid for sid, name in STATION_NAMES.items() if name == station_name), "DT_0001")
if not start_time_str or not end_time_str:
return "질문에서 시간 정보를 찾을 수 없습니다."
try:
# KST 시간을 UTC로 변환하여 쿼리
result_data = get_tide_predictions(station_id, start_time_str, end_time_str)
if result_data:
info_text = f"'{station_name}'의 '{start_time_str}'부터 '{end_time_str}'까지 조위 정보입니다.\n\n"
if len(result_data) > 10:
levels = [d['final_tide_level'] for d in result_data]
max_level = max(levels)
min_level = min(levels)
info_text += f"- 최고 조위: {max_level:.1f}cm\n- 최저 조위: {min_level:.1f}cm"
else:
for d in result_data:
# UTC 시간을 KST로 변환하여 표시
utc_time = date_parser.parse(d['predicted_at']).replace(tzinfo=pytz.UTC)
kst_time = utc_time.astimezone(pytz.timezone('Asia/Seoul'))
time_kst = kst_time.strftime('%H:%M')
info_text += f"- {time_kst}: 최종 조위 {d['final_tide_level']:.1f}cm (잔차 {d['predicted_residual']:.1f}cm)\n"
return info_text
else:
return "해당 기간의 예측 데이터를 찾을 수 없습니다. '통합 조위 예측' 탭에서 먼저 예측을 실행해주세요."
except Exception as e:
return f"데이터 검색 중 오류 발생: {traceback.format_exc()}"
def process_chatbot_query_with_llm(message: str, history: list) -> str:
"""최종 RAG 파이프라인"""
if not GEMINI_AVAILABLE:
return "Gemini API를 사용할 수 없습니다. API 키를 확인하세요."
intent = parse_intent_with_llm(message)
retrieved_data = retrieve_context_from_db(intent)
prompt = f"""당신은 친절한 해양 조위 정보 전문가입니다. 주어진 [검색된 데이터]를 바탕으로 사용자의 [질문]에 대해 자연스러운 문장으로 답변해주세요.
[검색된 데이터]: {retrieved_data}
[사용자 질문]: {message}
[답변]:"""
try:
model = genai.GenerativeModel('gemini-1.5-flash')
response = model.generate_content(prompt)
return response.text
except Exception as e:
return f"Gemini 답변 생성 중 오류가 발생했습니다: {e}"