WPatrick81 commited on
Commit
2c03bab
ยท
verified ยท
1 Parent(s): 02e2916

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +85 -121
app.py CHANGED
@@ -3429,16 +3429,19 @@ def handle_chat(n_send, n_submit, quick_clicks,
3429
  return chat_items, history, ""
3430
 
3431
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
3432
- # 15. LLM API ์—ฐ๋™
3433
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
3434
 
3435
  # โ”€โ”€ ์„ค์ • โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
3436
- LLM_PROVIDER = os.environ.get("LLM_PROVIDER", "openai") # "openai" | "anthropic" | "local"
3437
- LLM_MODEL = os.environ.get("LLM_MODEL", "gpt-4o-mini")
3438
- LLM_API_KEY = os.environ.get("LLM_API_KEY", "")
3439
- LLM_API_URL = os.environ.get("LLM_API_URL", "") # local/custom endpoint
 
 
 
3440
  LLM_MAX_TOKENS = int(os.environ.get("LLM_MAX_TOKENS", "1024"))
3441
- LLM_TEMP = float(os.environ.get("LLM_TEMP", "0.4"))
3442
 
3443
  SYSTEM_PROMPT = """๋‹น์‹ ์€ ์•ผ๊ตฌ ๋ฐ์ดํ„ฐ ๋ถ„์„ ์ „๋ฌธ๊ฐ€ AI์ž…๋‹ˆ๋‹ค.
3444
  KBO, NPB, ๋งˆ์ด๋„ˆ๋ฆฌ๊ทธ ํˆฌ์ˆ˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„ํ•˜๊ณ  ์ธ์‚ฌ์ดํŠธ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
@@ -3449,36 +3452,18 @@ KBO, NPB, ๋งˆ์ด๋„ˆ๋ฆฌ๊ทธ ํˆฌ์ˆ˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„ํ•˜๊ณ  ์ธ์‚ฌ์ดํŠธ๋ฅผ ์ œ
3449
  """
3450
 
3451
 
3452
- # โ”€โ”€ OpenAI ํ˜ธ์ถœ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
3453
- def _call_openai(question, context, history):
3454
- import openai
3455
- client = openai.OpenAI(api_key=LLM_API_KEY)
3456
-
3457
- messages = [{"role": "system", "content": SYSTEM_PROMPT}]
3458
- for h in (history or [])[-10:]:
3459
- messages.append({"role": h["role"], "content": h["content"]})
3460
- messages.append({
3461
- "role": "user",
3462
- "content": f"[๋ฐ์ดํ„ฐ ์ปจํ…์ŠคํŠธ]\n{context}\n\n[์งˆ๋ฌธ]\n{question}",
3463
- })
3464
-
3465
- resp = client.chat.completions.create(
3466
- model = LLM_MODEL,
3467
- messages = messages,
3468
- max_tokens = LLM_MAX_TOKENS,
3469
- temperature = LLM_TEMP,
3470
- )
3471
- return resp.choices[0].message.content.strip()
3472
-
3473
-
3474
- # โ”€โ”€ Anthropic ํ˜ธ์ถœ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
3475
  def _call_anthropic(question, context, history):
3476
  import anthropic
 
3477
  client = anthropic.Anthropic(api_key=LLM_API_KEY)
3478
 
 
3479
  messages = []
3480
  for h in (history or [])[-10:]:
3481
- messages.append({"role": h["role"], "content": h["content"]})
 
 
3482
  messages.append({
3483
  "role": "user",
3484
  "content": f"[๋ฐ์ดํ„ฐ ์ปจํ…์ŠคํŠธ]\n{context}\n\n[์งˆ๋ฌธ]\n{question}",
@@ -3493,67 +3478,30 @@ def _call_anthropic(question, context, history):
3493
  return resp.content[0].text.strip()
3494
 
3495
 
3496
- # โ”€โ”€ Local / Custom Endpoint ํ˜ธ์ถœ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
3497
- def _call_local(question, context, history):
3498
- import requests as req
3499
-
3500
- messages = [{"role": "system", "content": SYSTEM_PROMPT}]
3501
- for h in (history or [])[-10:]:
3502
- messages.append({"role": h["role"], "content": h["content"]})
3503
- messages.append({
3504
- "role": "user",
3505
- "content": f"[๋ฐ์ดํ„ฐ ์ปจํ…์ŠคํŠธ]\n{context}\n\n[์งˆ๋ฌธ]\n{question}",
3506
- })
3507
-
3508
- payload = {
3509
- "model": LLM_MODEL,
3510
- "messages": messages,
3511
- "max_tokens": LLM_MAX_TOKENS,
3512
- "temperature": LLM_TEMP,
3513
- "stream": False,
3514
- }
3515
- headers = {}
3516
- if LLM_API_KEY:
3517
- headers["Authorization"] = f"Bearer {LLM_API_KEY}"
3518
-
3519
- r = req.post(
3520
- LLM_API_URL or "http://localhost:11434/v1/chat/completions",
3521
- json = payload,
3522
- headers = headers,
3523
- timeout = 60,
3524
- )
3525
- r.raise_for_status()
3526
- data = r.json()
3527
- return data["choices"][0]["message"]["content"].strip()
3528
-
3529
-
3530
  # โ”€โ”€ ํ†ตํ•ฉ ํ˜ธ์ถœ ํ•จ์ˆ˜ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
3531
  def call_llm_api(question, context="", history=None):
3532
  """
3533
- LLM_PROVIDER ํ™˜๊ฒฝ๋ณ€์ˆ˜์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ๋ฐฑ์—”๋“œ๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
3534
- LLM_API_KEY ๋ฏธ์„ค์ • ์‹œ ๊ทœ์น™ ๊ธฐ๋ฐ˜ ๏ฟฝ๏ฟฝ๏ฟฝ๋ฐฑ์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
3535
  """
3536
- if not LLM_API_KEY and LLM_PROVIDER != "local":
3537
  return _rule_based_answer(question, context)
3538
 
3539
  try:
3540
- if LLM_PROVIDER == "anthropic":
3541
- return _call_anthropic(question, context, history)
3542
- elif LLM_PROVIDER == "local":
3543
- return _call_local(question, context, history)
3544
- else:
3545
- return _call_openai(question, context, history)
3546
- except ImportError as e:
3547
- return f"โš ๏ธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ฏธ์„ค์น˜: {e}\n`pip install openai` ๋˜๋Š” `pip install anthropic`"
3548
  except Exception as e:
3549
- return f"โš ๏ธ LLM ํ˜ธ์ถœ ์˜ค๋ฅ˜: {e}"
3550
 
3551
 
3552
  # โ”€โ”€ ๊ทœ์น™ ๊ธฐ๋ฐ˜ ํด๋ฐฑ (API ํ‚ค ์—†์„ ๋•Œ) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
3553
  def _rule_based_answer(question, context):
3554
  q = question.lower()
3555
 
3556
- # ์ปจํ…์ŠคํŠธ ํŒŒ์‹ฑ
3557
  ctx = {}
3558
  for part in context.split("|"):
3559
  part = part.strip()
@@ -3561,62 +3509,78 @@ def _rule_based_answer(question, context):
3561
  k, v = part.split("=", 1)
3562
  ctx[k.strip()] = v.strip()
3563
 
3564
- pitcher = ctx.get("ํˆฌ์ˆ˜", "ํ•ด๋‹น ํˆฌ์ˆ˜")
3565
- era = ctx.get("ERA", "โ€”")
3566
- k9 = ctx.get("K9", "โ€”")
3567
- bb9 = ctx.get("BB9", "โ€”")
3568
- whip = ctx.get("WHIP", "โ€”")
3569
- fip = ctx.get("FIP", "โ€”")
3570
- spd = ctx.get("ํ‰๊ท ๊ตฌ์†", "โ€”")
3571
- pitches = ctx.get("์ฃผ๋ ฅ ๊ตฌ์ข…", "โ€”")
3572
 
3573
  if any(w in q for w in ["๊ตฌ์ข…", "์ฃผ๋ ฅ", "pitch"]):
3574
- return (f"**{pitcher}**์˜ ์ฃผ๋ ฅ ๊ตฌ์ข…์€ **{pitches}** ์ž…๋‹ˆ๋‹ค.\n\n"
3575
- f"ํ‰๊ท  ๊ตฌ์†์€ **{spd} km/h** ์ด๋ฉฐ, "
3576
- f"K9={k9}, BB9={bb9} ๊ธฐ์ค€์œผ๋กœ "
3577
- f"{'ํƒˆ์‚ผ์ง„ ๋Šฅ๋ ฅ์ด ์šฐ์ˆ˜ํ•ฉ๋‹ˆ๋‹ค.' if k9 != 'โ€”' and float(k9.replace('โ€”','0')) > 8 else '์ œ๊ตฌ๋ ฅ ์ค‘์‹ฌ์˜ ํˆฌ์ˆ˜์ž…๋‹ˆ๋‹ค.'}")
 
 
 
 
 
 
3578
 
3579
  if any(w in q for w in ["์•ฝ์ ", "์นด์šดํŠธ", "count"]):
3580
- return (f"์นด์šดํŠธ๋ณ„ ์•ฝ์  ๋ถ„์„์€ **์นด์šดํŠธ๋ณ„ ํƒญ**์—์„œ ํžˆํŠธ๋งต์„ ํ™•์ธํ•ด ์ฃผ์„ธ์š”.\n\n"
3581
- f"์ผ๋ฐ˜์ ์œผ๋กœ ๋ณผ์นด์šดํŠธ๊ฐ€ ๋ถˆ๋ฆฌํ•  ๋•Œ(2-0, 3-1) ํŒจ์ŠคํŠธ๋ณผ ๋น„์œจ์ด ๋†’์•„์ง€๋ฏ€๋กœ "
3582
- f"ํƒ€์ž๊ฐ€ ๋…ธ๋ฆฌ๊ธฐ ์‰ฌ์šด ๊ฒฝํ–ฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค.")
 
 
3583
 
3584
  if any(w in q for w in ["์ขŒํƒ€", "์šฐํƒ€", "stand", "ํƒ€์ž ์œ ํ˜•"]):
3585
- return (f"์ขŒํƒ€/์šฐํƒ€ ๋ถ„์„์€ **์‚ฌ์ด๋“œ๋ฐ”์˜ ํƒ€์ž ์œ ํ˜• ํ•„ํ„ฐ**๋ฅผ ๋ณ€๊ฒฝํ•˜์—ฌ "
3586
- f"๊ฐ ํƒญ์—์„œ ๋น„๊ตํ•ด ๋ณด์„ธ์š”.\n\n"
3587
- f"๋ฌด๋ธŒ๋จผํŠธ ์ฐจํŠธ์™€ ๋กœ์ผ€์ด์…˜ ์ฐจํŠธ์—์„œ ์œ ํ˜•๋ณ„ ์ฐจ์ด๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.")
 
 
3588
 
3589
  if any(w in q for w in ["๊ตฌ์†", "์†๋„", "speed", "ํŠธ๋ Œ๋“œ"]):
3590
- return (f"**{pitcher}**์˜ ํ‰๊ท  ๊ตฌ์†์€ **{spd} km/h** ์ž…๋‹ˆ๋‹ค.\n\n"
3591
- f"๊ฒŒ์ž„๋กœ๊ทธ ํƒญ์—์„œ ๋‚ ์งœ๋ณ„ ๊ตฌ์† ์ถ”์ด๋ฅผ ํ™•์ธํ•˜๊ฑฐ๋‚˜, "
3592
- f"๋ฌด๋ธŒ๋จผํŠธ ํƒญ์˜ ๊ตฌ์† vs ๋ฌด๋ธŒ๋จผํŠธ ์Šค์บํ„ฐ ์ฐจํŠธ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.")
 
 
3593
 
3594
  if any(w in q for w in ["๋ฆฌ๊ทธ", "๋น„๊ต", "์ˆœ์œ„", "rank"]):
3595
- return (f"**{pitcher}** ๋ฆฌ๊ทธ ๋น„๊ต ์ง€ํ‘œ:\n\n"
3596
- f"- ERA: **{era}**\n"
3597
- f"- FIP: **{fip}**\n"
3598
- f"- WHIP: **{whip}**\n"
3599
- f"- K9: **{k9}**\n"
3600
- f"- BB9: **{bb9}**\n\n"
3601
- f"์ž์„ธํ•œ ๋ฆฌ๊ทธ ์ˆœ์œ„๋Š” **๋ฆฌ๊ทธ๋น„๊ต ํƒญ**์„ ํ™•์ธํ•ด ์ฃผ์„ธ์š”.")
 
 
3602
 
3603
  if any(w in q for w in ["๊ฐ•์ ", "์ž˜ํ•˜", "good", "best"]):
3604
- return (f"**{pitcher}**์˜ ์ฃผ์š” ์ง€ํ‘œ:\n\n"
3605
- f"ERA={era}, K9={k9}, BB9={bb9}, WHIP={whip}\n\n"
3606
- f"์ˆ˜์น˜๊ฐ€ ๋‚ฎ์€ ERA/WHIP/BB9์™€ ๋†’์€ K9์ด ๊ฐ•์  ์ง€ํ‘œ์ž…๋‹ˆ๋‹ค. "
3607
- f"๋ฆฌ๊ทธ๋น„๊ต ํƒญ์—์„œ ํ‰๊ท  ๋Œ€๋น„ ์šฐ์œ„ ํ•ญ๋ชฉ์„ ํ™•์ธํ•˜์„ธ์š”.")
3608
-
3609
- # ๊ธฐ๋ณธ ์‘๋‹ต
3610
- return (f"**{pitcher}** ({ctx.get('์—ฐ๋„','โ€”')}, {ctx.get('๋ฆฌ๊ทธ','โ€”')}) ๊ธฐ๋ณธ ์Šคํƒฏ:\n\n"
3611
- f"| ์ง€ํ‘œ | ๊ฐ’ |\n|------|----|\n"
3612
- f"| ERA | {era} |\n"
3613
- f"| FIP | {fip} |\n"
3614
- f"| WHIP | {whip} |\n"
3615
- f"| K9 | {k9} |\n"
3616
- f"| BB9 | {bb9} |\n"
3617
- f"| ํ‰๊ท ๊ตฌ์† | {spd} |\n\n"
3618
- f"๋” ๊ตฌ์ฒด์ ์ธ ์งˆ๋ฌธ์„ ํ•ด์ฃผ์‹œ๋ฉด ๋” ์ •ํ™•ํ•œ ๋ถ„์„์„ ๋“œ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜Š\n\n"
3619
- f"*(LLM_API_KEY ์„ค์ • ์‹œ AI ๋ถ„์„์ด ํ™œ์„ฑํ™”๋ฉ๋‹ˆ๋‹ค)*")
 
 
3620
 
3621
 
3622
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
 
3429
  return chat_items, history, ""
3430
 
3431
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
3432
+ # 15. LLM API ์—ฐ๋™ (Claude / Anthropic ์ „์šฉ)
3433
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
3434
 
3435
  # โ”€โ”€ ์„ค์ • โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
3436
+ # โ˜… ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์šฐ์„ , ์—†์œผ๋ฉด ์•„๋ž˜ ๊ธฐ๋ณธ๊ฐ’ ์‚ฌ์šฉ
3437
+ LLM_PROVIDER = "anthropic"
3438
+ LLM_MODEL = os.environ.get("LLM_MODEL", "claude-sonnet-4-5")
3439
+ LLM_API_KEY = os.environ.get(
3440
+ "ANTHROPIC_API_KEY",
3441
+ "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"
3442
+ )
3443
  LLM_MAX_TOKENS = int(os.environ.get("LLM_MAX_TOKENS", "1024"))
3444
+ LLM_TEMP = float(os.environ.get("LLM_TEMP", "0.4"))
3445
 
3446
  SYSTEM_PROMPT = """๋‹น์‹ ์€ ์•ผ๊ตฌ ๋ฐ์ดํ„ฐ ๋ถ„์„ ์ „๋ฌธ๊ฐ€ AI์ž…๋‹ˆ๋‹ค.
3447
  KBO, NPB, ๋งˆ์ด๋„ˆ๋ฆฌ๊ทธ ํˆฌ์ˆ˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์„ํ•˜๊ณ  ์ธ์‚ฌ์ดํŠธ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
 
3452
  """
3453
 
3454
 
3455
+ # โ”€โ”€ Anthropic (Claude) ํ˜ธ์ถœ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3456
  def _call_anthropic(question, context, history):
3457
  import anthropic
3458
+
3459
  client = anthropic.Anthropic(api_key=LLM_API_KEY)
3460
 
3461
+ # user/assistant ๊ต๋Œ€ ๋ฉ”์‹œ์ง€ ๊ตฌ์„ฑ
3462
  messages = []
3463
  for h in (history or [])[-10:]:
3464
+ if h["role"] in ("user", "assistant"):
3465
+ messages.append({"role": h["role"], "content": h["content"]})
3466
+
3467
  messages.append({
3468
  "role": "user",
3469
  "content": f"[๋ฐ์ดํ„ฐ ์ปจํ…์ŠคํŠธ]\n{context}\n\n[์งˆ๋ฌธ]\n{question}",
 
3478
  return resp.content[0].text.strip()
3479
 
3480
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3481
  # โ”€โ”€ ํ†ตํ•ฉ ํ˜ธ์ถœ ํ•จ์ˆ˜ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
3482
  def call_llm_api(question, context="", history=None):
3483
  """
3484
+ Claude API๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
3485
+ API ํ‚ค ๋ฏธ์„ค์ • ์‹œ ๊ทœ์น™ ๊ธฐ๋ฐ˜ ํด๋ฐฑ์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
3486
  """
3487
+ if not LLM_API_KEY:
3488
  return _rule_based_answer(question, context)
3489
 
3490
  try:
3491
+ return _call_anthropic(question, context, history)
3492
+ except ImportError:
3493
+ return (
3494
+ "โš ๏ธ anthropic ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์„ค์น˜๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.\n"
3495
+ "`pip install anthropic` ์„ ์‹คํ–‰ํ•ด ์ฃผ์„ธ์š”."
3496
+ )
 
 
3497
  except Exception as e:
3498
+ return f"โš ๏ธ Claude API ํ˜ธ์ถœ ์˜ค๋ฅ˜: {e}"
3499
 
3500
 
3501
  # โ”€โ”€ ๊ทœ์น™ ๊ธฐ๋ฐ˜ ํด๋ฐฑ (API ํ‚ค ์—†์„ ๋•Œ) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
3502
  def _rule_based_answer(question, context):
3503
  q = question.lower()
3504
 
 
3505
  ctx = {}
3506
  for part in context.split("|"):
3507
  part = part.strip()
 
3509
  k, v = part.split("=", 1)
3510
  ctx[k.strip()] = v.strip()
3511
 
3512
+ pitcher = ctx.get("ํˆฌ์ˆ˜", "ํ•ด๋‹น ํˆฌ์ˆ˜")
3513
+ era = ctx.get("ERA", "โ€”")
3514
+ k9 = ctx.get("K9", "โ€”")
3515
+ bb9 = ctx.get("BB9", "โ€”")
3516
+ whip = ctx.get("WHIP", "โ€”")
3517
+ fip = ctx.get("FIP", "โ€”")
3518
+ spd = ctx.get("ํ‰๊ท ๊ตฌ์†","โ€”")
3519
+ pitches = ctx.get("์ฃผ๋ ฅ ๊ตฌ์ข…","โ€”")
3520
 
3521
  if any(w in q for w in ["๊ตฌ์ข…", "์ฃผ๋ ฅ", "pitch"]):
3522
+ try:
3523
+ k9_val = float(k9)
3524
+ k9_txt = "ํƒˆ์‚ผ์ง„ ๋Šฅ๋ ฅ์ด ์šฐ์ˆ˜ํ•ฉ๋‹ˆ๋‹ค." if k9_val > 8 else "์ œ๊ตฌ๋ ฅ ์ค‘์‹ฌ์˜ ํˆฌ์ˆ˜์ž…๋‹ˆ๋‹ค."
3525
+ except ValueError:
3526
+ k9_txt = "์ œ๊ตฌ๋ ฅ ์ค‘์‹ฌ์˜ ํˆฌ์ˆ˜์ž…๋‹ˆ๋‹ค."
3527
+ return (
3528
+ f"**{pitcher}**์˜ ์ฃผ๋ ฅ ๊ตฌ์ข…์€ **{pitches}** ์ž…๋‹ˆ๋‹ค.\n\n"
3529
+ f"ํ‰๊ท  ๊ตฌ์†์€ **{spd} km/h** ์ด๋ฉฐ, "
3530
+ f"K9={k9}, BB9={bb9} ๊ธฐ์ค€์œผ๋กœ {k9_txt}"
3531
+ )
3532
 
3533
  if any(w in q for w in ["์•ฝ์ ", "์นด์šดํŠธ", "count"]):
3534
+ return (
3535
+ "์นด์šดํŠธ๋ณ„ ์•ฝ์  ๋ถ„์„์€ **์นด์šดํŠธ๋ณ„ ํƒญ**์—์„œ ํžˆํŠธ๋งต์„ ํ™•์ธํ•ด ์ฃผ์„ธ์š”.\n\n"
3536
+ "์ผ๋ฐ˜์ ์œผ๋กœ ๋ณผ์นด์šดํŠธ๊ฐ€ ๋ถˆ๋ฆฌํ•  ๋•Œ(2-0, 3-1) ํŒจ์ŠคํŠธ๋ณผ ๋น„์œจ์ด ๋†’์•„์ง€๋ฏ€๋กœ "
3537
+ "ํƒ€์ž๊ฐ€ ๋…ธ๋ฆฌ๊ธฐ ์‰ฌ์šด ๊ฒฝํ–ฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค."
3538
+ )
3539
 
3540
  if any(w in q for w in ["์ขŒํƒ€", "์šฐํƒ€", "stand", "ํƒ€์ž ์œ ํ˜•"]):
3541
+ return (
3542
+ "์ขŒํƒ€/์šฐํƒ€ ๋ถ„์„์€ **์‚ฌ์ด๋“œ๋ฐ”์˜ ํƒ€์ž ์œ ํ˜• ํ•„ํ„ฐ**๋ฅผ ๋ณ€๊ฒฝํ•˜์—ฌ "
3543
+ "๊ฐ ํƒญ์—์„œ ๋น„๊ตํ•ด ๋ณด์„ธ์š”.\n\n"
3544
+ "๋ฌด๋ธŒ๋จผํŠธ ์ฐจํŠธ์™€ ๋กœ์ผ€์ด์…˜ ์ฐจํŠธ์—์„œ ์œ ํ˜•๋ณ„ ์ฐจ์ด๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค."
3545
+ )
3546
 
3547
  if any(w in q for w in ["๊ตฌ์†", "์†๋„", "speed", "ํŠธ๋ Œ๋“œ"]):
3548
+ return (
3549
+ f"**{pitcher}**์˜ ํ‰๊ท  ๊ตฌ์†์€ **{spd} km/h** ์ž…๋‹ˆ๋‹ค.\n\n"
3550
+ "๊ฒŒ์ž„๋กœ๊ทธ ํƒญ์—์„œ ๋‚ ์งœ๋ณ„ ๊ตฌ์† ์ถ”์ด๋ฅผ ํ™•์ธํ•˜๊ฑฐ๋‚˜, "
3551
+ "๋ฌด๋ธŒ๋จผํŠธ ํƒญ์˜ ๊ตฌ์† vs ๋ฌด๋ธŒ๋จผํŠธ ์Šค์บํ„ฐ ์ฐจํŠธ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”."
3552
+ )
3553
 
3554
  if any(w in q for w in ["๋ฆฌ๊ทธ", "๋น„๊ต", "์ˆœ์œ„", "rank"]):
3555
+ return (
3556
+ f"**{pitcher}** ๋ฆฌ๊ทธ ๋น„๊ต ์ง€ํ‘œ:\n\n"
3557
+ f"- ERA: **{era}**\n"
3558
+ f"- FIP: **{fip}**\n"
3559
+ f"- WHIP: **{whip}**\n"
3560
+ f"- K9: **{k9}**\n"
3561
+ f"- BB9: **{bb9}**\n\n"
3562
+ "์ž์„ธํ•œ ๋ฆฌ๊ทธ ์ˆœ์œ„๋Š” **๋ฆฌ๊ทธ๋น„๊ต ํƒญ**์„ ํ™•์ธํ•ด ์ฃผ์„ธ์š”."
3563
+ )
3564
 
3565
  if any(w in q for w in ["๊ฐ•์ ", "์ž˜ํ•˜", "good", "best"]):
3566
+ return (
3567
+ f"**{pitcher}**์˜ ์ฃผ์š” ์ง€ํ‘œ:\n\n"
3568
+ f"ERA={era}, K9={k9}, BB9={bb9}, WHIP={whip}\n\n"
3569
+ "์ˆ˜์น˜๊ฐ€ ๋‚ฎ์€ ERA/WHIP/BB9์™€ ๋†’์€ K9์ด ๊ฐ•์  ์ง€ํ‘œ์ž…๋‹ˆ๋‹ค. "
3570
+ "๋ฆฌ๊ทธ๋น„๊ต ํƒญ์—์„œ ํ‰๊ท  ๋Œ€๋น„ ์šฐ์œ„ ํ•ญ๋ชฉ์„ ํ™•์ธํ•˜์„ธ์š”."
3571
+ )
3572
+
3573
+ return (
3574
+ f"**{pitcher}** ({ctx.get('์—ฐ๋„','โ€”')}, {ctx.get('๋ฆฌ๊ทธ','โ€”')}) ๊ธฐ๋ณธ ์Šคํƒฏ:\n\n"
3575
+ f"| ์ง€ํ‘œ | ๊ฐ’ |\n|------|----|\n"
3576
+ f"| ERA | {era} |\n"
3577
+ f"| FIP | {fip} |\n"
3578
+ f"| WHIP | {whip} |\n"
3579
+ f"| K9 | {k9} |\n"
3580
+ f"| BB9 | {bb9} |\n"
3581
+ f"| ํ‰๊ท ๊ตฌ์† | {spd} |\n\n"
3582
+ "๋” ๊ตฌ์ฒด์ ์ธ ์งˆ๋ฌธ์„ ํ•ด์ฃผ์‹œ๋ฉด ๋” ์ •ํ™•ํ•œ ๋ถ„์„์„ ๋“œ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜Š"
3583
+ )
3584
 
3585
 
3586
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•