internal/support-bot/README.md

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:

  1. ./infra/scripts/setup-pulumi.sh — R2 backend login.
  2. 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 send
  • fallback: 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)

  1. Outlook Trigger - On new email in support inbox
  2. HTTP Request Node:
    • URL: YOUR_GCF_URL/ask
    • Method: POST
    • Body: {"user_email_body": "{{ $json.body }}"}
  3. Switch Node - Check {{ $json.status }}
    • Case success: Use Outlook Node to reply with {{ $json.response_html }}
    • Case fallback: Forward to human agent folder

Workflow 3a: Forward-to-Learn

  1. Outlook Trigger - Watch n8n-Learn folder or specific email address
  2. Parse Email - Extract original query + human answer
  3. HTTP Request Node:
    • URL: YOUR_GCF_URL/log-correction
    • Method: POST
    • Body: Query + human answer

Workflow 3b: Weekly Analyst

  1. Schedule Trigger - Every Friday at 8:00 AM
  2. Supabase Node - Fetch rows where status = 'processed_by_human'
  3. HTTP Request Node:
    • URL: YOUR_GCF_URL/analyze
    • Method: POST
    • Body: {"logs": {{ $json }}}
  4. If Node - Check if response array is not empty
  5. Slack Node - Send approval message
  6. Google Docs Node - Append approved entries to Master KB
  7. Supabase Node - Update logs to status = 'added_to_kb'

Configuration Details

Knowledge Base Sources (Priority Order)

  1. Google Docs (if GOOGLE_DOC_ID is set)
    • Uses gcloud Application Default Credentials
    • Automatically fetches latest version
    • No caching needed - AI reads full doc each time
  2. Local File (fallback)
    • Reads from KB_FILE_PATH
    • Good for development/testing

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:

  1. Create service class in services/
  2. Add Pydantic models in models/api.py
  3. 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_ID is set correctly
  • Verify gcloud authentication: gcloud auth application-default login
  • Ensure the document is shared with your service account
  • Check that local KB_FILE_PATH exists 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_URL and SUPABASE_KEY are set
  • Check services/database.py exceptions

License & Support

Built for Studyflash. For questions, contact the engineering team.