Studyflash Customer Support Bot (Maximilian)
A modular, API-first customer support automation system built with FastAPI. Designed for easy integration with N8N workflows and implements a closed-loop learning system that continuously improves from human agent corrections.
Studyflash Customer Support Bot (Maximilian)
A modular, API-first customer support automation system built with FastAPI. Designed for easy integration with N8N workflows and implements a closed-loop learning system that continuously improves from human agent corrections.
Architecture Overview
This system implements a Context Stuffing approach instead of vector databases. The entire Knowledge Base (~10 pages) is fed to the AI on every request, making responses more accurate and eliminating the complexity of embeddings and vector search.
Key Components
customer_support_bot/
├── main.py # FastAPI application with 3 core endpoints
├── config.py # Environment configuration
├── requirements.txt # Python dependencies
├── models/
│ └── api.py # Pydantic data models for requests/responses
├── services/
│ ├── maximilian.py # AI agent that answers support queries
│ ├── analyst.py # Weekly gap analyzer (suggests new KB entries)
│ ├── kb_service.py # Fetches KB from Google Docs or local file
│ └── database.py # Supabase logging (learning loop)
└── utils/
└── prompts.py # System prompts for AI agents
Setup
1. Install Dependencies
cd customer_support_bot
pip install -r requirements.txt
2. Configure Environment Variables
Create a .env file in the customer_support_bot/ directory:
# Required
OPENAI_API_KEY=sk-your-openai-api-key
# Optional - Google Docs Integration
GOOGLE_DOC_ID=your-google-doc-id-here
# Optional - Supabase Integration (for learning loop)
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_KEY=your-supabase-key
# Optional - Knowledge Base
KB_FILE_PATH=../KnowledgeBase # Path to local KB file (fallback)
3. Google Docs Authentication (Optional)
If using Google Docs for the Knowledge Base:
# Authenticate with gcloud
gcloud auth application-default login
# Set your active account (if you have multiple)
gcloud config set account alejandro@studyflash.ch
The system will automatically use your gcloud credentials to fetch the document.
Running the API
# Development mode with auto-reload
uvicorn main:app --reload --port 8000
# Production mode
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
The API will be available at http://localhost:8000
Deployment
The bot runs on the self-hosted Dokploy instance at
dokploy.studyflash.ch (project Customer Support, environment
production). Pushes to main of studyflash-ai/studyflash auto-deploy
via Dokploy's GitHub integration (autoDeploy: true); no just deploy
or local Docker build is involved.
The service is publicly reachable at https://support-bot.studyflash.dev.
The Dokploy resources (project / environment / application / domain) are
declared in index.ts and reconciled by Pulumi via the Go-native
studyflash-dokploy provider. Apply changes with:
pnpm preview # pulumi preview --diff
pnpm run pulumi:up # pulumi up
Both inline infisical run --path=/internal/support-bot/ -- pulumi -s prod <action>.
Fresh-machine setup:
./infra/scripts/setup-pulumi.sh— R2 backend login.cd infra/dokploy && make install-plugin— build the provider binary and register it under~/.pulumi/plugins/.
Application env (GEMINI_API_KEY, CHATWOOT_API_URL, CHATWOOT_API_TOKEN,
etc.) is sourced from Infisical at /internal/support-bot/ on the prod
env at apply time and written to Dokploy's native env — see index.ts
for the full key list.
API Endpoints
1. POST /ask - Live Support Agent (Workflow 2)
The main endpoint for handling customer queries. Maximilian reads the entire KB and generates context-aware responses.
Request:
{
"user_email_body": "How do I cancel my subscription?",
"user_email_subject": "Cancellation question"
}
Response:
{
"response_html": "<p>Hi there! To cancel your subscription...</p>",
"status": "success"
}
Status Values:
success: Answer found in KB, HTML response ready to sendfallback: No answer found, returns<p>(No Answer)</p>, logs to database for human review
N8N Integration:
Outlook Trigger → HTTP Request Node (/ask) → Switch Node (check status)
→ Success: Reply to customer
→ Fallback: Forward to human agent
2. POST /analyze - Weekly Gap Analyzer (Workflow 3b)
Analyzes support gaps and suggests new Knowledge Base entries for recurring issues (3+ occurrences).
Request:
{
"logs": [
{
"user_query": "Can I use Studyflash offline?",
"ai_response": "(No Answer)",
"status": "processed_by_human",
"human_golden_answer": "Studyflash requires internet..."
}
],
"current_kb": "Optional - will fetch from Google Docs if not provided"
}
Response:
[
{
"Headline": "Offline Mode Support",
"Problem": "Users asking about offline functionality",
"Solution": "Studyflash requires internet connection..."
}
]
N8N Integration:
Schedule Trigger (Friday 8am) → Supabase Node (fetch logs)
→ HTTP Request (/analyze) → Slack Approval → Update Google Doc
3. POST /log-correction - Forward-to-Learn (Workflow 3a)
Logs human corrections when an agent manually answers a query that the AI couldn't handle.
Request:
{
"user_email_body": "Original customer question",
"human_answer": "The human agent's correct answer"
}
Response:
{
"status": "logged"
}
N8N Integration Guide
Workflow 2: Live Agent (Maximilian)
- Outlook Trigger - On new email in support inbox
- HTTP Request Node:
- URL:
YOUR_GCF_URL/ask - Method:
POST - Body:
{"user_email_body": "{{ $json.body }}"}
- URL:
- Switch Node - Check
{{ $json.status }}- Case
success: Use Outlook Node to reply with{{ $json.response_html }} - Case
fallback: Forward to human agent folder
- Case
Workflow 3a: Forward-to-Learn
- Outlook Trigger - Watch
n8n-Learnfolder or specific email address - Parse Email - Extract original query + human answer
- HTTP Request Node:
- URL:
YOUR_GCF_URL/log-correction - Method:
POST - Body: Query + human answer
- URL:
Workflow 3b: Weekly Analyst
- Schedule Trigger - Every Friday at 8:00 AM
- Supabase Node - Fetch rows where
status = 'processed_by_human' - HTTP Request Node:
- URL:
YOUR_GCF_URL/analyze - Method:
POST - Body:
{"logs": {{ $json }}}
- URL:
- If Node - Check if response array is not empty
- Slack Node - Send approval message
- Google Docs Node - Append approved entries to Master KB
- Supabase Node - Update logs to
status = 'added_to_kb'
Configuration Details
Knowledge Base Sources (Priority Order)
- Google Docs (if
GOOGLE_DOC_IDis set)- Uses gcloud Application Default Credentials
- Automatically fetches latest version
- No caching needed - AI reads full doc each time
- Local File (fallback)
- Reads from
KB_FILE_PATH - Good for development/testing
- Reads from
AI Model Configuration
Default: gpt-4o (set in config.py)
Supabase Database Schema
Create this table for the learning loop:
CREATE TABLE learning_logs (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
timestamp TIMESTAMPTZ DEFAULT NOW(),
user_query TEXT NOT NULL,
ai_response TEXT,
status TEXT NOT NULL, -- 'new_gap', 'processed_by_human', 'added_to_kb'
human_golden_answer TEXT
);
Enable it by uncommenting lines in services/database.py and installing:
pip install supabase
Key Design Decisions
Why Context Stuffing Over Vectors?
- Scale: KB is only ~10 pages (~10k tokens)
- Cost: Processing 10k tokens costs ~$0.0015 per query
- Accuracy: AI sees full context, not just retrieved chunks
- Simplicity: No embeddings, no vector sync, no chunking logic
Why Python API + N8N?
- Separation of Concerns: Business logic (Python) separate from orchestration (N8N)
- Testability: Easy to test API endpoints independently
- Flexibility: Can swap N8N for other workflow tools
- Debugging: Clear API boundaries make troubleshooting easier
Customization
Changing Prompts
Edit utils/prompts.py to modify:
- Agent personality and tone
- Language detection rules
- Fallback behavior
- Analyst criteria (e.g., change from 3+ to 5+ occurrences)
Adding New Endpoints
Follow the pattern in main.py:
- Create service class in
services/ - Add Pydantic models in
models/api.py - Add endpoint with dependency injection
Troubleshooting
"No module named 'google'"
pip install google-api-python-client google-auth
"Knowledge Base content is empty"
- Check
GOOGLE_DOC_IDis set correctly - Verify gcloud authentication:
gcloud auth application-default login - Ensure the document is shared with your service account
- Check that local
KB_FILE_PATHexists as fallback
AI returning "(No Answer)" too often
- Verify KB content is loading (check logs)
- Review prompt in
utils/prompts.py - Consider using GPT-4 instead of GPT-4o-mini for better instruction following
Supabase not logging
- Ensure
SUPABASE_URLandSUPABASE_KEYare set - Check
services/database.pyexceptions
License & Support
Built for Studyflash. For questions, contact the engineering team.