{
 "nbformat": 4,
 "nbformat_minor": 0,
 "metadata": {
  "colab": {
   "provenance": []
  },
  "kernelspec": {
   "name": "python3",
   "display_name": "Python 3"
  },
  "language_info": {
   "name": "python"
  }
 },
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "# 실습 02c · 타로 해석 웹앱 — 상용 API 모델\n\n> **연계 강의자료:** M2 「생성형 AI와 LLM의 이해」 · 3주차\n> **목표:** 같은 앱을 상용 API(Claude)로 구현해 로컬 오픈 모델(02b)과 응답 품질·비용·지연을 비교한다.\n>\n> 실행하려면 API 키가 필요하다(호출마다 비용이 발생하므로 주의한다). 2026-2 기준 모델명은 `claude-opus-5`이며, 최신 Claude 모델은 temperature 파라미터를 받지 않으므로 응답 스타일은 프롬프트로 조절한다.\n"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "b2PhS5cY9M14"
   },
   "outputs": [],
   "source": [
    "!pip install openai"
   ]
  },
  {
   "cell_type": "code",
   "source": [
    "!pip install anthropic"
   ],
   "metadata": {
    "id": "Ao5vhP139WbE"
   },
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "code",
   "source": [
    "!pip install gradio"
   ],
   "metadata": {
    "id": "N0P0LXSJ9Xto"
   },
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "code",
   "source": [
    "import random\n",
    "import gradio as gr\n",
    "from PIL import Image\n",
    "import os\n",
    "import anthropic\n",
    "import openai\n",
    "\n",
    "# Tarot card data (22 Major Arcana cards)\n",
    "tarot_cards = [\n",
    "    {\"name\": \"The Fool\", \"image\": \"fool.jpg\", \"description\": \"새로운 시작, 모험, 순수함, 자유로운 정신\"},\n",
    "    {\"name\": \"The Magician\", \"image\": \"magician.jpg\", \"description\": \"창의성, 기술, 의지력, 집중력\"},\n",
    "    {\"name\": \"The High Priestess\", \"image\": \"high_priestess.jpg\", \"description\": \"직관, 신비, 잠재의식, 내면의 지혜\"},\n",
    "    {\"name\": \"The Empress\", \"image\": \"empress.jpg\", \"description\": \"풍요, 창의성, 모성, 자연\"},\n",
    "    {\"name\": \"The Emperor\", \"image\": \"emperor.jpg\", \"description\": \"권위, 구조, 리더십, 안정\"},\n",
    "    {\"name\": \"The Hierophant\", \"image\": \"hierophant.jpg\", \"description\": \"전통, 교육, 신념 체계, 조언\"},\n",
    "    {\"name\": \"The Lovers\", \"image\": \"lovers.jpg\", \"description\": \"사랑, 조화, 관계, 가치관\"},\n",
    "    {\"name\": \"The Chariot\", \"image\": \"chariot.jpg\", \"description\": \"의지력, 결단력, 승리, 자제력\"},\n",
    "    {\"name\": \"Strength\", \"image\": \"strength.jpg\", \"description\": \"용기, 내면의 힘, 인내, 자신감\"},\n",
    "    {\"name\": \"The Hermit\", \"image\": \"hermit.jpg\", \"description\": \"내면의 성찰, 고독, 지혜, 안내\"},\n",
    "    {\"name\": \"Wheel of Fortune\", \"image\": \"wheel_of_fortune.jpg\", \"description\": \"운명, 기회, 변화, 주기\"},\n",
    "    {\"name\": \"Justice\", \"image\": \"justice.jpg\", \"description\": \"정의, 균형, 진실, 인과관계\"},\n",
    "    {\"name\": \"The Hanged Man\", \"image\": \"hanged_man.jpg\", \"description\": \"희생, 새로운 관점, 중단, 항복\"},\n",
    "    {\"name\": \"Death\", \"image\": \"death.jpg\", \"description\": \"끝, 변화, 변형, 새로운 시작\"},\n",
    "    {\"name\": \"Temperance\", \"image\": \"temperance.jpg\", \"description\": \"균형, 절제, 조화, 중용\"},\n",
    "    {\"name\": \"The Devil\", \"image\": \"devil.jpg\", \"description\": \"속박, 중독, 물질주의, 그림자 자아\"},\n",
    "    {\"name\": \"The Tower\", \"image\": \"tower.jpg\", \"description\": \"급격한 변화, 혼란, 계시, 해방\"},\n",
    "    {\"name\": \"The Star\", \"image\": \"star.jpg\", \"description\": \"희망, 영감, 평온, 갱신\"},\n",
    "    {\"name\": \"The Moon\", \"image\": \"moon.jpg\", \"description\": \"환상, 불확실성, 직관, 잠재의식\"},\n",
    "    {\"name\": \"The Sun\", \"image\": \"sun.jpg\", \"description\": \"기쁨, 성공, 활력, 명료함\"},\n",
    "    {\"name\": \"Judgement\", \"image\": \"judgement.jpg\", \"description\": \"심판, 부활, 내면의 소리, 깨달음\"},\n",
    "    {\"name\": \"The World\", \"image\": \"world.jpg\", \"description\": \"완성, 통합, 성취, 여행\"}\n",
    "]\n",
    "\n",
    "# Initialize Claude API client\n",
    "client = anthropic.Anthropic(api_key=\"api 키를 넣으세요\")  # 환경변수 ANTHROPIC_API_KEY 설정 시 인자 생략 가능\n",
    "\n",
    "# OpenAI API 키 설정\n",
    "# OpenAI 대안 사용 시: openai_client = openai.OpenAI(api_key=\"api 키를 넣으세요\")\n",
    "\n",
    "def get_image_path(image_name):\n",
    "    return os.path.join('tarot_data', image_name)\n",
    "\n",
    "def load_and_resize_image(image_path, is_upright):\n",
    "    try:\n",
    "        img = Image.open(image_path)\n",
    "        img = img.resize((150, 280))  # Resize image\n",
    "        if not is_upright:\n",
    "            img = img.rotate(180)\n",
    "        return img\n",
    "    except Exception as e:\n",
    "        print(f\"이미지 로딩 중 오류 발생: {e}\")\n",
    "        return None\n",
    "\n",
    "def get_claude_interpretation(cards, orientations, interest):\n",
    "    prompt = f\"\"\"\n",
    "    당신은 최고의 타로 점술가입니다. 과거, 현재, 미래의 카드 점을 보고 있으며, 다음 카드들이 선택되었습니다:\n",
    "\n",
    "    과거: {cards[0]['name']} ({orientations[0]})\n",
    "    기본 의미: {cards[0]['description']}\n",
    "\n",
    "    현재: {cards[1]['name']} ({orientations[1]})\n",
    "    기본 의미: {cards[1]['description']}\n",
    "\n",
    "    미래: {cards[2]['name']} ({orientations[2]})\n",
    "    기본 의미: {cards[2]['description']}\n",
    "\n",
    "    관심사: {interest}\n",
    "\n",
    "    카드가 역방향이면 주의해야 할 점이나 개선이 필요한 영역에 초점을 맞추세요.\n",
    "\n",
    "    위 정보를 바탕으로 질문자의 관심사인 '{interest}'에 대한 과거, 현재, 미래의 상황을 각각 200자 내외로 해석해주세요.\n",
    "\n",
    "    마지막으로, 이 세 가지 상황을 연결하여 하나의 일관된 이야기로 풀어내주세요.\n",
    "\n",
    "    답변은 구조화되고 간결해야 하며, 반복을 피해주세요.\n",
    "    \"\"\"\n",
    "\n",
    "    try:\n",
    "        response = client.messages.create(\n",
    "            model=\"claude-opus-5\",   # 2026 현행 모델 (구 claude-3-opus는 2026-01 퇴역)\n",
    "            max_tokens=1000,\n",
    "            messages=[\n",
    "                {\"role\": \"user\", \"content\": prompt}\n",
    "            ]\n",
    "        )\n",
    "        return response.content[0].text\n",
    "        '''\n",
    "            response = openai_client.chat.completions.create(\n",
    "            model=\"gpt-4o-mini\",\n",
    "            messages=[\n",
    "                {\"role\": \"system\", \"content\": \"You are a skilled tarot reader providing insightful interpretations.\"},\n",
    "                {\"role\": \"user\", \"content\": prompt}\n",
    "            ],\n",
    "            max_tokens=1000,\n",
    "        )\n",
    "        return response.choices[0].message.content.strip()\n",
    "        '''\n",
    "    except Exception as e:\n",
    "        print(f\"Claude 해석 생성 중 오류 발생: {e}\")\n",
    "        return f\"과거: {cards[0]['name']} ({orientations[0]}): {cards[0]['description']}.\\n\\n\" \\\n",
    "               f\"현재: {cards[1]['name']} ({orientations[1]}): {cards[1]['description']}.\\n\\n\" \\\n",
    "               f\"미래: {cards[2]['name']} ({orientations[2]}): {cards[2]['description']}.\\n\\n\" \\\n",
    "               f\"이는 당신의 '{interest}'에 대해 시간의 흐름에 따른 변화를 보여줍니다.\"\n",
    "\n",
    "def get_tarot_reading(interest):\n",
    "    if not interest:\n",
    "        return \"관심사나 고민을 입력해주세요.\", None, None, None\n",
    "\n",
    "    selected_cards = random.sample(tarot_cards, 3)\n",
    "    orientations = [random.choice([\"정방향\", \"역방향\"]) for _ in range(3)]\n",
    "\n",
    "    interpretation = get_claude_interpretation(selected_cards, orientations, interest)\n",
    "\n",
    "    images = [load_and_resize_image(get_image_path(card['image']), orientation == \"정방향\")\n",
    "              for card, orientation in zip(selected_cards, orientations)]\n",
    "\n",
    "    return interpretation, images[0], images[1], images[2]\n",
    "\n",
    "# Gradio interface setup\n",
    "with gr.Blocks() as iface:\n",
    "    gr.Markdown(\"# LLM 기반 타로 카드 해석\")\n",
    "    gr.Markdown(\"당신의 관심사나 고민을 입력하면, 3장의 카드를 자동으로 선택하여 해석해드립니다.\")\n",
    "\n",
    "    interest = gr.Textbox(label=\"당신의 관심사나 고민을 입력해주세요\")\n",
    "    submit_button = gr.Button(\"타로 해석 받기\")\n",
    "\n",
    "    with gr.Row():\n",
    "        past_card = gr.Image(label=\"과거\")\n",
    "        present_card = gr.Image(label=\"현재\")\n",
    "        future_card = gr.Image(label=\"미래\")\n",
    "\n",
    "    interpretation = gr.Textbox(label=\"타로 해석\", lines=10)\n",
    "\n",
    "    submit_button.click(\n",
    "        get_tarot_reading,\n",
    "        inputs=[interest],\n",
    "        outputs=[interpretation, past_card, present_card, future_card],\n",
    "    )\n",
    "\n",
    "iface.launch(share=True)"
   ],
   "metadata": {
    "id": "nsR_ILnt9cGh"
   },
   "execution_count": null,
   "outputs": []
  }
 ]
}