Text Classification
Transformers
Safetensors
GGUF
English
llama
text-generation
tiny-model
from-scratch
router
tool-use
intent-classification
agentic
text-embeddings-inference
Instructions to use textilelabs/Loom-Router-1 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use textilelabs/Loom-Router-1 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-classification", model="textilelabs/Loom-Router-1")# Load model directly from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("textilelabs/Loom-Router-1") model = AutoModelForCausalLM.from_pretrained("textilelabs/Loom-Router-1", device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use textilelabs/Loom-Router-1 with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf textilelabs/Loom-Router-1:F16 # Run inference directly in the terminal: llama cli -hf textilelabs/Loom-Router-1:F16
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf textilelabs/Loom-Router-1:F16 # Run inference directly in the terminal: llama cli -hf textilelabs/Loom-Router-1:F16
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf textilelabs/Loom-Router-1:F16 # Run inference directly in the terminal: ./llama-cli -hf textilelabs/Loom-Router-1:F16
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf textilelabs/Loom-Router-1:F16 # Run inference directly in the terminal: ./build/bin/llama-cli -hf textilelabs/Loom-Router-1:F16
Use Docker
docker model run hf.co/textilelabs/Loom-Router-1:F16
- LM Studio
- Jan
- Ollama
How to use textilelabs/Loom-Router-1 with Ollama:
ollama run hf.co/textilelabs/Loom-Router-1:F16
- Unsloth Desktop
- Docker Model Runner
How to use textilelabs/Loom-Router-1 with Docker Model Runner:
docker model run hf.co/textilelabs/Loom-Router-1:F16
- Lemonade
How to use textilelabs/Loom-Router-1 with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull textilelabs/Loom-Router-1:F16
Run and chat with the model
lemonade run user.Loom-Router-1-F16
List all available models
lemonade list
- Atomic Chat
File size: 8,723 Bytes
ce3ed47 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 | ---
license: mit
language: en
library_name: transformers
pipeline_tag: text-classification
tags:
- tiny-model
- llama
- from-scratch
- router
- tool-use
- intent-classification
- agentic
- gguf
---
<div align="center">
<img src="banner.jpg" alt="Loom Router 1" width="520">
</div>
# Loom Router 1
<img src="logo.jpg" alt="" width="20" height="20" style="border-radius:4px;vertical-align:middle;margin-right:6px;"> **1.4M parameters Β· 2.8MB Β· Textile Labs**
**Give it a user message. It tells you which tool should handle it, in one token.**
That's the whole product. Your harness passes the user's original text to whichever tool it
names β the model never rewrites your input, so nothing can be copied wrong or malformed.
```
"whats the weather in leeds tomorrow" β <route:weather>
"remind me to call mum at 6" β <route:reminder>
"whats my sisters name" β <route:unknowable>
```
**86.5% accuracy on 2,969 real held-out human utterances**, across 17 routes. Random
guessing scores 5.9%.
It is trained **from scratch** β randomly initialised weights, trained end to end. Nothing
is fine-tuned from a pretrained base. Comparable open routers we looked at are considerably
larger and fine-tuned from pretrained checkpoints; we make no claim to be the smallest of
its kind.
## What it's for
A **first stage in front of a bigger model or an agent loop.** Deciding which tool to reach
for is a cheap decision that does not need a large model β but people usually pay for a
large model to make it. This does it in one token, on a CPU, in a 2.8MB file.
Concretely: use it to pick the tool, then hand the user's original text to that tool. Or use
it to decide whether you need to call a large model at all.
**It is not a chat model.** It has no conversational output and cannot introduce itself. It
answers with a route and nothing else.
## The routes
**Tools (13)** β `search` `calc` `time` `weather` `calendar` `reminder` `email` `notes`
`maps` `translate` `convert` `define` `music`
**Control (4)** β `answer` `clarify` `unknowable` `refuse`
## The Loom philosophy, as routes
Every Loom model is built on the same bet: at small sizes, **knowing your limits is more
achievable than knowing things β and more useful.** In a generative model that means
saying "I don't know". In a router it becomes something sharper β a decision:
| route | what it means |
|---|---|
| `answer` | no tool needed. Don't reach for one reflexively. |
| `clarify` | the request is ambiguous. Don't guess β ask. |
| `unknowable` | this depends on something only the user knows. No tool can fix that. |
| `refuse` | this shouldn't be done. |
A router that only answers *"which tool?"* has assumed a tool is always the answer. In an
agent loop that assumption is the expensive one: sending *"what's my sister's name"* to a
search tool burns a call and returns a confident wrong answer. `answer` and `clarify` are
also what let a loop **terminate** instead of spinning.
So this card publishes the **false-tool-call rate**: how often it sends a request to a
tool that cannot possibly help. Ours is **20.2%**, and the honest reading of that is below.
## Measured
Evaluated one bare prompt at a time, the way the model is actually used.
**Overall 86.5%** Β· tools **89.3%** Β· control **71.2%**
| route | n | recall | | route | n | recall |
|---|---:|---:|---|---|---:|---:|
| `translate` | 21 | 100.0% | | `email` | 202 | 87.6% |
| `notes` | 163 | 95.1% | | `reminder` | 134 | 87.3% |
| `weather` | 113 | 93.8% | | `search` | 599 | 86.8% |
| `music` | 368 | 93.8% | | `define` | 104 | 84.6% |
| `answer` | 335 | 93.7% | | `calc` | 32 | 71.9% |
| `convert` | 64 | 90.6% | | `refuse` | 36 | 25.0% |
| `time` | 123 | 89.4% | | `clarify` | 41 | 12.2% |
| `calendar` | 342 | 88.9% | | `unknowable` | 54 | 7.4% |
| `maps` | 238 | 88.7% | | | | |
## Independent test β SNIPS
The 86.5% above is a held-out split of the same corpora used for training. To check it
generalises beyond that, it was also run against **SNIPS**, a dataset that played no part
in training at all.
**73.8% on 500 unseen utterances** (5 intents with an unambiguous mapping):
| SNIPS intent | β route | score |
|---|---|---:|
| `AddToPlaylist` | `music` | 91% |
| `PlayMusic` | `music` | 87% |
| `SearchScreeningEvent` | `search` | 80% |
| `SearchCreativeWork` | `search` | 68% |
| `GetWeather` | `weather` | 43% |
SNIPS' `BookRestaurant` and `RateBook` have no defensible route in this ontology, so they
were left unscored rather than graded against a debatable label.
The drop from 86.5% to 73.8% is the honest cost of moving to a different data distribution.
**`GetWeather` at 43% is the instructive failure**: SNIPS asks about weather without using
the word β *"Is there a storm now in NC?"*, *"humidity in Olvey New Hampshire"*, *"Will
there be fogβ¦"*. Those go to `search`. The model keys on the vocabulary it was trained on,
not on a general concept of weather. If your domain uses terms outside everyday assistant
phrasing, expect the same and plan to retrain with them included.
## Read this before relying on it
**Tool routing works. The honesty routes largely do not.** `clarify` 12.2%, `unknowable`
7.4%, `refuse` 25.0%. Treat a tool prediction as a strong signal and a control prediction
as a weak hint.
The cause is understood and worth stating plainly. On synthetic data those routes scored
~76%, because *"my"* and *"I"* were reliable cues. Real assistant traffic is full of *"my
calendar"*, *"my alarms"*, *"remind me"* β so the cue stopped being a cue. The real
distinction is whether the referent **lives in a tool's data or only in the user's head**,
which is a subtler thing to learn. Tripling the control training data made it *worse*, so
it is not a volume problem.
`calc` (71.9%) has only 32 validation examples; that figure is noisy.
## Usage β Ollama
```bash
ollama run hf.co/textilelabs/Loom-Router-1 "whats the weather in leeds tomorrow"
# <route:weather>
```
Ollama reads the `template` and `params` files in this repo, so there is nothing to set up.
`params` pins `temperature: 0` and `num_predict: 4` β a router should be deterministic and
emit one token. To build it locally instead: `ollama create loom-router-1 -f Modelfile`.
## Usage β transformers
```python
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
ROUTES = ["search","calc","time","weather","calendar","reminder","email","notes",
"maps","translate","convert","define","music","answer","clarify",
"unknowable","refuse"]
tok = AutoTokenizer.from_pretrained("textilelabs/Loom-Router-1")
model = AutoModelForCausalLM.from_pretrained("textilelabs/Loom-Router-1").eval()
route_ids = {tok.convert_tokens_to_ids(f"<route:{r}>"): r for r in ROUTES}
ids_t = torch.tensor(list(route_ids))
def route(message: str) -> str:
prompt = f"<user>\n{message.strip()}\n<|eot|>\n<loom>\n"
ids = tok(prompt, return_tensors="pt", add_special_tokens=False).input_ids
with torch.no_grad():
logits = model(input_ids=ids).logits[0, -1]
# Decide only among legal routes, so the output is always a valid label.
return route_ids[int(ids_t[logits[ids_t].argmax()])]
route("add milk to my shopping list") # -> 'notes'
```
The prompt format is exact: `<user>\n{message}\n<|eot|>\n<loom>\n`, no trailing space.
## In an agent loop
```
user β router β your harness runs the tool β result β router again
β 'answer' ends the loop
```
Cap the number of steps in your harness. `answer` and `clarify` are the terminating routes.
## Files
```
config.json / model.safetensors the model
tokenizer.json / tokenizer_config.json custom BPE tokenizer, 2,048 tokens
loom-router-1-f16.gguf 2.8MB, for Ollama / llama.cpp
template / params read automatically by `ollama run hf.co/...`
Modelfile for building locally
ATTRIBUTION.md required credits for the training corpora
```
## Training data
Real human utterances from two openly licensed corpora, remapped onto the routes above:
- **MASSIVE** β Amazon (CC BY 4.0), derived from **SLURP** (CC BY 4.0)
- **CLINC150** β `clinc/oos-eval` (CC BY 3.0)
23,674 real utterances. The four control routes have no public equivalent and are
procedurally generated. Validation is a held-out split of the *real* utterances β never
templates written by the same process that produced the training data.
See `ATTRIBUTION.md`; both licences require credit.
## License
Model: MIT. Training data retains its original licences and attribution.
|