--- license: apache-2.0 base_model: Qwen/Qwen3.5-0.8B base_model_relation: adapter pipeline_tag: text-generation datasets: - Gramscii-IT/semantic-repair-routing language: - it - en - fr - de - es tags: - query-rewriting - query-understanding - intent-detection - routing - capability-routing - distillation - lora - retrieval - rag - slm - gguf - multilingual - qwen3.5 model-index: - name: SemanticRepair-0.8B results: - task: type: text-generation name: 'The repair adapter in the chat''s routing chain, with ToolChooser-0.8B: real chat questions' dataset: type: gramscii-real-chat-questions name: 480 real Italian chat questions, held out from training split: test metrics: - type: accuracy name: Ran only capabilities the question asks for (%) value: 36.3 - type: wrong-execution-rate name: Ran a capability the question does not ask for (%), lower is better value: 6.3 - task: type: text-generation name: 'The repair adapter in the chat''s routing chain: test messages written to probe it' dataset: type: gramscii-repair-exam name: 100 test messages, 50 Italian and 50 English split: test metrics: - type: accuracy name: Routed right (%) value: 94.0 - type: wrong-execution-rate name: Routed wrong (%), lower is better value: 4.0 --- # SemanticRepair-0.8B A LoRA adapter on Qwen3.5-0.8B that works behind an embedding router. When a message does not land clearly on one capability of a chat, it writes every request the message carries on its own line, and names for each line the capability that does its work among the ones the chat offers, or `none`. A message that asks for nothing comes back as `NO_REQUEST`. It reads the message after what the chat's last turns ran, so a message that continues the conversation («e per Milano?») is read as part of it. It does not answer the question. The router checks every capability the repair names against its own scores, and the host application decides what runs: a model's output alone never authorizes an operation. Three companion adapters load on the same base, each in a repository of its own: - [**ToolChooser-0.8B**](https://huggingface.co/Gramscii-IT/ToolChooser-0.8B): when the router and the repair still leave two or more capabilities close, it chooses one of them, or none, in which case the person is asked. - [**QueryTranslator-0.8B**](https://huggingface.co/Gramscii-IT/QueryTranslator-0.8B): it writes a search request again in the language of the passages it will be looked up in. - [**FilterReader-0.8B**](https://huggingface.co/Gramscii-IT/FilterReader-0.8B): it reads a question's filters among the codes a capability declares. ## What is in this repository | file | what it is | |---|---| | `qwen3.5-0.8b-base-2fc06364-q8_0.gguf` | **the base**: Qwen3.5-0.8B at revision `2fc06364`, Q8_0, 833,592,320 bytes, sha256 `6a94a8273ab1fc5735743967b621eb5a3baba8e36a202f61c20dc47fbb80eb51` | | `semantic-repair-0.8b-repair-20261109-lora-f16.gguf` | **the repair adapter**, F16 LoRA at rank 32, 43,316,320 bytes, sha256 `c1d903763cedd49b7b82eb58cb66f036e85e18ba4e4a058930cc1318133a0094` | | `LICENSE` | the base model's Apache-2.0 licence | One base serves every job: an adapter costs 22 to 43 MB, not another 800 MB model. Pin the sha256 of both files: a routing decision has to be reproducible. ## Where it runs With llama-server, the model loaded only when it is needed and released after an idle window: ``` llama-server -m qwen3.5-0.8b-base-2fc06364-q8_0.gguf \ --lora semantic-repair-0.8b-repair-20261109-lora-f16.gguf \ --jinja --host 127.0.0.1 --port 9250 -ngl 99 -c 32768 --no-context-shift --cache-ram 0 ``` When the companion adapters are loaded too, with one `--lora` each, every request names its adapter at full scale and the others at none, so the jobs never mix: for a repair, `"lora": [{"id": 0, "scale": 1.0}, {"id": 1, "scale": 0.0}, ...]`. `--cache-ram 0` keeps llama-server from holding up to 8 GiB of earlier prompts in memory: every call asks about a message of its own. ## The surface it reads, which is not a chat prompt Fine-tuned on a bare completion surface. It has never seen a chat template, a system message or a few-shot example. Speak to it the way it was trained: ``` Capabilities: {the capability ids the chat offers, sorted, comma separated} Earlier: {capability} | {request} {Language}: {the message, verbatim} => ``` It completes with one line per request, `capability | request`, or the single token `NO_REQUEST`. A request no offered capability does is written `none | request`. - `Earlier:` lines are what the chat's last turns ran, one line for each request, in the `capability | request` form the adapter itself writes. A chat's first message has none, and the surface is then the two lines around it. They are how it reads «e per Milano?» after a question about Rome as the same request for another city. - The newline after `=>` is part of the surface: a prompt that stops at `=>` is a surface the model never read. - `{Language}` is the English name of the language **the message** is in: `Italian`, `English`, `French`, `German`, `Spanish`. - The capabilities line is context the model reads, not a formality. **Pass the whole list your chat offers**, not a shortlist (see *What it does not fix*). This is the whole call, on `/v1/completions` and never on a chat endpoint: ```json { "prompt": "Capabilities: document-search, locate-place, opendata-statistics, web-search\nItalian: dov'è sassari? mostramelo nella mappa e dammi le ultime notizie in italia\n=>\n", "stop": ["\n=>"], "temperature": 0.0, "repeat_penalty": 1.0, "max_tokens": 512 } ``` Greedy, because the same words must always produce the same lines: a router that reruns on a different answer each time cannot be reasoned about. `repeat_penalty` 1.0 is neutral, since a restatement legitimately repeats the words of the message. The stop string cuts a runaway that starts echoing another message in its own trained format. 512 tokens is the answer cap every measure below used. ## What it is measured to do Served on the base at Q8_0 with the three companion adapters, through the chat's routing chain. **480 real Italian chat questions**, held out from every training and selection step: | | | |---|---| | ran only capabilities the question asks for | 174 | | **ran a capability the question does not ask for** | **30** | | the person asked to choose | 276 | Most of the questions where the person is asked need something the chain was not given in this measure: a document search with no file selected, or a web search with the web switched off. **100 test messages**, 50 Italian and 50 English, written to probe the repair: | | right | wrong | the person asked | |---|---|---|---| | Italian | **48** | 1 | 1 | | English | **46** | 3 | 1 | **The repair alone on 363 real prompts**, the distinct prompts the chain sends the repair for the real questions, each answer judged against the capabilities the prompt offered: | | | |---|---| | the capabilities asked for | 98 | | nothing named, rightly | 244 | | nothing named, though a capability was asked for | 10 | | a capability the message does not ask for | 7 | | a capability named where none was asked for | 3 | | part of what was asked | 1 | **Held-out rows of its own training set**, which it never trained on: the right capabilities for 1,508 of 1,615. ## What it does not fix **A list without the capability a request needs is answered `none`.** Its training rows list the capabilities a chat offers, and in some of them the right one is taken out and the answer becomes `none`: it reads the list as context. Pass the whole list the chat offers. **Long briefs lose steps and names.** A brief of several hundred tokens comes back with some of its steps and names missing: ask it about one step at a time. **The restatements are terse.** «statistiche abitanti Cagliari» is good input for a router and poor prose. It is not meant to be shown to anyone. **Measured in Italian and English.** The base pairs cover five languages, but the capability labels and every measure above are Italian and English. ## The data it was trained on **159,079 training rows and 1,615 validation rows**, from: - The published [`Gramscii-IT/semantic-repair-routing`](https://huggingface.co/datasets/Gramscii-IT/semantic-repair-routing) pairs, every line carrying the capability that does its work as two judge models, Qwen3.8-27B and Gemma-4-31B, agreed on it; a row they disagreed on is left out. - **57,473 generated rows** in Italian and English, written by Qwen3.8-27B (about two thirds) and Gemma-4-31B (about one third), kept only with the capability each was written for; rows for the capabilities that held few lines, and trap and contrast rows for pairs of capabilities that are easy to confuse, kept when both judges found nothing the line leaves out. - **1,500 long briefs**, each step of their plans a line. - **17,107 chat messages** from scripted conversations played through the application, whose capability the run that answered them confirmed and both judges confirmed reading the message alone, each with the line Qwen3.8-27B wrote for that capability and both judges confirmed. - **4,360 follow-up messages**, from scripted chats whose later turns continue the first question about public funding calls or tenders, each row carrying the requests the earlier turns ran as `Earlier:` lines. Each message was labelled twice by Qwen3.8-27B, greedy and with its own sampling, and kept only where both readings named the same capability, with the line Qwen3.8-27B wrote for it. - **6,510 messages written the way people write**, each read by both judges as the capability it was written for, with the line Qwen3.8-27B wrote for it and both judges confirmed: typing mistakes, words of another language, capitals and impatience, a correction within the message, an attempt to steer the chat; short, colloquial and misspelled requests for every capability; Open Data questions of one sentence shape that different Open Data capabilities answer; requests about Italian municipalities; and compound messages, two confirmed lines joined into one. Each row's prompt lists the capabilities its lines name plus a random draw of the others, because real chats offer lists as varied as that. For some rows the right capability is taken out of the list and the answer becomes `none`; every one of the 6,510 messages also comes that way, and a capability easy to confuse with the one taken out stays on the list. About one row in a hundred goes to validation, and no row equals a question of the measures above. ## Training | | | |---|---| | base | [`Qwen/Qwen3.5-0.8B`](https://huggingface.co/Qwen/Qwen3.5-0.8B) at `2fc06364`, its language model only | | method | LoRA, rank 32, alpha 64, dropout 0.05, on every linear layer but the embeddings and the head (186 modules, 21.6M parameters) | | schedule | one pass: 19,885 updates of 8 examples, learning rate 1×10⁻⁴, 100 warm-up steps | | tokens | 32.3M in the examples, 4.2M of them in the answers | | loss | on the answer only | | hardware | one NVIDIA A100 64 GB of the Leonardo supercomputer in Bologna, thanks to CINECA | | release | F16 LoRA GGUF | It starts from the original base. ## Licence Apache-2.0, as its base model [Qwen3.5-0.8B](https://huggingface.co/Qwen/Qwen3.5-0.8B). What it writes is yours, and so is the responsibility for what you let it decide.