mirror of
https://github.com/Significant-Gravitas/AutoModerate.git
synced 2026-11-09 04:59:33 -10:00
'minimal' is only valid on older gpt-5 / o-series models and is rejected
by gpt-5.4 with a 400 ("Supported values are: 'none', 'low', 'medium',
'high', 'xhigh'"). Our fail-safe silently approved every request when the
API errored, which presented as "moderation is fast but broken" in real
tests — scam / phishing / misinfo content was getting through.
'low' is the lowest common value accepted by every reasoning model in the
family so the default works regardless of which model you point the
OPENAI_CHAT_MODEL at. Empirically verified: 8/8 real-world classification
accuracy on gpt-5.4-nano with reasoning=low, vs 2/4 with reasoning=none
(false negatives on obvious scams).
98 lines
3.9 KiB
Python
98 lines
3.9 KiB
Python
import os
|
|
|
|
from dotenv import load_dotenv
|
|
|
|
load_dotenv()
|
|
|
|
|
|
class Config:
|
|
SECRET_KEY = os.environ.get(
|
|
'SECRET_KEY') or 'dev-secret-key-change-in-production'
|
|
SQLALCHEMY_DATABASE_URI = os.environ.get(
|
|
'DATABASE_URL') or 'sqlite:///automoderate.db'
|
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
|
# Increase max content length to handle large prompts (10MB)
|
|
MAX_CONTENT_LENGTH = 10 * 1024 * 1024
|
|
OPENAI_API_KEY = os.environ.get('OPENAI_API_KEY')
|
|
# OpenAI model and token configuration
|
|
OPENAI_CHAT_MODEL = os.environ.get(
|
|
'OPENAI_CHAT_MODEL', 'gpt-5-nano-2025-08-07')
|
|
# Estimated maximum context window size for the selected model
|
|
OPENAI_CONTEXT_WINDOW = int(os.environ.get(
|
|
'OPENAI_CONTEXT_WINDOW', '272000'))
|
|
# Upper bound for output tokens; actual requests may use much less
|
|
# AI moderator only returns small JSON responses (~100-200 tokens), so 500 is plenty
|
|
OPENAI_MAX_OUTPUT_TOKENS = int(os.environ.get(
|
|
'OPENAI_MAX_OUTPUT_TOKENS', '500'))
|
|
# Reasoning level for gpt-5 / o-series models. Valid values are model-
|
|
# specific:
|
|
# gpt-5 / o-series: minimal | low | medium | high
|
|
# gpt-5.4 (nano etc): none | low | medium | high | xhigh
|
|
# 'low' is the only value accepted across every reasoning model in the
|
|
# supported family, so it's a safe default. Override per-deployment in
|
|
# .env if you've picked a model and want a different point on the
|
|
# latency/quality curve. Note that 'none' on gpt-5.4 measurably degrades
|
|
# moderation accuracy on borderline content (false negatives on scams,
|
|
# phishing, misinformation) — verified empirically on this codebase.
|
|
OPENAI_REASONING_EFFORT = os.environ.get('OPENAI_REASONING_EFFORT', 'low')
|
|
ADMIN_EMAIL = os.environ.get('ADMIN_EMAIL')
|
|
ADMIN_PASSWORD = os.environ.get('ADMIN_PASSWORD')
|
|
|
|
# Google OAuth configuration
|
|
GOOGLE_CLIENT_ID = os.environ.get('GOOGLE_CLIENT_ID')
|
|
GOOGLE_CLIENT_SECRET = os.environ.get('GOOGLE_CLIENT_SECRET')
|
|
GOOGLE_DISCOVERY_URL = "https://accounts.google.com/.well-known/openid-configuration"
|
|
|
|
# Sentry configuration
|
|
SENTRY_DSN = os.environ.get('SENTRY_DSN')
|
|
|
|
# Database connection preference
|
|
USE_DIRECT_POSTGRES = bool(os.environ.get(
|
|
'DATABASE_URL', '').startswith('postgresql://'))
|
|
|
|
# SQLAlchemy connection pool configuration
|
|
SQLALCHEMY_ENGINE_OPTIONS = {
|
|
'pool_size': 50, # Number of connections to maintain in pool (was 5)
|
|
'pool_timeout': 60, # Seconds to wait for connection from pool (was 30)
|
|
'pool_recycle': 1800, # Seconds before recreating connection (30 min)
|
|
'pool_pre_ping': True, # Verify connections before use
|
|
'max_overflow': 100, # Additional connections beyond pool_size (was 10)
|
|
'echo': bool(os.environ.get('SQL_DEBUG', False)) # SQL debugging via env var
|
|
}
|
|
|
|
# ThreadPoolExecutor configuration for async database operations
|
|
DB_THREAD_POOL_WORKERS = int(os.environ.get('DB_THREAD_POOL_WORKERS', '100'))
|
|
|
|
|
|
class DevelopmentConfig(Config):
|
|
DEBUG = True
|
|
|
|
|
|
class ProductionConfig(Config):
|
|
DEBUG = False
|
|
|
|
|
|
def validate_production_config():
|
|
"""Fail fast if required secrets are missing in production.
|
|
|
|
A silent fallback to the dev default SECRET_KEY would make session cookies
|
|
forgeable using a publicly-known key. Better to refuse to boot.
|
|
"""
|
|
missing = []
|
|
if not os.environ.get('SECRET_KEY'):
|
|
missing.append('SECRET_KEY')
|
|
if not os.environ.get('OPENAI_API_KEY'):
|
|
missing.append('OPENAI_API_KEY')
|
|
if missing:
|
|
raise RuntimeError(
|
|
f"Missing required environment variables in production: {', '.join(missing)}. "
|
|
"Generate a SECRET_KEY with: python -c 'import secrets; print(secrets.token_urlsafe(64))'"
|
|
)
|
|
|
|
|
|
config = {
|
|
'development': DevelopmentConfig,
|
|
'production': ProductionConfig,
|
|
'default': DevelopmentConfig
|
|
}
|