Ben wanted his desk lamp to turn blue when he was ready to play a game. He could already do that with an app, which made the project difficult to explain to sensible people. But he spent his working day writing software and kept reading about enormous AI models. He wanted to make something small enough to understand himself. A lamp seemed unlikely to demand a data center. A friend was bringing smart glasses to their game night, and Ben wanted to see what they could show.
He began with a lamp he had already added to Home Assistant, software that could bring supported home devices into one system. Its dashboard could change the lamp's color. That gave him something working to compare his own program with. Putting a device on Wi-Fi did not automatically give his code permission or a usable way to control it. The matching integration still had to support the device.
Ben chose a smaller job than conversation. His model would read a short request and pick a label for warm light, blue light or switching off. That kind of model was a classifier. Ben limited it to these labels so he could inspect every result. He wrote the training code himself and started a file of labelled phrases.
He gave it twelve examples, with four for each label. A phrase such as "make lamp blue" went with blue, while "warm reading light" went with warm. The labels were answers Ben supplied during training. The program counted the words associated with each group and used those counts to estimate which label suited a new request. He could inspect the counts instead of staring at a huge file of unfamiliar numbers.
The method was called multinomial naive Bayes. In this small version, it treated a sentence as a collection of words and ignored their order. Training calculated the numbers from the examples. Prediction used those numbers on another phrase. This was machine learning, but it was not a small version of ChatGPT. Ben could run the counting and scoring on his laptop's CPU without a graphics card doing the work. He had checked a guide to memory and computing needs for local AI before choosing this model for his laptop.
He first made the program print a proposal in the terminal. A small web page he wrote showed the same proposal with a Confirm button. "Blue lamp please" produced the blue label. Nothing happened to the lamp because he had not connected the output to a device action. He liked that separation when his next request, "do not turn the lamp off," produced off. The word he cared about most had failed to change the result.
Ben looked at the words reaching the scorer. Lamp and off appeared in the training data, while not did not. The model ignored that unknown word and kept picking its familiar label. He added a separate rule to stop requests containing explicit negation and ask for a person to review them. That did not teach the model to understand negatives. He kept confirmation in the surrounding app for every proposal that passed the checks.
Then he typed "warm blue light." His program chose blue, though Ben had not decided what that combination should mean. He stopped requests naming both colors instead of letting the model settle it for him. "Pancakes tomorrow" also got a raw label, blue, because the classifier still had to choose among its known groups. A check for requests with no known words blocked that proposal.
He kept these awkward requests beside the ordinary ones. There were six checks, each different from the training phrases. Passing them gave him a small set of behaviors he could repeat after an edit. It did not tell him how well the model would handle every sentence a guest might try. The extra rules covered a few mistakes he knew about, and he could still find others.
For requests he approved, ordinary Python code picked the device action from a fixed table. The model's label could select an entry, but could not invent a device name or a network address. Ben's table contained actions for his desk lamp. He could read what the program intended to send before confirming it.
His first attempt to use the small app from his phone went nowhere. He had copied a laptop address containing localhost into the phone's browser. On the phone, that name referred to the phone itself. Ben had been asking it to find a server that was running on another machine. He changed the address to reach his laptop over the home network and checked that the app was listening where the phone could reach it.
He kept that app limited to his home network, with a login for its controls. The laptop sent approved requests to Home Assistant's authenticated API, a defined way for one program to ask another to do something. The access token belonged in that connection code, outside his training file. Wi-Fi carried the request between machines.
After another edit, the Home Assistant dashboard said the lamp was on. The actual lamp was dark. Ben nearly added more training phrases, then looked at the proposed label and the request his code had sent. His first request had only changed Home Assistant's stored record for the lamp. The API documentation explained that this did not send an instruction to the physical device.
He changed the connection code to call the lamp action, light.turn_on, through the integration. This time the lamp turned blue. Ben checked the returned information against the state reported by Home Assistant and looked at the lamp itself. More training data would not have repaired the wrong API call.
He added the input phrase and selected label to his log, followed by the mapped action and reported device state. Blue appeared beside both attempts, but the requests did different things. He put the failed request beside the working one and kept both entries.
Ben stopped the program and added "reading time" to his warm-light training examples. Starting it again recalculated the word counts. Typing a request had not changed his training file, and he checked that the file stayed the same during prediction. He needed to edit the examples and train again to change those learned numbers.
His friend arrived with the glasses and put a bag of snacks on the desk. He asked whether the lamp assistant could also write emails. Ben showed him the three labels. A local language model could handle a wider range of text, and Home Assistant had an Ollama integration for supported models. That approach used a model someone else had already trained. Ben had chosen this tiny classifier so he could write and inspect the learning process himself.
He checked the other meaning of local too. A device whose integration needed a company's cloud would still need that connection, even if the model ran in his apartment. For his test, he kept the home Wi-Fi working while disconnecting the internet connection. His locally controlled lamp still responded. The classifier made no hosted model request.
The glasses his friend had brought were Brilliant Labs Frame. Their development tools could let a program on the laptop send text to the display over Bluetooth Low Energy. Ben borrowed them for a small status display while keeping the model on the laptop. The phone-to-laptop link used the home network; the glasses used a different wireless connection. These particular glasses offered a programming interface, which he would have to check separately on any other pair.
He displayed the lamp's reported state where he could see it through the glasses. It was a rather elaborate way to find out that the blue lamp beside him was blue. When the Bluetooth connection dropped, the last message stopped being a fresh update. He added a last-updated time to each message and refreshed the status after reconnecting. There was still no camera recognition or understanding of the room involved.
Ben watched the text arrive and handed them back for his friend to try. He could change the message in his own code and see the result. For this game-night experiment, a small line of status text was enough to keep him entertained.
Ben left the troublesome phrases in his checks file and put the glasses down beside the keyboard. He had spent much longer getting ready for the game than he intended. His friend reached for the ordinary lamp control, and Ben let him. They could play with the model again after they had actually played the game.
The downloadable local AI example reproduces the starting classifier and terminal proposals from this story. Run python3 toy_intent.py inside the extracted folder to see all six checks, including the raw off prediction for the negated request. The example uses Python's standard library and makes no network requests. It does not operate a lamp or glasses. The hardware scenes are fictional, while the example's inputs and printed results can be inspected and rerun.