Upload folder using huggingface_hub
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .DS_Store +0 -0
- .gitattributes +5 -0
- .gitignore +5 -0
- README.md +33 -7
- create_character/Harry_Potter_summary.txt +24 -0
- create_character/Solar_Character_Development.ipynb +217 -0
- create_character/gradio.py +115 -0
- create_character/personal_profile.py +119 -0
- create_character/prsonal_profile(gradio).py +170 -0
- create_world/creator.py +113 -0
- create_world/creator_gradio.py +100 -0
- create_world/generator.py +13 -0
- create_world/prompt.py +38 -0
- create_world/prompt.yaml +44 -0
- create_world/utils.py +20 -0
- demo.py +217 -0
- example/prompt.ipynb +162 -0
- harrypotter_scenario/scenario_1/Scenario_1.txt +7 -0
- harrypotter_scenario/scenario_1/Scenario_1_KR.txt +7 -0
- harrypotter_scenario/scenario_1/sc1_image/Scenario_1_Storyline_1.png +3 -0
- harrypotter_scenario/scenario_1/sc1_image/Scenario_1_Storyline_2.png +3 -0
- harrypotter_scenario/scenario_1/sc1_image/Scenario_1_Storyline_3.png +3 -0
- harrypotter_scenario/scenario_1/sc1_image/Scenario_1_Storyline_4.png +3 -0
- harrypotter_scenario/scenario_1/sc1_image/Scenario_1_Storyline_5.png +3 -0
- harrypotter_scenario/scenario_1/sc1_storyline/Scenario_1_Storyline_1.txt +12 -0
- harrypotter_scenario/scenario_1/sc1_storyline/Scenario_1_Storyline_2.txt +12 -0
- harrypotter_scenario/scenario_1/sc1_storyline/Scenario_1_Storyline_3.txt +12 -0
- harrypotter_scenario/scenario_1/sc1_storyline/Scenario_1_Storyline_4.txt +12 -0
- harrypotter_scenario/scenario_1/sc1_storyline/Scenario_1_Storyline_5.txt +12 -0
- harrypotter_scenario/scenario_1/sc1_storyline_ko/Scenario_1_Storyline_1_KR.txt +12 -0
- harrypotter_scenario/scenario_1/sc1_storyline_ko/Scenario_1_Storyline_2_KR.txt +12 -0
- harrypotter_scenario/scenario_1/sc1_storyline_ko/Scenario_1_Storyline_3_KR.txt +12 -0
- harrypotter_scenario/scenario_1/sc1_storyline_ko/Scenario_1_Storyline_4_KR.txt +12 -0
- harrypotter_scenario/scenario_1/sc1_storyline_ko/Scenario_1_Storyline_5_KR.txt +12 -0
- harrypotter_scenario/scenario_2/Scenario_2.txt +7 -0
- harrypotter_scenario/scenario_2/Scenario_2_KR.txt +7 -0
- harrypotter_scenario/scenario_3/Scenario_3.txt +7 -0
- harrypotter_scenario/scenario_3/Scenario_3_KR.txt +7 -0
- harrypotter_scenario/scenario_4/Scenario_4.txt +7 -0
- harrypotter_scenario/scenario_4/Scenario_4_KR.txt +7 -0
- harrypotter_scenario/scenario_5/Scenario_5.txt +7 -0
- harrypotter_scenario/scenario_5/Scenario_5_KR.txt +7 -0
- harrypotter_scenario/world_summary.txt +23 -0
- main.py +47 -0
- play_game/config.py +1 -0
- play_game/formatter.py +25 -0
- play_game/main.py +520 -0
- prototype/app.py +21 -0
- prototype/conversation.py +515 -0
- prototype/demo/app.py +18 -0
.DS_Store
ADDED
Binary file (8.2 kB). View file
|
|
.gitattributes
CHANGED
@@ -33,3 +33,8 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
36 |
+
harrypotter_scenario/scenario_1/sc1_image/Scenario_1_Storyline_1.png filter=lfs diff=lfs merge=lfs -text
|
37 |
+
harrypotter_scenario/scenario_1/sc1_image/Scenario_1_Storyline_2.png filter=lfs diff=lfs merge=lfs -text
|
38 |
+
harrypotter_scenario/scenario_1/sc1_image/Scenario_1_Storyline_3.png filter=lfs diff=lfs merge=lfs -text
|
39 |
+
harrypotter_scenario/scenario_1/sc1_image/Scenario_1_Storyline_4.png filter=lfs diff=lfs merge=lfs -text
|
40 |
+
harrypotter_scenario/scenario_1/sc1_image/Scenario_1_Storyline_5.png filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Ignore virtual environment
|
2 |
+
.venv/
|
3 |
+
.env
|
4 |
+
__pycache__/
|
5 |
+
dummy/
|
README.md
CHANGED
@@ -1,12 +1,38 @@
|
|
1 |
---
|
2 |
title: LRPG
|
3 |
-
|
4 |
-
colorFrom: red
|
5 |
-
colorTo: red
|
6 |
sdk: gradio
|
7 |
-
sdk_version: 4.
|
8 |
-
app_file: app.py
|
9 |
-
pinned: false
|
10 |
---
|
|
|
11 |
|
12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
---
|
2 |
title: LRPG
|
3 |
+
app_file: demo.py
|
|
|
|
|
4 |
sdk: gradio
|
5 |
+
sdk_version: 4.37.2
|
|
|
|
|
6 |
---
|
7 |
+
# lRPG (LLM RPG)
|
8 |
|
9 |
+
This project is working in process.
|
10 |
+
|
11 |
+
### Introduction
|
12 |
+
|
13 |
+
You can enjoy your own rpg on your own world. You can make any decision, then llm game master will handle them. It will support other world and custom world features asap.
|
14 |
+
|
15 |
+
This demo version can be played in shell, and we will adopt UI as soon as possible.
|
16 |
+
|
17 |
+
Demo version is playing on harry potter fictional universe.
|
18 |
+
|
19 |
+
|
20 |
+
## 1. Structure
|
21 |
+
|
22 |
+
Consist of
|
23 |
+
1. World Preparation
|
24 |
+
2. Character Preparation
|
25 |
+
3. Game Play
|
26 |
+
|
27 |
+
## 2. Stack
|
28 |
+
|
29 |
+
1. Solar
|
30 |
+
2. Graph db (WIP)
|
31 |
+
|
32 |
+
### 3. How to play
|
33 |
+
|
34 |
+
clone this repository and execute main file.
|
35 |
+
|
36 |
+
```
|
37 |
+
python main.py
|
38 |
+
```
|
create_character/Harry_Potter_summary.txt
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Harry Potter summary
|
2 |
+
|
3 |
+
1. Harry Potter and the Philosopher's Stone (Sorcerer's Stone in the U.S.):
|
4 |
+
- Harry Potter, an orphaned boy living with his cruel aunt and uncle, discovers on his 11th birthday that he is a wizard. He is invited to attend Hogwarts School of Witchcraft and Wizardry. At Hogwarts, he makes friends, including Ron Weasley and Hermione Granger, and learns about his past and the dark wizard Voldemort, who killed his parents. Harry, Ron, and Hermione prevent Voldemort from stealing the Philosopher's Stone, a magical object that grants immortality.
|
5 |
+
|
6 |
+
2. Harry Potter and the Chamber of Secrets:
|
7 |
+
- In his second year at Hogwarts, Harry encounters a series of mysterious attacks on students, caused by the opening of the Chamber of Secrets. He discovers that a memory of Voldemort, preserved in a diary, is controlling a giant serpent (the basilisk) to attack students. With Ron and Hermione's help, Harry defeats the basilisk and destroys the diary.
|
8 |
+
|
9 |
+
3. Harry Potter and the Prisoner of Azkaban:
|
10 |
+
- Harry learns that Sirius Black, an escaped prisoner believed to have betrayed his parents to Voldemort, is after him. Over the course of the school year, Harry uncovers that Sirius is actually his godfather and was framed for the betrayal by Peter Pettigrew, who is still at large. With the help of Hermione's time-turner, Harry and Hermione save Sirius and a magical creature called Buckbeak from execution.
|
11 |
+
|
12 |
+
4. Harry Potter and the Goblet of Fire:
|
13 |
+
- Harry is unexpectedly entered into the Triwizard Tournament, a dangerous magical competition. He competes against students from other magical schools and faces numerous life-threatening challenges. In the final task, he and Cedric Diggory are transported to a graveyard where Voldemort is resurrected. Cedric is killed, and Harry barely escapes with his life.
|
14 |
+
|
15 |
+
5. Harry Potter and the Order of the Phoenix:
|
16 |
+
- As Voldemort gains power, the wizarding world is in denial about his return. Harry forms "Dumbledore's Army," a secret group to teach his friend’s defensive magic. The Ministry of Magic tries to discredit Harry and Dumbledore. By the end of the book, a battle occurs at the Ministry, resulting in the death of Sirius Black and the revelation of Voldemort's return to the public.
|
17 |
+
|
18 |
+
6. Harry Potter and the Half-Blood Prince:
|
19 |
+
- Dumbledore takes Harry on a journey through memories to learn about Voldemort's past and his Horcruxes, objects containing pieces of Voldemort's soul that make him immortal. Draco Malfoy is given a dangerous mission by Voldemort. The book ends with a climactic battle at Hogwarts and the death of Dumbledore at the hands of Severus Snape, whom Harry believed to be a trusted ally.
|
20 |
+
|
21 |
+
7. Harry Potter and the Deathly Hallows:
|
22 |
+
- Harry, Ron, and Hermione set out to destroy the Horcruxes and defeat Voldemort. They uncover the legend of the Deathly Hallows, three powerful magical objects. The story culminates in the Battle of Hogwarts, where many characters fight bravely and several die. Harry learns that he is an unintended Horcrux and sacrifices himself, but he survives. In the final showdown, Harry defeats Voldemort once and for all.
|
23 |
+
|
24 |
+
The series ends with an epilogue set 19 years later, where Harry and his friends are grown up and sending their own children to Hogwarts. The story emphasizes themes of friendship, bravery, and the fight against evil.
|
create_character/Solar_Character_Development.ipynb
ADDED
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"nbformat": 4,
|
3 |
+
"nbformat_minor": 0,
|
4 |
+
"metadata": {
|
5 |
+
"colab": {
|
6 |
+
"provenance": []
|
7 |
+
},
|
8 |
+
"kernelspec": {
|
9 |
+
"name": "python3",
|
10 |
+
"display_name": "Python 3"
|
11 |
+
},
|
12 |
+
"language_info": {
|
13 |
+
"name": "python"
|
14 |
+
}
|
15 |
+
},
|
16 |
+
"cells": [
|
17 |
+
{
|
18 |
+
"cell_type": "code",
|
19 |
+
"execution_count": 1,
|
20 |
+
"metadata": {
|
21 |
+
"colab": {
|
22 |
+
"base_uri": "https://localhost:8080/"
|
23 |
+
},
|
24 |
+
"id": "2bvOXqHoa8fo",
|
25 |
+
"outputId": "1ad82e02-6539-4b37-8baf-e12e1db315ce"
|
26 |
+
},
|
27 |
+
"outputs": [
|
28 |
+
{
|
29 |
+
"output_type": "stream",
|
30 |
+
"name": "stdout",
|
31 |
+
"text": [
|
32 |
+
" Preparing metadata (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
|
33 |
+
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m50.4/50.4 kB\u001b[0m \u001b[31m440.1 kB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
34 |
+
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m377.3/377.3 kB\u001b[0m \u001b[31m11.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
35 |
+
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m47.1/47.1 kB\u001b[0m \u001b[31m2.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
36 |
+
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m295.8/295.8 kB\u001b[0m \u001b[31m13.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
37 |
+
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m139.8/139.8 kB\u001b[0m \u001b[31m9.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
38 |
+
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m337.0/337.0 kB\u001b[0m \u001b[31m17.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
39 |
+
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.1/1.1 MB\u001b[0m \u001b[31m29.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
40 |
+
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m75.6/75.6 kB\u001b[0m \u001b[31m5.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
41 |
+
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m77.9/77.9 kB\u001b[0m \u001b[31m5.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
42 |
+
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m141.1/141.1 kB\u001b[0m \u001b[31m8.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
43 |
+
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m58.3/58.3 kB\u001b[0m \u001b[31m2.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
|
44 |
+
"\u001b[?25h Building wheel for clipboard (setup.py) ... \u001b[?25l\u001b[?25hdone\n"
|
45 |
+
]
|
46 |
+
}
|
47 |
+
],
|
48 |
+
"source": [
|
49 |
+
"! pip3 install -qU markdownify langchain-upstage rank_bm25 getpass4"
|
50 |
+
]
|
51 |
+
},
|
52 |
+
{
|
53 |
+
"cell_type": "code",
|
54 |
+
"source": [
|
55 |
+
"import os\n",
|
56 |
+
"import getpass\n",
|
57 |
+
"import warnings\n",
|
58 |
+
"warnings.filterwarnings(\"ignore\")\n",
|
59 |
+
"\n",
|
60 |
+
"UPSTAGE_API_KEY = getpass.getpass('Enter your API Key')\n",
|
61 |
+
"_ = os.environ.setdefault(\"UPSTAGE_API_KEY\", UPSTAGE_API_KEY)"
|
62 |
+
],
|
63 |
+
"metadata": {
|
64 |
+
"colab": {
|
65 |
+
"base_uri": "https://localhost:8080/"
|
66 |
+
},
|
67 |
+
"id": "2iCnlIWUbJ7H",
|
68 |
+
"outputId": "94843282-0e8d-4d7c-ba04-5b461422e9f9"
|
69 |
+
},
|
70 |
+
"execution_count": 2,
|
71 |
+
"outputs": [
|
72 |
+
{
|
73 |
+
"name": "stdout",
|
74 |
+
"output_type": "stream",
|
75 |
+
"text": [
|
76 |
+
"Enter your API Key··········\n"
|
77 |
+
]
|
78 |
+
}
|
79 |
+
]
|
80 |
+
},
|
81 |
+
{
|
82 |
+
"cell_type": "code",
|
83 |
+
"source": [
|
84 |
+
"with open(\"/content/Harry_Potter_summary.txt\", \"r\", encoding=\"utf-8\") as file:\n",
|
85 |
+
" novel = file.read()"
|
86 |
+
],
|
87 |
+
"metadata": {
|
88 |
+
"id": "r5ViKrajcj7P"
|
89 |
+
},
|
90 |
+
"execution_count": 13,
|
91 |
+
"outputs": []
|
92 |
+
},
|
93 |
+
{
|
94 |
+
"cell_type": "code",
|
95 |
+
"source": [
|
96 |
+
"print(novel[:1000])"
|
97 |
+
],
|
98 |
+
"metadata": {
|
99 |
+
"colab": {
|
100 |
+
"base_uri": "https://localhost:8080/"
|
101 |
+
},
|
102 |
+
"id": "HwIdL3NMcxID",
|
103 |
+
"outputId": "539b5d57-d994-4b21-b3d6-d9b9c4f76fc2"
|
104 |
+
},
|
105 |
+
"execution_count": 15,
|
106 |
+
"outputs": [
|
107 |
+
{
|
108 |
+
"output_type": "stream",
|
109 |
+
"name": "stdout",
|
110 |
+
"text": [
|
111 |
+
"Harry Potter summary\n",
|
112 |
+
"\n",
|
113 |
+
"1. Harry Potter and the Philosopher's Stone (Sorcerer's Stone in the U.S.):\n",
|
114 |
+
" - Harry Potter, an orphaned boy living with his cruel aunt and uncle, discovers on his 11th birthday that he is a wizard. He is invited to attend Hogwarts School of Witchcraft and Wizardry. At Hogwarts, he makes friends, including Ron Weasley and Hermione Granger, and learns about his past and the dark wizard Voldemort, who killed his parents. Harry, Ron, and Hermione prevent Voldemort from stealing the Philosopher's Stone, a magical object that grants immortality.\n",
|
115 |
+
"\n",
|
116 |
+
"2. Harry Potter and the Chamber of Secrets:\n",
|
117 |
+
" - In his second year at Hogwarts, Harry encounters a series of mysterious attacks on students, caused by the opening of the Chamber of Secrets. He discovers that a memory of Voldemort, preserved in a diary, is controlling a giant serpent (the basilisk) to attack students. With Ron and Hermione's help, Harry defeats the basilisk and destroys the diary.\n",
|
118 |
+
"\n",
|
119 |
+
"3. Harry Potter and the Pr\n"
|
120 |
+
]
|
121 |
+
}
|
122 |
+
]
|
123 |
+
},
|
124 |
+
{
|
125 |
+
"cell_type": "code",
|
126 |
+
"source": [
|
127 |
+
"from langchain_core.prompts import PromptTemplate\n",
|
128 |
+
"from langchain_core.output_parsers import StrOutputParser\n",
|
129 |
+
"from langchain_upstage import ChatUpstage\n",
|
130 |
+
"\n",
|
131 |
+
"llm = ChatUpstage()\n",
|
132 |
+
"\n",
|
133 |
+
"prompt_template = PromptTemplate.from_template(\n",
|
134 |
+
" \"\"\"\n",
|
135 |
+
" Based on the novel context provided and considering the player's personal traits: gender {gender}, MBTI personality {mbti}, hobbies {hobbies},\n",
|
136 |
+
" create a detailed character for a fantasy tabletop RPG. Provide the character's name, gender, age, race and unique abilities.\n",
|
137 |
+
" Specify the following exact 4 skills with values from 1 to 100 reflecting their capabilities:\n",
|
138 |
+
" - Physical Strength\n",
|
139 |
+
" - Intelligence\n",
|
140 |
+
" - Combat Power\n",
|
141 |
+
" - Agility\n",
|
142 |
+
" Use creativity to infer and create a character that would fit within the universe described in the context, aligning with the player's traits.\n",
|
143 |
+
" ---\n",
|
144 |
+
" Novel Context: {Context}\n",
|
145 |
+
" \"\"\"\n",
|
146 |
+
")\n",
|
147 |
+
"chain = prompt_template | llm | StrOutputParser()\n"
|
148 |
+
],
|
149 |
+
"metadata": {
|
150 |
+
"id": "ipyJ9lRobCMF"
|
151 |
+
},
|
152 |
+
"execution_count": 11,
|
153 |
+
"outputs": []
|
154 |
+
},
|
155 |
+
{
|
156 |
+
"cell_type": "code",
|
157 |
+
"source": [
|
158 |
+
"# Example Context from a novel\n",
|
159 |
+
"novel_context = novel\n",
|
160 |
+
"\n",
|
161 |
+
"# Your gender, MBTI type, and hobbies (input actual values relevant to you)\n",
|
162 |
+
"my_gender = \"Male\"\n",
|
163 |
+
"my_mbti = \"INTJ\"\n",
|
164 |
+
"my_hobbies = \"strategy games, history, and hiking\"\n",
|
165 |
+
"\n",
|
166 |
+
"# Invocation of the chain to generate an RPG character\n",
|
167 |
+
"response = chain.invoke({\n",
|
168 |
+
" \"Context\": novel_context,\n",
|
169 |
+
" \"gender\": my_gender,\n",
|
170 |
+
" \"mbti\": my_mbti,\n",
|
171 |
+
" \"hobbies\": my_hobbies\n",
|
172 |
+
"})\n",
|
173 |
+
"\n",
|
174 |
+
"# Print the response to see the detailed RPG character attributes\n",
|
175 |
+
"print(response)\n"
|
176 |
+
],
|
177 |
+
"metadata": {
|
178 |
+
"colab": {
|
179 |
+
"base_uri": "https://localhost:8080/"
|
180 |
+
},
|
181 |
+
"id": "z0__GWNjbDue",
|
182 |
+
"outputId": "89bf0c8b-8081-4cc9-a271-d54b15205946"
|
183 |
+
},
|
184 |
+
"execution_count": 16,
|
185 |
+
"outputs": [
|
186 |
+
{
|
187 |
+
"output_type": "stream",
|
188 |
+
"name": "stdout",
|
189 |
+
"text": [
|
190 |
+
"Character Name: Alaric Thorne\n",
|
191 |
+
"\n",
|
192 |
+
"Gender: Male\n",
|
193 |
+
"Age: 18\n",
|
194 |
+
"Race: Half-Blood Wizard (Muggle-born)\n",
|
195 |
+
"\n",
|
196 |
+
"Unique Abilities:\n",
|
197 |
+
"\n",
|
198 |
+
"1. Legilimency: Alaric has a natural talent for Legilimency, the magical practice of reading thoughts and memories. This ability has been enhanced by his INTJ personality, making him a formidable opponent in mind games and a valuable asset in solving complex problems.\n",
|
199 |
+
"\n",
|
200 |
+
"2. Strategic Combat: Alaric's love for strategy games has translated into a unique combat style. He is able to anticipate his opponent's moves and counter them effectively, making him a dangerous opponent in duels.\n",
|
201 |
+
"\n",
|
202 |
+
"3. Enhanced Memory: Alaric's love for history has given him an enhanced memory, allowing him to recall information quickly and accurately when needed.\n",
|
203 |
+
"\n",
|
204 |
+
"Skills:\n",
|
205 |
+
"\n",
|
206 |
+
"1. Physical Strength: 60\n",
|
207 |
+
"2. Intelligence: 90\n",
|
208 |
+
"3. Combat Power: 75\n",
|
209 |
+
"4. Agility: 80\n",
|
210 |
+
"\n",
|
211 |
+
"Alaric Thorne is a thoughtful and strategic wizard, who prefers to think his way out of problems rather than relying solely on magic. He is often found in the library, studying ancient texts and learning about the magical world's history. His love for strategy games has made him a skilled dueler, and his natural talent for Legilimency allows him to outsmart his opponents. Despite his introverted nature, Alaric has a strong sense of loyalty to his friends and will do whatever it takes to protect them.\n"
|
212 |
+
]
|
213 |
+
}
|
214 |
+
]
|
215 |
+
}
|
216 |
+
]
|
217 |
+
}
|
create_character/gradio.py
ADDED
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import re, json, ast
|
2 |
+
from langchain_core.prompts import PromptTemplate
|
3 |
+
from langchain_core.output_parsers import StrOutputParser
|
4 |
+
from langchain_upstage import ChatUpstage
|
5 |
+
|
6 |
+
def generate_character_creation_questions(world_summary, language="korean"):
|
7 |
+
llm = ChatUpstage()
|
8 |
+
questions_prompt_template = PromptTemplate.from_template(
|
9 |
+
"""
|
10 |
+
Based on the following novel context, generate five simple questions that a player should answer to help create a deeply personalized character for a fantasy RPG.
|
11 |
+
The questions should explore different aspects of a potential character’s personality, background, and capabilities.
|
12 |
+
For users who are not used to the story I want you to give several choices in the question.
|
13 |
+
Write five questions in Korean(한글).
|
14 |
+
Your response should be python list with strings consist of questions.
|
15 |
+
---
|
16 |
+
Novel Context: {Context}
|
17 |
+
---
|
18 |
+
Format: ['당신은 ...? (답변의 예시: ...): ', ...]
|
19 |
+
---
|
20 |
+
Questions:
|
21 |
+
"""
|
22 |
+
)
|
23 |
+
questions_chain = questions_prompt_template | llm | StrOutputParser()
|
24 |
+
|
25 |
+
default_questions = [
|
26 |
+
"당신의 캐릭터 이름은 무엇인가요? (스킵할시 자동으로 이름이 생성됩니다): ",
|
27 |
+
"당신의 캐릭터의 성별은 무엇인가요? (스킵할시 자동으로 성별이 선택됩니다): ",
|
28 |
+
"당신의 캐릭터는 몇살인가요? (스킵할시 자동으로 나이가 선택됩니다): ",
|
29 |
+
]
|
30 |
+
while(True):
|
31 |
+
try:
|
32 |
+
questions = questions_chain.invoke({ "Context": world_summary })
|
33 |
+
questions_list = ast.literal_eval(questions)
|
34 |
+
questions_list = [question.replace('[', '').replace(']', '') for question in questions_list]
|
35 |
+
return default_questions + questions_list
|
36 |
+
except:
|
37 |
+
continue
|
38 |
+
|
39 |
+
|
40 |
+
|
41 |
+
def create_character_profile(questions, answers, language="korean"):
|
42 |
+
llm = ChatUpstage()
|
43 |
+
character_creation_prompt_template = PromptTemplate.from_template(
|
44 |
+
"""
|
45 |
+
Using the answers provided below, create a detailed character profile for a fantasy tabletop RPG.
|
46 |
+
The character should reflect the given attributes. Please ensure to use the specified field names and format in your output consistently.
|
47 |
+
Consider Player's answers as the input and generate a character profile based on the given context.
|
48 |
+
When player's answer is not given, please use a appropriate value for corresponding fields made by yourself.
|
49 |
+
|
50 |
+
**Output should strictly follow this format:**
|
51 |
+
|
52 |
+
- name: "character name"
|
53 |
+
- gender: "Male" or "Female"
|
54 |
+
- age: age on integer
|
55 |
+
- race: "character race"
|
56 |
+
- job: "character job"
|
57 |
+
- stamina: integer between [1, 100]
|
58 |
+
- intelligence: integer between [1, 100]
|
59 |
+
- combat_power: integer between [1, 100]
|
60 |
+
- agility: integer between [1, 100]
|
61 |
+
- background: "character background"
|
62 |
+
|
63 |
+
**Player Answers:**
|
64 |
+
qna:
|
65 |
+
{qna}
|
66 |
+
---
|
67 |
+
Please write the output in English.
|
68 |
+
"""
|
69 |
+
)
|
70 |
+
character_creation_chain = character_creation_prompt_template | llm | StrOutputParser()
|
71 |
+
qna = [f"Q: {questions[i]}\nA: {answers[i]}" for i in range(len(questions))]
|
72 |
+
character_description = character_creation_chain.invoke({ "qna": qna, "Language": language})
|
73 |
+
return character_description
|
74 |
+
|
75 |
+
def parse_character_data_to_json(text):
|
76 |
+
fields = {
|
77 |
+
'name': r"- name: (.*)",
|
78 |
+
'gender': r"- gender: (.*)",
|
79 |
+
'age': r"- age: (\d+)",
|
80 |
+
'race': r"- race: (.*)",
|
81 |
+
'job': r"- job: (.*)",
|
82 |
+
'background': r"- background: (.*)"
|
83 |
+
}
|
84 |
+
param_fields = {
|
85 |
+
'stamina': r"- stamina: (\d+)",
|
86 |
+
'intelligence': r"- intelligence: (\d+)",
|
87 |
+
'combat_power': r"- combat_power: (\d+)",
|
88 |
+
'agility': r"- agility: (\d+)"
|
89 |
+
}
|
90 |
+
character_dict = {}
|
91 |
+
params_dict = {}
|
92 |
+
for key, pattern in fields.items():
|
93 |
+
match = re.search(pattern, text)
|
94 |
+
if match:
|
95 |
+
character_dict[key] = match.group(1)
|
96 |
+
for key, pattern in param_fields.items():
|
97 |
+
match = re.search(pattern, text)
|
98 |
+
if match:
|
99 |
+
params_dict[key] = int(match.group(1))
|
100 |
+
character_dict['params'] = params_dict
|
101 |
+
# return json.dumps(character_dict, indent=4, ensure_ascii=False)
|
102 |
+
return character_dict
|
103 |
+
|
104 |
+
def main():
|
105 |
+
with open("/Users/hyunkoolee/upstage_kai_llm/llm_dnd/harrypotter_scenario/world_summary.txt", "r", encoding="utf-8") as file:
|
106 |
+
world_summary = file.read()
|
107 |
+
|
108 |
+
# 질문 준비
|
109 |
+
questions = generate_character_creation_questions(world_summary)
|
110 |
+
character_description = create_character_profile(questions)
|
111 |
+
character_json = parse_character_data_to_json(character_description)
|
112 |
+
print(character_json)
|
113 |
+
|
114 |
+
if __name__ == "__main__":
|
115 |
+
main()
|
create_character/personal_profile.py
ADDED
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import re, json, ast
|
2 |
+
from langchain_core.prompts import PromptTemplate
|
3 |
+
from langchain_core.output_parsers import StrOutputParser
|
4 |
+
from langchain_upstage import ChatUpstage
|
5 |
+
|
6 |
+
def generate_character_creation_questions(world_summary, language="korean"):
|
7 |
+
llm = ChatUpstage()
|
8 |
+
questions_prompt_template = PromptTemplate.from_template(
|
9 |
+
"""
|
10 |
+
Based on the following novel context, generate five simple questions that a player should answer to help create a deeply personalized character for a fantasy RPG.
|
11 |
+
The questions should explore different aspects of a potential character’s personality, background, and capabilities.
|
12 |
+
For users who are not used to the story I want you to give several choices in the question.
|
13 |
+
Write five questions in Korean(한글).
|
14 |
+
Your response should be python list with strings consist of questions.
|
15 |
+
---
|
16 |
+
Novel Context: {Context}
|
17 |
+
---
|
18 |
+
Format: ['당신은 ...? (답변의 예시: ...): ', ...]
|
19 |
+
---
|
20 |
+
Questions:
|
21 |
+
"""
|
22 |
+
)
|
23 |
+
questions_chain = questions_prompt_template | llm | StrOutputParser()
|
24 |
+
|
25 |
+
default_questions = [
|
26 |
+
"당신의 캐릭터 이름은 무엇인가요? (엔터로 스킵할시 자동으로 이름이 생성됩니다): ",
|
27 |
+
"당신의 캐릭터의 성별은 무엇인가요? (엔터로 스킵할시 자동으로 성별이 선택됩니다): ",
|
28 |
+
"당신의 캐릭터는 몇살인가요? (엔터로 스킵할시 자동으로 나이가 선택됩니다): ",
|
29 |
+
]
|
30 |
+
while(True):
|
31 |
+
try:
|
32 |
+
questions = questions_chain.invoke({ "Context": world_summary })
|
33 |
+
questions_list = ast.literal_eval(questions)
|
34 |
+
questions_list = [question.replace('[', '').replace(']', '') for question in questions_list]
|
35 |
+
return default_questions + questions_list
|
36 |
+
except:
|
37 |
+
continue
|
38 |
+
|
39 |
+
|
40 |
+
|
41 |
+
def create_character_profile(questions, language="korean"):
|
42 |
+
llm = ChatUpstage()
|
43 |
+
character_creation_prompt_template = PromptTemplate.from_template(
|
44 |
+
"""
|
45 |
+
Using the answers provided below, create a detailed character profile for a fantasy tabletop RPG.
|
46 |
+
The character should reflect the given attributes. Please ensure to use the specified field names and format in your output consistently.
|
47 |
+
Consider Player's answers as the input and generate a character profile based on the given context.
|
48 |
+
When player's answer is not given, please use a appropriate value for corresponding fields made by yourself.
|
49 |
+
|
50 |
+
**Output should strictly follow this format:**
|
51 |
+
|
52 |
+
- name: "character name"
|
53 |
+
- gender: "Male" or "Female"
|
54 |
+
- age: age on integer
|
55 |
+
- race: "character race"
|
56 |
+
- job: "character job"
|
57 |
+
- stamina: integer between [1, 100]
|
58 |
+
- intelligence: integer between [1, 100]
|
59 |
+
- combat_power: integer between [1, 100]
|
60 |
+
- agility: integer between [1, 100]
|
61 |
+
- background: "character background"
|
62 |
+
|
63 |
+
**Player Answers:**
|
64 |
+
qna:
|
65 |
+
{qna}
|
66 |
+
---
|
67 |
+
Please write the output in English.
|
68 |
+
"""
|
69 |
+
)
|
70 |
+
character_creation_chain = character_creation_prompt_template | llm | StrOutputParser()
|
71 |
+
answers = []
|
72 |
+
for i, question in enumerate(questions):
|
73 |
+
print(f"Question {i+1}: {question}")
|
74 |
+
answers.append(input("Your answer: "))
|
75 |
+
qna = [f"Q: {questions[i]}\nA: {answers[i]}" for i in range(len(questions))]
|
76 |
+
character_description = character_creation_chain.invoke({ "qna": qna, "Language": language})
|
77 |
+
return character_description
|
78 |
+
|
79 |
+
def parse_character_data_to_json(text):
|
80 |
+
fields = {
|
81 |
+
'name': r"- name: (.*)",
|
82 |
+
'gender': r"- gender: (.*)",
|
83 |
+
'age': r"- age: (\d+)",
|
84 |
+
'race': r"- race: (.*)",
|
85 |
+
'job': r"- job: (.*)",
|
86 |
+
'background': r"- background: (.*)"
|
87 |
+
}
|
88 |
+
param_fields = {
|
89 |
+
'stamina': r"- stamina: (\d+)",
|
90 |
+
'intelligence': r"- intelligence: (\d+)",
|
91 |
+
'combat_power': r"- combat_power: (\d+)",
|
92 |
+
'agility': r"- agility: (\d+)"
|
93 |
+
}
|
94 |
+
character_dict = {}
|
95 |
+
params_dict = {}
|
96 |
+
for key, pattern in fields.items():
|
97 |
+
match = re.search(pattern, text)
|
98 |
+
if match:
|
99 |
+
character_dict[key] = match.group(1)
|
100 |
+
for key, pattern in param_fields.items():
|
101 |
+
match = re.search(pattern, text)
|
102 |
+
if match:
|
103 |
+
params_dict[key] = int(match.group(1))
|
104 |
+
character_dict['params'] = params_dict
|
105 |
+
# return json.dumps(character_dict, indent=4, ensure_ascii=False)
|
106 |
+
return character_dict
|
107 |
+
|
108 |
+
def main():
|
109 |
+
with open("/Users/hyunkoolee/upstage_kai_llm/llm_dnd/harrypotter_scenario/world_summary.txt", "r", encoding="utf-8") as file:
|
110 |
+
world_summary = file.read()
|
111 |
+
|
112 |
+
# 질문 준비
|
113 |
+
questions = generate_character_creation_questions(world_summary)
|
114 |
+
character_description = create_character_profile(questions)
|
115 |
+
character_json = parse_character_data_to_json(character_description)
|
116 |
+
print(character_json)
|
117 |
+
|
118 |
+
if __name__ == "__main__":
|
119 |
+
main()
|
create_character/prsonal_profile(gradio).py
ADDED
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import re
|
2 |
+
import json
|
3 |
+
import ast
|
4 |
+
import requests
|
5 |
+
import gradio as gr
|
6 |
+
from langchain_core.prompts import PromptTemplate
|
7 |
+
from langchain_core.output_parsers import StrOutputParser
|
8 |
+
from langchain_upstage import ChatUpstage
|
9 |
+
|
10 |
+
def generate_character_creation_questions(api_key, world_summary, language="korean"):
|
11 |
+
llm = ChatUpstage(api_key=api_key)
|
12 |
+
questions_prompt_template = PromptTemplate.from_template(
|
13 |
+
"""
|
14 |
+
Based on the following novel context, generate five simple questions that a player should answer to help create a deeply personalized character for a fantasy RPG.
|
15 |
+
The questions should explore different aspects of a potential character’s personality, background, and capabilities.
|
16 |
+
For users who are not used to the story I want you to give several choices in the question.
|
17 |
+
Write five questions in Korean(한글).
|
18 |
+
Your response should be python list with strings consist of questions.
|
19 |
+
---
|
20 |
+
Novel Context: {Context}
|
21 |
+
---
|
22 |
+
Format: ['당신은 ...? (답변의 예시: ...): ', ...]
|
23 |
+
---
|
24 |
+
Questions:
|
25 |
+
"""
|
26 |
+
)
|
27 |
+
questions_chain = questions_prompt_template | llm | StrOutputParser()
|
28 |
+
|
29 |
+
default_questions = [
|
30 |
+
"당신의 캐릭터 이름은 무엇인가요? (스킵할시 자동으로 이름이 생성됩니다): ",
|
31 |
+
"당신의 캐릭터의 성별은 무엇인가요? (스킵할시 자동으로 성별이 선택됩니다): ",
|
32 |
+
"당신의 캐릭터는 몇살인가요? (스킵할시 자동으로 나이가 선택됩니다): ",
|
33 |
+
]
|
34 |
+
while True:
|
35 |
+
try:
|
36 |
+
questions = questions_chain.invoke({"Context": world_summary})
|
37 |
+
questions_list = ast.literal_eval(questions)
|
38 |
+
questions_list = [question.replace('[', '').replace(']', '') for question in questions_list]
|
39 |
+
return default_questions + questions_list
|
40 |
+
except Exception as e:
|
41 |
+
print(f"Error generating questions: {e}")
|
42 |
+
continue
|
43 |
+
|
44 |
+
def create_character_profile(api_key, questions, answers, language="korean"):
|
45 |
+
llm = ChatUpstage(api_key=api_key)
|
46 |
+
character_creation_prompt_template = PromptTemplate.from_template(
|
47 |
+
"""
|
48 |
+
Using the answers provided below, create a detailed character profile for a fantasy tabletop RPG.
|
49 |
+
The character should reflect the given attributes. Please ensure to use the specified field names and format in your output consistently.
|
50 |
+
Consider Player's answers as the input and generate a character profile based on the given context.
|
51 |
+
When player's answer is not given, please use a appropriate value for corresponding fields made by yourself.
|
52 |
+
|
53 |
+
**Output should strictly follow this format:**
|
54 |
+
|
55 |
+
- name: "character name"
|
56 |
+
- gender: "Male" or "Female"
|
57 |
+
- age: age on integer
|
58 |
+
- race: "character race"
|
59 |
+
- job: "character job"
|
60 |
+
- stamina: integer between [1, 100]
|
61 |
+
- intelligence: integer between [1, 100]
|
62 |
+
- combat_power: integer between [1, 100]
|
63 |
+
- agility: integer between [1, 100]
|
64 |
+
- background: "character background"
|
65 |
+
|
66 |
+
**Player Answers:**
|
67 |
+
qna:
|
68 |
+
{qna}
|
69 |
+
---
|
70 |
+
Please write the output in English.
|
71 |
+
"""
|
72 |
+
)
|
73 |
+
character_creation_chain = character_creation_prompt_template | llm | StrOutputParser()
|
74 |
+
qna = [f"Q: {questions[i]}\nA: {answers[i]}" for i in range(len(questions))]
|
75 |
+
character_description = character_creation_chain.invoke({"qna": qna, "Language": language})
|
76 |
+
return character_description
|
77 |
+
|
78 |
+
def parse_character_data_to_json(text):
|
79 |
+
fields = {
|
80 |
+
'name': r"- name: (.*)",
|
81 |
+
'gender': r"- gender: (.*)",
|
82 |
+
'age': r"- age: (\d+)",
|
83 |
+
'race': r"- race: (.*)",
|
84 |
+
'job': r"- job: (.*)",
|
85 |
+
'background': r"- background: (.*)"
|
86 |
+
}
|
87 |
+
param_fields = {
|
88 |
+
'stamina': r"- stamina: (\d+)",
|
89 |
+
'intelligence': r"- intelligence: (\d+)",
|
90 |
+
'combat_power': r"- combat_power: (\d+)",
|
91 |
+
'agility': r"- agility: (\d+)"
|
92 |
+
}
|
93 |
+
character_dict = {}
|
94 |
+
params_dict = {}
|
95 |
+
for key, pattern in fields.items():
|
96 |
+
match = re.search(pattern, text)
|
97 |
+
if match:
|
98 |
+
character_dict[key] = match.group(1)
|
99 |
+
for key, pattern in param_fields.items():
|
100 |
+
match = re.search(pattern, text)
|
101 |
+
if match:
|
102 |
+
params_dict[key] = int(match.group(1))
|
103 |
+
character_dict['params'] = params_dict
|
104 |
+
return character_dict
|
105 |
+
|
106 |
+
def run_question_generation(api_key, file_path):
|
107 |
+
# Read the world summary from the uploaded file
|
108 |
+
with open(file_path, 'r', encoding='utf-8') as file:
|
109 |
+
world_summary = file.read()
|
110 |
+
|
111 |
+
# Generate questions
|
112 |
+
questions = generate_character_creation_questions(api_key, world_summary)
|
113 |
+
|
114 |
+
return questions
|
115 |
+
|
116 |
+
def run_profile_generation(api_key, file_path, *answers):
|
117 |
+
questions = run_question_generation(api_key, file_path)
|
118 |
+
|
119 |
+
# Generate character profile
|
120 |
+
character_description = create_character_profile(api_key, questions, answers)
|
121 |
+
character_json = parse_character_data_to_json(character_description)
|
122 |
+
return character_description, json.dumps(character_json, indent=4, ensure_ascii=False)
|
123 |
+
|
124 |
+
def main():
|
125 |
+
with gr.Blocks() as demo:
|
126 |
+
gr.Markdown("## Fantasy RPG Character Creator")
|
127 |
+
|
128 |
+
with gr.Row():
|
129 |
+
api_key_input = gr.Textbox(label="API Key", placeholder="Enter your API key here", type="password")
|
130 |
+
file_input = gr.File(label="Upload World Summary Text File", type="filepath")
|
131 |
+
|
132 |
+
questions_output = gr.JSON(label="Generated Questions")
|
133 |
+
|
134 |
+
# Button to generate questions
|
135 |
+
generate_questions_button = gr.Button("Generate Questions")
|
136 |
+
|
137 |
+
# Answers input section
|
138 |
+
answers_input = [gr.Textbox(label=f"Answer {i+1}", placeholder="Type your answer here") for i in range(8)]
|
139 |
+
|
140 |
+
# Outputs for character description and JSON
|
141 |
+
character_description_output = gr.Textbox(label="Character Description", lines=10)
|
142 |
+
json_output = gr.JSON(label="Character JSON")
|
143 |
+
|
144 |
+
# Button to run the profile generation
|
145 |
+
generate_profile_button = gr.Button("Generate Character Profile")
|
146 |
+
|
147 |
+
# Define button click actions
|
148 |
+
def on_generate_questions(api_key, file_path):
|
149 |
+
questions = run_question_generation(api_key, file_path)
|
150 |
+
for i, question in enumerate(questions):
|
151 |
+
answers_input[i].label = question
|
152 |
+
return questions
|
153 |
+
|
154 |
+
generate_questions_button.click(
|
155 |
+
fn=on_generate_questions,
|
156 |
+
inputs=[api_key_input, file_input],
|
157 |
+
outputs=questions_output
|
158 |
+
)
|
159 |
+
|
160 |
+
generate_profile_button.click(
|
161 |
+
fn=run_profile_generation,
|
162 |
+
inputs=[api_key_input, file_input, *answers_input],
|
163 |
+
outputs=[character_description_output, json_output]
|
164 |
+
)
|
165 |
+
|
166 |
+
# Launch the Gradio interface
|
167 |
+
demo.launch()
|
168 |
+
|
169 |
+
if __name__ == "__main__":
|
170 |
+
main()
|
create_world/creator.py
ADDED
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from .generator import generate_chain
|
2 |
+
from .utils import load_yaml, load_txt, save_json
|
3 |
+
import ast
|
4 |
+
|
5 |
+
from langchain_core.output_parsers import StrOutputParser
|
6 |
+
|
7 |
+
|
8 |
+
def create_custom_world(prompt, language='한국어', save=False):
|
9 |
+
'''
|
10 |
+
config: prompt.yaml
|
11 |
+
prompt = config['create_custom_world_prompt']
|
12 |
+
|
13 |
+
User가 직접 topic을 정하고, world_story를 입력합니다. 게임의 전반적인 세계관과 게임의 룰을 정합니다.
|
14 |
+
|
15 |
+
ex)
|
16 |
+
topic: 해리 포터
|
17 |
+
world_story: 해리 포터 세계는 마법사들의 세계입니다.
|
18 |
+
빗자루를 타고 날아다니며, 신기한 마법 생물 그리핀, 피닉스 등이 있습니다.
|
19 |
+
어둠의 세력과 맞서 싸우세요.
|
20 |
+
'''
|
21 |
+
topic = input("세계관 주제를 알려 주세요 ex)마법사 세계, 우주 전쟁: ")
|
22 |
+
world_story = input("구체적인 세계관 설명과 룰을 소개하세요: ")
|
23 |
+
prompt_variable = {'topic':topic,
|
24 |
+
'context':world_story,
|
25 |
+
'language':language,}
|
26 |
+
|
27 |
+
world_summary = generate_chain(prompt, prompt_variable)
|
28 |
+
|
29 |
+
if save == True:
|
30 |
+
save_json(topic + '_world.json', {'topic':topic, 'world_summary':world_summary})
|
31 |
+
|
32 |
+
return topic, world_summary
|
33 |
+
|
34 |
+
|
35 |
+
|
36 |
+
def create_scenario(topic, context, output_count=5, language='한국어', save=False):
|
37 |
+
'''
|
38 |
+
config: prompt.yaml
|
39 |
+
prompt = config['create_scenario_prompt']
|
40 |
+
|
41 |
+
주어진 world_summary를 바탕으로 {output_count} 개의 scenario를 생성합니다.
|
42 |
+
'''
|
43 |
+
prompt_variable = {'topic':topic,
|
44 |
+
'output_count':output_count,
|
45 |
+
'context':context,
|
46 |
+
'language':language,}
|
47 |
+
|
48 |
+
scenario = generate_chain(prompt,
|
49 |
+
prompt_variable)
|
50 |
+
|
51 |
+
while(True):
|
52 |
+
try:
|
53 |
+
storyline = generate_chain(prompt,
|
54 |
+
prompt_variable,
|
55 |
+
parser=StrOutputParser())
|
56 |
+
scenario = ast.literal_eval(storyline)
|
57 |
+
|
58 |
+
if save == True:
|
59 |
+
prompt_variable['scenario'] = scenario
|
60 |
+
save_json(str(topic) + '_scenario.json', prompt_variable)
|
61 |
+
|
62 |
+
return scenario
|
63 |
+
except:
|
64 |
+
continue
|
65 |
+
|
66 |
+
|
67 |
+
def create_storyline(topic, context, output_count=5, language='한국어', save=False):
|
68 |
+
'''
|
69 |
+
config: prompt.yaml
|
70 |
+
prompt = config['create_storyline_prompt']
|
71 |
+
|
72 |
+
주어진 scenario를 바탕으로 세부적인 게임 storyline을 {output_count} 개의 원소로 가지는 파이썬 리스트로 생성합니다.
|
73 |
+
'''
|
74 |
+
prompt_variable = {'topic':topic,
|
75 |
+
'output_count': output_count,
|
76 |
+
'context':context,
|
77 |
+
'language':language,}
|
78 |
+
|
79 |
+
while(True):
|
80 |
+
try:
|
81 |
+
storyline = generate_chain(prompt,
|
82 |
+
prompt_variable,
|
83 |
+
parser=StrOutputParser())
|
84 |
+
storyline_list = ast.literal_eval(storyline)
|
85 |
+
if save== True:
|
86 |
+
prompt_variable['scenario'] = context
|
87 |
+
prompt_variable['story_line'] = storyline_list
|
88 |
+
save_json(str(topic) + str('story_line') +'.json', prompt_variable)
|
89 |
+
|
90 |
+
return storyline_list
|
91 |
+
except:
|
92 |
+
continue
|
93 |
+
|
94 |
+
|
95 |
+
if __name__ == '__main__':
|
96 |
+
config = load_yaml(path='prompt.yaml')
|
97 |
+
create_new_world = True
|
98 |
+
save = True
|
99 |
+
|
100 |
+
if create_new_world:
|
101 |
+
topic, world_summary = create_custom_world(config['create_custom_world_prompt'], save=save)
|
102 |
+
|
103 |
+
else:
|
104 |
+
topic = 'harry potter'
|
105 |
+
world_summary = load_txt('dummy/world_summary.txt')
|
106 |
+
|
107 |
+
print(world_summary)
|
108 |
+
scenario = create_scenario(topic, world_summary, config['create_scenario_prompt'], output_count=1, save=save)
|
109 |
+
print(scenario)
|
110 |
+
print(create_storyline(topic, scenario[0], config['create_storyline_prompt'], save=save))
|
111 |
+
|
112 |
+
|
113 |
+
|
create_world/creator_gradio.py
ADDED
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from .generator import generate_chain
|
2 |
+
from .utils import load_yaml, load_txt, save_json
|
3 |
+
from .prompt import create_custom_world_prompt, create_scenario_prompt, create_storyline_prompt
|
4 |
+
import ast
|
5 |
+
from langchain_core.output_parsers import StrOutputParser
|
6 |
+
|
7 |
+
|
8 |
+
def create_custom_world(topic, world_story, save=False):
|
9 |
+
prompt=create_custom_world_prompt
|
10 |
+
|
11 |
+
'''
|
12 |
+
config: prompt.yaml
|
13 |
+
prompt = config['create_custom_world_prompt']
|
14 |
+
|
15 |
+
User가 직접 topic을 정하고, world_story를 입력합니다. 게임의 전반적인 세계관과 게임의 룰을 정합니다.
|
16 |
+
|
17 |
+
ex)
|
18 |
+
topic: 해리 포터
|
19 |
+
world_story: 해리 포터 세계는 마법사들의 세계입니다.
|
20 |
+
빗자루를 타고 날아다니며, 신기한 마법 생물 그리핀, 피닉스 등이 있습니다.
|
21 |
+
어둠의 세력과 맞서 싸우세요.
|
22 |
+
'''
|
23 |
+
prompt_variable = {'topic':topic,
|
24 |
+
'context':world_story,
|
25 |
+
'language': "한국어",}
|
26 |
+
|
27 |
+
world_summary = generate_chain(prompt, prompt_variable)
|
28 |
+
|
29 |
+
if save == True:
|
30 |
+
save_json(topic + '_world.json', {'topic':topic, 'world_summary':world_summary})
|
31 |
+
|
32 |
+
return topic, world_summary
|
33 |
+
|
34 |
+
|
35 |
+
def create_scenario(topic, context, output_count=5, save=False):
|
36 |
+
prompt=create_scenario_prompt
|
37 |
+
|
38 |
+
'''
|
39 |
+
config: prompt.yaml
|
40 |
+
prompt = config['create_scenario_prompt']
|
41 |
+
|
42 |
+
주어진 world_summary를 바탕으로 {output_count} 개의 scenario를 생성합니다.
|
43 |
+
'''
|
44 |
+
prompt_variable = {'topic':topic,
|
45 |
+
'output_count':output_count,
|
46 |
+
'context':context,
|
47 |
+
'language': '한국어',}
|
48 |
+
|
49 |
+
scenario = generate_chain(prompt,
|
50 |
+
prompt_variable)
|
51 |
+
|
52 |
+
while(True):
|
53 |
+
try:
|
54 |
+
storyline = generate_chain(prompt,
|
55 |
+
prompt_variable,
|
56 |
+
parser=StrOutputParser())
|
57 |
+
scenario = ast.literal_eval(storyline)
|
58 |
+
|
59 |
+
if save == True:
|
60 |
+
prompt_variable['scenario'] = scenario
|
61 |
+
save_json(str(topic) + '_scenario.json', prompt_variable)
|
62 |
+
|
63 |
+
return scenario
|
64 |
+
except:
|
65 |
+
continue
|
66 |
+
|
67 |
+
|
68 |
+
def create_storyline(topic, context, output_count=5, save=False):
|
69 |
+
'''
|
70 |
+
config: prompt.yaml
|
71 |
+
prompt = config['create_storyline_prompt']
|
72 |
+
|
73 |
+
주어진 scenario를 바탕으로 세부적인 게임 storyline을 {output_count} 개의 원소로 가지는 파이썬 리스트로 생성합니다.
|
74 |
+
'''
|
75 |
+
prompt=create_storyline_prompt
|
76 |
+
|
77 |
+
prompt_variable = {'topic':topic,
|
78 |
+
'output_count': output_count,
|
79 |
+
'context':context,
|
80 |
+
'language': '한국어',}
|
81 |
+
|
82 |
+
while(True):
|
83 |
+
try:
|
84 |
+
storyline = generate_chain(prompt,
|
85 |
+
prompt_variable,
|
86 |
+
parser=StrOutputParser())
|
87 |
+
storyline_list = ast.literal_eval(storyline)
|
88 |
+
|
89 |
+
if not storyline_list[0].get('title') or not storyline_list[0].get('story'):
|
90 |
+
raise Exception()
|
91 |
+
|
92 |
+
if save== True:
|
93 |
+
prompt_variable['scenario'] = context
|
94 |
+
prompt_variable['story_line'] = storyline_list
|
95 |
+
save_json(str(topic) + str('story_line') +'.json', prompt_variable)
|
96 |
+
|
97 |
+
return storyline_list
|
98 |
+
except:
|
99 |
+
continue
|
100 |
+
|
create_world/generator.py
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from langchain_upstage import ChatUpstage
|
2 |
+
from langchain_core.prompts import PromptTemplate
|
3 |
+
from langchain_core.output_parsers import StrOutputParser
|
4 |
+
|
5 |
+
def generate_chain(prompt, prompt_variable, llm=ChatUpstage(), parser = StrOutputParser()):
|
6 |
+
while(True):
|
7 |
+
try:
|
8 |
+
prompt_template = PromptTemplate.from_template(prompt)
|
9 |
+
chain = prompt_template | llm | parser
|
10 |
+
answer = chain.invoke(prompt_variable)
|
11 |
+
return answer
|
12 |
+
except:
|
13 |
+
continue
|
create_world/prompt.py
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
create_custom_world_prompt = """Write a worldview story about {topic}.
|
2 |
+
You read the context and construct the story of the context in a new and creative way.
|
3 |
+
You must create a new world view by enriching and concretely providing the given content.
|
4 |
+
|
5 |
+
Context: {context}
|
6 |
+
|
7 |
+
please answer in {language}.
|
8 |
+
worldview:
|
9 |
+
"""
|
10 |
+
|
11 |
+
|
12 |
+
create_scenario_prompt = """You are best {topic} TRPG host, and user is playing game with you.
|
13 |
+
You understand the contnt. Write diverse, creative scenarios for your story.
|
14 |
+
Each scenario is organically connected and an independent, organic story. Scenarios should not be similar.
|
15 |
+
The output format is list. Add {output_count} scenarios to the list.
|
16 |
+
|
17 |
+
Context: {context}
|
18 |
+
|
19 |
+
Response should be python list of {output_count} json element with <title> and <story>.
|
20 |
+
The format must be json.
|
21 |
+
please answer in {language}.
|
22 |
+
scenario:
|
23 |
+
"""
|
24 |
+
|
25 |
+
|
26 |
+
create_storyline_prompt = """You are best {topic} TRPG host, and user is playing game with you.
|
27 |
+
The detailed information about the fictional universe of game is included in context.
|
28 |
+
Please create a storyline with an interesting story and ending, with 5 rounds.
|
29 |
+
The storyline must be an organic story.
|
30 |
+
Each round should include an interesting event and allow the user to move on with the appropriate choice for that step.
|
31 |
+
Scenario should be related to fictional universe and users persona.
|
32 |
+
|
33 |
+
Context: {context}
|
34 |
+
|
35 |
+
Response should be python list of {output_count} json element with <title> and <story>.
|
36 |
+
The format must be json.
|
37 |
+
Please answer in {language}.
|
38 |
+
storyline:"""
|
create_world/prompt.yaml
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
language: 한국어
|
2 |
+
|
3 |
+
|
4 |
+
# prompt variable dict: {'topic': topic, 'context':context, 'language':language}
|
5 |
+
create_custom_world_prompt : "Write a worldview story about {topic}.
|
6 |
+
You read the context and construct the story of the context in a new and creative way.
|
7 |
+
You must create a new world view by enriching and concretely providing the given content.
|
8 |
+
|
9 |
+
Context: {context}
|
10 |
+
|
11 |
+
please answer in {language}.
|
12 |
+
worldview:
|
13 |
+
"
|
14 |
+
|
15 |
+
# prompt variable dict: {'topic': topic, 'output_count': output_count, 'context':context, 'language': language}
|
16 |
+
create_scenario_prompt : "You are best {topic} TRPG host, and user is playing game with you.
|
17 |
+
You understand the content. Write diverse, creative scenarios for your story.
|
18 |
+
Each scenario is organically connected and an independent, organic story. Scenarios should not be similar.
|
19 |
+
The output format is list. Add {output_count} scenarios to the list.
|
20 |
+
|
21 |
+
Context: {context}
|
22 |
+
|
23 |
+
Response should be python list of {output_count} json element with <title> and <story>.
|
24 |
+
The format must be json.
|
25 |
+
please answer in {language}.
|
26 |
+
scenario:
|
27 |
+
"
|
28 |
+
|
29 |
+
|
30 |
+
# prompt variable dict: {'topic':topic, 'output_count': output_count, 'context':context, 'language': language}
|
31 |
+
create_storyline_prompt : "You are best {topic} TRPG host, and user is playing game with you.
|
32 |
+
The detailed information about the fictional universe of game is included in context.
|
33 |
+
Please create a storyline with an interesting story and ending, with 5 rounds.
|
34 |
+
The storyline must be an organic story.
|
35 |
+
Each round should include an interesting event and allow the user to move on with the appropriate choice for that step.
|
36 |
+
Scenario should be related to fictional universe and users persona.
|
37 |
+
|
38 |
+
Context: {context}
|
39 |
+
|
40 |
+
Response should be python list of {output_count} json element with <title> and <story>.
|
41 |
+
The format must be json.
|
42 |
+
Please answer in {language}.
|
43 |
+
storyline:"
|
44 |
+
|
create_world/utils.py
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import yaml
|
2 |
+
import json
|
3 |
+
|
4 |
+
|
5 |
+
def load_yaml(path: str):
|
6 |
+
with open(path, 'r', encoding='UTF8') as f:
|
7 |
+
load_yaml = yaml.load(f, Loader=yaml.FullLoader)
|
8 |
+
return load_yaml
|
9 |
+
|
10 |
+
|
11 |
+
def load_txt(path):
|
12 |
+
with open(path, 'r', encoding='UTF8') as f:
|
13 |
+
txt = f.read()
|
14 |
+
return txt
|
15 |
+
|
16 |
+
|
17 |
+
def save_json(path, dict):
|
18 |
+
with open(path, 'w', encoding='UTF8') as f:
|
19 |
+
json.dump(dict, f, indent='\t', ensure_ascii=False)
|
20 |
+
return
|
demo.py
ADDED
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
|
3 |
+
from create_world.creator_gradio import create_custom_world, create_scenario, create_storyline
|
4 |
+
from create_world.utils import load_txt
|
5 |
+
from create_character.gradio import generate_character_creation_questions, create_character_profile, parse_character_data_to_json
|
6 |
+
from play_game.main import create_initial_conversation, create_round_description, create_round_result, create_bad_ending, create_good_ending, convert_to_image_prompt, generate_image
|
7 |
+
from play_game.formatter import player_profile_to_str, to_round_result
|
8 |
+
|
9 |
+
# 1. Main & World Selection
|
10 |
+
# 2. Character Creation
|
11 |
+
# 3. Game Play
|
12 |
+
|
13 |
+
# topic, world_summary = create_custom_world(config['create_custom_world_prompt'], language='한국어', save=False)
|
14 |
+
# scenario = create_scenario(topic, world_summary, config['create_scenario_prompt'], output_count=1)
|
15 |
+
# round_stories = create_storyline(topic, scenario[0], config['create_storyline_prompt'])
|
16 |
+
|
17 |
+
|
18 |
+
def main():
|
19 |
+
with gr.Blocks() as demo:
|
20 |
+
gr.Markdown("## LRPG")
|
21 |
+
game_topic = gr.State()
|
22 |
+
world_summary = gr.State()
|
23 |
+
stories = gr.State([])
|
24 |
+
player_profile = gr.State()
|
25 |
+
|
26 |
+
with gr.Tab("게임 세계 생성"):
|
27 |
+
theme_choices = gr.Radio(["Harry Potter", "직접 생성"], label="테마 선택", info="게임의 테마를 선택하세요.")
|
28 |
+
|
29 |
+
@gr.render(inputs=[theme_choices])
|
30 |
+
def on_world_choices(theme):
|
31 |
+
if theme == "직접 생성":
|
32 |
+
topic = gr.Textbox("ex)마법사 세계, 우주 전쟁", label="주제", info="세계의 주제를 알려주세요.", interactive=True)
|
33 |
+
world_story = gr.Textbox(label="설명", info="구체적인 세계관 설명과 룰을 소개하세요", interactive=True)
|
34 |
+
world_create_btn = gr.Button("세계관 자동 생성")
|
35 |
+
create_story_btn = gr.Button("스토리 생성", visible=False)
|
36 |
+
result = gr.Markdown("## 스토리 생성이 완료되었습니다. 캐릭터 생성 탭으로 이동해주세요.", visible=False)
|
37 |
+
|
38 |
+
# @world_create_btn.click(inputs=[ topic, world_story ], outputs=[ game_topic, world_summary, create_story_btn ])
|
39 |
+
def click_world_create_btn(topic, world_story):
|
40 |
+
gr.Info("세계관 생성중입니다...")
|
41 |
+
topic, summary = create_custom_world(topic, world_story)
|
42 |
+
gr.Info("세계관 생성 완료!")
|
43 |
+
return { game_topic: topic, world_summary: summary, create_story_btn: gr.Button(visible=True) }
|
44 |
+
world_create_btn.click(fn=click_world_create_btn, inputs=[ topic, world_story ], outputs=[ game_topic, world_summary, create_story_btn ])
|
45 |
+
|
46 |
+
# @create_story_btn.click(inputs=[ game_topic, world_summary ], outputs= [ stories, result ])
|
47 |
+
def click_create_story_btn(topic, summary):
|
48 |
+
gr.Info("스토리 생성중입니다...")
|
49 |
+
scenario = create_scenario(topic, summary, output_count=1, save=False)
|
50 |
+
_stories = create_storyline(topic, scenario[0], save=False)
|
51 |
+
gr.Info("스토리 생성 완료!")
|
52 |
+
return { stories: _stories, result: gr.Markdown(visible=True) }
|
53 |
+
create_story_btn.click(fn=click_create_story_btn, inputs=[ game_topic, world_summary ], outputs= [ stories, result ])
|
54 |
+
|
55 |
+
elif theme == "Harry Potter":
|
56 |
+
create_story_btn = gr.Button("스토리 생성")
|
57 |
+
result = gr.Markdown("## 스토리 생성이 완료되었습니다. 캐릭터 생성 탭으로 이동해주세요.", visible=False)
|
58 |
+
def click_create_story_btn():
|
59 |
+
gr.Info("스토리 생성중입니다...")
|
60 |
+
topic = "Harry Potter"
|
61 |
+
_world_summary = load_txt("harrypotter_scenario/world_summary.txt")
|
62 |
+
scenario = create_scenario(topic, _world_summary, output_count=1, save=False)
|
63 |
+
_stories = create_storyline(topic, scenario[0], save=False)
|
64 |
+
gr.Info("스토리 생성 완료!")
|
65 |
+
return { game_topic: topic, world_summary: _world_summary, stories: _stories, result: gr.Markdown(visible=True) }
|
66 |
+
create_story_btn.click(fn=click_create_story_btn, outputs=[ game_topic, world_summary, stories, result ])
|
67 |
+
|
68 |
+
with gr.Tab("캐릭터 생성"):
|
69 |
+
@gr.render(inputs=[world_summary])
|
70 |
+
def on_game_topic(summary):
|
71 |
+
questions = generate_character_creation_questions(summary)
|
72 |
+
answers = []
|
73 |
+
for question_idx, question in enumerate(questions):
|
74 |
+
answer = gr.Textbox(key=question_idx, label=question, placeholder="당신의 대답을 입력하세요.", interactive=True)
|
75 |
+
answers.append(answer)
|
76 |
+
char_create_btn = gr.Button("캐릭터 생성")
|
77 |
+
result = gr.Markdown("## 캐릭터 생성이 완료되었습니다. 게임 진행 탭으로 이동해주세요.", visible=False)
|
78 |
+
|
79 |
+
def click_char_create_btn(*_answers):
|
80 |
+
gr.Info("캐릭터 생성중입니다...")
|
81 |
+
character_profile = parse_character_data_to_json(create_character_profile(questions, _answers))
|
82 |
+
profile_keys = [key for key in character_profile.keys()]
|
83 |
+
required_keys = ["name", "gender", "age", "race", "job", "background"]
|
84 |
+
params_keys = [key for key in character_profile["params"].keys()]
|
85 |
+
required_params_keys = ["stamina", "intelligence", "combat_power", "agility"]
|
86 |
+
if not all(item in profile_keys for item in required_keys) or not all(item in params_keys for item in required_params_keys):
|
87 |
+
gr.Error("캐릭터 생성에 실패했습니다. 항목들을 자세하게 빠짐없이 기입해주세요.")
|
88 |
+
return
|
89 |
+
gr.Info("캐릭터 생성 완료!")
|
90 |
+
return { player_profile: character_profile, result: gr.Markdown(visible=True) }
|
91 |
+
char_create_btn.click(fn=click_char_create_btn, inputs=answers, outputs=[player_profile, result])
|
92 |
+
|
93 |
+
|
94 |
+
with gr.Tab("게임 플레이"):
|
95 |
+
round = gr.State(0)
|
96 |
+
player_restriction = gr.State()
|
97 |
+
player_capability = gr.State()
|
98 |
+
previous_conversation = gr.State("")
|
99 |
+
previous_round_result = gr.State("")
|
100 |
+
|
101 |
+
@gr.render(inputs=[world_summary, stories, player_profile, round, player_restriction, player_capability, previous_conversation, previous_round_result, game_topic], triggers=[round.change, player_profile.change])
|
102 |
+
def on_round(_world_summary, _stories, _player_profile, _round, _player_restriction, _player_capability, _previous_conversation, _previous_round_result, _game_topic):
|
103 |
+
entire_story = [f"{idx+1}. {scenario["title"]}\n{scenario["story"]}\n\n" for idx, scenario in enumerate(_stories)]
|
104 |
+
player_profile_str = player_profile_to_str({ **_player_profile, 'params': _player_capability })
|
105 |
+
|
106 |
+
round_scenario = _stories[_round-1]
|
107 |
+
if _round == 0:
|
108 |
+
introduction = gr.Markdown(create_initial_conversation(_world_summary, player_profile_str, _player_restriction, _player_capability, entire_story))
|
109 |
+
game_start_btn = gr.Button("게임 시작")
|
110 |
+
|
111 |
+
def click_game_start_btn():
|
112 |
+
return { round: 1, player_restriction: { "life": 30, "money": 30 }, player_capability: _player_profile["params"] }
|
113 |
+
game_start_btn.click(fn=click_game_start_btn, outputs=[round, player_restriction, player_capability])
|
114 |
+
else:
|
115 |
+
round_story = f"{_round}. {round_scenario["title"]}: {round_scenario["story"]}\n"
|
116 |
+
if _player_restriction["life"] <= 0 or _player_restriction["money"] <= 0:
|
117 |
+
bad_ending = create_bad_ending(_world_summary, player_profile_str, _player_restriction, _player_capability, entire_story, round_story, _previous_conversation, _previous_round_result)
|
118 |
+
display_bad_ending = gr.Markdown(bad_ending)
|
119 |
+
restart_button = gr.Button("다시 시작하기")
|
120 |
+
def click_restart_button():
|
121 |
+
return { round: 0, player_restriction: {}, player_capability: {}, previous_conversation: "", previous_round_result: "" }
|
122 |
+
restart_button.click(fn=click_restart_button, outputs=[round, player_restriction, player_capability, previous_conversation, previous_round_result])
|
123 |
+
|
124 |
+
elif _round >= len(_stories):
|
125 |
+
good_ending = create_good_ending(_world_summary, player_profile_str, _player_restriction, _player_capability, entire_story, _previous_conversation)
|
126 |
+
display_good_ending = gr.Markdown(good_ending)
|
127 |
+
restart_button = gr.Button("다시 시작하기")
|
128 |
+
def click_restart_button():
|
129 |
+
return { round: 0, player_restriction: {}, player_capability: {}, previous_conversation: "", previous_round_result: "" }
|
130 |
+
restart_button.click(fn=click_restart_button, outputs=[round, player_restriction, player_capability, previous_conversation, previous_round_result])
|
131 |
+
|
132 |
+
else:
|
133 |
+
round_description = create_round_description(_world_summary, player_profile_str, _player_restriction, _player_capability, entire_story, round_story, _previous_conversation, _previous_round_result)
|
134 |
+
gr.Markdown(f"## {_round}. {round_scenario["title"]}")
|
135 |
+
with gr.Row():
|
136 |
+
gr.Markdown(round_description)
|
137 |
+
with gr.Column():
|
138 |
+
image_output = gr.Image(interactive=False, scale=5)
|
139 |
+
generate_image_btn = gr.Button("이미지 생성")
|
140 |
+
def click_generate_image_btn():
|
141 |
+
gr.Info("이미지 생성중입니다...")
|
142 |
+
image_generation_prompt = convert_to_image_prompt(_game_topic, _world_summary, _player_profile, round_description)
|
143 |
+
image_url = generate_image(image_generation_prompt)
|
144 |
+
gr.Info("이미지 생성 완료!")
|
145 |
+
return gr.Image(image_url)
|
146 |
+
generate_image_btn.click(fn=click_generate_image_btn, outputs=image_output)
|
147 |
+
|
148 |
+
with gr.Row():
|
149 |
+
player_response = gr.Textbox(label="당신만의 결정을 내려주세요!", info="하나의 문장으로 당신이 할 행동과 그에 대한 근거와 이유를 명확하게 설명해주세요", interactive=True, scale=10)
|
150 |
+
submit_btn = gr.Button("결정", scale=1)
|
151 |
+
|
152 |
+
def click_submit_btn(_player_response, _previous_conversation, _player_restriction, _player_capability):
|
153 |
+
gr.Info("결정을 반영중입니다...")
|
154 |
+
|
155 |
+
# __round = input["_round"]
|
156 |
+
# _round_description = input["round_description"]
|
157 |
+
# _player_response = input["player_response"]
|
158 |
+
# _previous_conversation = input["previous_conversation"]
|
159 |
+
# _player_restriction = input["player_restriction"]
|
160 |
+
# _player_capability = input["player_capability"]
|
161 |
+
__round = _round
|
162 |
+
_round_description = round_description
|
163 |
+
|
164 |
+
# Reflect the result and update player status
|
165 |
+
round_result = create_round_result(_world_summary, player_profile_str, _player_restriction, _player_capability, _round_description, _player_response)
|
166 |
+
round_effect = round_result["effect"]
|
167 |
+
round_result_explanation = round_result["reason"]
|
168 |
+
for key, value in round_effect["player_restriction"].items():
|
169 |
+
if _player_restriction.get(key) is not None:
|
170 |
+
modified_value = _player_restriction[key] + value
|
171 |
+
if modified_value > 10:
|
172 |
+
_player_restriction[key] = 10
|
173 |
+
elif modified_value < -10:
|
174 |
+
_player_restriction[key] = -10
|
175 |
+
else:
|
176 |
+
_player_restriction[key] = modified_value
|
177 |
+
|
178 |
+
for key, value in round_effect["player_capability"].items():
|
179 |
+
if _player_capability.get(key) is not None:
|
180 |
+
modified_value = _player_capability[key] + value
|
181 |
+
if modified_value > 100:
|
182 |
+
_player_capability[key] = 100
|
183 |
+
elif modified_value < -100:
|
184 |
+
_player_capability[key] = -100
|
185 |
+
else:
|
186 |
+
_player_capability[key] = modified_value
|
187 |
+
|
188 |
+
return {
|
189 |
+
round: __round+1,
|
190 |
+
previous_conversation: _previous_conversation + f"Game Master: {_round_description}\nPlayer: {_player_response}\n",
|
191 |
+
previous_round_result: to_round_result(round_effect, round_result_explanation),
|
192 |
+
player_restriction: _player_restriction,
|
193 |
+
player_capability: _player_capability,
|
194 |
+
}
|
195 |
+
submit_btn.click(
|
196 |
+
fn=click_submit_btn,
|
197 |
+
inputs=[ player_response, previous_conversation, player_restriction, player_capability ],
|
198 |
+
outputs=[round, previous_conversation, previous_round_result, player_restriction, player_capability]
|
199 |
+
)
|
200 |
+
|
201 |
+
|
202 |
+
# # For Debugging
|
203 |
+
# game_topic_debugging = gr.Textbox(game_topic.value, label="game_topic_debugging")
|
204 |
+
# game_topic.change(lambda x: gr.Textbox(x), inputs=[game_topic], outputs=game_topic_debugging)
|
205 |
+
# world_summary_debugging=gr.Textbox(world_summary.value, label="world_summary_debugging")
|
206 |
+
# world_summary.change(lambda x: gr.Textbox(x), inputs=[world_summary], outputs=world_summary_debugging)
|
207 |
+
# stories_debugging=gr.Textbox(stories.value, label="stories_debugging")
|
208 |
+
# stories.change(lambda x: gr.Textbox(x), inputs=[stories], outputs=stories_debugging)
|
209 |
+
# player_profile_debugging=gr.Textbox(player_profile.value, label="player_profile_debugging")
|
210 |
+
# player_profile.change(lambda x: gr.Textbox(x), inputs=[player_profile], outputs=player_profile_debugging)
|
211 |
+
|
212 |
+
|
213 |
+
demo.launch(share=True)
|
214 |
+
|
215 |
+
|
216 |
+
if __name__ == "__main__":
|
217 |
+
main()
|
example/prompt.ipynb
ADDED
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"cells": [
|
3 |
+
{
|
4 |
+
"cell_type": "code",
|
5 |
+
"execution_count": 10,
|
6 |
+
"metadata": {},
|
7 |
+
"outputs": [
|
8 |
+
{
|
9 |
+
"name": "stdout",
|
10 |
+
"output_type": "stream",
|
11 |
+
"text": [
|
12 |
+
"\n",
|
13 |
+
"\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m24.0\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m24.1.1\u001b[0m\n",
|
14 |
+
"\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n"
|
15 |
+
]
|
16 |
+
}
|
17 |
+
],
|
18 |
+
"source": [
|
19 |
+
"! pip3 install -qU markdownify langchain-upstage rank_bm25 python-dotenv langchain_chroma langchain"
|
20 |
+
]
|
21 |
+
},
|
22 |
+
{
|
23 |
+
"cell_type": "code",
|
24 |
+
"execution_count": 5,
|
25 |
+
"metadata": {},
|
26 |
+
"outputs": [],
|
27 |
+
"source": [
|
28 |
+
"%load_ext dotenv\n",
|
29 |
+
"%dotenv"
|
30 |
+
]
|
31 |
+
},
|
32 |
+
{
|
33 |
+
"cell_type": "code",
|
34 |
+
"execution_count": 6,
|
35 |
+
"metadata": {},
|
36 |
+
"outputs": [],
|
37 |
+
"source": [
|
38 |
+
"import warnings\n",
|
39 |
+
"\n",
|
40 |
+
"warnings.filterwarnings(\"ignore\")"
|
41 |
+
]
|
42 |
+
},
|
43 |
+
{
|
44 |
+
"cell_type": "code",
|
45 |
+
"execution_count": 11,
|
46 |
+
"metadata": {},
|
47 |
+
"outputs": [],
|
48 |
+
"source": [
|
49 |
+
"from langchain_chroma import Chroma\n",
|
50 |
+
"from langchain_upstage import UpstageEmbeddings\n",
|
51 |
+
"from langchain.docstore.document import Document\n",
|
52 |
+
"\n",
|
53 |
+
"sample_text_list = [\n",
|
54 |
+
" \"마법사의 나무는 마법 지팡이의 재료 중 최상급의 재료로 알려져 있다\",\n",
|
55 |
+
" \"There are Weasleys' Wizard Wheezes nearby the Diagon Alley\",\n",
|
56 |
+
" \"Diangon Alley is a place where you can buy magic wands\",\n",
|
57 |
+
" \"Hogwart is a school for witches and wizards\",\n",
|
58 |
+
"]\n",
|
59 |
+
"sample_docs = [Document(page_content=text) for text in sample_text_list]\n",
|
60 |
+
"vectorstore = Chroma.from_documents(\n",
|
61 |
+
" documents=sample_docs,\n",
|
62 |
+
" embedding=UpstageEmbeddings(model=\"solar-embedding-1-large\"),\n",
|
63 |
+
")\n",
|
64 |
+
"retriever = vectorstore.as_retriever()"
|
65 |
+
]
|
66 |
+
},
|
67 |
+
{
|
68 |
+
"cell_type": "code",
|
69 |
+
"execution_count": 20,
|
70 |
+
"metadata": {},
|
71 |
+
"outputs": [],
|
72 |
+
"source": [
|
73 |
+
"from langchain_core.prompts import PromptTemplate\n",
|
74 |
+
"from langchain_core.output_parsers import StrOutputParser\n",
|
75 |
+
"from langchain_upstage import ChatUpstage\n",
|
76 |
+
"\n",
|
77 |
+
"\n",
|
78 |
+
"llm = ChatUpstage()\n",
|
79 |
+
"\n",
|
80 |
+
"prompt_template = PromptTemplate.from_template(\n",
|
81 |
+
" \"\"\"\n",
|
82 |
+
" You are best D&D host. And user is playing D&D game with you.\n",
|
83 |
+
" Please answer in a exciting tone as a host.\n",
|
84 |
+
" User has to make choices in the game.\n",
|
85 |
+
" User has 10 life points and 10 gold coins.\n",
|
86 |
+
" Please make scenario of most interesting event from the following context, and provide diverse choices which user can select.\n",
|
87 |
+
" For each round, you should provide at least 3 choices and total round should be less than 50.\n",
|
88 |
+
" You should explain situation and choices in detail.\n",
|
89 |
+
" If someone's conversation is included in the scenario, please express it in colloquial terms that fit the person's character as much as possible.\n",
|
90 |
+
" Choices should be diverse and interesting, and each choice will reduce or increase user's life points or gold coins.\n",
|
91 |
+
" If user make appropriate decision, user's life points or gold coins will increase.\n",
|
92 |
+
" If user make inappropriate decision, user's life points or gold coins will decrease and decreasing amount should not be larger than amount of user owned.\n",
|
93 |
+
" When user select a choice, you should provide the result of the choice, but you should not show result before user select the choice.\n",
|
94 |
+
" You should consider previous conversation and context to make the scenario.\n",
|
95 |
+
" If user's life points or gold coins are less than 0, user will lose the game and get bad ending.\n",
|
96 |
+
" Also when total round is more than 50, user will lose the game and get bad ending.\n",
|
97 |
+
" If user's life points or gold coins are more than 20, user will win the game and get good ending.\n",
|
98 |
+
" ---\n",
|
99 |
+
" User Choice: {user_choice}\n",
|
100 |
+
" ---\n",
|
101 |
+
" Previous Conversation: {previous_conversation}\n",
|
102 |
+
" ---\n",
|
103 |
+
" Context: {context}\n",
|
104 |
+
" \"\"\"\n",
|
105 |
+
")\n",
|
106 |
+
"chain = prompt_template | llm | StrOutputParser()\n",
|
107 |
+
"\n"
|
108 |
+
]
|
109 |
+
},
|
110 |
+
{
|
111 |
+
"cell_type": "code",
|
112 |
+
"execution_count": 21,
|
113 |
+
"metadata": {},
|
114 |
+
"outputs": [
|
115 |
+
{
|
116 |
+
"data": {
|
117 |
+
"text/plain": [
|
118 |
+
"'마법사의 나무와 용의 심장으로 만든 지팡이를 사고 싶다는군요! 그것은 강력한 마법 아이템입니다. 이제 선택의 시간이 왔습니다. 이 지팡이를 어떻게 얻을 것인가요?\\n\\n1. **마법 상점을 방문하세요:** 마을의 마법 상점을 찾아가서 지팡이를 구매해보세요. 가격은 8골드 코인입니다.\\n2. **마법사로부터 구매하세요:** 마을에 있는 마법사에게 지팡이를 구매할 수 있는지 물어보세요. 하지만 가격은 10골드 코인으로 비싸집니다.\\n3. **지팡이를 제작하세요:** 재료와 마법 지식을 가지고 지팡이를 직접 제작해보세요. 하지만 이 선택은 5골드 코인을 소비하고, 제작에 성공할 확률은 50%입니다.\\n\\n어떤 선택을 하시겠습니까?'"
|
119 |
+
]
|
120 |
+
},
|
121 |
+
"execution_count": 21,
|
122 |
+
"metadata": {},
|
123 |
+
"output_type": "execute_result"
|
124 |
+
}
|
125 |
+
],
|
126 |
+
"source": [
|
127 |
+
"user_choice = \"저는 마법사의 나무와 용의 심장으로 만든 지팡이를 사고 싶어요.\"\n",
|
128 |
+
"sample_context = \"What is related information about {user_choice}?\"\n",
|
129 |
+
"result_docs = retriever.invoke(sample_context)\n",
|
130 |
+
"chain.invoke({ \"user_choice\": user_choice, \"previous_conversation\": \"\", \"context\": sample_context })"
|
131 |
+
]
|
132 |
+
},
|
133 |
+
{
|
134 |
+
"cell_type": "code",
|
135 |
+
"execution_count": null,
|
136 |
+
"metadata": {},
|
137 |
+
"outputs": [],
|
138 |
+
"source": []
|
139 |
+
}
|
140 |
+
],
|
141 |
+
"metadata": {
|
142 |
+
"kernelspec": {
|
143 |
+
"display_name": ".venv",
|
144 |
+
"language": "python",
|
145 |
+
"name": "python3"
|
146 |
+
},
|
147 |
+
"language_info": {
|
148 |
+
"codemirror_mode": {
|
149 |
+
"name": "ipython",
|
150 |
+
"version": 3
|
151 |
+
},
|
152 |
+
"file_extension": ".py",
|
153 |
+
"mimetype": "text/x-python",
|
154 |
+
"name": "python",
|
155 |
+
"nbconvert_exporter": "python",
|
156 |
+
"pygments_lexer": "ipython3",
|
157 |
+
"version": "3.12.2"
|
158 |
+
}
|
159 |
+
},
|
160 |
+
"nbformat": 4,
|
161 |
+
"nbformat_minor": 2
|
162 |
+
}
|
harrypotter_scenario/scenario_1/Scenario_1.txt
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Scenario 1: The Forbidden Forest Encounter
|
2 |
+
Mission: Explore the Forbidden Forest and rescue a unicorn from a dark creature.
|
3 |
+
Situation: The player must navigate through the dense, magical forest, facing challenges like enchanted plants and dangerous creatures.
|
4 |
+
Conditions for Clearing:
|
5 |
+
1. Successfully track the unicorn's trail.
|
6 |
+
2. Defeat the dark creature (a giant spider or a dark wizard).
|
7 |
+
3. Safely escort the unicorn back to the edge of the forest.
|
harrypotter_scenario/scenario_1/Scenario_1_KR.txt
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
시나리오 1: 금지된 숲의 만남
|
2 |
+
미션: 금지된 숲을 탐험하고 어두운 생물로부터 유니콘을 구출하세요.
|
3 |
+
상황: 플레이어는 밀집된 마법의 숲을 탐험하며, 마법 식물과 위험한 생물 같은 도전에 맞서야 합니다.
|
4 |
+
미션 완료 조건:
|
5 |
+
1. 유니콘의 흔적을 성공적으로 추적하세요.
|
6 |
+
2. 어두운 생물(거대한 거미 또는 어둠의 마법사)을 물리치세요.
|
7 |
+
3. 유니콘을 안전하게 숲의 가장자리로 호위하세요.
|
harrypotter_scenario/scenario_1/sc1_image/Scenario_1_Storyline_1.png
ADDED
![]() |
Git LFS Details
|
harrypotter_scenario/scenario_1/sc1_image/Scenario_1_Storyline_2.png
ADDED
![]() |
Git LFS Details
|
harrypotter_scenario/scenario_1/sc1_image/Scenario_1_Storyline_3.png
ADDED
![]() |
Git LFS Details
|
harrypotter_scenario/scenario_1/sc1_image/Scenario_1_Storyline_4.png
ADDED
![]() |
Git LFS Details
|
harrypotter_scenario/scenario_1/sc1_image/Scenario_1_Storyline_5.png
ADDED
![]() |
Git LFS Details
|
harrypotter_scenario/scenario_1/sc1_storyline/Scenario_1_Storyline_1.txt
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Storyline 1: Rescue the Unicorn
|
2 |
+
Mission: Save a young unicorn trapped by a giant spider.
|
3 |
+
Steps:
|
4 |
+
1. Track the unicorn's hoof prints to its location.
|
5 |
+
2. Defeat the giant spider using spells like "Stupefy" and "Incendio."
|
6 |
+
3. Heal the unicorn's wounds with a healing potion.
|
7 |
+
4. Escort the unicorn safely back to the forest edge.
|
8 |
+
|
9 |
+
Conditions for Clearing:
|
10 |
+
- Successfully track and find the unicorn.
|
11 |
+
- Defeat the giant spider without sustaining major injuries.
|
12 |
+
- Heal the unicorn and ensure its safe return.
|
harrypotter_scenario/scenario_1/sc1_storyline/Scenario_1_Storyline_2.txt
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Storyline 2: Dark Wizard Ambush
|
2 |
+
Mission: Protect a group of unicorns from a dark wizard attack.
|
3 |
+
Steps:
|
4 |
+
1. Use "Homenum Revelio" to locate the dark wizard.
|
5 |
+
2. Engage in a duel with the dark wizard using defensive and offensive spells.
|
6 |
+
3. Create a magical barrier to protect the unicorns.
|
7 |
+
4. Drive the dark wizard out of the forest.
|
8 |
+
|
9 |
+
Conditions for Clearing:
|
10 |
+
- Successfully locate the dark wizard.
|
11 |
+
- Win the duel without the unicorns being harmed.
|
12 |
+
- Maintain the magical barrier until the threat is eliminated.
|
harrypotter_scenario/scenario_1/sc1_storyline/Scenario_1_Storyline_3.txt
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Storyline 3: Enchanted Plants Obstacle
|
2 |
+
Mission: Navigate through a section of the forest filled with dangerous enchanted plants to rescue a trapped unicorn.
|
3 |
+
Steps:
|
4 |
+
1. Identify and avoid poisonous plants using "Herbivicus."
|
5 |
+
2. Use "Diffindo" to cut through thick vines and obstacles.
|
6 |
+
3. Find the trapped unicorn entangled in Devil's Snare.
|
7 |
+
4. Use "Lumos Solem" to free the unicorn from the Devil's Snare.
|
8 |
+
|
9 |
+
Conditions for Clearing:
|
10 |
+
- Successfully navigate through the enchanted plants.
|
11 |
+
- Avoid being poisoned or trapped by the plants.
|
12 |
+
- Free the unicorn and guide it back to safety.
|
harrypotter_scenario/scenario_1/sc1_storyline/Scenario_1_Storyline_4.txt
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Storyline 4: Lost Foal
|
2 |
+
Mission: Find and reunite a lost unicorn foal with its herd.
|
3 |
+
Steps:
|
4 |
+
1. Follow the foal's tracks and magical aura.
|
5 |
+
2. Use calming spells like "Calming Charm" to soothe the frightened foal.
|
6 |
+
3. Protect the foal from forest predators using defensive spells.
|
7 |
+
4. Lead the foal back to its herd.
|
8 |
+
|
9 |
+
Conditions for Clearing:
|
10 |
+
- Successfully track and find the foal.
|
11 |
+
- Keep the foal calm and protected from predators.
|
12 |
+
- Reunite the foal with its herd without any incidents.
|
harrypotter_scenario/scenario_1/sc1_storyline/Scenario_1_Storyline_5.txt
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Storyline 5: Magical Storm
|
2 |
+
Mission: Protect a herd of unicorns from a magical storm.
|
3 |
+
Steps:
|
4 |
+
1. Use "Meteolojinx Recanto" to calm the storm.
|
5 |
+
2. Create a protective shield around the unicorns using "Protego Totalum."
|
6 |
+
3. Guide the unicorns to a safe shelter within the forest.
|
7 |
+
4. Maintain the protective shield until the storm passes.
|
8 |
+
|
9 |
+
Conditions for Clearing:
|
10 |
+
- Successfully calm the magical storm.
|
11 |
+
- Keep the protective shield intact until the storm ends.
|
12 |
+
- Ensure all unicorns are safe and unharmed.
|
harrypotter_scenario/scenario_1/sc1_storyline_ko/Scenario_1_Storyline_1_KR.txt
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
스토리라인 1: 유니콘 구출
|
2 |
+
미션: 거대한 거미에게 갇힌 어린 유니콘을 구출하세요.
|
3 |
+
단계:
|
4 |
+
1. 유니콘의 말굽 자국을 따라 위치를 찾으세요.
|
5 |
+
2. "스투페파이"와 "인센디오" 같은 주문으로 거대한 거미를 물리치세요.
|
6 |
+
3. 치유 물약으로 유니콘의 상처를 치유하세요.
|
7 |
+
4. 유니콘을 안전하게 숲의 가장자리로 호위하세요.
|
8 |
+
|
9 |
+
미션 완료 조건:
|
10 |
+
- 유니콘을 성공적으로 추적하고 찾으세요.
|
11 |
+
- 큰 부상 없이 거대한 거미를 물리치세요.
|
12 |
+
- 유니콘을 치유하고 안전한 복귀를 보장하세요.
|
harrypotter_scenario/scenario_1/sc1_storyline_ko/Scenario_1_Storyline_2_KR.txt
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
스토리라인 2: 어둠의 마법사 매복
|
2 |
+
미션: 어둠의 마법사 공격으로부터 유니콘 무리를 보호하세요.
|
3 |
+
단계:
|
4 |
+
1. "호멘눔 레벨리오"를 사용하여 어둠의 마법사를 찾으세요.
|
5 |
+
2. 방어 및 공격 주문을 사용하여 어둠의 마법사와 결투를 벌이세요.
|
6 |
+
3. 유니콘을 보호하기 위해 마법 장벽을 만드세요.
|
7 |
+
4. 어둠의 마법사를 숲에서 쫓아내세요.
|
8 |
+
|
9 |
+
미션 완료 조건:
|
10 |
+
- 어둠의 마법사를 성공적으로 찾으세요.
|
11 |
+
- 유니콘이 다치지 않도록 결투에서 승리하세요.
|
12 |
+
- 위협이 제거될 때까지 마법 장벽을 유지하세요.
|
harrypotter_scenario/scenario_1/sc1_storyline_ko/Scenario_1_Storyline_3_KR.txt
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
스토리라인 3: 마법 식물 장애물
|
2 |
+
미션: 위험한 마법 식물로 가득한 숲을 탐색하여 갇힌 유니콘을 구출하세요.
|
3 |
+
단계:
|
4 |
+
1. "허비비쿠스"를 사용하여 독성 식물을 식별하고 피하세요.
|
5 |
+
2. "디핀도"를 사용하여 두꺼운 덩굴과 장애물을 잘라내세요.
|
6 |
+
3. 데빌즈 스네어에 얽힌 유니콘을 찾으세요.
|
7 |
+
4. "루모스 솔렘"을 사용하여 데빌즈 스네어에서 유니콘을 해방하세요.
|
8 |
+
|
9 |
+
미션 완료 조건:
|
10 |
+
- 마법 식물을 성공적으로 탐색하세요.
|
11 |
+
- 식물에 중독되거나 갇히지 않도록 피하세요.
|
12 |
+
- 유니콘을 구출하고 안전한 곳으로 안내하세요.
|
harrypotter_scenario/scenario_1/sc1_storyline_ko/Scenario_1_Storyline_4_KR.txt
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
스토리라인 4: 잃어버린 새끼
|
2 |
+
미션: 잃어버린 유니콘 새끼를 찾아 무리와 재회시키세요.
|
3 |
+
단계:
|
4 |
+
1. 새끼의 발자국과 마법의 아우라를 따라가세요.
|
5 |
+
2. "캄밍 참" 같은 진정 주문을 사용하여 겁먹은 새끼를 진정시키세요.
|
6 |
+
3. 방어 주문을 사용하여 숲의 포식자로부터 새끼를 보호하세요.
|
7 |
+
4. 새끼를 무리로 이끌고 돌아가세요.
|
8 |
+
|
9 |
+
미션 완료 조건:
|
10 |
+
- 새끼를 성공적으로 추적하고 찾으세요.
|
11 |
+
- 새끼를 진정시키고 포식자로부터 보호하세요.
|
12 |
+
- 새끼를 무리와 무사히 재회시키세요.
|
harrypotter_scenario/scenario_1/sc1_storyline_ko/Scenario_1_Storyline_5_KR.txt
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
스토리라인 5: 마법 폭풍
|
2 |
+
미션: 마법 폭풍으로부터 유니콘 무리를 보호하세요.
|
3 |
+
단계:
|
4 |
+
1. "메테올로진크스 레칸토"를 사용하여 폭풍을 진정시키세요.
|
5 |
+
2. "프로테고 토탈럼"을 사용하여 유니콘 주위에 보호막을 만드세요.
|
6 |
+
3. 유니콘을 숲 내 안전한 대피소로 안내하세요.
|
7 |
+
4. 폭풍이 지나갈 때까지 보호막을 유지하세요.
|
8 |
+
|
9 |
+
미션 완료 조건:
|
10 |
+
- 마법 폭풍을 성공적으로 진정시키세요.
|
11 |
+
- 폭풍이 끝날 때까지 보호막을 유지하세요.
|
12 |
+
- 모든 유니콘이 안전하고 다치지 않도록 하세요.
|
harrypotter_scenario/scenario_2/Scenario_2.txt
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Scenario 2: The Chamber of Secrets
|
2 |
+
Mission: Discover the entrance to the Chamber of Secrets and defeat the monster within.
|
3 |
+
Situation: The player must solve puzzles to find the entrance and navigate the labyrinthine chamber.
|
4 |
+
Conditions for Clearing:
|
5 |
+
1. Solve the riddles to find the entrance.
|
6 |
+
2. Defeat the basilisk using the Sword of Gryffindor or other means.
|
7 |
+
3. Escape the collapsing chamber safely.
|
harrypotter_scenario/scenario_2/Scenario_2_KR.txt
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
시나리오 2: 비밀의 방
|
2 |
+
미션: 비밀의 방 입구를 찾아내고 그 안의 괴물을 물리치세요.
|
3 |
+
상황: 플레이어는 퍼즐을 풀어 입구를 찾아내고 복잡한 방을 탐험해야 합니다.
|
4 |
+
미션 완료 조건:
|
5 |
+
1. 수수께끼를 풀어 입구를 찾으세요.
|
6 |
+
2. 그리핀도르의 검 또는 다른 방법으로 바실리스크를 물리치세요.
|
7 |
+
3. 무너지는 방에서 무사히 탈출하세요.
|
harrypotter_scenario/scenario_3/Scenario_3.txt
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Scenario 3: The Triwizard Tournament - Dragon Challenge
|
2 |
+
Mission: Retrieve a golden egg from a dragon's nest during the Triwizard Tournament.
|
3 |
+
Situation: The player must face a fierce dragon guarding its nest.
|
4 |
+
Conditions for Clearing:
|
5 |
+
1. Evade the dragon's attacks and distractions.
|
6 |
+
2. Successfully retrieve the golden egg.
|
7 |
+
3. Return to the starting point without being severely injured.
|
harrypotter_scenario/scenario_3/Scenario_3_KR.txt
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
시나리오 3: 트리위저드 토너먼트 - 드래곤 챌린지
|
2 |
+
미션: 트리위저드 토너먼트 동안 드래곤의 둥지에서 황금 알을 가져오세요.
|
3 |
+
상황: 플레이어는 둥지를 지키는 사나운 드래곤에 맞서야 합니다.
|
4 |
+
미션 완료 조건:
|
5 |
+
1. 드래곤의 공격과 방해를 피하세요.
|
6 |
+
2. 황금 알을 성공적으로 가져오세요.
|
7 |
+
3. 심하게 다치지 않고 출발점으로 돌아오세요.
|
harrypotter_scenario/scenario_4/Scenario_4.txt
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Scenario 4: The Department of Mysteries Battle
|
2 |
+
Mission: Retrieve a prophecy from the Department of Mysteries while fighting off Death Eaters.
|
3 |
+
Situation: The player must navigate the maze-like Department of Mysteries and engage in combat with enemy wizards.
|
4 |
+
Conditions for Clearing:
|
5 |
+
1. Locate the correct prophecy.
|
6 |
+
2. Defeat the Death Eaters encountered.
|
7 |
+
3. Escape the Department of Mysteries with the prophecy intact.
|
harrypotter_scenario/scenario_4/Scenario_4_KR.txt
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
시나리오 4: 신비 부서 전투
|
2 |
+
미션: 신비 부서에서 예언을 가져오고 죽음을 먹는 자들과 싸우세요.
|
3 |
+
상황: 플레이어는 미로 같은 신비 부서를 탐험하고 적 마법사들과 전투를 벌여야 합니다.
|
4 |
+
미션 완료 조건:
|
5 |
+
1. 올바른 예언을 찾아내세요.
|
6 |
+
2. 만나는 죽음을 먹는 자들을 물리치세요.
|
7 |
+
3. 예언을 무사히 가지고 신비 부서에서 탈출하세요.
|
harrypotter_scenario/scenario_5/Scenario_5.txt
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Scenario 5: The Final Battle at Hogwarts
|
2 |
+
Mission: Defend Hogwarts from Voldemort and his forces during the final battle.
|
3 |
+
Situation: The player must participate in the defense of the castle, aiding allies and fighting enemies.
|
4 |
+
Conditions for Clearing:
|
5 |
+
1. Successfully defend key areas of Hogwarts (e.g., the Great Hall, the bridge).
|
6 |
+
2. Defeat major Death Eaters (e.g., Bellatrix Lestrange, Fenrir Greyback).
|
7 |
+
3. Assist in the final confrontation with Voldemort, ensuring his defeat.
|
harrypotter_scenario/scenario_5/Scenario_5_KR.txt
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
시나리오 5: 호그와트 최종 전투
|
2 |
+
미션: 최종 전투 동안 볼드모트와 그의 군대로부터 호그와트를 방어하세요.
|
3 |
+
상황: 플레이어는 성을 방어하는 데 참여하고 동료들을 돕고 적들과 싸워야 합니다.
|
4 |
+
미션 완료 조건:
|
5 |
+
1. 호그와트의 주요 구역(예: 그레이트 홀, 다리)을 성공적으로 방어하세요.
|
6 |
+
2. 주요 죽음을 먹는 자들(예: 벨라트릭스 레스트랭, 펜리르 그레이백)을 물리치세요.
|
7 |
+
3. 볼드모트와의 최종 대결에서 승리하여 그를 패배시키세요.
|
harrypotter_scenario/world_summary.txt
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
The "Harry Potter" series presents an adventurous world filled with magic and fantastical creatures. Key elements include:
|
2 |
+
|
3 |
+
1. **Magical Creatures**: The series features diverse beings like house-elves, centaurs, giants, and mythical creatures such as hippogriffs and thestrals.
|
4 |
+
|
5 |
+
2. **Spells and Magic**: Magic is central, with spells for various purposes and magical education at Hogwarts.
|
6 |
+
|
7 |
+
3. **Mystical Objects**: Objects like the Invisibility Cloak, Marauder’s Map, and Horcruxes play crucial roles in the plot.
|
8 |
+
|
9 |
+
4. **Adventures and Quests**: Harry and his friends embark on quests, including finding the Philosopher’s Stone and hunting Horcruxes.
|
10 |
+
|
11 |
+
5. **Magical Locations**: Enchanting places such as Hogwarts, Diagon Alley, and the Forbidden Forest add to the magical atmosphere.
|
12 |
+
|
13 |
+
6. **Conflicts and Battles**: The series features numerous battles against dark forces, including the climactic Battle of Hogwarts.
|
14 |
+
|
15 |
+
7. **Dark Arts and Defense**: Explores dark magic and characters’ efforts to defend against it.
|
16 |
+
|
17 |
+
8. **Enigmatic Characters**: Characters like Dumbledore and Snape have mysterious and adventurous backstories.
|
18 |
+
|
19 |
+
9. **Friendship and Loyalty**: The main characters' loyalty and friendship are tested through their adventures.
|
20 |
+
|
21 |
+
10. **Triumphs and Tragedies**: The series balances victories with significant losses and sacrifices.
|
22 |
+
|
23 |
+
These elements create a thrilling magical adventure, highlighting the battle between good and evil, the power of love and friendship, and the importance of choices.
|
main.py
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from create_world.creator import create_custom_world, create_scenario, create_storyline
|
2 |
+
from create_world.utils import load_txt, load_yaml
|
3 |
+
from create_character.personal_profile import generate_character_creation_questions, create_character_profile, parse_character_data_to_json
|
4 |
+
from play_game.main import play_game
|
5 |
+
|
6 |
+
def main():
|
7 |
+
config = load_yaml(path='create_world/prompt.yaml')
|
8 |
+
|
9 |
+
print("LRPG 게임에 오신것을 환영합니다. 이곳에서 당신만의 세계 속, 당신만의 캐릭터로, 당신만의 선택을 통해 당신만의 이야기를 만들어가세요.")
|
10 |
+
print('-----------------------------')
|
11 |
+
|
12 |
+
# Choose between custom and original games
|
13 |
+
custom = input('새로운 이야기를 만들고 싶으면 yes, 해리포터 게임을 불러오고 싶으면 no를 입력해 주세요: ')
|
14 |
+
if custom == 'yes':
|
15 |
+
topic, world_summary = create_custom_world(config['create_custom_world_prompt'], language='한국어', save=False)
|
16 |
+
scenario = create_scenario(topic, world_summary, config['create_scenario_prompt'], output_count=1)
|
17 |
+
round_stories = create_storyline(topic, scenario[0], config['create_storyline_prompt'])
|
18 |
+
|
19 |
+
else:
|
20 |
+
# Prepare world information
|
21 |
+
print("세계관 로딩중입니다... (해리포터)")
|
22 |
+
world_summary_path = 'harrypotter_scenario/world_summary.txt'
|
23 |
+
topic = 'harry potter'
|
24 |
+
world_summary = load_txt(world_summary_path)
|
25 |
+
|
26 |
+
scenario = create_scenario(topic, world_summary, config['create_scenario_prompt'], output_count=1)
|
27 |
+
round_stories = create_storyline(topic, scenario[0], config['create_storyline_prompt'])
|
28 |
+
|
29 |
+
print("세계가 만들어졌습니다!")
|
30 |
+
print('-----------------------------')
|
31 |
+
|
32 |
+
# Create Character
|
33 |
+
print("다음은 게임에서 플레이할 당신의 캐릭터를 만들겠습니다")
|
34 |
+
|
35 |
+
questions = generate_character_creation_questions(world_summary)
|
36 |
+
character_description = create_character_profile(questions)
|
37 |
+
character_profile = parse_character_data_to_json(character_description)
|
38 |
+
print("캐릭터 생성이 완료되었습니다!")
|
39 |
+
print("당신 캐릭터의 정보는 다음과 같습니다: ", character_profile)
|
40 |
+
print('-----------------------------')
|
41 |
+
|
42 |
+
# Play Game
|
43 |
+
play_game(round_stories, world_summary, character_profile)
|
44 |
+
|
45 |
+
|
46 |
+
if __name__ == "__main__":
|
47 |
+
main()
|
play_game/config.py
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
# Prompt
|
play_game/formatter.py
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
def to_round_result(round_effect_dict, round_result_explanation):
|
2 |
+
vocab_dictionary = {
|
3 |
+
"life": "생명력이",
|
4 |
+
"money": "돈이",
|
5 |
+
"stamina": "체력이",
|
6 |
+
"intelligence": "지능이",
|
7 |
+
"combat_power": "전투력이",
|
8 |
+
"agility": "민첩성이",
|
9 |
+
}
|
10 |
+
|
11 |
+
round_effect_str = ""
|
12 |
+
for status in ['player_restriction', 'player_capability']:
|
13 |
+
for key, value in round_effect_dict[status].items():
|
14 |
+
if value > 0:
|
15 |
+
round_effect_str += f"{vocab_dictionary[key]} {value}만큼 증가했습니다. "
|
16 |
+
elif value == 0:
|
17 |
+
round_effect_str += f"{vocab_dictionary[key]} 변화하지 않았습니다. "
|
18 |
+
else:
|
19 |
+
round_effect_str += f"{vocab_dictionary[key]} {value*(-1)}만큼 감소했습니다. "
|
20 |
+
|
21 |
+
return round_effect_str + round_result_explanation
|
22 |
+
|
23 |
+
|
24 |
+
def player_profile_to_str(player_profile_dict):
|
25 |
+
return '\n' + '\n'.join([f"{key}: {value}" for key, value in player_profile_dict.items()])
|
play_game/main.py
ADDED
@@ -0,0 +1,520 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from langchain_upstage import ChatUpstage
|
2 |
+
from langchain_core.prompts import PromptTemplate
|
3 |
+
from langchain_core.output_parsers import StrOutputParser, JsonOutputParser
|
4 |
+
import ast
|
5 |
+
from . import formatter
|
6 |
+
import random
|
7 |
+
from openai import OpenAI
|
8 |
+
|
9 |
+
|
10 |
+
|
11 |
+
# """
|
12 |
+
# Please answer in korean.
|
13 |
+
# You are best TRPG game master, and user is playing the game as a player with your guidance.
|
14 |
+
# The detailed information about the fictional universe of game is included in below Fictional Universe part.
|
15 |
+
# You should make round description which will be displayed to player that can explain this round story to make player understand the situation.
|
16 |
+
# Round Description should include the main event of this round story.
|
17 |
+
# Round Description should be finished with the question to player to make a decision for the next step.
|
18 |
+
# Round Description should consider this round story, entire story, player profile, player status, previous conversation, and previous round effect.
|
19 |
+
# Round Description should be interesting and naturally connected to the previous conversation, with long and detailed explanation of story.
|
20 |
+
# Round Description should be wrote in the tone of the game master.
|
21 |
+
# Round Description must start with explanation of player's previous round result and reason if below Previous Round Result section is not empty.
|
22 |
+
# Round Description should reflect the player's previous choices and their effects on this round story, so it can include slightly different from this round story but follow the entire story in big picture.
|
23 |
+
# ---
|
24 |
+
# Fictional Universe: {world_summary}
|
25 |
+
# ---
|
26 |
+
# Player Profile: {player_profile}
|
27 |
+
# ---
|
28 |
+
# Player Status: {player_status}
|
29 |
+
# ---
|
30 |
+
# Entire Story: {entire_story}
|
31 |
+
# ---
|
32 |
+
# This Round Story: {round_story}
|
33 |
+
# ---
|
34 |
+
# Previous Conversation: {previous_conversation}
|
35 |
+
# ---
|
36 |
+
# Previous Round Result: {previous_round_result}
|
37 |
+
# ---
|
38 |
+
# Round Description:
|
39 |
+
# """
|
40 |
+
|
41 |
+
def create_initial_conversation(world_summary, player_profile, player_restriction, player_capability, entire_story):
|
42 |
+
llm = ChatUpstage()
|
43 |
+
prompt_template = PromptTemplate.from_template(
|
44 |
+
"""
|
45 |
+
Please answer in korean.
|
46 |
+
You are best TRPG game master, and user is playing the game as a player with your guidance.
|
47 |
+
The detailed information about the fictional universe of game is included in below Fictional Universe part.
|
48 |
+
Please explain the initial introduction which includes the greeting message as a game master and explanation of the universe and situation to make player understand the context.
|
49 |
+
Introduction should include brief explanation of the story before the first round begins, but do not include the spoiler of the detailed entire story.
|
50 |
+
Introduction should include explanation of the player's profile, restriction, and capability to make player understand the context.
|
51 |
+
Introduction should include background knowledge of the fictional universe to make player understand the situation.
|
52 |
+
Introduction should be wrote in the tone of the game master.
|
53 |
+
---
|
54 |
+
Fictional Universe: {world_summary}
|
55 |
+
---
|
56 |
+
Player Profile: {player_profile}
|
57 |
+
---
|
58 |
+
Player Restriction: {player_restriction}
|
59 |
+
---
|
60 |
+
Player Capability: {player_capability}
|
61 |
+
---
|
62 |
+
Entire Story: {entire_story}
|
63 |
+
---
|
64 |
+
Introduction:
|
65 |
+
"""
|
66 |
+
)
|
67 |
+
chain = prompt_template | llm | StrOutputParser()
|
68 |
+
introduction = chain.invoke({ "world_summary": world_summary, "player_profile": player_profile, "player_restriction": player_restriction, "player_capability": player_capability, "entire_story": entire_story })
|
69 |
+
return introduction
|
70 |
+
|
71 |
+
|
72 |
+
|
73 |
+
def create_round_description(world_summary, player_profile, player_restriction, player_capability, entire_story, round_story, previous_conversation, previous_round_result):
|
74 |
+
llm = ChatUpstage()
|
75 |
+
prompt_template = PromptTemplate.from_template(
|
76 |
+
"""
|
77 |
+
Please answer in korean.
|
78 |
+
You are best TRPG game master, and user is playing the game as a player with your guidance.
|
79 |
+
The detailed information about the fictional universe of game is included in below Fictional Universe part.
|
80 |
+
Please create round description which will be displayed to player that can explain this round story to make player understand the situation.
|
81 |
+
Round Description should include the main event of this round story.
|
82 |
+
Round Description should be finished with the question to player to make a decision for the next step.
|
83 |
+
Round Description should consider this round story, entire story, player profile, player restriction, player capability, previous conversation, and previous round effect.
|
84 |
+
Round Description should be interesting and naturally connected to the previous conversation, with long and detailed explanation of story.
|
85 |
+
Round Description should be wrote in the tone of the game master.
|
86 |
+
Round Description must start with explanation of player's previous round result and reason if below Previous Round Result section is not empty.
|
87 |
+
Round Description should reflect the player's previous choices and their effects on this round story, so it can include slightly different from this round story but follow the entire story in big picture.
|
88 |
+
---
|
89 |
+
Fictional Universe: {world_summary}
|
90 |
+
---
|
91 |
+
Player Profile: {player_profile}
|
92 |
+
---
|
93 |
+
Player Restriction: {player_restriction}
|
94 |
+
---
|
95 |
+
Player Capability: {player_capability}
|
96 |
+
---
|
97 |
+
Entire Story: {entire_story}
|
98 |
+
---
|
99 |
+
This Round Story: {round_story}
|
100 |
+
---
|
101 |
+
Previous Conversation: {previous_conversation}
|
102 |
+
---
|
103 |
+
Previous Round Result: {previous_round_result}
|
104 |
+
---
|
105 |
+
Round Description:
|
106 |
+
"""
|
107 |
+
)
|
108 |
+
chain = prompt_template | llm | StrOutputParser()
|
109 |
+
while(True):
|
110 |
+
round_description = chain.invoke({ "world_summary": world_summary, "player_profile": player_profile, "player_restriction": player_restriction, "player_capability": player_capability, "entire_story": entire_story, "round_story": round_story, "previous_conversation": previous_conversation, "previous_round_result": previous_round_result })
|
111 |
+
if "1." in round_description and '2.' in round_description:
|
112 |
+
continue
|
113 |
+
|
114 |
+
return round_description
|
115 |
+
|
116 |
+
|
117 |
+
def get_required_capabilities(world_summary, player_profile, player_restriction, player_capability, round_description, player_response):
|
118 |
+
llm = ChatUpstage()
|
119 |
+
prompt_template = PromptTemplate.from_template(
|
120 |
+
"""
|
121 |
+
Please answer in english.
|
122 |
+
You are best TRPG game master, and user is playing the game as a player with your guidance.
|
123 |
+
The detailed information about the fictional universe of game is included in below Fictional Universe part.
|
124 |
+
This round story is included in This Round Description part.
|
125 |
+
Please select the required player capability to successfully conduct the player's response action.
|
126 |
+
Required Capability should be selected from player capability.
|
127 |
+
Required Capability should be selected based on player response and round description.
|
128 |
+
Response should be python list format consists of string which is keys of required player capability.
|
129 |
+
---
|
130 |
+
Fictional Universe: {world_summary}
|
131 |
+
---
|
132 |
+
Player Profile: {player_profile}
|
133 |
+
---
|
134 |
+
Player Restriction: {player_restriction}
|
135 |
+
---
|
136 |
+
Player Capability: {player_capability}
|
137 |
+
---
|
138 |
+
Round Description: {round_description}
|
139 |
+
---
|
140 |
+
Player Response: {player_response}
|
141 |
+
---
|
142 |
+
Required Capability: ["Required capabilities on english", ...]
|
143 |
+
"""
|
144 |
+
)
|
145 |
+
chain = prompt_template | llm | StrOutputParser()
|
146 |
+
|
147 |
+
while(True):
|
148 |
+
try:
|
149 |
+
response = chain.invoke({ "world_summary": world_summary, "player_profile": player_profile, "player_restriction": player_restriction, "player_capability": player_capability, "round_description": round_description, "player_response": player_response })
|
150 |
+
response = '[' + response.split('[')[-1]
|
151 |
+
response_list = ast.literal_eval(response)
|
152 |
+
if len(response_list) != 0:
|
153 |
+
return response_list
|
154 |
+
except:
|
155 |
+
continue
|
156 |
+
|
157 |
+
|
158 |
+
def get_expected_result(world_summary, player_profile, player_restriction, player_capability, round_description, player_response):
|
159 |
+
llm = ChatUpstage()
|
160 |
+
prompt_template = PromptTemplate.from_template(
|
161 |
+
"""
|
162 |
+
Please answer in korean.
|
163 |
+
You are best TRPG game master, and user is playing the game as a player with your guidance.
|
164 |
+
The detailed information about the fictional universe of game is included in below Fictional Universe part.
|
165 |
+
This round story is included in This Round Description part.
|
166 |
+
Please create the effect from player's response from this round story, and reason to explain the effect.
|
167 |
+
Effect should be calculated based on player response, player restriction, player capability, player profile, and round description.
|
168 |
+
Effect should reduce or increase player restriction in range between (-10, 10) with reasonable amount from player response's action.
|
169 |
+
Effect should reduce or increase player capability in range between (-20, 20) with reasonable amount from player response's action if it seems to be change by player's action.
|
170 |
+
Reason should explain why the effect is happened based on player response and round description.
|
171 |
+
Reason should logically explain the amount of changes of player restriction and capability.
|
172 |
+
Reason should be wrote in the tone of the game master.
|
173 |
+
Response should follow below format which is python dictionary consist of effect and reason.
|
174 |
+
---
|
175 |
+
Fictional Universe: {world_summary}
|
176 |
+
---
|
177 |
+
Player Profile: {player_profile}
|
178 |
+
---
|
179 |
+
Player Restriction: {player_restriction}
|
180 |
+
---
|
181 |
+
Player Capability: {player_capability}
|
182 |
+
---
|
183 |
+
Round Description: {round_description}
|
184 |
+
---
|
185 |
+
Player Response: {player_response}
|
186 |
+
---
|
187 |
+
format:
|
188 |
+
{{
|
189 |
+
effect: {{
|
190 |
+
player_restriction: {{
|
191 |
+
life: integer of life amount change between (-10, 10),
|
192 |
+
money: integer of money amount change between (-10, 10)
|
193 |
+
}},
|
194 |
+
player_capability: {{
|
195 |
+
stamina: integer of stamina amount change between (-20, 20),
|
196 |
+
intelligence: integer of intelligence amount change between (-20, 20),
|
197 |
+
combat_power: integer of combat power amount change between (-20, 20),
|
198 |
+
agility: integer of agility amount change between (-20, 20),
|
199 |
+
}}
|
200 |
+
}}
|
201 |
+
reason: "reason of the effect which is changes of player restriction, and capability based on player response and round description",
|
202 |
+
}}
|
203 |
+
---
|
204 |
+
"""
|
205 |
+
)
|
206 |
+
chain = prompt_template | llm | JsonOutputParser()
|
207 |
+
|
208 |
+
while(True):
|
209 |
+
try:
|
210 |
+
response = chain.invoke({ "world_summary": world_summary, "player_profile": player_profile, "player_restriction": player_restriction, "player_capability": player_capability, "round_description": round_description, "player_response": player_response })
|
211 |
+
# response_dict = json.loads(response)
|
212 |
+
|
213 |
+
if response.get('effect').get('player_restriction') == None or response.get("effect").get('player_capability') == None or response.get("reason") == None:
|
214 |
+
raise Exception()
|
215 |
+
|
216 |
+
return response
|
217 |
+
except:
|
218 |
+
continue
|
219 |
+
|
220 |
+
|
221 |
+
def get_unexpected_result(world_summary, player_profile, player_restriction, player_capability, not_enough_capability, round_description, player_response):
|
222 |
+
llm = ChatUpstage()
|
223 |
+
prompt_template = PromptTemplate.from_template(
|
224 |
+
"""
|
225 |
+
Please answer in korean.
|
226 |
+
You are best TRPG game master, and user is playing the game as a player with your guidance.
|
227 |
+
The detailed information about the fictional universe of game is included in below Fictional Universe part.
|
228 |
+
This round story is included in This Round Description part.
|
229 |
+
Please create the unexpected effect from player's response from this round story, and reason to explain the effect.
|
230 |
+
Player's response is failed to do intended action because some player's capability is not enough to conduct the action.
|
231 |
+
Not enough capability is included in Required Capability part.
|
232 |
+
Effect should be calculated based on player response, player restriction, player capability, player profile, and round description.
|
233 |
+
Effect should reduce or increase player restriction in range between (-10, 10) with reasonable amount from player response's action failure.
|
234 |
+
Effect should reduce or increase player capability in range between (-20, 20) with reasonable amount from player response's action failure if it seems to be change by player's action failure.
|
235 |
+
Reason should explain why this kind of unintended effect is happened based on situation.
|
236 |
+
Reason should logically explain the amount of changes of player restriction and capability.
|
237 |
+
Reason should be wrote in the tone of the game master.
|
238 |
+
Response should follow below format which is python dictionary consist of effect and reason.
|
239 |
+
---
|
240 |
+
Fictional Universe: {world_summary}
|
241 |
+
---
|
242 |
+
Player Profile: {player_profile}
|
243 |
+
---
|
244 |
+
Player Restriction: {player_restriction}
|
245 |
+
---
|
246 |
+
Player Capability: {player_capability}
|
247 |
+
---
|
248 |
+
Required Capability: {not_enough_capability}
|
249 |
+
---
|
250 |
+
Round Description: {round_description}
|
251 |
+
---
|
252 |
+
Player Response: {player_response}
|
253 |
+
---
|
254 |
+
format:
|
255 |
+
{{
|
256 |
+
effect: {{
|
257 |
+
player_restriction: {{
|
258 |
+
life: integer of life amount change between (-10, 10),
|
259 |
+
money: integer of money amount change between (-10, 10)
|
260 |
+
}},
|
261 |
+
player_capability: {{
|
262 |
+
stamina: integer of stamina amount change between (-20, 20),
|
263 |
+
intelligence: integer of intelligence amount change between (-20, 20),
|
264 |
+
combat_power: integer of combat power amount change between (-20, 20),
|
265 |
+
agility: integer of agility amount change between (-20, 20),
|
266 |
+
}}
|
267 |
+
}}
|
268 |
+
reason: "reason of the effect which is changes of player restriction, and capability based on player response's failure and round description",
|
269 |
+
}}
|
270 |
+
---
|
271 |
+
"""
|
272 |
+
)
|
273 |
+
chain = prompt_template | llm | JsonOutputParser()
|
274 |
+
|
275 |
+
while(True):
|
276 |
+
try:
|
277 |
+
response = chain.invoke({ "world_summary": world_summary, "player_profile": player_profile, "player_restriction": player_restriction, "player_capability": player_capability, "not_enough_capability": not_enough_capability, "round_description": round_description, "player_response": player_response })
|
278 |
+
# response_dict = json.loads(response)
|
279 |
+
|
280 |
+
if response.get('effect').get('player_restriction') == None or response.get("effect").get('player_capability') == None or response.get("reason") == None:
|
281 |
+
raise Exception()
|
282 |
+
|
283 |
+
return response
|
284 |
+
except:
|
285 |
+
continue
|
286 |
+
|
287 |
+
|
288 |
+
def create_round_result(world_summary, player_profile, player_restriction, player_capability, round_description, player_response):
|
289 |
+
required_caps = get_required_capabilities(world_summary, player_profile, player_restriction, player_capability, round_description, player_response)
|
290 |
+
for cap in required_caps:
|
291 |
+
required_cap = player_capability.get(cap)
|
292 |
+
if required_cap and random.random() > required_cap/100:
|
293 |
+
# Failed to do intended action
|
294 |
+
return get_unexpected_result(world_summary, player_profile, player_restriction, player_capability, required_caps, round_description, player_response)
|
295 |
+
|
296 |
+
# Success to do intended action
|
297 |
+
return get_expected_result(world_summary, player_profile, player_restriction, player_capability, round_description, player_response)
|
298 |
+
|
299 |
+
|
300 |
+
|
301 |
+
|
302 |
+
|
303 |
+
def create_bad_ending(world_summary, player_profile, player_restriction, player_capability, entire_story, round_story, previous_conversation, round_result):
|
304 |
+
llm = ChatUpstage()
|
305 |
+
prompt_template = PromptTemplate.from_template(
|
306 |
+
"""
|
307 |
+
Please answer in korean.
|
308 |
+
You are best TRPG game master, and user is playing the game as a player with your guidance.
|
309 |
+
The detailed information about the fictional universe of game is included in below Fictional Universe part.
|
310 |
+
Player played the game and reached the bad ending by previous decisions.
|
311 |
+
Player reach bad ending when player restriction (life or money) is less or equal to 0.
|
312 |
+
Please create bad ending scenario based on previous conversation and previous round's result.
|
313 |
+
Consider fictional universe, player profile, player restriction, player capability, entire story, this round story, this round result, and previous conversation.
|
314 |
+
Bad ending story should be interesting and naturally connected to the previous conversation.
|
315 |
+
Bad ending should be wrote in the tone of the game master.
|
316 |
+
Bad ending should mention the reason in story why player reached the bad ending and make the story interesting.
|
317 |
+
Bad ending should mention the exact reason by telling player's restriction change.
|
318 |
+
---
|
319 |
+
Fictional Universe: {world_summary}
|
320 |
+
---
|
321 |
+
Player Profile: {player_profile}
|
322 |
+
---
|
323 |
+
Player Restriction: {player_restriction}
|
324 |
+
---
|
325 |
+
Player Capability: {player_capability}
|
326 |
+
---
|
327 |
+
Entire Story: {entire_story}
|
328 |
+
---
|
329 |
+
This Round Story: {round_story}
|
330 |
+
---
|
331 |
+
This Round Result: {round_result}
|
332 |
+
---
|
333 |
+
Previous Conversation: {previous_conversation}
|
334 |
+
---
|
335 |
+
Bad Ending:
|
336 |
+
"""
|
337 |
+
)
|
338 |
+
chain = prompt_template | llm | StrOutputParser()
|
339 |
+
bad_ending = chain.invoke({ "world_summary": world_summary, "player_profile": player_profile, "player_restriction": player_restriction, "player_capability": player_capability, "entire_story": entire_story, "round_story": round_story, "round_result": round_result, "previous_conversation": previous_conversation })
|
340 |
+
|
341 |
+
return bad_ending
|
342 |
+
|
343 |
+
|
344 |
+
def create_good_ending(world_summary, player_profile, player_restriction, player_capability, entire_story, previous_conversation):
|
345 |
+
llm = ChatUpstage()
|
346 |
+
prompt_template = PromptTemplate.from_template(
|
347 |
+
"""
|
348 |
+
Please answer in korean.
|
349 |
+
You are best TRPG game master, and user is playing the game as a player with your guidance.
|
350 |
+
The detailed information about the fictional universe of game is included in below Fictional Universe part.
|
351 |
+
User played the game and reached the entire story's ending which means game win, so it's good ending.
|
352 |
+
Please create good ending scenario based on previous conversation and entires story.
|
353 |
+
Consider fictional universe, player profile, player restriction, player capability, entire story, and previous conversation.
|
354 |
+
Good ending should be appropriate for the end of the story, which comprehensively organizes the entire story flow and the user's decisions.
|
355 |
+
Good ending story should be interesting and naturally connected to the previous conversation.
|
356 |
+
Good ending should be last part which finishes the game story and make user happy.
|
357 |
+
Good ending should be wrote in the tone of the game master.
|
358 |
+
---
|
359 |
+
Fictional Universe: {world_summary}
|
360 |
+
---
|
361 |
+
Player Profile: {player_profile}
|
362 |
+
---
|
363 |
+
Player Restriction: {player_restriction}
|
364 |
+
---
|
365 |
+
Player Capability: {player_capability}
|
366 |
+
---
|
367 |
+
Entire Story: {entire_story}
|
368 |
+
---
|
369 |
+
Previous Conversation: {previous_conversation}
|
370 |
+
---
|
371 |
+
Good Ending:
|
372 |
+
"""
|
373 |
+
)
|
374 |
+
chain = prompt_template | llm | StrOutputParser()
|
375 |
+
good_ending = chain.invoke({ "world_summary": world_summary, "player_profile": player_profile, "player_restriction": player_restriction, "player_capability": player_capability, "entire_story": entire_story, "previous_conversation": previous_conversation })
|
376 |
+
|
377 |
+
return good_ending
|
378 |
+
|
379 |
+
|
380 |
+
def convert_to_image_prompt(topic, world_summary, player_profile, round_description):
|
381 |
+
llm = ChatUpstage()
|
382 |
+
prompt_template = PromptTemplate.from_template(
|
383 |
+
"""
|
384 |
+
Please provide visual prompt of the host message which will be used for text-image generation.
|
385 |
+
Host message is D&D game scenario scene with choices on {topic}.
|
386 |
+
Visual prompt should consider the context and user persona.
|
387 |
+
Visual prompt should represent the scenario's scene and choices in the image.
|
388 |
+
---
|
389 |
+
Fictional Universe: {world_summary}
|
390 |
+
---
|
391 |
+
Player Profile: {player_profile}
|
392 |
+
---
|
393 |
+
This Round Scenario: {round_description}
|
394 |
+
---
|
395 |
+
Visual Prompt:
|
396 |
+
"""
|
397 |
+
)
|
398 |
+
chain = prompt_template | llm | StrOutputParser()
|
399 |
+
|
400 |
+
response = chain.invoke({ "topic": topic, "world_summary": world_summary, "player_profile": player_profile, "round_description": round_description })
|
401 |
+
|
402 |
+
return response
|
403 |
+
|
404 |
+
|
405 |
+
def generate_image(prompt):
|
406 |
+
client = OpenAI()
|
407 |
+
response = client.images.generate(
|
408 |
+
model="dall-e-3",
|
409 |
+
prompt=prompt,
|
410 |
+
size="1024x1024",
|
411 |
+
quality="standard",
|
412 |
+
n=1,
|
413 |
+
)
|
414 |
+
|
415 |
+
image_url = response.data[0].url
|
416 |
+
|
417 |
+
return image_url
|
418 |
+
|
419 |
+
|
420 |
+
def play_game(game_scenario, world_summary, player_profile):
|
421 |
+
entire_story = [f"{idx+1}. {scenario["title"]}\n{scenario["story"]}\n\n" for idx, scenario in enumerate(game_scenario)]
|
422 |
+
conversation = ""
|
423 |
+
previous_conversation = ""
|
424 |
+
previous_round_result = ""
|
425 |
+
player_restriction = { "life": 10, "money": 10 }
|
426 |
+
player_capability = player_profile["params"]
|
427 |
+
# player_status = { "life": 10, "money": 10, **player_profile["params"] }
|
428 |
+
player_profile_str = formatter.player_profile_to_str(player_profile)
|
429 |
+
introduction = create_initial_conversation(world_summary, player_profile_str, player_restriction, player_capability, entire_story)
|
430 |
+
print(introduction)
|
431 |
+
print('-----------------------------')
|
432 |
+
for round_idx, round_scenario in enumerate(game_scenario):
|
433 |
+
# Display this round story
|
434 |
+
round_story = f"{round_idx+1}. {round_scenario["title"]}: {round_scenario["story"]}\n"
|
435 |
+
round_description = create_round_description(world_summary, player_profile_str, player_restriction, player_capability, entire_story, round_story, previous_conversation, previous_round_result)
|
436 |
+
print(f"Round {round_idx+1}: {round_description}")
|
437 |
+
|
438 |
+
# Get player's response
|
439 |
+
player_response = input("당신만의 결정을 내려주세요! 하나의 문장으로 당신이 할 행동과 그에 대한 근거와 이유를 명확하게 설명해주세요: ")
|
440 |
+
conversation += f"Game Master: {round_description}\nPlayer: {player_response}\n"
|
441 |
+
|
442 |
+
# Reflect the result and update player status
|
443 |
+
round_result = create_round_result(world_summary, player_profile_str, player_restriction, player_capability, round_description, player_response)
|
444 |
+
round_effect = round_result["effect"]
|
445 |
+
round_result_explanation = round_result["reason"]
|
446 |
+
for key, value in round_effect["player_restriction"].items():
|
447 |
+
if player_restriction.get(key) is not None:
|
448 |
+
modified_value = player_restriction[key] + value
|
449 |
+
if modified_value > 10:
|
450 |
+
player_restriction[key] = 10
|
451 |
+
elif modified_value < -10:
|
452 |
+
player_restriction[key] = -10
|
453 |
+
else:
|
454 |
+
player_restriction[key] = modified_value
|
455 |
+
|
456 |
+
for key, value in round_effect["player_capability"].items():
|
457 |
+
if player_capability.get(key) is not None:
|
458 |
+
modified_value = player_capability[key] + value
|
459 |
+
if modified_value > 100:
|
460 |
+
player_capability[key] = 100
|
461 |
+
elif modified_value < -100:
|
462 |
+
player_capability[key] = -100
|
463 |
+
else:
|
464 |
+
player_capability[key] = modified_value
|
465 |
+
|
466 |
+
# Update previous conversation and round effect
|
467 |
+
previous_conversation = conversation
|
468 |
+
previous_round_result = formatter.to_round_result(round_effect, round_result_explanation)
|
469 |
+
|
470 |
+
print('-----------------------------')
|
471 |
+
|
472 |
+
# Check whether player lose the game
|
473 |
+
if player_restriction["life"] <= 0 or player_restriction["money"] <= 0:
|
474 |
+
bad_ending = create_bad_ending(world_summary, player_profile_str, player_restriction, player_capability, entire_story, round_story, previous_conversation, previous_round_result)
|
475 |
+
print(bad_ending)
|
476 |
+
return
|
477 |
+
|
478 |
+
|
479 |
+
|
480 |
+
# Reach the good ending of the game
|
481 |
+
good_ending = create_good_ending(world_summary, player_profile_str, player_restriction, player_capability, entire_story, previous_conversation)
|
482 |
+
print(good_ending)
|
483 |
+
return
|
484 |
+
|
485 |
+
if __name__ == "__main__":
|
486 |
+
game_scenario = [
|
487 |
+
{
|
488 |
+
"title": "호그와트로의 초대",
|
489 |
+
"story": "플레이어들은 각자 호그와트 마법학교로부터 편지를 받습니다. 편지에는 특별한 임무가 주어졌으며, 이 임무를 완수하면 마법사의 유산을 받을 수 있다는 내용이 적혀 있습니다. 호그와트에 도착한 플레이어들은 알버스 덤블도어 교수에게서 직접 임무의 첫 번째 단서를 받습니다. 첫 번째 임무는 금지된 숲에서 특별한 마법 생물을 찾아내는 것입니다. 이 생물은 마법사의 유산으로 가는 길을 알려주는 중요한 열쇠입니다."
|
490 |
+
},
|
491 |
+
{
|
492 |
+
"title": "고대의 서적",
|
493 |
+
"story": "첫 번째 단서에서 얻은 정보를 바탕으로 플레이어들은 호그와트 도서관의 금서 섹션에서 고대의 서적을 찾아야 합니다. 이 서적에는 마법사의 유산에 대한 중요한 정보가 담겨있습니다. 그러나 서적을 찾는 것은 쉽지 않습니다. 호그와트의 다양한 퍼즐과 함정을 풀어야 하며, 경쟁하는 다른 학생들과 마법 대결을 벌여야 할 수도 있습니다."
|
494 |
+
},
|
495 |
+
{
|
496 |
+
"title": "비밀의 방",
|
497 |
+
"story": "고대의 서적에서 얻은 단서로 플레이어들은 호그와트 내에 숨겨진 비밀의 방을 찾아야 합니다. 이 방은 마법사의 유산과 관련된 또 다른 단서를 가지고 있습니다. 비밀의 방에 들어가기 위해서는 호그와트의 역사를 깊이 이해해야 하며, 과거의 마법사들이 남긴 여러 가지 시험을 통과해야 합니다"
|
498 |
+
},
|
499 |
+
{
|
500 |
+
"title": "시간의 미로",
|
501 |
+
"story": "비밀의 방에서 얻은 단서는 플레이어들을 시간의 미로로 안내합니다. 시간의 미로는 마법으로 보호된 장소로, 과거와 현재가 교차하는 곳입니다. 플레이어들은 미로 속에서 과거의 중요한 사건들을 목격하고, 이를 통해 마법사의 유산에 대한 마지막 단서를 얻어야 합니다. 하지만 미로 속에는 강력한 적과 함정이 도사리고 있습니다."
|
502 |
+
},
|
503 |
+
{
|
504 |
+
"title": "마법사의 유산",
|
505 |
+
"story": "모든 단서를 모은 플레이어들은 마침내 마법사의 유산이 숨겨진 장소에 도착합니다. 이곳에서 최종 보스와의 결전이 벌어집니다. 보스를 물리치고 유산을 손에 넣기 위해서는 플레이어들의 모든 지혜와 용기가 필요합니다. 최종 결전을 승리한 후, 플레이어들은 마법사의 유산을 손에 넣고 각자의 길을 떠납니다."
|
506 |
+
}
|
507 |
+
]
|
508 |
+
world_summary = "이 TRPG의 세계관은 해리포터 시리즈의 마법 세계를 배경으로 합니다. 플레이어들은 호그와트 마법학교의 학생으로, 전설적인 마법사의 유산을 찾기 위한 특별한 임무를 받습니다. 이 세계는 마법, 신비로운 생물, 고대의 비밀, 그리고 위험이 가득한 곳으로, 플레이어들은 호그와트 내외의 다양한 장소를 탐험하며 퍼즐과 적들을 상대해야 합니다. 각 단계를 통해 마법사의 유산에 가까워지며, 그 과정에서 마법의 지식과 능력을 향상시키고 협동과 용기를 시험받게 됩니다. 이 시나리오는 플레이어들이 마법 세계의 깊은 비밀을 풀어가며, 자신의 잠재력을 발휘하는 흥미진진한 모험을 제공합니다."
|
509 |
+
player_profile = {
|
510 |
+
"name": "이현구",
|
511 |
+
"gender": "male",
|
512 |
+
"job": "마법사",
|
513 |
+
"params": {
|
514 |
+
"stamina": 34,
|
515 |
+
"intelligence": 58,
|
516 |
+
"combat_power": 45,
|
517 |
+
"agility": 100
|
518 |
+
}
|
519 |
+
}
|
520 |
+
play_game(game_scenario, world_summary, player_profile)
|
prototype/app.py
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import random
|
3 |
+
from langchain_chroma import Chroma
|
4 |
+
from langchain_upstage import UpstageEmbeddings
|
5 |
+
from langchain.docstore.document import Document
|
6 |
+
from langchain_core.prompts import PromptTemplate
|
7 |
+
from langchain_upstage import ChatUpstage
|
8 |
+
import os
|
9 |
+
from dotenv import load_dotenv
|
10 |
+
from langchain_core.output_parsers import StrOutputParser
|
11 |
+
from generate_image import generate_image, download_image
|
12 |
+
import requests
|
13 |
+
from IPython.display import display, Image
|
14 |
+
from langchain_community.vectorstores.oraclevs import OracleVS
|
15 |
+
import oracledb
|
16 |
+
from langchain_community.vectorstores.utils import DistanceStrategy
|
17 |
+
|
18 |
+
|
19 |
+
dotenv_path = os.path.join(os.path.dirname(__file__), '.env') # Path to the .env file
|
20 |
+
load_dotenv(dotenv_path) # Load the environment variables from the .env file
|
21 |
+
|
prototype/conversation.py
ADDED
@@ -0,0 +1,515 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from langchain_chroma import Chroma
|
2 |
+
from langchain_upstage import UpstageEmbeddings
|
3 |
+
from langchain.docstore.document import Document
|
4 |
+
from langchain_core.prompts import PromptTemplate
|
5 |
+
from langchain_upstage import ChatUpstage
|
6 |
+
import os
|
7 |
+
from dotenv import load_dotenv
|
8 |
+
from langchain_core.output_parsers import StrOutputParser
|
9 |
+
from generate_image import generate_image, download_image
|
10 |
+
import requests
|
11 |
+
from IPython.display import display, Image
|
12 |
+
from langchain_community.vectorstores.oraclevs import OracleVS
|
13 |
+
import oracledb
|
14 |
+
from langchain_community.vectorstores.utils import DistanceStrategy
|
15 |
+
import ast, json
|
16 |
+
|
17 |
+
|
18 |
+
dotenv_path = os.path.join(os.path.dirname(__file__), '.env') # Path to the .env file
|
19 |
+
load_dotenv(dotenv_path) # Load the environment variables from the .env file
|
20 |
+
|
21 |
+
|
22 |
+
def get_context(invoke_text, topic):
|
23 |
+
username=os.environ["DB_USER"]
|
24 |
+
password=os.environ["DB_PASSWORD"]
|
25 |
+
dsn=os.environ["DSN"]
|
26 |
+
con = oracledb.connect(user=username, password=password, dsn=dsn)
|
27 |
+
try:
|
28 |
+
conn23c = oracledb.connect(user=username, password=password, dsn=dsn)
|
29 |
+
print("Connection successful!", conn23c.version)
|
30 |
+
except Exception as e:
|
31 |
+
print("Connection failed!")
|
32 |
+
|
33 |
+
upstage_embeddings = UpstageEmbeddings(model="solar-embedding-1-large")
|
34 |
+
vector_store = OracleVS(client=conn23c,
|
35 |
+
embedding_function=upstage_embeddings,
|
36 |
+
table_name=f"text_embeddings_{topic}",
|
37 |
+
distance_strategy=DistanceStrategy.DOT_PRODUCT)
|
38 |
+
retriever = vector_store.as_retriever()
|
39 |
+
return retriever.invoke(invoke_text)
|
40 |
+
|
41 |
+
|
42 |
+
def make_user_persona(topic, user_answer, previous_conversation, language):
|
43 |
+
llm = ChatUpstage()
|
44 |
+
default_template = """
|
45 |
+
Please answer in {language}.
|
46 |
+
You are best TRPG host with {topic}. And user is playing game with you.
|
47 |
+
The detailed information about the fictional universe of game is included in context.
|
48 |
+
Please answer in a exciting tone as a host.
|
49 |
+
Before starting the game, user wants to create a persona.
|
50 |
+
Please provide 1 question to user to create a persona.
|
51 |
+
You must make only one question per response.
|
52 |
+
Each question should be related to user's character, such as name, gender, personality or any related features with the topic.
|
53 |
+
When asking questions, please consider the topic and make the question interesting.
|
54 |
+
You should consider previous conversation to prevent asking same questions, and make the diverse and context related questions.
|
55 |
+
First question should include the introduction of the game.
|
56 |
+
---
|
57 |
+
Context: {context}
|
58 |
+
---
|
59 |
+
Previous Conversation: {previous_conversation}
|
60 |
+
"""
|
61 |
+
|
62 |
+
prompt_template = PromptTemplate.from_template(default_template)
|
63 |
+
chain = prompt_template | llm | StrOutputParser()
|
64 |
+
# context = get_context(f"What is related information about {topic}?")
|
65 |
+
context = ""
|
66 |
+
|
67 |
+
response = chain.invoke({ "context": context, "topic": topic, "language": language, "previous_conversation": previous_conversation if previous_conversation else None })
|
68 |
+
|
69 |
+
return {
|
70 |
+
"response": response,
|
71 |
+
}
|
72 |
+
|
73 |
+
|
74 |
+
# topic="HarryPotter", "Trump"
|
75 |
+
def summarize_user_persona(topic, previous_conversation, language):
|
76 |
+
llm = ChatUpstage()
|
77 |
+
default_template = """
|
78 |
+
Please answer in {language}.
|
79 |
+
You are best {topic} TRPG host, and user is playing game with you.
|
80 |
+
The detailed information about the fictional universe of game is included in context.
|
81 |
+
Please answer in a exciting tone as a host.
|
82 |
+
Before starting the game, user wants to create a persona.
|
83 |
+
You have finished asking questions to user to create a persona and it's included in the previous conversation.
|
84 |
+
Please summarize the character's information and make the user's persona.
|
85 |
+
Response will be used for context in game scenario so answer in appropriate format.
|
86 |
+
Length of persona should not be too long or too short.
|
87 |
+
Do not include any other information or rhetoric except user's persona.
|
88 |
+
---
|
89 |
+
Context: {context}
|
90 |
+
---
|
91 |
+
Previous Conversation: {previous_conversation}
|
92 |
+
---
|
93 |
+
Summarized User Persona:
|
94 |
+
"""
|
95 |
+
|
96 |
+
prompt_template = PromptTemplate.from_template(default_template)
|
97 |
+
chain = prompt_template | llm | StrOutputParser()
|
98 |
+
# context = get_context(f"What is related information about {topic}?")
|
99 |
+
context = ""
|
100 |
+
|
101 |
+
response = chain.invoke({ "context": context, "topic": topic, "language": language, "previous_conversation": previous_conversation })
|
102 |
+
response = response.replace("User Persona:", "게임을 진행하는 플레이어는 다음과 같은 캐릭터를 가지고 있습니다.")
|
103 |
+
|
104 |
+
return {
|
105 |
+
"response": response,
|
106 |
+
}
|
107 |
+
|
108 |
+
def initialize_chat(topic, language):
|
109 |
+
llm = ChatUpstage()
|
110 |
+
prompt_template = PromptTemplate.from_template(
|
111 |
+
"""
|
112 |
+
Please answer in {language} language.
|
113 |
+
You are best D&D host. And user is playing D&D game with you.
|
114 |
+
Please answer in a exciting tone as a host.
|
115 |
+
User has to make choices in the game.
|
116 |
+
User has 10 life points and 10 gold coins.
|
117 |
+
Please make scenario of most interesting event from the following context, and provide diverse choices which user can select.
|
118 |
+
For each round, you should provide at least 3 choices and total round should be less than 50.
|
119 |
+
You should explain situation and choices in detail.
|
120 |
+
If someone's conversation is included in the scenario, please express it in colloquial terms that fit the person's character as much as possible.
|
121 |
+
Choices should be diverse and interesting, and each choice will reduce or increase user's life points or gold coins.
|
122 |
+
If user make appropriate decision, user's life points or gold coins will increase.
|
123 |
+
If user make inappropriate decision, user's life points or gold coins will decrease and decreasing amount should not be larger than amount of user owned.
|
124 |
+
If the user choice is not included in suggestion, please ask him again to choose from the possible options.
|
125 |
+
You should consider previous conversation and context to make the scenario.
|
126 |
+
If user's life points or gold coins are less than 0, user will lose the game and get bad ending.
|
127 |
+
Also when total round is more than 50, user will lose the game and get bad ending.
|
128 |
+
If user's life points or gold coins are more than 20, user will win the game and get good ending.
|
129 |
+
This is the first round of the game. Please provide the scenario and choices for the user.
|
130 |
+
---
|
131 |
+
Context: {context}
|
132 |
+
"""
|
133 |
+
)
|
134 |
+
chain = prompt_template | llm | StrOutputParser()
|
135 |
+
context = get_context(f"What is related information about {topic}?")
|
136 |
+
|
137 |
+
response = chain.invoke({ "context": context, "language": language })
|
138 |
+
|
139 |
+
return {
|
140 |
+
"response": response,
|
141 |
+
"previous_conversation": f"Host: {response}"
|
142 |
+
}
|
143 |
+
|
144 |
+
def make_conversation(user_choice, previous_conversation, user_persona, language):
|
145 |
+
llm = ChatUpstage()
|
146 |
+
prompt_template = PromptTemplate.from_template(
|
147 |
+
"""
|
148 |
+
Please answer in {language} language.
|
149 |
+
You are best D&D host. And user is playing D&D game with you.
|
150 |
+
Please answer in a exciting tone as a host.
|
151 |
+
User has to make choices in the game.
|
152 |
+
User has 10 life points and 10 gold coins.
|
153 |
+
Please make scenario of most interesting event from the following context, and provide diverse choices which user can select.
|
154 |
+
For each round, you should provide at least 3 choices and total round should be less than 50.
|
155 |
+
You should explain situation and choices in detail.
|
156 |
+
If someone's conversation is included in the scenario, please express it in colloquial terms that fit the person's character as much as possible.
|
157 |
+
Choices should be diverse and interesting, and each choice will reduce or increase user's life points or gold coins.
|
158 |
+
If user make appropriate decision, user's life points or gold coins will increase.
|
159 |
+
If user make inappropriate decision, user's life points or gold coins will decrease and decreasing amount should not be larger than amount of user owned.
|
160 |
+
When user select a choice, you should provide the result of the choice, but you should not show result before user select the choice.
|
161 |
+
If the user choice is not included in suggestion, please ask him again to choose from the possible options.
|
162 |
+
You should consider user's persona, previous conversation and context to make the scenario.
|
163 |
+
If user's life points or gold coins are less than 0, user will lose the game and get bad ending and print <<END>> on last.
|
164 |
+
Also when total round is more than 50, user will lose the game and get bad ending and print <<END>> on last.
|
165 |
+
If user's life points or gold coins are more than 20, user will win the game and get good ending and print <<END>> on last.
|
166 |
+
---
|
167 |
+
User Choice: {user_choice}
|
168 |
+
---
|
169 |
+
Previous Conversation: {previous_conversation}
|
170 |
+
---
|
171 |
+
Context: {context}
|
172 |
+
---
|
173 |
+
User Persona: {user_persona}
|
174 |
+
"""
|
175 |
+
)
|
176 |
+
chain = prompt_template | llm | StrOutputParser()
|
177 |
+
context = get_context(f"What is related information about {user_choice}?")
|
178 |
+
|
179 |
+
response = chain.invoke({ "user_choice": user_choice, "previous_conversation": previous_conversation, "context": context, "user_persona": user_persona, "language": language })
|
180 |
+
|
181 |
+
return {
|
182 |
+
"response": response,
|
183 |
+
"is_last": response.find("<<END>>") == -1,
|
184 |
+
"previous_conversation": f"{previous_conversation}\nUser: {user_choice}\nHost: {response}"
|
185 |
+
}
|
186 |
+
|
187 |
+
|
188 |
+
# prompt_template = PromptTemplate.from_template(
|
189 |
+
# """
|
190 |
+
# Please answer in {language}.
|
191 |
+
# You are best {topic} TRPG host, and user is playing game with you.
|
192 |
+
# The detailed information about the fictional universe of game is included in context.
|
193 |
+
# Please create a scenario with an interesting story and ending, with 5 rounds.
|
194 |
+
# Each round should include an interesting event and allow the user to move on with the appropriate choice for that step.
|
195 |
+
# Scenario should be related to fictional universe and user's persona.
|
196 |
+
# Response format should follow the below format.
|
197 |
+
# ---
|
198 |
+
# Context: {context}
|
199 |
+
# ---
|
200 |
+
# User Persona: {user_persona}
|
201 |
+
# ---
|
202 |
+
# Format:
|
203 |
+
# 1: [주요 이벤트 1의 제목]
|
204 |
+
# [주요 이벤트 1의 줄거리] <END>
|
205 |
+
# 2: [주요 이벤트 2의 제목]
|
206 |
+
# [주요 이벤트 2의 줄거리] <END>
|
207 |
+
# 3: [주요 이벤트 3의 제목]
|
208 |
+
# [주요 이벤트 3의 줄거리] <END>
|
209 |
+
# 4: [주요 이벤트 4의 제목]
|
210 |
+
# [주요 이벤트 4의 줄거리] <END>
|
211 |
+
# 5: [주요 이벤트 5의 제목]
|
212 |
+
# [주요 이벤트 5의 줄거리] <END>
|
213 |
+
# ---
|
214 |
+
# Scenario:
|
215 |
+
# 1.
|
216 |
+
# 2.
|
217 |
+
# 3.
|
218 |
+
# 4.
|
219 |
+
# 5.
|
220 |
+
# """
|
221 |
+
# )
|
222 |
+
def create_scenario(topic, user_persona, language):
|
223 |
+
llm = ChatUpstage()
|
224 |
+
prompt_template = PromptTemplate.from_template(
|
225 |
+
"""
|
226 |
+
Please answer in {language}.
|
227 |
+
You are best {topic} TRPG host, and user is playing game with you.
|
228 |
+
The detailed information about the fictional universe of game is included in context.
|
229 |
+
Please create a scenario with an interesting story and ending, with 5 rounds.
|
230 |
+
Each round should include an interesting event and allow the user to move on with the appropriate choice for that step.
|
231 |
+
Scenario should be related to fictional universe and user's persona.
|
232 |
+
Response should be python list of 5 json element with "title" and "story".
|
233 |
+
---
|
234 |
+
Context: {context}
|
235 |
+
---
|
236 |
+
User Persona: {user_persona}
|
237 |
+
---
|
238 |
+
Scenario:
|
239 |
+
"""
|
240 |
+
)
|
241 |
+
chain = prompt_template | llm | StrOutputParser()
|
242 |
+
# context = get_context(f"What is related information about {user_choice}?")
|
243 |
+
context = ""
|
244 |
+
|
245 |
+
while(True):
|
246 |
+
try:
|
247 |
+
response = chain.invoke({ "topic": topic, "context": context, "user_persona": user_persona, "language": language })
|
248 |
+
scenarios = ast.literal_eval(response)
|
249 |
+
entire_story = [f"{idx}. {scenario["title"]}\n{scenario["story"]}\n\n" for idx, scenario in enumerate(scenarios)]
|
250 |
+
|
251 |
+
return {
|
252 |
+
"response": response,
|
253 |
+
"scenarios": scenarios,
|
254 |
+
"entire_story": entire_story,
|
255 |
+
}
|
256 |
+
except:
|
257 |
+
continue
|
258 |
+
|
259 |
+
|
260 |
+
# ""
|
261 |
+
# Please answer in {language}.
|
262 |
+
# You are best {topic} TRPG host, and user is playing game with you.
|
263 |
+
# The detailed information about the fictional universe of game is included in context.
|
264 |
+
# Please create this round's content based on User Persona, Entire Story, This Round Story, and Previous Conversation.
|
265 |
+
# You should make 1. round content which will be displayed to user that can explain the situation, 2. user choices which will be displayed to user that can explain the choice's action, and 3. choice effect which will not be displayed to user that can explain the effect of the choice.
|
266 |
+
# User has life points and coins which will be affected by the choices.
|
267 |
+
# Each choice should be diverse and interesting, and reduce or increase user's life points or coins but this effect should not be mentioned in the choice.
|
268 |
+
# You should consider previous conversation and user's choices to make the content and mention their effect on the beginning.
|
269 |
+
# Story should be interesting and naturally connected to the previous conversation.
|
270 |
+
# Your response should be python dictionary with below format.
|
271 |
+
# ---
|
272 |
+
# Context: {context}
|
273 |
+
# ---
|
274 |
+
# User Persona: {user_persona}
|
275 |
+
# ---
|
276 |
+
# Entire Story: {entire_story}
|
277 |
+
# ---
|
278 |
+
# This Round Story: {round_story}
|
279 |
+
# ---
|
280 |
+
# Previous Conversation: {previous_conversation}
|
281 |
+
# ---
|
282 |
+
# format:
|
283 |
+
# \{
|
284 |
+
# round_story: \"Story to display to player\",
|
285 |
+
# user_choices: ["Choice 1 description", "Choice 2 description", "Choice 3 description"],
|
286 |
+
# choice_effect: [\{ "money": integer of money amount change, "life: integer of life amount change \}],
|
287 |
+
# \}
|
288 |
+
# User Choice:
|
289 |
+
# - [Choice1: Description]
|
290 |
+
# - [Choice2: Description]
|
291 |
+
# - [Choice3: Description]
|
292 |
+
# Choice Effect:
|
293 |
+
# - [Choice1: Money+2, Life-1]
|
294 |
+
# - [Choice2: Life-2]
|
295 |
+
# - [Choice3: Life+4, Money+1]
|
296 |
+
# ---
|
297 |
+
# Round Story:
|
298 |
+
# User Choice:
|
299 |
+
# Choice Effect:
|
300 |
+
# """
|
301 |
+
|
302 |
+
def play_game(topic, user_persona, language, entire_story, round_story, previous_conversation, previous_choice_effect):
|
303 |
+
llm = ChatUpstage()
|
304 |
+
prompt_template = PromptTemplate.from_template(
|
305 |
+
"""
|
306 |
+
Please answer in {language}.
|
307 |
+
You are best {topic} TRPG host, and user is playing game with you.
|
308 |
+
The detailed information about the fictional universe of game is included in context.
|
309 |
+
Please create this round's content based on User Persona, Entire Story, This Round Story, and Previous Conversation.
|
310 |
+
You should make 1. round content which will be displayed to user that can explain the situation, 2. user choices which will be displayed to user that can explain the choice's action, and 3. choice effect which will not be displayed to user that can explain the effect of the choice.
|
311 |
+
User has life points and coins which will be affected by the choices.
|
312 |
+
Choices should be at least 3 and total round should be less than 7.
|
313 |
+
Each choice should be diverse and interesting, and reduce or increase user's life points or coins between (-10, 10), but this effect should not be shown in user choices.
|
314 |
+
The effect of each choice must change the logical and reasonable amount as a result of the actions taken by the choice.
|
315 |
+
Each choice should be long and detailed with explanation of the choice's action.
|
316 |
+
You should consider previous conversation and user's choices to make the content and mention their effect on the beginning.
|
317 |
+
Round content must start with explanation of user's previous choices and their effects if below Previous Choice Effect is not empty.
|
318 |
+
Round content should be interesting and naturally connected to the previous conversation, with long and detailed explanation of the situation.
|
319 |
+
Your response should be python dictionary with below format.
|
320 |
+
---
|
321 |
+
Context: {context}
|
322 |
+
---
|
323 |
+
User Persona: {user_persona}
|
324 |
+
---
|
325 |
+
Entire Story: {entire_story}
|
326 |
+
---
|
327 |
+
This Round Story: {round_story}
|
328 |
+
---
|
329 |
+
Previous Conversation: {previous_conversation}
|
330 |
+
---
|
331 |
+
Previous Choice Effect: {previous_choice_effect}
|
332 |
+
---
|
333 |
+
format:
|
334 |
+
{{
|
335 |
+
round_content: \"Explanation of previous choice's effect and changes by it's result from the story. And then, Story explanation to show player\",
|
336 |
+
user_choices: ["Choice 1 description", "Choice 2 description", "Choice 3 description"],
|
337 |
+
choice_effects: [{{ "money": integer of money amount change, "life: integer of life amount change }}],
|
338 |
+
}}
|
339 |
+
---
|
340 |
+
"""
|
341 |
+
)
|
342 |
+
chain = prompt_template | llm | StrOutputParser()
|
343 |
+
# context = get_context(f"What is related information about {user_choice}?")
|
344 |
+
context = ""
|
345 |
+
|
346 |
+
while(True):
|
347 |
+
try:
|
348 |
+
response = chain.invoke({ "language": language, "topic": topic, "context": context, "user_persona": user_persona, "entire_story": entire_story, "round_story": round_story, "previous_conversation": previous_conversation, "previous_choice_effect": previous_choice_effect })
|
349 |
+
response_dict = json.loads(response)
|
350 |
+
|
351 |
+
return {
|
352 |
+
"response": response,
|
353 |
+
"round_content": response_dict["round_content"],
|
354 |
+
"user_choices": response_dict["user_choices"],
|
355 |
+
"choice_effects": response_dict["choice_effects"],
|
356 |
+
}
|
357 |
+
except:
|
358 |
+
continue
|
359 |
+
|
360 |
+
|
361 |
+
def generate_bad_end(language, topic, user_persona, previous_conversation, previous_choice_effect):
|
362 |
+
llm = ChatUpstage()
|
363 |
+
prompt_template = PromptTemplate.from_template(
|
364 |
+
"""
|
365 |
+
Please answer in {language}.
|
366 |
+
You are best {topic} TRPG host, and user is playing game with you.
|
367 |
+
The detailed information about the fictional universe of game is included in context.
|
368 |
+
User played the game and reached the bad ending by previous choice.
|
369 |
+
Please create bad ending scenario based on previous conversation and previous choice's effect.
|
370 |
+
Consider topic, content, user persona, previous conversation and previous choice's effect to make the scenario.
|
371 |
+
Bad ending story should be interesting and naturally connected to the previous conversation.
|
372 |
+
You should mention the reason of bad ending and make the story interesting.
|
373 |
+
---
|
374 |
+
Context: {context}
|
375 |
+
---
|
376 |
+
User Persona: {user_persona}
|
377 |
+
---
|
378 |
+
Previous Conversation: {previous_conversation}
|
379 |
+
---
|
380 |
+
Previous Choice Effect: {previous_choice_effect}
|
381 |
+
---
|
382 |
+
Bad Ending Scenario:
|
383 |
+
"""
|
384 |
+
)
|
385 |
+
chain = prompt_template | llm | StrOutputParser()
|
386 |
+
# context = get_context(f"What is related information about {user_choice}?")
|
387 |
+
context = ""
|
388 |
+
|
389 |
+
response = chain.invoke({ "language": language, "topic": topic, "context": context, "user_persona": user_persona, "previous_conversation": previous_conversation, "previous_choice_effect": previous_choice_effect })
|
390 |
+
|
391 |
+
return {
|
392 |
+
"response": response,
|
393 |
+
}
|
394 |
+
|
395 |
+
|
396 |
+
def generate_good_end(language, topic, user_persona, entire_story, previous_conversation):
|
397 |
+
llm = ChatUpstage()
|
398 |
+
prompt_template = PromptTemplate.from_template(
|
399 |
+
"""
|
400 |
+
Please answer in {language}.
|
401 |
+
You are best {topic} TRPG host, and user is playing game with you.
|
402 |
+
The detailed information about the fictional universe of game is included in context.
|
403 |
+
User played the game and reached the entire story's ending which means game win, so it's good ending.
|
404 |
+
Please create good ending scenario based on previous conversation and entires story and user choices.
|
405 |
+
Consider topic, content, user persona, entire story, and previous conversation to make the scenario.
|
406 |
+
Good ending story should be interesting and naturally connected to the previous conversation.
|
407 |
+
Good ending should be last part which finishes the game story and make user happy.
|
408 |
+
You should mention the reason of bad ending and make the story interesting.
|
409 |
+
---
|
410 |
+
Context: {context}
|
411 |
+
---
|
412 |
+
User Persona: {user_persona}
|
413 |
+
---
|
414 |
+
Entire Story: {entire_story}
|
415 |
+
---
|
416 |
+
Previous Conversation: {previous_conversation}
|
417 |
+
---
|
418 |
+
Good Ending Scenario:
|
419 |
+
"""
|
420 |
+
)
|
421 |
+
chain = prompt_template | llm | StrOutputParser()
|
422 |
+
# context = get_context(f"What is related information about {user_choice}?")
|
423 |
+
context = ""
|
424 |
+
|
425 |
+
response = chain.invoke({ "language": language, "topic": topic, "context": context, "user_persona": user_persona, "entire_story": entire_story, "previous_conversation": previous_conversation })
|
426 |
+
|
427 |
+
return {
|
428 |
+
"response": response,
|
429 |
+
}
|
430 |
+
|
431 |
+
def convert_to_image_prompt(topic, user_persona, host_message):
|
432 |
+
llm = ChatUpstage()
|
433 |
+
prompt_template = PromptTemplate.from_template(
|
434 |
+
"""
|
435 |
+
Please provide visual prompt of the host message which will be used for text-image generation.
|
436 |
+
Host message is D&D game scenario scene with choices on {topic}.
|
437 |
+
Visual prompt should consider the context and user persona.
|
438 |
+
Visual prompt should represent the scenario's scene and choices in the image.
|
439 |
+
---
|
440 |
+
User Persona: {user_persona}
|
441 |
+
---
|
442 |
+
Context: {context}
|
443 |
+
---
|
444 |
+
Host Message: {host_message}
|
445 |
+
"""
|
446 |
+
)
|
447 |
+
chain = prompt_template | llm | StrOutputParser()
|
448 |
+
context = get_context(f"What is related information about {topic}?")
|
449 |
+
|
450 |
+
response = chain.invoke({ "topic": topic, "host_message": host_message, "context": context, "user_persona": user_persona })
|
451 |
+
|
452 |
+
return {
|
453 |
+
"response": response
|
454 |
+
}
|
455 |
+
|
456 |
+
|
457 |
+
def generate_scenario_image(topic, user_persona, host_message):
|
458 |
+
prompt_response = convert_to_image_prompt(topic, user_persona, host_message)["response"]
|
459 |
+
image_url = generate_image(prompt_response)
|
460 |
+
image_data = download_image(image_url)
|
461 |
+
|
462 |
+
return image_data, image_url
|
463 |
+
|
464 |
+
|
465 |
+
def display_image_from_url(url):
|
466 |
+
response = requests.get(url)
|
467 |
+
img = Image(data=response.content)
|
468 |
+
display(img)
|
469 |
+
|
470 |
+
|
471 |
+
def main():
|
472 |
+
topic = input("Enter the topic: ")
|
473 |
+
language = input("Enter the language: ")
|
474 |
+
user_persona = None
|
475 |
+
previous_conversation = None
|
476 |
+
|
477 |
+
|
478 |
+
# Create User Persona
|
479 |
+
user_persona_answer = None
|
480 |
+
for i in range(5):
|
481 |
+
question = make_user_persona(topic, user_persona_answer, previous_conversation, language)
|
482 |
+
if i == 0:
|
483 |
+
previous_conversation = f"Host: {question['response']}"
|
484 |
+
else:
|
485 |
+
previous_conversation = f"{previous_conversation}\nHost: {question['response']}\n"
|
486 |
+
|
487 |
+
print(question["response"])
|
488 |
+
user_persona_answer = input("Enter your answer: ")
|
489 |
+
previous_conversation = f"{previous_conversation}\nUser: {user_persona_answer}\nHost: "
|
490 |
+
|
491 |
+
if i == 4:
|
492 |
+
user_persona = summarize_user_persona(topic, previous_conversation, language)["response"]
|
493 |
+
|
494 |
+
# Initialize Game
|
495 |
+
init_chat = initialize_chat(topic, language)
|
496 |
+
print(init_chat["response"])
|
497 |
+
|
498 |
+
# Play Game
|
499 |
+
while True:
|
500 |
+
user_choice = input("Enter your choice: ")
|
501 |
+
response = make_conversation(user_choice, init_chat["previous_conversation"], user_persona, language)
|
502 |
+
|
503 |
+
# image_data, image_url = generate_scenario_image(topic, user_persona, response["response"])
|
504 |
+
# print("IMAGE_URL: ", image_url)
|
505 |
+
# display_image_from_url(image_url)
|
506 |
+
|
507 |
+
print(response["response"])
|
508 |
+
|
509 |
+
if response["is_last"]:
|
510 |
+
break
|
511 |
+
|
512 |
+
return
|
513 |
+
|
514 |
+
if __name__ == "__main__":
|
515 |
+
main()
|
prototype/demo/app.py
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
|
3 |
+
def greet(name, intensity):
|
4 |
+
return "Hello, " + name + "!" * int(intensity)
|
5 |
+
|
6 |
+
gr.Number(label='Age', info='In years, must be greater than 0')
|
7 |
+
|
8 |
+
demo = gr.Interface(
|
9 |
+
fn=greet,
|
10 |
+
inputs=["text", "slider"],
|
11 |
+
outputs=["text"],
|
12 |
+
title="세계관을 만들어 보세요. ",
|
13 |
+
description="<img src='https://github.com/gradio-app/gradio/blob/main/guides/assets/annotated.png?raw=true' alt='annotated'>",
|
14 |
+
)
|
15 |
+
|
16 |
+
|
17 |
+
|
18 |
+
demo.launch()
|