File size: 18,590 Bytes
cb92864 6fecdf0 08fb91a cb92864 5019e1b cb92864 08fb91a 79ef842 cb92864 76d8844 6fecdf0 76d8844 6fecdf0 76d8844 cb92864 7f57ffc cb92864 13ff777 cb92864 79ef842 cb92864 13ff777 cb92864 79ef842 cb92864 79ef842 a766c96 79ef842 a766c96 cb92864 79ef842 cb92864 13ff777 79ef842 cb92864 13ff777 622f700 cb92864 622f700 cb92864 622f700 cb92864 79ef842 cb92864 622f700 79ef842 622f700 cb92864 6fecdf0 d1f6f57 6fecdf0 80ff70e 6fecdf0 495d5e7 38d9fac 495d5e7 38d9fac d8c8177 6fecdf0 495d5e7 6fecdf0 495d5e7 6fecdf0 cb92864 e471528 ff0395c d1f6f57 a766c96 d1f6f57 ff0395c e471528 6fecdf0 d8c8177 47e7138 d8c8177 47e7138 d8c8177 e471528 72c2a85 13ff777 8c8e9d7 7ec56d7 e471528 47e7138 7ec56d7 13ff777 e471528 e1ae1af 2a84ec0 e471528 6fecdf0 71c5f81 e471528 6fecdf0 79ef842 6fecdf0 79ef842 e471528 79ef842 6fecdf0 6c92d06 71c5f81 a766c96 6c92d06 6fecdf0 72c2a85 6fecdf0 72c2a85 71c5f81 6c92d06 71c5f81 6fecdf0 71c5f81 cb92864 e471528 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 | """
app.py β FinNode GraphRAG μ±λ΄
================================
Hugging Face Spaces λ°°ν¬ μ§μ
μ .
Gradio ChatInterface + LangGraph κΈ°λ° λν νλ¦ μ μ΄.
μ€ν:
python app.py
"""
from typing import Any, Dict, List, TypedDict
import dotenv
import gradio.networking
# ββββββββββββββββββββββββββββββββββββββββββ
# HF Spaces/Docker 루νλ°± μ μ κ²μ¦ μ°ν λͺ½ν€ ν¨μΉ
# ββββββββββββββββββββββββββββββββββββββββββ
# μΌλΆ κ°μν/λ컀 νκ²½μμ 127.0.0.1:7860 λ‘컬 μ μ μ¬λΆ μ체 체ν¬κ°
# νλ‘μ λ° λ£¨νλ°± μΈν°νμ΄μ€ μ°¨λ¨μΌλ‘ μΈν΄ μ€ν¨νμ¬ ValueErrorκ° λ°μνλ νμμ λ°©μ§ν©λλ€.
gradio.networking.url_ok = lambda *args, **kwargs: True
import gradio as gr
from langgraph.graph import END, StateGraph
from src.retrieval.finRetrieval import HybridResult, graphrag
from src.utils.ui_templates import CUSTOM_CSS, build_stats_html
dotenv.load_dotenv()
# ββββββββββββββββββββββββββββββββββββββββββ
# Startup DB μκ° μ§λ¨ (Fail-Fast)
# ββββββββββββββββββββββββββββββββββββββββββ
# νκΉ
νμ΄μ€Spaces λ° μ€μ μ± μλ² κ΅¬λ μμ μλ μ¦μ μκ° μ§λ¨μ μννμ¬,
# Neo4j λ°μ΄ν°λ² μ΄μ€ μ°κ²°μ΄ λΆκ°λ₯νλ©΄ ꡬλ μ€ν¨(Crash Early)λ₯Ό μΌμΌν΅λλ€.
try:
graphrag._init_once()
try:
print("β
[μκ° μ§λ¨ μλ£] Neo4j AuraDB μ§μ κ·Έλνμ μλ²½νκ² μ μλμμ΅λλ€!")
except UnicodeEncodeError:
print("[OK] [μκ° μ§λ¨ μλ£] Neo4j AuraDB μ§μ κ·Έλνμ μλ²½νκ² μ μλμμ΅λλ€!")
except Exception as e:
try:
print(f"β [μκ° μ§λ¨ μ€ν¨] Neo4j DB μ°κ²° νμΈ μ€ μλ¬κ° λ°μνμ΅λλ€: {e}")
except UnicodeEncodeError:
print(f"[FAIL] [μκ° μ§λ¨ μ€ν¨] Neo4j DB μ°κ²° νμΈ μ€ μλ¬κ° λ°μνμ΅λλ€: {e}")
raise e
# ββββββββββββββββββββββββββββββββββββββββββ
# 1. LangGraph μ±λ΄ State μ μ
# ββββββββββββββββββββββββββββββββββββββββββ
class ChatState(TypedDict):
question: str # μ¬μ©μ μ§λ¬Έ
history: List[dict] # λν νμ€ν 리 [{"role": "user"/"assistant", "content": "..."}]
context: str # GraphRAG κ²μ κ²°κ³Ό λλ μΌλ° μ§μ λ΅λ³
answer: str # μ΅μ’
λ΅λ³
mode: str # "graph": κ·Έλν κΈ°λ° | "general": μΌλ° μ§μ κΈ°λ°
# ββββββββββββββββββββββββββββββββββββββββββ
# 2. LangGraph λ
Έλ μ μ
# ββββββββββββββββββββββββββββββββββββββββββ
def retrieve_node(state: ChatState) -> ChatState:
"""Node 1: search_with_fallbackμΌλ‘ κ·Έλν κ²μ λλ μΌλ° μ§μ μλ΅ λΌμ°ν
"""
try:
hybrid: HybridResult = graphrag.search_with_fallback(
query_text=state["question"],
history=state["history"],
)
if hybrid.mode == "general":
# μΌλ° μ§μ λͺ¨λ: λ°°λ + GPT-4o λ΅λ³ λ°ν
disclaimer = (
"> β οΈ **μ§μ κ·Έλνμμ κ΄λ ¨ λ΄μ€λ₯Ό μ°Ύμ§ λͺ»νμ΅λλ€.**\n"
"> GPT-4oμ μΌλ° νμ΅ λ°μ΄ν°λ₯Ό κΈ°λ°μΌλ‘ λ΅λ³ν©λλ€.\n"
"> μ΅μ κ΅λ΄ λ΄μ€ κΈ°λ° μ λ³΄κ° νμνλ€λ©΄ μ§λ¬Έμ λ ꡬ체μ μΌλ‘ μ
λ ₯ν΄ λ³΄μΈμ.\n\n"
"---\n\n"
)
context = disclaimer + hybrid.answer
return {**state, "context": context, "mode": "general"}
# κ·Έλν κΈ°λ° λͺ¨λ: κΈ°μ‘΄ μΆμ² μΆμΆ + λ΄μ€ νΌλ λ‘μ§
context = hybrid.answer
sources = []
seen_urls: set = set()
# retriever_resultμμ μμ 3κ° λ΄μ€ μΆμ² μΆμΆ
retriever_result = hybrid.retriever_result
if retriever_result and hasattr(retriever_result, "items"):
for item in retriever_result.items:
meta = getattr(item, "metadata", {})
title = meta.get("article_title")
url = meta.get("article_url")
date = meta.get("article_date")
if title and url and url not in seen_urls:
seen_urls.add(url)
# date νμ ν¬λ§·ν
(μ: 2026-05-19T00:00:00Z -> 2026-05-19)
if date and "T" in str(date):
date = str(date).split("T")[0]
sources.append({"title": title, "url": url, "date": date})
if len(sources) >= 3:
break
# λ§μ½ retriever_resultμμ μ°Ύμ§ λͺ»ν κ²½μ°, Neo4j DBμμ ν€μλ κΈ°λ°μΌλ‘ μ§μ κ΄λ ¨ λ΄μ€ 3κ° λ°±μ
μ‘°ν
if not sources:
try:
from src.retrieval.finRetrieval import get_neo4j_driver
driver = get_neo4j_driver()
# λ¨μ ν€μλ λ§€μΉ μΏΌλ¦¬
query_words = [w for w in state["question"].split() if len(w) > 1]
conditions = []
for w in query_words[:3]:
conditions.append(f"a.title CONTAINS '{w}' OR a.description CONTAINS '{w}'")
with driver.session() as session:
cypher = "MATCH (a:Article) "
if conditions:
cypher += "WHERE " + " OR ".join(conditions) + " "
cypher += "RETURN a.title as title, a.url as url, a.published_date as date ORDER BY a.published_date DESC LIMIT 3"
res_backup = session.run(cypher)
for r in res_backup:
title = r["title"]
url = r["url"]
date = r["date"]
if title and url and url not in seen_urls:
seen_urls.add(url)
if date and "T" in str(date):
date = str(date).split("T")[0]
sources.append({"title": title, "url": url, "date": date})
except Exception:
pass
# λ§μ½ μ¬μ ν λΉμ΄μλ€λ©΄, μ΅μ λ΄μ€ 3κ° λ
ΈμΆ (μμν΄ λΈ κ°μ§ μ 보 λ°©μ§)
if not sources:
try:
from src.retrieval.finRetrieval import get_neo4j_driver
driver = get_neo4j_driver()
with driver.session() as session:
res_latest = session.run(
"MATCH (a:Article) RETURN a.title as title, a.url as url, a.published_date as date "
"ORDER BY a.published_date DESC LIMIT 3"
)
for r in res_latest:
title = r["title"]
url = r["url"]
date = r["date"]
if title and url and url not in seen_urls:
seen_urls.add(url)
if date and "T" in str(date):
date = str(date).split("T")[0]
sources.append({"title": title, "url": url, "date": date})
except Exception:
pass
# λ΅λ³ λμ π° κ΄λ ¨ λ΄μ€ νΌλ ννΈ μ μ±μ€λ½κ² λ§λΆμ΄κΈ°
if sources:
news_feed = "\n\nπ° **κ΄λ ¨ λ΄μ€ νΌλ (μ€μκ° λΆμ μΆμ²)**\n"
for s in sources:
date_str = f" ({s['date']})" if s['date'] else ""
news_feed += f"- π [{s['title']}]({s['url']}){date_str}\n"
# μ€λ³΅μΌλ‘ κ΄λ ¨ λ΄μ€ νΌλκ° λΆμ§ μλλ‘ λ°©μ§
if "κ΄λ ¨ λ΄μ€ νΌλ" not in context:
context += news_feed
except Exception as e:
context = f"[κ²μ μ€λ₯: {e}]"
return {**state, "context": context, "mode": state.get("mode", "graph")}
def generate_node(state: ChatState) -> ChatState:
"""Node 2: λν νμ€ν 리λ₯Ό κ³ λ €νμ¬ μ΅μ’
λ΅λ³ μμ±
GraphRAG(graph λͺ¨λ) λλ μΌλ° μ§μ(general λͺ¨λ) μλ΅ λͺ¨λ
retrieve_nodeμμ contextμ μ΅μ’
ν
μ€νΈλ₯Ό λ΄μμ£Όλ―λ‘ κ·Έλλ‘ μ¬μ©ν©λλ€.
"""
answer = state["context"] if state["context"] else "κ΄λ ¨ μ 보λ₯Ό μ°Ύμ μ μμ΅λλ€."
return {**state, "answer": answer}
# ββββββββββββββββββββββββββββββββββββββββββ
# 3. LangGraph μν¬νλ‘μ° μ»΄νμΌ
# ββββββββββββββββββββββββββββββββββββββββββ
builder = StateGraph(ChatState)
builder.add_node("retrieve", retrieve_node)
builder.add_node("generate", generate_node)
builder.set_entry_point("retrieve")
builder.add_edge("retrieve", "generate")
builder.add_edge("generate", END)
chat_graph = builder.compile()
# ββββββββββββββββββββββββββββββββββββββββββ
# 4. Gradio μ°λ ν¨μ
# ββββββββββββββββββββββββββββββββββββββββββ
def chat(message: str, history: list):
"""Gradio ChatInterfaceκ° νΈμΆνλ ν¨μ.
Args:
message: μ¬μ©μ μ
λ ₯ λ©μμ§
history: Gradioκ° κ΄λ¦¬νλ λν νμ€ν 리
[{"role": "user"/"assistant", "content": "..."}] νμ
Returns:
Generator: μ±λ΄ λ΅λ³ (μ€μκ° μν νμ ν¬ν¨)
"""
if not message.strip():
yield "μ§λ¬Έμ μ
λ ₯ν΄ μ£ΌμΈμ."
return
# Gradio history β LangGraph state νμμΌλ‘ λ³ν
state: ChatState = {
"question": message,
"history": history,
"context": "",
"answer": "",
"mode": "",
}
yield "π μ€μκ° μ§μ κ·Έλνμμ κ΄λ ¨ λ΄μ€λ₯Ό κ²μνλ μ€μ
λλ€..."
try:
# LangGraphμ streamμ μ¬μ©νμ¬ κ° λ
Έλ μ€ν μμ λ§λ€ μ΄λ²€νΈλ₯Ό λ°μ
for event in chat_graph.stream(state):
if "retrieve" in event:
retrieved_mode = event["retrieve"].get("mode", "graph")
if retrieved_mode == "general":
yield "π κ΄λ ¨ λ΄μ€ μμ β GPT-4o μΌλ° μ§μμΌλ‘ λ΅λ³μ μμ±νλ μ€μ
λλ€..."
else:
yield "π‘ κ²μ μλ£! λΆμ κ²°κ³Όλ₯Ό λ°νμΌλ‘ μ΅μ’
λ΅λ³μ μμ±νλ μ€μ
λλ€..."
elif "generate" in event:
yield event["generate"]["answer"]
except Exception as e:
yield f"β οΈ μ±λ΄ μ²λ¦¬ μ€ μ€λ₯κ° λ°μνμ΅λλ€: {str(e)}"
def get_db_stats() -> Dict[str, Any]:
"""Neo4j λ°μ΄ν°λ² μ΄μ€λ‘λΆν° μ€μκ° μ§μ κ·Έλν ν΅κ³ λ° μμ½μ μμ νκ² μ‘°νν©λλ€.
Returns:
Dict[str, Any]: κΈ°μ¬ κ±΄μ, κΈ°μ
μ, κΈ°μ μ, κ΄κ³ μ, μΈλΆ μ€λͺ
λͺ©λ‘
"""
stats: Dict[str, Any] = {
"articles": 0,
"companies": 0,
"technologies": 0,
"techs_list": [],
"recent_articles": [],
}
try:
from src.retrieval.finRetrieval import get_neo4j_driver
driver = get_neo4j_driver()
with driver.session() as session:
# 1. κ° λ
Έλλ³ κ°―μ μ‘°ν
res_articles = session.run("MATCH (a:Article) RETURN count(a) as cnt").single()
if res_articles:
stats["articles"] = res_articles["cnt"]
res_companies = session.run("MATCH (c:AICompany) RETURN count(c) as cnt").single()
if res_companies:
stats["companies"] = res_companies["cnt"]
res_techs = session.run("MATCH (t:AITechnology) RETURN count(t) as cnt").single()
if res_techs:
stats["technologies"] = res_techs["cnt"]
# 2. κΈ°μ λͺ©λ‘ & μ€λͺ
μ‘°ν (μμ 8κ°)
res_tech_list = session.run(
"MATCH (t:AITechnology) "
"RETURN t.name as name, COALESCE(t.description, 'AI νμ κΈ°μ μΈνλΌ') as desc LIMIT 8"
)
stats["techs_list"] = [{"name": r["name"], "desc": r["desc"]} for r in res_tech_list]
# 2.5 μ΅μ μ£Όλͺ© κΈ°μ
리μ€νΈ (μμ 5κ°)
res_comp_list = session.run(
"MATCH (c:AICompany) "
"OPTIONAL MATCH (a:Article)-[:MENTIONS]->(c) "
"RETURN c.name as name, count(a) as cnt "
"ORDER BY cnt DESC LIMIT 5"
)
stats["companies_list"] = [{"name": r["name"]} for r in res_comp_list]
# 3. μ΅κ·Ό κΈ°μ¬ λͺ©λ‘ μ‘°ν (μ΅κ·Ό 4κ°)
res_art_list = session.run(
"MATCH (a:Article) "
"RETURN a.title as title, a.published_date as date, a.url as url "
"ORDER BY a.published_date DESC LIMIT 4"
)
stats["recent_articles"] = [
{"title": r["title"], "date": r["date"], "url": r["url"]}
for r in res_art_list
]
except Exception as e:
print(f"β οΈ [ν΅κ³ μ‘°ν μ€ν¨] Neo4j ν΅κ³λ₯Ό κ°μ Έμ€λ λ° μ€ν¨νμ΅λλ€: {e}")
return stats
# ββββββββββββββββββββββββββββββββββββββββββ
# 5. Gradio UI ꡬμ±
# ββββββββββββββββββββββββββββββββββββββββββ
# Gradio λ²μ λμ κ°μ§ λ° ν
λ§ μ€μ λΆκΈ° (λ‘컬 6.x vs μ격 4.x ν¬λμ μλ²½ λ°©μ§)
try:
gradio_major = int(gr.__version__.split(".")[0])
except Exception:
gradio_major = 4 # κΈ°λ³Έκ° λ°±μ
theme_obj = gr.themes.Soft(
font=["Pretendard", "-apple-system", "BlinkMacSystemFont", "system-ui", "sans-serif"],
primary_hue="sky",
secondary_hue="slate",
)
CHATBOT_DESCRIPTION = """
<div class="prose">
<h3>π AI κΈ°λ° κΈμ΅/νν
ν¬ νμ νΈλ λλ₯Ό λΆμνλ μ§μ κ·Έλν(GraphRAG)μ μ§λ¬ΈνμΈμ.</h3>
<ul>
<li>π° <b>κΈμ΅μ¬/νν
ν¬ AI λν₯</b> β μ νμν, μΉ΄μΉ΄μ€νμ΄, ν μ€λ±
ν¬, λ€μ΄λ²νμ΄ λ±μ μ΅μ κΈμ΅ AI νΈλ λ</li>
<li>π¬ <b>νν
ν¬ ν΅μ¬ κΈ°μ λΆμ</b> β λ‘보μ΄λλ°μ΄μ , λμμ μ©νκ°, AI FDS, κΈμ΅ λ§μ΄λ°μ΄ν° λ± μ 리</li>
<li>π <b>μ€μ λ΄μ€ μΆμ² μ 곡</b> β λ΅λ³λ§λ€ μ€μ 보λλ κ·Όκ±° κΈ°μ¬ λ° μΆμ² URL ν¬ν¨</li>
</ul>
<p>π μλ μμ μ§λ¬Έ λ²νΌμ ν΄λ¦νκ±°λ μ§μ μ
λ ₯ν΄ λ³΄μΈμ.</p>
</div>
"""
interface_kwargs = {
"fn": chat,
"chatbot": gr.Chatbot(height=700, placeholder=CHATBOT_DESCRIPTION),
"textbox": gr.Textbox(
placeholder="λΆμνκ³ μΆμ λ΄μ©μ μμ°μ΄λ‘ μ
λ ₯ν΄μ£ΌμΈμ...",
container=False,
scale=7,
submit_btn="μ μ‘",
),
"examples": [
"μ νμνμ 'μ ν AI μ ν¬νΈν΄λ¦¬μ€' λ‘보μ΄λλ°μ΄μ κΈ°μ κ³Ό κ°μΈ λ§μΆ€ν μλΉμ€μ νΉμ§μ μ€λͺ
ν΄μ€",
"μΉ΄μΉ΄μ€νμ΄κ° μ¬νμΌλ¬λ₯Ό μν΄ κ°λ°ν 'AI λμμ μ©νκ°' λͺ¨λΈμ μ₯μ κ³Ό λμΆ μΉμΈ ν¨κ³Όλ 무μμΈκ°μ?",
"ν μ€λ±
ν¬μ μ€μκ° λ³΄μ΄μ€νΌμ± νμ§ κΈ°μ μΈ 'ν μ€ AI FDS'μ μλ μ리μ μ°¨λ¨μ¨μ μλ €μ€",
"λ€μ΄λ²νμ΄κ° μΆμν 'AI κΈμ΅ λΉμ'κ° λ§μ΄λ°μ΄ν°μ κ²°ν©νμ¬ μ 곡νλ λ§μΆ€ μμ° κ°μ΄λλ μ΄λ€ κ²μΈκ°μ?",
],
"cache_examples": False,
}
# HF Spaces 컨ν
μ΄λ λ΄ λ£¨νλ°± κ²μ¦ μ€ν¨(ValueError) μ°ν λ° λ‘컬/μ격 νΈν ꡬλμ μν΄ launch μΈμ μ λ° μ€κ³
launch_kwargs = {
"server_name": "0.0.0.0",
"server_port": 7860,
}
# λ²μ μ λ§μΆ ν
λ§ λ° CSS μ£Όμ
νμ΄νλΌμΈ (Gradio 6.x νΈνμ± λ³΄μ₯)
blocks_kwargs: Dict[str, Any] = {}
if gradio_major < 5:
interface_kwargs["theme"] = theme_obj
blocks_kwargs["theme"] = theme_obj
blocks_kwargs["css"] = CUSTOM_CSS
elif gradio_major < 6:
launch_kwargs["theme"] = theme_obj
blocks_kwargs["theme"] = theme_obj
blocks_kwargs["css"] = CUSTOM_CSS
else:
launch_kwargs["theme"] = theme_obj
launch_kwargs["css"] = CUSTOM_CSS
# Blocksλ₯Ό νμ©ν 2μ»¬λΌ λ μ΄μμ λμ보λ κ°νΈ
with gr.Blocks(**blocks_kwargs) as demo:
# 1. μλ¨ κΈλ‘λ² λ€λΉκ²μ΄μ
λ° (GNB)
gr.HTML("""
<div style="display: flex; justify-content: space-between; align-items: center; padding: 10px 20px; border-bottom: 1px solid rgba(196, 195, 236, 0.45); background-color: rgba(255, 255, 255, 0.65); backdrop-filter: blur(12px); margin: -20px -20px 6px -20px;">
<div style="font-size: 20px; font-weight: 900; color: #0f172a; display: flex; align-items: center; gap: 12px;">
π FinGraph <span style="font-size: 14px; font-weight: 700; color: #475569;">GraphRAG Enhanced AI Terminal</span>
</div>
</div>
""")
with gr.Row():
# 2. μΌμͺ½ 컬λΌ: μ¬μ΄λλ° (λμ보λ λ° νλ¨ λ©λ΄) - 3:7 splitμ μν΄ scale=3 μ€μ
with gr.Column(scale=3, min_width=320):
stats_data = get_db_stats()
stats_html = build_stats_html(stats_data)
gr.HTML(stats_html)
# 3. μ€λ₯Έμͺ½ 컬λΌ: λ©μΈ μ±λ΄ μμ΄λ¦¬μ΄ - 3:7 splitμ μν΄ scale=7 μ€μ
with gr.Column(scale=7, min_width=500, elem_id="chat-column"):
# ChatInterface without redundant titles/descriptions
chatbot_interface_kwargs: Dict[str, Any] = interface_kwargs.copy()
chatbot_interface_kwargs.pop("title", None)
chatbot_interface_kwargs.pop("description", None)
chatbot_interface_kwargs.pop("theme", None)
gr.ChatInterface(**chatbot_interface_kwargs) # type: ignore
if __name__ == "__main__":
demo.launch(**launch_kwargs)
|