Spaces:
Runtime error
Runtime error
| """Music catalog tools for the multi-agent system.""" | |
| import logging | |
| from langchain_core.tools import tool | |
| from src.db.database import run_query_safe | |
| logger = logging.getLogger(__name__) | |
| def _safe_int(value: str, label: str = "value") -> int: | |
| try: | |
| return int(value) | |
| except (ValueError, TypeError): | |
| raise ValueError(f"Invalid {label}: '{value}'. Please provide a numeric value.") | |
| def get_albums_by_artist(artist: str) -> str: | |
| """Get albums by an artist from the music catalog. Uses fuzzy matching on artist name.""" | |
| logger.info(f"TOOL_CALL: get_albums_by_artist | artist={artist}") | |
| try: | |
| result = run_query_safe( | |
| """ | |
| SELECT Album.AlbumId, Album.Title AS AlbumTitle, Artist.Name AS ArtistName | |
| FROM Album | |
| JOIN Artist ON Album.ArtistId = Artist.ArtistId | |
| WHERE Artist.Name LIKE :pattern | |
| ORDER BY Album.Title; | |
| """, | |
| {"pattern": f"%{artist}%"}, | |
| ) | |
| logger.info(f"TOOL_RESULT: get_albums_by_artist | result_length={len(result)}") | |
| if result == "[]": | |
| return f"No albums found for artist: {artist}" | |
| return result | |
| except Exception as e: | |
| logger.error(f"Error in get_albums_by_artist: {e}") | |
| return f"Error looking up albums for '{artist}'. Please try again." | |
| def get_tracks_by_artist(artist: str) -> str: | |
| """ | |
| Get songs/tracks by an artist from the catalog. | |
| Returns total count and a sample of up to 20 tracks with full details. | |
| """ | |
| logger.info(f"TOOL_CALL: get_tracks_by_artist | artist={artist}") | |
| try: | |
| count_result = run_query_safe( | |
| """ | |
| SELECT COUNT(*) AS total_tracks | |
| FROM Track | |
| JOIN Album ON Track.AlbumId = Album.AlbumId | |
| JOIN Artist ON Album.ArtistId = Artist.ArtistId | |
| WHERE Artist.Name LIKE :pattern; | |
| """, | |
| {"pattern": f"%{artist}%"}, | |
| ) | |
| result = run_query_safe( | |
| """ | |
| SELECT Track.TrackId, | |
| Track.Name AS SongName, | |
| Artist.Name AS ArtistName, | |
| Album.Title AS AlbumTitle, | |
| Genre.Name AS GenreName, | |
| Track.Composer, | |
| Track.Milliseconds, | |
| ROUND(Track.Milliseconds / 60000.0, 1) AS DurationMinutes, | |
| Track.Bytes, | |
| Track.UnitPrice, | |
| MediaType.Name AS MediaType | |
| FROM Track | |
| JOIN Album ON Track.AlbumId = Album.AlbumId | |
| JOIN Artist ON Album.ArtistId = Artist.ArtistId | |
| LEFT JOIN Genre ON Track.GenreId = Genre.GenreId | |
| LEFT JOIN MediaType ON Track.MediaTypeId = MediaType.MediaTypeId | |
| WHERE Artist.Name LIKE :pattern | |
| ORDER BY Album.Title, Track.Name | |
| LIMIT 20; | |
| """, | |
| {"pattern": f"%{artist}%"}, | |
| ) | |
| logger.info(f"TOOL_RESULT: get_tracks_by_artist | count={count_result} | sample_length={len(result)}") | |
| if result == "[]": | |
| return f"No tracks found for artist: {artist}" | |
| return f"Total tracks found: {count_result}. Sample (up to 20): {result}" | |
| except Exception as e: | |
| logger.error(f"Error in get_tracks_by_artist: {e}") | |
| return f"Error looking up tracks for '{artist}'. Please try again." | |
| def get_songs_by_genre(genre: str) -> str: | |
| """ | |
| Fetch a representative sample of songs from a specific genre. | |
| Returns total count and one song per artist (up to 10), deterministically | |
| picking each artist's first track by TrackId. | |
| """ | |
| logger.info(f"TOOL_CALL: get_songs_by_genre | genre={genre}") | |
| try: | |
| count_result = run_query_safe( | |
| """ | |
| SELECT COUNT(*) AS total_tracks | |
| FROM Track | |
| JOIN Genre ON Track.GenreId = Genre.GenreId | |
| WHERE Genre.Name LIKE :pattern; | |
| """, | |
| {"pattern": f"%{genre}%"}, | |
| ) | |
| result = run_query_safe( | |
| """ | |
| WITH ranked AS ( | |
| SELECT Track.TrackId, | |
| Track.Name AS SongName, | |
| Artist.Name AS ArtistName, | |
| Album.Title AS AlbumTitle, | |
| Genre.Name AS GenreName, | |
| Track.Composer, | |
| Track.Milliseconds, | |
| ROUND(Track.Milliseconds / 60000.0, 1) AS DurationMinutes, | |
| Track.Bytes, | |
| Track.UnitPrice, | |
| MediaType.Name AS MediaType, | |
| ROW_NUMBER() OVER (PARTITION BY Artist.ArtistId ORDER BY Track.TrackId) AS rn | |
| FROM Track | |
| JOIN Genre ON Track.GenreId = Genre.GenreId | |
| JOIN Album ON Track.AlbumId = Album.AlbumId | |
| JOIN Artist ON Album.ArtistId = Artist.ArtistId | |
| LEFT JOIN MediaType ON Track.MediaTypeId = MediaType.MediaTypeId | |
| WHERE Genre.Name LIKE :pattern | |
| ) | |
| SELECT TrackId, SongName, ArtistName, AlbumTitle, GenreName, | |
| Composer, Milliseconds, DurationMinutes, Bytes, UnitPrice, MediaType | |
| FROM ranked | |
| WHERE rn = 1 | |
| ORDER BY ArtistName | |
| LIMIT 10; | |
| """, | |
| {"pattern": f"%{genre}%"}, | |
| ) | |
| logger.info(f"TOOL_RESULT: get_songs_by_genre | count={count_result} | sample_length={len(result)}") | |
| if result == "[]": | |
| return f"No songs found for the genre: {genre}" | |
| return ( | |
| f"Total {genre} tracks in catalog: {count_result}. " | |
| f"Representative sample (one per artist, up to 10): {result}" | |
| ) | |
| except Exception as e: | |
| logger.error(f"Error in get_songs_by_genre: {e}") | |
| return f"Error looking up songs for genre '{genre}'. Please try again." | |
| def check_for_songs(song_title: str) -> str: | |
| """ | |
| Check if a song exists in the catalog by its name. Uses fuzzy matching. | |
| Returns up to 10 matches with full track details. | |
| """ | |
| logger.info(f"TOOL_CALL: check_for_songs | song_title={song_title}") | |
| try: | |
| result = run_query_safe( | |
| """ | |
| SELECT Track.TrackId, | |
| Track.Name AS SongName, | |
| Artist.Name AS ArtistName, | |
| Album.Title AS AlbumTitle, | |
| Genre.Name AS GenreName, | |
| Track.Composer, | |
| Track.Milliseconds, | |
| ROUND(Track.Milliseconds / 60000.0, 1) AS DurationMinutes, | |
| Track.Bytes, | |
| Track.UnitPrice, | |
| MediaType.Name AS MediaType | |
| FROM Track | |
| JOIN Album ON Track.AlbumId = Album.AlbumId | |
| JOIN Artist ON Album.ArtistId = Artist.ArtistId | |
| LEFT JOIN Genre ON Track.GenreId = Genre.GenreId | |
| LEFT JOIN MediaType ON Track.MediaTypeId = MediaType.MediaTypeId | |
| WHERE Track.Name LIKE :pattern | |
| ORDER BY Track.Name | |
| LIMIT 10; | |
| """, | |
| {"pattern": f"%{song_title}%"}, | |
| ) | |
| logger.info(f"TOOL_RESULT: check_for_songs | result_length={len(result)}") | |
| if result == "[]": | |
| return f"No songs found matching: {song_title}" | |
| return result | |
| except Exception as e: | |
| logger.error(f"Error in check_for_songs: {e}") | |
| return f"Error looking up song '{song_title}'. Please try again." | |
| def get_track_details(track_id: str) -> str: | |
| """ | |
| Get complete details for a specific track by its TrackId. | |
| Use this when the customer needs detailed info about a specific known track. | |
| """ | |
| logger.info(f"TOOL_CALL: get_track_details | track_id={track_id}") | |
| try: | |
| result = run_query_safe( | |
| """ | |
| SELECT Track.TrackId, | |
| Track.Name AS SongName, | |
| Artist.Name AS ArtistName, | |
| Album.Title AS AlbumTitle, | |
| Genre.Name AS GenreName, | |
| Track.Composer, | |
| Track.Milliseconds, | |
| ROUND(Track.Milliseconds / 60000.0, 1) AS DurationMinutes, | |
| Track.Bytes, | |
| ROUND(Track.Bytes / 1048576.0, 1) AS SizeMB, | |
| Track.UnitPrice, | |
| MediaType.Name AS MediaType | |
| FROM Track | |
| LEFT JOIN Album ON Track.AlbumId = Album.AlbumId | |
| LEFT JOIN Artist ON Album.ArtistId = Artist.ArtistId | |
| LEFT JOIN Genre ON Track.GenreId = Genre.GenreId | |
| LEFT JOIN MediaType ON Track.MediaTypeId = MediaType.MediaTypeId | |
| WHERE Track.TrackId = :track_id; | |
| """, | |
| {"track_id": _safe_int(track_id, "track ID")}, | |
| ) | |
| logger.info(f"TOOL_RESULT: get_track_details | result_length={len(result)}") | |
| if result == "[]": | |
| return f"No track found with TrackId: {track_id}" | |
| return result | |
| except Exception as e: | |
| logger.error(f"Error in get_track_details: {e}") | |
| return f"Error looking up track {track_id}. Please try again." | |
| music_tools = [ | |
| get_albums_by_artist, | |
| get_tracks_by_artist, | |
| get_songs_by_genre, | |
| check_for_songs, | |
| get_track_details, | |
| ] | |