code-slicer commited on
Commit
1c24194
Β·
verified Β·
1 Parent(s): 7b0a7b6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +187 -0
app.py CHANGED
@@ -45,6 +45,7 @@ os.environ.setdefault("HF_HUB_ENABLE_HF_TRANSFER", "1")
45
  from huggingface_hub import hf_hub_download
46
  import pandas as pd
47
  import streamlit as st
 
48
  from streamlit.components.v1 import html
49
  from css import render_message, render_chip_buttons, log_and_render, replay_log, _get_colors
50
 
@@ -149,7 +150,174 @@ from chat_a import (
149
  format_summary_tags_custom,
150
  make_companion_age_message
151
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  # μ§€μ—° μ΄ˆκΈ°ν™”: import μ‹œμ μ—λŠ” 데이터 μ ‘κ·Ό κΈˆμ§€, μ—¬κΈ°μ„œ ν•œ 번만 μ£Όμž…
154
  init_datasets(
155
  travel_df=travel_df,
@@ -693,6 +861,11 @@ def region_ui(travel_df, external_score_df, festival_df, weather_df, package_df,
693
  log_and_render("ν•„μš”ν•˜μ‹€ λ•Œ μ–Έμ œλ“ μ§€ 또 μ°Ύμ•„μ£Όμ„Έμš”! ✈️",
694
  sender="bot", chat_container=chat_container,
695
  key="goodbye")
 
 
 
 
 
696
 
697
  # ───────────────────────────────────── intent λͺ¨λ“œ
698
  def intent_ui(travel_df, external_score_df, festival_df, weather_df, package_df,
@@ -1045,6 +1218,11 @@ def intent_ui(travel_df, external_score_df, festival_df, weather_df, package_df,
1045
  log_and_render("ν•„μš”ν•˜μ‹€ λ•Œ μ–Έμ œλ“ μ§€ 또 μ°Ύμ•„μ£Όμ„Έμš”! ✈️",
1046
  sender="bot", chat_container=chat_container,
1047
  key="goodbye")
 
 
 
 
 
1048
 
1049
  # ───────────────────────────────────── emotion λͺ¨λ“œ
1050
  def emotion_ui(travel_df, external_score_df, festival_df, weather_df, package_df,
@@ -1469,6 +1647,11 @@ def emotion_ui(travel_df, external_score_df, festival_df, weather_df, package_df
1469
  log_and_render("ν•„μš”ν•˜μ‹€ λ•Œ μ–Έμ œλ“ μ§€ 또 μ°Ύμ•„μ£Όμ„Έμš”! ✈️",
1470
  sender="bot", chat_container=chat_container,
1471
  key="goodbye")
 
 
 
 
 
1472
 
1473
  # ───────────────────────────────────── unknown λͺ¨λ“œ
1474
  def unknown_ui(country, city, chat_container, log_and_render):
@@ -1497,6 +1680,10 @@ def main():
1497
  init_session()
1498
  chat_container = st.container()
1499
 
 
 
 
 
1500
  # πŸŽ›οΈ 말풍선/ν‘œμ‹œ μ˜΅μ…˜ (β‘’, β‘£)
1501
  st.sidebar.subheader("βš™οΈ λŒ€ν™” ν‘œμ‹œ")
1502
  st.sidebar.selectbox("ν…Œλ§ˆ", ["ν”ΌμŠ€νƒ€μΉ˜μ˜€", "μŠ€μΉ΄μ΄λΈ”λ£¨", "크리미였트"], key="bubble_theme")
 
45
  from huggingface_hub import hf_hub_download
46
  import pandas as pd
47
  import streamlit as st
48
+ import requests
49
  from streamlit.components.v1 import html
50
  from css import render_message, render_chip_buttons, log_and_render, replay_log, _get_colors
51
 
 
150
  format_summary_tags_custom,
151
  make_companion_age_message
152
  )
153
+ OLLAMA_HOST = os.getenv("OLLAMA_HOST", "http://localhost:11434")
154
+ OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "gemma2:9b")
155
+ OLLAMA_TIMEOUT = int(os.getenv("OLLAMA_TIMEOUT", "60"))
156
+
157
+ KOREAN_SYSTEM_PROMPT = """당신은 ν•œκ΅­μ–΄ μ–΄μ‹œμŠ€ν„΄νŠΈμž…λ‹ˆλ‹€. 항상 ν•œκ΅­μ–΄λ‘œ λ‹΅ν•˜μ„Έμš”."""
158
+
159
+ STRUCTURED_EXTRACTION_SYSTEM = """\
160
+ You are a travel assistant that extracts structured fields from Korean user queries.
161
+ Return ONLY a valid JSON object:
162
+ {
163
+ "emotion": "happy|sad|stressed|excited|tired|none",
164
+ "intent": "beach|hiking|shopping|food|museum|relaxing|none",
165
+ "country_hint": "",
166
+ "city_hint": "",
167
+ "themes_hint": ["<0..3 words>"],
168
+ "notes": "<very short reasoning in Korean>"
169
+ }
170
+ If unknown, use "none" or "" and NEVER add extra text outside JSON.
171
+ """
172
+
173
+ def _build_structured_user_prompt(user_text: str) -> str:
174
+ # λΆˆν•„μš”ν•œ λž˜ν•‘ 없이, λͺ¨λΈμ΄ JSON만 내도둝 κΉ”λ”νžˆ 전달
175
+ return user_text.strip()
176
+
177
+ def _call_ollama_chat(
178
+ messages,
179
+ model=OLLAMA_MODEL,
180
+ temperature=0.8, top_p=0.9, top_k=40, repeat_penalty=1.1,
181
+ system_prompt=None
182
+ ):
183
+ url = f"{OLLAMA_HOST}/api/chat"
184
+ _msgs = []
185
+ if system_prompt:
186
+ _msgs.append({"role": "system", "content": system_prompt})
187
+ _msgs.extend(messages)
188
+
189
+ payload = {
190
+ "model": model,
191
+ "messages": _msgs,
192
+ "options": {
193
+ "temperature": temperature,
194
+ "top_p": top_p,
195
+ "top_k": top_k,
196
+ "repeat_penalty": repeat_penalty,
197
+ },
198
+ "stream": False,
199
+ }
200
+ try:
201
+ r = requests.post(url, json=payload, timeout=OLLAMA_TIMEOUT)
202
+ r.raise_for_status()
203
+ return (r.json().get("message") or {}).get("content", "") or ""
204
+ except requests.RequestException:
205
+ return ""
206
+
207
+ def _llm_structured_extract(user_text: str):
208
+ out = _call_ollama_chat(
209
+ [
210
+ {"role": "system", "content": STRUCTURED_EXTRACTION_SYSTEM},
211
+ {"role": "user", "content": _build_structured_user_prompt(user_text)}
212
+ ],
213
+ system_prompt=None # μœ„μ—μ„œ system으둜 이미 λ„£μ—ˆμŒ
214
+ )
215
+ try:
216
+ data = json.loads(out)
217
+ except Exception:
218
+ data = {}
219
+ data.setdefault("emotion", "none")
220
+ data.setdefault("intent", "none")
221
+ data.setdefault("country_hint", "")
222
+ data.setdefault("city_hint", "")
223
+ data.setdefault("themes_hint", [])
224
+ data.setdefault("notes", "")
225
+ return data
226
+
227
+ # ──────────────────────────────── Streamlit용 LLM λͺ¨λ“œ UI ────────────────────────────────
228
+ def _ensure_llm_state():
229
+ st.session_state.setdefault("llm_mode", False)
230
+ st.session_state.setdefault("llm_history", []) # [{'role':'user'|'assistant', 'content': str}, ...]
231
+ st.session_state.setdefault("llm_intro_needed", False)
232
+ st.session_state.setdefault("llm_input", "")
233
+
234
+ def render_llm_followup(chat_container):
235
+ """
236
+ μΆ”μ²œ ν”Œλ‘œμš° μ’…λ£Œ ν›„ μžλ™ μ§„μž…ν•˜λŠ” 자유질문(LLM) λͺ¨λ“œ UI.
237
+ """
238
+ _ensure_llm_state()
239
+ MAX_TURNS = 6
240
+
241
+ if st.session_state.get("llm_intro_needed"):
242
+ log_and_render(
243
+ "🧠 μ΄μ œλΆ€ν„°λŠ” 자유 질문 λͺ¨λ“œμ˜ˆμš”. μ—¬ν–‰ 외에도 뭐든 ν•œκ΅­μ–΄λ‘œ λ¬Όμ–΄λ³΄μ„Έμš”!",
244
+ sender="bot",
245
+ chat_container=chat_container,
246
+ key=f"llm_intro_{random.randint(1,999999)}"
247
+ )
248
+ st.session_state["llm_intro_needed"] = False
249
+
250
+ # μž…λ ₯μ°½
251
+ q = st.text_input(
252
+ "LLM 질문",
253
+ placeholder="무엇이든 λ¬Όμ–΄λ³΄μ„Έμš” (μ’…λ£Œν•˜λ €λ©΄ 'μ’…λ£Œ' μž…λ ₯)",
254
+ key="llm_input"
255
+ )
256
+
257
+ # λ™μž‘
258
+ if q:
259
+ # μ’…λ£Œ ν‚€μ›Œλ“œ
260
+ if q.strip() in ("μ’…λ£Œ", "quit", "exit"):
261
+ st.session_state["llm_mode"] = False
262
+ st.session_state["llm_input"] = ""
263
+ log_and_render(
264
+ "LLM λͺ¨λ“œλ₯Ό μ’…λ£Œν• κ²Œμš”. ν•„μš”ν•˜μ‹€ λ•Œ μ–Έμ œλ“  λ‹€μ‹œ μ§ˆλ¬Έν•΄ μ£Όμ„Έμš”! ✨",
265
+ sender="bot",
266
+ chat_container=chat_container,
267
+ key=f"llm_end_{random.randint(1,999999)}"
268
+ )
269
+ st.rerun()
270
+ return
271
+
272
+ # μ‚¬μš©μž 버블
273
+ log_and_render(
274
+ q,
275
+ sender="user",
276
+ chat_container=chat_container,
277
+ key=f"llm_user_{random.randint(1,999999)}"
278
+ )
279
+ st.session_state.llm_history.append({"role": "user", "content": q})
280
+
281
+ # 졜근 νžˆμŠ€ν† λ¦¬λ‘œ LLM 호좜
282
+ msgs = st.session_state.llm_history[-(MAX_TURNS-1):]
283
+ a = _call_ollama_chat(
284
+ messages=msgs,
285
+ system_prompt=KOREAN_SYSTEM_PROMPT,
286
+ temperature=0.8, top_p=0.9, top_k=40, repeat_penalty=1.1
287
+ )
288
 
289
+ if not a:
290
+ log_and_render(
291
+ "⚠️ LLM 응닡을 λ°›μ§€ λͺ»ν–ˆμŠ΅λ‹ˆλ‹€. Ollama μ„œλ²„ μƒνƒœλ₯Ό 확인해 μ£Όμ„Έμš”.",
292
+ sender="bot",
293
+ chat_container=chat_container,
294
+ key=f"llm_err_{random.randint(1,999999)}"
295
+ )
296
+ else:
297
+ log_and_render(
298
+ a,
299
+ sender="bot",
300
+ chat_container=chat_container,
301
+ key=f"llm_bot_{random.randint(1,999999)}"
302
+ )
303
+ st.session_state.llm_history.append({"role": "assistant", "content": a})
304
+
305
+ # μž…λ ₯ μ΄ˆκΈ°ν™”
306
+ st.session_state["llm_input"] = ""
307
+
308
+ # ν•˜λ‹¨ μ’…λ£Œ λ²„νŠΌ
309
+ cols = st.columns(3)
310
+ with cols[0]:
311
+ if st.button("πŸ”š LLM λͺ¨λ“œ μ’…λ£Œ"):
312
+ st.session_state["llm_mode"] = False
313
+ log_and_render(
314
+ "LLM λͺ¨λ“œλ₯Ό μ’…λ£Œν• κ²Œμš”. λ‹€μŒμ— 또 λ§Œλ‚˜μš”! πŸ‘‹",
315
+ sender="bot",
316
+ chat_container=chat_container,
317
+ key=f"llm_end_btn_{random.randint(1,999999)}"
318
+ )
319
+ st.rerun()
320
+
321
  # μ§€μ—° μ΄ˆκΈ°ν™”: import μ‹œμ μ—λŠ” 데이터 μ ‘κ·Ό κΈˆμ§€, μ—¬κΈ°μ„œ ν•œ 번만 μ£Όμž…
322
  init_datasets(
323
  travel_df=travel_df,
 
861
  log_and_render("ν•„μš”ν•˜μ‹€ λ•Œ μ–Έμ œλ“ μ§€ 또 μ°Ύμ•„μ£Όμ„Έμš”! ✈️",
862
  sender="bot", chat_container=chat_container,
863
  key="goodbye")
864
+
865
+ # βœ… μ—¬κΈ°μ„œ LLM λͺ¨λ“œλ‘œ μžλ™ μ „ν™˜
866
+ st.session_state["llm_mode"] = True
867
+ st.session_state["llm_intro_needed"] = True
868
+ st.rerun()
869
 
870
  # ───────────────────────────────────── intent λͺ¨λ“œ
871
  def intent_ui(travel_df, external_score_df, festival_df, weather_df, package_df,
 
1218
  log_and_render("ν•„μš”ν•˜μ‹€ λ•Œ μ–Έμ œλ“ μ§€ 또 μ°Ύμ•„μ£Όμ„Έμš”! ✈️",
1219
  sender="bot", chat_container=chat_container,
1220
  key="goodbye")
1221
+
1222
+ # βœ… μ—¬κΈ°μ„œ LLM λͺ¨λ“œλ‘œ μžλ™ μ „ν™˜
1223
+ st.session_state["llm_mode"] = True
1224
+ st.session_state["llm_intro_needed"] = True
1225
+ st.rerun()
1226
 
1227
  # ───────────────────────────────────── emotion λͺ¨λ“œ
1228
  def emotion_ui(travel_df, external_score_df, festival_df, weather_df, package_df,
 
1647
  log_and_render("ν•„μš”ν•˜μ‹€ λ•Œ μ–Έμ œλ“ μ§€ 또 μ°Ύμ•„μ£Όμ„Έμš”! ✈️",
1648
  sender="bot", chat_container=chat_container,
1649
  key="goodbye")
1650
+
1651
+ # βœ… μ—¬κΈ°μ„œ LLM λͺ¨λ“œλ‘œ μžλ™ μ „ν™˜
1652
+ st.session_state["llm_mode"] = True
1653
+ st.session_state["llm_intro_needed"] = True
1654
+ st.rerun()
1655
 
1656
  # ───────────────────────────────────── unknown λͺ¨λ“œ
1657
  def unknown_ui(country, city, chat_container, log_and_render):
 
1680
  init_session()
1681
  chat_container = st.container()
1682
 
1683
+ if st.session_state.get("llm_mode"):
1684
+ render_llm_followup(chat_container)
1685
+ return
1686
+
1687
  # πŸŽ›οΈ 말풍선/ν‘œμ‹œ μ˜΅μ…˜ (β‘’, β‘£)
1688
  st.sidebar.subheader("βš™οΈ λŒ€ν™” ν‘œμ‹œ")
1689
  st.sidebar.selectbox("ν…Œλ§ˆ", ["ν”ΌμŠ€νƒ€μΉ˜μ˜€", "μŠ€μΉ΄μ΄λΈ”λ£¨", "크리미였트"], key="bubble_theme")