feat(api): add LLM response validation and input sanitization

Implement Phase 1: Safety & Validation for all LLM-based suggestion engines.

- Add input sanitization module to prevent prompt injection attacks
- Implement 5 comprehensive validators (routine, batch, shopping, product parse, photo)
- Add 10+ critical safety checks (retinoid+acid conflicts, barrier compatibility, etc.)
- Integrate validation into all 5 API endpoints (routines, products, skincare)
- Add validation fields to ai_call_logs table (validation_errors, validation_warnings, auto_fixed)
- Create database migration for validation fields
- Add comprehensive test suite (9/9 tests passing, 88% coverage on validators)

Safety improvements:
- Blocks retinoid + acid conflicts in same routine/day
- Rejects unknown product IDs
- Enforces min_interval_hours rules
- Protects compromised skin barriers
- Prevents prohibited fields (dose, amount) in responses
- Validates all enum values and score ranges

All validation failures are logged and responses are rejected with HTTP 502.
This commit is contained in:
Piotr Oleszczyk 2026-03-06 10:16:47 +01:00
parent e3ed0dd3a3
commit 2a9391ad32
16 changed files with 2357 additions and 13 deletions

View file

@ -0,0 +1,42 @@
"""Add validation fields to ai_call_logs
Revision ID: 60c8e1ade29d
Revises: 1f7e3b9c4a2d
Create Date: 2026-03-06 00:24:18.842351
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "60c8e1ade29d"
down_revision: Union[str, Sequence[str], None] = "1f7e3b9c4a2d"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# Add validation fields to ai_call_logs
op.add_column(
"ai_call_logs", sa.Column("validation_errors", sa.JSON(), nullable=True)
)
op.add_column(
"ai_call_logs", sa.Column("validation_warnings", sa.JSON(), nullable=True)
)
op.add_column(
"ai_call_logs",
sa.Column("auto_fixed", sa.Boolean(), nullable=False, server_default="false"),
)
def downgrade() -> None:
"""Downgrade schema."""
# Remove validation fields from ai_call_logs
op.drop_column("ai_call_logs", "auto_fixed")
op.drop_column("ai_call_logs", "validation_warnings")
op.drop_column("ai_call_logs", "validation_errors")