Skip to content

feat(home-assistant): look music up before playing it - #1790

Merged
johnae merged 1 commit into
mainfrom
ha-music-lookup
Sep 24, 2026
Merged

johnae merged 1 commit into
mainfrom
ha-music-lookup

Conversation

@johnae

@johnae johnae commented Sep 24, 2026

Copy link
Copy Markdown
Owner

Voice music requests go through HassMediaSearchAndPlay, which plays the top
search match immediately. When speech-to-text mishears, the wrong artist
starts: "Saint Germain" came through as "San Shuman" and played Robert
Schumann.

This adds Sök musik, a script that searches Music Assistant without
playing
and returns the top three artists, albums and tracks by name. The
model then compares what was heard with what was found:

  • clearly the same → play it by the exact name found
  • only sounds similar → ask, e.g. "Menade du Robert Schumann?"

A reply ending in ? keeps Home Assistant's conversation open
(ChatLog.continue_conversation), so the yes/no is heard without a wake word.

The Music Assistant config entry is found with
integration_entities('music_assistant'), so no instance ID is hard-coded.
Both templates were rendered through Home Assistant's template API against a
sample result and an empty one.

After deploying (runtime, not in this repo): expose script.sok_musik to
Assist, and add the instruction to the Eris agent's prompt.

The cost is one extra search and one extra model turn per music request.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XfYTVcQVjCevVeoJofu8FF

HassMediaSearchAndPlay starts the top match at once, so a misheard name plays
the wrong artist - "Saint Germain" heard as "San Shuman" started Robert
Schumann. A search-only script lets the model compare what was heard with
what was found, and ask before playing a name that only sounds similar.
@claude

claude Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @johnae's task —— View job


Review: search before play

  • Read repo guidelines (DEVELOPMENT.md)
  • Read the diff in profiles/home-assistant.nix
  • Analyze the templates and the script flow
  • Post review feedback

The premise is right, and the shape matches the existing vaderprognos/nyheter scripts (service call → variables → stop with response_variable), so this slots into the file cleanly. A few things I'd change before trusting it on a misheard name.


Duplicates can eat the whole answer

profiles/home-assistant.nix:122-130 slices to three before deduplicating. Music Assistant merges results per provider, so the same artist commonly comes back several times (Spotify plus a Sonos favourite plus a library entry). "Robert Schumann, Robert Schumann, Robert Schumann" pushes out the alternative the user actually asked for — exactly the case this PR exists to fix. Dedupe first, slice after:

{% for a in resultat.artists or [] %}
  {% set ns.artister = ns.artister + [a.name] %}
{% endfor %}
...
{{ {'artister': (ns.artister | unique | list)[:3], ...} }}

| first on an empty list breaks the script

profiles/home-assistant.nix:112: if the Music Assistant entry hasn't loaded yet (server restarting, HA booting first), integration_entities('music_assistant') is [], | first is undefined, and config_entry_id(undefined) raises — the agent gets a raw script error rather than "kunde inte söka just nu". A guarded step, or continue_on_error on the search plus the existing or [] fallbacks, would degrade to an empty result instead. Worth a {% if %} given how much of this file is defensive about exactly this kind of startup ordering.

Name-only results leave the second search free to wander

The model confirms a name, then HassMediaSearchAndPlay searches again by that name — and the second search is unconstrained, so a track title matching a different artist can still win. The MA result items carry a uri; returning it alongside the name lets a follow-up music_assistant.play_media play the exact item that was confirmed. Otherwise the confirmation buys less determinism than it looks like. (The comment at :118-119 says URIs would flood context — three URIs is ~200 characters, artwork is the expensive part.)

Search cost

music_assistant.search defaults to five per type and queries every media type. You only read artists, albums and tracks, and only three of each, so limit: 3 and media_type: [artist, album, track] in the data block at :111-114 skips the playlist and radio round trips. That matters here: profiles/music-assistant.nix:58-66 documents that a degraded Spotify session throttles to one request every two seconds, and this PR adds a search to every music request.

Smaller things

  • :126 and :129 produce "Album av " with a dangling av when an item has no artists. {% if a.artists %} around the suffix.
  • :131 renders a Python dict repr that HA parses back with literal_eval. It works (nyheter does the same), but expressing the mapping as three templated keys — variables.svar = { artister = "{{ ... }}"; album = "{{ ... }}"; "låtar" = "{{ ... }}"; } — renders each value on its own and drops the repr round trip entirely.
  • mode = "parallel" at :102 is the only non-single script in the file. Fine for a read-only search, but this file explains every deviation in a comment; this one deserves the same.

Security, tests

Nothing sensitive: one read-only service call, user text bounded as a search query. No automated coverage is possible for embedded Jinja here — CI only builds the Nix — so rendering both templates through the template API, as the description says you did, is the right check. The empty-result case you tested is the one that matters most; the duplicate-heavy result is the one I'd add.

None of this blocks the idea, which is sound. The dedupe and the empty-first guard are the two I'd want fixed.
• branch ha-music-lookup

@johnae
johnae merged commit aff548c into main Sep 24, 2026
3 checks passed
@johnae
johnae deleted the ha-music-lookup branch September 24, 2026 16:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant