An immersive, AI-powered interactive storytelling platform. Interactive Story allows users to generate dynamic, branching narratives where their choices genuinely shape the world, characters, and plotline.
This project consists of a Next.js frontend and a FastAPI + Google Agent Development Kit (ADK) + MongoDB backend.
The core of Interactive Story lies in its backend, which orchestrates Large Language Models (LLMs) to dynamically generate, track, and persist branching storylines in real-time.
- Framework: FastAPI
- Database: MongoDB (via Beanie ODM)
- Agent Orchestration: Google Agent Development Kit (ADK)
- Image Generation: SiliconFlow (FLUX.1-schnell) with automated AWS S3 uploads.
- Authentication: JWT via HTTP-only cookies.
The story generation is fundamentally modeled as a Graph of interconnected narrative nodes, guided by a master plan.
When a user initiates a new story, the backend triggers the StoryPlanner Agent:
- The Bottleneck Map: The AI acts as a Dungeon Master, outlining the overall narrative arc. It defines a "bottleneck map" which dictates the total number of levels/decisions it will take to reach a conclusion.
- The Branching Logic: The AI establishes the logical constraints of the world. It sets up the initial conditions and boundaries.
Once the master plan is generated, the backend triggers the NodeGenerator Agent to construct the very first scene (StoryNode).
Each step of the story is encapsulated in a StoryNode, which contains:
content: The descriptive narrative text.image_url: A dynamically generated image illustrating the scene (stored in S3).choices: A list ofChoiceobjects that the user can select from.
What makes this engine unique is that choices do not simply jump to a static piece of text—they actively mutate the world's State Variables.
During the initial planning phase, the StoryPlanner generates a list of dynamic StateVariable definitions (e.g., Hero_Health: 100, Has_Ancient_Key: False, Kingdom_Tension: 50).
When the NodeGenerator generates a StoryNode, it explicitly calculates the consequence of every single choice it offers.
Inside a Choice object:
text: What the user sees (e.g., "Attack the dragon!").next_node_id: A unique ID linking to the future outcome.story_state_variables: An updated snapshot of the world's state variables if this choice is selected.
Example Flow:
- User is at Node A. The
Kingdom_Tensionis currently50. - The user selects Choice 2: "Insult the King."
- The backend receives the request. It looks at the
Choice 2object, which specifically dictates thatKingdom_Tensionmust change to80. - The backend packages this new updated state (
Kingdom_Tension: 80) alongside the user's action and feeds it back into theNodeGeneratorAgent. - The LLM processes the new constraint and generates Node B. Because the tension is now
80, the AI alters its tone, generates a hostile scenario, and creates new choices reflecting the consequences of the user's past actions.
POST /story/create_story: Takes a prompt. Initializes the ADK Runner. TheStoryPlannergenerates the Master Plotline and State Variables. TheNodeGeneratoryields the first node. A SiliconFlow image is generated, uploaded to S3, and the entireStorydocument is saved to MongoDB.POST /story/{public_story_id}/create_node: Takes theprevious_node_idand the user'schoice_id. Looks up the expected State Variables from the chosen path, feeds them back into the LLM, and generates the next scene.GET /story/stories: Fetches all root stories associated with the authenticated User (or Guest).
Deployed via PM2 behind an Nginx reverse proxy managing port 8000 (Backend) and port 3000 (Frontend) on an AWS EC2 Ubuntu instance.