Skip to content

Commit 22c6114

Browse files
authored
feat: change recommendation song algorithm (#42)
1 parent aaded6e commit 22c6114

2 files changed

Lines changed: 32 additions & 14444 deletions

File tree

app/lib/generateSeedPlaylist.ts

Lines changed: 32 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -117,42 +117,40 @@ export async function generateSeedPlaylist(
117117
// p-limit concurrency of 15
118118
const limit = pLimit(15);
119119

120-
let collected = 0;
120+
// 0.7 is a good starting point, but you can tune this
121+
const THRESHOLD = 0.7
122+
const MIN_MATCHES = Math.floor(seedEmbeddings.length / 2) + 1
121123

124+
// we want to avoid low quality recommendations so we check that the ai tracks match at least MIN_MATCHES of the original seeds(songs)
122125
const scoredTracks = await Promise.all(
123-
aiTracks.map((track) =>
124-
limit(async () => {
125-
if (collected >= remainingNeeded + 20) return null;
126-
// Skip if already generated for this user
127-
if (userId && previouslyGeneratedIds.includes(track.id)) {
128-
return null;
129-
}
130-
131-
try {
132-
const processed = await processSong({
133-
id: track.id,
134-
title: track.name,
135-
artist: track.artistName,
136-
album: track.albumName,
137-
});
138-
139-
const emb = processed.embeddingData;
140-
if (!emb) return null;
141-
142-
const similarity =
143-
seedEmbeddings.reduce((sum, seedEmb) => {
144-
return sum + calculateCosineSimilarity(emb, seedEmb);
145-
}, 0) / seedEmbeddings.length;
146-
147-
collected++;
148-
return { uri: track.uri, similarity };
149-
} catch (e) {
150-
console.error(`Skipping ${track.name} due to error`);
151-
return null;
152-
}
153-
}),
154-
),
155-
);
126+
aiTracks.map(track => limit(async () => {
127+
try {
128+
const processed = await processSong({
129+
id: track.id,
130+
title: track.name,
131+
artist: track.artistName,
132+
album: track.albumName,
133+
})
134+
135+
const emb = processed.embeddingData
136+
if (!emb) return null
137+
138+
const scores = seedEmbeddings.map(seedEmb =>
139+
calculateCosineSimilarity(emb, seedEmb)
140+
)
141+
142+
const matchingSeeds = scores.filter(s => s >= THRESHOLD).length
143+
if (matchingSeeds < MIN_MATCHES) return null // doesn't qualify
144+
145+
const maxScore = Math.max(...scores)
146+
return { uri: track.uri, similarity: maxScore }
147+
148+
} catch (e) {
149+
console.error(`Skipping ${track.name} due to error`)
150+
return null
151+
}
152+
}))
153+
)
156154

157155
// Sort by similarity descending, filtering out nulls
158156
fallbackTracksUris = (

0 commit comments

Comments
 (0)