Personalization Engine
The Personalization Engine learns user preferences over time and adapts AI responses to match individual interaction patterns, preferred visualization types, query complexity levels, and domain focus areas. It operates as an internal integration within the AI Service, feeding into the agent orchestrator and response generation pipeline.
Architecture
The personalization system is implemented in src/personalization/ with four components:
| Component | File | Purpose |
|---|---|---|
| Engine | engine.py | Core personalization logic and preference computation |
| Models | models.py | User profile and preference data models |
| Feedback Collector | feedback.py | Integrates with the feedback system for implicit signals |
| Router | router.py | Personalized routing decisions for agent selection |
User Profile Model
Each user builds a profile over time through interactions:
class UserProfile:
user_id: str
tenant_id: str
preferences: UserPreferences
interaction_history: InteractionSummary
skill_level: SkillLevel # beginner, intermediate, advanced
domain_expertise: dict[str, float] # domain -> confidence
created_at: datetime
last_active: datetimePreference Categories
| Category | Tracked Signals | Adaptation |
|---|---|---|
| Visualization | Chart types clicked, modified, or exported | Default chart type selection |
| Complexity | SQL complexity of accepted queries | Adjust explanation detail level |
| Domain | Tables and metrics frequently queried | Prioritize relevant schema context |
| Response style | Length of preferred responses | Adjust verbosity |
| Time patterns | Active hours, query frequency | Proactive suggestion timing |
| Interaction mode | Chat vs. dashboard vs. direct SQL | Adapt interface suggestions |
Personalization Signals
The engine collects signals from multiple sources:
Explicit Signals
- User sets preferred chart types in settings
- User selects a persona during onboarding
- User bookmarks specific dashboards or queries
Implicit Signals
- Query topics and tables accessed
- Visualization types accepted vs. modified
- Response ratings and corrections
- Session duration and engagement patterns
- SQL complexity of accepted queries
Response Adaptation
The personalization engine influences responses at three points:
1. Agent Routing
class PersonalizedRouter:
async def select_agent(self, query: str, profile: UserProfile) -> str:
if profile.skill_level == SkillLevel.BEGINNER:
return "guided_sql_agent" # More explanatory
return "standard_sql_agent" # Concise responses2. Context Assembly
The schema context retrieval is biased toward tables and columns the user frequently queries:
async def get_personalized_context(query: str, profile: UserProfile):
base_context = await get_schema_context(query)
# Boost relevance of frequently-used tables
for table in profile.preferences.frequent_tables:
base_context.boost(table, weight=1.5)
return base_context3. Response Formatting
Response detail level is adjusted based on user expertise:
| Skill Level | SQL Explanation | Visualization | Insights |
|---|---|---|---|
| Beginner | Full step-by-step | Simple charts with annotations | Detailed business context |
| Intermediate | Key highlights | Standard charts | Relevant comparisons |
| Advanced | Minimal (SQL only on request) | Advanced charts | Statistical significance |
Configuration
| Environment Variable | Default | Description |
|---|---|---|
PERSONALIZATION_ENABLED | true | Enable personalization engine |
PERSONALIZATION_MIN_INTERACTIONS | 10 | Minimum interactions before personalizing |
PERSONALIZATION_DECAY_DAYS | 30 | Days before old signals lose weight |
PERSONALIZATION_PROFILE_TTL | 86400 | Profile cache TTL in seconds |
Privacy
Personalization profiles respect user privacy settings:
- Users can opt out of personalization entirely
- Profiles are tenant-isolated and never shared across tenants
- PII is stripped from interaction history before profile computation
- Users can request profile deletion through the Privacy Center