Skip to content

Commit c8c0ccf

Browse files
feat(agent): add performance fixes and artist variety controls
Add max_tokens limit (1024) to prevent long response generation eagerly spread same-artist tracks apart using greedy shuffle algorithm Update system prompt with artist variety rules: - Default to 1 track per artist for maximum variety - Only add 2nd track when unique artists exhausted - Priority: N tracks from N artists > N tracks from fewer artists Python script improvements: - Add _shuffle_spread_artists() for playlist ordering - Add AGENT_MIN_PLAYLIST_TRACKS env var (default: 12) - Add num_predict: 2048 to prevent response truncation - Update system prompt with parallel tool calling emphasis Documentation: - Update docs/agent.md with new features - Update backlog task-277 with implementation notes References: scripts/agent.py as working reference implementation
1 parent 9c01254 commit c8c0ccf

File tree

12 files changed

+652
-732
lines changed

12 files changed

+652
-732
lines changed

app/frontend/js/components/genius-browser.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import { agent } from '../api/agent.js';
1010

11-
const DEFAULT_MODEL = 'llama3.2:1b';
11+
const DEFAULT_MODEL = 'qwen3.5:9b';
1212

1313
export function createGeniusBrowser(Alpine) {
1414
Alpine.data('geniusBrowser', () => ({
@@ -198,10 +198,10 @@ export function createGeniusBrowser(Alpine) {
198198

199199
navigateToPlaylist(playlistId) {
200200
const sidebar = document.querySelector('[x-data="sidebar"]');
201-
if (sidebar && sidebar.__x) {
202-
sidebar.__x.$data.loadPlaylist(`playlist-${playlistId}`);
201+
const data = sidebar?._x_dataStack?.[0];
202+
if (data?.loadPlaylist) {
203+
data.loadPlaylist(`playlist-${playlistId}`);
203204
} else {
204-
// Fallback: dispatch event for sidebar to pick up
205205
window.dispatchEvent(
206206
new CustomEvent('mt:navigate-playlist', { detail: { playlistId } }),
207207
);

app/frontend/views/genius.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ <h3 class="text-sm font-semibold mb-1">Ollama Required</h3>
115115
<div>
116116
<h3 class="text-sm font-semibold mb-1">Download AI Model</h3>
117117
<p class="text-xs text-foreground/50 leading-relaxed">
118-
Ollama is running. Download the llama3.2:1b model (~1.3 GB) to enable playlist
118+
Ollama is running. Download the qwen3.5:9b model (~6 GB) to enable playlist
119119
generation.
120120
</p>
121121
</div>
@@ -134,7 +134,7 @@ <h3 class="text-sm font-semibold mb-1">Download AI Model</h3>
134134
>
135135
</path>
136136
</svg>
137-
Download Model (1.3 GB)
137+
Download Model (~6 GB)
138138
</button>
139139
</template>
140140

backlog/tasks/task-277 - Genius-playlist-creator.md

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ title: Genius playlist creator
44
status: In Progress
55
assignee: []
66
created_date: '2026-02-18 05:58'
7-
updated_date: '2026-03-31 21:27'
7+
updated_date: '2026-04-02 21:01'
88
labels:
99
- feature
1010
- playlists
@@ -226,6 +226,87 @@ Cfg-gate fix: After merging main (which had reverted lastfm discovery methods),
226226
## Remaining
227227

228228
- Phase 7: Frontend — Genius sidebar category, prompt UI, onboarding wizard UI
229+
230+
## 2026-04-02: Agent Performance Fixes
231+
232+
### Issues Identified from Logs
233+
1. **Parallel tool execution not working**: `with_tool_concurrency(8)` was configured but model wasn't calling multiple tools per turn
234+
2. **Token generation too long**: Final LLM turn took 63 seconds generating ~57 IDs with multiple recounts
235+
236+
### Changes Made
237+
238+
**mod.rs - Agent builder:**
239+
- Added `.max_tokens(1024)` to cap response length (prevents endless recounting)
240+
241+
**prompt.rs - System prompt:**
242+
- Enhanced RULES section with explicit parallel tool calling instructions:
243+
- "Call MULTIPLE tools PER TURN in PARALLEL"
244+
- "When planning your strategy, call ALL independent tools at once"
245+
246+
### Why These Fixes Work
247+
248+
1. **max_tokens(1024)**: Limits the LLM to ~1024 tokens for the final response. The playlist format (name + 25 track IDs) needs only ~200-500 tokens. This prevents the model from generating excessive intermediate reasoning (listing 57 IDs, recounting, selecting, etc.) that was causing the 63-second response time.
249+
250+
2. **Explicit parallel instructions**: The previous prompt mentioned "Call multiple tools per turn" but wasn't emphatic enough. The new language uses ALL CAPS for key concepts and provides concrete examples ("get_similar_artists + search_library + get_track_tags together") to guide the model toward parallel tool calling.
251+
252+
### Test Results
253+
- All 757 tests pass with `cargo nextest run --workspace --features agent`
254+
- Both `cargo check --features agent` and `cargo check` compile cleanly
255+
256+
## 2026-04-02: Python Reference Implementation Complete
257+
258+
**Working Solution**: `scripts/agent.py` serves as the reference implementation for the Genius playlist creator.
259+
260+
### Key Features Implemented (Python Script)
261+
262+
1. **8 Agent Tools** - Full implementation matching Rust tool specifications:
263+
- `get_recently_played` - Recently played tracks from local library
264+
- `get_top_artists` - Top artists by play history
265+
- `search_library` - Text search across title/artist/album
266+
- `get_similar_tracks` - Last.fm similar tracks, cross-referenced with library
267+
- `get_similar_artists` - Last.fm similar artists with library sample tracks
268+
- `get_track_tags` - Last.fm mood/genre tags
269+
- `get_top_artists_by_tag` - Genre-based artist discovery
270+
- `get_top_tracks_by_country` - Regional track discovery
271+
272+
2. **Artist Variety Priority** - System prompt enforces 1 track per artist by default, only adding 2nd tracks when artist diversity is exhausted
273+
274+
3. **Shuffled Playlist Output** - `_shuffle_spread_artists()` uses greedy algorithm to spread same-artist tracks apart
275+
276+
4. **Configurable Track Counts** - Environment variables:
277+
- `AGENT_MIN_PLAYLIST_TRACKS` (default: 12)
278+
- `AGENT_MAX_PLAYLIST_TRACKS` (default: 25)
279+
280+
5. **Performance Tuning** - `num_predict: 2048` prevents response truncation
281+
282+
### Rust Implementation Notes
283+
284+
The Python script demonstrates the correct behavior for playlist generation:
285+
- Multi-turn tool calling with parallel execution
286+
- Last.fm cross-referencing with local library
287+
- Maximum artist variety (N tracks from N artists)
288+
- Post-generation shuffle for playback order
289+
290+
When implementing the Rust version, use `scripts/agent.py` as the behavioral reference for:
291+
- System prompt wording (artist variety rules)
292+
- Tool response format and hint messages
293+
- Playlist shuffling algorithm
294+
- Token limit settings (2048 via `max_tokens`)
295+
296+
### Usage
297+
298+
```bash
299+
# Default: 12-25 tracks with maximum artist variety
300+
./scripts/agent.py "make me a chill playlist"
301+
302+
# Custom range: 5-10 tracks
303+
AGENT_MIN_PLAYLIST_TRACKS=5 AGENT_MAX_PLAYLIST_TRACKS=10 ./scripts/agent.py "quick mix"
304+
305+
# Faster execution with fewer turns
306+
./scripts/agent.py --max-turns 2 "make me a chill playlist"
307+
```
308+
309+
Reference file: `scripts/agent.py` (1114 lines, fully functional)
229310
<!-- SECTION:NOTES:END -->
230311

231312
## Definition of Done

0 commit comments

Comments
 (0)