Inspiration
How to Build a Procedural Interactive Museum Audio Guide for Weird Fictional Artifacts Using Claude and Python
Tired of boring real-world history? Learn how to write a Python script that taps into Claude to procedurally generate an eerie, interactive audio guide for an infinite museum of surrealist, impossible objects.
Updated 10/5/2026
The Charm of the Unexplained
Let’s be honest: traditional museum audio guides are a bit dry. No matter how enthusiastic the narrator sounds, there are only so many times you can listen to a detailed breakdown of a seventeenth-century butter churn before your eyes start to glaze over.
But what if we could step into a museum of the impossible? A gallery filled with items that defy physics, logic, and polite society—like a pocket watch that ticks backward only when you aren't looking, or a silent flute that makes listeners remember events that never happened.
By combining the structured reasoning of Claude with a simple Python CLI wrapper, we can build a procedural generator that crafts an infinite, interactive audio guide for a fictional museum of high strangeness. Every time you boot up the script, you are greeted by a stuffy, slightly unnerved virtual curator who walks you through a fresh gallery of procedurally generated anomalies.
Here is how to build this engine yourself, complete with interactive choices, curator commentary, and structured outputs.
The Architecture of an Impossible Museum
To make this system work without falling into repetitive, generic sci-fi tropes, we need a solid code structure. We aren't just asking an LLM to "write something weird." We want a system that maintains a coherent, overarching museum atmosphere, tracks which exhibits the user has "visited," and outputs clean, structured data so our Python script can handle navigation.
Our application will use a simple state machine: 1. The Foyer: The curator welcomes the user and presents three randomly generated exhibit rooms. 2. The Exhibit Room: The user selects an exhibit. The generator fetches a detailed description of the artifact, its bizarre history, and an interactive action the user can perform. 3. The Interaction: The user decides how to interact with the anomaly (e.g., "press the brass lever," "peer into the dark glass"), and Claude evaluates the eerie consequences.
To keep our terminal application running smoothly, we must define a strict schema. We will use Claude’s JSON-producing capabilities to ensure our Python code can reliably parse what makes these fictional anomalies tick.
Step 1: Setting Up the Python Environment
First, make sure you have the required libraries. We will use the official Anthropic SDK and Pydantic to enforce our data structures. If you are unfamiliar with how structured JSON parsing works in modern LLMs, check out our glossary for a quick refresher on schema enforcement.
`bash
pip install anthropic pydantic
`
Create a new file named museum_guide.py and set up your imports and client initialization:
`python
import os
import json
from typing import List
from pydantic import BaseModel, Field
from anthropic import Anthropic
Initialize the client. Make sure your ANTHROPIC_API_KEY environment variable is set. client = Anthropic() ```
If you run into issues with your API key or client setup, you can check the troubleshooting guides over at Claude Support to get sorted.
Step 2: Defining the Artifact Schema
To prevent the AI from generating unstructured walls of text, we define exactly what an "artifact" looks like using Pydantic. This guarantees we always get a name, an origin epoch, a physical description, a "hazard level," and a set of possible interactions.
`python
class Artifact(BaseModel):
name: str = Field(description="The unsettling or poetic name of the artifact.")
epoch: str = Field(description="The historical era or impossible timeline the artifact belongs to.")
description: str = Field(description="A highly sensory, physical description of the object.")
curator_notes: str = Field(description="Spoken-style audio guide monologue by an eccentric, slightly anxious curator.")
danger_rating: str = Field(description="An arbitrary, highly specific rating (e.g., 'Mildly Mimetic', 'Catastrophic if wet').")
interaction_choices: List[str] = Field(description="Three distinct, evocative actions the visitor can perform.", min_items=3, max_items=3)
`
Step 3: Drafting the Prompt and Calling Claude
Now, we write the generator function. We want to instruct Claude to adopt a specific, gothic-curator persona—think late-night BBC radio meets cosmic horror.
We will use claude-3-5-sonnet because of its superior ability to handle complex creative constraints and output strict JSON structures without dropping character.
`python
def generate_artifact(theme: str) -> Artifact:
system_prompt = (
"You are the chief curator of the Blackwood Institute of Impossible Antiquities. "
"Your voice is academic, dryly witty, slightly British, and subtly terrified of the objects in your care. "
"You must output a single JSON object matching the requested schema. Do not include any conversational filler "
"outside the JSON object."
)
user_prompt = f"Generate a completely unique, bizarre museum artifact themed around: {theme}. It must feel eerie, grounded, and slightly inexplicable."
response = client.messages.create( model="claude-3-5-sonnet-20241022", max_tokens=1500, temperature=0.9, system=system_prompt, messages=[{"role": "user", "content": user_prompt}], # We force JSON format to make parsing trivial response_format={"type": "json_object"} )
Load the JSON string into our Pydantic model for validation raw_json = json.loads(response.content[0].text) return Artifact(**raw_json) ```
Step 4: Building the Interactive Loop
Now we need to write the logic that presents these choices to the user in the terminal and evaluates their decisions. When a user selects an action, we send the context back to Claude to find out what happens next.
`python
def evaluate_action(artifact: Artifact, action: str):
prompt = (
f"The visitor decided to perform the following action on the artifact '{artifact.name}': '{action}'. "
f"Describe the immediate, surreal, and atmospheric consequences. Keep the tone consistent with the eccentric curator's audio guide. "
f"End with a polite but firm warning to move away from the glass."
)
response = client.messages.create( model="claude-3-5-sonnet-20241022", max_tokens=600, temperature=0.85, messages=[{"role": "user", "content": prompt}] ) print( f"\n[AUDIO GUIDE]: {response.content[0].text}\n" )
def main_loop(): themes = ["Lost Time", "Unsent Letters", "Folds in Space", "Acoustic Shadows", "Forgotten Liquids"] print("========================================================") print(" WELCOME TO THE BLACKWOOD INSTITUTE OF IMPOSSIBLE ANTIQUITIES") print("========================================================") print("\n[Curator]: 'Ah. Welcome. Please do not touch anything. Let me load up your guide...'\n") # Generate a random artifact for this run import random selected_theme = random.choice(themes) print(f"Loading audio guide entry for theme: {selected_theme}...") artifact = generate_artifact(selected_theme) print(f"\n--- EXHIBIT: {artifact.name.upper()} ---") print(f"Origin: {artifact.epoch}") print(f"Containment Danger Rating: {artifact.danger_rating}") print(f"\nPhysical Appearance: {artifact.description}") print(f"\n[AUDIO TRACK PLAYING]:") print(f'"{artifact.curator_notes}"') print("--------------------------------------------------------") print("\nWhat would you like to do?") for i, choice in enumerate(artifact.interaction_choices, 1): print(f"{i}. {choice}") try: user_choice = int(input("\nEnter choice (1-3): ")) - 1 if 0 <= user_choice < 3: selected_action = artifact.interaction_choices[user_choice] evaluate_action(artifact, selected_action) else: print("\n[Curator]: 'Please focus. We don't have the insurance for wandering minds.'") except ValueError: print("\n[Curator]: 'That is not a valid physical movement. Try again.'")
if __name__ == "__main__":
main_loop()
`
Taking It Further
If you want to truly elevate this project, you can feed the resulting texts directly into a local Text-to-Speech (TTS) library like pyttsx3 or use an API like ElevenLabs to generate a physical, raspy British voice file to play through your speakers.
Because the themes are randomized, you can run this script repeatedly, watching as Claude weaves bizarre micro-fiction stories out of thin air. It is a brilliant, low-stakes experiment in using LLMs as dynamic world-building engines rather than mere search-summarisers.
Now, put on your headphones, step into the gallery, and remember: do not look directly at the ticking shadow.
Keep going
Build something with the prompt generator, decode the jargon in the glossary, or compare the tools on our platform deep-dives.