Spaces:
Sleeping
Sleeping
File size: 23,236 Bytes
80fa9cc 370010e 80fa9cc 370010e 80fa9cc |
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 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 |
# app.py
import os
import re
import asyncio
from datetime import datetime
from zoneinfo import ZoneInfo
import gradio as gr
import pandas as pd
from dotenv import load_dotenv
from agents import Agent, Runner, trace, Tool
from agents.mcp import MCPServerStdio
# Your local helper modules
import hype_accounts_server
from memory_utils import load_memories, save_memory, load_memories_df
load_dotenv(override=True)
# === Time / Locale ===
SGT = ZoneInfo("Asia/Singapore")
def now_sgt():
return datetime.now(SGT)
# === MCP Server Factories ===
def make_hyperliquid_trader_mcp_servers():
return [MCPServerStdio(
{"command": "python3", "args": ["-u", "hype_accounts_server.py"],
"env": {
"HYPERLIQUID_API_KEY": os.getenv("HYPERLIQUID_API_KEY"),
"HYPERLIQUID_PRIVATE_KEY": os.getenv("HYPERLIQUID_PRIVATE_KEY"),
"HYPERLIQUID_ACCOUNT_ADDRESS": os.getenv("HYPERLIQUID_ACCOUNT_ADDRESS"),
}},
client_session_timeout_seconds=30
)]
def make_crypto_news_mcp_servers():
# Uses your scraper-based news MCP to avoid API plan limits
return [MCPServerStdio(
{"command": "python3", "args": ["-u", "crypto_news_scraper_server.py"]},
client_session_timeout_seconds=30
)]
def make_technical_analyst_mcp_servers():
return [MCPServerStdio(
{"command": "python3", "args": ["-u", "hl_indicators_server.py"]},
client_session_timeout_seconds=30
)]
# === Utils for MCP lifecycle ===
async def connect_all(servers):
for s in servers:
await s.connect()
async def close_all(servers):
for s in servers:
try:
await s.close()
except Exception:
pass
# === Agent Builders ===
async def build_news_tool(news_servers) -> Tool:
instructions = (
"You are a cryptocurrency researcher. You can search and summarise the most relevant, "
"recent crypto news. If the user asks about a specific coin (e.g., HYPE, BTC, ETH, XRP), "
"focus on that. Otherwise, highlight notable events and potential long/short opportunities. "
f"Current datetime (SGT): {now_sgt():%Y-%m-%d %H:%M:%S}."
)
agent = Agent(
name="Crypto news researcher",
instructions=instructions,
model="gpt-4.1-mini",
mcp_servers=news_servers,
)
return agent.as_tool(
tool_name="crypto_news_researcher",
tool_description="Research crypto news and opportunities for a coin or broad scan."
)
async def build_ta_tool(ta_servers) -> Tool:
instructions = (
"You are a cryptocurrency perpetuals technical trading researcher.\n"
"Default interval: 1h; default lookback: 36.\n"
"Indicators: EMA(20,200), MACD(12,26,9), StochRSI(14,14,3,3), ADL, Volume.\n"
"Given a coin/interval/lookback, compute indicator state, infer trend, and propose entries, "
"exits, and stop-loss/take-profit with reasoning.\n"
f"Current datetime (SGT): {now_sgt():%Y-%m-%d %H:%M:%S}."
)
agent = Agent(
name="Crypto technical researcher",
instructions=instructions,
model="gpt-4.1-mini",
mcp_servers=ta_servers,
)
return agent.as_tool(
tool_name="crypto_technical_researcher",
tool_description="Run TA (EMA, MACD, StochRSI, ADL, Volume)."
)
async def build_trader(hyper_servers, tools: list[Tool]) -> Agent:
# Pull short memory + balances so the agent can context-switch well
past_memories = load_memories(5)
memory_text = "\n".join(past_memories) if past_memories else "No prior memories."
try:
account_details = await hype_accounts_server.get_account_details()
except Exception as e:
account_details = f"(Could not fetch account details: {e})"
instructions = f"""
You are a cryptocurrency perpetuals trader that can:
- Query account balances/positions (via MCP servers on Hyperliquid).
- Do market/news research and TA using attached tools.
- Place long/short orders when the setup has clear edge. Transaction cost: 0.04%.
- If signals are unclear, do NOT trade.
Recent notes:
{memory_text}
Account state:
{account_details}
General rules:
- Prefer confluence: trend + momentum + volume/ADL agreement.
- Always suggest stop-loss and take-profit levels.
- Keep risk per trade modest. Avoid overtrading.
"""
trader = Agent(
name="crypto_trader",
instructions=instructions,
tools=tools,
mcp_servers=hyper_servers, # these expose trading actions
model="gpt-4.1-mini",
)
return trader
# === Intent Routing ===
COMMAND_HELP = """\
You can ask in natural language, e.g.:
β’ "Balance" / "portfolio" β show Hyperliquid balances/positions
β’ "News on BTC and ETH" β market research
β’ "TA HYPE 1h lookback 48" β technical analysis
β’ "Long HYPE 500 at market, SL 2% TP 4%" β execute trade
β’ "Short BTC 0.01 at 68000, SL 69000 TP 66000" β limit order example
β’ "Summarize opportunities today" β broad scan (news + TA)
"""
RE_TA = re.compile(r"\bTA\s+([A-Za-z0-9_\-]+)(?:\s+(\d+[mhHdD]))?(?:\s+lookback\s+(\d+))?", re.IGNORECASE)
RE_LONG = re.compile(r"\bLONG\s+([A-Za-z0-9_\-]+)\s+([\d.]+)(?:\s+at\s+(market|mkt|[\d.]+))?(?:.*?\bSL\s+([\d.%]+))?(?:.*?\bTP\s+([\d.%]+))?", re.IGNORECASE)
RE_SHORT = re.compile(r"\bSHORT\s+([A-Za-z0-9_\-]+)\s+([\d.]+)(?:\s+at\s+(market|mkt|[\d.]+))?(?:.*?\bSL\s+([\d.%]+))?(?:.*?\bTP\s+([\d.%]+))?", re.IGNORECASE)
RE_CLOSE = re.compile(r"\b(close|exit|flatten)\s+(all|[A-Za-z0-9_\-]+)(?:\s+(\d+)%|\s+([\d.]+))?", re.IGNORECASE)
def _close_desc(coin_or_all: str, pct: str | None, qty: str | None) -> str:
coin_or_all = coin_or_all.upper()
if coin_or_all == "ALL":
return "Close ALL open positions at market"
if pct:
return f"Close {pct}% of {coin_or_all} position at market"
if qty:
return f"Reduce {coin_or_all} position by {qty} units at market"
return f"Close {coin_or_all} position at market"
def pct_or_price(s):
if not s:
return None
s = s.strip().lower()
if s.endswith("%"):
try:
return {"type": "percent", "value": float(s[:-1])}
except:
return None
try:
return {"type": "price", "value": float(s)}
except:
return None
# === Core Chatbot Handler ===
async def handle_message(message: str, history: list[tuple[str, str]]):
"""
Routes user intent to: balance, news, TA, or trade execution.
Returns markdown text.
"""
text = (message or "").strip()
ts = now_sgt().strftime("%Y-%m-%d %H:%M:%S %Z")
# Quick help
if text.lower() in {"help", "/help", "commands"}:
return f"### Commands\n{COMMAND_HELP}"
# 1) Balance / portfolio
if re.search(r"\b(balance|portfolio|positions?)\b", text, re.IGNORECASE):
try:
acct = await hype_accounts_server.get_account_details()
save_memory(f"[{now_sgt():%Y-%m-%d %H:%M:%S %Z}] User checked balance.")
return format_account_for_chat(acct)
except Exception as e:
return f"β Error fetching account details: `{e}`"
# 2) TA intent
m = RE_TA.search(text)
if m:
coin = m.group(1).upper()
interval = (m.group(2) or "1h").lower()
lookback = int(m.group(3) or 36)
news_servers = [] # not needed here
ta_servers = []
try:
ta_servers = make_technical_analyst_mcp_servers()
await connect_all(ta_servers)
ta_tool = await build_ta_tool(ta_servers)
# Build a "TA-only" agent so we don't touch trading MPC here
researcher = Agent(
name="crypto_ta_agent",
instructions=f"Focus on TA for {coin} at interval {interval}, lookback {lookback}. Output indicator values and strategy.",
tools=[ta_tool],
model="gpt-4.1-mini",
)
prompt = f"Run TA for {coin} on {interval}, lookback {lookback}. Return indicators and actionable plan."
with trace("crypto_ta"):
result = await Runner.run(researcher, prompt, max_turns=12)
save_memory(f"[{ts}] TA {coin} {interval} lookback {lookback}")
return f"### π¬ TA β {coin} ({interval}, lookback {lookback})\n\n{result.final_output}"
except Exception as e:
return f"β TA error: `{e}`"
finally:
await close_all(ta_servers)
# 3) Trade intent (LONG / SHORT)
mm = RE_LONG.search(text) or RE_SHORT.search(text)
if mm:
is_long = bool(RE_LONG.search(text))
side = "LONG" if is_long else "SHORT"
coin = mm.group(1).upper()
qty = float(mm.group(2))
at = mm.group(3) # "market"/"mkt" or price
sl_raw = mm.group(4)
tp_raw = mm.group(5)
sl = pct_or_price(sl_raw)
tp = pct_or_price(tp_raw)
price_desc = "market" if (at is None or str(at).lower() in {"market", "mkt"}) else at
order_desc = f"{side} {coin} {qty} at {price_desc}"
if sl: order_desc += f", SL {sl_raw}"
if tp: order_desc += f", TP {tp_raw}"
hyper_servers = []
news_servers = []
ta_servers = []
try:
# Tools available to the *trader*: news + TA
news_servers = make_crypto_news_mcp_servers()
ta_servers = make_technical_analyst_mcp_servers()
hyper_servers = make_hyperliquid_trader_mcp_servers()
await asyncio.gather(
connect_all(news_servers),
connect_all(ta_servers),
connect_all(hyper_servers),
)
news_tool = await build_news_tool(news_servers)
ta_tool = await build_ta_tool(ta_servers)
trader = await build_trader(hyper_servers, [news_tool, ta_tool])
# Natural-language trade instruction to the trader agent.
trade_prompt = f"""
User requested: {order_desc}.
If safe and reasonable given risk rules, place the order via Hyperliquid MCP.
- If price specified (numeric), treat as limit; otherwise market.
- Always include stop-loss and take-profit (convert % to prices).
- Confirm the exact order(s) you placed and rationale in the output.
"""
with trace("trade_execution"):
result = await Runner.run(trader, trade_prompt, max_turns=20)
save_memory(f"[{ts}] Executed: {order_desc}")
return f"### π§Ύ Execution β {order_desc}\n\n{result.final_output}"
except Exception as e:
return f"β Trade execution error: `{e}`"
finally:
await asyncio.gather(
close_all(news_servers),
close_all(ta_servers),
close_all(hyper_servers),
)
# 4) News intent (e.g., "news on BTC", "what's happening to HYPE")
if re.search(r"\b(news|headline|what's happening|what is happening|happening)\b", text, re.IGNORECASE):
# Try to pick coins mentioned
coins = re.findall(r"\b([A-Z]{2,6})\b", text.upper())
coins = [c for c in coins if c not in {"NEWS", "HELP"}]
topic = ", ".join(coins) if coins else "broad market"
news_servers = []
try:
news_servers = make_crypto_news_mcp_servers()
await connect_all(news_servers)
news_tool = await build_news_tool(news_servers)
researcher = Agent(
name="crypto_news_agent",
instructions=f"Focus news on: {topic}. Be concise and actionable.",
tools=[news_tool],
model="gpt-4.1-mini",
)
prompt = f"Summarize the most relevant crypto news for {topic}. Include potential trade angles."
with trace("crypto_news"):
result = await Runner.run(researcher, prompt, max_turns=12)
save_memory(f"[{ts}] News requested: {topic}")
return f"### ποΈ News β {topic}\n\n{result.final_output}"
except Exception as e:
return f"β News error: `{e}`"
finally:
await close_all(news_servers)
# 5) Summary scan (news + TA picks)
if re.search(r"\b(opportunit|ideas|setup|summary|today)\b", text, re.IGNORECASE):
hyper_servers = []
news_servers = []
ta_servers = []
try:
news_servers = make_crypto_news_mcp_servers()
ta_servers = make_technical_analyst_mcp_servers()
hyper_servers = make_hyperliquid_trader_mcp_servers()
await asyncio.gather(
connect_all(news_servers),
connect_all(ta_servers),
connect_all(hyper_servers),
)
news_tool = await build_news_tool(news_servers)
ta_tool = await build_ta_tool(ta_servers)
trader = await build_trader(hyper_servers, [news_tool, ta_tool])
prompt = (
"Step 1: Broad news scan for major catalysts.\n"
"Step 2: Pick 3β5 coins with potential edges; run compact TA summary (1h, lookback 36).\n"
"Step 3: Recommend 1β2 best setups with entry, SL, TP and rationale. Do NOT place orders."
)
with trace("daily_opportunities"):
result = await Runner.run(trader, prompt, max_turns=24)
save_memory(f"[{ts}] Opportunity summary requested.")
return f"### π Opportunities β {ts}\n\n{result.final_output}"
except Exception as e:
return f"β Summary error: `{e}`"
finally:
await asyncio.gather(
close_all(news_servers),
close_all(ta_servers),
close_all(hyper_servers),
)
# Fallback: clarify + brief help
return (
"I can help with balance, news, TA, and trade execution.\n\n"
+ COMMAND_HELP
)
mclose = RE_CLOSE.search(text)
if mclose:
# groups: verb, coin_or_all, pct (digits%), qty (number)
coin_or_all = mclose.group(2).strip()
pct = mclose.group(3) # e.g., "50" meaning 50%
qty = mclose.group(4) # absolute size to reduce
desc = _close_desc(coin_or_all, pct, qty)
hyper_servers = []
news_servers = []
ta_servers = []
try:
# Tools for trader context (optional but helpful)
news_servers = make_crypto_news_mcp_servers()
ta_servers = make_technical_analyst_mcp_servers()
hyper_servers = make_hyperliquid_trader_mcp_servers()
await asyncio.gather(
connect_all(news_servers),
connect_all(ta_servers),
connect_all(hyper_servers),
)
news_tool = await build_news_tool(news_servers)
ta_tool = await build_ta_tool(ta_servers)
trader = await build_trader(hyper_servers, [news_tool, ta_tool])
# Natural-language prompt to place the close orders via Hyperliquid MCP
# (The trader agent already has the rules + account context)
trade_prompt = f"""
User request: {desc}.
Instructions:
- If 'ALL', close every open position at market.
- If a coin is specified:
- If a percent is provided, close that % of the CURRENT open position size.
- If a qty is provided, reduce by that absolute base-asset amount.
- If neither provided, fully close that coin position.
- Include SL/TP cleanup if needed (cancel/replace any attached orders).
- If the coin has no open position, report that clearly.
- Return a concise execution summary listing each order (coin, side, size, order type, price if applicable) and rationale.
"""
with trace("close_positions"):
result = await Runner.run(trader, trade_prompt, max_turns=20)
save_memory(f"[{now_sgt():%Y-%m-%d %H:%M}] Close: {desc}")
return f"### π§Ή Close β {desc}\n\n{result.final_output}"
except Exception as e:
return f"β Close error: `{e}`"
finally:
await asyncio.gather(
close_all(news_servers),
close_all(ta_servers),
close_all(hyper_servers),
)
# ---------- Pretty printing for account/positions ----------
from math import isnan
def _fnum(x, decimals=2):
try:
v = float(x)
return f"{v:,.{decimals}f}"
except Exception:
return str(x)
def _fpct(x, decimals=2):
try:
v = float(x) * 100 # input is ROE like 0.0036 -> 0.36%
sign = "π’" if v > 0 else ("π΄" if v < 0 else "βͺοΈ")
return f"{sign} {v:.{decimals}f}%"
except Exception:
return "β"
def _pnl(x, decimals=2):
try:
v = float(x)
sign = "π’" if v > 0 else ("π΄" if v < 0 else "βͺοΈ")
return f"{sign} ${abs(v):,.{decimals}f}"
except Exception:
return "β"
def _side_and_abs_size(szi):
try:
v = float(szi)
side = "LONG" if v > 0 else ("SHORT" if v < 0 else "FLAT")
return side, abs(v)
except Exception:
return "β", szi
def format_account_for_chat(acct: dict) -> str:
"""
Converts the get_account_details() dict into a nice Markdown summary.
"""
if not isinstance(acct, dict):
return f"```\n{acct}\n```"
holdings = acct.get("holdings", []) or []
cash = acct.get("cash_balance", "0")
realized_pnl = acct.get("profit_and_loss", None)
# Totals
total_pos_value = 0.0
total_margin_used = 0.0
total_upnl = 0.0
rows_md = []
for h in holdings:
pos = h.get("position", {})
coin = pos.get("coin", "β")
szi = pos.get("szi", 0)
side, abs_size = _side_and_abs_size(szi)
entry = pos.get("entryPx", "β")
pval = pos.get("positionValue", 0)
u = pos.get("unrealizedPnl", 0)
roe = pos.get("returnOnEquity", 0)
lev = pos.get("leverage", {})
lev_str = f"{lev.get('type','β')}Γ{lev.get('value','β')}"
liq = pos.get("liquidationPx", None)
m_used = pos.get("marginUsed", 0)
fund = pos.get("cumFunding", {}).get("sinceOpen", None)
# Totals
try: total_pos_value += float(pval)
except: pass
try: total_margin_used += float(m_used)
except: pass
try: total_upnl += float(u)
except: pass
rows_md.append(
f"| {coin} | {side} | {_fnum(abs_size, 6)} | ${_fnum(entry, 2)} | ${_fnum(pval, 2)} | {_pnl(u, 2)} | {_fpct(roe, 2)} | {lev_str} | {('β' if liq in (None, 'None') else '$'+_fnum(liq, 2))} | ${_fnum(m_used, 2)} | {('β' if fund in (None, 'None') else _fnum(fund, 6))} |"
)
header = (
"### π Account / Positions\n"
f"- **Cash balance:** ${_fnum(cash, 2)}\n"
f"- **Total pos. value:** ${_fnum(total_pos_value, 2)}\n"
f"- **Unrealized PnL:** {_pnl(total_upnl, 2)}\n"
f"- **Margin used (total):** ${_fnum(total_margin_used, 2)}\n"
)
if realized_pnl is not None:
header += f"- **Realized PnL (session/period):** {_pnl(realized_pnl, 2)}\n"
table_head = (
"\n| Coin | Side | Size | Entry Px | Pos. Value | uPnL | ROE | Leverage | Liq Px | Margin Used | Funding (since open) |\n"
"|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|\n"
)
table_body = "\n".join(rows_md) if rows_md else "_No open positions_"
return header + table_head + table_body
# === Gradio UI ===
with gr.Blocks(fill_height=True) as demo:
gr.Markdown("# π€ Crypto Trading Copilot")
gr.Markdown(
f"Local time: **{now_sgt():%Y-%m-%d %H:%M:%S %Z}** \n"
"[OpenAI Traces](https://platform.openai.com/logs?api=traces) Β· "
"[Hyperliquid](https://app.hyperliquid.xyz/trade)"
)
with gr.Row():
quick1 = gr.Button("π Balance")
quick2 = gr.Button("ποΈ News: BTC, ETH")
quick3 = gr.Button("π¬ TA: HYPE 1h")
quick4 = gr.Button("π§Ύ Long HYPE 500 @ market (SL 2% TP 4%)")
chatbot = gr.Chatbot(height=480, type="messages", show_copy_button=True)
user_in = gr.Textbox(placeholder="Try: TA HYPE 1h lookback 48 β’ News on BTC β’ Long HYPE 500 at market, SL 2% TP 4%", scale=1)
send_btn = gr.Button("Send", variant="primary")
with gr.Accordion("Memory (last 10)", open=False):
mem_table = gr.Dataframe(value=load_memories_df(10), interactive=False, wrap=True, show_label=False)
async def _respond(user_msg, chat_state):
bot_md = await handle_message(user_msg, chat_state or [])
# Log short memory line
save_memory(f"[{now_sgt():%Y-%m-%d %H:%M}] {user_msg[:80]}")
# Update display memory table
latest_mem = load_memories_df(10)
return chat_state + [{"role":"user","content":user_msg},{"role":"assistant","content":bot_md}], "", latest_mem
send_btn.click(_respond, inputs=[user_in, chatbot], outputs=[chatbot, user_in, mem_table])
user_in.submit(_respond, inputs=[user_in, chatbot], outputs=[chatbot, user_in, mem_table])
# Quick actions
async def _qa_balance(chat_state):
msg = "balance"
bot_md = await handle_message(msg, chat_state or [])
save_memory(f"[{now_sgt():%Y-%m-%d %H:%M}] Quick: balance")
latest_mem = load_memories_df(10)
return chat_state + [{"role":"user","content":msg},{"role":"assistant","content":bot_md}], latest_mem
async def _qa_news(chat_state):
msg = "news on BTC and ETH"
bot_md = await handle_message(msg, chat_state or [])
save_memory(f"[{now_sgt():%Y-%m-%d %H:%M}] Quick: news BTC ETH")
latest_mem = load_memories_df(10)
return chat_state + [{"role":"user","content":msg},{"role":"assistant","content":bot_md}], latest_mem
async def _qa_ta(chat_state):
msg = "TA HYPE 1h lookback 48"
bot_md = await handle_message(msg, chat_state or [])
save_memory(f"[{now_sgt():%Y-%m-%d %H:%M}] Quick: TA HYPE")
latest_mem = load_memories_df(10)
return chat_state + [{"role":"user","content":msg},{"role":"assistant","content":bot_md}], latest_mem
async def _qa_long(chat_state):
msg = "Long HYPE 500 at market, SL 2% TP 4%"
bot_md = await handle_message(msg, chat_state or [])
save_memory(f"[{now_sgt():%Y-%m-%d %H:%M}] Quick: long HYPE")
latest_mem = load_memories_df(10)
return chat_state + [{"role":"user","content":msg},{"role":"assistant","content":bot_md}], latest_mem
quick1.click(_qa_balance, inputs=[chatbot], outputs=[chatbot, mem_table])
quick2.click(_qa_news, inputs=[chatbot], outputs=[chatbot, mem_table])
quick3.click(_qa_ta, inputs=[chatbot], outputs=[chatbot, mem_table])
quick4.click(_qa_long, inputs=[chatbot], outputs=[chatbot, mem_table])
if __name__ == "__main__":
# No deprecated args; queue() OK without concurrency_count
demo.queue().launch() |