VibeFinder / src /agentic_workflow.py
Abubakar Diallo
Add VibeFinder app
55ed80a
Raw
History Blame Contribute Delete
11.2 kB
"""
Agentic Workflow for Music Recommendations.
This module implements an AI agent that can:
1. Analyze user preferences
2. Generate recommendations
3. Evaluate recommendation quality
4. Refine recommendations based on feedback
5. Self-correct and improve through iteration
The agent follows a plan-act-evaluate loop for autonomous refinement.
"""
from typing import List, Dict, Tuple, Optional
from enum import Enum
import json
class AgentState(Enum):
"""States the recommendation agent can be in."""
ANALYZING = "analyzing"
GENERATING = "generating"
EVALUATING = "evaluating"
REFINING = "refining"
COMPLETE = "complete"
class RecommendationAgent:
"""
Agentic AI system that autonomously improves recommendations through iteration.
Workflow:
1. ANALYZE: Understand user preferences deeply
2. GENERATE: Create initial recommendations
3. EVALUATE: Assess recommendation quality
4. REFINE: Improve based on quality metrics
5. COMPLETE: Return final recommendations with confidence scores
"""
def __init__(self, recommender, rag_system=None):
"""
Initialize the agent.
Args:
recommender: The base recommender system
rag_system: Optional RAG system for LLM-enhanced explanations
"""
self.recommender = recommender
self.rag_system = rag_system
self.state = AgentState.ANALYZING
self.iteration_count = 0
self.max_iterations = 3
self.action_log: List[Dict] = []
self.quality_scores: List[float] = []
def plan(self, user_prefs: Dict) -> Dict:
"""
PLAN: Analyze user preferences and create a recommendation strategy.
Args:
user_prefs: User preference dictionary
Returns:
Strategy dictionary with analysis and approach
"""
strategy = {
"step": "plan",
"user_profile": user_prefs,
"constraints": [],
"scoring_mode": "default",
"diversity_focus": False,
}
# Analyze preference constraints
if user_prefs.get("energy", 0.5) > 0.8:
strategy["constraints"].append("high_energy_required")
elif user_prefs.get("energy", 0.5) < 0.4:
strategy["constraints"].append("low_energy_preferred")
if user_prefs.get("likes_acoustic"):
strategy["constraints"].append("prefer_acoustic")
else:
strategy["constraints"].append("prefer_electronic")
# Determine scoring strategy
mood = user_prefs.get("mood", "").lower()
if "intense" in mood or "aggressive" in mood:
strategy["scoring_mode"] = "energy_focused"
elif "chill" in mood or "relaxed" in mood:
strategy["scoring_mode"] = "mood_first"
strategy["num_recommendations"] = 5
strategy["diversity_penalty"] = len(strategy["constraints"]) > 2
self.action_log.append(strategy)
return strategy
def act(
self,
user_prefs: Dict,
strategy: Dict,
songs: List[Dict],
) -> List[Tuple[Dict, float, List[str]]]:
"""
ACT: Generate recommendations based on the strategy.
Args:
user_prefs: User preferences
strategy: Strategy from planning phase
songs: Available songs
Returns:
List of (song, score, reasons) tuples
"""
from .recommender import recommend_songs
self.state = AgentState.GENERATING
recommendations = recommend_songs(
user_prefs=user_prefs,
songs=songs,
k=strategy.get("num_recommendations", 5),
mode=strategy.get("scoring_mode", "default"),
diversity_penalty=strategy.get("diversity_penalty", False),
)
action = {
"step": "act",
"strategy_applied": strategy,
"recommendations_generated": len(recommendations),
"top_score": recommendations[0][1] if recommendations else 0,
}
self.action_log.append(action)
return recommendations
def evaluate(
self,
recommendations: List[Tuple[Dict, float, List[str]]],
user_prefs: Dict,
) -> Tuple[float, Dict]:
"""
EVALUATE: Assess the quality of recommendations.
Args:
recommendations: Generated recommendations
user_prefs: User preferences
Returns:
(quality_score, evaluation_dict) tuple
"""
self.state = AgentState.EVALUATING
if not recommendations:
return 0.0, {"error": "No recommendations generated"}
quality_metrics = {
"genre_match_rate": 0.0,
"mood_match_rate": 0.0,
"energy_alignment": 0.0,
"diversity_score": 0.0,
"avg_score": 0.0,
}
# Genre and mood match rates
genre_matches = sum(
1 for song, _, _ in recommendations
if song.get("genre") == user_prefs.get("genre")
)
quality_metrics["genre_match_rate"] = genre_matches / len(recommendations)
mood_matches = sum(
1 for song, _, _ in recommendations
if song.get("mood") == user_prefs.get("mood")
)
quality_metrics["mood_match_rate"] = mood_matches / len(recommendations)
# Energy alignment
target_energy = user_prefs.get("energy", 0.5)
energy_diffs = [
abs(song["energy"] - target_energy)
for song, _, _ in recommendations
]
quality_metrics["energy_alignment"] = 1.0 - (
sum(energy_diffs) / len(energy_diffs)
)
# Diversity (artist and genre diversity)
artists = set(song["artist"] for song, _, _ in recommendations)
genres = set(song["genre"] for song, _, _ in recommendations)
quality_metrics["diversity_score"] = (
len(artists) / len(recommendations) * 0.5 +
len(genres) / len(recommendations) * 0.5
)
# Average recommendation score
avg_score = sum(score for _, score, _ in recommendations) / len(
recommendations
)
quality_metrics["avg_score"] = avg_score
# Overall quality score (weighted)
quality_score = (
quality_metrics["genre_match_rate"] * 0.25 +
quality_metrics["mood_match_rate"] * 0.25 +
quality_metrics["energy_alignment"] * 0.20 +
quality_metrics["diversity_score"] * 0.15 +
min(avg_score / 4.5, 1.0) * 0.15 # Normalize avg score
)
self.quality_scores.append(quality_score)
evaluation = {
"step": "evaluate",
"iteration": self.iteration_count,
"quality_score": round(quality_score, 2),
"metrics": {k: round(v, 2) for k, v in quality_metrics.items()},
"pass_threshold": quality_score > 0.65,
}
self.action_log.append(evaluation)
return quality_score, evaluation
def refine(
self,
user_prefs: Dict,
quality_score: float,
strategy: Dict,
) -> Optional[Dict]:
"""
REFINE: Adjust strategy based on evaluation.
Args:
user_prefs: User preferences
quality_score: Quality score from evaluation
strategy: Current strategy
Returns:
Refined strategy or None if no refinement needed
"""
if quality_score > 0.65 or self.iteration_count >= self.max_iterations:
return None
self.state = AgentState.REFINING
refined_strategy = strategy.copy()
if quality_score < 0.4:
# Low quality: switch to different scoring mode
modes = ["default", "mood_first", "energy_focused"]
current_idx = modes.index(strategy.get("scoring_mode", "default"))
refined_strategy["scoring_mode"] = modes[(current_idx + 1) % len(modes)]
refined_strategy["diversity_penalty"] = True
elif quality_score < 0.6:
# Medium quality: increase diversity
refined_strategy["diversity_penalty"] = True
refinement = {
"step": "refine",
"iteration": self.iteration_count,
"quality_score": quality_score,
"changes": {
"old_mode": strategy.get("scoring_mode"),
"new_mode": refined_strategy.get("scoring_mode"),
"old_diversity": strategy.get("diversity_penalty"),
"new_diversity": refined_strategy.get("diversity_penalty"),
},
}
self.action_log.append(refinement)
return refined_strategy
def run(
self,
user_prefs: Dict,
songs: List[Dict],
) -> Tuple[List[Tuple[Dict, float, List[str]]], Dict]:
"""
Execute the full agentic workflow: plan → act → evaluate → refine (loop).
Args:
user_prefs: User preferences
songs: Available songs
Returns:
(final_recommendations, workflow_summary) tuple
"""
self.iteration_count = 0
best_recommendations = None
best_quality = 0.0
while self.iteration_count < self.max_iterations:
# PLAN
strategy = self.plan(user_prefs)
# ACT
recommendations = self.act(user_prefs, strategy, songs)
# EVALUATE
quality_score, evaluation = self.evaluate(recommendations, user_prefs)
# Store best result
if quality_score > best_quality:
best_quality = quality_score
best_recommendations = recommendations
# Check if quality is acceptable
if evaluation.get("pass_threshold"):
self.state = AgentState.COMPLETE
break
# REFINE
refined_strategy = self.refine(user_prefs, quality_score, strategy)
if refined_strategy is None:
self.state = AgentState.COMPLETE
break
self.iteration_count += 1
self.state = AgentState.COMPLETE
summary = {
"total_iterations": self.iteration_count,
"final_quality_score": round(best_quality, 2),
"quality_improvement": (
round(self.quality_scores[-1] - self.quality_scores[0], 2)
if len(self.quality_scores) > 1
else 0.0
),
"workflow_log": self.action_log,
}
return best_recommendations or [], summary
def export_workflow(self) -> str:
"""Export the agent's workflow and decisions as JSON."""
return json.dumps(
{
"final_state": self.state.value,
"iterations": self.iteration_count,
"quality_scores": self.quality_scores,
"action_log": self.action_log,
},
indent=2,
)