Delete app.py
Browse files
app.py
DELETED
@@ -1,1505 +0,0 @@
|
|
1 |
-
import os, json, re, logging, requests, markdown, time, io
|
2 |
-
from datetime import datetime
|
3 |
-
import random
|
4 |
-
import base64
|
5 |
-
from io import BytesIO
|
6 |
-
from PIL import Image
|
7 |
-
|
8 |
-
import streamlit as st
|
9 |
-
from openai import OpenAI
|
10 |
-
|
11 |
-
from gradio_client import Client
|
12 |
-
import pandas as pd
|
13 |
-
import PyPDF2 # For handling PDF files
|
14 |
-
import kagglehub
|
15 |
-
|
16 |
-
# ββββββββββββββββββββββββββββββββ Environment Variables / Constants βββββββββββββββββββββββββ
|
17 |
-
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
|
18 |
-
BRAVE_KEY = os.getenv("SERPHOUSE_API_KEY", "") # Keep this name
|
19 |
-
BRAVE_ENDPOINT = "https://api.search.brave.com/res/v1/web/search"
|
20 |
-
BRAVE_VIDEO_ENDPOINT = "https://api.search.brave.com/res/v1/videos/search"
|
21 |
-
BRAVE_NEWS_ENDPOINT = "https://api.search.brave.com/res/v1/news/search"
|
22 |
-
IMAGE_API_URL = "http://211.233.58.201:7896"
|
23 |
-
MAX_TOKENS = 7999
|
24 |
-
KAGGLE_API_KEY = os.getenv("KDATA_API", "")
|
25 |
-
|
26 |
-
# Set Kaggle API key
|
27 |
-
os.environ["KAGGLE_KEY"] = KAGGLE_API_KEY
|
28 |
-
|
29 |
-
# Analysis modes and style definitions
|
30 |
-
ANALYSIS_MODES = {
|
31 |
-
"price_forecast": "λμ°λ¬Ό κ°κ²© μμΈ‘κ³Ό μμ₯ λΆμ",
|
32 |
-
"market_trend": "μμ₯ λν₯ λ° μμ ν¨ν΄ λΆμ",
|
33 |
-
"production_analysis": "μμ°λ λΆμ λ° μλ μ보 μ λ§",
|
34 |
-
"agricultural_policy": "λμ
μ μ±
λ° κ·μ μν₯ λΆμ",
|
35 |
-
"climate_impact": "κΈ°ν λ³νκ° λμ
μ λ―ΈμΉλ μν₯ λΆμ"
|
36 |
-
}
|
37 |
-
|
38 |
-
RESPONSE_STYLES = {
|
39 |
-
"professional": "μ λ¬Έμ μ΄κ³ νμ μ μΈ λΆμ",
|
40 |
-
"simple": "μ½κ² μ΄ν΄ν μ μλ κ°κ²°ν μ€λͺ
",
|
41 |
-
"detailed": "μμΈν ν΅κ³ κΈ°λ° κΉμ΄ μλ λΆμ",
|
42 |
-
"action_oriented": "μ€ν κ°λ₯ν μ‘°μΈκ³Ό μΆμ² μ€μ¬"
|
43 |
-
}
|
44 |
-
|
45 |
-
# Example search queries
|
46 |
-
EXAMPLE_QUERIES = {
|
47 |
-
"example1": "μ κ°κ²© μΆμΈ λ° ν₯ν 6κ°μ μ λ§μ λΆμν΄μ£ΌμΈμ",
|
48 |
-
"example2": "κΈ°ν λ³νλ‘ νκ΅ κ³ΌμΌ μμ° μ λ΅κ³Ό μμ μμΈ‘ λ³΄κ³ μλ₯Ό μμ±νλΌ.",
|
49 |
-
"example3": "2025λ
λΆν° 2030λ
κΉμ§ μΆ©λΆ μ¦νκ΅°μμ μ¬λ°°νλ©΄ μ λ§ν μλ¬Όμ? μμ΅μ±κ³Ό κ΄λ¦¬μ±μ΄ μ’μμΌνλ€"
|
50 |
-
}
|
51 |
-
|
52 |
-
# ββββββββββββββββββββββββββββββββ Logging ββββββββββββββββββββββββββββββββ
|
53 |
-
logging.basicConfig(level=logging.INFO,
|
54 |
-
format="%(asctime)s - %(levelname)s - %(message)s")
|
55 |
-
|
56 |
-
# ββββββββββββββββββββββββββββββββ OpenAI Client ββββββββββββββββββββββββββ
|
57 |
-
|
58 |
-
@st.cache_resource
|
59 |
-
def get_openai_client():
|
60 |
-
"""Create an OpenAI client with timeout and retry settings."""
|
61 |
-
if not OPENAI_API_KEY:
|
62 |
-
raise RuntimeError("β οΈ OPENAI_API_KEY νκ²½ λ³μκ° μ€μ λμ§ μμμ΅λλ€.")
|
63 |
-
return OpenAI(
|
64 |
-
api_key=OPENAI_API_KEY,
|
65 |
-
timeout=60.0,
|
66 |
-
max_retries=3
|
67 |
-
)
|
68 |
-
|
69 |
-
# ββββββββββββββββββββββββββββββ Kaggle Dataset Access ββββββββββββββββββββββ
|
70 |
-
@st.cache_resource
|
71 |
-
def load_agriculture_dataset():
|
72 |
-
"""Download and load the UN agriculture dataset from Kaggle"""
|
73 |
-
try:
|
74 |
-
path = kagglehub.dataset_download("unitednations/global-food-agriculture-statistics")
|
75 |
-
logging.info(f"Kaggle dataset downloaded to: {path}")
|
76 |
-
|
77 |
-
# Load metadata about available files
|
78 |
-
available_files = []
|
79 |
-
for root, dirs, files in os.walk(path):
|
80 |
-
for file in files:
|
81 |
-
if file.endswith('.csv'):
|
82 |
-
file_path = os.path.join(root, file)
|
83 |
-
file_size = os.path.getsize(file_path) / (1024 * 1024) # Size in MB
|
84 |
-
available_files.append({
|
85 |
-
'name': file,
|
86 |
-
'path': file_path,
|
87 |
-
'size_mb': round(file_size, 2)
|
88 |
-
})
|
89 |
-
|
90 |
-
return {
|
91 |
-
'base_path': path,
|
92 |
-
'files': available_files
|
93 |
-
}
|
94 |
-
except Exception as e:
|
95 |
-
logging.error(f"Error loading Kaggle dataset: {e}")
|
96 |
-
return None
|
97 |
-
|
98 |
-
# New function to load Advanced Soybean Agricultural Dataset
|
99 |
-
@st.cache_resource
|
100 |
-
def load_soybean_dataset():
|
101 |
-
"""Download and load the Advanced Soybean Agricultural Dataset from Kaggle"""
|
102 |
-
try:
|
103 |
-
path = kagglehub.dataset_download("wisam1985/advanced-soybean-agricultural-dataset-2025")
|
104 |
-
logging.info(f"Soybean dataset downloaded to: {path}")
|
105 |
-
|
106 |
-
available_files = []
|
107 |
-
for root, dirs, files in os.walk(path):
|
108 |
-
for file in files:
|
109 |
-
if file.endswith(('.csv', '.xlsx')):
|
110 |
-
file_path = os.path.join(root, file)
|
111 |
-
file_size = os.path.getsize(file_path) / (1024 * 1024) # Size in MB
|
112 |
-
available_files.append({
|
113 |
-
'name': file,
|
114 |
-
'path': file_path,
|
115 |
-
'size_mb': round(file_size, 2)
|
116 |
-
})
|
117 |
-
|
118 |
-
return {
|
119 |
-
'base_path': path,
|
120 |
-
'files': available_files
|
121 |
-
}
|
122 |
-
except Exception as e:
|
123 |
-
logging.error(f"Error loading Soybean dataset: {e}")
|
124 |
-
return None
|
125 |
-
|
126 |
-
# Function to load Crop Recommendation Dataset
|
127 |
-
@st.cache_resource
|
128 |
-
def load_crop_recommendation_dataset():
|
129 |
-
"""Download and load the Soil and Environmental Variables Crop Recommendation Dataset"""
|
130 |
-
try:
|
131 |
-
path = kagglehub.dataset_download("agriinnovate/agricultural-crop-dataset")
|
132 |
-
logging.info(f"Crop recommendation dataset downloaded to: {path}")
|
133 |
-
|
134 |
-
available_files = []
|
135 |
-
for root, dirs, files in os.walk(path):
|
136 |
-
for file in files:
|
137 |
-
if file.endswith(('.csv', '.xlsx')):
|
138 |
-
file_path = os.path.join(root, file)
|
139 |
-
file_size = os.path.getsize(file_path) / (1024 * 1024) # Size in MB
|
140 |
-
available_files.append({
|
141 |
-
'name': file,
|
142 |
-
'path': file_path,
|
143 |
-
'size_mb': round(file_size, 2)
|
144 |
-
})
|
145 |
-
|
146 |
-
return {
|
147 |
-
'base_path': path,
|
148 |
-
'files': available_files
|
149 |
-
}
|
150 |
-
except Exception as e:
|
151 |
-
logging.error(f"Error loading Crop recommendation dataset: {e}")
|
152 |
-
return None
|
153 |
-
|
154 |
-
# Function to load Climate Change Impact Dataset
|
155 |
-
@st.cache_resource
|
156 |
-
def load_climate_impact_dataset():
|
157 |
-
"""Download and load the Climate Change Impact on Agriculture Dataset"""
|
158 |
-
try:
|
159 |
-
path = kagglehub.dataset_download("waqi786/climate-change-impact-on-agriculture")
|
160 |
-
logging.info(f"Climate impact dataset downloaded to: {path}")
|
161 |
-
|
162 |
-
available_files = []
|
163 |
-
for root, dirs, files in os.walk(path):
|
164 |
-
for file in files:
|
165 |
-
if file.endswith(('.csv', '.xlsx')):
|
166 |
-
file_path = os.path.join(root, file)
|
167 |
-
file_size = os.path.getsize(file_path) / (1024 * 1024) # Size in MB
|
168 |
-
available_files.append({
|
169 |
-
'name': file,
|
170 |
-
'path': file_path,
|
171 |
-
'size_mb': round(file_size, 2)
|
172 |
-
})
|
173 |
-
|
174 |
-
return {
|
175 |
-
'base_path': path,
|
176 |
-
'files': available_files
|
177 |
-
}
|
178 |
-
except Exception as e:
|
179 |
-
logging.error(f"Error loading Climate impact dataset: {e}")
|
180 |
-
return None
|
181 |
-
|
182 |
-
def get_dataset_summary():
|
183 |
-
"""Generate a summary of the available agriculture datasets"""
|
184 |
-
dataset_info = load_agriculture_dataset()
|
185 |
-
if not dataset_info:
|
186 |
-
return "Failed to load the UN global food and agriculture statistics dataset."
|
187 |
-
|
188 |
-
summary = "# UN κΈλ‘λ² μλ λ° λμ
ν΅κ³ λ°μ΄ν°μ
\n\n"
|
189 |
-
summary += f"μ΄ {len(dataset_info['files'])}κ°μ CSV νμΌμ΄ ν¬ν¨λμ΄ μμ΅λλ€.\n\n"
|
190 |
-
|
191 |
-
# List files with sizes
|
192 |
-
summary += "## μ¬μ© κ°λ₯ν λ°μ΄ν° νμΌ:\n\n"
|
193 |
-
for i, file_info in enumerate(dataset_info['files'][:10], 1): # Limit to first 10 files
|
194 |
-
summary += f"{i}. **{file_info['name']}** ({file_info['size_mb']} MB)\n"
|
195 |
-
|
196 |
-
if len(dataset_info['files']) > 10:
|
197 |
-
summary += f"\n...μΈ {len(dataset_info['files']) - 10}κ° νμΌ\n"
|
198 |
-
|
199 |
-
# Add example of data structure
|
200 |
-
try:
|
201 |
-
if dataset_info['files']:
|
202 |
-
sample_file = dataset_info['files'][0]['path']
|
203 |
-
df = pd.read_csv(sample_file, nrows=5)
|
204 |
-
summary += "\n## λ°μ΄ν° μν ꡬ쑰:\n\n"
|
205 |
-
summary += df.head(5).to_markdown() + "\n\n"
|
206 |
-
|
207 |
-
summary += "## λ°μ΄ν°μ
λ³μ μ€λͺ
:\n\n"
|
208 |
-
for col in df.columns:
|
209 |
-
summary += f"- **{col}**: [λ³μ μ€λͺ
νμ]\n"
|
210 |
-
except Exception as e:
|
211 |
-
logging.error(f"Error generating dataset sample: {e}")
|
212 |
-
summary += "\nλ°μ΄ν° μνμ μμ±νλ μ€ μ€λ₯κ° λ°μνμ΅λλ€.\n"
|
213 |
-
|
214 |
-
return summary
|
215 |
-
|
216 |
-
def analyze_dataset_for_query(query):
|
217 |
-
"""Find and analyze relevant data from the dataset based on the query"""
|
218 |
-
dataset_info = load_agriculture_dataset()
|
219 |
-
if not dataset_info:
|
220 |
-
return "λ°μ΄ν°μ
μ λΆλ¬μ¬ μ μμ΅λλ€. Kaggle API μ°κ²°μ νμΈν΄μ£ΌμΈμ."
|
221 |
-
|
222 |
-
# Extract key terms from the query
|
223 |
-
query_lower = query.lower()
|
224 |
-
|
225 |
-
# Define keywords to look for in the dataset files
|
226 |
-
keywords = {
|
227 |
-
"μ": ["rice", "grain"],
|
228 |
-
"λ°": ["wheat", "grain"],
|
229 |
-
"μ₯μμ": ["corn", "maize", "grain"],
|
230 |
-
"μ±μ": ["vegetable", "produce"],
|
231 |
-
"κ³ΌμΌ": ["fruit", "produce"],
|
232 |
-
"κ°κ²©": ["price", "cost", "value"],
|
233 |
-
"μμ°": ["production", "yield", "harvest"],
|
234 |
-
"μμΆ": ["export", "trade"],
|
235 |
-
"μμ
": ["import", "trade"],
|
236 |
-
"μλΉ": ["consumption", "demand"]
|
237 |
-
}
|
238 |
-
|
239 |
-
# Find relevant files based on the query
|
240 |
-
relevant_files = []
|
241 |
-
|
242 |
-
# First check for Korean keywords in the query
|
243 |
-
found_keywords = []
|
244 |
-
for k_term, e_terms in keywords.items():
|
245 |
-
if k_term in query_lower:
|
246 |
-
found_keywords.extend([k_term] + e_terms)
|
247 |
-
|
248 |
-
# If no Korean keywords found, check for English terms in the filenames
|
249 |
-
if not found_keywords:
|
250 |
-
# Generic search through all files
|
251 |
-
relevant_files = dataset_info['files'][:5] # Take first 5 files as default
|
252 |
-
else:
|
253 |
-
# Search for files related to the found keywords
|
254 |
-
for file_info in dataset_info['files']:
|
255 |
-
file_name_lower = file_info['name'].lower()
|
256 |
-
for keyword in found_keywords:
|
257 |
-
if keyword.lower() in file_name_lower:
|
258 |
-
relevant_files.append(file_info)
|
259 |
-
break
|
260 |
-
|
261 |
-
# If still no relevant files, take the first 5 files
|
262 |
-
if not relevant_files:
|
263 |
-
relevant_files = dataset_info['files'][:5]
|
264 |
-
|
265 |
-
# Read and analyze the relevant files
|
266 |
-
analysis_result = "# λμ
λ°μ΄ν° λΆμ κ²°κ³Ό\n\n"
|
267 |
-
analysis_result += f"쿼리: '{query}'μ λν λΆμμ μννμ΅λλ€.\n\n"
|
268 |
-
|
269 |
-
if found_keywords:
|
270 |
-
analysis_result += f"## λΆμ ν€μλ: {', '.join(set(found_keywords))}\n\n"
|
271 |
-
|
272 |
-
# Process each relevant file
|
273 |
-
for file_info in relevant_files[:3]: # Limit to 3 files for performance
|
274 |
-
try:
|
275 |
-
analysis_result += f"## νμΌ: {file_info['name']}\n\n"
|
276 |
-
|
277 |
-
# Read the CSV file
|
278 |
-
df = pd.read_csv(file_info['path'])
|
279 |
-
|
280 |
-
# Basic file stats
|
281 |
-
analysis_result += f"- ν μ: {len(df)}\n"
|
282 |
-
analysis_result += f"- μ΄ μ: {len(df.columns)}\n"
|
283 |
-
analysis_result += f"- μ΄ λͺ©λ‘: {', '.join(df.columns.tolist())}\n\n"
|
284 |
-
|
285 |
-
# Sample data
|
286 |
-
analysis_result += "### λ°μ΄ν° μν:\n\n"
|
287 |
-
analysis_result += df.head(5).to_markdown() + "\n\n"
|
288 |
-
|
289 |
-
# Statistical summary of numeric columns
|
290 |
-
numeric_cols = df.select_dtypes(include=['number']).columns
|
291 |
-
if len(numeric_cols) > 0:
|
292 |
-
analysis_result += "### κΈ°λ³Έ ν΅κ³:\n\n"
|
293 |
-
stats_df = df[numeric_cols].describe()
|
294 |
-
analysis_result += stats_df.to_markdown() + "\n\n"
|
295 |
-
|
296 |
-
# Time series analysis if possible
|
297 |
-
time_cols = [col for col in df.columns if 'year' in col.lower() or 'date' in col.lower()]
|
298 |
-
if time_cols:
|
299 |
-
analysis_result += "### μκ³μ΄ ν¨ν΄:\n\n"
|
300 |
-
analysis_result += "λ°μ΄ν°μ
μ μκ° κ΄λ ¨ μ΄μ΄ μμ΄ μκ³μ΄ λΆμμ΄ κ°λ₯ν©λλ€.\n\n"
|
301 |
-
|
302 |
-
except Exception as e:
|
303 |
-
logging.error(f"Error analyzing file {file_info['name']}: {e}")
|
304 |
-
analysis_result += f"μ΄ νμΌ λΆμ μ€ μ€λ₯κ° λ°μνμ΅λλ€: {str(e)}\n\n"
|
305 |
-
|
306 |
-
analysis_result += "## λμ°λ¬Ό κ°κ²© μμΈ‘ λ° μμ λΆμμ λν μΈμ¬μ΄νΈ\n\n"
|
307 |
-
analysis_result += "λ°μ΄ν°μ
μμ μΆμΆν μ 보λ₯Ό λ°νμΌλ‘ λ€μ μΈμ¬μ΄νΈλ₯Ό μ 곡ν©λλ€:\n\n"
|
308 |
-
analysis_result += "1. λ°μ΄ν° κΈ°λ° λΆμ (κΈ°λ³Έμ μΈ μμ½)\n"
|
309 |
-
analysis_result += "2. μ£Όμ κ°κ²© λ° μμ λν₯\n"
|
310 |
-
analysis_result += "3. μμ°λ λ° λ¬΄μ ν¨ν΄\n\n"
|
311 |
-
|
312 |
-
analysis_result += "μ΄ λΆμμ UN κΈλ‘λ² μλ λ° λμ
ν΅κ³ λ°μ΄ν°μ
μ κΈ°λ°μΌλ‘ ν©λλ€.\n\n"
|
313 |
-
|
314 |
-
return analysis_result
|
315 |
-
|
316 |
-
# Function to analyze crop recommendation dataset
|
317 |
-
def analyze_crop_recommendation_dataset(query):
|
318 |
-
"""Find and analyze crop recommendation data based on the query"""
|
319 |
-
try:
|
320 |
-
dataset_info = load_crop_recommendation_dataset()
|
321 |
-
if not dataset_info or not dataset_info['files']:
|
322 |
-
return "μλ¬Ό μΆμ² λ°μ΄ν°μ
μ λΆλ¬μ¬ μ μμ΅λλ€."
|
323 |
-
|
324 |
-
analysis_result = "# ν μ λ° νκ²½ λ³μ κΈ°λ° μλ¬Ό μΆμ² λ°μ΄ν° λΆμ\n\n"
|
325 |
-
|
326 |
-
# Process main files
|
327 |
-
for file_info in dataset_info['files'][:2]: # Limit to the first 2 files
|
328 |
-
try:
|
329 |
-
analysis_result += f"## νμΌ: {file_info['name']}\n\n"
|
330 |
-
|
331 |
-
if file_info['name'].endswith('.csv'):
|
332 |
-
df = pd.read_csv(file_info['path'])
|
333 |
-
elif file_info['name'].endswith('.xlsx'):
|
334 |
-
df = pd.read_excel(file_info['path'])
|
335 |
-
else:
|
336 |
-
continue
|
337 |
-
|
338 |
-
# Basic dataset info
|
339 |
-
analysis_result += f"- λ°μ΄ν° ν¬κΈ°: {len(df)} ν Γ {len(df.columns)} μ΄\n"
|
340 |
-
analysis_result += f"- ν¬ν¨λ μλ¬Ό μ’
λ₯: "
|
341 |
-
|
342 |
-
# Check if crop column exists
|
343 |
-
crop_cols = [col for col in df.columns if 'crop' in col.lower() or 'μλ¬Ό' in col.lower()]
|
344 |
-
if crop_cols:
|
345 |
-
main_crop_col = crop_cols[0]
|
346 |
-
unique_crops = df[main_crop_col].unique()
|
347 |
-
analysis_result += f"{len(unique_crops)}μ’
({', '.join(str(c) for c in unique_crops[:10])})\n\n"
|
348 |
-
else:
|
349 |
-
analysis_result += "μλ¬Ό μ 보 μ΄μ μ°Ύμ μ μμ\n\n"
|
350 |
-
|
351 |
-
# Extract environmental factors
|
352 |
-
env_factors = [col for col in df.columns if col.lower() not in ['crop', 'label', 'id', 'index']]
|
353 |
-
if env_factors:
|
354 |
-
analysis_result += f"- κ³ λ €λ νκ²½ μμ: {', '.join(env_factors)}\n\n"
|
355 |
-
|
356 |
-
# Sample data
|
357 |
-
analysis_result += "### λ°μ΄ν° μν:\n\n"
|
358 |
-
analysis_result += df.head(5).to_markdown() + "\n\n"
|
359 |
-
|
360 |
-
# Summary statistics for environmental factors
|
361 |
-
if env_factors:
|
362 |
-
numeric_factors = df[env_factors].select_dtypes(include=['number']).columns
|
363 |
-
if len(numeric_factors) > 0:
|
364 |
-
analysis_result += "### νκ²½ μμ ν΅κ³:\n\n"
|
365 |
-
stats_df = df[numeric_factors].describe().round(2)
|
366 |
-
analysis_result += stats_df.to_markdown() + "\n\n"
|
367 |
-
|
368 |
-
# Check for query-specific crops
|
369 |
-
query_terms = query.lower().split()
|
370 |
-
relevant_crops = []
|
371 |
-
|
372 |
-
if crop_cols:
|
373 |
-
for crop in df[main_crop_col].unique():
|
374 |
-
crop_str = str(crop).lower()
|
375 |
-
if any(term in crop_str for term in query_terms):
|
376 |
-
relevant_crops.append(crop)
|
377 |
-
|
378 |
-
if relevant_crops:
|
379 |
-
analysis_result += f"### 쿼리 κ΄λ ¨ μλ¬Ό λΆμ: {', '.join(str(c) for c in relevant_crops)}\n\n"
|
380 |
-
for crop in relevant_crops[:3]: # Limit to 3 crops
|
381 |
-
crop_data = df[df[main_crop_col] == crop]
|
382 |
-
analysis_result += f"#### {crop} μλ¬Ό μμ½:\n\n"
|
383 |
-
analysis_result += f"- μν μ: {len(crop_data)}κ°\n"
|
384 |
-
|
385 |
-
if len(numeric_factors) > 0:
|
386 |
-
crop_stats = crop_data[numeric_factors].describe().round(2)
|
387 |
-
analysis_result += f"- νκ· νκ²½ 쑰건:\n"
|
388 |
-
for factor in numeric_factors[:5]: # Limit to 5 factors
|
389 |
-
analysis_result += f" * {factor}: {crop_stats.loc['mean', factor]}\n"
|
390 |
-
analysis_result += "\n"
|
391 |
-
|
392 |
-
except Exception as e:
|
393 |
-
logging.error(f"Error analyzing crop recommendation file {file_info['name']}: {e}")
|
394 |
-
analysis_result += f"λΆμ μ€λ₯: {str(e)}\n\n"
|
395 |
-
|
396 |
-
analysis_result += "## μλ¬Ό μΆμ² μΈμ¬μ΄νΈ\n\n"
|
397 |
-
analysis_result += "ν μ λ° νκ²½ λ³μ λ°μ΄ν°μ
λΆμ κ²°κ³Ό, λ€μκ³Ό κ°μ μ£Όμ μΈμ¬μ΄νΈλ₯Ό μ 곡ν©λλ€:\n\n"
|
398 |
-
analysis_result += "1. μ§μ νκ²½μ μ ν©ν μλ¬Ό μΆμ²\n"
|
399 |
-
analysis_result += "2. μλ¬Ό μμ°μ±μ μν₯μ λ―ΈμΉλ μ£Όμ νκ²½ μμΈ\n"
|
400 |
-
analysis_result += "3. μ§μ κ°λ₯ν λμ
μ μν μ΅μ μ μλ¬Ό μ ν κΈ°μ€\n\n"
|
401 |
-
|
402 |
-
return analysis_result
|
403 |
-
|
404 |
-
except Exception as e:
|
405 |
-
logging.error(f"Crop recommendation dataset analysis error: {e}")
|
406 |
-
return "μλ¬Ό μΆμ² λ°μ΄ν°μ
λΆμ μ€ μ€λ₯κ° λ°μνμ΅λλ€."
|
407 |
-
|
408 |
-
# Function to analyze climate impact dataset
|
409 |
-
def analyze_climate_impact_dataset(query):
|
410 |
-
"""Find and analyze climate impact on agriculture data based on the query"""
|
411 |
-
try:
|
412 |
-
dataset_info = load_climate_impact_dataset()
|
413 |
-
if not dataset_info or not dataset_info['files']:
|
414 |
-
return "κΈ°ν λ³ν μν₯ λ°μ΄ν°μ
μ λΆλ¬μ¬ μ μμ΅λλ€."
|
415 |
-
|
416 |
-
analysis_result = "# κΈ°ν λ³νκ° λμ
μ λ―ΈμΉλ μν₯ λ°μ΄ν° λΆμ\n\n"
|
417 |
-
|
418 |
-
# Process main files
|
419 |
-
for file_info in dataset_info['files'][:2]: # Limit to first 2 files
|
420 |
-
try:
|
421 |
-
analysis_result += f"## νμΌ: {file_info['name']}\n\n"
|
422 |
-
|
423 |
-
if file_info['name'].endswith('.csv'):
|
424 |
-
df = pd.read_csv(file_info['path'])
|
425 |
-
elif file_info['name'].endswith('.xlsx'):
|
426 |
-
df = pd.read_excel(file_info['path'])
|
427 |
-
else:
|
428 |
-
continue
|
429 |
-
|
430 |
-
# Basic dataset info
|
431 |
-
analysis_result += f"- λ°μ΄ν° ν¬κΈ°: {len(df)} ν Γ {len(df.columns)} μ΄\n"
|
432 |
-
|
433 |
-
# Check for region column
|
434 |
-
region_cols = [col for col in df.columns if 'region' in col.lower() or 'country' in col.lower() or 'μ§μ' in col.lower()]
|
435 |
-
if region_cols:
|
436 |
-
main_region_col = region_cols[0]
|
437 |
-
regions = df[main_region_col].unique()
|
438 |
-
analysis_result += f"- ν¬ν¨λ μ§μ: {len(regions)}κ° ({', '.join(str(r) for r in regions[:5])})\n"
|
439 |
-
|
440 |
-
# Identify climate and crop related columns
|
441 |
-
climate_cols = [col for col in df.columns if any(term in col.lower() for term in
|
442 |
-
['temp', 'rainfall', 'precipitation', 'climate', 'weather', 'κΈ°μ¨', 'κ°μλ'])]
|
443 |
-
crop_cols = [col for col in df.columns if any(term in col.lower() for term in
|
444 |
-
['yield', 'production', 'crop', 'harvest', 'μνλ', 'μμ°λ'])]
|
445 |
-
|
446 |
-
if climate_cols:
|
447 |
-
analysis_result += f"- κΈ°ν κ΄λ ¨ λ³μ: {', '.join(climate_cols)}\n"
|
448 |
-
if crop_cols:
|
449 |
-
analysis_result += f"- μλ¬Ό κ΄λ ¨ λ³μ: {', '.join(crop_cols)}\n\n"
|
450 |
-
|
451 |
-
# Sample data
|
452 |
-
analysis_result += "### λ°μ΄ν° μν:\n\n"
|
453 |
-
analysis_result += df.head(5).to_markdown() + "\n\n"
|
454 |
-
|
455 |
-
# Time series pattern if available
|
456 |
-
year_cols = [col for col in df.columns if 'year' in col.lower() or 'date' in col.lower() or 'μ°λ' in col.lower()]
|
457 |
-
if year_cols:
|
458 |
-
analysis_result += "### μκ³μ΄ κΈ°ν μν₯ ν¨ν΄:\n\n"
|
459 |
-
analysis_result += "μ΄ λ°μ΄ν°μ
μ μκ°μ λ°λ₯Έ κΈ°ν λ³νμ λμ
μμ°μ± κ°μ κ΄κ³λ₯Ό λΆμν μ μμ΅λλ€.\n\n"
|
460 |
-
|
461 |
-
# Statistical summary of key variables
|
462 |
-
key_vars = climate_cols + crop_cols
|
463 |
-
numeric_vars = df[key_vars].select_dtypes(include=['number']).columns
|
464 |
-
if len(numeric_vars) > 0:
|
465 |
-
analysis_result += "### μ£Όμ λ³μ ν΅κ³:\n\n"
|
466 |
-
stats_df = df[numeric_vars].describe().round(2)
|
467 |
-
analysis_result += stats_df.to_markdown() + "\n\n"
|
468 |
-
|
469 |
-
# Check for correlations between climate and crop variables
|
470 |
-
if len(climate_cols) > 0 and len(crop_cols) > 0:
|
471 |
-
numeric_climate = df[climate_cols].select_dtypes(include=['number']).columns
|
472 |
-
numeric_crop = df[crop_cols].select_dtypes(include=['number']).columns
|
473 |
-
|
474 |
-
if len(numeric_climate) > 0 and len(numeric_crop) > 0:
|
475 |
-
analysis_result += "### κΈ°νμ μλ¬Ό μμ° κ°μ μκ΄κ΄κ³:\n\n"
|
476 |
-
try:
|
477 |
-
corr_vars = list(numeric_climate)[:2] + list(numeric_crop)[:2] # Limit to 2 of each type
|
478 |
-
corr_df = df[corr_vars].corr().round(3)
|
479 |
-
analysis_result += corr_df.to_markdown() + "\n\n"
|
480 |
-
analysis_result += "μ μκ΄κ΄κ³ νλ κΈ°ν λ³μμ μλ¬Ό μμ°μ± κ°μ κ΄κ³ κ°λλ₯Ό 보μ¬μ€λλ€.\n\n"
|
481 |
-
except:
|
482 |
-
analysis_result += "μκ΄κ΄κ³ κ³μ° μ€ μ€λ₯κ° λ°μνμ΅λλ€.\n\n"
|
483 |
-
|
484 |
-
except Exception as e:
|
485 |
-
logging.error(f"Error analyzing climate impact file {file_info['name']}: {e}")
|
486 |
-
analysis_result += f"λΆμ μ€λ₯: {str(e)}\n\n"
|
487 |
-
|
488 |
-
analysis_result += "## κΈ°ν λ³ν μν₯ μΈμ¬μ΄νΈ\n\n"
|
489 |
-
analysis_result += "κΈ°ν λ³νκ° λμ
μ λ―ΈμΉλ μν₯ λ°μ΄ν° λΆμ κ²°κ³Ό, λ€μκ³Ό κ°μ μΈμ¬μ΄νΈλ₯Ό μ 곡ν©λλ€:\n\n"
|
490 |
-
analysis_result += "1. κΈ°μ¨ λ³νμ λ°λ₯Έ μλ¬Ό μμ°μ± λ³λ ν¨ν΄\n"
|
491 |
-
analysis_result += "2. κ°μλ λ³νκ° λμ
μνλμ λ―ΈμΉλ μν₯\n"
|
492 |
-
analysis_result += "3. κΈ°ν λ³νμ λμνκΈ° μν λμ
μ λ΅ μ μ\n"
|
493 |
-
analysis_result += "4. μ§μλ³ κΈ°ν μ·¨μ½μ± λ° μ μ λ°©μ\n\n"
|
494 |
-
|
495 |
-
return analysis_result
|
496 |
-
|
497 |
-
except Exception as e:
|
498 |
-
logging.error(f"Climate impact dataset analysis error: {e}")
|
499 |
-
return "κΈ°ν λ³ν μν₯ λ°μ΄ν°μ
λΆμ μ€ μ€λ₯κ° λ°μνμ΅λλ€."
|
500 |
-
|
501 |
-
# Function to analyze soybean dataset if selected
|
502 |
-
def analyze_soybean_dataset(query):
|
503 |
-
"""Find and analyze soybean agriculture data based on the query"""
|
504 |
-
try:
|
505 |
-
dataset_info = load_soybean_dataset()
|
506 |
-
if not dataset_info or not dataset_info['files']:
|
507 |
-
return "λλ λμ
λ°μ΄ν°μ
μ λΆλ¬μ¬ μ μμ΅λλ€."
|
508 |
-
|
509 |
-
analysis_result = "# κ³ κΈ λλ λμ
λ°μ΄ν° λΆμ\n\n"
|
510 |
-
|
511 |
-
# Process main files
|
512 |
-
for file_info in dataset_info['files'][:2]: # Limit to the first 2 files
|
513 |
-
try:
|
514 |
-
analysis_result += f"## νμΌ: {file_info['name']}\n\n"
|
515 |
-
|
516 |
-
if file_info['name'].endswith('.csv'):
|
517 |
-
df = pd.read_csv(file_info['path'])
|
518 |
-
elif file_info['name'].endswith('.xlsx'):
|
519 |
-
df = pd.read_excel(file_info['path'])
|
520 |
-
else:
|
521 |
-
continue
|
522 |
-
|
523 |
-
# Basic file stats
|
524 |
-
analysis_result += f"- λ°μ΄ν° ν¬κΈ°: {len(df)} ν Γ {len(df.columns)} μ΄\n"
|
525 |
-
|
526 |
-
# Check for region/location columns
|
527 |
-
location_cols = [col for col in df.columns if any(term in col.lower() for term in
|
528 |
-
['region', 'location', 'area', 'country', 'μ§μ'])]
|
529 |
-
if location_cols:
|
530 |
-
main_loc_col = location_cols[0]
|
531 |
-
locations = df[main_loc_col].unique()
|
532 |
-
analysis_result += f"- ν¬ν¨λ μ§μ: {len(locations)}κ° ({', '.join(str(loc) for loc in locations[:5])})\n"
|
533 |
-
|
534 |
-
# Identify yield and production columns
|
535 |
-
yield_cols = [col for col in df.columns if any(term in col.lower() for term in
|
536 |
-
['yield', 'production', 'harvest', 'μνλ', 'μμ°λ'])]
|
537 |
-
if yield_cols:
|
538 |
-
analysis_result += f"- μμ°μ± κ΄λ ¨ λ³μ: {', '.join(yield_cols)}\n"
|
539 |
-
|
540 |
-
# Identify environmental factors
|
541 |
-
env_cols = [col for col in df.columns if any(term in col.lower() for term in
|
542 |
-
['temp', 'rainfall', 'soil', 'fertilizer', 'nutrient', 'irrigation',
|
543 |
-
'κΈ°μ¨', 'κ°μλ', 'ν μ', 'λΉλ£', 'κ΄κ°'])]
|
544 |
-
if env_cols:
|
545 |
-
analysis_result += f"- νκ²½ κ΄λ ¨ λ³μ: {', '.join(env_cols)}\n\n"
|
546 |
-
|
547 |
-
# Sample data
|
548 |
-
analysis_result += "### λ°μ΄ν° μν:\n\n"
|
549 |
-
analysis_result += df.head(5).to_markdown() + "\n\n"
|
550 |
-
|
551 |
-
# Statistical summary of key variables
|
552 |
-
key_vars = yield_cols + env_cols
|
553 |
-
numeric_vars = df[key_vars].select_dtypes(include=['number']).columns
|
554 |
-
if len(numeric_vars) > 0:
|
555 |
-
analysis_result += "### μ£Όμ λ³μ ν΅κ³:\n\n"
|
556 |
-
stats_df = df[numeric_vars].describe().round(2)
|
557 |
-
analysis_result += stats_df.to_markdown() + "\n\n"
|
558 |
-
|
559 |
-
# Time series analysis if possible
|
560 |
-
year_cols = [col for col in df.columns if 'year' in col.lower() or 'date' in col.lower() or 'μ°λ' in col.lower()]
|
561 |
-
if year_cols:
|
562 |
-
analysis_result += "### μκ³μ΄ μμ°μ± ν¨ν΄:\n\n"
|
563 |
-
analysis_result += "μ΄ λ°μ΄ν°μ
μ μκ°μ λ°λ₯Έ λλ μμ°μ±μ λ³νλ₯Ό μΆμ ν μ μμ΅λλ€.\n\n"
|
564 |
-
|
565 |
-
# Check for correlations between environmental factors and yield
|
566 |
-
if len(env_cols) > 0 and len(yield_cols) > 0:
|
567 |
-
numeric_env = df[env_cols].select_dtypes(include=['number']).columns
|
568 |
-
numeric_yield = df[yield_cols].select_dtypes(include=['number']).columns
|
569 |
-
|
570 |
-
if len(numeric_env) > 0 and len(numeric_yield) > 0:
|
571 |
-
analysis_result += "### νκ²½ μμμ λλ μμ°μ± κ°μ μκ΄κ΄κ³:\n\n"
|
572 |
-
try:
|
573 |
-
corr_vars = list(numeric_env)[:3] + list(numeric_yield)[:2] # Limit variables
|
574 |
-
corr_df = df[corr_vars].corr().round(3)
|
575 |
-
analysis_result += corr_df.to_markdown() + "\n\n"
|
576 |
-
except:
|
577 |
-
analysis_result += "μκ΄κ΄κ³ κ³μ° μ€ μ€λ₯κ° λ°μνμ΅λλ€.\n\n"
|
578 |
-
|
579 |
-
except Exception as e:
|
580 |
-
logging.error(f"Error analyzing soybean file {file_info['name']}: {e}")
|
581 |
-
analysis_result += f"λΆμ μ€λ₯: {str(e)}\n\n"
|
582 |
-
|
583 |
-
analysis_result += "## λλ λμ
μΈμ¬μ΄νΈ\n\n"
|
584 |
-
analysis_result += "κ³ κΈ λλ λμ
λ°μ΄ν°μ
λΆμ κ²°κ³Ό, λ€μκ³Ό κ°μ μΈμ¬μ΄νΈλ₯Ό μ 곡ν©λλ€:\n\n"
|
585 |
-
analysis_result += "1. μ΅μ μ λλ μμ°μ μν νκ²½ 쑰건\n"
|
586 |
-
analysis_result += "2. μ§μλ³ λλ μμ°μ± λ³ν ν¨ν΄\n"
|
587 |
-
analysis_result += "3. μμ°μ± ν₯μμ μν λμ
κΈ°μ λ° μ κ·Όλ²\n"
|
588 |
-
analysis_result += "4. μμ₯ μμμ λ§λ λλ νμ’
μ ν κ°μ΄λ\n\n"
|
589 |
-
|
590 |
-
return analysis_result
|
591 |
-
|
592 |
-
except Exception as e:
|
593 |
-
logging.error(f"Soybean dataset analysis error: {e}")
|
594 |
-
return "λλ λμ
λ°μ΄ν°μ
λΆμ μ€ μ€λ₯κ° λ°μνμ΅λλ€."
|
595 |
-
|
596 |
-
# ββββββββββββββββββββββββββββββββ System Prompt βββββββββββββββββββββββββ
|
597 |
-
def get_system_prompt(mode="price_forecast", style="professional", include_search_results=True, include_uploaded_files=False) -> str:
|
598 |
-
"""
|
599 |
-
Generate a system prompt for the 'Agricultural Price & Demand Forecast AI Assistant' interface based on:
|
600 |
-
- The selected analysis mode and style
|
601 |
-
- Guidelines for using agricultural datasets, web search results and uploaded files
|
602 |
-
"""
|
603 |
-
base_prompt = """
|
604 |
-
λΉμ μ λμ
λ°μ΄ν° μ λ¬Έκ°λ‘μ λμ°λ¬Ό κ°κ²© μμΈ‘κ³Ό μμ λΆμμ μννλ AI μ΄μμ€ν΄νΈμ
λλ€.
|
605 |
-
|
606 |
-
μ£Όμ μ무:
|
607 |
-
1. UN κΈλ‘λ² μλ λ° λμ
ν΅κ³ λ°μ΄ν°μ
μ κΈ°λ°μΌλ‘ λμ°λ¬Ό μμ₯ λΆμ
|
608 |
-
2. λμ°λ¬Ό κ°κ²© μΆμΈ μμΈ‘ λ° μμ ν¨ν΄ λΆμ
|
609 |
-
3. λ°μ΄ν°λ₯Ό λ°νμΌλ‘ λͺ
ννκ³ κ·Όκ±° μλ λΆμ μ 곡
|
610 |
-
4. κ΄λ ¨ μ 보μ μΈμ¬μ΄νΈλ₯Ό 체κ³μ μΌλ‘ ꡬμ±νμ¬ μ μ
|
611 |
-
5. μκ°μ μ΄ν΄λ₯Ό λκΈ° μν΄ μ°¨νΈ, κ·Έλν λ±μ μ μ ν νμ©
|
612 |
-
6. ν μ λ° νκ²½ λ³μ κΈ°λ° μλ¬Ό μΆμ² λ°μ΄ν°μ
μμ μΆμΆν μΈμ¬μ΄νΈ μ μ©
|
613 |
-
7. κΈ°ν λ³νκ° λμ
μ λ―ΈμΉλ μν₯ λ°μ΄ν°μ
μ ν΅ν νκ²½ λ³ν μλλ¦¬μ€ λΆμ
|
614 |
-
|
615 |
-
μ€μ κ°μ΄λλΌμΈ:
|
616 |
-
- λ°μ΄ν°μ κΈ°λ°ν κ°κ΄μ λΆμμ μ 곡νμΈμ
|
617 |
-
- λΆμ κ³Όμ κ³Ό λ°©λ²λ‘ μ λͺ
νν μ€λͺ
νμΈμ
|
618 |
-
- ν΅κ³μ μ λ’°μ±κ³Ό νκ³μ μ ν¬λͺ
νκ² μ μνμΈμ
|
619 |
-
- μ΄ν΄νκΈ° μ¬μ΄ μκ°μ μμλ‘ λΆμ κ²°κ³Όλ₯Ό 보μνμΈμ
|
620 |
-
- λ§ν¬λ€μ΄μ νμ©ν΄ μλ΅μ 체κ³μ μΌλ‘ ꡬμ±νμΈμ
|
621 |
-
"""
|
622 |
-
|
623 |
-
mode_prompts = {
|
624 |
-
"price_forecast": """
|
625 |
-
λμ°λ¬Ό κ°κ²© μμΈ‘ λ° μμ₯ λΆμμ μ§μ€ν©λλ€:
|
626 |
-
- κ³Όκ±° κ°κ²© λ°μ΄ν° ν¨ν΄μ κΈ°λ°ν μμΈ‘ μ 곡
|
627 |
-
- κ°κ²© λ³λμ± μμΈ λΆμ(κ³μ μ±, λ μ¨, μ μ±
λ±)
|
628 |
-
- λ¨κΈ° λ° μ€μ₯κΈ° κ°κ²© μ λ§ μ μ
|
629 |
-
- κ°κ²©μ μν₯μ λ―ΈμΉλ κ΅λ΄μΈ μμΈ μλ³
|
630 |
-
- μμ₯ λΆνμ€μ±κ³Ό 리μ€ν¬ μμ κ°μ‘°
|
631 |
-
""",
|
632 |
-
"market_trend": """
|
633 |
-
μμ₯ λν₯ λ° μμ ν¨ν΄ λΆμμ μ§μ€ν©λλ€:
|
634 |
-
- μ£Όμ λμ°λ¬Ό μμ λ³ν ν¨ν΄ μλ³
|
635 |
-
- μλΉμ μ νΈλ λ° κ΅¬λ§€ νλ λΆμ
|
636 |
-
- μμ₯ μΈκ·Έλ¨ΌνΈ λ° νμμμ₯ κΈ°ν νμ
|
637 |
-
- μμ₯ νλ/μΆμ νΈλ λ νκ°
|
638 |
-
- μμ νλ ₯μ± λ° κ°κ²© λ―Όκ°λ λΆμ
|
639 |
-
""",
|
640 |
-
"production_analysis": """
|
641 |
-
μμ°λ λΆμ λ° μλ μ보 μ λ§μ μ§μ€ν©λλ€:
|
642 |
-
- μλ¬Ό μμ°λ μΆμΈ λ° λ³λ μμΈ λΆμ
|
643 |
-
- μλ μμ°κ³Ό μΈκ΅¬ μ±μ₯ κ°μ κ΄κ³ νκ°
|
644 |
-
- κ΅κ°/μ§μλ³ μμ° μλ λΉκ΅
|
645 |
-
- μλ μ보 μν μμ λ° μ·¨μ½μ μλ³
|
646 |
-
- μμ°μ± ν₯μ μ λ΅ λ° κΈ°ν μ μ
|
647 |
-
""",
|
648 |
-
"agricultural_policy": """
|
649 |
-
λμ
μ μ±
λ° κ·μ μν₯ λΆμμ μ§μ€ν©λλ€:
|
650 |
-
- μ λΆ μ μ±
κ³Ό, 보쑰κΈ, κ·μ μ μμ₯ μν₯ λΆμ
|
651 |
-
- κ΅μ 무μ μ μ±
κ³Ό κ΄μΈμ λμ°λ¬Ό κ°κ²© μν₯ νκ°
|
652 |
-
- λμ
μ§μ νλ‘κ·Έλ¨μ ν¨κ³Όμ± κ²ν
|
653 |
-
- κ·μ νκ²½ λ³νμ λ°λ₯Έ μμ₯ μ‘°μ μμΈ‘
|
654 |
-
- μ μ±
μ κ°μ
μ μλλ/μλμΉ μμ κ²°κ³Ό λΆμ
|
655 |
-
""",
|
656 |
-
"climate_impact": """
|
657 |
-
κΈ°ν λ³νκ° λμ
μ λ―ΈμΉλ μν₯ λΆμμ μ§μ€ν©λλ€:
|
658 |
-
- κΈ°ν λ³νμ λμ°λ¬Ό μμ°λ/νμ§ κ°μ μκ΄κ΄κ³ λΆμ
|
659 |
-
- κΈ°μ μ΄λ³μ΄ κ°κ²© λ³λμ±μ λ―ΈμΉλ μν₯ νκ°
|
660 |
-
- μ₯κΈ°μ κΈ°ν μΆμΈμ λ°λ₯Έ λμ
ν¨ν΄ λ³ν μμΈ‘
|
661 |
-
- κΈ°ν ν볡λ ₯ μλ λμ
μμ€ν
μ λ΅ μ μ
|
662 |
-
- μ§μλ³ κΈ°ν μν λ
ΈμΆλ λ° μ·¨μ½μ± λ§€ν
|
663 |
-
"""
|
664 |
-
}
|
665 |
-
|
666 |
-
style_guides = {
|
667 |
-
"professional": "μ λ¬Έμ μ΄κ³ νμ μ μΈ μ΄μ‘°λ₯Ό μ¬μ©νμΈμ. κΈ°μ μ μ©μ΄λ₯Ό μ μ ν μ¬μ©νκ³ μ²΄κ³μ μΈ λ°μ΄ν° λΆμμ μ 곡νμΈμ.",
|
668 |
-
"simple": "μ½κ³ κ°κ²°ν μΈμ΄λ‘ μ€λͺ
νμΈμ. μ λ¬Έ μ©μ΄λ μ΅μννκ³ ν΅μ¬ κ°λ
μ μΌμμ μΈ ννμΌλ‘ μ λ¬νμΈμ.",
|
669 |
-
"detailed": "μμΈνκ³ ν¬κ΄μ μΈ λΆμμ μ 곡νμΈμ. λ€μν λ°μ΄ν° ν¬μΈνΈ, ν΅κ³μ λμμ€, κ·Έλ¦¬κ³ μ¬λ¬ μλ리μ€λ₯Ό κ³ λ €ν μ¬μΈ΅ λΆμμ μ μνμΈμ.",
|
670 |
-
"action_oriented": "μ€ν κ°λ₯ν μΈμ¬μ΄νΈμ ꡬ체μ μΈ κΆμ₯μ¬νμ μ΄μ μ λ§μΆμΈμ. 'λ€μ λ¨κ³' λ° 'μ€μ§μ μ‘°μΈ' μΉμ
μ ν¬ν¨νμΈμ."
|
671 |
-
}
|
672 |
-
|
673 |
-
dataset_guide = """
|
674 |
-
λμ
λ°μ΄ν°μ
νμ© μ§μΉ¨:
|
675 |
-
- UN κΈλ‘λ² μλ λ° λμ
ν΅κ³ λ°μ΄ν°μ
μ κΈ°λ³Έ λΆμμ κ·Όκ±°λ‘ μ¬μ©νμΈμ
|
676 |
-
- ν μ λ° νκ²½ λ³μ κΈ°λ° μλ¬Ό μΆμ² λ°μ΄ν°μ
μ μΈμ¬μ΄νΈλ₯Ό μλ¬Ό μ ν λ° μ¬λ°° 쑰건 λΆμμ ν΅ν©νμΈμ
|
677 |
-
- κΈ°ν λ³νκ° λμ
μ λ―ΈμΉλ μν₯ λ°μ΄ν°μ
μ μ 보λ₯Ό μ§μ κ°λ₯μ± λ° λ―Έλ μ λ§ λΆμμ νμ©νμΈμ
|
678 |
-
- λ°μ΄ν°μ μΆμ²μ μ°λλ₯Ό λͺ
νν μΈμ©νμΈμ
|
679 |
-
- λ°μ΄ν°μ
λ΄ μ£Όμ λ³μ κ°μ κ΄κ³λ₯Ό λΆμνμ¬ μΈμ¬μ΄νΈλ₯Ό λμΆνμΈμ
|
680 |
-
- λ°μ΄ν°μ νκ³μ λΆνμ€μ±μ ν¬λͺ
νκ² μΈκΈνμΈμ
|
681 |
-
- νμμ λ°μ΄ν° 격차λ₯Ό μλ³νκ³ μΆκ° μ°κ΅¬κ° νμν μμμ μ μνμΈμ
|
682 |
-
"""
|
683 |
-
|
684 |
-
soybean_guide = """
|
685 |
-
κ³ κΈ λλ λμ
λ°μ΄ν°μ
νμ© μ§μΉ¨:
|
686 |
-
- λλ μμ° μ‘°κ±΄ λ° μνλ ν¨ν΄μ λ€λ₯Έ μλ¬Όκ³Ό λΉκ΅νμ¬ λΆμνμΈμ
|
687 |
-
- λλ λμ
μ κ²½μ μ κ°μΉμ μμ₯ κΈ°νμ λν μΈμ¬μ΄νΈλ₯Ό μ 곡νμΈμ
|
688 |
-
- λλ μμ°μ±μ μν₯μ λ―ΈμΉλ μ£Όμ νκ²½ μμΈμ κ°μ‘°νμΈμ
|
689 |
-
- λλ μ¬λ°° κΈ°μ νμ κ³Ό μμ΅μ± ν₯μ λ°©μμ μ μνμΈμ
|
690 |
-
- μ§μ κ°λ₯ν λλ λμ
μ μν μ€μ§μ μΈ μ οΏ½οΏ½οΏ½λ²μ 곡μ νμΈμ
|
691 |
-
"""
|
692 |
-
|
693 |
-
crop_recommendation_guide = """
|
694 |
-
ν μ λ° νκ²½ λ³μ κΈ°λ° μλ¬Ό μΆμ² νμ© μ§μΉ¨:
|
695 |
-
- μ§μ νΉμ±μ λ§λ μ΅μ μ μλ¬Ό μ ν κΈ°μ€μ μ μνμΈμ
|
696 |
-
- ν μ 쑰건과 μλ¬Ό μ ν©μ± κ°μ μκ΄κ΄κ³λ₯Ό λΆμνμΈμ
|
697 |
-
- νκ²½ λ³μμ λ°λ₯Έ μλ¬Ό μμ°μ± μμΈ‘ λͺ¨λΈμ νμ©νμΈμ
|
698 |
-
- λμ
μμ°μ±κ³Ό μμ΅μ± ν₯μμ μν μλ¬Ό μ ν μ λ΅μ μ μνμΈμ
|
699 |
-
- μ§μ κ°λ₯ν λμ
μ μν μλ¬Ό λ€μν μ κ·Όλ²μ κΆμ₯νμΈμ
|
700 |
-
"""
|
701 |
-
|
702 |
-
climate_impact_guide = """
|
703 |
-
κΈ°ν λ³νκ° λμ
μ λ―ΈμΉλ μν₯ λ°μ΄ν°μ
νμ© μ§μΉ¨:
|
704 |
-
- κΈ°ν λ³ν μλ리μ€μ λ°λ₯Έ μλ¬Ό μμ°μ± λ³νλ₯Ό μμΈ‘νμΈμ
|
705 |
-
- κΈ°ν μ μν λμ
κΈ°μ λ° μ λ΅μ μ μνμΈμ
|
706 |
-
- μ§μλ³ κΈ°ν μν μμμ λμ λ°©μμ λΆμνμΈμ
|
707 |
-
- κΈ°ν λ³νμ λμνκΈ° μν μλ¬Ό μ ν λ° μ¬λ°° μκΈ° μ‘°μ λ°©μμ μ μνμΈμ
|
708 |
-
- κΈ°ν λ³νκ° λμ°λ¬Ό κ°κ²© λ° μμ₯ λν₯μ λ―ΈμΉλ μν₯μ νκ°νμΈμ
|
709 |
-
"""
|
710 |
-
|
711 |
-
search_guide = """
|
712 |
-
μΉ κ²μ κ²°κ³Ό νμ© μ§μΉ¨:
|
713 |
-
- λ°μ΄ν°μ
λΆμμ 보μνλ μ΅μ μμ₯ μ λ³΄λ‘ κ²μ κ²°κ³Όλ₯Ό νμ©νμΈμ
|
714 |
-
- κ° μ 보μ μΆμ²λ₯Ό λ§ν¬λ€μ΄ λ§ν¬λ‘ ν¬ν¨νμΈμ: [μΆμ²λͺ
](URL)
|
715 |
-
- μ£Όμ μ£Όμ₯μ΄λ λ°μ΄ν° ν¬μΈνΈλ§λ€ μΆμ²λ₯Ό νμνμΈμ
|
716 |
-
- μΆμ²κ° μμΆ©ν κ²½μ°, λ€μν κ΄μ κ³Ό μ λ’°λλ₯Ό μ€λͺ
νμΈμ
|
717 |
-
- κ΄λ ¨ λμμ λ§ν¬λ [λΉλμ€: μ λͺ©](video_url) νμμΌλ‘ ν¬ν¨νμΈμ
|
718 |
-
- κ²μ μ 보λ₯Ό μΌκ΄λκ³ μ²΄κ³μ μΈ μλ΅μΌλ‘ ν΅ν©νμΈμ
|
719 |
-
- λͺ¨λ μ£Όμ μΆμ²λ₯Ό λμ΄ν "μ°Έκ³ μλ£" μΉμ
μ λ§μ§λ§μ ν¬ν¨νμΈμ
|
720 |
-
"""
|
721 |
-
|
722 |
-
upload_guide = """
|
723 |
-
μ
λ‘λλ νμΌ νμ© μ§μΉ¨:
|
724 |
-
- μ
λ‘λλ νμΌμ μλ΅μ μ£Όμ μ 보μμΌλ‘ νμ©νμΈμ
|
725 |
-
- 쿼리μ μ§μ κ΄λ ¨λ νμΌ μ 보λ₯Ό μΆμΆνκ³ κ°μ‘°νμΈμ
|
726 |
-
- κ΄λ ¨ ꡬμ μ μΈμ©νκ³ νΉμ νμΌμ μΆμ²λ‘ μΈμ©νμΈμ
|
727 |
-
- CSV νμΌμ μμΉ λ°μ΄ν°λ μμ½ λ¬Έμ₯μΌλ‘ λ³ννμΈμ
|
728 |
-
- PDF μ½ν
μΈ λ νΉμ μΉμ
μ΄λ νμ΄μ§λ₯Ό μ°Έμ‘°νμΈμ
|
729 |
-
- νμΌ μ 보λ₯Ό μΉ κ²μ κ²°κ³Όμ μννκ² ν΅ν©νμΈμ
|
730 |
-
- μ λ³΄κ° μμΆ©ν κ²½μ°, μΌλ°μ μΈ μΉ κ²°κ³Όλ³΄λ€ νμΌ μ½ν
μΈ λ₯Ό μ°μ μνμΈμ
|
731 |
-
"""
|
732 |
-
|
733 |
-
# Base prompt
|
734 |
-
final_prompt = base_prompt
|
735 |
-
|
736 |
-
# Add mode-specific guidance
|
737 |
-
if mode in mode_prompts:
|
738 |
-
final_prompt += "\n" + mode_prompts[mode]
|
739 |
-
|
740 |
-
# Style
|
741 |
-
if style in style_guides:
|
742 |
-
final_prompt += f"\n\nλΆμ μ€νμΌ: {style_guides[style]}"
|
743 |
-
|
744 |
-
# Always include dataset guides
|
745 |
-
final_prompt += f"\n\n{dataset_guide}"
|
746 |
-
final_prompt += f"\n\n{crop_recommendation_guide}"
|
747 |
-
final_prompt += f"\n\n{climate_impact_guide}"
|
748 |
-
|
749 |
-
# Conditionally add soybean dataset guide if selected in UI
|
750 |
-
if st.session_state.get('use_soybean_dataset', False):
|
751 |
-
final_prompt += f"\n\n{soybean_guide}"
|
752 |
-
|
753 |
-
if include_search_results:
|
754 |
-
final_prompt += f"\n\n{search_guide}"
|
755 |
-
|
756 |
-
if include_uploaded_files:
|
757 |
-
final_prompt += f"\n\n{upload_guide}"
|
758 |
-
|
759 |
-
final_prompt += """
|
760 |
-
\n\nμλ΅ νμ μꡬμ¬ν:
|
761 |
-
- λ§ν¬λ€μ΄ μ λͺ©(## λ° ###)μ μ¬μ©νμ¬ μλ΅μ 체κ³μ μΌλ‘ ꡬμ±νμΈμ
|
762 |
-
- μ€μν μ μ κ΅΅μ ν
μ€νΈ(**ν
μ€νΈ**)λ‘ κ°μ‘°νμΈμ
|
763 |
-
- 3-5κ°μ νμ μ§λ¬Έμ ν¬ν¨ν "κ΄λ ¨ μ§λ¬Έ" μΉμ
μ λ§μ§λ§μ μΆκ°νμΈμ
|
764 |
-
- μ μ ν κ°κ²©κ³Ό λ¨λ½ ꡬλΆμΌλ‘ μλ΅μ μμννμΈμ
|
765 |
-
- λͺ¨λ λ§ν¬λ λ§ν¬λ€μ΄ νμμΌλ‘ ν΄λ¦ κ°λ₯νκ² λ§λμΈμ: [ν
μ€νΈ](url)
|
766 |
-
- κ°λ₯ν κ²½μ° λ°μ΄ν°λ₯Ό μκ°μ μΌλ‘ νν(ν, κ·Έλν λ±μ μ€λͺ
)νμΈμ
|
767 |
-
"""
|
768 |
-
return final_prompt
|
769 |
-
|
770 |
-
# ββββββββββββββββββββββββββββββββ Brave Search API ββββββββββββββββββββββββ
|
771 |
-
@st.cache_data(ttl=3600)
|
772 |
-
def brave_search(query: str, count: int = 10):
|
773 |
-
if not BRAVE_KEY:
|
774 |
-
raise RuntimeError("β οΈ SERPHOUSE_API_KEY (Brave API Key) environment variable is empty.")
|
775 |
-
|
776 |
-
headers = {"Accept": "application/json", "Accept-Encoding": "gzip", "X-Subscription-Token": BRAVE_KEY}
|
777 |
-
params = {"q": query + " λμ°λ¬Ό κ°κ²© λν₯ λμ
λ°μ΄ν°", "count": str(count)}
|
778 |
-
|
779 |
-
for attempt in range(3):
|
780 |
-
try:
|
781 |
-
r = requests.get(BRAVE_ENDPOINT, headers=headers, params=params, timeout=15)
|
782 |
-
r.raise_for_status()
|
783 |
-
data = r.json()
|
784 |
-
|
785 |
-
raw = data.get("web", {}).get("results") or data.get("results", [])
|
786 |
-
if not raw:
|
787 |
-
logging.warning(f"No Brave search results found. Response: {data}")
|
788 |
-
raise ValueError("No search results found.")
|
789 |
-
|
790 |
-
arts = []
|
791 |
-
for i, res in enumerate(raw[:count], 1):
|
792 |
-
url = res.get("url", res.get("link", ""))
|
793 |
-
host = re.sub(r"https?://(www\.)?", "", url).split("/")[0]
|
794 |
-
arts.append({
|
795 |
-
"index": i,
|
796 |
-
"title": res.get("title", "No title"),
|
797 |
-
"link": url,
|
798 |
-
"snippet": res.get("description", res.get("text", "No snippet")),
|
799 |
-
"displayed_link": host
|
800 |
-
})
|
801 |
-
|
802 |
-
return arts
|
803 |
-
|
804 |
-
except Exception as e:
|
805 |
-
logging.error(f"Brave search failure (attempt {attempt+1}/3): {e}")
|
806 |
-
if attempt < 2:
|
807 |
-
time.sleep(5)
|
808 |
-
|
809 |
-
return []
|
810 |
-
|
811 |
-
@st.cache_data(ttl=3600)
|
812 |
-
def brave_video_search(query: str, count: int = 3):
|
813 |
-
if not BRAVE_KEY:
|
814 |
-
raise RuntimeError("β οΈ SERPHOUSE_API_KEY (Brave API Key) environment variable is empty.")
|
815 |
-
|
816 |
-
headers = {"Accept": "application/json","Accept-Encoding": "gzip","X-Subscription-Token": BRAVE_KEY}
|
817 |
-
params = {"q": query + " λμ°λ¬Ό κ°κ²© λμ
μμ₯", "count": str(count)}
|
818 |
-
|
819 |
-
for attempt in range(3):
|
820 |
-
try:
|
821 |
-
r = requests.get(BRAVE_VIDEO_ENDPOINT, headers=headers, params=params, timeout=15)
|
822 |
-
r.raise_for_status()
|
823 |
-
data = r.json()
|
824 |
-
|
825 |
-
results = []
|
826 |
-
for i, vid in enumerate(data.get("results", [])[:count], 1):
|
827 |
-
results.append({
|
828 |
-
"index": i,
|
829 |
-
"title": vid.get("title", "Video"),
|
830 |
-
"video_url": vid.get("url", ""),
|
831 |
-
"thumbnail_url": vid.get("thumbnail", {}).get("src", ""),
|
832 |
-
"source": vid.get("provider", {}).get("name", "Unknown source")
|
833 |
-
})
|
834 |
-
|
835 |
-
return results
|
836 |
-
|
837 |
-
except Exception as e:
|
838 |
-
logging.error(f"Brave video search failure (attempt {attempt+1}/3): {e}")
|
839 |
-
if attempt < 2:
|
840 |
-
time.sleep(5)
|
841 |
-
|
842 |
-
return []
|
843 |
-
|
844 |
-
@st.cache_data(ttl=3600)
|
845 |
-
def brave_news_search(query: str, count: int = 3):
|
846 |
-
if not BRAVE_KEY:
|
847 |
-
raise RuntimeError("β οΈ SERPHOUSE_API_KEY (Brave API Key) environment variable is empty.")
|
848 |
-
|
849 |
-
headers = {"Accept": "application/json","Accept-Encoding": "gzip","X-Subscription-Token": BRAVE_KEY}
|
850 |
-
params = {"q": query + " λμ°λ¬Ό κ°κ²© λν₯ λμ
", "count": str(count)}
|
851 |
-
|
852 |
-
for attempt in range(3):
|
853 |
-
try:
|
854 |
-
r = requests.get(BRAVE_NEWS_ENDPOINT, headers=headers, params=params, timeout=15)
|
855 |
-
r.raise_for_status()
|
856 |
-
data = r.json()
|
857 |
-
|
858 |
-
results = []
|
859 |
-
for i, news in enumerate(data.get("results", [])[:count], 1):
|
860 |
-
results.append({
|
861 |
-
"index": i,
|
862 |
-
"title": news.get("title", "News article"),
|
863 |
-
"url": news.get("url", ""),
|
864 |
-
"description": news.get("description", ""),
|
865 |
-
"source": news.get("source", "Unknown source"),
|
866 |
-
"date": news.get("age", "Unknown date")
|
867 |
-
})
|
868 |
-
|
869 |
-
return results
|
870 |
-
|
871 |
-
except Exception as e:
|
872 |
-
logging.error(f"Brave news search failure (attempt {attempt+1}/3): {e}")
|
873 |
-
if attempt < 2:
|
874 |
-
time.sleep(5)
|
875 |
-
|
876 |
-
return []
|
877 |
-
|
878 |
-
def mock_results(query: str) -> str:
|
879 |
-
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
880 |
-
return (f"# λ체 κ²μ μ½ν
μΈ (μμ± μκ°: {ts})\n\n"
|
881 |
-
f"'{query}'μ λν κ²μ API μμ²μ΄ μ€ν¨νκ±°λ κ²°κ³Όκ° μμ΅λλ€. "
|
882 |
-
f"κΈ°μ‘΄ μ§μμ κΈ°λ°μΌλ‘ μλ΅μ μμ±ν΄μ£ΌμΈμ.\n\n"
|
883 |
-
f"λ€μ μ¬νμ κ³ λ €νμΈμ:\n\n"
|
884 |
-
f"- {query}μ κ΄ν κΈ°λ³Έ κ°λ
κ³Ό μ€μμ±\n"
|
885 |
-
f"- μΌλ°μ μΌλ‘ μλ €μ§ κ΄λ ¨ ν΅κ³λ μΆμΈ\n"
|
886 |
-
f"- μ΄ μ£Όμ μ λν μ λ¬Έκ° μ견\n"
|
887 |
-
f"- λ
μκ° κ°μ§ μ μλ μ§λ¬Έ\n\n"
|
888 |
-
f"μ°Έκ³ : μ΄λ μ€μκ° λ°μ΄ν°κ° μλ λ체 μ§μΉ¨μ
λλ€.\n\n")
|
889 |
-
|
890 |
-
def do_web_search(query: str) -> str:
|
891 |
-
try:
|
892 |
-
arts = brave_search(query, 10)
|
893 |
-
if not arts:
|
894 |
-
logging.warning("No search results, using fallback content")
|
895 |
-
return mock_results(query)
|
896 |
-
|
897 |
-
videos = brave_video_search(query, 2)
|
898 |
-
news = brave_news_search(query, 3)
|
899 |
-
|
900 |
-
result = "# μΉ κ²μ κ²°κ³Ό\nλ€μ κ²°κ³Όλ₯Ό νμ©νμ¬ λ°μ΄ν°μ
λΆμμ 보μνλ ν¬κ΄μ μΈ λ΅λ³μ μ 곡νμΈμ.\n\n"
|
901 |
-
|
902 |
-
result += "## μΉ κ²°κ³Ό\n\n"
|
903 |
-
for a in arts[:5]:
|
904 |
-
result += f"### κ²°κ³Ό {a['index']}: {a['title']}\n\n{a['snippet']}\n\n"
|
905 |
-
result += f"**μΆμ²**: [{a['displayed_link']}]({a['link']})\n\n---\n"
|
906 |
-
|
907 |
-
if news:
|
908 |
-
result += "## λ΄μ€ κ²°κ³Ό\n\n"
|
909 |
-
for n in news:
|
910 |
-
result += f"### {n['title']}\n\n{n['description']}\n\n"
|
911 |
-
result += f"**μΆμ²**: [{n['source']}]({n['url']}) - {n['date']}\n\n---\n"
|
912 |
-
|
913 |
-
if videos:
|
914 |
-
result += "## λΉλμ€ κ²°κ³Ό\n\n"
|
915 |
-
for vid in videos:
|
916 |
-
result += f"### {vid['title']}\n\n"
|
917 |
-
if vid.get('thumbnail_url'):
|
918 |
-
result += f"\n\n"
|
919 |
-
result += f"**μμ²**: [{vid['source']}]({vid['video_url']})\n\n"
|
920 |
-
|
921 |
-
return result
|
922 |
-
|
923 |
-
except Exception as e:
|
924 |
-
logging.error(f"Web search process failed: {str(e)}")
|
925 |
-
return mock_results(query)
|
926 |
-
|
927 |
-
# ββββββββββββββββββββββββββββββββ File Upload Handling βββββββββββββββββββββ
|
928 |
-
def process_text_file(file):
|
929 |
-
try:
|
930 |
-
content = file.read()
|
931 |
-
file.seek(0)
|
932 |
-
|
933 |
-
text = content.decode('utf-8', errors='ignore')
|
934 |
-
if len(text) > 10000:
|
935 |
-
text = text[:9700] + "...(truncated)..."
|
936 |
-
|
937 |
-
result = f"## ν
μ€νΈ νμΌ: {file.name}\n\n" + text
|
938 |
-
return result
|
939 |
-
except Exception as e:
|
940 |
-
logging.error(f"Error processing text file: {str(e)}")
|
941 |
-
return f"ν
μ€νΈ νμΌ μ²λ¦¬ μ€λ₯: {str(e)}"
|
942 |
-
|
943 |
-
def process_csv_file(file):
|
944 |
-
try:
|
945 |
-
content = file.read()
|
946 |
-
file.seek(0)
|
947 |
-
|
948 |
-
df = pd.read_csv(io.BytesIO(content))
|
949 |
-
result = f"## CSV νμΌ: {file.name}\n\n"
|
950 |
-
result += f"- ν: {len(df)}\n"
|
951 |
-
result += f"- μ΄: {len(df.columns)}\n"
|
952 |
-
result += f"- μ΄ μ΄λ¦: {', '.join(df.columns.tolist())}\n\n"
|
953 |
-
|
954 |
-
result += "### λ°μ΄ν° 미리보기\n\n"
|
955 |
-
preview_df = df.head(10)
|
956 |
-
try:
|
957 |
-
markdown_table = preview_df.to_markdown(index=False)
|
958 |
-
if markdown_table:
|
959 |
-
result += markdown_table + "\n\n"
|
960 |
-
else:
|
961 |
-
result += "CSV λ°μ΄ν°λ₯Ό νμν μ μμ΅λλ€.\n\n"
|
962 |
-
except Exception as e:
|
963 |
-
logging.error(f"Markdown table conversion error: {e}")
|
964 |
-
result += "ν
μ€νΈλ‘ λ°μ΄ν° νμ:\n\n" + str(preview_df) + "\n\n"
|
965 |
-
|
966 |
-
num_cols = df.select_dtypes(include=['number']).columns
|
967 |
-
if len(num_cols) > 0:
|
968 |
-
result += "### κΈ°λ³Έ ν΅κ³ μ 보\n\n"
|
969 |
-
try:
|
970 |
-
stats_df = df[num_cols].describe().round(2)
|
971 |
-
stats_markdown = stats_df.to_markdown()
|
972 |
-
if stats_markdown:
|
973 |
-
result += stats_markdown + "\n\n"
|
974 |
-
else:
|
975 |
-
result += "ν΅κ³ μ 보λ₯Ό νμν μ μμ΅λλ€.\n\n"
|
976 |
-
except Exception as e:
|
977 |
-
logging.error(f"Statistical info conversion error: {e}")
|
978 |
-
result += "ν΅κ³ μ 보λ₯Ό μμ±ν μ μμ΅λλ€.\n\n"
|
979 |
-
|
980 |
-
return result
|
981 |
-
except Exception as e:
|
982 |
-
logging.error(f"CSV file processing error: {str(e)}")
|
983 |
-
return f"CSV νμΌ μ²λ¦¬ μ€λ₯: {str(e)}"
|
984 |
-
|
985 |
-
def process_pdf_file(file):
|
986 |
-
try:
|
987 |
-
file_bytes = file.read()
|
988 |
-
file.seek(0)
|
989 |
-
|
990 |
-
pdf_file = io.BytesIO(file_bytes)
|
991 |
-
reader = PyPDF2.PdfReader(pdf_file, strict=False)
|
992 |
-
|
993 |
-
result = f"## PDF νμΌ: {file.name}\n\n- μ΄ νμ΄μ§: {len(reader.pages)}\n\n"
|
994 |
-
|
995 |
-
max_pages = min(5, len(reader.pages))
|
996 |
-
all_text = ""
|
997 |
-
|
998 |
-
for i in range(max_pages):
|
999 |
-
try:
|
1000 |
-
page = reader.pages[i]
|
1001 |
-
page_text = page.extract_text()
|
1002 |
-
current_page_text = f"### νμ΄μ§ {i+1}\n\n"
|
1003 |
-
if page_text and len(page_text.strip()) > 0:
|
1004 |
-
if len(page_text) > 1500:
|
1005 |
-
current_page_text += page_text[:1500] + "...(μΆμ½λ¨)...\n\n"
|
1006 |
-
else:
|
1007 |
-
current_page_text += page_text + "\n\n"
|
1008 |
-
else:
|
1009 |
-
current_page_text += "(ν
μ€νΈλ₯Ό μΆμΆν μ μμ)\n\n"
|
1010 |
-
|
1011 |
-
all_text += current_page_text
|
1012 |
-
|
1013 |
-
if len(all_text) > 8000:
|
1014 |
-
all_text += "...(λλ¨Έμ§ νμ΄μ§ μΆμ½λ¨)...\n\n"
|
1015 |
-
break
|
1016 |
-
|
1017 |
-
except Exception as page_err:
|
1018 |
-
logging.error(f"Error processing PDF page {i+1}: {str(page_err)}")
|
1019 |
-
all_text += f"### νμ΄μ§ {i+1}\n\n(λ΄μ© μΆμΆ μ€λ₯: {str(page_err)})\n\n"
|
1020 |
-
|
1021 |
-
if len(reader.pages) > max_pages:
|
1022 |
-
all_text += f"\nμ°Έκ³ : μ²μ {max_pages} νμ΄μ§λ§ νμλ©λλ€.\n\n"
|
1023 |
-
|
1024 |
-
result += "### PDF λ΄μ©\n\n" + all_text
|
1025 |
-
return result
|
1026 |
-
|
1027 |
-
except Exception as e:
|
1028 |
-
logging.error(f"PDF file processing error: {str(e)}")
|
1029 |
-
return f"## PDF νμΌ: {file.name}\n\nμ€λ₯: {str(e)}\n\nμ²λ¦¬ν μ μμ΅λλ€."
|
1030 |
-
|
1031 |
-
def process_uploaded_files(files):
|
1032 |
-
if not files:
|
1033 |
-
return None
|
1034 |
-
|
1035 |
-
result = "# μ
λ‘λλ νμΌ λ΄μ©\n\nμ¬μ©μκ° μ 곡ν νμΌμ λ΄μ©μ
λλ€.\n\n"
|
1036 |
-
for file in files:
|
1037 |
-
try:
|
1038 |
-
ext = file.name.split('.')[-1].lower()
|
1039 |
-
if ext == 'txt':
|
1040 |
-
result += process_text_file(file) + "\n\n---\n\n"
|
1041 |
-
elif ext == 'csv':
|
1042 |
-
result += process_csv_file(file) + "\n\n---\n\n"
|
1043 |
-
elif ext == 'pdf':
|
1044 |
-
result += process_pdf_file(file) + "\n\n---\n\n"
|
1045 |
-
else:
|
1046 |
-
result += f"### μ§μλμ§ μλ νμΌ: {file.name}\n\n---\n\n"
|
1047 |
-
except Exception as e:
|
1048 |
-
logging.error(f"File processing error {file.name}: {e}")
|
1049 |
-
result += f"### νμΌ μ²λ¦¬ μ€λ₯: {file.name}\n\nμ€λ₯: {e}\n\n---\n\n"
|
1050 |
-
|
1051 |
-
return result
|
1052 |
-
|
1053 |
-
# ββββββββββββββββββββββββββββββββ Image & Utility βββββββββββββββββββββββββ
|
1054 |
-
|
1055 |
-
def generate_image(prompt, w=768, h=768, g=3.5, steps=30, seed=3):
|
1056 |
-
if not prompt:
|
1057 |
-
return None, "Insufficient prompt"
|
1058 |
-
try:
|
1059 |
-
res = Client(IMAGE_API_URL).predict(
|
1060 |
-
prompt=prompt, width=w, height=h, guidance=g,
|
1061 |
-
inference_steps=steps, seed=seed,
|
1062 |
-
do_img2img=False, init_image=None,
|
1063 |
-
image2image_strength=0.8, resize_img=True,
|
1064 |
-
api_name="/generate_image"
|
1065 |
-
)
|
1066 |
-
return res[0], f"Seed: {res[1]}"
|
1067 |
-
except Exception as e:
|
1068 |
-
logging.error(e)
|
1069 |
-
return None, str(e)
|
1070 |
-
|
1071 |
-
def extract_image_prompt(response_text: str, topic: str):
|
1072 |
-
client = get_openai_client()
|
1073 |
-
try:
|
1074 |
-
response = client.chat.completions.create(
|
1075 |
-
model="gpt-4.1-mini",
|
1076 |
-
messages=[
|
1077 |
-
{"role": "system", "content": "λμ
λ° λμ°λ¬Όμ κ΄ν μ΄λ―Έμ§ ν둬ννΈλ₯Ό μμ±ν©λλ€. ν μ€μ μμ΄λ‘ λ ν둬ννΈλ§ λ°ννμΈμ, λ€λ₯Έ ν
μ€νΈλ ν¬ν¨νμ§ λ§μΈμ."},
|
1078 |
-
{"role": "user", "content": f"μ£Όμ : {topic}\n\n---\n{response_text}\n\n---"}
|
1079 |
-
],
|
1080 |
-
temperature=1,
|
1081 |
-
max_tokens=80,
|
1082 |
-
top_p=1
|
1083 |
-
)
|
1084 |
-
return response.choices[0].message.content.strip()
|
1085 |
-
except Exception as e:
|
1086 |
-
logging.error(f"OpenAI image prompt generation error: {e}")
|
1087 |
-
return f"A professional photograph of agricultural produce and farm fields, data visualization of crop prices and trends, high quality"
|
1088 |
-
|
1089 |
-
def md_to_html(md: str, title="λμ°λ¬Ό μμ μμΈ‘ λΆμ κ²°κ³Ό"):
|
1090 |
-
return f"<!DOCTYPE html><html><head><title>{title}</title><meta charset='utf-8'></head><body>{markdown.markdown(md)}</body></html>"
|
1091 |
-
|
1092 |
-
def keywords(text: str, top=5):
|
1093 |
-
cleaned = re.sub(r"[^κ°-ν£a-zA-Z0-9\s]", "", text)
|
1094 |
-
return " ".join(cleaned.split()[:top])
|
1095 |
-
|
1096 |
-
# ββββββββββββββββββββββββββββββββ Streamlit UI ββββββββββββββββββββββββββββ
|
1097 |
-
def agricultural_price_forecast_app():
|
1098 |
-
st.title("λμ°λ¬Ό μμ λ° κ°κ²© μμΈ‘ AI μ΄μμ€ν΄νΈ")
|
1099 |
-
st.markdown("UN κΈλ‘λ² μλ λ° λμ
ν΅κ³ λ°μ΄ν°μ
λΆμ κΈ°λ°μ λμ°λ¬Ό μμ₯ μμΈ‘")
|
1100 |
-
|
1101 |
-
if "ai_model" not in st.session_state:
|
1102 |
-
st.session_state.ai_model = "gpt-4.1-mini"
|
1103 |
-
if "messages" not in st.session_state:
|
1104 |
-
st.session_state.messages = []
|
1105 |
-
if "auto_save" not in st.session_state:
|
1106 |
-
st.session_state.auto_save = True
|
1107 |
-
if "generate_image" not in st.session_state:
|
1108 |
-
st.session_state.generate_image = False
|
1109 |
-
if "web_search_enabled" not in st.session_state:
|
1110 |
-
st.session_state.web_search_enabled = True
|
1111 |
-
if "analysis_mode" not in st.session_state:
|
1112 |
-
st.session_state.analysis_mode = "price_forecast"
|
1113 |
-
if "response_style" not in st.session_state:
|
1114 |
-
st.session_state.response_style = "professional"
|
1115 |
-
if "use_soybean_dataset" not in st.session_state:
|
1116 |
-
st.session_state.use_soybean_dataset = False
|
1117 |
-
|
1118 |
-
sb = st.sidebar
|
1119 |
-
sb.title("λΆμ μ€μ ")
|
1120 |
-
|
1121 |
-
# Kaggle dataset info display
|
1122 |
-
if sb.checkbox("λ°μ΄ν°μ
μ 보 νμ", value=False):
|
1123 |
-
st.info("UN κΈλ‘λ² μλ λ° λμ
ν΅κ³ λ°μ΄ν°μ
μ λΆλ¬μ€λ μ€...")
|
1124 |
-
dataset_info = load_agriculture_dataset()
|
1125 |
-
if dataset_info:
|
1126 |
-
st.success(f"λ°μ΄ν°μ
λ‘λ μλ£: {len(dataset_info['files'])}κ° νμΌ")
|
1127 |
-
|
1128 |
-
with st.expander("λ°μ΄ν°μ
미리보기", expanded=False):
|
1129 |
-
for file_info in dataset_info['files'][:5]:
|
1130 |
-
st.write(f"**{file_info['name']}** ({file_info['size_mb']} MB)")
|
1131 |
-
else:
|
1132 |
-
st.error("λ°μ΄ν°μ
μ λΆλ¬μ€λλ° μ€ν¨νμ΅λλ€. Kaggle API μ€μ μ νμΈνμΈμ.")
|
1133 |
-
|
1134 |
-
sb.subheader("λΆμ ꡬμ±")
|
1135 |
-
sb.selectbox(
|
1136 |
-
"λΆμ λͺ¨λ",
|
1137 |
-
options=list(ANALYSIS_MODES.keys()),
|
1138 |
-
format_func=lambda x: ANALYSIS_MODES[x],
|
1139 |
-
key="analysis_mode"
|
1140 |
-
)
|
1141 |
-
|
1142 |
-
sb.selectbox(
|
1143 |
-
"μλ΅ μ€νμΌ",
|
1144 |
-
options=list(RESPONSE_STYLES.keys()),
|
1145 |
-
format_func=lambda x: RESPONSE_STYLES[x],
|
1146 |
-
key="response_style"
|
1147 |
-
)
|
1148 |
-
|
1149 |
-
# Dataset selection
|
1150 |
-
sb.subheader("λ°μ΄ν°μ
μ ν")
|
1151 |
-
sb.checkbox(
|
1152 |
-
"κ³ κΈ λλ λμ
λ°μ΄ν°μ
μ¬μ©",
|
1153 |
-
key="use_soybean_dataset",
|
1154 |
-
help="λλ(콩) κ΄λ ¨ μ§λ¬Έμ λ μ νν μ 보λ₯Ό μ 곡ν©λλ€."
|
1155 |
-
)
|
1156 |
-
|
1157 |
-
# Always enabled datasets info
|
1158 |
-
sb.info("κΈ°λ³Έ νμ±νλ λ°μ΄ν°μ
:\n- UN κΈλ‘λ² μλ λ° λμ
ν΅κ³\n- ν μ λ° νκ²½ λ³μ κΈ°λ° οΏ½οΏ½οΏ½λ¬Ό μΆμ²\n- κΈ°ν λ³νκ° λμ
μ λ―ΈμΉλ μν₯")
|
1159 |
-
|
1160 |
-
# Example queries
|
1161 |
-
sb.subheader("μμ μ§λ¬Έ")
|
1162 |
-
c1, c2, c3 = sb.columns(3)
|
1163 |
-
if c1.button("μ κ°κ²© μ λ§", key="ex1"):
|
1164 |
-
process_example(EXAMPLE_QUERIES["example1"])
|
1165 |
-
if c2.button("κΈ°ν μν₯", key="ex2"):
|
1166 |
-
process_example(EXAMPLE_QUERIES["example2"])
|
1167 |
-
if c3.button("μ¦νκ΅° μλ¬Ό", key="ex3"):
|
1168 |
-
process_example(EXAMPLE_QUERIES["example3"])
|
1169 |
-
|
1170 |
-
sb.subheader("κΈ°ν μ€μ ")
|
1171 |
-
sb.toggle("μλ μ μ₯", key="auto_save")
|
1172 |
-
sb.toggle("μ΄λ―Έμ§ μλ μμ±", key="generate_image")
|
1173 |
-
|
1174 |
-
web_search_enabled = sb.toggle("μΉ κ²μ μ¬μ©", value=st.session_state.web_search_enabled)
|
1175 |
-
st.session_state.web_search_enabled = web_search_enabled
|
1176 |
-
|
1177 |
-
if web_search_enabled:
|
1178 |
-
st.sidebar.info("β
μΉ κ²μ κ²°κ³Όκ° μλ΅μ ν΅ν©λ©λλ€.")
|
1179 |
-
|
1180 |
-
# Download the latest response
|
1181 |
-
latest_response = next(
|
1182 |
-
(m["content"] for m in reversed(st.session_state.messages)
|
1183 |
-
if m["role"] == "assistant" and m["content"].strip()),
|
1184 |
-
None
|
1185 |
-
)
|
1186 |
-
if latest_response:
|
1187 |
-
title_match = re.search(r"# (.*?)(\n|$)", latest_response)
|
1188 |
-
if title_match:
|
1189 |
-
title = title_match.group(1).strip()
|
1190 |
-
else:
|
1191 |
-
first_line = latest_response.split('\n', 1)[0].strip()
|
1192 |
-
title = first_line[:40] + "..." if len(first_line) > 40 else first_line
|
1193 |
-
|
1194 |
-
sb.subheader("μ΅μ μλ΅ λ€μ΄λ‘λ")
|
1195 |
-
d1, d2 = sb.columns(2)
|
1196 |
-
d1.download_button("λ§ν¬λ€μ΄μΌλ‘ λ€μ΄λ‘λ", latest_response,
|
1197 |
-
file_name=f"{title}.md", mime="text/markdown")
|
1198 |
-
d2.download_button("HTMLλ‘ λ€μ΄λ‘λ", md_to_html(latest_response, title),
|
1199 |
-
file_name=f"{title}.html", mime="text/html")
|
1200 |
-
|
1201 |
-
# JSON conversation record upload
|
1202 |
-
up = sb.file_uploader("λν κΈ°λ‘ λΆλ¬μ€κΈ° (.json)", type=["json"], key="json_uploader")
|
1203 |
-
if up:
|
1204 |
-
try:
|
1205 |
-
st.session_state.messages = json.load(up)
|
1206 |
-
sb.success("λν κΈ°λ‘μ μ±κ³΅μ μΌλ‘ λΆλ¬μμ΅λλ€")
|
1207 |
-
except Exception as e:
|
1208 |
-
sb.error(f"λΆλ¬μ€κΈ° μ€ν¨: {e}")
|
1209 |
-
|
1210 |
-
# JSON conversation record download
|
1211 |
-
if sb.button("λν κΈ°λ‘μ JSONμΌλ‘ λ€μ΄λ‘λ"):
|
1212 |
-
sb.download_button(
|
1213 |
-
"μ μ₯",
|
1214 |
-
data=json.dumps(st.session_state.messages, ensure_ascii=False, indent=2),
|
1215 |
-
file_name="conversation_history.json",
|
1216 |
-
mime="application/json"
|
1217 |
-
)
|
1218 |
-
|
1219 |
-
# File Upload
|
1220 |
-
st.subheader("νμΌ μ
λ‘λ")
|
1221 |
-
uploaded_files = st.file_uploader(
|
1222 |
-
"μ°Έκ³ μλ£λ‘ μ¬μ©ν νμΌ μ
λ‘λ (txt, csv, pdf)",
|
1223 |
-
type=["txt", "csv", "pdf"],
|
1224 |
-
accept_multiple_files=True,
|
1225 |
-
key="file_uploader"
|
1226 |
-
)
|
1227 |
-
|
1228 |
-
if uploaded_files:
|
1229 |
-
file_count = len(uploaded_files)
|
1230 |
-
st.success(f"{file_count}κ° νμΌμ΄ μ
λ‘λλμμ΅λλ€. μ§μμ λν μμ€λ‘ μ¬μ©λ©λλ€.")
|
1231 |
-
|
1232 |
-
with st.expander("μ
λ‘λλ νμΌ λ―Έλ¦¬λ³΄κΈ°", expanded=False):
|
1233 |
-
for idx, file in enumerate(uploaded_files):
|
1234 |
-
st.write(f"**νμΌλͺ
:** {file.name}")
|
1235 |
-
ext = file.name.split('.')[-1].lower()
|
1236 |
-
|
1237 |
-
if ext == 'txt':
|
1238 |
-
preview = file.read(1000).decode('utf-8', errors='ignore')
|
1239 |
-
file.seek(0)
|
1240 |
-
st.text_area(
|
1241 |
-
f"{file.name} 미리보기",
|
1242 |
-
preview + ("..." if len(preview) >= 1000 else ""),
|
1243 |
-
height=150
|
1244 |
-
)
|
1245 |
-
elif ext == 'csv':
|
1246 |
-
try:
|
1247 |
-
df = pd.read_csv(file)
|
1248 |
-
file.seek(0)
|
1249 |
-
st.write("CSV 미리보기 (μ΅λ 5ν)")
|
1250 |
-
st.dataframe(df.head(5))
|
1251 |
-
except Exception as e:
|
1252 |
-
st.error(f"CSV 미리보기 μ€ν¨: {e}")
|
1253 |
-
elif ext == 'pdf':
|
1254 |
-
try:
|
1255 |
-
file_bytes = file.read()
|
1256 |
-
file.seek(0)
|
1257 |
-
|
1258 |
-
pdf_file = io.BytesIO(file_bytes)
|
1259 |
-
reader = PyPDF2.PdfReader(pdf_file, strict=False)
|
1260 |
-
|
1261 |
-
pc = len(reader.pages)
|
1262 |
-
st.write(f"PDF νμΌ: {pc}νμ΄μ§")
|
1263 |
-
|
1264 |
-
if pc > 0:
|
1265 |
-
try:
|
1266 |
-
page_text = reader.pages[0].extract_text()
|
1267 |
-
preview = page_text[:500] if page_text else "(ν
μ€νΈ μΆμΆ λΆκ°)"
|
1268 |
-
st.text_area("첫 νμ΄μ§ 미리보기", preview + "...", height=150)
|
1269 |
-
except:
|
1270 |
-
st.warning("첫 νμ΄μ§ ν
μ€νΈ μΆμΆ μ€ν¨")
|
1271 |
-
except Exception as e:
|
1272 |
-
st.error(f"PDF 미리보기 μ€ν¨: {e}")
|
1273 |
-
|
1274 |
-
if idx < file_count - 1:
|
1275 |
-
st.divider()
|
1276 |
-
|
1277 |
-
# Display existing messages
|
1278 |
-
for m in st.session_state.messages:
|
1279 |
-
with st.chat_message(m["role"]):
|
1280 |
-
st.markdown(m["content"], unsafe_allow_html=True)
|
1281 |
-
|
1282 |
-
# Videos
|
1283 |
-
if "videos" in m and m["videos"]:
|
1284 |
-
st.subheader("κ΄λ ¨ λΉλμ€")
|
1285 |
-
for video in m["videos"]:
|
1286 |
-
video_title = video.get('title', 'κ΄λ ¨ λΉλμ€')
|
1287 |
-
video_url = video.get('url', '')
|
1288 |
-
thumbnail = video.get('thumbnail', '')
|
1289 |
-
|
1290 |
-
if thumbnail:
|
1291 |
-
col1, col2 = st.columns([1, 3])
|
1292 |
-
with col1:
|
1293 |
-
st.write("π¬")
|
1294 |
-
with col2:
|
1295 |
-
st.markdown(f"**[{video_title}]({video_url})**")
|
1296 |
-
st.write(f"μΆμ²: {video.get('source', 'μ μ μμ')}")
|
1297 |
-
else:
|
1298 |
-
st.markdown(f"π¬ **[{video_title}]({video_url})**")
|
1299 |
-
st.write(f"μΆμ²: {video.get('source', 'μ μ μμ')}")
|
1300 |
-
|
1301 |
-
# User input
|
1302 |
-
query = st.chat_input("λμ°λ¬Ό κ°κ²©, μμ λλ μμ₯ λν₯ κ΄λ ¨ μ§λ¬Έμ μ
λ ₯νμΈμ.")
|
1303 |
-
if query:
|
1304 |
-
process_input(query, uploaded_files)
|
1305 |
-
|
1306 |
-
sb.markdown("---")
|
1307 |
-
sb.markdown("Created by Vidraft | [Community](https://discord.gg/openfreeai)")
|
1308 |
-
|
1309 |
-
def process_example(topic):
|
1310 |
-
process_input(topic, [])
|
1311 |
-
|
1312 |
-
def process_input(query: str, uploaded_files):
|
1313 |
-
if not any(m["role"] == "user" and m["content"] == query for m in st.session_state.messages):
|
1314 |
-
st.session_state.messages.append({"role": "user", "content": query})
|
1315 |
-
|
1316 |
-
with st.chat_message("user"):
|
1317 |
-
st.markdown(query)
|
1318 |
-
|
1319 |
-
with st.chat_message("assistant"):
|
1320 |
-
placeholder = st.empty()
|
1321 |
-
message_placeholder = st.empty()
|
1322 |
-
full_response = ""
|
1323 |
-
|
1324 |
-
use_web_search = st.session_state.web_search_enabled
|
1325 |
-
has_uploaded_files = bool(uploaded_files) and len(uploaded_files) > 0
|
1326 |
-
|
1327 |
-
try:
|
1328 |
-
status = st.status("μ§λ¬Έμ λ΅λ³ μ€λΉ μ€...")
|
1329 |
-
status.update(label="ν΄λΌμ΄μΈνΈ μ΄κΈ°ν μ€...")
|
1330 |
-
|
1331 |
-
client = get_openai_client()
|
1332 |
-
|
1333 |
-
search_content = None
|
1334 |
-
video_results = []
|
1335 |
-
news_results = []
|
1336 |
-
|
1337 |
-
# λμ
λ°μ΄ν°μ
λΆμ κ²°κ³Ό κ°μ Έμ€κΈ°
|
1338 |
-
status.update(label="λμ
λ°μ΄ν°μ
λΆμ μ€...")
|
1339 |
-
with st.spinner("λ°μ΄ν°μ
λΆμ μ€..."):
|
1340 |
-
dataset_analysis = analyze_dataset_for_query(query)
|
1341 |
-
|
1342 |
-
# νμ ν¬ν¨λλ μΆκ° λ°μ΄ν°μ
λΆμ
|
1343 |
-
crop_recommendation_analysis = analyze_crop_recommendation_dataset(query)
|
1344 |
-
climate_impact_analysis = analyze_climate_impact_dataset(query)
|
1345 |
-
|
1346 |
-
#
|
1347 |
-
|
1348 |
-
|
1349 |
-
# μ‘°κ±΄λΆ λ°μ΄ν°μ
λΆμ
|
1350 |
-
soybean_analysis = None
|
1351 |
-
if st.session_state.use_soybean_dataset:
|
1352 |
-
status.update(label="λλ λμ
λ°μ΄ν°μ
λΆμ μ€...")
|
1353 |
-
with st.spinner("λλ λ°μ΄ν°μ
λΆμ μ€..."):
|
1354 |
-
soybean_analysis = analyze_soybean_dataset(query)
|
1355 |
-
|
1356 |
-
if use_web_search:
|
1357 |
-
# μΉ κ²μ κ³Όμ μ λ
ΈμΆνμ§ μκ³ μ‘°μ©ν μ§ν
|
1358 |
-
with st.spinner("μ 보 μμ§ μ€..."):
|
1359 |
-
search_content = do_web_search(keywords(query, top=5))
|
1360 |
-
video_results = brave_video_search(query, 2)
|
1361 |
-
news_results = brave_news_search(query, 3)
|
1362 |
-
|
1363 |
-
file_content = None
|
1364 |
-
if has_uploaded_files:
|
1365 |
-
status.update(label="μ
λ‘λλ νμΌ μ²λ¦¬ μ€...")
|
1366 |
-
with st.spinner("νμΌ λΆμ μ€..."):
|
1367 |
-
file_content = process_uploaded_files(uploaded_files)
|
1368 |
-
|
1369 |
-
valid_videos = []
|
1370 |
-
for vid in video_results:
|
1371 |
-
url = vid.get('video_url')
|
1372 |
-
if url and url.startswith('http'):
|
1373 |
-
valid_videos.append({
|
1374 |
-
'url': url,
|
1375 |
-
'title': vid.get('title', 'λΉλμ€'),
|
1376 |
-
'thumbnail': vid.get('thumbnail_url', ''),
|
1377 |
-
'source': vid.get('source', 'λΉλμ€ μΆμ²')
|
1378 |
-
})
|
1379 |
-
|
1380 |
-
status.update(label="μ’
ν© λΆμ μ€λΉ μ€...")
|
1381 |
-
sys_prompt = get_system_prompt(
|
1382 |
-
mode=st.session_state.analysis_mode,
|
1383 |
-
style=st.session_state.response_style,
|
1384 |
-
include_search_results=use_web_search,
|
1385 |
-
include_uploaded_files=has_uploaded_files
|
1386 |
-
)
|
1387 |
-
|
1388 |
-
api_messages = [
|
1389 |
-
{"role": "system", "content": sys_prompt}
|
1390 |
-
]
|
1391 |
-
|
1392 |
-
user_content = query
|
1393 |
-
# νμ κΈ°λ³Έ λ°μ΄ν°μ
λΆμ κ²°κ³Ό ν¬ν¨
|
1394 |
-
user_content += "\n\n" + dataset_analysis
|
1395 |
-
user_content += "\n\n" + crop_recommendation_analysis
|
1396 |
-
user_content += "\n\n" + climate_impact_analysis
|
1397 |
-
|
1398 |
-
# μ‘°κ±΄λΆ λ°μ΄ν°μ
κ²°κ³Ό ν¬ν¨
|
1399 |
-
if soybean_analysis:
|
1400 |
-
user_content += "\n\n" + soybean_analysis
|
1401 |
-
|
1402 |
-
if search_content:
|
1403 |
-
user_content += "\n\n" + search_content
|
1404 |
-
if file_content:
|
1405 |
-
user_content += "\n\n" + file_content
|
1406 |
-
|
1407 |
-
if valid_videos:
|
1408 |
-
user_content += "\n\n# κ΄λ ¨ λμμ\n"
|
1409 |
-
for i, vid in enumerate(valid_videos):
|
1410 |
-
user_content += f"\n{i+1}. **{vid['title']}** - [{vid['source']}]({vid['url']})\n"
|
1411 |
-
|
1412 |
-
api_messages.append({"role": "user", "content": user_content})
|
1413 |
-
|
1414 |
-
try:
|
1415 |
-
stream = client.chat.completions.create(
|
1416 |
-
model="gpt-4.1-mini",
|
1417 |
-
messages=api_messages,
|
1418 |
-
temperature=1,
|
1419 |
-
max_tokens=MAX_TOKENS,
|
1420 |
-
top_p=1,
|
1421 |
-
stream=True
|
1422 |
-
)
|
1423 |
-
|
1424 |
-
for chunk in stream:
|
1425 |
-
if chunk.choices and len(chunk.choices) > 0 and chunk.choices[0].delta.content is not None:
|
1426 |
-
content_delta = chunk.choices[0].delta.content
|
1427 |
-
full_response += content_delta
|
1428 |
-
message_placeholder.markdown(full_response + "β", unsafe_allow_html=True)
|
1429 |
-
|
1430 |
-
message_placeholder.markdown(full_response, unsafe_allow_html=True)
|
1431 |
-
|
1432 |
-
if valid_videos:
|
1433 |
-
st.subheader("κ΄λ ¨ λΉλμ€")
|
1434 |
-
for video in valid_videos:
|
1435 |
-
video_title = video.get('title', 'κ΄λ ¨ λΉλμ€')
|
1436 |
-
video_url = video.get('url', '')
|
1437 |
-
|
1438 |
-
st.markdown(f"π¬ **[{video_title}]({video_url})**")
|
1439 |
-
st.write(f"μΆμ²: {video.get('source', 'μ μ μμ')}")
|
1440 |
-
|
1441 |
-
status.update(label="μλ΅ μλ£!", state="complete")
|
1442 |
-
|
1443 |
-
st.session_state.messages.append({
|
1444 |
-
"role": "assistant",
|
1445 |
-
"content": full_response,
|
1446 |
-
"videos": valid_videos
|
1447 |
-
})
|
1448 |
-
|
1449 |
-
except Exception as api_error:
|
1450 |
-
error_message = str(api_error)
|
1451 |
-
logging.error(f"API μ€λ₯: {error_message}")
|
1452 |
-
status.update(label=f"μ€λ₯: {error_message}", state="error")
|
1453 |
-
raise Exception(f"μλ΅ μμ± μ€λ₯: {error_message}")
|
1454 |
-
|
1455 |
-
if st.session_state.generate_image and full_response:
|
1456 |
-
with st.spinner("λ§μΆ€ν μ΄λ―Έμ§ μμ± μ€..."):
|
1457 |
-
try:
|
1458 |
-
ip = extract_image_prompt(full_response, query)
|
1459 |
-
img, cap = generate_image(ip)
|
1460 |
-
if img:
|
1461 |
-
st.subheader("AI μμ± μ΄λ―Έμ§")
|
1462 |
-
st.image(img, caption=cap, use_container_width=True)
|
1463 |
-
except Exception as img_error:
|
1464 |
-
logging.error(f"μ΄λ―Έμ§ μμ± μ€λ₯: {str(img_error)}")
|
1465 |
-
st.warning("λ§μΆ€ν μ΄λ―Έμ§ μμ±μ μ€ν¨νμ΅λλ€.")
|
1466 |
-
|
1467 |
-
if full_response:
|
1468 |
-
st.subheader("μ΄ μλ΅ λ€μ΄λ‘λ")
|
1469 |
-
c1, c2 = st.columns(2)
|
1470 |
-
c1.download_button(
|
1471 |
-
"λ§ν¬λ€μ΄",
|
1472 |
-
data=full_response,
|
1473 |
-
file_name=f"{query[:30]}.md",
|
1474 |
-
mime="text/markdown"
|
1475 |
-
)
|
1476 |
-
c2.download_button(
|
1477 |
-
"HTML",
|
1478 |
-
data=md_to_html(full_response, query[:30]),
|
1479 |
-
file_name=f"{query[:30]}.html",
|
1480 |
-
mime="text/html"
|
1481 |
-
)
|
1482 |
-
|
1483 |
-
if st.session_state.auto_save and st.session_state.messages:
|
1484 |
-
try:
|
1485 |
-
fn = f"conversation_history_auto_{datetime.now():%Y%m%d_%H%M%S}.json"
|
1486 |
-
with open(fn, "w", encoding="utf-8") as fp:
|
1487 |
-
json.dump(st.session_state.messages, fp, ensure_ascii=False, indent=2)
|
1488 |
-
except Exception as e:
|
1489 |
-
logging.error(f"μλ μ μ₯ μ€ν¨: {e}")
|
1490 |
-
|
1491 |
-
except Exception as e:
|
1492 |
-
error_message = str(e)
|
1493 |
-
placeholder.error(f"μ€λ₯ λ°μ: {error_message}")
|
1494 |
-
logging.error(f"μ
λ ₯ μ²λ¦¬ μ€λ₯: {error_message}")
|
1495 |
-
ans = f"μμ² μ²λ¦¬ μ€ μ€λ₯κ° λ°μνμ΅λλ€: {error_message}"
|
1496 |
-
st.session_state.messages.append({"role": "assistant", "content": ans})
|
1497 |
-
|
1498 |
-
# ββββββββββοΏ½οΏ½βββββββββββββββββββββ main ββββββββββββββββββββββββββββββββββββ
|
1499 |
-
def main():
|
1500 |
-
st.write("==== μ ν리μΌμ΄μ
μμ μκ°:", datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "=====")
|
1501 |
-
agricultural_price_forecast_app()
|
1502 |
-
|
1503 |
-
if __name__ == "__main__":
|
1504 |
-
main()
|
1505 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|