File size: 16,291 Bytes
f9db034
 
 
 
 
 
e6cbce9
f9db034
 
fec9e33
 
f9db034
 
e6cbce9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f9db034
 
 
e6cbce9
 
 
fec9e33
e6cbce9
 
 
 
00dd8ea
fec9e33
00dd8ea
 
 
 
 
 
 
fec9e33
f9db034
f7943d1
 
 
 
 
 
e6cbce9
 
 
f9db034
e6cbce9
 
d5eabda
f9db034
e6cbce9
 
 
f9db034
e6cbce9
 
f9db034
e6cbce9
 
 
 
00dd8ea
f9db034
d71324e
 
d5eabda
d71324e
f9db034
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d5eabda
f9db034
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fec9e33
 
 
 
 
2092684
f7943d1
42651db
d5eabda
42651db
d5eabda
42651db
 
d5eabda
42651db
 
 
 
 
 
 
fec9e33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
"""
Agent Models for BeatDebate Multi-Agent Music Recommendation System

Pydantic models for state management, agent communication, and data structures.
"""

from typing import Dict, List, Any, Optional, Annotated
from pydantic import BaseModel, Field
from datetime import datetime
from enum import Enum
from dataclasses import dataclass


def keep_first(x: Any, y: Any) -> Any:
    """Reducer that keeps the first non-None value"""
    # If x is None or empty, use y
    if x is None or (isinstance(x, (list, dict, str)) and len(x) == 0):
        return y
    # Otherwise keep x (the first/existing value)
    return x


def list_replace_reducer(x: List, y: List) -> List:
    """Reducer that replaces the list entirely if y is not empty"""
    if y is None:
        return x if x is not None else []
    if len(y) > 0:
        return y
    return x if x is not None else []


def list_append_reducer(x: List, y: List) -> List:
    """Reducer that appends new items to existing list"""
    if x is None:
        x = []
    if y is None:
        y = []
    return x + y


def dict_update_reducer(x: Dict, y: Dict) -> Dict:
    """Reducer that updates dictionary with new values"""
    if x is None:
        x = {}
    if y is None:
        y = {}
    result = x.copy()
    result.update(y)
    return result


