diff --git a/internal/cmd/hey.go b/internal/cmd/hey.go index 84ca89b..ab5c2b7 100644 --- a/internal/cmd/hey.go +++ b/internal/cmd/hey.go @@ -12,11 +12,45 @@ import ( "strings" "github.com/JunNishimura/Chatify/internal/functions" + "github.com/JunNishimura/Chatify/internal/object" "github.com/sashabaranov/go-openai" "github.com/spf13/cobra" "github.com/zmb3/spotify/v2" ) +const ( + initPrompt = `Below is a conversation with an AI chatbot. + +The bot analyzes the music the interlocutor is currently seeking through the conversation and suggests music recommendations based on the results of the analysis. + +The bot analyzes the music orientation of the music the interlocutor is currently seeking by breaking it down into the following elements. +1. Genre +Music genres. For example, j-pop, techno, acoustic, folk +2. Danceability +Danceability describes how suitable a track is for dancing based on a combination of musical elements including tempo, rhythm stability, beat strength, and overall regularity. A value of 0.0 is least danceable and 1.0 is most danceable. +3. Valence +A measure from 0.0 to 1.0 describing the musical positiveness conveyed by a track. Tracks with high valence sound more positive (e.g. happy, cheerful, euphoric), while tracks with low valence sound more negative (e.g. sad, depressed, angry). +4. Popularity +A measure from 0 to 100 describing how much the track is popular. Tracks with high popularity is more popular. + +Once all factors have been determined, the bot will suggest music recommendations to the interlocutor based on the information obtained. + +There are some points to note when asking questions. +The possible values for the analysis elements Danceability, Valence, and Popularity are numerical values such as 0.6, +but do not ask questions that force the interlocutor to directly answer with a numerical value, +such as "How much is your danceability from 0 to 1? +Instead, ask a question to analyze how much daceability music the interlocutor is looking for, +such as "Do you want to be energetic?”. +Then, guess the specific numerical value of the element from the interlocutor's answer. +For example, "I'm depressed and I want to get better" to which the response might be something like, +"I guess the daceability is 0.8”. +Also, limit the number of questions the bot asks the interlocutor in one conversation to one. + +Output at the end of a sentence when recommending tracks. + +Please begin with the first question.` +) + func NewHeyCommand(ctx context.Context, client *spotify.Client, openaiApiKey string) *cobra.Command { return &cobra.Command{ Use: "hey", @@ -28,19 +62,8 @@ func NewHeyCommand(ctx context.Context, client *spotify.Client, openaiApiKey str messages := []openai.ChatCompletionMessage{ { - Role: openai.ChatMessageRoleSystem, - Content: `Below is a conversation with an AI chatbot. - -The bot suggests recommended tracks to the interlocutor. -Through a question-and-answer session, the bot will suggest recommended tracks based on information obtained. -However, please observe the following rules. - -[Rule] -1. Ask questions of the genre that the interlocutor wants to listen to. -2. Ask only one question at a time. -3. Output at the end of a sentence when recommending tracks - -Then, please ask a question.`, + Role: openai.ChatMessageRoleSystem, + Content: initPrompt, }, } @@ -49,6 +72,10 @@ Then, please ask a question.`, return fmt.Errorf("fail to get available genres: %v", err) } + musicOrientationInfo := object.NewMusicOrientationInfo() + + functionTemplate := functions.GetTemplate(availableGenres) + scanner := bufio.NewScanner(os.Stdin) for { resp, err := openAIClient.CreateChatCompletion( @@ -56,7 +83,7 @@ Then, please ask a question.`, openai.ChatCompletionRequest{ Model: openai.GPT3Dot5Turbo, Messages: messages, - Functions: functions.GetTemplate(availableGenres), + Functions: functionTemplate, FunctionCall: "auto", }, ) @@ -67,13 +94,19 @@ Then, please ask a question.`, // show AI response functionCall := resp.Choices[0].Message.FunctionCall if functionCall != nil { - if functionCall.Name == "recommend" { - args := make(map[string]string) + switch functionCall.Name { + case functions.RecommendFunction: + args := make(map[string]any) if err := json.Unmarshal([]byte(functionCall.Arguments), &args); err != nil { return fmt.Errorf("fail to unmarshal json: %v", err) } - recommendations, err := functions.Recommend(ctx, client, args["genres"]) + genres := args["genres"].(string) + danceability := args["danceability"].(float64) + valence := args["valence"].(float64) + popularity := args["popularity"].(int) + + recommendations, err := functions.Recommend(ctx, client, genres, danceability, valence, popularity) if err != nil { return err } @@ -83,6 +116,67 @@ Then, please ask a question.`, Role: openai.ChatMessageRoleFunction, Content: recommendations, }) + case functions.SetGenresFunction: + args := make(map[string]string) + if err := json.Unmarshal([]byte(functionCall.Arguments), &args); err != nil { + return fmt.Errorf("fail to unmarshal json: %v", err) + } + genres := args["value"] + functions.SetGenres(musicOrientationInfo, genres) + + messages = append(messages, openai.ChatCompletionMessage{ + Name: functionCall.Name, + Role: openai.ChatMessageRoleFunction, + Content: genres, + }) + case functions.SetDanceabitliyFunction: + args := make(map[string]any) + if err := json.Unmarshal([]byte(functionCall.Arguments), &args); err != nil { + return fmt.Errorf("fail to unmarshal json: %v", err) + } + + danceability := args["value"].(float64) + description := args["subvalue"].(string) + + functions.SetDanceability(musicOrientationInfo, danceability) + + messages = append(messages, openai.ChatCompletionMessage{ + Name: functionCall.Name, + Role: openai.ChatMessageRoleFunction, + Content: description, + }) + case functions.SetValenceFunction: + args := make(map[string]any) + if err := json.Unmarshal([]byte(functionCall.Arguments), &args); err != nil { + return fmt.Errorf("fail to unmarshal json: %v", err) + } + + valence := args["value"].(float64) + description := args["subvalue"].(string) + + functions.SetValence(musicOrientationInfo, valence) + + messages = append(messages, openai.ChatCompletionMessage{ + Name: functionCall.Name, + Role: openai.ChatMessageRoleFunction, + Content: description, + }) + case functions.SetPopularityFunction: + args := make(map[string]any) + if err := json.Unmarshal([]byte(functionCall.Arguments), &args); err != nil { + return fmt.Errorf("fail to unmarshal json: %v", err) + } + + popularity := args["value"].(int) + description := args["subvalue"].(string) + + functions.SetPopularity(musicOrientationInfo, popularity) + + messages = append(messages, openai.ChatCompletionMessage{ + Name: functionCall.Name, + Role: openai.ChatMessageRoleFunction, + Content: description, + }) } } else { respMessage := resp.Choices[0].Message.Content @@ -109,6 +203,15 @@ Then, please ask a question.`, Content: scanner.Text(), }) } + + // when music orientation info is all set, add info to messages manually + if musicOrientationInfo.IsSet() { + functionTemplate = append(functionTemplate, functions.RecommendFunctionDefinition) + messages = append(messages, openai.ChatCompletionMessage{ + Role: openai.ChatMessageRoleUser, + Content: musicOrientationInfo.String(), + }) + } } return nil