A FastAPI-based backend service for the Stories We Tell cinematic intake chatbot application. Provides intelligent AI-powered story development with conversation management, dossier extraction, and multi-user support.
- Multi-Model LLM System: Intelligent routing to specialized AI models
- Chat: GPT-4o-mini for conversational story development
- Dossier Extraction: GPT-4o for intelligent story element extraction
- Context-Aware Responses: Full conversation history for coherent storytelling
- Streaming Responses: Real-time Server-Sent Events (SSE) for instant feedback
- Smart Dossier Generation: AI decides when to extract story elements based on conversation flow
- Conversation Memory: Persistent chat history across sessions
- Automatic Extraction: Intelligently extracts story elements from conversations:
- Characters: Names, descriptions, relationships, character arcs
- Themes: Central themes and motifs
- Locations: Settings, environments, and places
- Plot Points: Story structure and key beats
- Smart Update Logic: Only updates dossier when meaningful new information is added
- Structured JSON Storage: Clean, queryable story data format
- Project-Based Organization: Each story has its own dossier linked to project_id
- Multi-User Support: Full user authentication and authorization
- Session Persistence: Conversations saved and retrievable across devices
- Anonymous Sessions: Support for unauthenticated users with session migration
- Session Migration: Seamlessly transfer anonymous sessions to authenticated accounts
- Chat History: Complete message history with timestamps and metadata
- Active Session Tracking: Smart session lifecycle management
- Supabase Integration: PostgreSQL database with real-time capabilities
- Efficient Queries: Optimized database queries for fast retrieval
- Data Models:
users: User profiles and authenticationchat_sessions: Conversation sessions with metadatamessages: Individual chat messages with turn trackingdossiers: Story element storage with versioningprojects: Project-level organization
- Automatic Timestamps: Created/updated tracking for all records
- Data Integrity: Foreign key constraints and validation
- Supabase Auth Integration: Secure JWT-based authentication
- User ID Validation: Request-level user identification
- Protected Endpoints: Authorization checks on sensitive operations
- CORS Configuration: Secure cross-origin resource sharing
- Environment-Based Secrets: Secure API key management
- Async/Await: Non-blocking I/O for high concurrency
- Streaming API: Memory-efficient response streaming
- Connection Pooling: Efficient database connection management
- Error Handling: Comprehensive error catching and logging
- Logging System: Detailed debugging and monitoring logs
- FastAPI Framework: Modern Python web framework with automatic docs
- Type Safety: Full Pydantic models with validation
- Auto-Generated API Docs: Interactive Swagger UI and ReDoc
- Hot Reload: Automatic server restart on code changes
- Structured Logging: Console logging for debugging
- Python 3.8+
- Supabase account and project
- OpenAI API key
- Google Gemini API key (for descriptions)
- Anthropic Claude API key (for scenes)
-
Navigate to the backend directory:
cd backend -
Install dependencies:
pip install -r requirements.txt
-
Set up environment variables: Create a
.envfile in the backend directory with the following variables:SUPABASE_URL=your_supabase_project_url SUPABASE_KEY=your_supabase_anon_key OPENAI_API_KEY=your_openai_api_key GEMINI_API_KEY=your_gemini_api_key ANTHROPIC_API_KEY=your_anthropic_api_key
-
Install Supabase CLI (if not already installed):
# Using Scoop (Windows) scoop install supabase # Or using npm npm install -g supabase
-
Initialize Supabase project:
supabase init
-
Start local Supabase services:
supabase start
-
Run database migrations:
supabase db push
uvicorn app.main:app --reloadThe server will start on http://127.0.0.1:8000
uvicorn app.main:app --host 0.0.0.0 --port 8000Once the server is running, you can access:
- Interactive API Docs (Swagger UI):
http://127.0.0.1:8000/docs - Alternative API Docs (ReDoc):
http://127.0.0.1:8000/redoc - OpenAPI Schema:
http://127.0.0.1:8000/openapi.json
Stream AI chat responses with automatic dossier extraction. Uses Server-Sent Events (SSE) for real-time streaming.
Request Body:
{
"text": "Tell me about John, a detective in 1940s Los Angeles",
"session_id": "uuid-optional",
"project_id": "uuid-optional",
"user_id": "uuid-optional"
}Response (SSE Stream):
data: {"type": "text", "content": "John is "}
data: {"type": "text", "content": "a hardboiled detective..."}
data: {"type": "metadata", "metadata": {"session_id": "uuid", "project_id": "uuid"}}
data: {"type": "done"}
Headers:
x-user-id: User ID for authenticated requests (optional)
Get all active sessions for the authenticated user.
Response:
[
{
"session_id": "uuid",
"user_id": "uuid",
"project_id": "uuid",
"title": "Detective Story",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T12:00:00Z",
"last_message_at": "2024-01-01T12:00:00Z",
"is_active": true,
"message_count": 15
}
]Get message history for a specific session.
Query Parameters:
limit: Number of messages to retrieve (default: 50)offset: Pagination offset (default: 0)
Response:
[
{
"message_id": "uuid",
"session_id": "uuid",
"role": "user",
"content": "Tell me about John",
"created_at": "2024-01-01T12:00:00Z"
},
{
"message_id": "uuid",
"session_id": "uuid",
"role": "assistant",
"content": "John is a detective...",
"created_at": "2024-01-01T12:00:01Z"
}
]Delete (deactivate) a session.
Response:
{
"message": "Session deleted successfully"
}Migrate an anonymous session to an authenticated user.
Request Body:
{
"temp_user_id": "anonymous-uuid",
"permanent_user_id": "authenticated-uuid"
}Create or update a user profile.
Request Body:
{
"user_id": "uuid",
"email": "user@example.com",
"display_name": "John Doe",
"avatar_url": "https://example.com/avatar.jpg"
}Response:
{
"user_id": "uuid",
"email": "user@example.com",
"display_name": "John Doe",
"created_at": "2024-01-01T00:00:00Z"
}Get the story dossier for a specific project.
Response:
{
"project_id": "uuid",
"user_id": "uuid",
"snapshot_json": {
"characters": [
{
"name": "John",
"description": "A hardboiled detective in 1940s LA",
"relationships": ["Partner with Sarah"],
"character_arc": "Learns to trust again"
}
],
"themes": ["Justice", "Redemption", "Trust"],
"locations": ["Los Angeles", "Detective Office"],
"plot_points": ["John meets a mysterious client", "discovers corruption"]
},
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T12:00:00Z"
}Get all dossiers for the authenticated user.
Response:
[
{
"project_id": "uuid",
"title": "Detective Story",
"character_count": 5,
"theme_count": 3,
"updated_at": "2024-01-01T12:00:00Z"
}
]Create a new dossier.
Request Body:
{
"project_id": "uuid",
"snapshot_json": {
"characters": [],
"themes": [],
"locations": [],
"plot_points": []
}
}Update an existing dossier.
Request Body:
{
"snapshot_json": {
"characters": [...],
"themes": [...],
"locations": [...],
"plot_points": [...]
}
}Delete a dossier.
Response:
{
"message": "Dossier deleted successfully"
}backend/
├── app/
│ ├── ai/
│ │ ├── llm_service.py # LLM service abstraction
│ │ ├── dossier_extractor.py # Story element extraction logic
│ │ └── prompts.py # AI prompt templates
│ ├── api/
│ │ ├── chat.py # Chat streaming endpoint
│ │ ├── chat_sessions.py # Session management endpoints
│ │ ├── dossier.py # Dossier CRUD endpoints
│ │ └── users.py # User management endpoints
│ ├── database/
│ │ ├── supabase_client.py # Supabase client singleton
│ │ ├── session_service_supabase.py # Session DB operations
│ │ └── supabase/
│ │ └── migrations/ # Database migration SQL files
│ ├── models/
│ │ ├── chat.py # Chat request/response models
│ │ ├── session.py # Session data models
│ │ ├── dossier.py # Dossier data models
│ │ └── user.py # User data models
│ ├── utils/
│ │ ├── auth.py # Authentication utilities
│ │ └── logging.py # Logging configuration
│ ├── main.py # FastAPI application entry point
│ └── config.py # Configuration management
├── .env # Environment variables (create this)
├── requirements.txt # Python dependencies
└── README.md # This file
{
"text": str, # User's message
"session_id": Optional[str], # Session identifier
"project_id": Optional[str], # Project identifier
"user_id": Optional[str] # User identifier
}{
"message_id": str, # Unique message ID
"session_id": str, # Session this message belongs to
"turn_id": Optional[str], # Conversation turn ID
"role": str, # "user" or "assistant"
"content": str, # Message text
"metadata": Optional[dict], # Additional metadata
"created_at": datetime, # Message timestamp
"updated_at": datetime # Last update timestamp
}{
"session_id": str, # Unique session ID
"user_id": str, # Owner user ID
"project_id": str, # Associated project ID
"title": str, # Session title
"created_at": datetime, # Creation timestamp
"updated_at": datetime, # Last update timestamp
"last_message_at": datetime, # Last message timestamp
"is_active": bool, # Session status
"message_count": int # Total messages in session
}{
"temp_user_id": str, # Anonymous user ID
"permanent_user_id": str # Authenticated user ID
}{
"user_id": str, # Unique user ID (from Supabase Auth)
"email": str, # User email
"display_name": Optional[str], # Display name
"avatar_url": Optional[str], # Profile picture URL
"created_at": datetime, # Account creation timestamp
"updated_at": datetime # Last update timestamp
}{
"project_id": str, # Associated project ID
"user_id": str, # Owner user ID
"snapshot_json": { # Story elements
"characters": List[Character],
"themes": List[str],
"locations": List[str],
"plot_points": List[str]
},
"created_at": datetime, # Creation timestamp
"updated_at": datetime # Last update timestamp
}{
"name": str, # Character name
"description": str, # Character description
"relationships": List[str], # Relationships with other characters
"character_arc": Optional[str] # Character development arc
}{
"type": "text",
"content": str # Partial response text
}{
"type": "metadata",
"metadata": {
"session_id": str, # Session ID (created or existing)
"project_id": str, # Project ID (created or existing)
"dossier_updated": bool # Whether dossier was updated
}
}{
"type": "done" # Signals end of stream
}| Variable | Description | Required |
|---|---|---|
SUPABASE_URL |
Your Supabase project URL | Yes |
SUPABASE_KEY |
Your Supabase anon/public key | Yes |
OPENAI_API_KEY |
Your OpenAI API key | Yes |
GEMINI_API_KEY |
Your Google Gemini API key | Yes |
ANTHROPIC_API_KEY |
Your Anthropic Claude API key | Yes |
- Create new router in
app/api/ - Import and include in
app/main.py - Add corresponding models in
app/models.py
- Create migration files in
app/database/supabase/migrations/ - Update models in
app/models.py - Run
supabase db pushto apply changes
- ModuleNotFoundError: Make sure you're running from the backend directory
- Supabase Connection Error: Verify your
.envfile has correct credentials - OpenAI API Error: Check your API key and billing status
The application logs connection status and errors to the console. Look for:
Connected to Supabase!- Successful database connectionError connecting to Supabase: {error}- Database connection issues
- Follow PEP 8 style guidelines
- Add type hints to all functions
- Update this README when adding new features
- Test all endpoints before submitting changes
This project is part of the Stories We Tell application suite.