class MusicRecommenderState(BaseModel):
    """Shared state across all agents in the LangGraph workflow"""
    
    # Input - these should not change after initial setting
    user_query: Annotated[str, keep_first] = Field(..., description="Original user query for music recommendation")
    user_profile: Annotated[Optional[Dict[str, Any]], keep_first] = Field(default=None, description="User preferences and history")
    max_recommendations: Annotated[int, keep_first] = Field(default=10, description="Maximum number of recommendations to return")
    
    # Planning phase - set once by planner
    planning_strategy: Annotated[Optional[Dict[str, Any]], keep_first] = Field(default=None, description="Strategy created by PlannerAgent")
    execution_plan: Annotated[Optional[Dict[str, Any]], keep_first] = Field(default=None, description="Execution monitoring plan")
    coordination_strategy: Annotated[Optional[Dict[str, Any]], keep_first] = Field(default=None, description="Enhanced coordination strategy with confidence-based selection")
    agent_coordination: Annotated[Optional[Dict[str, Any]], keep_first] = Field(default=None, description="Agent coordination plan")
    
    # Enhanced Planning Phase - Entity Recognition (NEW)
    entities: Annotated[Optional[Dict[str, Any]], keep_first] = Field(default=None, description="Extracted entities from user query")
    intent_analysis: Annotated[Optional[Dict[str, Any]], keep_first] = Field(default=None, description="Intent analysis from user query")
    query_understanding: Annotated[Optional[Any], keep_first] = Field(default=None, description="Pure LLM query understanding result")
    conversation_context: Annotated[Optional[Dict[str, Any]], dict_update_reducer] = Field(default=None, description="Session conversation context")
    entity_reasoning: Annotated[List[Dict], list_append_reducer] = Field(default_factory=list, description="Entity extraction reasoning steps")
    context_decision: Annotated[Optional[Dict[str, Any]], keep_first] = Field(default=None, description="Context analysis decision")
    
    # 🔧 NEW: History tracking for follow-up queries  
    recently_shown_track_ids: Optional[List[str]] = Field(
        default_factory=list, 
        description="Track IDs recently shown to the user in this session to avoid duplicates in follow-up queries"
    )
    
    # Advocate phase - these will be updated by parallel agents
    genre_mood_recommendations: Annotated[List[Dict], list_append_reducer] = Field(default_factory=list, description="GenreMoodAgent recommendations")
    discovery_recommendations: Annotated[List[Dict], list_append_reducer] = Field(default_factory=list, description="DiscoveryAgent recommendations")
    
    # Judge phase - set by judge agent
    final_recommendations: Annotated[List[Dict], list_replace_reducer] = Field(default_factory=list, description="Final selected recommendations")
    judge_metadata: Annotated[Optional[Dict[str, Any]], keep_first] = Field(default=None, description="Metadata about judge decision process")
    
    # Reasoning transparency - can be updated by any agent
    reasoning_log: Annotated[List[str], list_append_reducer] = Field(default_factory=list, description="Step-by-step reasoning log")
    agent_deliberations: Annotated[List[Dict], list_append_reducer] = Field(default_factory=list, description="Agent decision records")
    
    # Error handling - can be set by any agent
    error_info: Annotated[Optional[Dict[str, str]], keep_first] = Field(default=None, description="Error information if workflow fails")
    
    # Metadata - set once at start, updated at end
    processing_start_time: Annotated[Optional[float], keep_first] = Field(default=None, description="Processing start timestamp")
    total_processing_time: Annotated[Optional[float], keep_first] = Field(default=None, description="Total processing time in seconds")
    session_id: Annotated[Optional[str], keep_first] = Field(default=None, description="Unique session identifier")
    confidence: Annotated[Optional[float], keep_first] = Field(default=None, description="Overall confidence in query understanding")

    # NEW: For intent override system
    context_override: Annotated[Optional[Dict[str, Any]], keep_first] = Field(default=None, description="Context override for intent system")
    effective_intent: Annotated[Optional[Dict[str, Any]], keep_first] = Field(default=None, description="Effective intent combining query analysis and context override")


class AgentStrategy(BaseModel):
    """Strategy object passed between agents"""
    
    task_analysis: Dict[str, Any] = Field(..., description="Analysis of the user query and task")
    coordination_strategy: Dict[str, Any] = Field(..., description="Strategy for each advocate agent")
    evaluation_framework: Dict[str, Any] = Field(..., description="Criteria for judge evaluation")
    execution_monitoring: Dict[str, Any] = Field(..., description="Monitoring and adaptation protocols")


class TaskAnalysis(BaseModel):
    """Analysis of user query complexity and intent"""
    
    primary_goal: str = Field(..., description="Main intent extracted from query")
    complexity_level: str = Field(..., description="Query complexity: simple, medium, complex")
    context_factors: List[str] = Field(default_factory=list, description="Context clues from query")
    mood_indicators: List[str] = Field(default_factory=list, description="Mood/energy indicators")
    genre_hints: List[str] = Field(default_factory=list, description="Genre preferences or hints")


class AgentCoordinationStrategy(BaseModel):
    """Coordination strategy for advocate agents"""
    
    genre_mood_agent: Dict[str, Any] = Field(..., description="Strategy for GenreMoodAgent")
    discovery_agent: Dict[str, Any] = Field(..., description="Strategy for DiscoveryAgent")


class EvaluationFramework(BaseModel):
    """Framework for judge evaluation"""
    
    primary_weights: Dict[str, float] = Field(..., description="Weights for different criteria")
    diversity_targets: Dict[str, int] = Field(..., description="Diversity targets for recommendations")
    explanation_style: str = Field(..., description="Style for generating explanations")


