Spaces:
Running
Running
| """ | |
| AI 서비스와의 통신을 담당하는 모듈 | |
| """ | |
| import time | |
| import streamlit as st | |
| import re | |
| import json | |
| try: | |
| import google.generativeai as genai | |
| except ImportError: | |
| genai = None | |
| from ..config.constants import BACKUP_RESPONSES, API_KEY_SECRET_NAME | |
| # 1시간 캐싱 | |
| def setup_gemini(): | |
| """ | |
| Gemini API 초기화 - 캐싱 및 오류 처리 개선 | |
| Returns: | |
| GenerativeModel or None: 초기화된 모델 인스턴스 또는 실패 시 None | |
| """ | |
| try: | |
| # Streamlit Secrets에서 API 키 가져오기 | |
| api_key = st.secrets.get(API_KEY_SECRET_NAME, None) | |
| if not api_key: | |
| st.sidebar.error("API 키가 설정되지 않음") | |
| st.session_state.use_backup_mode = True | |
| return None | |
| # Gemini API 초기화 | |
| genai.configure(api_key=api_key) | |
| # 최신 모델 이름으로 시도 | |
| try: | |
| model = genai.GenerativeModel("gemini-1.5-pro") | |
| return model | |
| except Exception as e: | |
| # 이전 모델 이름으로 시도 | |
| try: | |
| model = genai.GenerativeModel("gemini-pro") | |
| return model | |
| except Exception as inner_e: | |
| st.error(f"사용 가능한 Gemini 모델을 찾을 수 없습니다. 백업 응답을 사용합니다.") | |
| st.session_state.use_backup_mode = True | |
| return None | |
| except Exception as e: | |
| st.error(f"Gemini 모델 초기화 오류: {e}") | |
| st.session_state.use_backup_mode = True | |
| return None | |
| def generate_gemini_text(prompt, max_tokens=500, retries=2, timeout=10): | |
| """ | |
| Gemini API를 사용하여 텍스트 생성 - 오류 처리 및 재시도 로직 추가 | |
| Args: | |
| prompt (str): 텍스트 생성을 위한 프롬프트 | |
| max_tokens (int): 생성할 최대 토큰 수 | |
| retries (int): 실패 시 재시도 횟수 | |
| timeout (int): 타임아웃 시간(초) | |
| Returns: | |
| str: 생성된 텍스트 | |
| """ | |
| # 백업 모드 확인 | |
| if getattr(st.session_state, 'use_backup_mode', False): | |
| # 백업 모드면 즉시 백업 응답 반환 | |
| if "world" in prompt.lower(): | |
| return BACKUP_RESPONSES["world"] | |
| elif "character" in prompt.lower(): | |
| return BACKUP_RESPONSES["character"] | |
| elif "질문" in prompt.lower() or "question" in prompt.lower(): | |
| return BACKUP_RESPONSES["question"] | |
| else: | |
| return BACKUP_RESPONSES["story"] | |
| # 재시도 로직 | |
| for attempt in range(retries + 1): | |
| try: | |
| model = setup_gemini() | |
| if not model: | |
| # 모델 초기화 실패 시 백업 응답 사용 | |
| if "world" in prompt.lower(): | |
| return BACKUP_RESPONSES["world"] | |
| elif "character" in prompt.lower(): | |
| return BACKUP_RESPONSES["character"] | |
| elif "질문" in prompt.lower() or "question" in prompt.lower(): | |
| return BACKUP_RESPONSES["question"] | |
| else: | |
| return BACKUP_RESPONSES["story"] | |
| # 안전 설정 | |
| safety_settings = [ | |
| {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"}, | |
| {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_MEDIUM_AND_ABOVE"}, | |
| {"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"}, | |
| {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"} | |
| ] | |
| # 모델 생성 구성 | |
| generation_config = { | |
| "temperature": 0.7, | |
| "top_p": 0.95, | |
| "top_k": 40, | |
| "max_output_tokens": max_tokens, | |
| "stop_sequences": ["USER:", "ASSISTANT:"] | |
| } | |
| # 텍스트 생성 | |
| response = model.generate_content( | |
| prompt, | |
| generation_config=generation_config, | |
| safety_settings=safety_settings | |
| ) | |
| # 응답 텍스트 추출 및 길이 제한 | |
| text = response.text | |
| if len(text) > max_tokens * 4: | |
| text = text[:max_tokens * 4] + "..." | |
| return text | |
| except Exception as e: | |
| if attempt < retries: | |
| st.warning(f"API 호출 오류, 재시도 중... ({attempt+1}/{retries})") | |
| time.sleep(1) # 잠시 대기 후 재시도 | |
| continue | |
| else: | |
| st.error(f"Gemini API 호출 오류: {e}") | |
| st.session_state.use_backup_mode = True | |
| # 오류 발생 시 백업 응답 사용 | |
| if "world" in prompt.lower(): | |
| return BACKUP_RESPONSES["world"] | |
| elif "character" in prompt.lower(): | |
| return BACKUP_RESPONSES["character"] | |
| elif "질문" in prompt.lower() or "question" in prompt.lower(): | |
| return BACKUP_RESPONSES["question"] | |
| else: | |
| return BACKUP_RESPONSES["story"] | |
| # 이 코드는 실행되지 않음 (위에서 항상 반환함) | |
| return BACKUP_RESPONSES["story"] | |
| def generate_character_options(profession, theme): | |
| """ | |
| 직업과 테마에 기반한 캐릭터 배경 옵션 생성 | |
| Args: | |
| profession (str): 선택한 직업 | |
| theme (str): 세계관 테마 | |
| Returns: | |
| list: 배경 스토리 옵션 목록 | |
| """ | |
| prompt = f""" | |
| 당신은 TRPG 게임 마스터입니다. '{theme}' 테마의 세계에서 '{profession}' 직업을 가진 | |
| 캐릭터의 3가지 다른 배경 스토리 옵션을 한국어로 제안해주세요. | |
| 각 옵션은 다음 요소를 포함해야 합니다: | |
| ## 삼위일체 구조 | |
| 1. **배경 서사**: 캐릭터가 겪은 결정적 사건 3개 | |
| 2. **도덕적 축**: 선택을 규정하는 2가지 원칙 | |
| 3. **정체성 기반**: 타인에게 설명하는 5초 자기소개 | |
| ## 개성화를 위한 요소 | |
| - 캐릭터만의 독특한 특성이나 버릇 | |
| - 관계망 (가족, 멘토, 적대자 등) | |
| - 물리적 특징이나 외형적 특성 | |
| ## 직업 연계성 | |
| - 이 캐릭터가 해당 직업을 가지게 된 이유 | |
| - 직업 관련 전문 기술이나 지식 | |
| 각 옵션을 120단어 내외로 작성해주세요. | |
| 모든 문장은 완결된 형태로 작성하세요. | |
| 다음 형식으로 반환해주세요: | |
| #옵션 1: | |
| (첫 번째 배경 스토리) | |
| #옵션 2: | |
| (두 번째 배경 스토리) | |
| #옵션 3: | |
| (세 번째 배경 스토리) | |
| """ | |
| response = generate_gemini_text(prompt, 800) | |
| # 옵션 분리 | |
| options = [] | |
| current_option = "" | |
| for line in response.split('\n'): | |
| if line.startswith('#옵션') or line.startswith('# 옵션') or line.startswith('옵션'): | |
| if current_option: | |
| options.append(current_option.strip()) | |
| current_option = "" | |
| else: | |
| current_option += line + "\n" | |
| if current_option: | |
| options.append(current_option.strip()) | |
| # 옵션이 3개 미만이면 백업 옵션 추가 | |
| while len(options) < 3: | |
| options.append(f"당신은 {profession}으로, 험난한 세계에서 살아남기 위해 기술을 연마했습니다. 특별한 재능을 가지고 있으며, 자신의 운명을 개척하고자 합니다.") | |
| return options[:3] # 최대 3개까지만 반환 |