Add dungeon game code.
This commit is contained in:
@@ -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')
|
||||
@@ -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)
|
||||
146
community-contributions/dungeon_extration_game/game/config.py
Normal file
146
community-contributions/dungeon_extration_game/game/config.py
Normal file
@@ -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}')
|
||||
@@ -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']
|
||||
@@ -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__)
|
||||
@@ -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']
|
||||
@@ -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)))
|
||||
@@ -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)))
|
||||
@@ -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
|
||||
@@ -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)))
|
||||
@@ -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))
|
||||
@@ -0,0 +1,6 @@
|
||||
"""AI Mastered Dungeon Extraction Game interface package."""
|
||||
|
||||
from .interface import Interface_Config, get_interface
|
||||
|
||||
|
||||
__all__ = ['Interface_Config', 'get_interface']
|
||||
@@ -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
|
||||
@@ -0,0 +1,6 @@
|
||||
"""AI Mastered Dungeon Extraction Game Storyteller package."""
|
||||
|
||||
from .storyteller import narrate
|
||||
|
||||
|
||||
__all__ = ['narrate']
|
||||
@@ -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
|
||||
@@ -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",
|
||||
)
|
||||
],
|
||||
)
|
||||
Reference in New Issue
Block a user