Doosra tool caller: Qwen3.5 4B fine-tuned to use a cricket analytics toolkit (v1, run B at step 350)
Superseded by v2. This first version is near-perfect on questions shaped like its training templates but answered fewer open questions right than the base model it started from. The evaluation below shows why, and v2 fixes it. Kept for the comparison.
Doosra is a cricket analytics app with a local AI copilot: you ask a question, and the model answers it by calling Doosra's 25 tools (leaderboards, player profiles, match-ups, venues, records, match replays, charts, SQL…) over 11 million balls of Cricsheet data, then writes an answer from the results.
This is Qwen3.5 4B fine-tuned on 5,854 of Doosra's own tool-calling conversations, so the small model does the job reliably, with a prompt about 8x shorter than the app gives general models (about 280 tokens instead of 2,100, plus compact tool schemas), which matters on a laptop where reading the prompt is the slow part. See Evaluation for how it compares with the base models and v2.
v1, run B at step 350: rank 32, learning rate 1e-4, stopped at step 350 of 732 (about 1 epoch), dataset v1.
What it does
Who led in dot % during the death overs of the BPL?
| Call | |
|---|---|
| Qwen3.5 4B | leaderboard(metric="dot_pct", role="bowling", filters={"competition": "BPL", "phase": "death"}, limit=5) |
| This model | leaderboard(metric="dot_pct", role="bowling", filters={"competition": "Bangladesh Premier League", "phase": "death"}) |
The base model abbreviates the competition and cuts the table to 5 rows nobody asked for. The fine-tuned model makes the exact call; Doosra runs it, and the answer is written from the result (this one is from the test set):
Rashid Khan is top with 49.07 dot % (Bangladesh Premier League, male, death).
- Nasum Ahmed (Khulna Tigers): 47.92
- Mohammad Irfan (Rajshahi Royals): 45.61
Files
| File | |
|---|---|
Qwen3.5-4B.Q4_K_M.gguf |
the merged model, 4-bit (2.8 GB), for llama.cpp and Doosra's built-in engine |
adapter_model.safetensors, adapter_config.json |
the LoRA adapter (rank 32), to apply to Qwen/Qwen3.5-4B yourself |
Qwen3.5-4B.BF16-mmproj.gguf |
the base model's vision projector, unchanged (vision wasn't trained or tested) |
run_log.json |
the training run: settings, losses, timings |
training_loss.png |
the loss curves |
Use
In Doosra (3.0 and later): Settings → AI model → Qwen3.5 4B Doosra. The app sends the short prompt and compact tools this model was trained with.
With llama.cpp (any OpenAI-compatible client). The model emits tool calls; your code runs the tools. Give it the system prompt and tool list it was trained with, from the dataset, with thinking off:
llama-server -hf Sarthak213/doosra-qwen3.5-4b-toolcalls-b-step350:Q4_K_M --jinja --ctx-size 16384
import json
from huggingface_hub import hf_hub_download
from openai import OpenAI
data = "Sarthak213/doosra-toolcalls"
tools = json.load(open(hf_hub_download(data, "tools.json", repo_type="dataset")))
system = open(hf_hub_download(data, "system_prompt.txt", repo_type="dataset"), encoding="utf-8").read()
client = OpenAI(base_url="http://127.0.0.1:8080/v1", api_key="none")
reply = client.chat.completions.create(
model="local", temperature=0, tools=tools,
messages=[{"role": "system", "content": system},
{"role": "user", "content": "Who led in dot % during the death overs of the BPL?"}],
extra_body={"chat_template_kwargs": {"enable_thinking": False}})
print(reply.choices[0].message.tool_calls)
Send each tool's result back as a tool message and call again; the model finishes with a final_answer call.
Training
Data: Sarthak213/doosra-toolcalls, version 1: 7,000 conversations (5,854 train, 306 validation, 840 test) across 29 kinds of question, single calls and multi-step ones. Questions come from templates over real players, teams, venues and competitions, and 43% were reworded by Qwen3.5 9B; every tool call was executed against the real database, and every answer was written from the results. About 15% of players, teams and venues appear only in the test split, so the test measures names the model never saw.
Method: LoRA on unsloth/Qwen3.5-4B with Unsloth and TRL, on the
language layers (attention and MLP; vision layers frozen), loss on the assistant's turns only (tool calls and
answers, not the prompt, question or tool results). Conversations are rendered with Qwen's chat template, the tool
list and thinking off, exactly as the app sends them.
| Setting | |
|---|---|
| LoRA | rank 32, alpha 32, dropout 0, all attention and MLP projections (64,929,792 trainable parameters) |
| Base weights | 16-bit (bfloat16), not 4-bit QLoRA |
| Optimiser | AdamW 8-bit, learning rate 0.0001, cosine schedule, 5 warm-up steps, weight decay 0.01 |
| Batch | 16 conversations per step (2 × 8 accumulation), sequences up to 6,144 tokens (4.5% of conversations were cut short at the end, where the answer is; fixed in v2) |
| Length | 350 steps (about the end of epoch 1) of a 2-epoch run |
| Hardware | Google Colab, NVIDIA A100-SXM4-40GB (40 GB) |
| Time | the first 350 steps of run B: about 2 hours of A100 time, including a disconnect at step 195 that resumed from the step-150 checkpoint |
Validation loss fell from 0.035 (step 50) to 0.0056 (step 300) and ended at 0.0059.
Evaluation
Measured with ToolEval, a local evaluation app, each model served by llama.cpp as Doosra runs it (Q4_K_M, thinking off): the base models with the app's full prompt and tool list, the fine-tuned ones with the short prompt they were trained on.
End to end: 40 open questions (Doosra's own evaluation set, written separately from the training templates and kept out of training), each answered through a real tool loop against Doosra's database. 35 are graded automatically on the facts the answer must contain (accepting name variants such as "V Kohli" for "Virat Kohli"); 5 are open-ended (a qualification or definition to choose) and were checked by hand against the database.
End to end (the same 40 questions for every model)
| Model | Correct of 40 | Auto-graded (35) | Reviewed (5) | Calls per question | Median s | 90th pct s |
|---|---|---|---|---|---|---|
| Qwen3.5 4B (long prompt) | 29 | 28 | 1 | 3.125 | 30.4 | 85.1 |
| Qwen3.5 9B (long prompt) | 30 | 27 | 3 | 1.95 | 26.7 | 72.6 |
| v1, run A | 16 | 14 | 2 | 1.75 | 12.3 | 25.6 |
| v1, run B at step 350 | 18 | 16 | 2 | 1.675 | 12.0 | 23.8 |
| v1, run B | 21 | 19 | 2 | 2 | 13.4 | 25.8 |
| v2 at epoch 1 | 33 | 30 | 3 | 1.575 | 13.1 | 23.7 |
| v2 | 33 | 31 | 2 | 1.275 | 11.4 | 17.4 |
v1 was near-perfect on its templated tests but lost end to end: it couldn't write SQL (the compact tool list had dropped the schema), answered with a template's headline figure instead of the one asked (sixes, balls faced), made one call and stopped where a second was needed, and had learned answers with no figures from a template bug. Dataset v2 fixed each of these.
Held-out test set (dataset v1: unseen players, teams and venues)
First call of 300 conversations, and every step of 100 with the final answer checked against the tool results.
| Model | Right tool | Exact arguments | Conversations all right | Answers when it should | Answers fully grounded |
|---|---|---|---|---|---|
| Qwen3.5 4B (long prompt) | 82% | 52% | 57% | 23% | 52% |
| Qwen3.5 9B (long prompt) | 88% | 57% | 56% | 90% | 69% |
| v1, run A | 98% | 93% | 92% | 100% | 99% |
| v1, run B at step 350 | 99% | 94% | 94% | 100% | 99% |
| v1, run B | 99% | 96% | 96% | 100% | 99% |
Limitations
- Doosra's tools only. It learned these 25 tools, their arguments and Doosra's answer style. It isn't a general assistant or a general tool caller, and it expects the short system prompt it was trained with.
- Synthetic questions. Training questions come from templates, partly reworded; real users ask in more ways. The end-to-end questions are the check on that.
- SQL fine print. It writes valid SQL on Doosra's schema but can miss a rule in the schema notes: asked how many
matches were ties, it counted
winner IS NULL, which also includes draws and no-results. Check SQL answers to unusual questions. - Names as the data has them. Cricsheet names players as "V Kohli", "JJ Bumrah"; the tools resolve most forms, but unusual spellings may miss.
- The data's coverage. Answers are only as complete as Cricsheet: some competitions and teams are thin or missing (for example, there are no Afghanistan matches).
Licence
The model is released under Apache 2.0, like Qwen3.5 4B. The training data's cricket results are from Cricsheet under the Open Data Commons Attribution License.