Downloads · 30 days
24
5% of all-time downloads
lagna360/yo-router-270m
yo-router-270m is a text generation model from lagna360. Use it when you need the model to write or continue text. It is set up for mlx. The card lists the license as gemma.
A 270M-parameter tool router for macOS. Takes one plain-English sentence, emits one function call. That is the whole job — it never writes prose, never sees command output, and never chooses more than one tool.
Downloads · 30 days
24
5% of all-time downloads
All-time downloads
508
Public
Parameters
268M
570 MB on disk
Likes
0
Public
Click a slice to open those files.
.safetensors536 MB · 94%
From the Hugging Face model README
A 270M-parameter tool router for macOS. Takes one plain-English sentence, emits one function call. That is the whole job — it never writes prose, never sees command output, and never chooses more than one tool.
It is a LoRA fine-tune of google/functiongemma-270m-it,
fused back into full weights. Its 10 tool declarations are baked into the weights, so the
prompt does not carry them: 33 prompt tokens instead of 655, a 20x reduction.
| tool accuracy | arg accuracy | prompt tokens | |
|---|---|---|---|
gemma-3-270m-it, no fine-tune | 11.2% | 4.8% | 657 |
functiongemma-270m-it, no fine-tune | 56.2% | 33.9% | 655 |
| this model (baked) | 95.2% | 82.3% | 33 |
| sibling, declarations in prompt | 96.4% | 90.3% | 655 |
Gemma is provided under and subject to the Gemma Terms of Use found at ai.google.dev/gemma/terms. These files have been modified — see Licence below. This is a Model Derivative, not Google's original FunctionGemma.
$ yo whats eating my disk
→ disk_usage(path='~')
Downloads is the biggest folder in /Users/lagna360 at 47 GB — that's 31% of everything there.
[disk_usage · 33 prompt tok · 0.25s]
The model is one stage of a three-stage pipeline:
utterance → ROUTER (this model) → picks 1 tool + typed args
→ EXECUTOR (argv allowlist, never a shell) → structured data
→ RENDERER (templates) → one plain-English sentence
Because the model only classifies and extracts, numbers in the output are always exact and a filename can never become an instruction.
All are read-only macOS system queries.
| tool | args | what it answers |
|---|---|---|
disk_usage | path | which folders are eating space |
storage_summary | – | how much space is left |
largest_files | path | biggest individual files |
find_files | name, path | find a file by name |
top_processes | sort_by (cpu|mem) | what's hogging CPU/RAM |
network_listeners | port | what's using a port |
network_info | – | IP, wifi SSID, gateway |
battery_status | – | charge level, plugged in? |
system_info | – | macOS version, host, user, uptime |
datetime | – | current time and date |
Requires Apple Silicon and mlx-lm.
yo CLI (what these weights are built for)pip install yo-macos
yo pull yo-router-270m # downloads this repo into ~/.cache/yo/models/
yo whats eating my disk
The repo carries four files the CLI reads beyond the weights themselves:
yo-model.json (declares prompt: baked, so the client knows not to send
declarations), MANIFEST.json (sha256 + size for every runtime file, checked after
download), and NOTICE + LICENSE, which put the Gemma Terms on disk next to the
weights.
from mlx_lm import load, generate
model, tok = load("lagna360/yo-router-270m")
# The baked prompt shape. Note: NO tools= argument — that is the point.
msgs = [
{"role": "developer",
"content": "You are a model that can do function calling with the following functions"},
{"role": "user", "content": "whats eating my disk"},
]
prompt = tok.apply_chat_template(msgs, add_generation_prompt=True, tokenize=False)
print(generate(model, tok, prompt=prompt, max_tokens=96, verbose=False))
# <start_function_call>call:disk_usage{path:<escape>~<escape>}<end_function_call>
The prompt shape is load-bearing. The developer-role trigger string above is exactly what the
model was trained on. Change it, or pass a tools= list, and accuracy degrades — the model was
never shown declarations during training.
Output format:
no args: <start_function_call>call:datetime{}<end_function_call>
with args: <start_function_call>call:find_files{name:<escape>invoice<escape>}<end_function_call>
Arguments are key:<escape>value<escape>, comma-separated, keys sorted. Use greedy decoding —
every number reported here is from greedy decoding and is deterministic.
The full CLI, the parser for this format, and the executor are at github.com/lagna360/yo.
This is the exact configuration that produced these weights. It is also the recommended starting
point for pointing the same pipeline at a different domain — see RECIPE.md in the repo.
base: google/functiongemma-270m-it
fine_tune_type: lora
rank: 8
scale: 16.0 # alpha = 2 x rank
dropout: 0.0
learning_rate: 5e-5
optimizer: adam
num_layers: 16
targets: [q_proj, k_proj, v_proj, o_proj, gate_proj, down_proj, up_proj]
epochs: 3 # = 518 iters at this dataset/batch size
batch_size: 16
max_seq_length: 160
mask_prompt: true # loss on the completion only
grad_checkpoint: true
seed: 17
Trained with mlx_lm.lora, then mlx_lm.fuse to merge the adapter into the base weights.
Each of these is the difference between working and zero, not a tunable trade-off:
| cliff | wrong choice | result |
|---|---|---|
| base model | gemma-3-270m-it | 0% — no <start_function_call> control tokens, so they fragment into BPE pieces the model never learns to emit |
| adapter size | rank 64, lr 1e-4 | 0% — diverges, stops emitting calls entirely |
| argument supervision | omit args from training data | 0% arg accuracy (tool selection unaffected) |
Everything else is a plateau.
249 hand-written test cases across 10 tools, written by hand and independently of the training-data generator. The generator hard-fails if any training utterance exactly matches a test utterance — it caught one real collision during development.
Greedy decoding, so every accuracy figure is deterministic. Latency varies ~7% run to run.
| metric | value |
|---|---|
| tool accuracy | 95.2% (237/249) |
| argument accuracy | 82.3% (over the 62 arg-bearing cases) |
| no-call rate | 0.0% |
| hallucinated tool name | 1.6% |
| mean prompt tokens | 33 |
| latency p50 / p95 | 0.238s / 0.277s (M1 Pro) |
Argument accuracy is graded by loose containment, not exact match.
56.2% → 95.2%. Paired exact McNemar against the un-tuned functiongemma-270m-it:
p = 5.38e-28.
Much of what fine-tuning does here is suppression, not new knowledge. The base model's instinct is to apologise and explain; refusals go 14.9% → 0.0%.
The sibling run trained identically but with the 10 declarations in the prompt scores 96.4% tool / 90.3% arg at 655 prompt tokens. Baking costs 1.2 points of tool accuracy — McNemar p = 0.549, not significant at n=249 — and about 8 points of argument accuracy.
"Not significant" is not "no difference". With few discordant pairs the test is underpowered; both a real small cost and true equivalence remain consistent with this data.
The argument cost is the honest one: the declarations describe the argument schema, so deleting them hurts extraction more than classification.
The 12 remaining failures are semantically adjacent pairs — tools whose descriptions overlap:
storage_summary vs disk_usage "space left" vs "what's using space"
find_files vs largest_files a named file vs big files
top_processes vs system_info "what's slow" vs "what is this machine"
Plus 4 cases (1.6%) where it invents a name that is not in the menu at all (battery,
disk_split, loud_top_percent, disk_top_processes).
In scope:
Out of scope:
These are real and you should design around them.
none / no-tool option. Say "hi" and it will force a
tool call onto it. Fine-tuning drove the no-call rate to 0.0%, which is a feature for in-domain
utterances and a bug for everything else. Fixing this means adding a chitchat tool to the
schema and retraining.The model's output is untrusted input and the surrounding system treats it as such. In the
reference implementation the model can only name a tool; it cannot reach anything the executor
does not already implement. The executor builds argv arrays and never invokes a shell, paths are
canonicalised against a denylist, and rendering is done by templates rather than by the model. If
you build on these weights, do the same — do not pipe the output into a shell.
8m 14s on a single M1 Pro laptop for this run; ~5.2 GPU-hours across all 24 runs in the study. Inference is ~0.24s per query on the same laptop, entirely local — no network, no API call.
These weights are NOT MIT-licensed and cannot be relicensed.
Gemma is provided under and subject to the Gemma Terms of Use found at ai.google.dev/gemma/terms
Modification notice, as required by Section 3.1 of those terms: these files have been
modified. The base weights of google/functiongemma-270m-it were LoRA fine-tuned (rank 8,
alpha 16, lr 5e-5, 3 epochs) on a synthetic macOS tool-routing dataset by Pankaj Upreti, and the
adapter was fused back in. Every weight tensor differs from Google's original. FunctionGemma is
listed in the Appendix to the Gemma Terms of Use, so those terms govern this derivative.
Use restrictions carry forward. Your use of these weights is subject to the restrictions in Section 3.2 of the Agreement, which incorporate the Gemma Prohibited Use Policy by reference. If you redistribute these weights or anything derived from them, you must pass on a copy of the Agreement, this modification notice, and these restrictions.
A full copy of the Gemma Terms of Use ships with this repository as MODEL_LICENSE, and the
required notice as NOTICE.
"Open weights" is not "open source." These weights do not carry an OSI-approved licence, and describing them as open source would be wrong.
The code around them — CLI, tool registry, data generator, eval harness, experiment ladder — is MIT-licensed and lives at github.com/lagna360/yo.
Not affiliated with, endorsed by, or sponsored by Google. "Gemma", "FunctionGemma" and "Google" are trademarks of Google LLC, used here only to identify the base model as the Agreement requires.
@software{upreti2026yo,
author = {Upreti, Pankaj},
title = {yo: fine-tuning a 270M model for macOS tool routing},
year = {2026},
url = {https://github.com/lagna360/yo}
}