For Developers: How to integrate preference detection into your application
In your intent service, the preference detection is automatically integrated via hooks:
from services.intent_service.classifier import IntentClassifier
from services.intent_service.preference_handler import PreferenceDetectionHandler
# Preference detection is automatically enabled
classifier = IntentClassifier()
handler = PreferenceDetectionHandler()
# When classifying an intent, preferences are detected automatically
result = await classifier.classify(user_id, message)
# result.intent now includes:
# result.intent.preferences = {
# "hints": [...],
# "has_suggestions": True,
# "has_auto_applies": False,
# "analysis_summary": "..."
# }
When returning the response to the user:
from services.intent.intent_service import IntentService
intent_service = IntentService()
result = await intent_service.process_intent(user_id, message, session_id)
# Include preferences in JSON response
response = {
"response": result.response,
"preferences": result.preferences # Automatically set by hooks
}
return response
When user clicks “Accept” on a preference suggestion:
// In your JavaScript code
async function acceptPreference(hintId) {
const response = await fetch(
`/api/v1/preferences/hints/${hintId}/accept`,
{
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({session_id: getCurrentSessionId()})
}
);
const result = await response.json();
if (result.success) {
showToast(`Preference updated! ${result.message}`, 'success');
// Optionally refresh profile
updatePersonalityProfile();
}
}
User Message (with intent + context)
↓
IntentClassifier.classify()
├─ Classify intent
├─ Call hooks.on_intent_classified()
│ ├─ Load PersonalityProfile
│ ├─ Run ConversationAnalyzer.analyze_message()
│ ├─ Filter by confidence thresholds
│ ├─ Store hints in session
│ └─ Auto-apply high-confidence hints
↓
IntentProcessingResult with:
├─ intent (with preferences attached)
├─ response
└─ preferences: {hints, has_suggestions, has_auto_applies}
↓
Frontend receives response
├─ Display main response
└─ Display preference suggestions
↓
User confirms/rejects
↓
POST /api/v1/preferences/hints/{id}/accept
↓
PreferenceDetectionHandler.confirm_preference()
├─ Retrieve hint from session
├─ Create confirmation record
├─ Store in UserPreferenceManager
├─ Log to learning system
└─ Apply to personality profile
↓
All future responses reflect updated preferences
File: services/intent_service/classifier.py
Preferences are automatically detected after intent classification:
class IntentClassifier:
async def classify(self, user_id: str, message: str):
# ... existing classification logic ...
# Preferences are automatically detected via hooks
# See IntentProcessingHooks.on_intent_classified()
# Return intent with preferences attached
return intent
What it does:
File: services/intent_service/intent_service.py
Preferences are extracted and propagated to response:
class IntentService:
async def process_intent(self, user_id, message, session_id):
# ... intent processing ...
# Extract preferences from intent
result.preferences = intent.preferences
return result # Includes preferences
File: web/app.py
Preferences are included in API response:
@app.post("/api/v1/intent")
async def intent_endpoint(request):
result = await intent_service.process_intent(...)
return {
"response": result.response,
"preferences": result.preferences # ← Included here
}
File: services/intent_service/preference_handler.py
Manages the full preference lifecycle:
handler = PreferenceDetectionHandler()
# Store hints in session (called by hooks)
await handler._store_hints_in_session(session_id, hints)
# Retrieve hint when user confirms
hint = await handler._retrieve_hint_from_session(session_id, hint_id)
# Process confirmation (accept/reject)
result = await handler.confirm_preference(
user_id=user_id,
session_id=session_id,
hint_id=hint_id,
accepted=True
)
# Apply auto-preferences (high confidence)
await handler.apply_auto_preferences(
user_id=user_id,
session_id=session_id,
hints=auto_apply_hints
)
File: services/personality/conversation_analyzer.py
Detects preferences from message text:
analyzer = ConversationAnalyzer()
profile = await PersonalityProfile.load_with_preferences(user_id)
result = analyzer.analyze_message(
user_id=user_id,
message=message,
current_profile=profile
)
# result contains:
# - hints: All detected preferences
# - suggested_hints: Those ready for user confirmation (≥0.4)
# - auto_apply_hints: Those ready for silent application (≥0.9 + explicit)
# - analysis_summary: Human-readable explanation
To add your own detection logic:
from services.personality.conversation_analyzer import ConversationAnalyzer
from services.personality.preference_detection import PreferenceHint, DetectionMethod
class CustomAnalyzer(ConversationAnalyzer):
def _detect_custom_preference(self, user_id, message, current_profile):
"""Detect custom preference from message"""
# Your custom detection logic
if your_condition:
return PreferenceHint(
id=self._next_hint_id(),
user_id=user_id,
dimension=PreferenceDimension.YOUR_DIMENSION,
detected_value=your_value,
current_value=current_profile.your_field,
detection_method=DetectionMethod.CUSTOM_METHOD,
confidence_score=your_score,
source_text=message,
evidence={...}
)
return None
async def analyze_message(self, user_id, message, current_profile):
"""Override to include custom detection"""
result = await super().analyze_message(user_id, message, current_profile)
# Add custom detection
custom_hint = self._detect_custom_preference(user_id, message, current_profile)
if custom_hint:
result.hints.append(custom_hint)
if custom_hint.is_ready_for_suggestion():
result.suggested_hints.append(custom_hint)
return result
Add to services/personality/preference_detection.py:
class DetectionMethod(Enum):
"""Methods for detecting preferences"""
LANGUAGE_PATTERNS = "language_patterns"
BEHAVIORAL_SIGNALS = "behavioral_signals"
EXPLICIT_FEEDBACK = "explicit_feedback"
RESPONSE_ANALYSIS = "response_analysis"
YOUR_METHOD = "your_method" # ← Add here
Change when preferences appear as suggestions:
# In services/personality/conversation_analyzer.py
# Increase to only show high-confidence suggestions
SUGGESTION_THRESHOLD = 0.5 # Default: 0.4
# Then in PreferenceHint
def is_ready_for_suggestion(self) -> bool:
return self.confidence_score >= SUGGESTION_THRESHOLD
Change when preferences are applied silently:
# In services/personality/preference_detection.py
# Increase to require very high confidence for auto-apply
AUTO_APPLY_THRESHOLD = 0.95 # Default: 0.9
# Then in PreferenceHint
def is_ready_for_auto_apply(self) -> bool:
# Also requires explicit source (not just high confidence)
return (
self.confidence_score >= AUTO_APPLY_THRESHOLD and
self.detection_method == DetectionMethod.EXPLICIT_FEEDBACK
)
Change how long preference suggestions stay available:
# In services/intent_service/preference_handler.py
SESSION_HINT_TTL_MINUTES = 60 # Default: 30
# Hints older than this are automatically expired
# In web/app.py or your initialization
from services.intent_service.intent_hooks import IntentProcessingHooks
# Create hooks without preference handler
hooks = IntentProcessingHooks(preference_handler=None)
# Preference detection will be skipped
# In services/personality/preference_detection.py
# Set auto-apply threshold impossibly high
AUTO_APPLY_THRESHOLD = 1.1 # Never auto-apply
# Now users must confirm all preferences
# In services/personality/conversation_analyzer.py
async def analyze_message(self, user_id, message, current_profile):
result = PreferenceDetectionResult()
# Only run language patterns, skip others
warmth = self._detect_warmth_preference(...)
if warmth:
result.hints.append(warmth)
# Skip other detection methods
# (comment out _detect_confidence_preference, etc.)
return result
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger('services.personality.conversation_analyzer')
logger.setLevel(logging.DEBUG)
This will log:
curl http://localhost:8001/api/v1/preferences/health
Returns status of all components:
curl http://localhost:8001/api/v1/preferences/stats
Shows:
Preference detection failures don’t block intent processing:
# If preference detection fails, the system continues
# Check if preferences are present in result
if result.preferences and result.preferences.get('has_suggestions'):
# Show suggestions to user
pass
else:
# No suggestions (might be error or no signals)
# Continue without preference UI
pass
async def confirm_preference_with_retry(hint_id, session_id):
for attempt in range(3):
try:
result = await handler.confirm_preference(
user_id=user_id,
session_id=session_id,
hint_id=hint_id,
accepted=True
)
if result['success']:
return result
except Exception as e:
logger.error(f"Attempt {attempt+1} failed: {e}")
await asyncio.sleep(1) # Back off
raise Exception("Failed to confirm preference after retries")
# Good: Create once, reuse
analyzer = ConversationAnalyzer()
# Don't do this (creates new instance per call)
async def process(message):
analyzer = ConversationAnalyzer() # ← Avoid
return analyzer.analyze_message(...)
# If multiple preferences from one message
confirmations = []
for hint in result.preferences['hints']:
confirmation = await handler.confirm_preference(
user_id=user_id,
session_id=session_id,
hint_id=hint.id,
accepted=True
)
confirmations.append(confirmation)
# All confirmations processed
return {"confirmations": confirmations}
# Ensure database connections are pooled
# (Handled automatically by SQLAlchemy, but verify in production)
@pytest.mark.asyncio
async def test_preference_integration():
# Setup
analyzer = ConversationAnalyzer()
handler = PreferenceDetectionHandler()
profile = create_mock_profile()
# Test detection
message = "I'd like more technical detail"
result = analyzer.analyze_message(user_id, message, profile)
assert result.has_suggestions()
assert len(result.suggested_hints) > 0
# Test confirmation
hint = result.suggested_hints[0]
confirmation = await handler.confirm_preference(
user_id=user_id,
session_id=session_id,
hint_id=hint.id,
accepted=True
)
assert confirmation['success']
Issue: ConversationAnalyzer returns empty hints
Solution:
Issue: Preference accepted but doesn’t affect future responses
Solution:
Issue: analyze_message() takes >100ms
Solution:
Last Updated: November 22, 2025 Status: Production Ready ✅ Related: #248 (CONV-LEARN-PREF)