Files
Bentlybro 4935119ac2 Default OPENAI_REASONING_EFFORT to 'low' (not 'minimal')
'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).
2026-04-22 14:03:38 +01:00

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
}