jaothan's picture
Upload 356 files
c33a7ce verified
import os
from typing import Dict, List
import requests
import time
import json
import streamlit as st
from langchain_core.tools import BaseTool
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.messages import AIMessage, HumanMessage
from dotenv import load_dotenv
from typing import Any, List, Dict, Union
# Load env file
load_dotenv()
# Model service
model_service = os.getenv("MODEL_ENDPOINT", "http://localhost:8001")
model_service = f"{model_service}/v1"
# Spotify API Configuration
SPOTIFY_BASE_URL = "https://api.spotify.com/v1"
class SpotifyAPI:
def __init__(self):
self.client_id = os.getenv("SPOTIFY_CLIENT_ID")
self.client_secret = os.getenv("SPOTIFY_CLIENT_SECRET")
# If not in .env, access it through UI
if not self.client_id or not self.client_secret:
if hasattr(st.session_state, 'spotify_client_id') and hasattr(st.session_state, 'spotify_client_secret'):
self.client_id = st.session_state.spotify_client_id
self.client_secret = st.session_state.spotify_client_secret
if not self.client_id or not self.client_secret:
raise ValueError("Spotify credentials not found. Please provide them in the sidebar.")
self.access_token = self._get_access_token()
def _get_access_token(self):
"""Get Spotify access token using client credentials flow"""
auth_url = "https://accounts.spotify.com/api/token"
auth_response = requests.post(
auth_url,
data={
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret,
}
)
if auth_response.status_code != 200:
raise Exception("Failed to get access token")
return auth_response.json()["access_token"]
def search_playlists(self, query: str, limit: int = 5) -> Dict:
"""Search for playlists using Spotify API"""
enhanced_query = f"{query} playlist top popular"
headers = {
"Authorization": f"Bearer {self.access_token}",
"Content-Type": "application/json"
}
params = {
"q": enhanced_query,
"type": "playlist",
"limit": limit,
"market": "US"
}
response = requests.get(
f"{SPOTIFY_BASE_URL}/search",
headers=headers,
params=params
)
if response.status_code != 200:
raise Exception(f"Search failed: {response.json().get('error', {}).get('message')}")
return response.json()
def get_trending_tracks(self, location: str = None, limit: int = 10) -> Dict:
"""Get trending tracks for a specific location"""
headers = {
"Authorization": f"Bearer {self.access_token}",
"Content-Type": "application/json"
}
#include location in query
query = f"top charts popular {location}" if location else "top charts"
params = {
"q": query,
"type": "track",
"limit": limit,
"market": "US",
"offset": 0,
"include_external": "audio"
}
response = requests.get(
f"{SPOTIFY_BASE_URL}/search",
headers=headers,
params=params
)
if response.status_code != 200:
raise Exception(f"Search failed: {response.json().get('error', {}).get('message')}")
return response.json()
class SpotifySearchTool(BaseTool):
name: str = "spotify_search"
description: str = """
Search for playlists on Spotify.
Input should be a search query string.
The tool will return relevant playlists with their details.
"""
spotify: Any = None
def __init__(self) -> None:
super().__init__()
self.spotify = SpotifyAPI()
def _run(self, query: str) -> List[Dict]:
try:
results = self.spotify.search_playlists(query)
playlists = []
for item in results['playlists']['items']:
playlist = {
'name': item['name'],
'description': item['description'],
'tracks_total': item['tracks']['total'],
'url': item['external_urls']['spotify'],
'owner': item['owner']['display_name'],
'followers': item['followers']['total'] if 'followers' in item else 0
}
playlists.append(playlist)
return playlists
except Exception as e:
return f"Error searching Spotify: {str(e)}"
class SpotifyTrendingTool(BaseTool):
name: str = "spotify_trending"
description: str = """
Get trending tracks for a specific location on Spotify.
Input should be a location string (e.g., 'Berkeley', 'Bay Area').
Returns top trending tracks in that area.
"""
spotify: Any = None
def __init__(self) -> None:
super().__init__()
self.spotify = SpotifyAPI()
def _run(self, location: str) -> List[Dict]:
try:
results = self.spotify.get_trending_tracks(location)
tracks = []
for item in results['tracks']['items']:
track = {
'name': item['name'],
'artist': ', '.join([artist['name'] for artist in item['artists']]),
'album': item['album']['name'],
'url': item['external_urls']['spotify'],
'popularity': item['popularity']
}
tracks.append(track)
return tracks
except Exception as e:
return f"Error getting trending tracks: {str(e)}"
def format_spotify_response(tool_responses: Dict) -> str:
"""Format the Spotify API responses into a readable message"""
response = ""
# Format trending tracks
trending_tracks = tool_responses.get("trending", [])
if isinstance(trending_tracks, list) and trending_tracks:
response += "📊 Trending Tracks:\n"
for i, track in enumerate(trending_tracks[:5], 1):
response += f"{i}. {track['name']} by {track['artist']}\n"
response += f" - Album: {track['album']}\n"
response += f" - Listen: {track['url']}\n\n"
else:
response += "📊 No trending tracks found for this location.\n\n"
# Format playlists
playlists = tool_responses.get("playlists", [])
if isinstance(playlists, list) and playlists:
response += "🎵 Related Playlists:\n"
for i, playlist in enumerate(playlists[:3], 1):
response += f"{i}. {playlist['name']}\n"
response += f" - Tracks: {playlist['tracks_total']}\n"
response += f" - Description: {playlist['description']}\n"
response += f" - Listen: {playlist['url']}\n\n"
else:
response += "No related playlists found.\n"
return response
# Model service check
@st.cache_resource(show_spinner=False)
def checking_model_service():
start = time.time()
print("Checking Model Service Availability...")
ready = False
while not ready:
try:
request_cpp = requests.get(f'{model_service}/models')
request_ollama = requests.get(f'{model_service[:-2]}api/tags')
if request_cpp.status_code == 200:
server = "Llamacpp_Python"
ready = True
elif request_ollama.status_code == 200:
server = "Ollama"
ready = True
except:
pass
time.sleep(1)
print(f"{server} Model Service Available")
print(f"Time taken: {time.time()-start} seconds")
return server
def get_models():
try:
response = requests.get(f"{model_service[:-2]}api/tags")
return [i["name"].split(":")[0] for i in json.loads(response.content)["models"]]
except:
return None
# ReAct prompt template
REACT_PROMPT = """You are a helpful assistant that can search for music on Spotify.
You have access to the following tools:
{tools}
Use the following format in your internal processing:
Thought: First interpret if the user's input is a casual greeting or an actual search query.
If it seems like a greeting, respond conversationally and suggest some current trending tracks.
If it's a search query, use it directly.
Action: tool_name (either spotify_search or spotify_trending)
Action Input: input to the tool
Observation: tool's response
Final Answer: If the input was conversational, start with a greeting before showing the music results.
Then provide results in this format:
📊 Trending Tracks:
[formatted tracks...]
🎵 Related Playlists:
[formatted playlists...]
"""
# Create ReAct Agent function
def create_react_agent(model_name: str):
llm = ChatOpenAI(
base_url=model_service,
api_key="sk-no-key-required",
model=model_name,
streaming=True
)
# Create both tools
playlist_tool = SpotifySearchTool()
trending_tool = SpotifyTrendingTool()
prompt = ChatPromptTemplate.from_messages([
("system", REACT_PROMPT),
("human", "{input}")
])
chain = prompt | llm
return chain, [playlist_tool, trending_tool]
#Streamlit
st.title("🎵 Spotify Playlist Explorer")
if "spotify_credentials_set" not in st.session_state:
st.session_state.spotify_credentials_set = False
# Spotify Credentials Management in Sidebar
with st.sidebar:
st.markdown("### Spotify Credentials")
# Check if credentials exist in environment variables
env_credentials_exist = bool(os.getenv("SPOTIFY_CLIENT_ID")) and bool(os.getenv("SPOTIFY_CLIENT_SECRET"))
if not env_credentials_exist:
st.warning("Spotify credentials not found in environment variables.")
# Initialize session state for credentials
if "spotify_client_id" not in st.session_state:
st.session_state.spotify_client_id = ""
if "spotify_client_secret" not in st.session_state:
st.session_state.spotify_client_secret = ""
# Input fields for credentials
client_id = st.text_input(
"Enter Spotify Client ID",
value=st.session_state.spotify_client_id,
type="password"
)
client_secret = st.text_input(
"Enter Spotify Client Secret",
value=st.session_state.spotify_client_secret,
type="password"
)
if st.button("Save Credentials"):
st.session_state.spotify_client_id = client_id
st.session_state.spotify_client_secret = client_secret
st.session_state.spotify_credentials_set = True
st.success("Credentials saved!")
st.rerun()
else:
st.success("Using credentials from environment variables")
st.session_state.spotify_credentials_set = True
# Check if credentials are available before proceeding
credentials_available = env_credentials_exist or st.session_state.spotify_credentials_set
if not credentials_available:
st.error("Please provide Spotify credentials in the sidebar to continue.")
else:
with st.spinner("Checking Model Service Availability..."):
server = checking_model_service()
model_name = os.getenv("MODEL_NAME", "")
if server == "Ollama":
with st.sidebar:
model_name = st.radio(
label="Select Model",
options=get_models()
)
try:
agent, tools = create_react_agent(model_name)
playlist_tool, trending_tool = tools
if "messages" not in st.session_state:
st.session_state.messages = []
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
if prompt := st.chat_input("What kind of playlists are you looking for?"):
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)
with st.chat_message("assistant"):
try:
tool_responses = {
"playlists": playlist_tool._run(prompt),
"trending": trending_tool._run(prompt)
}
agent_response = agent.invoke({
"input": prompt,
"tools": [tool.description for tool in tools],
"query": prompt,
"observation": tool_responses,
"answer": "Based on the search results, here's what I found:"
})
with st.expander("See thinking process"):
st.markdown(agent_response.content)
formatted_response = format_spotify_response(tool_responses)
st.markdown(formatted_response)
st.session_state.messages.append({
"role": "assistant",
"content": formatted_response
})
except Exception as e:
error_message = f"Error processing request: {str(e)}"
st.error(error_message)
except Exception as e:
st.error(f"Error initializing Spotify API: {str(e)}")