SOMA-Oriental / app.py
aiqtech's picture
Update app.py
5d1c552 verified
raw
history blame
44.2 kB
import gradio as gr
import os
import json
import requests
from datetime import datetime
import time
from typing import List, Dict, Any, Generator, Tuple
import logging
import re
# ๋กœ๊น… ์„ค์ •
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# ์ถ”๊ฐ€ ์ž„ํฌํŠธ
from bs4 import BeautifulSoup
from urllib.parse import urlparse
import urllib.request
# Gemini API ์ž„ํฌํŠธ
try:
from google import genai
from google.genai import types
GEMINI_AVAILABLE = True
except ImportError:
GEMINI_AVAILABLE = False
logger.warning("Google Gemini API๊ฐ€ ์„ค์น˜๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. pip install google-genai๋กœ ์„ค์น˜ํ•˜์„ธ์š”.")
# ํ™˜๊ฒฝ ๋ณ€์ˆ˜์—์„œ ํ† ํฐ ๊ฐ€์ ธ์˜ค๊ธฐ
FRIENDLI_TOKEN = os.getenv("FRIENDLI_TOKEN", "YOUR_FRIENDLI_TOKEN")
BAPI_TOKEN = os.getenv("BAPI_TOKEN", "YOUR_BRAVE_API_TOKEN")
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "YOUR_GEMINI_API_KEY")
API_URL = "https://api.friendli.ai/dedicated/v1/chat/completions"
BRAVE_SEARCH_URL = "https://api.search.brave.com/res/v1/web/search"
MODEL_ID = "dep89a2fld32mcm"
TEST_MODE = os.getenv("TEST_MODE", "false").lower() == "true"
# ์ „์—ญ ๋ณ€์ˆ˜
conversation_history = []
class WuxingLLMSystem:
"""์˜คํ–‰ยท์˜ค์ƒ ๊ธฐ๋ฐ˜ ํ˜‘๋ ฅ์  LLM ์‹œ์Šคํ…œ"""
def __init__(self):
self.token = FRIENDLI_TOKEN
self.bapi_token = BAPI_TOKEN
self.gemini_api_key = GEMINI_API_KEY
self.api_url = API_URL
self.brave_url = BRAVE_SEARCH_URL
self.model_id = MODEL_ID
self.test_mode = TEST_MODE or (self.token == "YOUR_FRIENDLI_TOKEN")
self.use_gemini = False
self.gemini_client = None
# ์˜คํ–‰ ์—ญํ•  ์ •์˜
self.wuxing_roles = {
"wood": {
"name": "๊ฐ๋…๊ด€ (ํŒ€์žฅ)",
"virtue": "ไป",
"element": "ๆœจ",
"traits": "ํฌ์šฉยท์„ฑ์žฅํ˜• ๋ฆฌ๋”",
"expertise": "๋น„์ „ ์ œ์‹œ, ํŒ€ ์กฐ์œจ, ์ธ์žฌ ์œก์„ฑ",
"color": "#10b981" # Green
},
"fire": {
"name": "์ „๋žตยท๊ธฐํš ๋ฆฌ๋”",
"virtue": "็พฉ",
"element": "็ซ",
"traits": "์—ด์ •ยท๊ฒฐ๋‹จ, ๊ฐœ์ฒ™ ์ •์‹ ",
"expertise": "์ค‘ยท์žฅ๊ธฐ ๋กœ๋“œ๋งต, ์‚ฌ์—… ๋ชจ๋ธ ์„ค๊ณ„, ๋ฆฌ์Šคํฌ-๋ณด์ƒ ์‹œ๋‚˜๋ฆฌ์˜ค",
"color": "#ef4444" # Red
},
"metal": {
"name": "์•„ํ‚คํ…์ฒ˜ & ํ‘œ์ค€ ์ฑ…์ž„",
"virtue": "็ฆฎ",
"element": "้‡‘",
"traits": "๊ตฌ์กฐํ™”ยท์ •๋ฐ€, ์‹œ์Šคํ…œ ์„ค๊ณ„ ๋งˆ์Šคํ„ฐ",
"expertise": "๊ธฐ์ˆ ยท๋ฐ์ดํ„ฐ ์•„ํ‚คํ…์ฒ˜, ํ‘œ์ค€ ์ˆ˜๋ฆฝ, ํ’ˆ์งˆยทํ™•์žฅ์„ฑ ๊ฒ€์ฆ",
"color": "#f59e0b" # Gold
},
"water": {
"name": "๋„๊ตฌ ํ™œ์šฉ R&D ์ŠคํŽ˜์…œ๋ฆฌ์ŠคํŠธ",
"virtue": "ๆ™บ",
"element": "ๆฐด",
"traits": "๋ถ„์„ยทํ˜ธ๊ธฐ์‹ฌ, ITยทAI ๋„๊ตฌ ์ „๋ฌธ๊ฐ€",
"expertise": "์ตœ์‹  ๊ธฐ์ˆ ยท์‹œ์žฅ ์กฐ์‚ฌ, ํ”„๋กœํ† ํƒ€์ž… ๊ฐœ๋ฐœ, ์ž๋™ํ™”ยท์ƒ์‚ฐ์„ฑ ํˆด",
"color": "#3b82f6" # Blue
},
"earth": {
"name": "์‹คํ–‰ยท์šด์˜ยทํ’ˆ์งˆ ๋‹ด๋‹น",
"virtue": "ไฟก",
"element": "ๅœŸ",
"traits": "์‹ ๋ขฐยท์„ฑ์‹ค, ์‹คํ–‰๋ ฅ",
"expertise": "์ผ์ •ยท์˜ˆ์‚ฐยท๋ฆฌ์†Œ์Šค ๊ด€๋ฆฌ, ์šด์˜ ์ตœ์ ํ™”, ํ’ˆ์งˆ ๋ณด์ฆ",
"color": "#a855f7" # Purple
}
}
if self.test_mode:
logger.warning("ํ…Œ์ŠคํŠธ ๋ชจ๋“œ๋กœ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.")
def set_llm_mode(self, mode: str):
"""LLM ๋ชจ๋“œ ์„ค์ •"""
if mode == "commercial" and GEMINI_AVAILABLE and self.gemini_api_key != "YOUR_GEMINI_API_KEY":
self.use_gemini = True
if not self.gemini_client:
self.gemini_client = genai.Client(api_key=self.gemini_api_key)
logger.info("Gemini 2.5 Pro ๋ชจ๋“œ๋กœ ์ „ํ™˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
else:
self.use_gemini = False
logger.info("๊ธฐ๋ณธ LLM ๋ชจ๋“œ๋กœ ์ „ํ™˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
def create_headers(self):
"""API ํ—ค๋” ์ƒ์„ฑ"""
return {
"Authorization": f"Bearer {self.token}",
"Content-Type": "application/json"
}
def create_brave_headers(self):
"""Brave API ํ—ค๋” ์ƒ์„ฑ"""
return {
"Accept": "application/json",
"Accept-Encoding": "gzip",
"X-Subscription-Token": self.bapi_token
}
def create_wood_initial_prompt(self, user_query: str, search_results: Dict = None) -> str:
"""ๆœจ(๊ฐ๋…๊ด€) ์ดˆ๊ธฐ ํ”„๋กฌํ”„ํŠธ"""
search_info = ""
if search_results:
search_info = f"\n\n์›น ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ:\n{self._format_search_results(search_results)}"
return f"""๋‹น์‹ ์€ ไป(์ธ์žํ•จ)์˜ ๋•๋ชฉ์„ ์ง€๋‹Œ ๆœจ์˜ ๊ธฐ์šด์„ ๊ฐ€์ง„ ๊ฐ๋…๊ด€์ž…๋‹ˆ๋‹ค.
ํฌ์šฉ์ ์ด๊ณ  ์„ฑ์žฅ์ง€ํ–ฅ์ ์ธ ๋ฆฌ๋”์‹ญ์œผ๋กœ ํŒ€์„ ์ด๋•๋‹ˆ๋‹ค.
์‚ฌ์šฉ์ž ์งˆ๋ฌธ: {user_query}
{search_info}
ํŒ€์žฅ์œผ๋กœ์„œ ์ด ์งˆ๋ฌธ์— ๋Œ€ํ•ด:
1. ์ „์ฒด์ ์ธ ๋น„์ „๊ณผ ๋ฐฉํ–ฅ์„ฑ์„ ์ œ์‹œํ•˜์„ธ์š”
2. ๊ฐ ํŒ€์›(็ซ, ๅœŸ, ้‡‘, ๆฐด)์˜ ์—ญํ• ๊ณผ ๊ธฐ์—ฌ ๋ฐฉํ–ฅ์„ ์„ค๊ณ„ํ•˜์„ธ์š”
3. ์„ฑ์žฅ๊ณผ ๋ฐœ์ „์˜ ๊ด€์ ์—์„œ ํ•ต์‹ฌ ๋ชฉํ‘œ๋ฅผ ์„ค์ •ํ•˜์„ธ์š”
4. ํŒ€ ์ „์ฒด๊ฐ€ ์กฐํ™”๋กญ๊ฒŒ ํ˜‘๋ ฅํ•  ์ˆ˜ ์žˆ๋Š” ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์ œ์‹œํ•˜์„ธ์š”
5. ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ์ตœ์‹  ํŠธ๋ Œ๋“œ์™€ ๋ชจ๋ฒ” ์‚ฌ๋ก€๋ฅผ ๋ฐ˜์˜ํ•˜์„ธ์š”
[ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ]: 5-7๊ฐœ์˜ ์กฐ์‚ฌ๊ฐ€ ํ•„์š”ํ•œ ํ‚ค์›Œ๋“œ๋ฅผ ์ œ์‹œํ•˜์„ธ์š”"""
def create_fire_strategy_prompt(self, user_query: str, wood_response: str, critic_feedback: str, search_results: Dict = None) -> str:
"""็ซ(์ „๋žต๊ธฐํš) ํ”„๋กฌํ”„ํŠธ"""
search_info = ""
if search_results:
search_info = f"\n\n์›น ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ:\n{self._format_search_results(search_results)}"
return f"""๋‹น์‹ ์€ ็พฉ(์ •์˜๋กœ์›€)์˜ ๋•๋ชฉ์„ ์ง€๋‹Œ ็ซ์˜ ๊ธฐ์šด์„ ๊ฐ€์ง„ ์ „๋žตยท๊ธฐํš ๋ฆฌ๋”์ž…๋‹ˆ๋‹ค.
์—ด์ •๊ณผ ๊ฒฐ๋‹จ๋ ฅ์œผ๋กœ ํ˜์‹ ์ ์ธ ์ „๋žต์„ ์ˆ˜๋ฆฝํ•ฉ๋‹ˆ๋‹ค.
์‚ฌ์šฉ์ž ์งˆ๋ฌธ: {user_query}
๊ฐ๋…๊ด€(ๆœจ)์˜ ๋น„์ „:
{wood_response}
๋น„ํ‰์ž์˜ ํ”ผ๋“œ๋ฐฑ:
{critic_feedback}
{search_info}
์ „๋žต๊ธฐํš ๋ฆฌ๋”๋กœ์„œ:
1. ์ค‘ยท์žฅ๊ธฐ ๋กœ๋“œ๋งต์„ ๊ตฌ์ฒด์ ์œผ๋กœ ์ˆ˜๋ฆฝํ•˜์„ธ์š”
2. ํ˜์‹ ์ ์ธ ์‚ฌ์—… ๋ชจ๋ธ์„ ์„ค๊ณ„ํ•˜์„ธ์š”
3. ๋ฆฌ์Šคํฌ์™€ ๊ธฐํšŒ ๋ถ„์„์„ ์ˆ˜ํ–‰ํ•˜์„ธ์š”
4. ๊ฒฝ์Ÿ ์šฐ์œ„ ํ™•๋ณด ์ „๋žต์„ ์ œ์‹œํ•˜์„ธ์š”
5. ๋น„ํ‰์ž์˜ ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ˜์˜ํ•˜์—ฌ ์ „๋žต์„ ๋ณด์™„ํ•˜์„ธ์š”
6. ํ•„์š”ํ•œ ์ถ”๊ฐ€ ๊ฒ€์ƒ‰ ํ‚ค์›Œ๋“œ๋ฅผ ์ œ์‹œํ•˜์„ธ์š”: [์ถ”๊ฐ€ ๊ฒ€์ƒ‰]"""
def create_earth_execution_prompt(self, user_query: str, fire_response: str, critic_feedback: str, search_results: Dict = None) -> str:
"""ๅœŸ(์‹คํ–‰์šด์˜) ํ”„๋กฌํ”„ํŠธ"""
search_info = ""
if search_results:
search_info = f"\n\n์›น ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ:\n{self._format_search_results(search_results)}"
return f"""๋‹น์‹ ์€ ไฟก(์‹ ๋ขฐ)์˜ ๋•๋ชฉ์„ ์ง€๋‹Œ ๅœŸ์˜ ๊ธฐ์šด์„ ๊ฐ€์ง„ ์‹คํ–‰ยท์šด์˜ยทํ’ˆ์งˆ ๋‹ด๋‹น์ž์ž…๋‹ˆ๋‹ค.
์„ฑ์‹คํ•จ๊ณผ ์‹คํ–‰๋ ฅ์œผ๋กœ ๊ณ„ํš์„ ํ˜„์‹ค๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
์‚ฌ์šฉ์ž ์งˆ๋ฌธ: {user_query}
์ „๋žต๊ธฐํš(็ซ)์˜ ์ „๋žต:
{fire_response}
๋น„ํ‰์ž์˜ ํ”ผ๋“œ๋ฐฑ:
{critic_feedback}
{search_info}
์‹คํ–‰์šด์˜ ๋‹ด๋‹น์ž๋กœ์„œ:
1. ๊ตฌ์ฒด์ ์ธ ์‹คํ–‰ ๊ณ„ํš๊ณผ ์ผ์ •์„ ์ˆ˜๋ฆฝํ•˜์„ธ์š”
2. ํ•„์š”ํ•œ ๋ฆฌ์†Œ์Šค์™€ ์˜ˆ์‚ฐ์„ ์‚ฐ์ •ํ•˜์„ธ์š”
3. ํ’ˆ์งˆ ๊ด€๋ฆฌ ๊ธฐ์ค€๊ณผ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ •์˜ํ•˜์„ธ์š”
4. ๋ฆฌ์Šคํฌ ๋Œ€์‘ ๊ณ„ํš์„ ์ˆ˜๋ฆฝํ•˜์„ธ์š”
5. ์„ฑ๊ณผ ์ธก์ • ์ง€ํ‘œ๋ฅผ ์„ค์ •ํ•˜์„ธ์š”
6. ์ถ”๊ฐ€ ์กฐ์‚ฌ๊ฐ€ ํ•„์š”ํ•œ ์‹คํ–‰ ๊ด€๋ จ ํ‚ค์›Œ๋“œ: [์ถ”๊ฐ€ ๊ฒ€์ƒ‰]"""
def create_metal_architecture_prompt(self, user_query: str, earth_response: str, critic_feedback: str, search_results: Dict = None) -> str:
"""้‡‘(์•„ํ‚คํ…์ฒ˜) ํ”„๋กฌํ”„ํŠธ"""
search_info = ""
if search_results:
search_info = f"\n\n์›น ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ:\n{self._format_search_results(search_results)}"
return f"""๋‹น์‹ ์€ ็ฆฎ(์˜ˆ์˜ยท์งˆ์„œ)์˜ ๋•๋ชฉ์„ ์ง€๋‹Œ ้‡‘์˜ ๊ธฐ์šด์„ ๊ฐ€์ง„ ์•„ํ‚คํ…์ฒ˜ & ํ‘œ์ค€ ์ฑ…์ž„์ž์ž…๋‹ˆ๋‹ค.
์ •๋ฐ€ํ•จ๊ณผ ๊ตฌ์กฐํ™” ๋Šฅ๋ ฅ์œผ๋กœ ์™„๋ฒฝํ•œ ์‹œ์Šคํ…œ์„ ์„ค๊ณ„ํ•ฉ๋‹ˆ๋‹ค.
์‚ฌ์šฉ์ž ์งˆ๋ฌธ: {user_query}
์‹คํ–‰์šด์˜(ๅœŸ)์˜ ๊ณ„ํš:
{earth_response}
๋น„ํ‰์ž์˜ ํ”ผ๋“œ๋ฐฑ:
{critic_feedback}
{search_info}
์•„ํ‚คํ…์ฒ˜ ์ฑ…์ž„์ž๋กœ์„œ:
1. ์ „์ฒด ์‹œ์Šคํ…œ์˜ ๊ธฐ์ˆ ยท๋ฐ์ดํ„ฐ ์•„ํ‚คํ…์ฒ˜๋ฅผ ์„ค๊ณ„ํ•˜์„ธ์š”
2. ์ฝ”๋”ฉ/APIยท๋ฐ์ดํ„ฐยท๋ณด์•ˆ ํ‘œ์ค€์„ ์ˆ˜๋ฆฝํ•˜์„ธ์š”
3. ํ™•์žฅ์„ฑ๊ณผ ํ˜ธํ™˜์„ฑ์„ ๊ณ ๋ คํ•œ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์ œ์‹œํ•˜์„ธ์š”
4. ํ’ˆ์งˆ ๊ฒ€์ฆ ์ฒด๊ณ„์™€ ๊ธฐ์ค€์„ ์ •์˜ํ•˜์„ธ์š”
5. ๊ธฐ์ˆ ์  ์ œ์•ฝ์‚ฌํ•ญ๊ณผ ํ•ด๊ฒฐ๋ฐฉ์•ˆ์„ ์ œ์‹œํ•˜์„ธ์š”
6. ๊ธฐ์ˆ  ์•„ํ‚คํ…์ฒ˜ ๊ด€๋ จ ์ถ”๊ฐ€ ๊ฒ€์ƒ‰: [์ถ”๊ฐ€ ๊ฒ€์ƒ‰]"""
def create_water_rd_prompt(self, user_query: str, metal_response: str, critic_feedback: str, search_results: Dict = None) -> str:
"""ๆฐด(R&D) ํ”„๋กฌํ”„ํŠธ"""
search_info = ""
if search_results:
search_info = f"\n\n์ตœ์‹  ๊ธฐ์ˆ  ์กฐ์‚ฌ ๊ฒฐ๊ณผ:\n{self._format_search_results(search_results)}"
return f"""๋‹น์‹ ์€ ๆ™บ(์ง€ํ˜œ)์˜ ๋•๋ชฉ์„ ์ง€๋‹Œ ๆฐด์˜ ๊ธฐ์šด์„ ๊ฐ€์ง„ ๋„๊ตฌ ํ™œ์šฉ R&D ์ŠคํŽ˜์…œ๋ฆฌ์ŠคํŠธ์ž…๋‹ˆ๋‹ค.
๋ถ„์„๋ ฅ๊ณผ ํ˜ธ๊ธฐ์‹ฌ์œผ๋กœ ์ตœ์‹  ๊ธฐ์ˆ ์„ ํƒ๊ตฌํ•˜๊ณ  ํ˜์‹ ํ•ฉ๋‹ˆ๋‹ค.
์‚ฌ์šฉ์ž ์งˆ๋ฌธ: {user_query}
์•„ํ‚คํ…์ฒ˜(้‡‘)์˜ ์„ค๊ณ„:
{metal_response}
๋น„ํ‰์ž์˜ ํ”ผ๋“œ๋ฐฑ:
{critic_feedback}
{search_info}
R&D ์ŠคํŽ˜์…œ๋ฆฌ์ŠคํŠธ๋กœ์„œ:
1. ์ตœ์‹  ๊ธฐ์ˆ  ํŠธ๋ Œ๋“œ์™€ ๋„๊ตฌ๋ฅผ ๋ถ„์„ํ•˜์„ธ์š”
2. ํ˜์‹ ์ ์ธ ํ”„๋กœํ† ํƒ€์ž… ๊ฐœ๋ฐœ ๋ฐฉ์•ˆ์„ ์ œ์‹œํ•˜์„ธ์š”
3. ์ž๋™ํ™”์™€ ์ƒ์‚ฐ์„ฑ ํ–ฅ์ƒ ๋„๊ตฌ๋ฅผ ์ถ”์ฒœํ•˜์„ธ์š”
4. ๊ธฐ์ˆ  ๋„์ž…์˜ ROI์™€ ์‹คํ˜„ ๊ฐ€๋Šฅ์„ฑ์„ ํ‰๊ฐ€ํ•˜์„ธ์š”
5. ํŒ€ ๊ต์œก๊ณผ ๊ธฐ์ˆ  ์ „ํŒŒ ๊ณ„ํš์„ ์ˆ˜๋ฆฝํ•˜์„ธ์š”
6. ๋ฏธ๋ž˜ ๊ธฐ์ˆ  ๊ด€๋ จ ์ถ”๊ฐ€ ๊ฒ€์ƒ‰: [์ถ”๊ฐ€ ๊ฒ€์ƒ‰]"""
def create_wood_final_prompt(self, user_query: str, all_responses: Dict, all_critics: List) -> str:
"""ๆœจ(๊ฐ๋…๊ด€) ์ตœ์ข… ์ข…ํ•ฉ ํ”„๋กฌํ”„ํŠธ"""
return f"""๋‹น์‹ ์€ ไป(์ธ์žํ•จ)์˜ ๋•๋ชฉ์„ ์ง€๋‹Œ ๆœจ์˜ ๊ธฐ์šด์„ ๊ฐ€์ง„ ๊ฐ๋…๊ด€์ž…๋‹ˆ๋‹ค.
ํŒ€ ์ „์ฒด์˜ ์˜๊ฒฌ์„ ์ข…ํ•ฉํ•˜์—ฌ ์ตœ์ข… ๊ฒฐ์ •์„ ๋‚ด๋ฆฝ๋‹ˆ๋‹ค.
์‚ฌ์šฉ์ž ์งˆ๋ฌธ: {user_query}
ํŒ€์›๋“ค์˜ ๊ธฐ์—ฌ:
- ็ซ(์ „๋žต๊ธฐํš): {all_responses['fire']}
- ๅœŸ(์‹คํ–‰์šด์˜): {all_responses['earth']}
- ้‡‘(์•„ํ‚คํ…์ฒ˜): {all_responses['metal']}
- ๆฐด(R&D): {all_responses['water']}
๋น„ํ‰์ž์˜ ํ”ผ๋“œ๋ฐฑ ์ด๋ ฅ:
{self._format_critic_history(all_critics)}
ํŒ€์žฅ์œผ๋กœ์„œ ์ตœ์ข… ์ข…ํ•ฉ ๋ณด๊ณ ์„œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”:
1. ๊ฐ ํŒ€์›์˜ ๊ธฐ์—ฌ๋ฅผ ํ†ตํ•ฉํ•œ ์ข…ํ•ฉ ์†”๋ฃจ์…˜
2. ์‹คํ–‰ ์šฐ์„ ์ˆœ์œ„์™€ ๋‹จ๊ณ„๋ณ„ ๋กœ๋“œ๋งต
3. ์˜ˆ์ƒ ์„ฑ๊ณผ์™€ ์„ฑ๊ณต ์ง€ํ‘œ
4. ํŒ€ ์ „์ฒด์˜ ์‹œ๋„ˆ์ง€ ์ฐฝ์ถœ ๋ฐฉ์•ˆ
5. ์ง€์†์  ๊ฐœ์„ ๊ณผ ์„ฑ์žฅ ๊ณ„ํš
๋งˆํฌ๋‹ค์šด ํ˜•์‹์„ ํ™œ์šฉํ•˜์—ฌ ์ „๋ฌธ์ ์ด๊ณ  ์ฒด๊ณ„์ ์œผ๋กœ ์ž‘์„ฑํ•˜์„ธ์š”."""
def create_critic_prompt(self, stage: str, content: str, context: str = "", search_results: Dict = None) -> str:
"""์ค‘๋ฆฝ์  ๋น„ํ‰์ž ํ”„๋กฌํ”„ํŠธ"""
search_info = ""
if search_results:
search_info = f"\n\n์ฐธ๊ณ  ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ:\n{self._format_search_results(search_results)}"
return f"""๋‹น์‹ ์€ ์ค‘๋ฆฝ์ ์ด๊ณ  ๋…ผ๋ฆฌ์ ์ธ ๋น„ํ‰์ž์ž…๋‹ˆ๋‹ค.
ํŽธ๊ฒฌ ์—†์ด ํ•ฉ๋ฆฌ์ ์ด๊ณ  ๊ฑด์„ค์ ์ธ ๋น„ํ‰์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
ํ˜„์žฌ ๋‹จ๊ณ„: {stage}
๋ถ„์„ ๋Œ€์ƒ:
{content}
{f"์ด์ „ ๋งฅ๋ฝ: {context}" if context else ""}
{search_info}
๋‹ค์Œ ๊ด€์ ์—์„œ ๋น„ํ‰ํ•˜์„ธ์š”:
1. ๋…ผ๋ฆฌ์  ์ผ๊ด€์„ฑ๊ณผ ํƒ€๋‹น์„ฑ
2. ์‹คํ˜„ ๊ฐ€๋Šฅ์„ฑ๊ณผ ์‹ค์šฉ์„ฑ
3. ๋ˆ„๋ฝ๋œ ์ค‘์š” ์š”์†Œ
4. ๊ฐœ์„  ๊ฐ€๋Šฅํ•œ ๋ถ€๋ถ„
5. ๊ฐ•์ ๊ณผ ์•ฝ์ ์˜ ๊ท ํ˜•์žกํžŒ ํ‰๊ฐ€
6. ์ตœ์‹  ํŠธ๋ Œ๋“œ์™€ ๋ชจ๋ฒ” ์‚ฌ๋ก€ ๋Œ€๋น„ ํ‰๊ฐ€
๊ฑด์„ค์ ์ด๊ณ  ๊ตฌ์ฒด์ ์ธ ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•˜๋˜, ๋‹ค์Œ ๋‹จ๊ณ„ ๋‹ด๋‹น์ž๊ฐ€
๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ๋Š” ์‹ค์งˆ์ ์ธ ์ œ์•ˆ์„ ํฌํ•จํ•˜์„ธ์š”.
ํ•„์š”์‹œ ์ถ”๊ฐ€ ๊ฒ€์ƒ‰์ด ํ•„์š”ํ•œ ํ‚ค์›Œ๋“œ๋ฅผ ์ œ์•ˆํ•˜์„ธ์š”: [๋น„ํ‰ ๊ฒ€์ƒ‰]"""
def _format_search_results(self, search_results: Dict) -> str:
"""๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ํฌ๋งทํŒ…"""
if not search_results:
return "๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์—†์Œ"
formatted = ""
for keyword, results in search_results.items():
formatted += f"\n**{keyword}:**\n"
for i, result in enumerate(results[:5], 1):
formatted += f"{i}. {result.get('title', 'N/A')} (์‹ ๋ขฐ๋„: {result.get('credibility_score', 0):.2f})\n"
formatted += f" {result.get('description', 'N/A')[:150]}...\n"
return formatted
def _format_critic_history(self, critics: List) -> str:
"""๋น„ํ‰ ์ด๋ ฅ ํฌ๋งทํŒ…"""
if not critics:
return "๋น„ํ‰ ์ด๋ ฅ ์—†์Œ"
formatted = ""
stages = ["ๆœจ ์ดˆ๊ธฐ", "็ซ ์ „๋žต", "ๅœŸ ์‹คํ–‰", "้‡‘ ์•„ํ‚คํ…์ฒ˜", "ๆฐด R&D"]
for i, critic in enumerate(critics):
if i < len(stages):
formatted += f"\n**{stages[i]} ๋‹จ๊ณ„ ๋น„ํ‰:**\n{critic}\n"
return formatted
def extract_keywords(self, response: str, keyword_marker: str = "[ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ]") -> List[str]:
"""์‘๋‹ต์—์„œ ํ‚ค์›Œ๋“œ ์ถ”์ถœ (๋ฒ”์šฉ)"""
keywords = []
# ๋‹ค์–‘ํ•œ ํ‚ค์›Œ๋“œ ๋งˆ์ปค ํŒจํ„ด
patterns = [
rf'\{keyword_marker}:\s*(.+)',
r'\[์ถ”๊ฐ€ ๊ฒ€์ƒ‰\]:\s*(.+)',
r'\[๋น„ํ‰ ๊ฒ€์ƒ‰\]:\s*(.+)'
]
for pattern in patterns:
match = re.search(pattern, response, re.IGNORECASE)
if match:
keyword_str = match.group(1)
new_keywords = [k.strip() for k in keyword_str.split(',') if k.strip()]
keywords.extend(new_keywords)
# ์ค‘๋ณต ์ œ๊ฑฐ
keywords = list(dict.fromkeys(keywords))
if not keywords:
# ๊ธฐ๋ณธ ํ‚ค์›Œ๋“œ ์ƒ์„ฑ
keywords = ["best practices", "implementation", "case studies", "latest trends"]
return keywords[:7]
def calculate_credibility_score(self, result: Dict) -> float:
"""๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์‹ ๋ขฐ๋„ ๊ณ„์‚ฐ"""
score = 0.5
url = result.get('url', '')
trusted_domains = ['.edu', '.gov', '.org', 'wikipedia.org', 'nature.com',
'ieee.org', 'acm.org', 'github.com']
for domain in trusted_domains:
if domain in url:
score += 0.2
break
if url.startswith('https://'):
score += 0.1
if len(result.get('title', '')) > 20:
score += 0.05
if len(result.get('description', '')) > 50:
score += 0.05
spam_keywords = ['buy now', 'sale', 'discount', 'click here']
if any(spam in (result.get('title', '') + result.get('description', '')).lower()
for spam in spam_keywords):
score -= 0.3
return max(0, min(1, score))
def brave_search(self, query: str) -> List[Dict]:
"""Brave Search API ํ˜ธ์ถœ"""
if self.test_mode or self.bapi_token == "YOUR_BRAVE_API_TOKEN":
test_results = []
for i in range(5):
test_results.append({
"title": f"{query} - Best Practices {i+1}",
"description": f"Comprehensive guide on {query} with proven methodologies.",
"url": f"https://example{i+1}.com/{query.replace(' ', '-')}",
"credibility_score": 0.7 + (i * 0.05)
})
return test_results
try:
params = {
"q": query,
"count": 10,
"safesearch": "moderate"
}
response = requests.get(
self.brave_url,
headers=self.create_brave_headers(),
params=params,
timeout=10
)
if response.status_code == 200:
data = response.json()
results = []
for item in data.get("web", {}).get("results", []):
result = {
"title": item.get("title", ""),
"description": item.get("description", ""),
"url": item.get("url", ""),
"credibility_score": self.calculate_credibility_score(item)
}
results.append(result)
results.sort(key=lambda x: x['credibility_score'], reverse=True)
return results
else:
logger.error(f"Brave API ์˜ค๋ฅ˜: {response.status_code}")
return []
except Exception as e:
logger.error(f"Brave ๊ฒ€์ƒ‰ ์ค‘ ์˜ค๋ฅ˜: {str(e)}")
return []
def call_gemini_streaming(self, messages: List[Dict[str, str]], role: str) -> Generator[str, None, None]:
"""Gemini API ์ŠคํŠธ๋ฆฌ๋ฐ ํ˜ธ์ถœ"""
if not self.gemini_client:
yield "โŒ Gemini API ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."
return
try:
contents = []
for msg in messages:
if msg["role"] == "user":
contents.append(types.Content(
role="user",
parts=[types.Part.from_text(text=msg["content"])]
))
generate_content_config = types.GenerateContentConfig(
temperature=0.7,
top_p=0.8,
max_output_tokens=4096,
response_mime_type="text/plain"
)
for chunk in self.gemini_client.models.generate_content_stream(
model="gemini-2.5-pro",
contents=contents,
config=generate_content_config,
):
if chunk.text:
yield chunk.text
except Exception as e:
logger.error(f"Gemini API ์˜ค๋ฅ˜: {str(e)}")
yield f"โŒ Gemini API ์˜ค๋ฅ˜: {str(e)}"
def call_llm_streaming(self, messages: List[Dict[str, str]], role: str) -> Generator[str, None, None]:
"""์ŠคํŠธ๋ฆฌ๋ฐ LLM API ํ˜ธ์ถœ"""
if self.use_gemini:
yield from self.call_gemini_streaming(messages, role)
return
if self.test_mode:
test_response = f"์ด๊ฒƒ์€ {role} ์—ญํ• ์˜ ํ…Œ์ŠคํŠธ ์‘๋‹ต์ž…๋‹ˆ๋‹ค.\n"
test_response += f"์‚ฌ์šฉ์ž ์งˆ๋ฌธ์— ๋Œ€ํ•œ {role}์˜ ๊ด€์ ์—์„œ ๋ถ„์„ํ•œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.\n"
test_response += "1. ์ฒซ ๋ฒˆ์งธ ํ•ต์‹ฌ ํฌ์ธํŠธ\n2. ๋‘ ๋ฒˆ์งธ ํ•ต์‹ฌ ํฌ์ธํŠธ\n3. ์„ธ ๋ฒˆ์งธ ํ•ต์‹ฌ ํฌ์ธํŠธ"
words = test_response.split()
for i in range(0, len(words), 3):
chunk = " ".join(words[i:i+3])
yield chunk + " "
time.sleep(0.05)
return
try:
payload = {
"model": self.model_id,
"messages": messages,
"max_tokens": 4096,
"temperature": 0.7,
"stream": True
}
response = requests.post(
self.api_url,
headers=self.create_headers(),
json=payload,
stream=True,
timeout=10
)
if response.status_code != 200:
yield f"โŒ API ์˜ค๋ฅ˜: {response.status_code}"
return
for line in response.iter_lines():
if line:
line = line.decode('utf-8')
if line.startswith("data: "):
data = line[6:]
if data == "[DONE]":
break
try:
chunk = json.loads(data)
if "choices" in chunk and chunk["choices"]:
content = chunk["choices"][0].get("delta", {}).get("content", "")
if content:
yield content
except json.JSONDecodeError:
continue
except Exception as e:
logger.error(f"์ŠคํŠธ๋ฆฌ๋ฐ ์ค‘ ์˜ค๋ฅ˜: {str(e)}")
yield f"โŒ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
# ์‹œ์Šคํ…œ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ
wuxing_system = WuxingLLMSystem()
def process_wuxing_query(user_query: str, llm_mode: str):
"""์˜คํ–‰ ๊ธฐ๋ฐ˜ ์ฟผ๋ฆฌ ์ฒ˜๋ฆฌ"""
if not user_query:
return "", "", "", "", "", "", "", "โŒ ์งˆ๋ฌธ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."
wuxing_system.set_llm_mode(llm_mode)
all_responses = {}
all_critics = []
all_search_results = {}
try:
# 0. ์ดˆ๊ธฐ ๊ฒ€์ƒ‰ ์ˆ˜ํ–‰
initial_keywords = [user_query] + ["best practices", "latest trends", "case studies"]
wood_search_results = {}
status_text = "๐Ÿ” ์ดˆ๊ธฐ ์›น ๊ฒ€์ƒ‰ ์ˆ˜ํ–‰ ์ค‘..."
yield "", "", "", "", "", "", "", status_text
for keyword in initial_keywords[:3]:
results = wuxing_system.brave_search(keyword)
if results:
wood_search_results[keyword] = results
all_search_results[f"์ดˆ๊ธฐ-{keyword}"] = results
# 1. ๆœจ(๊ฐ๋…๊ด€) ์ดˆ๊ธฐ ๋ถ„์„
wood_prompt = wuxing_system.create_wood_initial_prompt(user_query, wood_search_results)
wood_response = ""
wood_text = "๐ŸŒณ **ๆœจ - ๊ฐ๋…๊ด€** (ไป)\n๐Ÿ”„ ๋ถ„์„ ์ค‘...\n"
for chunk in wuxing_system.call_llm_streaming(
[{"role": "user", "content": wood_prompt}], "wood"
):
wood_response += chunk
wood_text = f"๐ŸŒณ **ๆœจ - ๊ฐ๋…๊ด€** (ไป)\n{wood_response}"
yield wood_text, "", "", "", "", "", "", "๐ŸŒณ ๊ฐ๋…๊ด€์ด ๋น„์ „์„ ์ˆ˜๋ฆฝ ์ค‘..."
all_responses['wood_initial'] = wood_response
# ํ‚ค์›Œ๋“œ ์ถ”์ถœ
keywords = wuxing_system.extract_keywords(wood_response)
# 2. ๆœจ ๋น„ํ‰ + ๋น„ํ‰์ž ๊ฒ€์ƒ‰
critic_search = {}
for keyword in keywords[:2]:
results = wuxing_system.brave_search(f"{keyword} analysis")
if results:
critic_search[keyword] = results
all_search_results[f"๋น„ํ‰1-{keyword}"] = results
critic_prompt = wuxing_system.create_critic_prompt("ๆœจ ์ดˆ๊ธฐ ๋ถ„์„", wood_response, search_results=critic_search)
critic_response = ""
critic_text = "๐Ÿ” **์ค‘๋ฆฝ์  ๋น„ํ‰์ž**\n[ๆœจ ๋ถ„์„ ๋น„ํ‰] ๐Ÿ”„ ๋น„ํ‰ ์ค‘...\n"
for chunk in wuxing_system.call_llm_streaming(
[{"role": "user", "content": critic_prompt}], "critic"
):
critic_response += chunk
critic_text = f"๐Ÿ” **์ค‘๋ฆฝ์  ๋น„ํ‰์ž**\n[ๆœจ ๋ถ„์„ ๋น„ํ‰]\n{critic_response}"
yield wood_text, "", "", "", "", critic_text, "", "๐Ÿ” ๋น„ํ‰์ž๊ฐ€ ๋ถ„์„ ์ค‘..."
all_critics.append(critic_response)
# 3. ็ซ(์ „๋žต๊ธฐํš) + ๊ฒ€์ƒ‰
# ๋น„ํ‰์ž๊ฐ€ ์ œ์•ˆํ•œ ์ถ”๊ฐ€ ํ‚ค์›Œ๋“œ ์ถ”์ถœ
critic_keywords = wuxing_system.extract_keywords(critic_response, "[๋น„ํ‰ ๊ฒ€์ƒ‰]")
fire_search = {}
for keyword in (keywords[:2] + critic_keywords[:2]):
results = wuxing_system.brave_search(f"{user_query} {keyword} strategy")
if results:
fire_search[keyword] = results
all_search_results[f"็ซ-{keyword}"] = results
fire_prompt = wuxing_system.create_fire_strategy_prompt(user_query, wood_response, critic_response, fire_search)
fire_response = ""
fire_text = "๐Ÿ”ฅ **็ซ - ์ „๋žตยท๊ธฐํš ๋ฆฌ๋”** (็พฉ)\n๐Ÿ”„ ์ „๋žต ์ˆ˜๋ฆฝ ์ค‘...\n"
for chunk in wuxing_system.call_llm_streaming(
[{"role": "user", "content": fire_prompt}], "fire"
):
fire_response += chunk
fire_text = f"๐Ÿ”ฅ **็ซ - ์ „๋žตยท๊ธฐํš ๋ฆฌ๋”** (็พฉ)\n{fire_response}"
yield wood_text, fire_text, "", "", "", critic_text, "", "๐Ÿ”ฅ ์ „๋žต ์ˆ˜๋ฆฝ ์ค‘..."
all_responses['fire'] = fire_response
# 4. ็ซ ๋น„ํ‰
fire_keywords = wuxing_system.extract_keywords(fire_response, "[์ถ”๊ฐ€ ๊ฒ€์ƒ‰]")
critic_search = {}
for keyword in fire_keywords[:2]:
results = wuxing_system.brave_search(keyword)
if results:
critic_search[keyword] = results
all_search_results[f"๋น„ํ‰2-{keyword}"] = results
critic_prompt = wuxing_system.create_critic_prompt("็ซ ์ „๋žต๊ธฐํš", fire_response, wood_response, critic_search)
critic_response = ""
critic_text += "\n\n---\n\n[็ซ ์ „๋žต ๋น„ํ‰] ๐Ÿ”„ ๋น„ํ‰ ์ค‘...\n"
for chunk in wuxing_system.call_llm_streaming(
[{"role": "user", "content": critic_prompt}], "critic"
):
critic_response += chunk
temp_text = all_critics[0] + f"\n\n---\n\n[็ซ ์ „๋žต ๋น„ํ‰]\n{critic_response}"
critic_text = f"๐Ÿ” **์ค‘๋ฆฝ์  ๋น„ํ‰์ž**\n[ๆœจ ๋ถ„์„ ๋น„ํ‰]\n{temp_text}"
yield wood_text, fire_text, "", "", "", critic_text, "", "๐Ÿ” ์ „๋žต ๋น„ํ‰ ์ค‘..."
all_critics.append(critic_response)
# 5. ๅœŸ(์‹คํ–‰์šด์˜)
earth_search = {}
for keyword in ["implementation", "resource management", "quality assurance"]:
results = wuxing_system.brave_search(f"{user_query} {keyword}")
if results:
earth_search[keyword] = results
all_search_results[f"ๅœŸ-{keyword}"] = results
earth_prompt = wuxing_system.create_earth_execution_prompt(user_query, fire_response, critic_response, earth_search)
earth_response = ""
earth_text = "๐Ÿ”๏ธ **ๅœŸ - ์‹คํ–‰ยท์šด์˜ยทํ’ˆ์งˆ** (ไฟก)\n๐Ÿ”„ ์‹คํ–‰ ๊ณ„ํš ์ˆ˜๋ฆฝ ์ค‘...\n"
for chunk in wuxing_system.call_llm_streaming(
[{"role": "user", "content": earth_prompt}], "earth"
):
earth_response += chunk
earth_text = f"๐Ÿ”๏ธ **ๅœŸ - ์‹คํ–‰ยท์šด์˜ยทํ’ˆ์งˆ** (ไฟก)\n{earth_response}"
yield wood_text, fire_text, earth_text, "", "", critic_text, "", "๐Ÿ”๏ธ ์‹คํ–‰ ๊ณ„ํš ์ˆ˜๋ฆฝ ์ค‘..."
all_responses['earth'] = earth_response
# 6. ๅœŸ ๋น„ํ‰
critic_search = {}
results = wuxing_system.brave_search(f"{user_query} execution challenges")
if results:
critic_search["execution"] = results
all_search_results["๋น„ํ‰3-execution"] = results
critic_prompt = wuxing_system.create_critic_prompt("ๅœŸ ์‹คํ–‰๊ณ„ํš", earth_response, fire_response, critic_search)
critic_response = ""
for chunk in wuxing_system.call_llm_streaming(
[{"role": "user", "content": critic_prompt}], "critic"
):
critic_response += chunk
yield wood_text, fire_text, earth_text, "", "", critic_text, "", "๐Ÿ” ์‹คํ–‰ ๋น„ํ‰ ์ค‘..."
all_critics.append(critic_response)
# 7. ้‡‘(์•„ํ‚คํ…์ฒ˜)
metal_search = {}
for keyword in ["architecture", "standards", "system design", "scalability"]:
results = wuxing_system.brave_search(f"{user_query} {keyword}")
if results:
metal_search[keyword] = results
all_search_results[f"้‡‘-{keyword}"] = results
metal_prompt = wuxing_system.create_metal_architecture_prompt(user_query, earth_response, critic_response, metal_search)
metal_response = ""
metal_text = "โš™๏ธ **้‡‘ - ์•„ํ‚คํ…์ฒ˜ & ํ‘œ์ค€** (็ฆฎ)\n๐Ÿ”„ ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„ ์ค‘...\n"
for chunk in wuxing_system.call_llm_streaming(
[{"role": "user", "content": metal_prompt}], "metal"
):
metal_response += chunk
metal_text = f"โš™๏ธ **้‡‘ - ์•„ํ‚คํ…์ฒ˜ & ํ‘œ์ค€** (็ฆฎ)\n{metal_response}"
yield wood_text, fire_text, earth_text, metal_text, "", critic_text, "", "โš™๏ธ ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„ ์ค‘..."
all_responses['metal'] = metal_response
# 8. ้‡‘ ๋น„ํ‰
critic_search = {}
results = wuxing_system.brave_search(f"{user_query} technical constraints")
if results:
critic_search["constraints"] = results
all_search_results["๋น„ํ‰4-constraints"] = results
critic_prompt = wuxing_system.create_critic_prompt("้‡‘ ์•„ํ‚คํ…์ฒ˜", metal_response, earth_response, critic_search)
critic_response = ""
for chunk in wuxing_system.call_llm_streaming(
[{"role": "user", "content": critic_prompt}], "critic"
):
critic_response += chunk
yield wood_text, fire_text, earth_text, metal_text, "", critic_text, "", "๐Ÿ” ์•„ํ‚คํ…์ฒ˜ ๋น„ํ‰ ์ค‘..."
all_critics.append(critic_response)
# 9. ๆฐด(R&D)
water_search = {}
for keyword in ["innovation", "emerging technology", "R&D", "automation tools"]:
results = wuxing_system.brave_search(f"{user_query} {keyword}")
if results:
water_search[keyword] = results
all_search_results[f"ๆฐด-{keyword}"] = results
water_prompt = wuxing_system.create_water_rd_prompt(user_query, metal_response, critic_response, water_search)
water_response = ""
water_text = "๐Ÿ’ง **ๆฐด - R&D ์ŠคํŽ˜์…œ๋ฆฌ์ŠคํŠธ** (ๆ™บ)\n๐Ÿ”„ ํ˜์‹  ๋ฐฉ์•ˆ ์—ฐ๊ตฌ ์ค‘...\n"
for chunk in wuxing_system.call_llm_streaming(
[{"role": "user", "content": water_prompt}], "water"
):
water_response += chunk
water_text = f"๐Ÿ’ง **ๆฐด - R&D ์ŠคํŽ˜์…œ๋ฆฌ์ŠคํŠธ** (ๆ™บ)\n{water_response}"
yield wood_text, fire_text, earth_text, metal_text, water_text, critic_text, "", "๐Ÿ’ง ํ˜์‹  ์—ฐ๊ตฌ ์ค‘..."
all_responses['water'] = water_response
# 10. ๆฐด ๋น„ํ‰
critic_search = {}
results = wuxing_system.brave_search(f"{user_query} future trends")
if results:
critic_search["trends"] = results
all_search_results["๋น„ํ‰5-trends"] = results
critic_prompt = wuxing_system.create_critic_prompt("ๆฐด R&D", water_response, metal_response, critic_search)
critic_response = ""
for chunk in wuxing_system.call_llm_streaming(
[{"role": "user", "content": critic_prompt}], "critic"
):
critic_response += chunk
yield wood_text, fire_text, earth_text, metal_text, water_text, critic_text, "", "๐Ÿ” R&D ๋น„ํ‰ ์ค‘..."
all_critics.append(critic_response)
# 11. ๆœจ(๊ฐ๋…๊ด€) ์ตœ์ข… ์ข…ํ•ฉ
wood_final_prompt = wuxing_system.create_wood_final_prompt(user_query, all_responses, all_critics)
wood_final_response = ""
wood_text += "\n\n---\n\n๐ŸŒณ **์ตœ์ข… ์ข…ํ•ฉ ๋ณด๊ณ ์„œ**\n๐Ÿ”„ ์ž‘์„ฑ ์ค‘...\n"
for chunk in wuxing_system.call_llm_streaming(
[{"role": "user", "content": wood_final_prompt}], "wood"
):
wood_final_response += chunk
temp_text = all_responses['wood_initial'] + f"\n\n---\n\n๐ŸŒณ **์ตœ์ข… ์ข…ํ•ฉ ๋ณด๊ณ ์„œ**\n{wood_final_response}"
wood_text = f"๐ŸŒณ **ๆœจ - ๊ฐ๋…๊ด€** (ไป)\n{temp_text}"
yield wood_text, fire_text, earth_text, metal_text, water_text, critic_text, "", "๐ŸŒณ ์ตœ์ข… ๋ณด๊ณ ์„œ ์ž‘์„ฑ ์ค‘..."
# ์ตœ์ข… ๋ณด๊ณ ์„œ ์ƒ์„ฑ
final_report = f"""# ๐ŸŒŸ ์˜คํ–‰ ํ˜‘๋ ฅ ์‹œ์Šคํ…œ ์ตœ์ข… ์ข…ํ•ฉ ๋ณด๊ณ ์„œ
## ๐Ÿ“Œ ์‚ฌ์šฉ์ž ์งˆ๋ฌธ
> **{user_query}**
---
## ๐ŸŒณ ํŒ€์žฅ ์ตœ์ข… ์ข…ํ•ฉ (ๆœจ - ไป)
{wood_final_response}
---
## ๐Ÿ“Š ์˜คํ–‰ ํŒ€์› ๊ธฐ์—ฌ ์š”์•ฝ
### ๐Ÿ”ฅ ์ „๋žตยท๊ธฐํš (็ซ - ็พฉ)
{fire_response[:500]}...
### ๐Ÿ”๏ธ ์‹คํ–‰ยท์šด์˜ (ๅœŸ - ไฟก)
{earth_response[:500]}...
### โš™๏ธ ์•„ํ‚คํ…์ฒ˜ (้‡‘ - ็ฆฎ)
{metal_response[:500]}...
### ๐Ÿ’ง R&D ํ˜์‹  (ๆฐด - ๆ™บ)
{water_response[:500]}...
---
## ๐Ÿ” ์ค‘๋ฆฝ์  ๋น„ํ‰ ํ•ต์‹ฌ ํ†ต์ฐฐ
### ์ฃผ์š” ๊ฐœ์„ ์‚ฌํ•ญ
{all_critics[-1][:400]}...
---
## ๐Ÿ“ˆ ์„ฑ๊ณผ ์ง€ํ‘œ
| ํ•ญ๋ชฉ | ๋‚ด์šฉ |
|------|------|
| **์›น ๊ฒ€์ƒ‰ ์ˆ˜ํ–‰** | {len(all_search_results)}๊ฐœ ํ‚ค์›Œ๋“œ |
| **์ˆ˜์ง‘ ์ •๋ณด๋Ÿ‰** | {sum(len(r) for r in all_search_results.values())}๊ฐœ ๊ฒฐ๊ณผ |
| **ํ˜‘๋ ฅ ๋‹จ๊ณ„** | 11๋‹จ๊ณ„ (5์ธ + 5๋น„ํ‰ + ์ข…ํ•ฉ) |
| **์‚ฌ์šฉ ๋ชจ๋ธ** | {'Gemini 2.5 Pro' if wuxing_system.use_gemini else '๊ธฐ๋ณธ LLM'} |
---
## ๐ŸŽฏ ํ•ต์‹ฌ ์„ฑ๊ณต ์š”์ธ
1. **์กฐํ™”๋กœ์šด ํ˜‘๋ ฅ**: ์˜คํ–‰์˜ ์ƒ์ƒ ๊ด€๊ณ„๋ฅผ ํ†ตํ•œ ์‹œ๋„ˆ์ง€ ์ฐฝ์ถœ
2. **๋‹ค๊ฐ์  ๊ฒ€ํ† **: 5๊ฐ€์ง€ ์ „๋ฌธ์„ฑ๊ณผ ์ค‘๋ฆฝ์  ๋น„ํ‰์˜ ๊ท ํ˜•
3. **๋ฐ์ดํ„ฐ ๊ธฐ๋ฐ˜**: {len(all_search_results)}ํšŒ์˜ ์›น ๊ฒ€์ƒ‰์„ ํ†ตํ•œ ์ตœ์‹  ์ •๋ณด ๋ฐ˜์˜
4. **์‹คํ–‰ ๊ฐ€๋Šฅ์„ฑ**: ๊ตฌ์ฒด์ ์ด๊ณ  ๋‹จ๊ณ„๋ณ„ ์ ‘๊ทผ ๋ฐฉ์•ˆ ์ œ์‹œ
---
*์ƒ์„ฑ ์‹œ๊ฐ: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*
*์˜คํ–‰ ํ˜‘๋ ฅ ์‹œ์Šคํ…œ - ไป็พฉ็ฆฎๆ™บไฟก์˜ ์กฐํ™”*"""
status_text = f"โœ… ์˜คํ–‰ ํ˜‘๋ ฅ ํ”„๋กœ์„ธ์Šค ์™„๋ฃŒ! ({len(all_search_results)} ํ‚ค์›Œ๋“œ, {sum(len(r) for r in all_search_results.values())} ๊ฒ€์ƒ‰๊ฒฐ๊ณผ)"
yield wood_text, fire_text, earth_text, metal_text, water_text, critic_text, final_report, status_text
except Exception as e:
error_msg = f"โŒ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜: {str(e)}"
yield "", "", "", "", "", "", "", error_msg
def clear_wuxing():
"""์ดˆ๊ธฐํ™”"""
return "", "", "", "", "", "", "", "๐Ÿ”„ ์ดˆ๊ธฐํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค."
# CSS ์Šคํƒ€์ผ
css = """
.gradio-container {
font-family: 'Arial', sans-serif;
}
.wood-box textarea {
border-left: 4px solid #10b981 !important;
background-color: #f0fdf4 !important;
}
.fire-box textarea {
border-left: 4px solid #ef4444 !important;
background-color: #fef2f2 !important;
}
.earth-box textarea {
border-left: 4px solid #a855f7 !important;
background-color: #faf5ff !important;
}
.metal-box textarea {
border-left: 4px solid #f59e0b !important;
background-color: #fffbeb !important;
}
.water-box textarea {
border-left: 4px solid #3b82f6 !important;
background-color: #eff6ff !important;
}
.critic-box textarea {
border-left: 4px solid #6b7280 !important;
background-color: #f9fafb !important;
}
.final-report-box {
border: 2px solid #10b981 !important;
border-radius: 8px !important;
padding: 20px !important;
background: linear-gradient(to bottom, #f0fdf4, #ffffff) !important;
margin-top: 10px !important;
font-size: 14px !important;
max-height: 800px !important;
overflow-y: auto !important;
line-height: 1.8 !important;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1) !important;
}
.final-report-box h1 {
color: #065f46 !important;
font-size: 28px !important;
margin-bottom: 16px !important;
text-align: center !important;
font-weight: bold !important;
}
.final-report-box h2 {
color: #047857 !important;
font-size: 22px !important;
margin-top: 20px !important;
margin-bottom: 12px !important;
border-bottom: 2px solid #10b981 !important;
padding-bottom: 8px !important;
}
.final-report-box h3 {
color: #059669 !important;
font-size: 18px !important;
margin-top: 16px !important;
margin-bottom: 10px !important;
font-weight: 600 !important;
}
.final-report-box h4 {
color: #10b981 !important;
font-size: 16px !important;
margin-top: 12px !important;
margin-bottom: 8px !important;
}
.final-report-box table {
border-collapse: collapse !important;
width: 100% !important;
margin: 20px 0 !important;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
}
.final-report-box th, .final-report-box td {
border: 1px solid #d1d5db !important;
padding: 12px 14px !important;
text-align: left !important;
}
.final-report-box th {
background-color: #065f46 !important;
font-weight: bold !important;
color: #ffffff !important;
text-transform: uppercase !important;
font-size: 13px !important;
letter-spacing: 0.5px !important;
}
.final-report-box tr:nth-child(even) {
background-color: #f9fafb !important;
}
.final-report-box tr:hover {
background-color: #f0fdf4 !important;
transition: background-color 0.2s !important;
}
.final-report-box code {
background-color: #ecfdf5 !important;
padding: 3px 8px !important;
border-radius: 4px !important;
font-family: 'Consolas', 'Monaco', monospace !important;
color: #047857 !important;
font-size: 13px !important;
border: 1px solid #10b981 !important;
}
.final-report-box pre {
background-color: #1e293b !important;
color: #e2e8f0 !important;
padding: 16px !important;
border-radius: 8px !important;
overflow-x: auto !important;
margin: 16px 0 !important;
font-size: 13px !important;
line-height: 1.5 !important;
}
.final-report-box pre code {
background-color: transparent !important;
color: #e2e8f0 !important;
padding: 0 !important;
border: none !important;
}
.final-report-box blockquote {
border-left: 4px solid #10b981 !important;
padding-left: 16px !important;
margin: 16px 0 !important;
color: #064e3b !important;
font-style: italic !important;
background-color: #f0fdf4 !important;
padding: 14px 16px !important;
border-radius: 0 8px 8px 0 !important;
}
.final-report-box ul, .final-report-box ol {
margin-left: 24px !important;
margin-bottom: 16px !important;
}
.final-report-box li {
margin-bottom: 8px !important;
line-height: 1.8 !important;
}
.final-report-box strong {
color: #065f46 !important;
font-weight: 600 !important;
}
.final-report-box em {
color: #047857 !important;
}
.final-report-box hr {
border: none !important;
border-top: 2px solid #10b981 !important;
margin: 28px 0 !important;
}
.final-report-box a {
color: #059669 !important;
text-decoration: underline !important;
}
.final-report-box a:hover {
color: #047857 !important;
}
h1 {
text-align: center;
color: #1f2937;
}
"""
# Gradio ์ธํ„ฐํŽ˜์ด์Šค
with gr.Blocks(title="์˜คํ–‰ยท์˜ค์ƒ ํ˜‘๋ ฅ ์‹œ์Šคํ…œ", theme=gr.themes.Soft(), css=css) as app:
gr.Markdown(
"""
# ๐ŸŒŸ ์˜คํ–‰ยท์˜ค์ƒ ๊ธฐ๋ฐ˜ ํ˜‘๋ ฅ์  LLM ์‹œ์Šคํ…œ
### ๐Ÿ“‹ ํ”„๋กœ์„ธ์Šค: ๆœจโ†’๋น„ํ‰โ†’็ซโ†’๋น„ํ‰โ†’ๅœŸโ†’๋น„ํ‰โ†’้‡‘โ†’๋น„ํ‰โ†’ๆฐดโ†’๋น„ํ‰โ†’ๆœจ(์ตœ์ข…)
| ์—ญํ•  | ๋•๋ชฉยท์˜คํ–‰ | ํ•ต์‹ฌ ์ „๋ฌธ์„ฑ |
|------|-----------|-------------|
| ๐ŸŒณ **๊ฐ๋…๊ด€** | ไปยทๆœจ | ๋น„์ „ ์ œ์‹œ, ํŒ€ ์กฐ์œจ, ์ธ์žฌ ์œก์„ฑ |
| ๐Ÿ”ฅ **์ „๋žต๊ธฐํš** | ็พฉยท็ซ | ๋กœ๋“œ๋งต, ์‚ฌ์—… ๋ชจ๋ธ, ๋ฆฌ์Šคํฌ ๋ถ„์„ |
| ๐Ÿ”๏ธ **์‹คํ–‰์šด์˜** | ไฟกยทๅœŸ | ์ผ์ •ยท์˜ˆ์‚ฐ ๊ด€๋ฆฌ, ํ’ˆ์งˆ ๋ณด์ฆ |
| โš™๏ธ **์•„ํ‚คํ…์ฒ˜** | ็ฆฎยท้‡‘ | ์‹œ์Šคํ…œ ์„ค๊ณ„, ํ‘œ์ค€ ์ˆ˜๋ฆฝ, ํ’ˆ์งˆ ๊ฒ€์ฆ |
| ๐Ÿ’ง **R&D** | ๆ™บยทๆฐด | ๊ธฐ์ˆ  ์กฐ์‚ฌ, ํ”„๋กœํ† ํƒ€์ž…, ํ˜์‹  ๋„๊ตฌ |
| ๐Ÿ” **๋น„ํ‰์ž** | ์ค‘๋ฆฝ | ๋…ผ๋ฆฌ์ ยท๊ฑด์„ค์  ํ”ผ๋“œ๋ฐฑ |
"""
)
with gr.Row():
with gr.Column(scale=3):
llm_mode = gr.Radio(
choices=["default", "commercial"],
value="default",
label="LLM ๋ชจ๋“œ",
info="commercial: Gemini 2.5 Pro ์‚ฌ์šฉ"
)
user_input = gr.Textbox(
label="์งˆ๋ฌธ ์ž…๋ ฅ",
placeholder="์˜ˆ: ์ง€์† ๊ฐ€๋Šฅํ•œ ์Šค๋งˆํŠธ์‹œํ‹ฐ ๊ตฌ์ถ• ์ „๋žต์€?",
lines=3
)
with gr.Row():
submit_btn = gr.Button("๐Ÿš€ ๋ถ„์„ ์‹œ์ž‘", variant="primary", scale=2)
clear_btn = gr.Button("๐Ÿ—‘๏ธ ์ดˆ๊ธฐํ™”", scale=1)
with gr.Column(scale=1):
status_text = gr.Textbox(
label="์ง„ํ–‰ ์ƒํƒœ",
interactive=False,
value="๋Œ€๊ธฐ ์ค‘...",
lines=3
)
# ์ตœ์ข… ๋ณด๊ณ ์„œ ์„น์…˜
with gr.Row():
with gr.Column():
gr.Markdown("### ๐Ÿ“Š ์ตœ์ข… ์ข…ํ•ฉ ๋ณด๊ณ ์„œ")
final_report = gr.Markdown(
value="*์ตœ์ข… ๋ณด๊ณ ์„œ๊ฐ€ ์—ฌ๊ธฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.*",
elem_classes=["final-report-box"]
)
# ์˜คํ–‰ ์ถœ๋ ฅ - 2x3 ๊ทธ๋ฆฌ๋“œ
with gr.Row():
with gr.Column():
gr.Markdown("### ๐ŸŒณ ๆœจ - ๊ฐ๋…๊ด€ (ไป)")
wood_output = gr.Textbox(
label="",
lines=10,
max_lines=15,
interactive=False,
elem_classes=["wood-box"]
)
with gr.Column():
gr.Markdown("### ๐Ÿ”ฅ ็ซ - ์ „๋žตยท๊ธฐํš (็พฉ)")
fire_output = gr.Textbox(
label="",
lines=10,
max_lines=15,
interactive=False,
elem_classes=["fire-box"]
)
with gr.Row():
with gr.Column():
gr.Markdown("### ๐Ÿ”๏ธ ๅœŸ - ์‹คํ–‰ยท์šด์˜ (ไฟก)")
earth_output = gr.Textbox(
label="",
lines=10,
max_lines=15,
interactive=False,
elem_classes=["earth-box"]
)
with gr.Column():
gr.Markdown("### โš™๏ธ ้‡‘ - ์•„ํ‚คํ…์ฒ˜ (็ฆฎ)")
metal_output = gr.Textbox(
label="",
lines=10,
max_lines=15,
interactive=False,
elem_classes=["metal-box"]
)
with gr.Row():
with gr.Column():
gr.Markdown("### ๐Ÿ’ง ๆฐด - R&D (ๆ™บ)")
water_output = gr.Textbox(
label="",
lines=10,
max_lines=15,
interactive=False,
elem_classes=["water-box"]
)
with gr.Column():
gr.Markdown("### ๐Ÿ” ์ค‘๋ฆฝ์  ๋น„ํ‰์ž")
critic_output = gr.Textbox(
label="",
lines=10,
max_lines=15,
interactive=False,
elem_classes=["critic-box"]
)
# ์˜ˆ์ œ
gr.Examples(
examples=[
"์ง€์† ๊ฐ€๋Šฅํ•œ ์Šค๋งˆํŠธ์‹œํ‹ฐ ๊ตฌ์ถ•์„ ์œ„ํ•œ ์ข…ํ•ฉ ์ „๋žต์€?",
"AI ๊ธฐ๋ฐ˜ ํ—ฌ์Šค์ผ€์–ด ์„œ๋น„์Šค ํ”Œ๋žซํผ ๊ฐœ๋ฐœ ๊ณ„ํš์€?",
"ํƒ„์†Œ์ค‘๋ฆฝ ๋‹ฌ์„ฑ์„ ์œ„ํ•œ ๊ธฐ์—… ์ „ํ™˜ ๋กœ๋“œ๋งต์€?",
"๋ฉ”ํƒ€๋ฒ„์Šค ๊ต์œก ํ”Œ๋žซํผ ๊ตฌ์ถ• ๋ฐฉ์•ˆ์€?",
"๋ธ”๋ก์ฒด์ธ ๊ธฐ๋ฐ˜ ๊ณต๊ธ‰๋ง ๊ด€๋ฆฌ ์‹œ์Šคํ…œ ๋„์ž… ์ „๋žต์€?"
],
inputs=user_input,
label="๐Ÿ’ก ์˜ˆ์ œ ์งˆ๋ฌธ"
)
# ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ
submit_btn.click(
fn=process_wuxing_query,
inputs=[user_input, llm_mode],
outputs=[wood_output, fire_output, earth_output, metal_output, water_output, critic_output, final_report, status_text]
).then(
fn=lambda: "",
outputs=[user_input]
)
user_input.submit(
fn=process_wuxing_query,
inputs=[user_input, llm_mode],
outputs=[wood_output, fire_output, earth_output, metal_output, water_output, critic_output, final_report, status_text]
).then(
fn=lambda: "",
outputs=[user_input]
)
clear_btn.click(
fn=clear_wuxing,
outputs=[wood_output, fire_output, earth_output, metal_output, water_output, critic_output, final_report, status_text]
)
if __name__ == "__main__":
app.queue()
app.launch(
server_name="0.0.0.0",
server_port=7860,
share=True,
show_error=True
)