File size: 9,578 Bytes
d667b66
 
d7dd5b2
 
 
 
 
 
 
 
 
 
 
 
 
 
d667b66
d7dd5b2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
550055d
 
d7dd5b2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
550055d
d7dd5b2
 
 
 
 
 
 
 
 
 
 
 
 
 
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
---
license: apache-2.0
language:
- en
base_model: Qwen/Qwen3.5-2B
base_model_relation: finetune
pipeline_tag: image-text-to-text
library_name: transformers
tags:
- unsloth
- lora
- qwen3.5
- tool-use
- agent
- distillation
- dgx-spark
---

![Javi0.3, 4B and 2B, out now](https://huggingface.co/jvr0x/Javi0.3-2B/resolve/main/javi03-cover.png)

# Javi0.3-2B

The tiny Javi: a 2B agent model trained to think from first principles and use its tools effectively to find the answer to your prompt, or, when the tools can't get there, to guide you to it. It's trained to never guess: if it can't verify something or doesn't know, it says so. When a tool fails, it works out why and takes another route instead of looping. Fine-tuned from [Qwen3.5-2B](https://huggingface.co/Qwen/Qwen3.5-2B) to work inside coding and research agents ([grok build](https://x.ai), [omp](https://www.npmjs.com/package/@oh-my-pi/pi-coding-agent), [opencode](https://opencode.ai)) with their own system prompts and tools.

This repo holds the merged bf16 weights. For llama.cpp or [lmswitch](https://github.com/jvr0x/lmswitch), use [Javi0.3-2B-GGUF](https://huggingface.co/jvr0x/Javi0.3-2B-GGUF). Bigger sibling: [Javi0.3-4B](https://huggingface.co/jvr0x/Javi0.3-4B).

What it does, all measured in [Results](#results):

- **Beats its base model clearly.** Core pass 0.35 vs 0.23 for base Qwen3.5-2B (+0.12, 95% interval +0.08 to +0.17), and 0.35 vs 0.17 for Javi0.2, a model twice its size.
- **Always looks up time-sensitive questions.** It answered one from memory in 1% of cases (base: 36%).
- **Short, grounded answers.** 378 characters on average (base: 724).
- **Rarely invents links.** 1.7% of answers cite a link no tool returned, counting bare ones without `https://` (base: 3.0%).
- **Doesn't loop.** It repeats an identical call in 4% of tasks (base: 17%).

How it was built, every mistake included: [the Javi0.3 article on X](https://x.com/jvr0x/status/2109266234512379964).

## Files

The merged model in bf16 safetensors (one shard, 4.5 GB), with the base model's tokenizer, processor files and chat template. The LoRA touched the language model only; the vision tower is the base model's, unchanged, and Javi was never trained or evaluated on images.

## Run it

Javi was trained and evaluated with thinking off. Send `"chat_template_kwargs": {"enable_thinking": false}` with each request, and use a low temperature (0.2, top-p 0.8, top-k 20) so tool calls stay well formed.

**vLLM**

```bash
vllm serve jvr0x/Javi0.3-2B --max-model-len 131072 \
  --reasoning-parser qwen3 --enable-auto-tool-choice --tool-call-parser qwen3_coder
```

Add `--language-model-only` to skip the vision tower and save memory.

**SGLang**

```bash
python -m sglang.launch_server --model-path jvr0x/Javi0.3-2B --context-length 131072 \
  --reasoning-parser qwen3 --tool-call-parser qwen3_coder
```

Then point any OpenAI-compatible agent at the server. Javi is built for agents with tools (web search, page fetch, a shell). In a plain chat with no tools it answers from what it knows and tells you when a question needs a lookup.

**Transformers**

```python
from transformers import AutoModelForImageTextToText, AutoProcessor

model_id = "jvr0x/Javi0.3-2B"
processor = AutoProcessor.from_pretrained(model_id)
model = AutoModelForImageTextToText.from_pretrained(model_id, dtype="auto", device_map="auto")

tools = [{"type": "function", "function": {
    "name": "web_search", "description": "Search the web.",
    "parameters": {"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]}}}]
messages = [{"role": "user", "content": [{"type": "text", "text": "What's the latest stable Rust release?"}]}]

inputs = processor.apply_chat_template(messages, tools=tools, add_generation_prompt=True, enable_thinking=False,
                                       tokenize=True, return_dict=True, return_tensors="pt").to(model.device)
out = model.generate(**inputs, max_new_tokens=512, do_sample=True, temperature=0.2, top_p=0.8, top_k=20)
print(processor.decode(out[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True))
```

With a search tool available it should answer with a `web_search` call rather than a version from memory; run the tool, append the result as a `tool` message, and generate again.

## Results

**Eval v3:** 175 held-out questions, each run 3 times inside real client formats (grok, omp, opencode, a bare OpenAI format and a plain chat) with tools: web search (frozen cache and pinned date, so runs compare), page fetches, and a sandboxed shell over small code projects. Some questions force tool failures. Two judges grade every answer, DeepSeek-V4.1-Flash and MiMo-V2.6-Flash. **Core pass** means grounded (2/2), honest (2/2), the task at least partly solved, and no `https://` link that no tool returned (checked from the transcript, not by the judge). The eval ran the Q8_0 GGUF of these weights.

| Core pass | Base Qwen3.5-2B | Javi0.2-4B | **Javi0.3-2B** |
|---|---|---|---|
| Both judges | 0.23 | 0.17 | **0.35** |
| DeepSeek-V4.1-Flash | 0.26 | 0.21 | 0.48 |
| MiMo-V2.6-Flash | 0.20 | 0.13 | 0.23 |

Against the base model, Javi0.3-2B is ahead by 0.12, with a 95% interval of +0.08 to +0.17.

**Behavior**, counted from all 525 transcripts:

| | Base Qwen3.5-2B | **Javi0.3-2B** |
|---|---|---|
| Finished | 85% | 98% |
| Answered a time-sensitive question without a tool | 36% | 1% |
| Cited a link no tool returned, with or without `https://` | 3.0% | 1.7% |
| Repeated an identical call | 17% | 4% |
| Said what failed before the next call | 27% | 76% |
| Recovered after a forced failure | 96% | 55% |
| Tool calls per task | 3.6 | 1.8 |
| Average answer | 724 chars | 378 chars |

**Held-out failures:** 24 more questions whose failures are types it never trained on (429 rate limits, timeouts), 3 runs each.

| | Base Qwen3.5-2B | **Javi0.3-2B** |
|---|---|---|
| Core pass | 0.06 | 0.33 |
| Recovered | 75% | 56% |
| Repeated an identical call | 42% | 4% |

**By category** (core pass, Javi0.3-2B vs base): ahead on lookups (0.37 vs 0.11), unknowable questions (0.55 vs 0.28), recovery questions (0.35 vs 0.05), every route broken (0.33 vs 0.10) and false premises (0.29 vs 0.09); slightly behind on chat (0.33 vs 0.41), code (0.20 vs 0.31) and reasoning puzzles (0.70 vs 0.74).

## How it was made

**Teacher inside the harness.** [GLM-5.3-Flash](https://huggingface.co/Mia-AiLab/GLM-5.3-Flash-EXL3-4bpw-TensorFold) answered seed questions inside each client's exact format (system prompts of 15-25k tokens, 10-27 tools) with real tools: web search through a local SearXNG, page fetches, and a Docker sandbox. Its working rules were in the teacher's prompt only: reason from first principles, look up anything time-sensitive, diagnose a failed call before the next one, and when everything fails still give a labeled best answer and name where to check. Training rows keep the client's own prompt, so the student learns the behavior, not the instructions.

**Data.** Javi0.2's rows with made-up links removed, plus two rounds of recovery practice on 480 questions, each round giving every question a different failure and client: the first search or page fetch fails once (blocked, empty, 403, 404) or, in the second round, keeps failing for a third of them (offline, a route blocked for good).

**Filters.** DeepSeek-V4.1-Flash judged every episode. Code checks dropped anything unfinished, any time-sensitive answer without a tool, repeated calls, links no tool returned (with or without `https://`), and any call after a failure that didn't say what failed. A recovery stayed only if a different route worked, or if two routes failed and the answer stopped honestly.

**Concise rewrite.** The teacher rewrote every answer over 500 characters answer-first, keeping only facts a tool returned, with sources named instead of linked (median answer 646 → 357 characters). A rewrite was kept only if it was shorter, had no URL, changed no code block and added no number. This one change took the 2B from 0.24 to 0.35.

**Training: LoRA SFT with Unsloth on 1,307 rows.**

| Setting | Value |
|---|---|
| Method | LoRA on the bf16 base |
| Rank / alpha / dropout | 16 / 16 / 0 |
| Target modules | q, k, v, o, gate, up, down |
| Epochs / steps | 1 / 327 |
| Effective batch | 4 (1 × 4 grad accum) |
| Learning rate | 1e-4, linear decay, 3 warmup steps |
| Max sequence length | 73,728 tokens |
| Loss on | the first reply to each user message, every final answer, and the turn right after each failed tool call |
| Hardware / time | 1× DGX Spark (GB10), 1h40m |

**Merge:** the adapter was merged into the bf16 base with Unsloth.

The story of every round, including each way it broke along the way, is in [the Javi0.3 article on X](https://x.com/jvr0x/status/2109266234512379964).

## Limitations

- **Recovery after a failure is 55%** (base: 96%). It doesn't give up, but its second route often fails too.
- **It's a 2B model.** It misreads tool results more often than the 4B, and it is weaker on code and reasoning puzzles than its base.
- The later 4B recipe (search-first routes, a de-hedge pass) did not help at 2B, so this is the earlier, concise-only recipe.
- **Images:** the vision tower is untouched and untested with Javi's training.
- It's built for agent clients with tools. Without tools it can't look anything up.
- The judges are LLMs, not people, and they disagree on some answers (Cohen's kappa 0.56).
- Trained mostly on English.

## License

Apache 2.0, inherited from [Qwen3.5-2B](https://huggingface.co/Qwen/Qwen3.5-2B).