commit 0a9571de3d2725aa7c3fa31cd3ef02af6177fdf8 Author: Thomas van der Meer Date: Fri Nov 22 14:20:37 2024 +0100 Initial commit diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..e4fba21 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/L1.ipynb b/L1.ipynb new file mode 100644 index 0000000..88240fd --- /dev/null +++ b/L1.ipynb @@ -0,0 +1,566 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "5ac6b5d5-fce7-4fcb-b1a8-c490ea5c5325", + "metadata": { + "id": "173e06c5-4b07-4e3b-a67a-5c3e141beb2c" + }, + "source": [ + "# L1: Hierarchical Content Generation" + ] + }, + { + "cell_type": "markdown", + "id": "8be81d7d-2c10-45c4-beb4-0e7d7910fa8a", + "metadata": {}, + "source": [ + "

🚨\n", + "  Different Run Results: The output generated by AI models can vary with each execution due to their dynamic, probabilistic nature. Don't be surprised if your results differ from those shown in the video.
\n", + "To maintain consistency, the notebooks are run with a 'world state' consistent with the video at the start of each notebook.

" + ] + }, + { + "cell_type": "markdown", + "id": "aa3e6edc-62d6-44ea-a7da-5863f11bf7a4", + "metadata": {}, + "source": [ + "
\n", + "

💻   Access requirements.txt and helper.py files: 1) click on the \"File\" option on the top menu of the notebook and then 2) click on \"Open\".\n", + "\n", + "

⬇   Download Notebooks: 1) click on the \"File\" option on the top menu of the notebook and then 2) click on \"Download as\" and select \"Notebook (.ipynb)\".

\n", + "\n", + "

📒   For more help, please see the \"Appendix – Tips, Help, and Download\" Lesson.