class TrackRecommendation(BaseModel):
    """Individual track recommendation with reasoning"""
    
    # Track metadata
    title: str = Field(..., description="Track title")
    artist: str = Field(..., description="Artist name")
    album: Optional[str] = Field(default=None, description="Album name")
    year: Optional[int] = Field(default=None, description="Release year")
    
    # External identifiers
    lastfm_url: Optional[str] = Field(default=None, description="Last.fm URL")
    spotify_url: Optional[str] = Field(default=None, description="Spotify URL")
    preview_url: Optional[str] = Field(default=None, description="Audio preview URL")
    
    # Metadata
    genres: List[str] = Field(default_factory=list, description="Genre tags")
    tags: List[str] = Field(default_factory=list, description="Mood/style tags")
    similar_artists: List[str] = Field(default_factory=list, description="Similar artists")
    
    # Recommendation context
    reasoning_chain: str = Field(..., description="Agent's reasoning for this recommendation")
    confidence_score: float = Field(..., ge=0.0, le=1.0, description="Confidence in recommendation")
    novelty_score: Optional[float] = Field(default=None, ge=0.0, le=1.0, description="Novelty/underground score")
    relevance_score: float = Field(..., ge=0.0, le=1.0, description="Relevance to user query")
    
    # Agent attribution
    recommending_agent: str = Field(..., description="Agent that made this recommendation")
    strategy_applied: Dict[str, Any] = Field(..., description="Strategy used for this recommendation")


class AgentDeliberation(BaseModel):
    """Record of agent decision-making process"""
    
    agent_name: str = Field(..., description="Name of the agent")
    timestamp: datetime = Field(default_factory=datetime.now, description="When deliberation occurred")
    input_data: Dict[str, Any] = Field(..., description="Input data for the agent")
    reasoning_steps: List[str] = Field(..., description="Step-by-step reasoning process")
    output_data: Dict[str, Any] = Field(..., description="Agent's output/decision")
    confidence: float = Field(..., ge=0.0, le=1.0, description="Agent's confidence in decision")
    processing_time: float = Field(..., description="Time taken for deliberation in seconds")


class ReasoningChain(BaseModel):
    """Structured reasoning chain for transparency"""
    
    step_number: int = Field(..., description="Step number in reasoning chain")
    step_type: str = Field(..., description="Type of reasoning step")
    description: str = Field(..., description="Description of reasoning step")
    evidence: List[str] = Field(default_factory=list, description="Evidence supporting this step")
    confidence: float = Field(..., ge=0.0, le=1.0, description="Confidence in this reasoning step")


class FinalRecommendationResponse(BaseModel):
    """Final response format for the user"""
    
    recommendations: List[TrackRecommendation] = Field(..., description="Final track recommendations")
    explanation: str = Field(..., description="Overall explanation of recommendations")
    planning_summary: str = Field(..., description="Summary of planning process")
    agent_coordination_summary: str = Field(..., description="Summary of agent coordination")
    
    # Metadata
    total_processing_time: float = Field(..., description="Total processing time in seconds")
    agents_involved: List[str] = Field(..., description="List of agents that participated")
    reasoning_transparency: List[AgentDeliberation] = Field(..., description="Full reasoning transparency")
    session_id: str = Field(..., description="Session identifier")
    
    # Quality metrics
    diversity_score: float = Field(..., ge=0.0, le=1.0, description="Diversity of recommendations")
    novelty_score: float = Field(..., ge=0.0, le=1.0, description="Average novelty score")
    confidence_score: float = Field(..., ge=0.0, le=1.0, description="Overall confidence")


class AgentConfig(BaseModel):
    """Configuration for individual agents"""
    
    agent_name: str = Field(..., description="Name of the agent")
    agent_type: str = Field(..., description="Type of agent (planner, advocate, judge)")
    llm_model: str = Field(default="gemini-2.0-flash-exp", description="LLM model to use")
    temperature: float = Field(default=0.7, ge=0.0, le=2.0, description="LLM temperature")
    max_tokens: int = Field(default=1000, description="Maximum tokens for LLM response")
    timeout_seconds: int = Field(default=60, description="Timeout for agent processing")
    
    # Agent-specific configuration
    specialty_config: Dict[str, Any] = Field(default_factory=dict, description="Agent-specific configuration")


