Spaces:
Running
Running
| # tools/profile_analyzer.py | |
| import json | |
| import traceback | |
| import pandas as pd | |
| import math | |
| import streamlit as st | |
| from langchain_core.tools import tool | |
| from langchain_core.messages import HumanMessage | |
| import config | |
| from modules.llm_provider import get_llm | |
| # filtering ๋ชจ๋์์ ๋ ์ง ์์ธก ํจ์ ๊ฐ์ ธ์ค๊ธฐ | |
| from modules.filtering import FestivalRecommender | |
| logger = config.get_logger(__name__) | |
| # nan ๊ฐ ์ฒ๋ฆฌ๊ธฐ | |
| def replace_nan_with_none(data): | |
| if isinstance(data, dict): | |
| return {k: replace_nan_with_none(v) for k, v in data.items()} | |
| elif isinstance(data, list): | |
| return [replace_nan_with_none(i) for i in data] | |
| elif isinstance(data, float) and math.isnan(data): | |
| return None | |
| return data | |
| # ์ถ์ ๋ฐ์ดํฐ ๋ก๋ | |
| def _load_festival_data(): | |
| try: | |
| file_path = config.PATH_FESTIVAL_DF | |
| if not file_path.exists(): | |
| logger.error(f"--- [Tool Definition ERROR] '{config.PATH_FESTIVAL_DF}' ํ์ผ์ ์ฐพ์ ์ ์์ต๋๋ค.") | |
| return None | |
| df = pd.read_csv(file_path) | |
| if '์ถ์ ๋ช ' not in df.columns: | |
| logger.error("--- [Tool Definition ERROR] '์ถ์ ๋ช ' ์ปฌ๋ผ์ด df์ ์์ต๋๋ค.") | |
| return None | |
| df_dict = df.set_index('์ถ์ ๋ช ').to_dict(orient='index') | |
| logger.info(f"--- [Cache] ์ถ์ ์๋ณธ CSV ๋ก๋ ๋ฐ ๋์ ๋๋ฆฌ ๋ณํ ์๋ฃ (์ด {len(df_dict)}๊ฐ) ---") | |
| return df_dict | |
| except Exception as e: | |
| logger.critical(f"--- [Tool Definition CRITICAL ERROR] ์ถ์ ๋ฐ์ดํฐ ๋ก๋ ์คํจ: {e} ---", exc_info=True) | |
| return None | |
| # ---------------------------- | |
| # Tool 1: ํน์ ์ถ์ ์ ๋ณด ์กฐํ | |
| def get_festival_profile_by_name(festival_name: str) -> str: | |
| """ | |
| ์ถ์ ์ด๋ฆ์ ์ ๋ ฅ๋ฐ์, ํด๋น ์ถ์ ์ ์์ธ ํ๋กํ(์๊ฐ, ์ง์ญ, ํค์๋, ๊ธฐ๊ฐ, ๊ณ ๊ฐ์ธต ๋ฑ)์ | |
| JSON ๋ฌธ์์ด๋ก ๋ฐํํฉ๋๋ค. ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ ํํ ์ด๋ฆ์ ์ฐพ์์ผ ํฉ๋๋ค. | |
| (์: "๋ณด๋ น๋จธ๋์ถ์ ์์ธ ์ ๋ณด ์๋ ค์ค") | |
| """ | |
| logger.info(f"--- [Tool] 'ํน์ ์ถ์ ์ ๋ณด ์กฐํ' ๋๊ตฌ ํธ์ถ (๋์: {festival_name}) ---") | |
| try: | |
| festival_db = _load_festival_data() | |
| if festival_db is None: | |
| return json.dumps({"error": "์ถ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๋ก๋ํ์ง ๋ชปํ์ต๋๋ค."}) | |
| profile_dict = festival_db.get(festival_name) | |
| if profile_dict: | |
| profile_dict = replace_nan_with_none(profile_dict) | |
| profile_dict['์ถ์ ๋ช '] = festival_name | |
| return json.dumps(profile_dict, ensure_ascii=False) | |
| else: | |
| return json.dumps({"error": f"'{festival_name}' ์ถ์ ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค. ์ฒ ์๋ฅผ ํ์ธํด์ฃผ์ธ์."}) | |
| except Exception as e: | |
| logger.critical(f"--- [Tool CRITICAL] 'ํน์ ์ถ์ ์ ๋ณด ์กฐํ' ์ค ์ค๋ฅ: {e} ---", exc_info=True) | |
| return json.dumps({"error": f"'{festival_name}' ์ถ์ ๊ฒ์ ์ค ์ค๋ฅ ๋ฐ์: {e}"}) | |
| # ---------------------------- | |
| # Tool 2: ๊ฐ๋งน์ ํ๋กํ ๋ถ์ (LLM) | |
| def analyze_merchant_profile(store_profile: str) -> str: | |
| """ | |
| ๊ฐ๋งน์ (๊ฐ๊ฒ)์ ํ๋กํ ๋ฐ์ดํฐ(JSON ๋ฌธ์์ด)๋ฅผ ์ ๋ ฅ๋ฐ์, LLM์ ์ฌ์ฉํ์ฌ | |
| [๊ฐ์ , ์ฝ์ , ๊ธฐํ ์์ธ]์ ๋ถ์ํ๋ ์ปจ์คํ ๋ฆฌํฌํธ๋ฅผ ์์ฑํฉ๋๋ค. | |
| ์ด ๋๊ตฌ๋ ๊ฐ๊ฒ์ ํ์ฌ ์ํ๋ฅผ ์ง๋จํ๊ณ ๋ง์ผํ ์ ๋ต์ ์ ์ํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. | |
| """ | |
| logger.info("--- [Tool] '๊ฐ๋งน์ ํ๋กํ ๋ถ์' ๋๊ตฌ ํธ์ถ ---") | |
| try: | |
| llm = get_llm(temperature=0.3) | |
| prompt = f""" | |
| ๋น์ ์ ์ต๊ณ ์ ์๊ถ ๋ถ์ ์ ๋ฌธ๊ฐ์ ๋๋ค. | |
| ์๋ [๊ฐ๊ฒ ํ๋กํ] ๋ฐ์ดํฐ๋ฅผ ๋ฐํ์ผ๋ก, ์ด ๊ฐ๊ฒ์ [๊ฐ์ ], [์ฝ์ ], [๊ธฐํ ์์ธ]์ | |
| ์ฌ์ฅ๋์ด ์ดํดํ๊ธฐ ์ฝ๊ฒ ์ปจ์คํ ๋ฆฌํฌํธ ํ์์ผ๋ก ์์ฝํด์ฃผ์ธ์. | |
| [๊ฐ๊ฒ ํ๋กํ] | |
| {store_profile} | |
| [๋ถ์ ๊ฐ์ด๋๋ผ์ธ] | |
| 1. **๊ฐ์ (Strengths)**: '๋์ผ ์๊ถ/์ ์ข ๋๋น' ๋์ ์์น(๋งค์ถ, ๋ฐฉ๋ฌธ๊ฐ, ๊ฐ๋จ๊ฐ ๋ฑ)๋ '์ฌ๋ฐฉ๋ฌธ์จ' ๋ฑ์ ์ฐพ์ **๊ฒฝ์ ์ฐ์**๊ฐ ๋๋ ํต์ฌ ์์ ๊ฐ์กฐํ์ธ์. | |
| 2. **์ฝ์ (Weaknesses)**: '๋์ผ ์๊ถ/์ ์ข ๋๋น' ๋ฎ์ ์์น๋ '์ ๊ท ๊ณ ๊ฐ ๋น์จ' ๋ฑ์ ์ฐพ์ **๊ฐ์ ์ด ์๊ธํ ์์ญ**์ ์ธ๊ธํ์ธ์. | |
| 3. **๊ธฐํ (Opportunities)**: ๊ฐ๊ฒ์ ํ์ฌ ๊ฐ์ ๊ณผ '์ฃผ์ ๊ณ ๊ฐ์ธต'์ด๋ '์๊ถ' ํน์ฑ์ ๋ฐํ์ผ๋ก, **๊ฐ๊ฒ๊ฐ ํ์ฉํ ์ ์๋ ๋ง์ผํ (์: ํน์ ์ฐ๋ น๋ ํ๊ฒ, ์ ๊ท ๊ณ ๊ฐ ์ ์น)์ด ํจ๊ณผ์ ์ผ์ง ์ ์ํ๊ณ ์ด๋ฅผ ๋ฌ์ฑํ๊ธฐ ์ํ ๋ฐฉํฅ์ฑ์ ์ ์ํ์ธ์. | |
| 4. **ํ์**: ๋งํฌ๋ค์ด์ ์ฌ์ฉํ์ฌ ๋ช ํํ๊ณ ๊ฐ๋ ์ฑ ์ข๊ฒ ์์ฑํ์ธ์. | |
| 5. **์ ๋ฌธ์ฑ/์น์ ํจ**: ์ ๋ฌธ์ ์ธ ๋ถ์ ์ฉ์ด๋ฅผ ์ฌ์ฉํ๋, ์ฌ์ฅ๋์ด ์ฝ๊ฒ ์ดํดํ ์ ์๋๋ก ์น์ ํ๊ณ ๋ช ํํ๊ฒ ์ค๋ช ํ์ธ์. | |
| 6. **(์์ฒญ 4) ์ทจ์์ ๊ธ์ง**: ์ ๋๋ก `~~text~~`์ ๊ฐ์ ์ทจ์์ ๋งํฌ๋ค์ด์ ์ฌ์ฉํ์ง ๋ง์ธ์. | |
| [๋ต๋ณ ํ์] | |
| ### ๐ช ์ฌ์ฅ๋ ๊ฐ๊ฒ ํ๋กํ ๋ถ์ ๋ฆฌํฌํธ | |
| **1. ๊ฐ์ (Strengths)** | |
| * [๋ถ์๋ ๊ฐ์ 1] (๋ถ์ ๊ทผ๊ฑฐ ๋ช ์) | |
| * [๋ถ์๋ ๊ฐ์ 2] (๋ถ์ ๊ทผ๊ฑฐ ๋ช ์) | |
| * [ํ์์ ์ถ๊ฐ ๊ฐ์ ] | |
| **2. ์ฝ์ (Weaknesses)** | |
| * [๋ถ์๋ ์ฝ์ 1] (๊ฐ์ ํ์์ฑ ๋ช ์) | |
| * [๋ถ์๋ ์ฝ์ 2] (๊ฐ์ ํ์์ฑ ๋ช ์) | |
| * [ํ์์ ์ถ๊ฐ ์ฝ์ ] | |
| **3. ๊ธฐํ (Opportunities)** | |
| * [๋ถ์๋ ๊ธฐํ ์์ธ 1] (ํ์ฉ ๋ฐฉ์ ์ ์) | |
| * [๋ถ์๋ ๊ธฐํ ์์ธ 2] (ํ์ฉ ๋ฐฉ์ ์ ์) | |
| * [ํ์์ ์ถ๊ฐ ๊ธฐํ ์์ธ] | |
| """ | |
| response = llm.invoke([HumanMessage(content=prompt)]) | |
| analysis_report = response.content.strip() | |
| return analysis_report | |
| except Exception as e: | |
| logger.critical(f"--- [Tool CRITICAL] '๊ฐ๋งน์ ํ๋กํ ๋ถ์' ์ค ์ค๋ฅ: {e} ---", exc_info=True) | |
| return f"๊ฐ๊ฒ ํ๋กํ์ ๋ถ์ํ๋ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: {e}" | |
| # ---------------------------- | |
| # Tool 3: ์ถ์ ํ๋กํ ๋ถ์ (LLM) | |
| def analyze_festival_profile(festival_name: str) -> str: | |
| """ | |
| ์ถ์ ์ด๋ฆ์ ์ ๋ ฅ๋ฐ์, ํด๋น ์ถ์ ์ ์์ธ ํ๋กํ์ ์กฐํํ๊ณ , | |
| LLM์ ์ฌ์ฉํ์ฌ [ํต์ฌ ํน์ง]๊ณผ [์ฃผ์ ๋ฐฉ๋ฌธ๊ฐ ํน์ฑ]์ ์์ฝ ๋ฆฌํฌํธ๋ก ๋ฐํํฉ๋๋ค. | |
| (์: "๋ณด๋ น๋จธ๋์ถ์ ๋ ์ด๋ค ์ถ์ ์ผ?") | |
| """ | |
| logger.info(f"--- [Tool] '์ถ์ ํ๋กํ ๋ถ์' ๋๊ตฌ ํธ์ถ (๋์: {festival_name}) ---") | |
| try: | |
| # 1. Tool 1 ํธ์ถ | |
| profile_json = get_festival_profile_by_name.invoke(festival_name) | |
| profile_dict = json.loads(profile_json) | |
| if "error" in profile_dict: | |
| return profile_json | |
| # 2. LLM ์์ฝ์ ์ํ ์ ๋ณด ์ถ์ถ | |
| summary = { | |
| "์ถ์ ๋ช ": profile_dict.get('์ถ์ ๋ช '), | |
| "์๊ฐ": profile_dict.get('์๊ฐ'), | |
| "์ง์ญ": profile_dict.get('์ง์ญ'), | |
| "ํค์๋": profile_dict.get('ํค์๋'), | |
| "2025_๊ธฐ๊ฐ": profile_dict.get('2025_๊ธฐ๊ฐ'), | |
| "์ฃผ์_๊ณ ๊ฐ์ธต": profile_dict.get('์ฃผ์๊ณ ๊ฐ์ธต', 'N/A'), | |
| "์ฃผ์_๋ฐฉ๋ฌธ์": profile_dict.get('์ฃผ์๋ฐฉ๋ฌธ์', 'N/A'), | |
| "์ถ์ _์ธ๊ธฐ๋": profile_dict.get('์ถ์ ์ธ๊ธฐ', 'N/A'), | |
| "์ธ๊ธฐ๋_์ ์": profile_dict.get('์ธ๊ธฐ๋_์ ์', 'N/A'), | |
| "ํํ์ด์ง": profile_dict.get('ํํ์ด์ง') | |
| } | |
| # 2026๋ ๋ ์ง ์์ธก ์ถ๊ฐ | |
| temp_recommender = FestivalRecommender("", "") | |
| predicted_2026_timing = temp_recommender._predict_next_year_date(summary["2025_๊ธฐ๊ฐ"]) | |
| summary_str = json.dumps(summary, ensure_ascii=False, indent=2) | |
| llm = get_llm(temperature=0.1) | |
| # --- ํ๋กฌํํธ ์์ --- | |
| prompt = f""" | |
| ๋น์ ์ ์ถ์ ์ ๋ฌธ ๋ถ์๊ฐ์ ๋๋ค. ์๋ [์ถ์ ํ๋กํ ์์ฝ]์ ๋ฐํ์ผ๋ก, | |
| ์ด ์ถ์ ์ **ํต์ฌ ํน์ง**๊ณผ **์ฃผ์ ๋ฐฉ๋ฌธ๊ฐ(ํ๊ฒ ๊ณ ๊ฐ) ํน์ฑ**์ | |
| ์ดํดํ๊ธฐ ์ฝ๊ฒ ์์ฝํด์ฃผ์ธ์. | |
| [์ถ์ ํ๋กํ ์์ฝ] | |
| {summary_str} | |
| [๋ถ์ ๊ฐ์ด๋๋ผ์ธ] | |
| 1. **ํต์ฌ ํน์ง**: ์ ๋ ฅ๋ **'์๊ฐ'** ๋ด์ฉ์ ๋ฐํ์ผ๋ก ์ถ์ ์ ์ฃผ์ ์ ์ฃผ์ ๋ด์ฉ์ **2~3๋ฌธ์ฅ์ผ๋ก ์์ธํ ์์ฝ**ํ๊ณ , 'ํค์๋'์ '์ถ์ _์ธ๊ธฐ๋', '์ธ๊ธฐ๋_์ ์'๋ฅผ ์ธ๊ธํ์ฌ ๋ถ์ฐ ์ค๋ช ํฉ๋๋ค. (์: "'{summary.get("์๊ฐ", "์๊ฐ ์ ๋ณด ์์")[:50]}...'์(๋ฅผ) ์ฃผ์ ๋ก ํ๋ ์ถ์ ์ ๋๋ค. ์ฃผ์ ํค์๋๋ '{summary.get("ํค์๋", "N/A")}'์ด๋ฉฐ, ์ธ๊ธฐ๋๋ '{summary.get("์ถ์ _์ธ๊ธฐ๋", "N/A")}' ์์ค์ ๋๋ค.") | |
| 2. **์ฃผ์ ๋ฐฉ๋ฌธ๊ฐ**: '์ฃผ์_๊ณ ๊ฐ์ธต'๊ณผ '์ฃผ์_๋ฐฉ๋ฌธ์' ์ปฌ๋ผ์ ์ง์ ์ธ์ฉํ์ฌ ์ค๋ช ํฉ๋๋ค. | |
| (์: {summary.get("์ฃผ์_๊ณ ๊ฐ์ธต", "N/A")}์ด ์ฃผ๋ก ๋ฐฉ๋ฌธํ๋ฉฐ, {summary.get("์ฃผ์_๋ฐฉ๋ฌธ์", "N/A")} ๋น์จ์ด ๋์ต๋๋ค.) | |
| 3. **ํ์**: ์๋์ ๊ฐ์ ๋งํฌ๋ค์ด ํ์์ผ๋ก ๋ต๋ณ์ ์์ฑํ์ธ์. | |
| 4. **์ทจ์์ ๊ธ์ง**: ์ ๋๋ก `~~text~~`์ ๊ฐ์ ์ทจ์์ ๋งํฌ๋ค์ด์ ์ฌ์ฉํ์ง ๋ง์ธ์. | |
| [๋ต๋ณ ํ์] | |
| ### ๐ ์ถ์ ํ๋กํ ๋ถ์ ๋ฆฌํฌํธ: {summary.get("์ถ์ ๋ช ")} | |
| **1. ์ถ์ ํต์ฌ ํน์ง** | |
| * [์ถ์ ์๊ฐ ๋ด์ฉ์ ๋ฐํ์ผ๋ก 2~3๋ฌธ์ฅ ์์ฝ. ํค์๋์ ์ธ๊ธฐ๋ ํฌํจ] | |
| **2. ์ฃผ์ ๋ฐฉ๋ฌธ๊ฐ ํน์ฑ** | |
| * **์ฃผ์ ๊ณ ๊ฐ์ธต:** {summary.get("์ฃผ์_๊ณ ๊ฐ์ธต")} | |
| * **์ฃผ์ ๋ฐฉ๋ฌธ์:** {summary.get("์ฃผ์_๋ฐฉ๋ฌธ์")} | |
| **3. 2026๋ ๊ฐ์ต ๊ธฐ๊ฐ (์์)** | |
| * {predicted_2026_timing} | |
| **4. ํํ์ด์ง** | |
| * {summary.get("ํํ์ด์ง", "์ ๋ณด ์์")} | |
| """ | |
| response = llm.invoke([HumanMessage(content=prompt)]) | |
| analysis_report = response.content.strip() | |
| return analysis_report | |
| except Exception as e: | |
| logger.critical(f"--- [Tool CRITICAL] '์ถ์ ํ๋กํ ๋ถ์' ์ค ์ค๋ฅ: {e} ---", exc_info=True) | |
| return f"'{festival_name}' ์ถ์ ํ๋กํ์ ๋ถ์ํ๋ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: {e}" | |