加载中…
加载中…
SHIVS turns a short speech transcript, such as "switch the bluetooth on and open whatsapp", into a compact JSON array of Android phone actions. It is a LoRA fine-tune of Qwen2.5-0.5B-Instruct, shipped as a Q4_K_M GGUF (about 380 MB) and meant to run on the phone with a GBNF grammar that makes invalid output impossible.
It is the language half of an on-device voice assistant. The speech half is whisper.cpp (base.en is included in this repository for convenience).
Read the limitations first. The model itself is accurate on text. The measured weak point is speech-to-text on a phone microphone, which mishears names. End-to-end accuracy on live speech was 57 to 78% in tests with one speaker and one phone, and has not been measured for other voices, accents or phones. See Limitations.
| File | Size | Purpose |
|---|---|---|
shivs-q4_k_m.gguf |
398 MB | The intent model (Qwen2.5-0.5B-Instruct + LoRA, merged, Q4_K_M) |
actions.gbnf |
4 KB | Grammar that constrains the output to the closed action schema |
ggml-base.en.bin |
148 MB | whisper.cpp base.en speech model, unchanged from the original release (MIT), included so an app can fetch everything from one place |
manifest.json |
small | File names, sizes and SHA-256 for each file; an app uses it to verify downloads |
SHA-256: shivs-q4_k_m.gguf f71238c9353949f8d4ecdb79b04314ef59fa50d15160d7124f5856be4ed6f339; ggml-base.en.bin a03779c86df3323075f5e796cb2ce5029f00ec8869eee3fdfb897afe36c6d002.
Input: one transcript, already stripped of any wake word, for example Turn on the Bluetooth and open WhatsApp.
Output: a JSON array of 1 to 3 actions, no spaces, fixed key order:
[{"action":"bluetooth_toggle","parameters":{"state":"on"}},{"action":"open_app","parameters":{"app_name":"WhatsApp"}}]
System prompt (must match exactly):
You are SHIVS. Convert voice commands into a JSON array of phone actions. Output JSON only.
Chat format: ChatML (<|im_start|>system ... <|im_end|><|im_start|>user ... <|im_end|><|im_start|>assistant).
| Group | Actions and parameters |
|---|---|
Toggles (state: on/off) |
bluetooth_toggle, wifi_toggle, flashlight_toggle, airplane_mode_toggle, do_not_disturb_toggle, mobile_data_toggle, hotspot_toggle, battery_saver_toggle, location_toggle, auto_rotate_toggle, speaker_toggle |
| Sound and screen | ringer_mode (mode: normal/vibrate/silent), set_volume (level 0-100, or direction up/down/mute with optional amount), set_brightness (level 0-100, or direction up/down with optional amount) |
| Time | set_alarm (time as HH:MM, optional label), set_timer (hours, minutes, seconds) |
| Apps | open_app (app_name) |
| Calls | call_contact (contact_name), call_number (number), video_call (contact_name), end_call |
| Messages | send_message (contact_name, body), send_email (contact_name, optional subject, body), read_messages (optional contact_name), share_location (contact_name) |
| System | take_screenshot, lock_screen |
| Dialogue | clarify (question), unsupported (reason) |
clarify and unsupported are always the only action in the array. The schema is closed: anything else is rejected by the grammar.
The model only chooses the action and its parameters. The app decides how to carry each one out. Android does not let a normal app silently switch Wi-Fi, mobile data, Bluetooth, airplane mode, location, battery saver or the hotspot, so an app typically opens the right settings screen for those.
llama-cli -m shivs-q4_k_m.gguf --grammar-file actions.gbnf --temp 0 -n 160 \
-sys "You are SHIVS. Convert voice commands into a JSON array of phone actions. Output JSON only." \
-p "switch the bluetooth on and open whatsapp"
from llama_cpp import Llama, LlamaGrammar
llm = Llama(model_path="shivs-q4_k_m.gguf", n_ctx=512, verbose=False)
grammar = LlamaGrammar.from_file("actions.gbnf")
SYSTEM = "You are SHIVS. Convert voice commands into a JSON array of phone actions. Output JSON only."
def parse(text):
out = llm.create_chat_completion(
messages=[{"role": "system", "content": SYSTEM}, {"role": "user", "content": text}],
temperature=0.0, max_tokens=160, grammar=grammar)
return out["choices"][0]["message"]["content"]
print(parse("set an alarm for 6 15 pm and turn on wifi"))
Always use the grammar. Without it the model sometimes invents action names (set_volume_level, battery_toggle) or enum values. With it, the output is always valid JSON of the schema.
Speed tip: the system prompt and chat header are identical on every call. Decode them once and keep them in the KV cache; on the test phone this cut the model step from about 1.5 s to about 1.0 s.
microphone, then whisper.cpp (base.en), then remove a leading wake word, then fix spoken digits (for example "double 5" to "55"), then this model with the grammar, then validate the JSON against the schema, then run the action.
A prompt that lists the user's contact and app names, given to whisper as its initial prompt, improved live accuracy noticeably in our tests (see below). For commands that contain digits, transcribing a second time without that prompt avoids letters appearing inside phone numbers.
Qwen/Qwen2.5-0.5B-Instruct (Apache-2.0).shivs_train.ipynb in the project for the exact run."Strict" means the whole JSON array must match exactly. "Name-tolerant" also accepts a contact or app name with the same first letter and at least 0.65 string similarity, which forgives speech-recognition spelling errors. Both are always shown; strict is the headline number.
| Set | Rows | Strict exact |
|---|---|---|
| Template test split, 16-bit model | 1,359 | 99.63% |
| Same split, Q4_K_M, no grammar | 1,359 | 99.56% |
| Local split, Q4_K_M, with grammar | 1,398 | 99.64% (different split, not comparable to the rows above) |
JSON parse rate 100% and schema validity 100% with the grammar. Because this is the ship model, these numbers measure the model on phrasing it has seen the templates for.
| Set | Whisper | Rows | Strict | Name-tolerant |
|---|---|---|---|---|
| Holdout (recorded once, scored once) | small.en | 58 | 86.2% | 91.4% |
| Holdout | tiny.en | 58 | 74.1% | 79.3% |
| Development set (tuned against) | small.en | 101 | 87.1% | 93.1% |
Q4_K_M with the grammar was within about two rows of the 16-bit model on every real-speech set (a difference at the level of noise for sets this size).
All with whisper.cpp base.en, this model with the grammar, on one phone, one speaker, one room. 45 fresh lines per set.
| Set | Condition | Strict | Name-tolerant |
|---|---|---|---|
| A | no whisper prompt | 64.4% | 68.9% |
| B (same audio replayed) | no whisper prompt | 57.8% | 64.4% |
| B | whisper prompt with contact and app names | 77.8% | 82.2% |
| C | whisper prompt built from the speaker's real contacts | 71.1% | 77.8% |
By type of line on set C: commands without a name about 92% right (24 of 26); commands with a contact name only 4 of 11, even with the name in the prompt; combined commands (two actions) 2 of 5.
What the errors are. The model copies whatever name whisper wrote, so the failure is the transcript ("Tarchan please" for "torch on please", "Carl Anita" for "call Anita"). Fed the text that was meant, the model got 12 of 13 two-action lines right. A larger whisper model (small.en) did not help on a fresh set.
Speed and cost on the test phone (CPU only): about 4.5 s per command in the foreground (speech-to-text 2.3 to 3.9 s, this model 1.7 to 2.1 s), about 1.1 GB of memory for the whole pipeline, about 1.4 mAh per command including the screen, 8 to 18 s to load the models at start.
base.en are English. Indian English is the main accent tested; other accents are untested.unsupported, but were tested on a small set only.Intended: converting short, clear voice commands into a fixed set of phone actions, inside an app that validates the JSON, asks before ambiguous or risky actions, and runs on the device.
Not intended, and not suitable without more work:
unsupported.This model runs entirely on the phone: speech, transcript and output stay on the device. The reference app needs microphone, contacts, messages and location permissions for the actions to work, and should explain this to users.
ggml-base.en.bin: converted OpenAI Whisper weights for whisper.cpp, MIT.The schema (actions.json), the grammar generator, the data generator and the evaluation scripts live in the SHIVS project repository, not here. The grammar in this repository was generated from that schema; the model, grammar and app must be updated together.
Maintainer: shivap1712 on Hugging Face (use the repository's Community tab for questions and reports).