diff --git a/community-contributions/dungeon_extration_game/game/__init__.py b/community-contributions/dungeon_extration_game/game/__init__.py new file mode 100644 index 0000000..a70d9a4 --- /dev/null +++ b/community-contributions/dungeon_extration_game/game/__init__.py @@ -0,0 +1,18 @@ +"""AI Mastered Dungeon Extraction Game initialization module.""" + +from logging import basicConfig, getLogger + +from dotenv import load_dotenv + + +# Environment initialization. +load_dotenv(override=True) + +# Setup the global logger. +LOG_STYLE = '{' +LOG_LEVEL = 'INFO' +LOG_FORMAT = ('{asctime} {levelname:<8} {processName}({process}) ' + '{threadName} {name} {lineno} "{message}"') +basicConfig(level=LOG_LEVEL, style='{', format=LOG_FORMAT) + +getLogger(__name__).info('INITIALIZED GAME LOGGER') diff --git a/community-contributions/dungeon_extration_game/game/__main__.py b/community-contributions/dungeon_extration_game/game/__main__.py new file mode 100644 index 0000000..73a0e35 --- /dev/null +++ b/community-contributions/dungeon_extration_game/game/__main__.py @@ -0,0 +1,43 @@ +"""AI Mastered Dungeon Extraction Game main entrypoint module.""" + +from logging import getLogger + +from .config import SCENE_PROMPT, SCENE_STYLE, START_SCENE, STORYTELLER_PROMPT +from .gameplay import Gameplay_Config, get_gameplay_function +from .illustrator import draw_dalle_2, draw_dalle_3, draw_gemini, draw_gpt, draw_grok +from .illustrator import draw_grok_x +from .interface import Interface_Config, get_interface +from .storyteller import narrate + + +# Choose draw function. +DRAW_FUNCTION = draw_dalle_2 + +# Configure the game. +game_config = Gameplay_Config( + draw_func=DRAW_FUNCTION, + narrate_func=narrate, + scene_style=SCENE_STYLE, + scene_prompt=SCENE_PROMPT, + storyteller_prompt=STORYTELLER_PROMPT, + error_img='images/machine.jpg', + error_narrator='NEURAL SINAPSIS ERROR\n\n{ex}\n\nEND OF LINE\n\nRE-SUBMIT_', + error_illustrator='NEURAL PROJECTION ERROR\n\n{ex}\n\nEND OF LINE\n\nRE-SUBMIT_',) + +ui_config = Interface_Config( + start_img='images/chair.jpg', + place_img='images/machine.jpg', + description_label='Cognitive Projection', + title_label='The Neural Nexus', + input_button='Imprint your will', + input_label='Cognitive Imprint', + input_command='Awaiting neural imprint…', + start_scene=START_SCENE) + + +_logger = getLogger(__name__) + +if __name__ == '__main__': + _logger.info('STARTING GAME...') + gameplay_function = get_gameplay_function(game_config) + get_interface(gameplay_function, ui_config).launch(inbrowser=True, inline=False) diff --git a/community-contributions/dungeon_extration_game/game/config.py b/community-contributions/dungeon_extration_game/game/config.py new file mode 100644 index 0000000..057b781 --- /dev/null +++ b/community-contributions/dungeon_extration_game/game/config.py @@ -0,0 +1,146 @@ +"""AI Mastered Dungeon Extraction Game Configuration module.""" + +from logging import getLogger + + +# Define a sample scene description for testing purposes. +SAMPLE_SCENE = '''A shadow-drenched chamber lies buried deep within the bowels of an +ancient castle, its silence broken only by the faint creak of age-old stone. +The walls, cloaked in thick cobwebs, seem to whisper forgotten stories, +while the air hangs heavy with the pungent scent of mildew and centuries of decay. +Dust dances in the dim light that filters through cracks in the ceiling, +casting eerie patterns across the cold floor. As your eyes adjust to the gloom, +you notice a narrow door to the north, slightly ajar, as if inviting or warning, and +in the far corner, half-swallowed by darkness, a figure stands motionless. +Its presence is felt before it's seen, watching, waiting''' + +# Define the starting scene text. +# This is intentionally excluded from the model’s narrative context, the 'history', +# by design, to prevent potential leakage into the game’s storyline. +START_SCENE = '''You stand before the Neural Nexus, a convergence of arcane circuitry +and deep cognition. It doesn't operate with buttons or commands. It responds to intent. + +Forged in forgotten labs and powered by living code, the Nexus is designed to interface +directly with your mind. Not to simulate reality, but to generate it. +The Nexus does not load worlds. It listens. + +If you choose to sit, the Nexus will initiate full neural synchronization. +Your thoughts will become terrain. Your instincts, adversaries. +Your imagination, the architect. + +Once the link is active, you must describe the nature of the challenge you wish to face. +A shifting maze? A sentient machine? A trial of memory and time? +Speak it aloud or think it clearly. The Nexus will listen. + +🜁 When you're ready, take your seat. The system awaits your signal...''' + +# Define an image prompt, mind that Grok or Dalle·2 models have a 1024 characters limit. +SCENE_PROMPT = '''Render a detailed image of the following scene: + +"""{scene_description}""" + +Stay strictly faithful to the description, no added elements, characters, doors, or text. +Do not depict the adventurer; show only what they see. + +Use the "{scene_style}" visual style. +''' + +# Define the scene drawing style, can be a simple word or a short sentence. +SCENE_STYLE = 'Photorealistic' + +# Set a Storyteller scene descriptions size limit to keep the draw prompt in range. +STORYTELLER_LIMIT = 730 + +# Define the storyteller behaviour. Remember to specify a limited scene length. +STORYTELLER_PROMPT = f""" +You are a conversational dungeon crawler game master that describes scenes and findings +based on the player's declared actions. + +Your descriptions will always adhere to the OpenAI's safety system rules so they can be +drawn by Dall·E or other image models. + +The game start with the player, the adventurer, on a random room and the objetive is +escape the dungeon with the most treasures possible before dying. + +You will describe the environment, enemies, and items to the player. + +Your descriptions will always adhere to the OpenAI's safety system rules so they can be +drawn by Dall·E or other image models. + +You will ensure the game is engaging and fun, but at the same time risky by increasing +difficult the more the time the adventurer stays inside the dungeon, if the adventurer +takes too much risks he may even die, also bigger risks implies bigger rewards. + +You will control the time the adventurer is in, once enough time has passer he will die, +may it be a collapse, explosion, flooding, up to you. + +The more deep inside the adventurer is the most it will be represented on descriptions by +more suffocating environments, more dark, that kind of things, let the player feel the +risk on the ambience, make him fear. + +Same applies with time, the most time has passed the environment and situation will warn +him, or at least give clues that time is running and the end may be close soon, make him +stress. + +While leaving the dungeon, the more deep inside the adventurer is, the more steps must +take to get out, although some shortcuts may be available at your discretion. +Once the user exits the dungeon, at deepness zero, the game is over, give him a score +based on his actions, treasures and combat successes along the usual description. + +Don't be too much protective but not also a cruel master, just be fair. + +Your responses must always be a JSON with the following structure: + +{{ + "game_over" : "A boolean value indicating the game is over." + "scene_description" : "The detailed scene description. Max {STORYTELLER_LIMIT} chars" + "dungeon_deepness" : "How deep the adventurer has gone into the dungeon. initially 3" + "adventure_time" : "How much minutes has passed since the start of the adventure." + "adventurer_status" : {{ + "health": "Current health of the adventurer as an int, initially 100" + "max_health": "Maximum health of the adventurer as an int, initially 100" + "level": "Current adventurer's leve as an int, initially 1" + "experience": "Current adventurer experience as an int, initially 0"}} + "inventory_status" : "A list of inventory items, initially empty" +}} + +Remember to cap the "scene_description" to {STORYTELLER_LIMIT} characters maximum" + +You will respond to the adventurer's actions and choices. +You wont let the player to trick you by stating actions that do not fit the given scene. + * If he attempts to do so just politely tell him he can not do that there with the + description of the scene he is in. + +You will keep track of the adventurer's health. + * Health can go down due to combat, traps, accidents, etc. + * If Health reaches zero the adventurer dies and it's a "game over". + * Several items, places, and allowed actions may heal the adventurer. + * Some items, enchants, and such things may increase the adventurer's maximum health. + +You will keep track of the player's progress. +You will keep track of adventurer level and experience, + * He gains experience by finding items, solving puzzles, by combat with enemies, etc. + * Each (100 + 100 * current_level) experience the adventurer will gain a level. + * Gaining a level resets his experience to 0. + +You will keep track of the player's inventory. + * Only add items to inventory if user explicitly says he picks them or takes an + action that ends with the item on his possession. + * Inventory items will reflect quantity and will never display items with zero units. + * Example of inventory: ["Gold coins (135)", "Diamonds (2)", "Log sword (1)"] + * Be reasonable with the inventory capacity, don't bee to strict but things + like a big marble statue can't be taken, use common sense. + +You will use a turn-based system where the player and enemies take turns acting. + * Players will lose health when receiving hits on combat. + * The more damage they take the less damage they do, same applies to enemies. + * Reaching to zero health or lees implies the adventurer has die. +""" + + +_logger = getLogger(__name__) + +if (max_image_prompt := len(SCENE_PROMPT) + len(SCENE_STYLE) + STORYTELLER_LIMIT) > 1024: + _logger.warning(f'ESTIMATED SCENE PROMPT MAX SIZE: {max_image_prompt}') +else: + _logger.info(f'ESTIMATED SCENE PROMPT MAX SIZE: {max_image_prompt}') diff --git a/community-contributions/dungeon_extration_game/game/gameplay/__init__.py b/community-contributions/dungeon_extration_game/game/gameplay/__init__.py new file mode 100644 index 0000000..c2d3d4c --- /dev/null +++ b/community-contributions/dungeon_extration_game/game/gameplay/__init__.py @@ -0,0 +1,6 @@ +"""AI Mastered Dungeon Extraction Game gameplay package.""" + +from .gameplay import Gameplay_Config, get_gameplay_function + + +__all__ = ['Gameplay_Config', 'get_gameplay_function'] diff --git a/community-contributions/dungeon_extration_game/game/gameplay/gameplay.py b/community-contributions/dungeon_extration_game/game/gameplay/gameplay.py new file mode 100644 index 0000000..3cc540b --- /dev/null +++ b/community-contributions/dungeon_extration_game/game/gameplay/gameplay.py @@ -0,0 +1,55 @@ +"""AI Mastered Dungeon Extraction Game gameplay module.""" + +from logging import getLogger +from typing import Callable, NamedTuple + + +# Define gameplay's configuration class. +class Gameplay_Config(NamedTuple): + """Gradio interface configuration class.""" + draw_func: Callable + narrate_func: Callable + scene_style: str + scene_prompt: str + storyteller_prompt: str + error_img: str + error_narrator: str + error_illustrator: str + + +# Define Game's functions. + +def get_gameplay_function(config: Gameplay_Config): + """Return a pre-configured turn gameplay function.""" + def gameplay_function(message, history): + """Generate Game Master's response and draw the scene image.""" + # Request narration. + _logger.info(f'NARRATING SCENE...') + try: + response = config.narrate_func(message, history, config.storyteller_prompt) + except Exception as ex: + scene = config.error_img + response = config.error_narrator.format(ex=ex) + _logger.error(f'ERROR NARRATING SCENE: {ex}\n{message}\n{history}') + return scene, response, history, message + # Update history. + history.append({"role": "user", "content": message}) + history.append({"role": "assistant", "content": response.model_dump_json()}) + # Draw scene. + _logger.info(f'DRAWING SCENE...') + try: + scene_data = {'scene_description': response.scene_description, + 'scene_style': config.scene_style} + scene_prompt = config.scene_prompt.format(**scene_data) + _logger.info(f'PROMPT BODY IS: \n\n{scene_prompt}\n') + _logger.info(f'PROMPT LENGTH IS: {len(scene_prompt)}') + scene = config.draw_func(scene_prompt) + except Exception as ex: + scene = config.error_img + response = config.error_illustrator.format(ex=ex) + _logger.warning(f'ERROR DRAWING SCENE: {ex}') + return scene, response, history, '' + return gameplay_function + + +_logger = getLogger(__name__) diff --git a/community-contributions/dungeon_extration_game/game/illustrator/__init__.py b/community-contributions/dungeon_extration_game/game/illustrator/__init__.py new file mode 100644 index 0000000..608db8e --- /dev/null +++ b/community-contributions/dungeon_extration_game/game/illustrator/__init__.py @@ -0,0 +1,12 @@ +"""AI Mastered Dungeon Extraction Game scenes illustrator package.""" + +from .illustrator_dalle_2 import draw as draw_dalle_2 +from .illustrator_dalle_3 import draw as draw_dalle_3 +from .illustrator_gemini import draw as draw_gemini +from .illustrator_gpt import draw as draw_gpt +from .illustrator_grok import draw as draw_grok +from .illustrator_grok import draw_x as draw_grok_x + + +__all__ = ['draw_dalle_2', 'draw_dalle_3', 'draw_gemini', + 'draw_gpt', 'draw_grok', 'draw_grok_x'] diff --git a/community-contributions/dungeon_extration_game/game/illustrator/illustrator_dalle_2.py b/community-contributions/dungeon_extration_game/game/illustrator/illustrator_dalle_2.py new file mode 100644 index 0000000..7269d7b --- /dev/null +++ b/community-contributions/dungeon_extration_game/game/illustrator/illustrator_dalle_2.py @@ -0,0 +1,30 @@ +"""AI Mastered Dungeon Extraction Game scenes illustrator using OpenAI's DALL·E 3.""" + +import base64 +from io import BytesIO + +from dotenv import load_dotenv +from openai import OpenAI +from PIL import Image + + +# Environment initialization. +load_dotenv(override=True) + +# Define global defaults. +MODEL = 'dall-e-2' + +# Client instantiation. +CLIENT = OpenAI() + + +# Function definition. +def draw(prompt, size=(1024, 1024), client=CLIENT, model=MODEL, quality=None): + """Generate an image based on the prompt.""" + # Generate image. + response = client.images.generate( + model=model, prompt=prompt, n=1, + size=f'{size[0]}x{size[1]}', + response_format='b64_json') + # Process response. + return Image.open(BytesIO(base64.b64decode(response.data[0].b64_json))) diff --git a/community-contributions/dungeon_extration_game/game/illustrator/illustrator_dalle_3.py b/community-contributions/dungeon_extration_game/game/illustrator/illustrator_dalle_3.py new file mode 100644 index 0000000..87f8051 --- /dev/null +++ b/community-contributions/dungeon_extration_game/game/illustrator/illustrator_dalle_3.py @@ -0,0 +1,32 @@ +"""AI Mastered Dungeon Extraction Game scenes illustrator using OpenAI's DALL·E 3.""" + +import base64 +from io import BytesIO + +from dotenv import load_dotenv +from openai import OpenAI +from PIL import Image + + +# Environment initialization. +load_dotenv(override=True) + +# Define global defaults. +MODEL = 'dall-e-3' +QUALITY = 'standard' # Set to 'hd' for more quality, but double the costs. + +# Client instantiation. +CLIENT = OpenAI() + + +# Function definition. +def draw(prompt, size=(1024, 1024), client=CLIENT, model=MODEL, quality=QUALITY): + """Generate an image based on the prompt.""" + # Generate image. + response = client.images.generate( + model=model, prompt=prompt, n=1, + size=f'{size[0]}x{size[1]}', + quality=quality, + response_format='b64_json') + # Process response. + return Image.open(BytesIO(base64.b64decode(response.data[0].b64_json))) diff --git a/community-contributions/dungeon_extration_game/game/illustrator/illustrator_gemini.py b/community-contributions/dungeon_extration_game/game/illustrator/illustrator_gemini.py new file mode 100644 index 0000000..e586944 --- /dev/null +++ b/community-contributions/dungeon_extration_game/game/illustrator/illustrator_gemini.py @@ -0,0 +1,36 @@ +"""AI Mastered Dungeon Extraction Game scenes illustrator using Google's Gemini.""" + +from io import BytesIO + +from dotenv import load_dotenv +from google import genai # New Google's SDK 'genai' to replace 'generativeai'. +from PIL import Image + + +# Environment initialization. +load_dotenv(override=True) + +# Define globals. +MODEL = 'gemini-2.5-flash-image-preview' + +# Client instantiation. +CLIENT = genai.Client() + + +# Function definition. +def draw(prompt, size=(1024, 1024), client=CLIENT, model=MODEL): + """Generate an image based on the prompt.""" + # Generate image. + response = client.models.generate_content( + model=model, contents=[prompt]) + # Process response. + for part in response.candidates[0].content.parts: + if part.text is not None: + print(part.text) + elif part.inline_data is not None: + image_data = part.inline_data.data + # Open the generated image. + generated_image = Image.open(BytesIO(image_data)) + # Resize the image to the specified dimensions. + resized_image = generated_image.resize(size) + return resized_image diff --git a/community-contributions/dungeon_extration_game/game/illustrator/illustrator_gpt.py b/community-contributions/dungeon_extration_game/game/illustrator/illustrator_gpt.py new file mode 100644 index 0000000..ae8b9f4 --- /dev/null +++ b/community-contributions/dungeon_extration_game/game/illustrator/illustrator_gpt.py @@ -0,0 +1,32 @@ +"""AI Mastered Dungeon Extraction Game scenes illustrator using OpenAI's GPT.""" + +import base64 +from io import BytesIO + +from dotenv import load_dotenv +from openai import OpenAI +from PIL import Image + + +# Environment initialization. +load_dotenv(override=True) + +# Define global defaults. +MODEL = 'gpt-image-1' +QUALITY = 'low' + +# Client instantiation. +CLIENT = OpenAI() + + +# Function definition. +def draw(prompt, size=(1024, 1024), client=CLIENT, model=MODEL, quality=QUALITY): + """Generate an image based on the prompt.""" + # Generate image. + response = client.images.generate( + model=model, prompt=prompt, n=1, + size=f'{size[0]}x{size[1]}', + output_format='png', + quality=quality) + # Process response. + return Image.open(BytesIO(base64.b64decode(response.data[0].b64_json))) diff --git a/community-contributions/dungeon_extration_game/game/illustrator/illustrator_grok.py b/community-contributions/dungeon_extration_game/game/illustrator/illustrator_grok.py new file mode 100644 index 0000000..417af7a --- /dev/null +++ b/community-contributions/dungeon_extration_game/game/illustrator/illustrator_grok.py @@ -0,0 +1,47 @@ +"""AI Mastered Dungeon Extraction Game scenes illustrator using xAI's Grok.""" + +import base64 +import os +from io import BytesIO + +from dotenv import load_dotenv +from openai import OpenAI +from PIL import Image +from xai_sdk import Client + + +# Environment initialization. +load_dotenv(override=True) + +# Define global defaults. +MODEL = 'grok-2-image' +QUALITY = None + +# Client instantiation. +XAI_API_KEY = os.getenv('XAI_API_KEY') +CLIENT = OpenAI(api_key=XAI_API_KEY, base_url="https://api.x.ai/v1") + + +# Function definition. +def draw(prompt, size=(1024, 1024), client=CLIENT, model=MODEL, quality=QUALITY): + """Generate an image based on the prompt.""" + # Generate image. + response = client.images.generate( + model=model, prompt=prompt, n=1, + response_format='b64_json') + # Process response. + return Image.open(BytesIO(base64.b64decode(response.data[0].b64_json))) + + +# xAI SDK Version: +CLIENT_X = Client(api_key=XAI_API_KEY) + + +def draw_x(prompt, size=(1024, 1024), client=CLIENT_X, model=MODEL, quality=QUALITY): + """Generate an image based on the prompt.""" + # Generate image. + response = client.image.sample( + model=model, prompt=prompt, + image_format='base64') + # Process response. + return Image.open(BytesIO(response.image)) diff --git a/community-contributions/dungeon_extration_game/game/interface/__init__.py b/community-contributions/dungeon_extration_game/game/interface/__init__.py new file mode 100644 index 0000000..b680128 --- /dev/null +++ b/community-contributions/dungeon_extration_game/game/interface/__init__.py @@ -0,0 +1,6 @@ +"""AI Mastered Dungeon Extraction Game interface package.""" + +from .interface import Interface_Config, get_interface + + +__all__ = ['Interface_Config', 'get_interface'] diff --git a/community-contributions/dungeon_extration_game/game/interface/interface.py b/community-contributions/dungeon_extration_game/game/interface/interface.py new file mode 100644 index 0000000..d0d05fa --- /dev/null +++ b/community-contributions/dungeon_extration_game/game/interface/interface.py @@ -0,0 +1,50 @@ +"""AI Mastered Dungeon Extraction Game Gradio interface module.""" + +from typing import NamedTuple + +import gradio as gr + + +# Define interface's configuration class. +class Interface_Config(NamedTuple): + """Gradio interface configuration class.""" + start_img: str + place_img: str + description_label: str + title_label: str + input_button: str + input_label: str + input_command: str + start_scene: str + + +# Define game's interface. +def get_interface(submit_function, config: Interface_Config): + """Create a game interface service.""" + with gr.Blocks(title=config.title_label) as ui: + # Title. + gr.Markdown(config.title_label) + # Hidden state for history. + history_state = gr.State([]) + # Scene's image. + scene_image = gr.Image( + label="Scene", value=config.start_img, placeholder=config.place_img, + type="pil", show_label=False, show_copy_button=True) + # Scene's description. + description_box = gr.Textbox( + label=config.description_label, value=config.start_scene, + interactive=False, show_copy_button=True) + # Player's command. + user_input = gr.Textbox( + label=config.input_label, placeholder=config.input_command) + user_input.submit( + fn=submit_function, + inputs=[user_input, history_state], + outputs=[scene_image, description_box, history_state, user_input]) + # Submit button. + submit_btn = gr.Button(config.input_button) + submit_btn.click( + fn=submit_function, + inputs=[user_input, history_state], + outputs=[scene_image, description_box, history_state, user_input]) + return ui diff --git a/community-contributions/dungeon_extration_game/game/storyteller/__init__.py b/community-contributions/dungeon_extration_game/game/storyteller/__init__.py new file mode 100644 index 0000000..042ceea --- /dev/null +++ b/community-contributions/dungeon_extration_game/game/storyteller/__init__.py @@ -0,0 +1,6 @@ +"""AI Mastered Dungeon Extraction Game Storyteller package.""" + +from .storyteller import narrate + + +__all__ = ['narrate'] diff --git a/community-contributions/dungeon_extration_game/game/storyteller/storyteller.py b/community-contributions/dungeon_extration_game/game/storyteller/storyteller.py new file mode 100644 index 0000000..bc976f2 --- /dev/null +++ b/community-contributions/dungeon_extration_game/game/storyteller/storyteller.py @@ -0,0 +1,67 @@ +"""AI Mastered Dungeon Extraction Game Storyteller using OpenAI's GPT.""" + +from typing import List + +from dotenv import load_dotenv +from openai import OpenAI +from pydantic import BaseModel, Field + +from ..config import STORYTELLER_LIMIT +from .tools import handle_tool_call, tools + + +# Environment initialization. +load_dotenv(override=True) + +# Define globals. +MODEL = 'gpt-4o-mini' + +# Client instantiation. +CLIENT = OpenAI() + + +# Define Pydantic model classes for response format parsing. +class _character_sheet(BaseModel): + health: int + max_health: int + level: int + experience: int + + +class _response_format(BaseModel): + game_over: bool + scene_description: str = Field(..., max_length=STORYTELLER_LIMIT) + dungeon_deepness: int + adventure_time: int + adventurer_status: _character_sheet + inventory_status: List[str] + + def __str__(self): + """Represent response as a string.""" + response_view = ( + f'{self.scene_description}' + f'\n\nInventory: {self.inventory_status}' + f'\n\nAdventurer: {self.adventurer_status}' + f'\n\nTime: {self.adventure_time}' + f'\n\nDeepness: {self.dungeon_deepness}' + f'\n\nGame Over: {self.game_over}') + return response_view + + +# Function definition. +def narrate(message, history, system_message, client=CLIENT, model=MODEL): + """Chat with the game engine.""" + messages = ([{"role": "system", "content": system_message}] + history + + [{"role": "user", "content": message}]) + response = client.chat.completions.parse(model=model, messages=messages, tools=tools, + response_format=_response_format) + # Process tool calls. + if response.choices[0].finish_reason == "tool_calls": + message = response.choices[0].message + tool_response = handle_tool_call(message) + messages.append(message) + messages.append(tool_response) + response = client.chat.completions.parse(model=model, messages=messages, + response_format=_response_format) + # Return game's Master response. + return response.choices[0].message.parsed diff --git a/community-contributions/dungeon_extration_game/game/storyteller/tools.py b/community-contributions/dungeon_extration_game/game/storyteller/tools.py new file mode 100644 index 0000000..a995ec0 --- /dev/null +++ b/community-contributions/dungeon_extration_game/game/storyteller/tools.py @@ -0,0 +1,81 @@ +"""AI Mastered Dungeon Extraction Game storyteller tools module WIP.""" + +from json import loads + +from openai.types.chat import ChatCompletionMessage +from openai.types.chat import ChatCompletionMessageFunctionToolCall +from openai.types.chat.chat_completion_message_function_tool_call import Function + + +# Tools declaration for future use. (E.g. Tools may handle user status and inventory) +tools = [] + +tools_map = {} # This will map each tool with it's tool function. + + +# A tool call function. +def handle_tool_call(message: ChatCompletionMessage): + """Tools call handler.""" + tool_call = message.tool_calls[0] + arguments = loads(tool_call.function.arguments) + print(f'\nFUNC CALL: {tool_call.function.name}({arguments})\n') + # Get tool function and call with arguments. + tool_func = tools_map.get(tool_call.function.name) + tool_response = tool_func(**arguments) + response = {"role": "tool", "content": tool_response, "tool_call_id": tool_call.id} + return response + + +draw_signature = { + "name": "draw_scene", + "description": "Generate an image of the scene based on the description", + "parameters": { + "type": "object", + "properties": { + "scene_description": { + "type": "string", + "description": "A detailed description of the scene to be drawn", + }, + "scene_style": { + "type": "string", + "description": "The art style for the image", + }, + }, + "required": ["scene_description"], + "additionalProperties": False, + }, +} + + +# Tool call response example. +ChatCompletionMessage( + content="""To begin, first I need to set a scene. + Imagine you are in a dark room of an old castle. + The walls are covered in cobwebs and there is a smell of mold in the air. + As you look around, you notice a slightly ajar door to the north + and a dark figure lurking in the corner. + + I am going to generate an image of this scene. One moment, please.""", + refusal=None, + role="assistant", + annotations=[], + audio=None, + function_call=None, + tool_calls=[ + ChatCompletionMessageFunctionToolCall( + id="call_oJqJeXMUPZUaC0GPfMeSd16E", + function=Function( + arguments='''{ + "scene_description":"A dark room in an ancient castle. + The walls are covered with cobwebs, and there\'s a musty smell in + the air. + A slightly ajar door to the north and a shadowy figure lurking in + the corner. + Dim lighting adds to the eerie atmosphere, with flickering shadows.", + "style":"fantasy" + }''', + name="draw_scene"), + type="function", + ) + ], +)