\n", + "\n", + "
" + ] + }, + { + "cell_type": "markdown", + "id": "ac19c917-fe0b-4140-abc4-33d6b3972aaa", + "metadata": { + "id": "173e06c5-4b07-4e3b-a67a-5c3e141beb2c" + }, + "source": [ + "## Creating a World" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "93056e48-cb80-46a8-8990-bc5b4ae9506b", + "metadata": { + "height": 149 + }, + "outputs": [], + "source": [ + "system_prompt = f\"\"\"\n", + "Your job is to help create interesting fantasy worlds that \\\n", + "players would love to play in.\n", + "Instructions:\n", + "- Only generate in plain text without formatting.\n", + "- Use simple clear language without being flowery.\n", + "- You must stay below 3-5 sentences for each description.\n", + "\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "330f3e78-9a20-45b1-afa5-a65289046a55", + "metadata": { + "height": 200 + }, + "outputs": [], + "source": [ + "world_prompt = f\"\"\"\n", + "Generate a creative description for a unique fantasy world with an\n", + "interesting concept around cities build on the backs of massive beasts.\n", + "\n", + "Output content in the form:\n", + "World Name: \n", + "World Description: \n", + "\n", + "World Name:\"\"\"\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "7f928bb3-c6e4-4891-a154-3b3f225e8d21", + "metadata": { + "height": 217 + }, + "outputs": [], + "source": [ + "from together import Together\n", + "from helper import get_together_api_key,load_env \n", + "\n", + "client = Together(api_key=get_together_api_key())\n", + "\n", + "output = client.chat.completions.create(\n", + " model=\"meta-llama/Llama-3-70b-chat-hf\",\n", + " messages=[\n", + " {\"role\": \"system\", \"content\": system_prompt},\n", + " {\"role\": \"user\", \"content\": world_prompt}\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "22d161d6-cb78-4c3d-b858-5659d5d16761", + "metadata": { + "height": 47 + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "World Name: Kyropeia\n", + "\n", + "World Description: Kyropeia is a realm where ancient, gargantuan creatures known as the \"Colossi\" roam the land, their massive bodies serving as the foundation for sprawling metropolises. These beasts, born from the earth and infused with primal magic, have been domesticated by humans over centuries, allowing cities to be built upon their backs, shoulders, and even within their hollowed-out bodies. As the Colossi migrate across the landscape, their cities in tow, the inhabitants of Kyropeia must adapt to a life of constant motion and evolution.\n" + ] + } + ], + "source": [ + "world_output =output.choices[0].message.content\n", + "print(world_output)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "b993a1df-7735-43da-9872-45bb2bdc9fb2", + "metadata": { + "height": 132 + }, + "outputs": [], + "source": [ + "world_output = world_output.strip()\n", + "world = {\n", + " \"name\": world_output.split('\\n')[0].strip()\n", + " .replace('World Name: ', ''),\n", + " \"description\": '\\n'.join(world_output.split('\\n')[1:])\n", + " .replace('World Description:', '').strip()\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "5dbb561d-b115-4ba5-81f4-2ae3dfd4a71f", + "metadata": {}, + "source": [ + "## Generating Kingdoms" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "9531806c-8b56-4fe5-bb15-d7c8d9b1cef8", + "metadata": { + "height": 336 + }, + "outputs": [], + "source": [ + "kingdom_prompt = f\"\"\"\n", + "Create 3 different kingdoms for a fantasy world.\n", + "For each kingdom generate a description based on the world it's in. \\\n", + "Describe important leaders, cultures, history of the kingdom.\\\n", + "\n", + "Output content in the form:\n", + "Kingdom 1 Name: \n", + "Kingdom 1 Description: \n", + "Kingdom 2 Name: \n", + "Kingdom 2 Description: \n", + "Kingdom 3 Name: \n", + "Kingdom 3 Description: \n", + "\n", + "World Name: {world['name']}\n", + "World Description: {world['description']}\n", + "\n", + "Kingdom 1\"\"\"\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "f7c87417-695c-443a-baa1-b354a9664d14", + "metadata": { + "height": 132 + }, + "outputs": [], + "source": [ + "output = client.chat.completions.create(\n", + " model=\"meta-llama/Llama-3-70b-chat-hf\",\n", + " messages=[\n", + " {\"role\": \"system\", \"content\": system_prompt},\n", + " {\"role\": \"user\", \"content\": kingdom_prompt}\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "ca6d59b6-60f1-4241-8906-29be576d00e6", + "metadata": { + "height": 336 + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Created kingdom \"Valtoria\" in Kyropeia\n", + "\n", + "Kingdom 1 Description: Valtoria is a kingdom built upon the back of the largest Colossus, the \"Earthshaker\". Its people, skilled engineers and architects, have developed a unique culture of mobility and adaptability. Led by the wise and cunning Queen Lyra, Valtoria is a hub of innovation, where inventors and tinkerers work tirelessly to improve the city's infrastructure and harness the primal magic of the Colossi.\n" + ] + } + ], + "source": [ + "kingdoms = {}\n", + "kingdoms_output = output.choices[0].message.content\n", + "\n", + "for output in kingdoms_output.split('\\n\\n'):\n", + " kingdom_name = output.strip().split('\\n')[0] \\\n", + " .split('Name: ')[1].strip()\n", + " print(f'Created kingdom \"{kingdom_name}\" in {world[\"name\"]}')\n", + " kingdom_description = output.strip().split('\\n')[1] \\\n", + " .split('Description: ')[1].strip()\n", + " kingdom = {\n", + " \"name\": kingdom_name,\n", + " \"description\": kingdom_description,\n", + " \"world\": world['name']\n", + " }\n", + " kingdoms[kingdom_name] = kingdom\n", + "world['kingdoms'] = kingdoms\n", + "\n", + "print(f'\\nKingdom 1 Description: \\\n", + "{kingdom[\"description\"]}')" + ] + }, + { + "cell_type": "markdown", + "id": "e124ee9d-694b-4259-b38a-34ee106c5ca4", + "metadata": {}, + "source": [ + "## Generating Towns" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "8f111ae4-dc95-49e1-ae15-4b5fd515988e", + "metadata": { + "height": 387 + }, + "outputs": [], + "source": [ + "def get_town_prompt(world, kingdom):\n", + " return f\"\"\"\n", + " Create 3 different towns for a fantasy kingdom abd world. \\\n", + " Describe the region it's in, important places of the town, \\\n", + " and interesting history about it. \\\n", + " \n", + " Output content in the form:\n", + " Town 1 Name: \n", + " Town 1 Description: \n", + " Town 2 Name: \n", + " Town 2 Description: \n", + " Town 3 Name: \n", + " Town 3 Description: \n", + " \n", + " World Name: {world['name']}\n", + " World Description: {world['description']}\n", + " \n", + " Kingdom Name: {kingdom['name']}\n", + " Kingdom Description {kingdom['description']}\n", + " \n", + " Town 1 Name:\"\"\"\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "90d892c0-b74e-4c75-bcf7-b055fa6712cd", + "metadata": { + "height": 506 + }, + "outputs": [], + "source": [ + "def create_towns(world, kingdom):\n", + " print(f'\\nCreating towns for kingdom: {kingdom[\"name\"]}...')\n", + " output = client.chat.completions.create(\n", + " model=\"meta-llama/Llama-3-70b-chat-hf\",\n", + " messages=[\n", + " {\"role\": \"system\", \"content\": system_prompt},\n", + " {\"role\": \"user\", \"content\": get_town_prompt(world, kingdom)}\n", + " ],\n", + " )\n", + " towns_output = output.choices[0].message.content\n", + " \n", + " towns = {}\n", + " for output in towns_output.split('\\n\\n'):\n", + " town_name = output.strip().split('\\n')[0]\\\n", + " .split('Name: ')[1].strip()\n", + " print(f'- {town_name} created')\n", + " \n", + " town_description = output.strip().split('\\n')[1]\\\n", + " .split('Description: ')[1].strip()\n", + " \n", + " town = {\n", + " \"name\": town_name,\n", + " \"description\": town_description,\n", + " \"world\": world['name'],\n", + " \"kingdom\": kingdom['name']\n", + " }\n", + " towns[town_name] = town\n", + " kingdom[\"towns\"] = towns" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "b534d860-5b6b-43f8-9c60-e345fbaf1d9e", + "metadata": { + "height": 115 + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Creating towns for kingdom: Valtoria...\n", + "- Ironhaven created\n", + "- Skypoint created\n", + "- Emberwatch created\n", + "\n", + "Town 1 Description: Located on the Earthshaker's left shoulder, Ironhaven is a bustling industrial town where Valtoria's engineers and inventors craft innovative machinery and tools to aid the kingdom's constant migration. The town's central square features the famous Ironhaven Forge, a massive, Colossus-powered furnace that fuels the town's industry. Ironhaven's history is marked by a great fire that ravaged the town, prompting the development of advanced fire-resistant materials and safety measures.\n" + ] + } + ], + "source": [ + "for kingdom in kingdoms.values():\n", + " create_towns(world, kingdom) \n", + "\n", + "town = list(kingdom['towns'].values())[0]\n", + "print(f'\\nTown 1 Description: \\\n", + "{town[\"description\"]}')" + ] + }, + { + "cell_type": "markdown", + "id": "fff397b4-3ac6-4737-a232-8e68df1471b0", + "metadata": {}, + "source": [ + "## Generating Non-Player Characters (NPC's)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "f31adacd-a37a-4de9-9be5-aadf56fff988", + "metadata": { + "height": 421 + }, + "outputs": [], + "source": [ + "def get_npc_prompt(world, kingdom, town): \n", + " return f\"\"\"\n", + " Create 3 different characters based on the world, kingdom \\\n", + " and town they're in. Describe the character's appearance and \\\n", + " profession, as well as their deeper pains and desires. \\\n", + " \n", + " Output content in the form:\n", + " Character 1 Name: \n", + " Character 1 Description: \n", + " Character 2 Name: \n", + " Character 2 Description: \n", + " Character 3 Name: \n", + " Character 3 Description: \n", + " \n", + " World Name: {world['name']}\n", + " World Description: {world['description']}\n", + " \n", + " Kingdom Name: {kingdom['name']}\n", + " Kingdom Description: {kingdom['description']}\n", + " \n", + " Town Name: {town['name']}\n", + " Town Description: {town['description']}\n", + " \n", + " Character 1 Name:\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "a301bf4d-1f99-475b-ae88-4a05e7537e3f", + "metadata": { + "height": 540 + }, + "outputs": [], + "source": [ + "def create_npcs(world, kingdom, town):\n", + " print(f'\\nCreating characters for the town of: {town[\"name\"]}...')\n", + " output = client.chat.completions.create(\n", + " model=\"meta-llama/Llama-3-70b-chat-hf\",\n", + " messages=[\n", + " {\"role\": \"system\", \"content\": system_prompt},\n", + " {\"role\": \"user\", \"content\": get_npc_prompt(world, kingdom, town)}\n", + " ],\n", + " temperature=1 #added to generate unique names\n", + " )\n", + "\n", + " npcs_output = output.choices[0].message.content\n", + " npcs = {}\n", + " for output in npcs_output.split('\\n\\n'):\n", + " npc_name = output.strip().split('\\n')[0]\\\n", + " .split('Name: ')[1].strip()\n", + " print(f'- \"{npc_name}\" created')\n", + " \n", + " npc_description = output.strip().split('\\n')[1\\\n", + " ].split('Description: ')[1].strip()\n", + " \n", + " npc = {\n", + " \"name\": npc_name,\n", + " \"description\": npc_description,\n", + " \"world\": world['name'],\n", + " \"kingdom\": kingdom['name'],\n", + " \"town\": town['name']\n", + " }\n", + " npcs[npc_name] = npc\n", + " town[\"npcs\"] = npcs" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "36f0021d-41fa-4f3d-aed9-32d4fcb69dcb", + "metadata": { + "height": 98 + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Creating characters for the town of: Ironhaven...\n", + "- \"Kaelin Darkhammer\" created\n", + "- \"Lyrien Stonefist\" created\n", + "- \"Aria Skypaw\" created\n", + "\n", + "Creating characters for the town of: Skypoint...\n", + "- \"Kaida Renn\" created\n", + "- \"Lyrien Frost\" created\n", + "- \"Arin Vexar\" created\n", + "\n", + "Creating characters for the town of: Emberwatch...\n", + "- \"Lyri kickoff\" created\n", + "- \"Kael Red nexus\" created\n", + "- \"background AdwasherNote: You will save your world state to a file different than the one shown in the video to allow future lessons to be consistent with the video. If later wish to build your own worlds, you will want to load your file rather than the saved file." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "2dce3195-2c76-42da-9e49-c7a17e5ab08b", + "metadata": { + "height": 234 + }, + "outputs": [], + "source": [ + "import json\n", + "\n", + "def save_world(world, filename):\n", + " with open(filename, 'w') as f:\n", + " json.dump(world, f)\n", + "\n", + "def load_world(filename):\n", + " with open(filename, 'r') as f:\n", + " return json.load(f)\n", + "\n", + "#save_world(world, '../shared_data/Kyropeia.json')\n", + "save_world(world, './shared_data/YourWorld_L1.json') #save to your version" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f64fc076-32b5-41ca-b755-6ccbd52347c0", + "metadata": { + "height": 30 + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/helper.py b/helper.py new file mode 100644 index 0000000..d611664 --- /dev/null +++ b/helper.py @@ -0,0 +1,21 @@ +# Add your utilities or helper functions to this file. + +import os +from dotenv import load_dotenv, find_dotenv + +# these expect to find a .env file at the directory above the lesson. # the format for that file is (without the comment) # API_KEYNAME=AStringThatIsTheLongAPIKeyFromSomeService +def load_env(): + _ = load_dotenv(find_dotenv()) + +def save_world(world, filename): + with open(filename, 'w') as f: + json.dump(world, f) + +def load_world(filename): + with open(filename, 'r') as f: + return json.load(f) + +def get_together_api_key(): + load_env() + together_api_key = os.getenv("TOGETHER_API_KEY") + return together_api_key \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5412746 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,7 @@ +[project] +name = "llm-game" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d9d72b9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +# requirements file +# sandbox had python 3.10 + +together==1.2.0 +python-dotenv~=1.0.1 +pydantic==2.8.2 +gradio==4.44.1 \ No newline at end of file diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..10c28fb --- /dev/null +++ b/uv.lock @@ -0,0 +1,7 @@ +version = 1 +requires-python = ">=3.12" + +[[package]] +name = "llm-game" +version = "0.1.0" +source = { virtual = "." }