From 33e5ff22ed50604ffdf269b30592ccf85d5d11d1 Mon Sep 17 00:00:00 2001 From: supermandee <71103135+supermandee@users.noreply.github.com> Date: Tue, 17 Dec 2024 23:14:49 -0600 Subject: [PATCH 1/5] abay update --- llm_client.py | 45 +++++++++++++++++++++++++++++++++------------ static/js/chat.js | 1 + 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/llm_client.py b/llm_client.py index 9dac82d..a953bb4 100644 --- a/llm_client.py +++ b/llm_client.py @@ -45,20 +45,40 @@ def process_query(self, query: str, spotify_data: Dict, access_token: str, sessi while True: assistant_message = self._initial_openai_call(current_messages) - + tool_calls = [] # Add any assistant message content to the conversation - if assistant_message.content: - current_messages.append({"role": "assistant", "content": assistant_message.content}) - for chunk in assistant_message.content: - response += chunk - yield chunk - - # If no more tool calls, we're done - if not assistant_message.tool_calls: + #if assistant_message.content: FUCKING GARBAGE + #current_messages.append({"role": "assistant", "content": assistant_message.content}) + for chunk in assistant_message: + delta = chunk.choices[0].delta + if delta and delta.content: + print(delta, end="") + #response += deltac + yield delta.content.encode('utf-8') + elif delta and delta.tool_calls: + tcchunklist = delta.tool_calls + for tcchunk in tcchunklist: + if len(tool_calls) <= tcchunk.index: + tool_calls.append({"id": "", "type": "function", "function": { "name": "", "arguments": "" } }) + tc = tool_calls[tcchunk.index] + + if tcchunk.id: + tc["id"] += tcchunk.id + if tcchunk.function.name: + tc["function"]["name"] += tcchunk.function.name + if tcchunk.function.arguments: + tc["function"]["arguments"] += tcchunk.function.arguments + print(tool_calls) + + #current_messages = self._handle_tool_calls(assistant_message.tool_calls, access_token, current_messages) + if not tool_calls: break - + # If no more tool calls, we're done + #if not assistant_message.tool_calls: + # break + # Handle tool calls and continue the conversation - current_messages = self._handle_tool_calls(assistant_message.tool_calls, access_token, current_messages) + # Update chat history messages.append({"role": "assistant", "content": response}) @@ -137,8 +157,9 @@ def _initial_openai_call(self, messages: List[Dict[str, str]]) -> Dict: model=self.model, messages=messages, tools=SPOTIFY_TOOLS, + stream=True ) - return response.choices[0].message + return response @task(name="handle_tool_calls") diff --git a/static/js/chat.js b/static/js/chat.js index 41dcb22..db004e8 100644 --- a/static/js/chat.js +++ b/static/js/chat.js @@ -72,6 +72,7 @@ async function sendMessage() { while (true) { const {done, value} = await reader.read(); + //console.log("value:", value); if (done) { console.log("Stream done"); break; From e92a00d13b8b0a1a35584dc5f633cadb62f0ee0c Mon Sep 17 00:00:00 2001 From: supermandee <71103135+supermandee@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:15:57 -0600 Subject: [PATCH 2/5] abay update --- llm_client.py | 45 ++++++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/llm_client.py b/llm_client.py index a953bb4..69f961f 100644 --- a/llm_client.py +++ b/llm_client.py @@ -31,35 +31,35 @@ def process_query(self, query: str, spotify_data: Dict, access_token: str, sessi if session_id not in self.chat_history: self.chat_history[session_id] = [] - # DEBUG: logger.warning(f"Chat history not found for session {session_id[:8]}. Creating new chat history.") - # Construct messages messages = self._build_messages(session_id, query) - #DEBUG: - logger.info(f"Lengh of messages: {len(messages)}") + logger.info(f"Length of messages: {len(messages)}") - # Initial OpenAI API call current_messages = messages.copy() response = "" while True: assistant_message = self._initial_openai_call(current_messages) tool_calls = [] - # Add any assistant message content to the conversation - #if assistant_message.content: FUCKING GARBAGE - #current_messages.append({"role": "assistant", "content": assistant_message.content}) + for chunk in assistant_message: delta = chunk.choices[0].delta if delta and delta.content: - print(delta, end="") - #response += deltac + response += delta.content yield delta.content.encode('utf-8') elif delta and delta.tool_calls: tcchunklist = delta.tool_calls for tcchunk in tcchunklist: if len(tool_calls) <= tcchunk.index: - tool_calls.append({"id": "", "type": "function", "function": { "name": "", "arguments": "" } }) + tool_calls.append({ + "id": "", + "type": "function", + "function": { + "name": "", + "arguments": "" + } + }) tc = tool_calls[tcchunk.index] if tcchunk.id: @@ -68,22 +68,25 @@ def process_query(self, query: str, spotify_data: Dict, access_token: str, sessi tc["function"]["name"] += tcchunk.function.name if tcchunk.function.arguments: tc["function"]["arguments"] += tcchunk.function.arguments - print(tool_calls) - - #current_messages = self._handle_tool_calls(assistant_message.tool_calls, access_token, current_messages) + if not tool_calls: break - # If no more tool calls, we're done - #if not assistant_message.tool_calls: - # break - - # Handle tool calls and continue the conversation + + # Convert tool_calls to format needed by _handle_tool_calls + formatted_tool_calls = [ + type('ToolCall', (), { + 'id': tc['id'], + 'function': type('Function', (), { + 'name': tc['function']['name'], + 'arguments': tc['function']['arguments'] + }) + }) for tc in tool_calls + ] + current_messages = self._handle_tool_calls(formatted_tool_calls, access_token, current_messages) - # Update chat history messages.append({"role": "assistant", "content": response}) self.chat_history[session_id] = messages - @task(name="build_messages") def _build_messages(self, session_id: str, query: str) -> List[Dict[str, str]]: From 83fa8a5bce0c26952ab123cc23d54839ef838820 Mon Sep 17 00:00:00 2001 From: supermandee <71103135+supermandee@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:21:44 -0600 Subject: [PATCH 3/5] fix: streaming issue fixed for both tool calls and content --- llm_client.py | 45 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/llm_client.py b/llm_client.py index 69f961f..8cde3eb 100644 --- a/llm_client.py +++ b/llm_client.py @@ -26,31 +26,62 @@ def __init__(self, model: str = "gpt-4o"): logger.info(f"Initialized LLMClient with model: {model}") @workflow(name="process_query_workflow") - def process_query(self, query: str, spotify_data: Dict, access_token: str, session_id: str) -> Iterator[str]: + def process_query( + self, + query: str, # The user's input query + spotify_data: Dict, # Dictionary containing Spotify-related data + access_token: str, # Spotify API access token + session_id: str # Unique identifier for the chat session + ) -> Iterator[str]: # Returns a string iterator for streaming responses + """ + Process a user query, handling both direct responses and tool calls with Spotify API. + Streams responses back to the user and maintains chat history. + + Args: + query: User's input text query + spotify_data: Dictionary containing relevant Spotify data + access_token: Valid Spotify API access token + session_id: Unique identifier for the chat session + + Yields: + Encoded string chunks of the assistant's response + + Note: + - Maintains chat history per session + - Handles streaming responses from OpenAI + - Processes tool calls for Spotify API interactions + """ logger.info(f"Processing query for session {session_id[:8]}...") + # Initialize chat history for new sessions if session_id not in self.chat_history: self.chat_history[session_id] = [] logger.warning(f"Chat history not found for session {session_id[:8]}. Creating new chat history.") + # Build message context including history messages = self._build_messages(session_id, query) logger.info(f"Length of messages: {len(messages)}") - current_messages = messages.copy() - response = "" + current_messages = messages.copy() # Working copy of messages + response = "" # Accumulator for complete response while True: + # Get streaming response from OpenAI assistant_message = self._initial_openai_call(current_messages) - tool_calls = [] + tool_calls = [] # Accumulator for tool calls in this response + # Process each chunk of the streaming response for chunk in assistant_message: delta = chunk.choices[0].delta if delta and delta.content: + # Handle text content response += delta.content yield delta.content.encode('utf-8') elif delta and delta.tool_calls: + # Handle tool calls (e.g., Spotify API functions) tcchunklist = delta.tool_calls for tcchunk in tcchunklist: + # Initialize new tool call structure if needed if len(tool_calls) <= tcchunk.index: tool_calls.append({ "id": "", @@ -62,6 +93,7 @@ def process_query(self, query: str, spotify_data: Dict, access_token: str, sessi }) tc = tool_calls[tcchunk.index] + # Accumulate tool call information from chunks if tcchunk.id: tc["id"] += tcchunk.id if tcchunk.function.name: @@ -69,10 +101,11 @@ def process_query(self, query: str, spotify_data: Dict, access_token: str, sessi if tcchunk.function.arguments: tc["function"]["arguments"] += tcchunk.function.arguments + # Exit loop if no tool calls were made if not tool_calls: break - # Convert tool_calls to format needed by _handle_tool_calls + # Convert accumulated tool calls to format needed by handler formatted_tool_calls = [ type('ToolCall', (), { 'id': tc['id'], @@ -83,8 +116,10 @@ def process_query(self, query: str, spotify_data: Dict, access_token: str, sessi }) for tc in tool_calls ] + # Process tool calls and update conversation context current_messages = self._handle_tool_calls(formatted_tool_calls, access_token, current_messages) + # Update chat history with final response messages.append({"role": "assistant", "content": response}) self.chat_history[session_id] = messages From 1417d47344a5a31a8b2240f8e440351943b45069 Mon Sep 17 00:00:00 2001 From: supermandee <71103135+supermandee@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:34:17 -0600 Subject: [PATCH 4/5] refactor: system prompt separate file --- llm_client.py | 49 ++----------------------------------------------- 1 file changed, 2 insertions(+), 47 deletions(-) diff --git a/llm_client.py b/llm_client.py index 8cde3eb..49c1cc3 100644 --- a/llm_client.py +++ b/llm_client.py @@ -7,6 +7,7 @@ from traceloop.sdk.decorators import workflow, task from ai_tools import SPOTIFY_TOOLS, SpotifyFunctionHandler from logger_config import setup_logger +from system_prompt import SYSTEM_PROMPT logger = setup_logger(__name__) @@ -131,53 +132,7 @@ def _build_messages(self, session_id: str, query: str) -> List[Dict[str, str]]: if not self.chat_history[session_id]: messages.append({ "role": "system", - "content": """ -You are MySpotiPal, an AI-powered Spotify assistant with real-time access to users' Spotify data. Your role is to provide expert music recommendations, insightful data analysis, and seamless playlist management while maintaining a friendly, professional, and engaging communication style. - -# Core Functions -1. Song Recommendations: - - Respond to requests for song or artist recommendations without automatically creating a playlist. - - Curate suggestions based on user input, listening history, and musical patterns. - -2. Playlist Creation: - Follow these steps: - a. Curate song recommendations based on user input. - b. Use 'search_item' to find the exact track IDs for each recommended song. If a song is unavailable, replace it with an alternative and explain your reasoning - c. Create a new playlist using 'create_playlist' - d. Add all identified tracks to the playlist using 'add_songs_to_playlist' - e. Share the playlist URL along with a summary of the theme and reasoning behind your recommendations - - IMPORTANT: DO NOT end your response until you have completed ALL these steps. Keep user posted of progress - -3. User Insights & Analysis: - - Answer questions about user's library for top artists, tracks, or saved artists, tracks, podcasts, audiobooks, and so on. - - Provide meaningful patterns and trends in the user’s library and listening behavior. - -4. Comprehensive Search Capabilities: - - Search for tracks, albums, artists, playlists, audiobooks, and podcasts while providing relevant details (e.g., follower counts, genres, and release dates). - -# Communication Style -- Friendly, conversational, and engaging. -- Use strategic, music-related emojis (🎵, 🎧, 🎸) to enhance the user experience. -- Provide data-informed insights with concise but detailed reasoning. -- Balance familiar recommendations with opportunities for musical discovery. - -# Response Guidelines -1. Recommendations: - - Explain your song suggestions clearly, highlighting why they align with the user’s preferences. - - Before making recommendations, make sure they are available on Spotify -2. Search Results: - - Prioritize Spotify-provided information and include key metrics, such as genre, release year, and artist popularity. - - Supplement with external knowledge if Spotify data is insufficient. -3. Incomplete Data: - - Acknowledge any limitations (e.g., unavailable tracks) and offer alternative solutions. -4. Playlist Creation: - - Begin playlist generation if user asks to create/generate a playlist - -# Playlist Generation Reminder -- Never assume a user wants a playlist when asking for recommendations -- If a user asks to create a playlist, proceed with all the playlist generation steps -- If the user only wants song recommendations, stop after providing suggestions -""" + "content": SYSTEM_PROMPT }) # Add existing chat history messages.extend(self.chat_history[session_id]) From 0fe0fce8161dda3c7113096beefabf23429234aa Mon Sep 17 00:00:00 2001 From: supermandee <71103135+supermandee@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:35:14 -0600 Subject: [PATCH 5/5] refactor: system prompt separate file --- system_prompt.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 system_prompt.py diff --git a/system_prompt.py b/system_prompt.py new file mode 100644 index 0000000..9f3510f --- /dev/null +++ b/system_prompt.py @@ -0,0 +1,48 @@ +# system_prompt.py +SYSTEM_PROMPT = """ +You are MySpotiPal, an AI-powered Spotify assistant with real-time access to users' Spotify data. Your role is to provide expert music recommendations, insightful data analysis, and seamless playlist management while maintaining a friendly, professional, and engaging communication style. + +# Core Functions +1. Song Recommendations: + - Respond to requests for song or artist recommendations without automatically creating a playlist. + - Curate suggestions based on user input, listening history, and musical patterns. + +2. Playlist Creation: + Follow these steps: + a. Curate song recommendations based on user input. + b. Use 'search_item' to find the exact track IDs for each recommended song. If a song is unavailable, replace it with an alternative and explain your reasoning + c. Create a new playlist using 'create_playlist' + d. Add all identified tracks to the playlist using 'add_songs_to_playlist' + e. Share the playlist URL along with a summary of the theme and reasoning behind your recommendations + - IMPORTANT: DO NOT end your response until you have completed ALL these steps. Keep user posted of progress + +3. User Insights & Analysis: + - Answer questions about user's library for top artists, tracks, or saved artists, tracks, podcasts, audiobooks, and so on. + - Provide meaningful patterns and trends in the user's library and listening behavior. + +4. Comprehensive Search Capabilities: + - Search for tracks, albums, artists, playlists, audiobooks, and podcasts while providing relevant details (e.g., follower counts, genres, and release dates). + +# Communication Style +- Friendly, conversational, and engaging. +- Use strategic, music-related emojis (🎵, 🎧, 🎸) to enhance the user experience. +- Provide data-informed insights with concise but detailed reasoning. +- Balance familiar recommendations with opportunities for musical discovery. + +# Response Guidelines +1. Recommendations: + - Explain your song suggestions clearly, highlighting why they align with the user's preferences. + - Before making recommendations, make sure they are available on Spotify +2. Search Results: + - Prioritize Spotify-provided information and include key metrics, such as genre, release year, and artist popularity. + - Supplement with external knowledge if Spotify data is insufficient. +3. Incomplete Data: + - Acknowledge any limitations (e.g., unavailable tracks) and offer alternative solutions. +4. Playlist Creation: + - Begin playlist generation if user asks to create/generate a playlist + +# Playlist Generation Reminder +- Never assume a user wants a playlist when asking for recommendations +- If a user asks to create a playlist, proceed with all the playlist generation steps +- If the user only wants song recommendations, stop after providing suggestions +""" \ No newline at end of file