# Smarter song requests with AI

Find a song from a description with !findsong, or let AI match requests, screen out trolls and check a song policy.

Updated 23 September 2026

## Before you start

For flows that queue tracks, set up [song requests](https://bloopbot.com/docs/song-requests) first. Only want a suggestion? [Find a song from a description](https://bloopbot.com/docs/song-requests-ai#find-song-from-description) needs Spotify linked, but does not need request flows enabled. Each capability below is its own AI feature, so your channel may include one and not the other; check [Your plan](https://bloopbot.com/docs/what-your-plan-includes) from the Flows page:

| Feature | What it adds |
| --- | --- |
| Smarter song matching | A **Find a song** step that picks the right track out of several near-identical results. |
| Song policy | An **AI: song fits my rules?** step for troll screening and your own plain-English policy. |
| Intent trigger + Judge & route nodes | Both together are what let a request work with no `!sr` in front of it at all — see [“No command at all”](https://bloopbot.com/docs/song-requests-ai#no-command) below. |

> **These are steps in a flow, not a page of switches**
>
> There is nothing to switch on for any of this outside the flow itself. Each capability here is a step you add to a flow and set up right there on it — which means any trigger can drive it, a chat command, a channel-points reward or a Stream Deck button, and you can always see what your channel actually does by looking at the flow.

## Start from the template

1. Open **Flows → Explore packs** and find **Smart !sr (find, check, queue)**, in the **AI helpers** pack.
2. Install it. You get the same shape as the plain `!sr`, with the AI in it: `!sr` → an **Only if** step that stops a bare `!sr` quietly → **Can they request a song?**, which checks your five song variables and answers a refusal with one line, replying with `$(from.rules.reason)` → **Find a song** with the AI picking the track → a second **Can they request a song?** on the found track, which skips a song already in the queue quietly → **AI: song fits my rules?**, which skips a troll upload quietly → **Request a song**. Nothing found, or Spotify not answering, gets one “couldn’t find that song” line.
3. Reword the two lines, or add the replies you want, the same as in any other flow.

Two changes people often make to their copy:

1. **Say why a troll upload was skipped.** Add a **Chat message** such as `@$(user) that looks like a troll upload, so I’m skipping it.` and wire **AI: song fits my rules?**’s **troll** output to it.
2. **Add your own song policy.** Type a rule into the screen’s **Your song policy** box, such as “chill music only, no screamo”, and wire its **breaks policy** output to a line of your own — see [Screen out trolls and enforce your own rules](https://bloopbot.com/docs/song-requests-ai#policy).

Already have an `!sr` flow? Either replace it with this one — pause the old one, or both will answer the same command — or drop a **Find a song** step in front of your existing **Request a song** and open the song box and choose that step’s **Track** value from the **Insert…** menu — or [drag a data wire](https://bloopbot.com/docs/data-wires) from its `uri` value onto the song box.

> **Why the rules come first**
>
> **Can they request a song?** refuses everything a request can be refused for before anybody knows which track it is: requests switched off, a blocked viewer, a cooldown, a full queue, their own limit. None of it is AI and none of it costs anything. It goes first so someone who was never going to be allowed to queue anything can’t spam `!sr` and spend your AI on it. Keep it at the front if you rebuild this flow yourself.

> **The one thing it cannot answer first**
>
> Before the search nobody knows which track the request is about, so “that one is already in the queue” waits for the second check, on the track **Find a song** settled on. It costs nothing — no AI, no Spotify call — and keeps a repeat from reaching the screening step. Keep it in front of **AI: song fits my rules?** when you rebuild this flow.

## Let AI pick the right track

This section describes **Search for → A title or Spotify link**, the default mode. A sentence that describes a song instead is a different case, covered at the end of this section; [Find a song from a description](https://bloopbot.com/docs/song-requests-ai#find-song-from-description) is the same artist search offered as a suggestion-only command.

Normally a song request queues Spotify’s first search result. The **Find a song** step looks at the top few results instead and asks the AI judge which one the viewer actually meant, from their own wording — the original rather than a karaoke cover, the artist they named rather than the loudest upload.

Its **Pick the track the viewer meant (AI)** switch is on by default. Turn it off and the step takes Spotify’s first hit, exactly as a plain song request always has. A Spotify link is never sent to the judge, because a link already names one track.

The step hands the track forward as data the rest of the flow can use: `uri`, `title`, `artist`, `album`, `explicit`, `duration`, the `confidence` behind the pick, and the runner-up it found — the `alt_uri` value in the **Insert…** menu.

> **What the Unsure branch carries**
>
> A judge that is unreachable, unsure, or not confident enough leaves the step by **Unsure** carrying Spotify’s first result — so wiring **Unsure** to the same place as **Found** gives you exactly the behaviour you had before any of this existed, and **Smart !sr** is wired that way. In this title/link mode, that includes a channel without Smarter song matching: the step still searches and still hands on a track, going by **Found**, exactly as with **Pick the track the viewer meant (AI)** switched off. It is a branch of its own so you can tell “the AI picked this” from “nobody asked the AI”, and the run log says which happened. Artist-description search is the one case with no such fallback — see “No first-result guess in artist mode” under [Find a song from a description](https://bloopbot.com/docs/song-requests-ai#find-song-from-description).

**When the request describes a song instead of naming it.** “That Bowie astronaut song” is not a title, so the search in **Smart !sr** and **Plain-English song requests** usually finds nothing for it: **Smart !sr** answers with its “couldn’t find that song” line, and **Plain-English song requests** stays quiet, because nobody typed a command. [Find a song from a description](https://bloopbot.com/docs/song-requests-ai#find-song-from-description) is the command for a description: it picks the artist out of the words, searches that artist’s tracks and suggests the one that matches, without queuing it.

## A smarter !wrongsong

The next-best candidate comes out of **Find a song** as the `alt_uri` pin, so “that’s the wrong song, give me the other one” is a flow you build and can read rather than something the bot quietly remembered.

The **Wrong song (swap to the runner-up)** template is exactly that: `!wrongsong` and the song they meant → find it → check there really is a runner-up → take back what they last asked for → queue the runner-up that **Find a song** offers on the **Insert…** menu instead.

> **It looks before it removes**
>
> The find comes first on purpose. A bare `!wrongsong` with nothing after it, a channel without the feature, or a judge that can’t answer all mean there is no second guess to swap to — and every one of those paths falls back to the ordinary `!wrongsong`, which takes the song out and says so. Nobody ever loses a request to a step that then found nothing to replace it with.

## Request a song with no command at all

Install **Plain-English song requests**, also in the **AI helpers** pack, and someone can type “can you play Space Oddity by David Bowie” — no `!sr` — to request it. **AI: chat means…** notices the request, **AI: pick out a detail** picks title-and-artist words already in the sentence, and **Find a song** searches for those words, the same as above.

> **Extraction plus literal search**
>
> The sentence is read in two steps — Extraction plus literal search: the title picker pulls out the words it takes for a song title, and the search looks those up literally. A sentence with no title in it, such as “that Bowie astronaut song?”, is not requested; for that, a viewer can use [Find a song from a description](https://bloopbot.com/docs/song-requests-ai#find-song-from-description) below, which suggests a match without queuing it. Installed this template when it still had a second row for descriptions? Your installed copy is not automatically upgraded to the simpler one, so install **Plain-English song requests** again from **Flows → Explore packs** — it lands as a paused copy you can review — then pause or delete the copy it replaces.

Because it uses **AI: chat means…**, it needs the Intent trigger feature too, and because **AI: pick out a detail** is a Judge & route node, it needs that feature as well — so this template asks more of your channel than **Smart !sr** does. Without Intent trigger it never starts; without Judge & route nodes it cannot extract a title.

**AI: pick out a detail** is its own step, not tied to songs — set **What to pick out** to a username, a game or category name, a number, or describe your own in plain English, and any trigger can drive it. The template uses it to read **Text** (`$(message)`) and pick out **A song title**, then points **Find a song**’s search box at that step’s value, picked from the **Insert…** menu or drawn there as a [data wire](https://bloopbot.com/docs/data-wires).

It screens the track with an **AI: song fits my rules?** step of its own, set up the same way as **Smart !sr**’s — a request with no command in front of it is not a request that skips the rules.

Nobody typed a command here, so nobody is waiting on a reply: before anything is picked out or searched for, the template checks your song variables with **Can they request a song?**, and every way a request can end without a song — turned down by your limits, no title in the sentence, nothing found, already in the queue, a troll upload — is left unwired, so it simply stops. Wire one to a quiet chat line yourself if you would rather it said something, such as `@$(user) $(from.rules.reason)` on the first check’s **Refused** output.

Two smaller templates in the same pack cover the other things people say without a command: **Plain-English “what song is this?”** answers someone asking what is playing, and **Plain-English “take my song off”** takes back the request of whoever asked — only ever their own, and it says so, so a misread sentence costs one request and no more.

## Find a song from a description

Remember the artist and a clue, but not the title? **Find a song from a description** adds `!findsong`: it replies with a possible Spotify match and never queues a song. The song-request templates search for a title; this is the command for a description, and it only ever suggests, so you see the match before anything is queued.

Before you start, check **Links → Spotify** shows **Connected** — [the Spotify linking guide](https://bloopbot.com/docs/song-requests) covers linking — and check [Your plan](https://bloopbot.com/docs/what-your-plan-includes) includes both **Judge & route nodes** and **Smarter song matching**. These AI features may not be available on your channel yet. This command does not need **Intent trigger** or **Song policy**: it only suggests, so it does not screen or approve a request.

1. Open **Flows → Explore packs**. Find **Find a song from a description** among the individual templates in **AI helpers** and install that template only. Do not reinstall the whole pack: existing installed flows stay unchanged, and extra copies can answer twice.
2. Open **Canvas** and inspect the command’s path: `!findsong` → cooldown → **AI: pick out a detail** → **Find a song** → a chat reply. Keep the cooldown before both AI steps: it waits **5 seconds across the channel** and **20 seconds per viewer**. There is no **Request a song** action.
3. Check the extraction step: **Text** is `$(query)` and **What to pick out** is **A music artist**. In **Find a song**, keep **Search for** set to **A description of an artist’s song**, **What to search for** at `$(from.artist.value)`, **Original song description** at `$(query)`, and **Pick the track the viewer meant (AI)** on. The artist narrows the search; the full original clue tells the judge what to pick.
4. Review the three replies — the suggestion, the one asking for an artist and a clue, and the miss — press **Save**, then enable the flow. Make sure only one enabled flow answers `!findsong`.
5. In Twitch chat, type `!findsong that Bowie astronaut song?`. Keep the text after the command to at most **32 words and 500 characters**, including a named artist and a clue to one song. Inspect the reply and the flow’s **Run log**.

Expected result: if Spotify returns Space Oddity among the candidates and the judge confidently matches the clue, chat may reply “Possible match: Space Oddity by David Bowie” with its Spotify URI. That is a possible result, not a guarantee. **Nothing is queued.** If it is the song you want and your channel’s request flows are enabled, send `!sr` followed by the returned URI; that separate request still goes through your normal request checks.

> **One artist search, not the whole catalogue**
>
> The artist picker chooses a literal 1–6 word span from your message; it cannot invent an artist you did not name. For the example, a successful pick of “Bowie” leads to one Spotify search for `artist:"Bowie"`, with at most ten tracks. Jev ranks those candidates against the **full original clue**, not just “Bowie”. This is not a full discography search, a semantic web search or a lyrics search. There is no hardcoded music lookup and no generative model writing a title. If the right song is outside those ten results, the command cannot suggest it.

> **No first-result guess in artist mode**
>
> Artist-description search always needs **Smarter song matching**, even if you turn its AI switch off. An unsure, unavailable or incomplete judgment supplies no track: it asks you to retry instead of presenting Spotify’s first hit. The default **A title or Spotify link** mode keeps its existing behaviour, including its first-result fallback.

> **Test fire is only a simulation**
>
> Test fire does not call Spotify or Jev, and does not send a live chat reply; it cannot prove the lookup works. An enabled command sent in Twitch chat is the live path: it can reach Jev, Spotify and chat. Only try it when you want that reply posted.

If the result is not what you expected:

- “I could not tell which song you mean”: a bare `!findsong`, no named artist, text over the input limit, or a judge that was not confident. Retry with a full artist name and one specific clue, and check both required features in Your plan. Missing AI answers and uncertainty never become a first-hit suggestion.
- “No match in the tracks I checked”: the search returned no usable match, or Spotify did not answer — not proof that the song does not exist. Check Spotify is still Connected on Links, try a clearer artist name, or use a known title or Spotify link in your request flow. The Run log shows whether the search failed.
- No reply at all? A search asked for again within a cooldown stops without a reply: wait 5 seconds, or 20 for your own next search. Otherwise check the flow is enabled, the command name is unchanged and its reply branches are connected.

## Screen out trolls and enforce your own rules

The **AI: song fits my rules?** step runs two independent checks on a track before anything queues — never a timeout or a ban, just a branch you decide what to do with:

- **Screen troll / meme / earrape uploads** — joke uploads, shock edits and bait tracks. On by default. A real song that happens to also be a meme still passes.
- **Your song policy** — write a rule in plain English, e.g. “chill music only, no screamo”, and a track that breaks it leaves by the **Breaks policy** branch. Leave it empty and that check is never asked.

One **How strict** picker covers both: **Only the obvious ones**, **Balanced** (the default, and the safe starting point) or **Strict — turns down anything borderline**. Each branch is yours to wire: reply in chat, queue it anyway, log it quietly, or leave it unconnected so nothing happens at all.

> **Write the policy for a viewer, not a filter**
>
> The judge reads your policy the way a person would. “No screamo” works; you don’t need boolean logic or a list of banned artists.

## The Unsure branch matters

Every AI step has an **Unsure** output. On a channel that doesn’t include the feature, that is the branch every request leaves by: the step is skipped rather than run, so a step that _checks_ a track simply doesn’t check it, and the request carries on with the track the flow already has.

**Find a song**, in its default title/link mode, is different: it is never skipped for missing the feature. With or without Smarter song matching, it searches and hands a track on — by **Found** when it is entitled and confident, and still by **Found** (Spotify’s first result) the moment the feature is unavailable, exactly as with its AI switch off. Artist-description search is the one exception: it needs Smarter song matching to run at all, and supplies no track when unavailable or when the judge is unsure — see [the description guide](https://bloopbot.com/docs/song-requests-ai#find-song-from-description). Leave **Unsure** unwired on a _checking_ step and your song requests quietly stop being screened, and the editor puts a warning on the step if you do.

## Troubleshooting

- Still queuing the first search result in title/link mode? Check your channel includes Smarter song matching and that **Pick the track the viewer meant (AI)** is on. In that mode, a low-confidence pick carries the first result on Unsure, which Smart !sr queues. Artist-description search never uses that fallback and the !findsong template never queues.
- A genuine request turned down as a troll? Move **How strict** down a step, or switch troll screening off on the step.
- Policy turning down things it shouldn’t? Reread it for anything stricter than you meant — the judge follows it literally.
- Nothing queues at all? Check the **Unsure** branch is wired on every _checking_ step (**AI: song fits my rules?** and the like) — those are skipped without Smarter song matching or Judge & route nodes, and a skipped check with Unsure unwired carries no track onward. Using artist-description search specifically, also check your channel includes Smarter song matching: that mode alone needs it to run at all. Title/link **Find a song** is not the cause — it always hands a track on, feature or no feature.
- AI parts silent but requests still work? That is the intended fallback: a judge outage, or a missing feature on a step that screens a track, and the request carries on without that step. A missing feature on **Find a song** is the one that does not stand down on its own — with its AI switch on, the step is skipped. In title/link mode you can turn that switch off or give it a fallback (see [“Let AI pick the right track”](https://bloopbot.com/docs/song-requests-ai#match)); artist-description mode always needs the matcher and asks for a retry instead.
- A request that describes a song isn’t found? The request templates search for a title. Use `!findsong` for a description — [Find a song from a description](https://bloopbot.com/docs/song-requests-ai#find-song-from-description) — and request the suggestion it gives with `!sr`.
- Want the newer, simpler version of a template you installed earlier? An installed flow does not change by itself. Open **Flows → Explore packs**, choose the same template again and install it — you get a paused copy — then pause or delete the older one; [templates and packs](https://bloopbot.com/docs/templates-and-packs) explains how copies work.
- A troll upload or a repeat request gets no reply? That is how the template is drawn: those checks stop quietly. [Start from the template](https://bloopbot.com/docs/song-requests-ai#template) shows how to wire a line of your own to them.