class SystemConfig(BaseModel):
    """Overall system configuration"""
    
    # API configurations
    gemini_api_key: str = Field(..., description="Gemini API key")
    lastfm_api_key: str = Field(..., description="Last.fm API key")
    spotify_client_id: Optional[str] = Field(default=None, description="Spotify client ID")
    spotify_client_secret: Optional[str] = Field(default=None, description="Spotify client secret")
    
    # Rate limiting
    gemini_rate_limit: int = Field(default=15, description="Gemini requests per minute")
    lastfm_rate_limit: float = Field(default=3.0, description="Last.fm requests per second")
    spotify_rate_limit: int = Field(default=50, description="Spotify requests per hour")
    
    # Caching
    cache_enabled: bool = Field(default=True, description="Enable caching")
    cache_ttl_hours: int = Field(default=24, description="Cache TTL in hours")
    cache_directory: str = Field(default="data/cache", description="Cache directory path")
    
    # Agent configurations
    agent_configs: Dict[str, AgentConfig] = Field(default_factory=dict, description="Configuration for each agent")
    
    # Performance settings
    max_concurrent_agents: int = Field(default=2, description="Maximum concurrent agent executions")
    total_timeout_minutes: int = Field(default=5, description="Total workflow timeout in minutes")


class QueryIntent(Enum):
    """Primary intent types for music queries."""
    BY_ARTIST = "by_artist"                     # "Music by X" - focus on artist's own tracks
    BY_ARTIST_UNDERGROUND = "by_artist_underground"  # "Discover underground tracks by X" - focus on artist's deep cuts/b-sides
    ARTIST_SIMILARITY = "artist_similarity"      # "Music like X" - focus on similarity
    ARTIST_GENRE = "artist_genre"               # "Songs by X that are Y genre" - artist tracks filtered by genre
    DISCOVERY = "discovery"                      # "Something new and different" - focus on novelty
    DISCOVERING_SERENDIPITY = "discovering_serendipity"  # "Surprise me with something unexpected" - focus on serendipitous discovery
    GENRE_MOOD = "genre_mood"                   # "Upbeat electronic music" - focus on style/vibe
    CONTEXTUAL = "contextual"                   # "Music for studying" - focus on functional fit
    HYBRID_SIMILARITY_GENRE = "hybrid_similarity_genre"  # "Music like [Artist] but [Genre]" - artist similarity + genre filtering
    
    # Legacy compatibility (can be removed after migration)
    GENRE_EXPLORATION = "genre_exploration"      # Maps to GENRE_MOOD
    MOOD_MATCHING = "mood_matching"              # Maps to GENRE_MOOD  
    ACTIVITY_CONTEXT = "activity_context"        # Maps to CONTEXTUAL
    PLAYLIST_BUILDING = "playlist_building"      # Maps to CONTEXTUAL
    SPECIFIC_REQUEST = "specific_request"        # Maps to ARTIST_SIMILARITY


class SimilarityType(Enum):
    """Types of similarity for artist-based queries."""
    STYLISTIC = "stylistic"        # Similar sound/production
    GENRE = "genre"               # Same genre family
    ERA = "era"                   # Same time period
    MOOD = "mood"                 # Similar emotional feel
    ENERGY = "energy"             # Similar energy level


@dataclass
class QueryUnderstanding:
    """Structured representation of understood query."""
    intent: QueryIntent
    confidence: float
    
    # Core entities
    artists: List[str]
    genres: List[str]
    moods: List[str]
    activities: List[str]
    
    # Intent-specific details
    similarity_type: Optional[SimilarityType] = None
    exploration_level: str = "moderate"  # strict, moderate, broad
    temporal_context: Optional[str] = None
    energy_level: Optional[str] = None
    
    # Agent coordination hints
    primary_agent: str = "genre_mood"  # Which agent should lead
    agent_weights: Dict[str, float] = None
    search_strategy: Dict[str, Any] = None
    
    # Original query context
    original_query: str = ""
    normalized_query: str = ""
    reasoning: str = ""