Setup guide, design rationale, and troubleshooting. If something is missing, open an issue.
Python 3.14+ (or Docker)
PostgreSQL 15+ (or Docker)
Redis 7+ (or Docker)
LLM API key (OpenRouter free tier or OpenAI)
uv package manager
LLM_PROVIDER "openai" or "openrouter". Default: openrouter.
LLM_MODEL Model ID. Default: nvidia/nemotron-3-super-120b-a12b:free.
OPENROUTER_API_KEY Required if LLM_PROVIDER=openrouter.
OPENAI_API_KEY Required if LLM_PROVIDER=openai.
REQUIRE_AUTH Set to "true" to enable API key validation. Default: false.
DATABASE_URL PostgreSQL connection string.
DATABASE_READONLY_URL Optional. Read-only replica for queries.
REDIS_URL Redis connection string.
CSRF_SECRET_KEY Optional. Secret for signing CSRF cookies.
ALLOWED_ORIGINS Optional. Comma-separated CORS origins.
MAX_PROMPT_LENGTH Max prompt chars. Default: 2000.
QUERY_TIMEOUT_SECONDS Query execution timeout. Default: 30.
LOG_LEVEL Logging level. Default: INFO.
LOG_FORMAT "json" or "text". Default: json.
git clone https://github.com/palmshed/predicate.git
cd predicate
cp .env.example .env
# Set OPENROUTER_API_KEY or OPENAI_API_KEY in .env
docker-compose up --build
API docs at http://localhost:8000/docs
Most natural language to SQL tools let the AI generate raw SQL directly. This is a security risk. The AI can hallucinate table names, inject arbitrary queries, or leak data across tenants.
predicate solves this by introducing a structured intermediate layer. The AI generates a constrained JSON blueprint (Pydantic validated). The compiler translates that blueprint into parameterized SQL. The AI never touches the query string.
tenant_id is extracted from the validated API key and injected into every WHERE clause. The AI cannot override this.ALLOWED_SCHEMA can be queried. Unknown fields are rejected before compilation./metrics.| Symptom | Cause | Resolution |
|---|---|---|
| 401 Unauthorized | Missing or invalid API key | Include X-Predicate-API-Key header. Check key in mock registry. |
| 403 Forbidden | CSRF token missing, expired, or mismatched | Ensure cookies are enabled. If behind a proxy, check that the X-CSRF-Token header matches the signed cookie. Set CSRF_SECRET_KEY for persistence. |
| 429 Too Many Requests | Rate limit exceeded for your tier | Wait for the window to reset. Sandbox: 60 RPM, Growth: 20 RPM, Enterprise: 100 RPM. |
| Empty results array | Query returned no matching rows | Check your data. Tenant isolation filters by tenant_id. |
| Compilation error | AI generated invalid blueprint | Check LLM API key in .env. The Pydantic schema constrains output to valid blueprints. |
| Query timeout | Execution exceeded QUERY_TIMEOUT_SECONDS |
Increase timeout or optimize the query. Default is 30 seconds. |
| Export stuck in PROCESSING | Celery worker not running | Start Celery: celery -A app.worker worker --loglevel=info |
| Redis connection error | Redis not running | Start Redis via Docker Compose, or caching falls back to no-cache mode. |
| DB connection pool exhausted | Too many concurrent queries | Increase maxconn in connection.py or reduce concurrency. |
GET /health → {"status": "healthy", "checks": {"db": true, "redis": true}, ...}
GET /ready → 200 if all checks pass, 503 otherwise
GET /metrics → Prometheus text format
Found something unexpected? Report an issue.