Language: Assist Router v0.1.0 understands Polish - its keywords, built-in phrases and spoken replies are Polish. The architecture (topics in router.yaml, data from entities, speaker recognition) is language-neutral; support for other languages is planned.
Setting up the whole voice stack from scratch (satellites, Whisper, Piper, Ollama, speaker recognition)? Start with the step-by-step guide (PRZEWODNIK.md, Polish), which walks you through it step by step and refers back here for the details.
The built-in Home Assistant assistant recognizes sentences using templates: HA intents, your own
sentences in custom_sentences and automations with a sentence trigger. It is fast and
reliable, but as soon as you phrase something differently („Na którą Ola idzie jutro do szkoły?”
(“What time does Ola go to school tomorrow?”) instead of „O której Ola zaczyna lekcje?” (“When do
Ola's lessons start?”)), you hear “Sorry, I couldn't understand that”. Adding more and more
sentence variants never ends.
Assist Router is a conversation agent layered on top of the built-in HA agent. Everything HA understands keeps working unchanged. When HA does not recognize a sentence:
Small models make mistakes when they have to pick the topic, the person and the day and call a tool all at once. That is why the work is split like this:
As a result, answers are not made up, and a small model is enough for most tasks. The same split applies to home control: the model picks the intent and parameters from lists, the code matches the device, and HA executes the action.
Proven in daily use: the whole setup runs every day in the author's home on an NVIDIA Jetson AGX Orin 64 GB: Ollama (Gemma 4 26B as both router and answer model), faster-whisper and Assist Speaker ID. It works very well: the router answers in about 0.7 s and an answer from data (router plus answer model) takes about 2 s. 64 GB of unified memory holds the model alongside Whisper.
Example results from an NVIDIA Jetson AGX Orin 64 GB. The test covered 96 router questions, 34 questions about real HA data and 8 Wikipedia summaries:
| Role | Model | Accuracy | Time |
|---|---|---|---|
| Router | Gemma 4 26B (same model as for answers, loaded in memory once) | 153/153 (incl. actions and gibberish) | 0.7 s |
| Router (smaller) | Gemma 4 E4B | 92/96 | 0.5 s |
| Answer model | Gemma 4 26B (MoE, 4B active) | 34/34 data, 8/8 summaries | 1.2 s / 5.5 s |
| Parsing control commands | Gemma 4 26B (control_model: answer) | 67/67 commands (incl. actions) | 2.7 s |
| Guessing the sentence behind gibberish | Gemma 4 26B | out of 10 samples: 6 correct, 3× “don't know” (asks to repeat), 1 bold guess | 1.6 s |
| Lightweight setup | Gemma 4 E4B in all roles | 29/34 data, 8/8 summaries, 26/32 commands | 0.8 s / 3.8 s / 1.7 s |
Full comparison of more than a dozen models (Gemma 4, Bielik, PLLuM, Qwen 3.5, Ministral – in Polish only, because the test used Polish questions): accuracy and time in each role, conclusions, typical errors and fine-tuning results. You can repeat the test on your own hardware: section 12.
custom_components/assist_router into the
/config/custom_components/ directory. Alternatively, add the repository to HACS as a
custom repository of type “Integration”.conversation.assist_router) and
turn off “Prefer handling commands locally”. With that option on, HA recognizes
built-in intents before Assist Router: „Ogłoszenie, …” (“Announcement, …”) would be spoken by
text-to-speech, and „Kto to mówi?” (“Who is speaking?”) and recording a voice sample would not
take priority. Assist Router passes every sentence to the built-in agent first anyway, so nothing
is lost.router.example.yaml from the repository to
/config/assist/router.yaml and adapt the entities to your home (file description:
section 8).Settings → Devices & services → Assist Router → Configure opens a menu:
| Item | What it does |
|---|---|
| Settings | Two steps, described below. |
| Router file (router.yaml) | Edit /config/assist/router.yaml directly
in HA. The file is validated before saving (invalid YAML or a configuration error shows a message
and saves nothing), and the previous version is kept as router.yaml.bak. The router
picks up changes from the next question, without a restart. |
| Example commands | Preview of sentences that work in this home, and generating them (see below). |
| Unrecognized commands | Table of the last 30 log entries (newest first)
and a “Clear log” option; the previous content is kept as
unrecognized_commands.csv.bak. |
The assist_router.generate_examples action, or the “Example commands” menu item,
works like this:
router.yaml, the answer model composes sentences using real
names: the topic's entities, devices and rooms (for control), and household members.Result:
/config/assist/router_examples.yaml: dzialaja (“working”)
lists the sentences that work; odrzucone (“rejected”) lists sentences together with
where they ended up, which hints at which topic description or example to improve;topics attribute holds the sentences per topic;Generation takes a few minutes, because every sentence is a separate router request. Action
options: per_topic (sentences per topic, default 5) and topics (only
selected topics). The gibberish topic (unclear) is skipped.
The assist_router.clear_log action clears the unrecognized log. The
“Unrecognized commands” sensor shows the number of entries, and its
last attribute holds the last 20, which is handy for a Markdown card on a dashboard.
Settings, step one:
| Option | What it does |
|---|---|
| Log unrecognized commands | Appends every unrecognized sentence to
/config/assist/unrecognized_commands.csv and fires the
assist_router_unrecognized event. |
| Fallback agent | Conversation agent for questions the router provided no data for (topic “other”, error). Empty = the message „Powiedziałeś: …” (“You said: …”). |
| Fallback agent timeout | After this time you hear „Powiedziałeś: …”. |
| Ollama address | E.g. http://localhost:11434. Empty = router disabled. |
Step two appears once you enter the Ollama address. The model list is fetched from the server:
| Option | What it does |
|---|---|
| Router model | Picks the topic. A small, fast model is enough. |
| Answer model | Optional, but required for answers from data. It writes the answer from the topic data and summarizes Wikipedia. Without it, data questions go to the fallback agent, and for Wikipedia the beginning of the article is read out. |
| Router / model timeout | Default 8 s and 15 s. The router limit also applies to the Wikipedia search. |
| Keep model in memory | Minutes since the last question. -1 =
always in memory, fastest response. Both models are loaded when the integration starts. |
/config/assist/router.yaml describes your home. The integration code is the same for
all users. Changes to the file take effect immediately, without restarting HA. Without the file the
router is disabled. Below is a skeleton with the most important fields; a full example home with
more than a dozen topics is in section 8, and how the code collects and
trims data is described in section 7.
# Who lives in the home - goes into the router and answer model prompts
household: "Domownicy: Tomek (tata), Ania (mama), dzieci: Ola, Kuba." # "Household: Tomek (dad), Ania (mum), children: Ola, Kuba."
# People in questions: match = word beginnings (regex), to catch Polish inflection
people:
ola: {name: Ola, match: "ola|oli|olą|olę|aleksandr"}
kuba: {name: Kuba, match: "kub|jakub"}
tomek: {name: Tomek, match: "tom|tat(a|y|o|ą)\\b"}
# family: {parents: [tomek, ania], children: [ola, kuba], men: [tomek, kuba]}
# speaker: voice identifier in Assist Speaker ID (defaults to the person key, here "ola", "kuba")
# owns: [calendar.kalendarz_oli] - this person's entities; room: Pokój Oli - their room
sentences: 4 # number of sentences in a Wikipedia summary
wikipedia_language: pl
only_exposed: false # true = topic entities only from those exposed to Assist
control_model: answer # which model parses control commands: router or answer
confirm_entities: # always ask for confirmation (entity_id patterns or names)
- switch.kuchnia_lodowka_przelacznik
- switch.*_enabled
music_players: # "play …": room -> players, first available wins
Salon: [media_player.salon_homepod, media_player.salon_home_assistant_voice]
topics:
lekcje:
description: szkoła, plan lekcji, zajęcia dodatkowe, o której kończy lekcje # school, timetable, extra classes, when lessons end
examples: ["Na którą Ola idzie jutro do szkoły?"] # "What time does Ola go to school tomorrow?"
details: [sensor.plan_lekcji] # entity attributes, trimmed to the person and day in the question
days: dzis # no day in the question - today
drop: [teacher, classroom] # keys to skip
kalendarz:
description: plany, wydarzenia, spotkania, urodziny # plans, events, meetings, birthdays
examples: ["Co mamy w weekend?"] # "What do we have at the weekend?"
days: "14" # no day in the question - 14 days from today
fetch: # service with a response
- action: calendar.get_events
entities: [calendar.rodzina, calendar.praca]
data: {start_date_time: "{start}", end_date_time: "{end}"}
merge: events # one list from all calendars, in order
title: Wydarzenia w kalendarzach # "Calendar events"
samochod:
description: auto - paliwo, zasięg, przebieg, czy zamknięte, gdzie stoi # car - fuel, range, mileage, locked?, where parked
examples: ["Ile paliwa ma auto?", "Czy zamknąłem samochód?"] # "How much fuel is in the car?", "Did I lock the car?"
entities: [sensor.moje_auto_*, binary_sensor.moje_auto_*, lock.moje_auto_*]
swiatla:
description: które światła są włączone # which lights are on
entities: [light.*, "!light.ipad_*"] # "!" = exclusion
skip_states: ["off"]
sterowanie:
description: polecenia - włącz, wyłącz, ustaw, przyciemnij, rolety, cieplej, głośniej # commands - turn on/off, set, dim, blinds, warmer, louder
examples: ["Zrób jaśniej w salonie.", "Opuść rolety do połowy."] # "Make it brighter in the living room.", "Lower the blinds halfway."
control: true
wiedza:
description: pytanie o wiedzę ogólną - kim był, co to jest, historia, geografia # general knowledge - who was, what is, history, geography
examples: ["Kim był Mikołaj Kopernik?"] # "Who was Nicolaus Copernicus?"
search: wikipedia
inne:
description: wszystko inne - rozmowa, żart, polecenia # everything else - chit-chat, jokes, requests
examples: ["Opowiedz coś ciekawego."] # "Tell me something interesting."
| Field | Meaning |
|---|---|
description | A short description for the router: what questions in this topic are about. Topic descriptions should not overlap. |
examples | 2–4 example questions. They help most with phrasings on the border between two topics. |
entities | entity_id patterns (* = any
string, |class1,class2 = only these sensor device classes, !pattern =
exclusion). Their states go into the data in Polish: a window is „otwarte” (“open”), a lock is
„zamknięty” (“locked”). unknown/unavailable are skipped; at most 80
entities, in pattern order (most important first). |
details | Patterns of entities whose attributes are sent
to the model (e.g. school timetable, account balances, tram departures). Lists are trimmed to the
person in the question (key child or person) and to the day (key
date or datetime); noise (html, avatars, colors, false
values) is skipped, and times are local and include the weekday. |
fetch | Services with a response: action,
entities, data ({start} and {end} = the day range
from the question), optionally merge (a list to combine across all entities),
title and limit (maximum number of items, default 40). E.g. calendars
(calendar.get_events) or the forecast (weather.get_forecasts). |
days | The day used when the question does not give one: dzis
(today), jutro (tomorrow), tydzien (week) or a number of days from today
("14"). Without this field, data is not filtered by day. |
drop | Attribute keys skipped in this topic. |
personal | true: the topic is for the person asking, even
without „mam” (“I have”) and „moje” (“my”) („Dzień dobry” (“Good morning”)). A parent
(family.parents) gets their own data and the children's data, a child only their own.
An unknown speaker means everyone's data. A name in the question always wins. |
answer_hint | An extra instruction for the answer model in this topic, e.g. how to structure a daily summary and what to say to a parent versus a child. |
verbatim | true: HA's answer to the template sentence in
command is read out word for word, without the answer model. Several sentences separated
by ; give all the answers in turn. Useful when an automation has a fixed answer layout,
e.g. the daily news: „Dzisiejsze wiadomości z Polski: Pierwsza wiadomość…” (“Today's news from
Poland: First item…”). |
answer_hints | Instructions that depend on who is asking. Keys, in order:
person (e.g. ola), girl/boy (children),
child/parent, unknown (speaker not recognized). If no key
matches, answer_hint is used. A simple instruction for one person works more reliably
than one long instruction with conditions. |
answer_tokens | A longer answer than the default 160 tokens, e.g. 360 for a daily summary. |
command | (legacy) An HA (Jinja) template with a template question for
your HA, e.g. an automation sentence. Separate several questions with ;.
It is better to use details and fetch instead - no separate Assist
automations needed. |
search: wikipedia | Knowledge topic: the search term is taken from the question and the article is summarized. |
control: true | Control commands topic (section 10). |
unclear: true | Speech recognition gibberish topic: the model guesses what was said and the assistant asks „Czy chodziło o…?” (“Did you mean…?”); without a guess it asks you to repeat (section 10). |
| no data and no search | The question goes to the fallback agent. |
command template (legacy field)| Variable | Value |
|---|---|
osoba | the first name in the question (the form from people.name) or none |
osoby | all names in the question, in order: „Ola i Kuba” (“Ola and Kuba”) → ['Ola', 'Kuba'] |
kto | the key of the first person from people (e.g. ola) |
kiedy | dzis, jutro, pojutrze, weekend, tydzien, a weekday (poniedzialek … niedziela, Monday … Sunday) or none |
tekst | the full question, e.g. to extract a tram line number |
The default prompts are built into the integration. You can override them in router.yaml
with the fields answer_prompt (answer from data) and summary_prompt
(summary). In the text you can use the fields {household}, {now} (date and
time in Polish) and {sentences}.
The router needs no special automations or Assist sentences. It takes data directly from Home
Assistant, and in router.yaml you only describe which entities and
services belong to a topic. The code does the rest: it picks out what was asked about, removes
noise and gives the answer model a concise list of facts.
| Topic field | What goes to the model | Typical examples |
|---|---|---|
entities | The state of every matching entity, in Polish, with the most important attributes: light brightness, blind position, thermostat temperature, what is playing, how long a sensor has been open. | windows and doors, lights, room sensors, car, people
(person.*), motion, alarm, washing machine |
details | Entity attributes, i.e. data you cannot see in the state itself: lists, timetables, balances, addresses. | school timetable from the e-gradebook,
departures from a stop, phone address (*_geocoded_location), pocket-money accounts |
fetch | The response of an HA service that returns data. The day range comes from the question. | calendars (calendar.get_events), forecast
(weather.get_forecasts) |
You can combine all three in a single topic. The weather topic in the example takes the current
reading from sensors (entities) and the forecast from a service (fetch).
child or person key is
kept only if it matches the person in the question (the match pattern from
people, so „Ola” also matches „Aleksandra”). If the person from the question does
not appear in the data at all (dad asking about the children's accounts), nothing is removed.
A question about „dzieci” (“the children”) or without a name gives everyone's data.date or datetime key is kept
only if it falls on the day from the question. If the question gives no day, the topic's
days applies. Without days, data is not filtered by day. When there is
nothing on that day (lessons on a Saturday), the model gets „brak” (“none”) instead of an empty
list. The filter does not apply to the attribute root itself, because there date is
usually the reading date.friendly_name, false values („odwołana: nie” (“cancelled: no”)) and
empty values, plus the keys listed in drop.2026-10-24 12:00 (sobota)
(Saturday). The model does not have to work out which day is Saturday.entities (in pattern order,
so put the most important first), 40 list items (limit in fetch
changes this per service) and 6000 characters per source.| In the question | Value | Day range |
|---|---|---|
| „dziś”, „dzisiaj”, „teraz” (“today”, “now”) | dzis | today |
| „jutro”, „pojutrze” (“tomorrow”, “the day after tomorrow”) | jutro, pojutrze | that single day |
| „w poniedziałek” … „w niedzielę” (“on Monday” … “on Sunday”) | poniedzialek … niedziela | the nearest such day (today, if it is today) |
| „w weekend” (“at the weekend”) | weekend | the nearest Saturday and Sunday |
| „w tym tygodniu” (“this week”) | tydzien | today and the next 6 days |
| nothing | the topic's days | e.g. dzis or "30" (today and the next 29 days) |
Question: „Na którą Ola idzie jutro do szkoły?”. The router picks the topic
szkola, and the code extracts the person ola and the day jutro.
The topic has details: [sensor.dziennik_plan_lekcji]. This entity's attributes contain
the whole week for both children, well over ten thousand characters:
{"friendly_name": "Plan lekcji", "week_start": "2026-10-19",
"children": [
{"child": "Aleksandra", "avatar": "https://…", "color": "#f5ec00",
"days": [
{"date": "2026-10-22", "lessons": [{"start": "08:00", "subject": "Matematyka", "canceled": false}, …]},
{"date": "2026-10-23", "lessons": [{"start": "08:00", "subject": "Matematyka", "canceled": false},
{"start": "08:55", "subject": "Przyroda", "canceled": true}, …]},
…]},
{"child": "Jakub", "days": […]}]}
After trimming, the model gets only Ola's tomorrow, without avatars, colors and
canceled: false:
Plan lekcji (stan: 2026-10-22):
week_start: 2026-10-19 (poniedziałek)
children:
- child: Aleksandra
days:
- date: 2026-10-23 (piątek)
lessons:
- start: 08:00
subject: Matematyka
- start: 08:55
subject: Przyroda
canceled: true
The model's answer: „Ola idzie jutro na 8:00, pierwsza jest matematyka. Przyroda o 8:55
jest odwołana.” (“Ola goes in at 8:00 tomorrow, maths is first. Science at 8:55 is
cancelled.”) The model noticed the cancelled lesson on its own, because the true flag
stayed in the data.
Without merge, each calendar would be a separate source and the model would have to
put the events in order itself. With merge, the question „Co mamy w piątek?” (“What do
we have on Friday?”) gets a ready-made plan for the day, and „Kiedy są urodziny babci?” (“When is
grandma's birthday?”) searches 30 days (days: "30").
fetch safety. The integration only calls services
that only return data (in HA: SupportsResponse.ONLY, e.g.
calendar.get_events, weather.get_forecasts,
todo.get_items). An entry with a service that does something (lock.unlock,
a script) is skipped with a warning in the log. A question never switches anything.They are not needed. If you already have them, they can stay: HA checks them before the
router, so a sentence that exactly matches an automation gets an answer in a fraction of a second,
without a model. Any other phrasing goes to the router and the model answers it from the data. The
legacy command field (a template question for an automation) still works, but new
topics are better described with entities, details and fetch.
With the Speaker ID integration (speech-to-text that also recognizes the voice), the router knows who asked the question. You only need to set its STT („Mowa na tekst z mówcą”, “Speech-to-text with speaker”) in your pipelines. The agent stays Polski Assist, which reads the speaker by itself from the same pipeline run.
details are trimmed to their data, as if their name
had been said.people, not from
Assist Speaker ID.speaker field on the person.
It defaults to the person key, so it is enough to save voice samples under the same identifiers
(ola, kuba…).A person's entities and room (owns, room):
owns are patterns of entities that belong to the person, e.g.
their calendar or their phone's location. When a question is about a specific person (by name,
or „mam”, „moje” from a recognized speaker), other people's entities are skipped in
details, fetch and entities. Entities that belong to no
one, e.g. the family calendar, are always included. So „Co mam jutro?” (“What do I have
tomorrow?”) from Ola gives the family calendar and her own, without dad's work calendar. This is
needed because calendar events have no person field to trim by.room is the person's room. „Zgaś światło w moim pokoju” (“Turn
off the light in my room”) or „u mnie w pokoju” (“in my room”) from that person means this area.family section:
parents, children, men). „Mój brat” (“my brother”), „moja
siostra” (“my sister”), „rodzeństwo” (“siblings”) from a child, and „mój syn” (“my son”), „moja
córka” (“my daughter”), „moje dzieci” (“my children”) from a parent point to the right people,
e.g. „moja córka” from dad means all daughters. They are added to the names in the question
(„Czy mama i mój brat są w domu?” (“Are mum and my brother at home?”)).
„Mama”, „tata”, „żona” and „mąż” (mum, dad, wife, husband) remain fixed names in match.mowca() macro in
custom_templates.Daily summary and “good night” for a person: topics with
personal: true collect the speaker's data. „Dzień dobry” (“Good morning”), „Jak wygląda
mój dzień?” (“What does my day look like?”), „Co mnie dziś czeka?” (“What's ahead of me today?”) from
a child give their lessons, tests, chores and calendar. From a parent they give the parent's calendar
and the weather, plus a short summary about the children. How to structure the answer is set by the
topic's answer_hint. Example: the podsumowanie_dnia and
dobranoc topics in router.example.yaml. Scenes with these names are only run
when asked explicitly („Uruchom scenę Dzień dobry” (“Run the Good morning scene”)). A plain „Dzień
dobry” is a greeting.
Announcements (intercom): a sentence starting with „ogłoszenie” (“announcement”),
„komunikat” (“message”), „do wszystkich” (“to everyone”) or „intercom” („Ogłoszenie, dzieci proszę
przyjść na obiad” (“Announcement, children please come for lunch”)) goes neither to HA nor to the
models. Assist Speaker ID plays the speaker's recording, uncut and unchanged, on the
other speakers, and the speaker that recorded it only says „Ogłoszenie wysłane.” (“Announcement
sent.”). Additional players, e.g. HomePods, are set in router.yaml as
announce_players: [media_player.…]. Without a recording (text typed by hand or no Assist
Speaker ID) the sentence continues along the normal path: HA reads it out with text-to-speech. These
words must be at the start of the sentence, so „Jakie są ogłoszenia w Librusie?” (“What are the
announcements in Librus?”) is an ordinary question.
Checking recognition: „Kto to mówi?” (“Who is speaking?”), „Kim jestem?” (“Who am I?”), „Jak mnie rozpoznajesz?” (“How do you recognize me?”), „Co to za głos?” (“Whose voice is this?”). The assistant answers immediately, without HA or models, from the result of that same utterance, e.g. „Mówi Ola, podobieństwo 82 procent. Następna osoba: Kuba, 35 procent.” (“Ola is speaking, 82 percent similarity. Next person: Kuba, 35 percent.”) The answer may also be „nie rozpoznaję tego głosu” (“I don't recognize this voice”) or „nie jestem pewien” (“I'm not sure”), with the two most similar voices. A short question (under 1 s of speech) gets a request for a longer sentence, e.g. „Powiedz mi, kto teraz do ciebie mówi” (“Tell me who is talking to you now”).
The router.example.yaml file in the repository describes a made-up home: Tomek, Ania
and two children, Ola and Kuba. Entities are named the way popular integrations usually name them:
the HA mobile app (phone location), Frigate (cameras), Met.no (weather), calendars, a car, a robot
vacuum, a washing machine and shopping lists. Just replace the entity names with your own
(Developer tools → States).
| Topic | Data source | What it shows |
|---|---|---|
kalendarz | fetch: calendar.get_events, merge | several calendars in one list, day from the question, 30 days if no day given |
szkola | details or fetch | timetable trimmed to the child and day; two e-gradebook variants |
pogoda | entities + 2 × fetch | sensor reading plus daily and hourly forecast (limit: 24) |
czujniki | entities with |class | sensors by device_class, exposed only |
okna_drzwi | entities | windows, doors, garage and blinds with the “since when” time |
osoby | entities + details | household members, Frigate cameras, motion, address from the phone; pattern order |
samochod | entities | all car sensors - the model picks |
swiatla | entities, skip_states, ! | only lights that are on, excluding the tablet screen |
media | entities, skip_states | only players that are playing something |
listy | entities: [todo.*] | list items (the integration calls todo.get_items itself) |
komunikacja | details | departures from stops, from attributes |
niezrozumiale | unclear: true | speech recognition gibberish: a „Czy chodziło o…?” (“Did you mean…?”) suggestion or a request to repeat |
sterowanie, wiedza, inne | - | commands, Wikipedia, fallback agent |
# Example router.yaml for Assist Router - copy to /config/assist/router.yaml
# and adapt the entities to your home (Developer tools → States).
#
# The example home: Tomek (dad), Ania (mum), children Ola and Kuba. Entities are named the way
# popular integrations usually name them: the HA mobile app (phone location),
# Frigate (cameras), Met.no (weather), Google/CalDAV calendars, a car, robot vacuum
# and washing machine integration. All entity names are examples.
#
# How the router uses this file:
# 1. The router model picks ONE topic from `topics` (based on description and examples).
# 2. The code extracts the people (`people`) and the day ("jutro", "w piątek") from the question.
# 3. The topic collects data: entity states (entities), entity attributes (details) and service
# responses (fetch) - trimmed to the person and day in the question.
# 4. The answer model answers the question using only this data.
# Changes to the file take effect immediately, without restarting HA.
# --- General settings ----------------------------------------------------------------
# Who lives in the home - goes into the prompts (the model knows that "mama" is Ania and
# "dzieci" (the children) are Ola and Kuba).
household: >-
Domownicy: Tomek (tata, najczęściej pyta), Ania (mama), dzieci: Ola (Aleksandra, 10 lat)
i Kuba (Jakub, 7 lat).
# ^ "Household: Tomek (dad, asks most often), Ania (mum), children: Ola (Aleksandra, 10) and Kuba (Jakub, 7)."
# People in questions. `match` is word beginnings (regex), to catch Polish inflection: "Oli", "Olą",
# "Kubie", "taty". A person from the question trims data that has a `child` or `person` key
# (e.g. only Ola's timetable). A question without a name ("dzieci") - everyone's data.
# With speaker recognition (the Assist Speaker ID integration) a first-person question ("Co mam jutro?")
# is about whoever is speaking. Extra person fields:
# speaker - their voice identifier in Assist Speaker ID (defaults to the person key, e.g. "ola"),
# owns - their entities (e.g. work calendar, phone): skipped when asking about another person,
# entities owned by no one (family calendar) - always included,
# room - their room: "zgaś światło w moim pokoju" from this person = this area.
people:
tomek:
name: Tomek
match: "tom|tat(a|y|o|ą|ę)\\b"
owns: [calendar.praca_tomka, sensor.telefon_tomka_*]
ania:
name: Ania
match: "ani|anna|anny|mam(a|y|ie|ą|ę)\\b"
owns: [sensor.telefon_ani_*]
ola:
name: Ola
match: "ol(a|i|ą|ę|u)\\b|olk|aleksandr"
room: Pokój Oli
kuba:
name: Kuba
match: "kub|jakub"
room: Pokój Kuby
# Family terms relative to the speaker: "mój brat" (my brother) from Ola = Kuba, "moja córka"
# (my daughter) from Tomek = Ola, "moje dzieci" (my children) from Ania = Ola and Kuba.
# "mama"/"tata" are fixed names (match above).
family:
parents: [tomek, ania]
children: [ola, kuba]
men: [tomek, kuba]
sentences: 4 # number of sentences in a Wikipedia summary
wikipedia_language: pl
only_exposed: false # true = topic entities only from those exposed to Assist
control_model: answer # parsing control commands: router or answer (larger model)
# Control: for these devices the assistant always asks "Czy wyłączyć…?" (Turn off…?) - also when
# they are part of a group command ("wyłącz wszystkie przełączniki" - turn off all switches).
confirm_entities:
- switch.lodowka
- switch.serwer_*
- switch.*_kamera_enabled # camera switches (Frigate)
- Router Wi-Fi # you can also give a device name
# "Puść …" (play …): room -> players in order (first available). Music Assistant
# players often have no room - hence this list. With no room in the command: the room
# of the speaker you are talking to.
music_players:
Salon: [media_player.salon_glosnik, media_player.salon_telewizor]
Sypialnia: [media_player.sypialnia_glosnik]
Kuchnia: [media_player.kuchnia_glosnik]
# --- Topics ---------------------------------------------------------------------------
#
# Topic data sources (can be combined):
# entities - entity_id patterns; the state goes to the model in Polish ("otwarte", "w domu").
# `*` = any string, `|class1,class2` = only these device_class values,
# `!pattern` = exclusion. Order matters (limit of 80 entities).
# details - patterns of entities whose ATTRIBUTES go to the model (lists, timetables, balances).
# fetch - services that return data (calendar.get_events, weather.get_forecasts…).
# `{start}` and `{end}` = the day range from the question.
# days - the day when the question gives none: dzis, jutro, tydzien or a number of days.
# drop - attribute keys to skip (noise the model does not need).
# skip_states - states to skip (e.g. lights that are off - only lights that are on remain).
topics:
# Summaries for the person asking (requires Assist Speaker ID; without it - for the whole family).
# personal: with no name in the question, the speaker's data, and for a parent also the children's.
podsumowanie_dnia:
# good morning, daily summary, what my day looks like, what's ahead today, plan for the rest of the day
description: dzień dobry, podsumowanie dnia, jak wygląda mój dzień, co mnie dziś czeka, plan na resztę dnia
examples: ["Dzień dobry", "Jak wygląda mój dzisiejszy dzień?", "Co mnie dzisiaj czeka?"]
personal: true
days: dzis
answer_tokens: 360
# "This is a spoken daily summary. Talk to the person from 'Pyta:', by name, in plain sentences.
# Weather first, then their plan (lessons, events, chores). For a parent add one or two
# sentences about the children's most important matters. Up to 6 sentences."
answer_hint: >-
To podsumowanie dnia na głos. Mów do osoby z „Pyta:”, po imieniu, zwykłymi zdaniami.
Najpierw pogoda, potem jej plan (lekcje, wydarzenia, obowiązki). Rodzicowi dodaj
jedno lub dwa zdania o najważniejszych sprawach dzieci. Do 6 zdań.
details: [sensor.plan_lekcji, sensor.obowiazki]
fetch:
- action: calendar.get_events
entities: [calendar.rodzina, calendar.praca_tomka]
data: {start_date_time: "{start}", end_date_time: "{end}"}
merge: events
- action: weather.get_forecasts
entities: [weather.dom]
data: {type: daily}
dobranoc:
description: dobranoc, idę spać, co mnie czeka jutro # good night, going to bed, what's ahead tomorrow
examples: ["Dobranoc", "Idę spać", "Co mnie jutro czeka?"]
personal: true
days: jutro
# speaker-dependent instruction: person, girl/boy, child/parent, unknown
# girl: one or two sentences on what awaits her tomorrow (no home, no weather), then a tender,
# fairy-tale good night by name, as to a princess
# boy: one or two sentences on what awaits him tomorrow, then a cheerful good night by name
# (e.g. recharging a superhero's batteries)
# parent: what awaits them tomorrow and the morning weather, what was left open or on at home,
# one sentence about the children's tomorrow. End with "Dobranoc". Up to 5 sentences.
# unknown: briefly for the whole family, no names: what's tomorrow and what is open or on at home
answer_hints:
girl: >-
Jednym-dwoma zdaniami powiedz, co ją czeka jutro (bez domu i pogody), potem czule,
bajkowo życz dobrej nocy po imieniu, jak księżniczce.
boy: >-
Jednym-dwoma zdaniami powiedz, co go czeka jutro (bez domu i pogody), potem na wesoło
życz dobrej nocy po imieniu (np. ładowanie baterii superbohatera).
parent: >-
Co go czeka jutro i pogoda rano, co w domu zostało otwarte lub włączone, jedno
zdanie o jutrze dzieci. Zakończ „Dobranoc”. Do 5 zdań.
unknown: >-
Krótko dla całej rodziny, bez imion: co jutro i co otwarte lub włączone w domu.
details: [sensor.plan_lekcji]
fetch:
- action: calendar.get_events
entities: [calendar.rodzina, calendar.praca_tomka]
data: {start_date_time: "{start}", end_date_time: "{end}"}
merge: events
entities: [binary_sensor.*_okno, light.*]
# Calendars: events from all calendars in one list, in order.
# "Co mamy w piątek?" (What do we have on Friday?) -> Friday only;
# "Kiedy są urodziny babci?" (When is grandma's birthday?) -> 30 days from today.
kalendarz:
# plans, events, meetings, birthdays, name days, holidays, what we have planned, dates
description: plany, wydarzenia, spotkania, urodziny, imieniny, święta, co mamy zaplanowane, terminy
examples:
- Co mamy w weekend?
- Ile mam jutro spotkań w pracy?
- Kiedy są urodziny babci?
- Czy w czwartek coś się dzieje?
days: "30"
drop: [description] # meeting descriptions (video call links) just take up space
fetch:
- action: calendar.get_events
entities: [calendar.rodzina, calendar.praca_tomka, calendar.urodziny, calendar.swieta_w_polsce]
data: {start_date_time: "{start}", end_date_time: "{end}"}
merge: events # one list from all calendars, sorted by time
title: Wydarzenia w kalendarzach (po kolei) # "Calendar events (in order)"
limit: 60
# School from the e-gradebook. Two typical variants - pick the one that fits:
# a) the integration provides an entity with attributes (children -> days -> lessons): `details`;
# b) the integration provides a lesson calendar for each child: `fetch` as for the calendar.
szkola:
# school, timetable, when lessons start or end, when they get back from school, substitutions, cancelled lessons
description: szkoła, plan lekcji, o której zaczyna lub kończy lekcje, kiedy wraca ze szkoły, zastępstwa, odwołane lekcje
examples:
- Na którą Ola idzie jutro do szkoły?
- Czy Kuba ma jutro WF?
- O której dzieci kończą dziś lekcje?
details: [sensor.dziennik_plan_lekcji] # variant a)
days: dzis # no day in the question - today
drop: [teacher_id, classroom_id]
# fetch: # variant b)
# - action: calendar.get_events
# entities: [calendar.lekcje_ola, calendar.lekcje_kuba]
# data: {start_date_time: "{start}", end_date_time: "{end}"}
# merge: events
# Weather: current conditions from the balcony sensors, forecast from a service (daily and hourly).
pogoda:
# outdoor weather now and forecast, rain, wind, take an umbrella?, what to wear, outside temperature
description: pogoda na zewnątrz teraz i prognoza, deszcz, wiatr, czy wziąć parasol, jak się ubrać, temperatura na dworze
examples:
- Czy jutro będzie padać?
- Czy wziąć parasol?
- Jak się ubrać na weekend?
- Ile jest stopni na dworze?
entities:
- sensor.balkon_temperatura
- sensor.balkon_wilgotnosc
- sensor.deszczomierz_*
drop: [wind_bearing, uv_index, cloud_coverage, dew_point, pressure]
fetch:
- action: weather.get_forecasts
entities: [weather.dom]
data: {type: daily}
title: Prognoza na kolejne dni # "Forecast for the coming days"
- action: weather.get_forecasts
entities: [weather.dom]
data: {type: hourly}
title: Prognoza godzinowa # "Hourly forecast"
limit: 24 # one day at most - more only slows the answer down
# Room climate: sensors by type (device_class) and thermostats.
czujniki:
# temperature, humidity, CO2, indoor air quality, heating, thermostats, is it stuffy
description: temperatura, wilgotność, CO2, jakość powietrza w pokojach, ogrzewanie, termostaty, czy jest duszno
examples:
- Ile stopni jest w pokoju Oli?
- Gdzie jest najwyższe CO2?
- Na ile jest ustawione ogrzewanie w salonie?
only_exposed: true # only sensors exposed to Assist (no technical ones)
entities:
- sensor.*|temperature,humidity,carbon_dioxide,pm25,volatile_organic_compounds
- climate.*
# Windows, doors and blinds: opening sensors and blind position.
okna_drzwi:
# which windows or doors are open, is everything closed, front door, balcony, garage, blinds
description: które okna lub drzwi są otwarte, czy wszystko zamknięte, drzwi wejściowe, balkon, garaż, rolety
examples:
- Które okna są otwarte?
- Czy zamknąłem drzwi balkonowe?
- Od kiedy jest otwarte okno w sypialni?
entities:
- binary_sensor.*|door,window,opening,garage_door
- cover.*
# Who is home and where: household members (person), address from the phone (HA mobile app),
# Frigate cameras (number of people), motion sensors. Order: most important first.
osoby:
# who is home, where someone is, is anyone in a room, where was motion, how many people the cameras see
description: kto jest w domu, gdzie ktoś jest, czy ktoś jest w pokoju, gdzie był ruch, ile osób widzą kamery
examples:
- Gdzie jest mama?
- Czy Kuba jest już w domu?
- Czy ktoś jest w salonie?
- Ile osób widzi kamera w ogrodzie?
entities:
- person.*
- sensor.*_person_count # Frigate: number of people in frame
- binary_sensor.*|motion,occupancy,presence
details: [sensor.telefon_*_geocoded_location] # phone address (attributes: street, city)
drop: [Location, Country, ISO Country Code, Postal Code, Time Zone, Areas Of Interest,
Administrative Area, Sub Administrative Area, Ocean, Inland Water]
# Car: all car sensors - the model picks what was asked about.
samochod:
# car - fuel, range, mileage, consumption, service, locked?, windows, tyres, where parked
description: samochód - paliwo, zasięg, przebieg, spalanie, serwis, czy zamknięty, okna, opony, gdzie stoi
examples:
- Czy muszę zatankować?
- Czy zamknąłem auto?
- Gdzie zaparkowałem?
- Kiedy przegląd?
entities:
- sensor.moje_auto_*
- binary_sensor.moje_auto_*
- lock.moje_auto_*
- device_tracker.moje_auto_*
# Lights: only those that are on (skip_states), excluding the tablet screen and speaker LED rings.
swiatla:
# which lights are on, where a light is on, was a light left on, lamp brightness
description: które światła są włączone, gdzie się świeci, czy zostało zapalone światło, jasność lamp
examples:
- Czy zostawiłem gdzieś zapalone światło?
- Które lampy świecą w kuchni?
only_exposed: true
entities:
- light.*
- "!light.*_ekran"
- "!light.*_led_ring"
skip_states: ["off"]
# What's playing: only players that are doing something.
media:
# what's playing now, music, TV, speakers, what volume, is the TV on
description: co teraz gra, muzyka, telewizor, głośniki, jaka głośność, czy telewizor jest włączony
examples:
- Co teraz gra w salonie?
- Czy telewizor jest włączony?
only_exposed: true
entities:
- media_player.*
skip_states: ["off", "idle", "standby"]
# Robot vacuum, washing machine - state and time remaining.
odkurzacz:
description: odkurzacz, robot sprzątający, czy skończył, bateria, filtr, szczotki # vacuum, cleaning robot, finished?, battery, filter, brushes
examples: ["Czy odkurzacz skończył?", "Ile baterii ma robot?"]
entities: [vacuum.*, sensor.odkurzacz_*]
pranie:
description: pralka, suszarka, czy pranie się skończyło, ile zostało do końca # washer, dryer, is the laundry done, how long left
examples: ["Czy pralka skończyła?", "Ile jeszcze będzie suszyć?"]
entities: [sensor.pralka_*, sensor.suszarka_*]
# Lists (todo): list contents, not just the number of items (todo.get_items).
listy:
description: lista zakupów, co trzeba kupić, przypomnienia, rzeczy do zrobienia # shopping list, what to buy, reminders, to-dos
examples: ["Co jest na liście zakupów?", "Czy mamy na liście mleko?"]
entities: [todo.*]
# Alarm and safety - read-only (leave arming the alarm to automations).
alarm:
# alarm, is the alarm armed, protection mode, flooding, smoke, gas, is water leaking anywhere
description: alarm, czy alarm jest włączony, tryb ochrony, zalanie, dym, gaz, czy gdzieś cieknie woda
examples: ["Czy alarm jest włączony?", "Czy czujnik dymu coś wykrył?"]
entities:
- alarm_control_panel.*
- binary_sensor.*|moisture,smoke,gas,carbon_monoxide
# Public transport: departures from stops (MPK/ZTM integrations keep a list of departures
# in attributes). Without a day - everything in the data.
komunikacja:
description: tramwaj, autobus, odjazdy z przystanku, za ile jest tramwaj # tram, bus, departures from a stop, when is the next tram
examples: ["Za ile mam tramwaj 8?", "Kiedy jedzie autobus z Parkowej?"]
details: [sensor.przystanek_*]
drop: [html, original_departure]
# State of other devices (sockets, switches).
urzadzenia:
description: czy urządzenie jest włączone - gniazdka, przełączniki, ładowarki, akwarium # is a device on - sockets, switches, chargers, aquarium
examples: ["Czy akwarium jest włączone?", "Czy ładowarka w garażu działa?"]
only_exposed: true
entities: [switch.*]
# Commands HA did not recognize: parsed into an HA intent and executed
# (with a confirmation question when in doubt). Details: readme, section "Home control".
sterowanie:
# commands - turn on, turn off, switch off, switch on, set, dim, light color, blinds, warmer, louder, play music, vacuum, add to list
description: polecenia - włącz, wyłącz, zgaś, zapal, ustaw, przyciemnij, kolor światła, rolety, cieplej, głośniej, puść muzykę, odkurz, dopisz do listy
examples:
- Zrób ciepłe światło w sypialni.
- Opuść rolety do połowy.
- Puść jazz w kuchni.
- Dopisz mleko do listy zakupów.
control: true
# General knowledge: a summary of a Wikipedia article (3–4 sentences).
wiedza:
description: pytanie o wiedzę ogólną - kim był, co to jest, historia, geografia, nauka # general knowledge - who was, what is, history, geography, science
examples: ["Kim był Mikołaj Kopernik?", "Co to jest fotosynteza?"]
search: wikipedia
# Speech recognition gibberish: the words do not fit together. The assistant guesses what was said
# ("Czy chodziło o: włącz nastrój w salonie?" - Did you mean: turn on the mood scene in the living
# room?) or asks you to repeat. State explicitly in the description what does NOT belong here -
# otherwise the router will also put strange but meaningful questions here.
niezrozumiale:
# a nonsensical sentence - the words do not fit together and neither a question nor a command can be
# understood; looks garbled by speech recognition. NOT for questions and commands with one garbled word
# when the meaning is clear, nor for knowledge questions, chit-chat and jokes
description: zdanie bez sensu - słowa do siebie nie pasują i nie da się z nich zrozumieć ani pytania, ani polecenia; wygląda na przekręcone przez rozpoznawanie mowy. Tu NIE należą pytania i polecenia z jednym przekręconym słowem, gdy sens jest jasny, ani pytania o wiedzę, rozmowa i żarty
examples:
- Głup nastrój w pisarowie.
- Szuflada wieczór gazeta kuchni.
unclear: true
# Everything else - fallback agent (chit-chat, jokes).
inne:
description: wszystko inne - rozmowa, żart, prośby, pytania bez danych w domu # everything else - chit-chat, jokes, requests, questions without home data
examples: ["Opowiedz coś ciekawego.", "Jak się masz?"]
Example: questions about the washing machine and dryer, e.g. „Ile jeszcze będzie prać?” (“How much longer will the wash take?”) or „Czy mogę wyjąć pranie?” (“Can I take the laundry out?”).
pralka. Check whether the information you need is in the state (e.g.
“time remaining: 25 min”) - then entities is enough - or in the
attributes (lists, timetables) - then use details.fetch; use {start} and {end} instead of
dates. pranie:
description: pralka, suszarka, czy pranie się skończyło, ile jeszcze potrwa # washer, dryer, is the laundry done, how much longer
examples: ["Czy suszarka skończyła?", "Ile jeszcze będzie prać?"] # "Is the dryer done?", "How much longer will the wash take?"
entities: [sensor.pralka_*, sensor.suszarka_*]route column, you will
see e.g. pranie + 12 encji + odpowiedź (12 entities + answer), and for
details and fetch szkola + 1 źródeł + odpowiedź (1 source +
answer).description and add
the question from the log to examples.drop, days, limit), the answer is
missing from it (add an entity), or it contains similar, confusing entities (exclude them with
!pattern). tools/compare_sources.py helps (section 12).days so that a question without a day does not pull in the
whole week.A topic with control: true handles commands that HA did not recognize. hassil
sentences and automations always take priority.
WHISPER_FIXES in control.py.intencja (intent) field is an HA intent: HassTurnOn/Off,
HassLightSet, HassSetPosition,
HassClimateSetTemperature, HassSetVolume/Relative,
media control, HassMediaSearchAndPlay („puść …” (“play …”)), vacuum or
HassListAddItem. The fields
domena (domain), obszar (area) and pietro (floor) are chosen
from your HA's lists. On top of that come nazwa (name, a fragment of the utterance),
wszystkie (all), wartosc (value) and zmiana (change: more /
less), and for lights kolor (color, e.g. red) and barwa (color
temperature: warm 2700 K, neutral 4000 K, cool 6500 K), and for music szukaj (search)
and rodzaj (type: track, album, artist, playlist, genre, podcast).confirm_entities list (also in a group command, e.g.
„wyłącz wszystkie przełączniki” (“turn off all switches”)).HassTurnOn for a lock = lock it). Any other
utterance cancels the question.intent.async_handle (HA):
DUPLICATE_NAME) and does nothing, and there is no way to specify an
entity_id in it. So the integration does not ask about “several matching devices”;
instead it calls the service on each of these entities (media_player.volume_set,
homeassistant.turn_on, light.turn_on …); pause and next track - only on
the one that is playing. A name given by the model together with the room („Łazienka HomePod”)
is compared without the room word.confirm_entities.unclear: true catches sentences whose words do not fit
together. The model guesses what was said - it knows the names of rooms and actions - and the
assistant asks: „Nie jestem pewien, czy dobrze usłyszałem. Czy chodziło o: włącz nastrój w
salonie?” (“I'm not sure I heard you right. Did you mean: turn on the mood scene in the living
room?”). On „tak” (“yes”) the corrected sentence goes through the whole normal path (HA sentences,
automations, router); on „nie” the assistant asks you to repeat, and „anuluj” ends the
conversation. When no guess is possible, it says straight away „Nie zrozumiałem. Powtórz, proszę.”
(“I didn't understand. Please repeat.”) In the topic description it is worth stating explicitly
what does not belong here: questions and commands with one garbled word when the meaning
is clear, knowledge questions, chit-chat. Nothing is executed without confirmation. The assistant
asks you to repeat at most once in a row. If it fails to understand a second time (gibberish again,
or „nie” to its suggestion again), it says „Przepraszam, nie rozumiem. Jeśli będziesz czegoś
potrzebować, poproś mnie jeszcze raz.” (“Sorry, I don't understand. If you need anything, just ask
me again.”) and ends the conversation - the microphone closes. Every command or question that is
understood resets the counter; each conversation (speaker) is counted separately.media_player.search_media, e.g. in Music Assistant) and playback of the first result.
The code chooses the player: from music_players (first available); without an entry, a
player with search support in that area or with the room name in its name; and with no room in the
command, the satellite's room. A light without color or color temperature support gets a message
instead of an error.confirm_entities, and do not expose those you never want controlled by voice./config/assist/unrecognized_commands.csv (UTF-8, separator ;)
opens in Numbers and Excel. Columns:
| Column | Content |
|---|---|
timestamp, area, device | when and on which speaker |
kind | no_intent_match (HA recognized nothing) or
no_valid_targets (it recognized a command for a non-existent area or device,
e.g. „…w szkole” (“…at school”)) |
text | what Whisper heard |
response | what the assistant answered |
route | the router's path: topic, number of entities and sources (details, fetch), „+ odpowiedź” (“+ answer”) / „+ streszczenie” (“+ summary”), and for control the intent and parameters |
speaker | recognized speaker (Assist Speaker ID), empty = unknown |
Review it from time to time. Questions with an empty route or a wrong answer show
which topic to improve. Frequent sentences are worth adding as regular HA sentences, because those
work instantly.
sensor.assist_router_nierozpoznane_komendy gives the number
of entries, and its last attribute holds the last 20. Handy for a Markdown card on a
dashboard.assist_router.clear_log action or “Clear log” in the
options. The header stays, and the previous content goes to
unrecognized_commands.csv.bak.speaker column contains the recognized speaker (Assist Speaker ID).The tools/ directory contains five tools (Python, run from any computer with access
to Ollama):
snapshot_data.py saves a snapshot of real data: HA answers to template questions and
Wikipedia articles. This way the expected facts in the test do not change.model_benchmark.py tests models in turn:
control section checks command parsing against the home snapshot, without executing
anything. It replays the integration's whole decision: the alarm safeguard, a plain “yes/no” reply,
the script name without the model, and parsing with the list of actions (scripts with descriptions,
scenes). It checks the intent, the area, the matched device or action, and whether the assistant
would ask for confirmation.
snapshot_data.py --only-home refreshes just the home snapshot, e.g. after changing
descriptions or aliases. The code tests alone (pytest) check control from sentence to
the called service on a fake home (tests/test_actions.py). The
--rescore option re-scores saved answers after test fixes.model_report.py creates an HTML report and appends history to a CSV, so new models
can be compared with old ones.compare_sources.py is a consistency test for changing a topic's data source.
For each question in a list it computes the data the old way and the new way (two router.yaml
files), e.g. automation sentences versus details/fetch, on live HA data.
It saves the model's answers side by side. Read-only.check_sentences.py checks your automations with sentences. From each
conversation trigger template it generates a few sentences and asks HA (debug mode,
nothing is executed) what would handle them. It reports sentences that hit a different automation
or HA intent, sentences that match nothing, and disabled automations. Run it after every change to
your sentences..venv/bin/python tools/model_benchmark.py --ollama http://localhost:11434 \
--router-config /config/assist/router.yaml --cases model_test.yaml \
--data model_test_data.yaml --router gemma4:e4b --answer gemma4:26b --pull --out wyniki.json
.venv/bin/python tools/model_report.py wyniki.json --label "test" \
--html result.html --csv model_results.csv --answers-csv model_answers.csv
.venv/bin/python tools/compare_sources.py --ha http://homeassistant.local:8123 \
--token-file ~/.ha_token --ollama http://localhost:11434 \
--old router.yaml --new router.new.yaml --questions pytania.txt --out porownanie.json
.venv/bin/python tools/check_sentences.py --ha http://homeassistant.local:8123 \
--token-file ~/.ha_token --config /config --per-template 5
The test case file (YAML) contains questions with the expected topic, and data with a question and
the expected facts. The format is described in README.md. Keep it outside the repository,
because it contains your home's data.
A general small model recognizes topics well, but it can be fine-tuned on your own home's data. This is done with a separate set of scripts that will move to a separate repository in the future. Training requires a graphics card (e.g. an RTX 4090), and after fine-tuning the model runs in Ollama like any other. Steps:
router.yaml;After the topic list changes, the fine-tuned model has to be fine-tuned again.
Lessons from the first attempt: Gemma 4 E2B fine-tuned on template sentences alone recognized them 99% of the time, but on free-form questions from the log it did worse than plain Gemma 4 E4B (88/96 versus 93/96). It learned the style of the templates, not the language. That is why the second run combines templates with paraphrases written by a large model (Gemma 4 26B): natural questions with names, rooms and Whisper errors. The base is E4B.
router.yaml, trimmed to the person and day.
With only_exposed: true these are exclusively entities exposed to Assist.fetch only calls services that
only return data. Control goes exclusively through the control: true topic and HA
intents, which check whether entities are exposed. Uncertain commands, locks, gates and devices in
confirm_entities require confirmation.