--- language: - en license: apache-2.0 base_model: LiquidAI/LFM2.5-350M tags: - smart-home - home-automation - tool-calling - function-calling - iot - unsloth - lora - lfm2 pipeline_tag: text-generation --- # LFM2.5-350M Home Assistant (Stage 2 Stable Release) A purpose-trained smart home automation model fine-tuned from [LiquidAI/LFM2.5-350M](https://huggingface.co/LiquidAI/LFM2.5-350M). This model controls lights, doors, thermostats, TVs, fans, speakers, and home scenes through structured tool calls, with full awareness of current device states. **⚠️ Version Notice:** This is the **Stable Release**, trained exclusively on our robust Stage 2 State-Aware dataset (v13 Final Merge). The experimental multi-stage version is currently still in training and will be released for comparison once stabilized. **👉 Which file should I download?** For the most stable experience right now, download one of the following GGUF files: * `LFM2.5-350M.F16.gguf` * `LFM2.5-350M.Q4_K_M.gguf` (Recommended for most users, best balance of speed and size) * `LFM2.5-350M.Q8_0.gguf` (Highest quality quantization) --- ## What It Does Given a natural language command and the current state of all connected devices, the model outputs the correct tool call — or explains in plain text why no action is needed. It handles: - **Advanced Device Disambiguation** — If you say "Turn off the TV", the model will intelligently resolve which TV you mean by checking if only one TV is connected, checking if you are in a room with a TV, or inferring intent from the state (e.g., if only one TV is currently ON, it turns that one off via a `` trace). - **Media & Music Playback** — "Play Truth In The World By Lucky Dube" → `control_speaker(room='living_room', action='play', media='Truth In The World By Lucky Dube')` - **Direct commands** — "Turn on the bedroom light" → `toggle_lights(room='bedroom', state='on')` - **Already-satisfied detection** — "Turn on the bedroom light" when `bedroom:on` in STATE → "The bedroom light is already on." (no tool call) - **Pronoun resolution** — "Turn off the light" when `current_user_room=kitchen` → `toggle_lights(room='kitchen', state='off')` - **Bulk state-aware actions** — "Turn off what's on" → reads STATE, emits one call per lit room using `` logic. - **Undo / repeat via action log** — "Undo that" + `[RECENT ACTIONS: toggle_lights(bedroom, on)]` → `toggle_lights(room='bedroom', state='off')` - **Multi-device compound commands** — "Lock the front door and turn off the living room light" → uses a rigid reasoning format (`Total: N tool calls required`) to emit parallel tool calls. - **Topology-aware rejection** — "Turn on the garage light" when garage not in connected rooms → `intent_unclear(reason='unsupported_device')` - **Scene activation** — "Movie night" / "Bedtime" / "I'm leaving" → `set_scene(...)` - **Fan control** — "Set the bedroom fan to high" → `control_fan(room='bedroom', state='on', speed='high')` - **Thermostat** — "Make it 72 degrees" → `set_thermostat(temperature=72, mode='heat')` - **Syntactic Action Triggers** — Internal reasoning strictly concludes with `ACTION REQUIRED.` or `ACTION NOT REQUIRED. Text reply only.` to reliably signal structural intent before opening a JSON block. --- ## Tool Schema The model outputs calls from the following 10-tool schema. All tools use exact parameter names. ```python toggle_lights(room: str, state: 'on'|'off') # room: living_room | bedroom | kitchen | bathroom | office | hallway toggle_all_lights(state: 'on'|'off') lock_door(door: str, state: 'lock'|'unlock') # door: front | back | garage | side | bedroom | bathroom | office | kitchen | living_room lock_all_doors(state: 'lock'|'unlock') set_thermostat(temperature: int, mode: 'heat'|'cool'|'auto') # temperature range: 60–80°F set_scene(scene: 'movie_night'|'bedtime'|'morning'|'away'|'party') control_tv(room: str, state: 'on'|'off') # room: living_room | bedroom | office control_fan(room: str, state: 'on'|'off', speed: 'low'|'medium'|'high' = optional) # room: living_room | bedroom | kitchen | office control_speaker(room: str, action: 'play'|'pause'|'stop'|'next'|'previous', media: str = optional) # room: living_room | bedroom | kitchen | office | hallway intent_unclear(reason: 'off_topic'|'incomplete'|'unsupported_device'|'unsupported_feature') ``` --- ## State Format Every user message must be prefixed with a `[STATE:]` block. ```text [STATE: lights={bedroom:on, kitchen:off, living_room:on}, doors={back:locked, front:unlocked}, thermostat=70F/heat, scene=none, tv={bedroom:off, living_room:on}, speaker={kitchen:stopped}, fan={bedroom:on(low)}, current_user_room=kitchen] ``` **Field breakdown:** | Field | Values | Notes | |---|---|---| | `lights` | `room:on\|off` | Only include connected rooms | | `doors` | `door:locked\|unlocked` | Only include connected doors | | `thermostat` | `F/` | e.g. `72F/heat` | | `scene` | scene name or `none` | Active scene or none | | `tv` | `{room:on\|off, ...}` | Dictionary of connected TVs | | `speaker` | `{room:playing\|paused\|stopped, ...}` | Dictionary of connected speakers | | `fan` | `{room:on\|off(speed), ...}` | Dictionary of connected fans | | `current_user_room` | room name or empty | Drives pronoun ("this room") resolution | --- ## Usage ### Minimal inference example ```python from transformers import AutoModelForCausalLM, AutoTokenizer import torch model_id = "OrdenWills/LFM2.5-350M-home-assistant-sft" tokenizer = AutoTokenizer.from_pretrained(model_id) model = AutoModelForCausalLM.from_pretrained( model_id, torch_dtype=torch.float16, device_map="auto", ) SYSTEM_PROMPT = """You are a smart home assistant AI. Use tools to control the home. Output function calls as JSON. TOOLS: toggle_lights(room, state='on'|'off') toggle_all_lights(state='on'|'off') lock_door(door, state='lock'|'unlock') lock_all_doors(state='lock'|'unlock') set_thermostat(temperature=, mode='heat'|'cool'|'auto') set_scene(scene='movie_night'|'bedtime'|'morning'|'away'|'party') control_tv(room, state='on'|'off') control_fan(room, state='on'|'off'[, speed='low'|'medium'|'high']) control_speaker(room, action='play'|'pause'|'stop'|'next'|'previous'[, media='']) intent_unclear(reason='off_topic'|'incomplete'|'unsupported_device'|'unsupported_feature') CONNECTED ROOMS (lights): living_room, bedroom, kitchen, bathroom, office, hallway CONNECTED DOORS: front, back, garage CONNECTED TVs: living_room, bedroom CONNECTED SPEAKERS: living_room CONNECTED FANS: bedroom STATE RULES: [STATE:] shows all current device states. State already matches request → plain text reply, NO tool call. Only rooms listed under CONNECTED TVs/SPEAKERS/FANS have those devices. Requesting a device in an unlisted room → intent_unclear(unsupported_device). TV / SPEAKER / FAN RESOLUTION when user says 'the TV'/'the fan'/'the speaker': 1. Exactly one connected → use that room automatically. 2. Multiple connected + current_user_room has device → use current_user_room. 3. Multiple connected + exactly ONE is in the eligible state for the action (e.g. only one TV is on and user says 'turn off the TV') → infer that room. 4. Multiple connected + ambiguous (rule 2 & 3 don't apply) → intent_unclear(incomplete). LIGHT / DOOR RESOLUTION: current_user_room set + connected → use current_user_room. current_user_room set + NOT connected → intent_unclear(unsupported_device). current_user_room empty → intent_unclear(incomplete). [RECENT ACTIONS:] → transaction log, newest entry first. Format: (X mins ago) [call1, call2, ...] -> summary. Each [...] bracket is ONE command the user previously issued. For 'undo'/'reverse'/'back': invert ONLY the most recent transaction (the FIRST [...] block). Older transactions are always ignored. For pronouns ('it'/'them'): refer to the device(s) in the first [...] block. Do NOT use recent actions to infer which room 'the light' or 'the door' refers to when current_user_room is explicitly set — current_user_room wins. For 'all lights' / 'all doors': use toggle_all_lights / lock_all_doors regardless of current_user_room. SYNONYMS: 'open'='unlock'; 'close'/'shut'='lock'; 'skip'='next'; 'back'='previous' (for speaker track navigation), but can also mean 'undo' for reverting device states based on [RECENT ACTIONS]. 'continue'/'resume'/'on the music'='play'; 'play ' = action='play' + media=''. Relative state clauses ('the light that is on', 'the door that is locked') override current_user_room — check STATE and act on the matching device.""" state = "[STATE: lights={bathroom:off, bedroom:off, hallway:off, kitchen:off, living_room:on, office:off}, doors={back:locked, front:locked, garage:unlocked}, thermostat=70F/heat, scene=none, tv={bedroom:off, living_room:on}, speaker={living_room:stopped}, fan={bedroom:off(medium)}, current_user_room=kitchen]" messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": f"{state}\nTurn off the TV."}, ] input_ids = tokenizer.apply_chat_template( messages, tokenize=True, add_generation_prompt=True, return_tensors="pt" ).to(model.device) with torch.no_grad(): output = model.generate( input_ids, max_new_tokens=256, temperature=0.1, # low temp for deterministic tool calls do_sample=True, pad_token_id=tokenizer.eos_token_id, ) response = tokenizer.decode( output[0][input_ids.shape[-1]:], skip_special_tokens=True ) print(response) # Expected behavior: The model will generate a trace noting that only the living_room TV is currently ON, infer the user wants to turn off the living_room TV, conclude with ACTION REQUIRED, and emit the tool call. ``` ### GGUF / Ollama ```bash # Pull the recommended q4_k_m quantization ollama run hf.co/OrdenWills/LFM2.5-350M-home-assistant-sft:Q4_K_M # Or use the higher precision q8_0 version ollama run hf.co/OrdenWills/LFM2.5-350M-home-assistant-sft:Q8_0 ``` --- ## Training Details (Stage 2 Stable) This release was fine-tuned directly on a 70,000-example state-aware synthetic dataset ("v13 Final Merge"). | Parameter | Value | |---|---| | Base model | LiquidAI/LFM2.5-350M | | Dataset Size | 70,000 examples | | Categories | 33 distinct instruction schemas | | Think Traces | Extensive (Present in majority of complex scenarios) | | Hardware | Kaggle T4 (16 GB) | ### Training Categories | Category | Description | Think Trace? | |---|---|---| | `already_satisfied` / `state_grounding` | Graceful replies if the device is already in the requested state. Forces reading of state arrays. | **Yes** / No | | `action_required` / `relative_clause` | Standard explicit device triggers and relative logic ("turn off the light that is on"). | **Yes** / No | | `user_room_lights/doors` | Resolves "the light" / "the door" based on `current_user_room`. | **Yes** | | `bulk_plus_local_door` | Complex compound logic mixing global state-aware commands with implicit local commands. | **Yes** | | `action_log_*` / `them_plurality` | "Undo", "repeat", "same for bedroom", and resolving plural pronouns using `[RECENT ACTIONS:]`. | **Yes** | | `scenes/thermostat` | Standard NLP triggering for scenes and temperature bounds. | No | | `rejections/missing` | "Make me coffee", unconnected rooms, and resolving `incomplete` vs `unsupported_device`. | **Yes** / No | | `compound_count` | Parallel tool calling with forced sub-action counting before generation. | **Yes** | | `status_queries` | "Are the lights on?" / "What is the thermostat set to?" plain text answers. | **Yes** | | `tv/speaker/fan commands`| Multi-device disambiguation logic (State inference via `Rule 3` vs implicit fallback). | **Yes**| | `local_media_commands` | Parsing local track titles via the `media` parameter for specific song playback. | **Yes** | **Selective Thinking (`...`):** For complex tasks (e.g., bulk state updates, parsing action logs, determining which TV the user meant based on state arrays, counting compound actions), the model is trained to output a reasoning trace before making the tool call. For direct explicit commands, it skips thinking entirely for speed. All thinking traces now end with explicitly formulated `ACTION REQUIRED.` triggers. --- ## Known Limitations - **Temperature range is fixed at 60–80°F.** Requests outside this range produce a plain-text explanation, not a tool call. - **No brightness or colour control.** Dimming and colour-change requests correctly trigger `intent_unclear(reason='unsupported_feature')`. This is by design — the connected lights only support on/off. - **Local music library only.** The speaker control `media` parameter maps to specific tracks from a bounded internal list of artists and songs. Out-of-domain conversational queries will likely trigger `intent_unclear(reason='off_topic')`. - **English only.** All training data is English. Performance in other languages is untested. - **State must be accurate.** The model trusts `[STATE:]` completely. If your app sends stale state, the model may incorrectly say a device is already in the requested state or infer the wrong device during disambiguation. --- ## Citation ```bibtex @misc{lfm2-home-assistant-2025, author = {OrdenWills}, title = {LFM2.5-350M Home Assistant: A Purpose-Trained Smart Home Automation Model}, year = {2025}, publisher = {HuggingFace}, url = {https://huggingface.co/OrdenWills/LFM2.5-350M-home-assistant-sft} } ``` --- ## Acknowledgements - [LiquidAI](https://www.liquid.ai/) for the incredible and highly capable LFM2.5-350M base model. - [Unsloth](https://github.com/unslothai/unsloth) for the fine-tuning framework. ```