Image-Text-to-Text
Transformers
Safetensors
English
qwen3_5
unsloth
lora
qwen3.5
tool-use
agent
distillation
dgx-spark
conversational
Instructions to use javierbits/Javi0.3-4B with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use javierbits/Javi0.3-4B with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-text-to-text", model="javierbits/Javi0.3-4B") messages = [ { "role": "user", "content": [ {"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/p-blog/candy.JPG"}, {"type": "text", "text": "What animal is on the candy?"} ] }, ] pipe(text=messages)# pip install -U transformers accelerate # Load model directly from transformers import AutoProcessor, AutoModelForMultimodalLM processor = AutoProcessor.from_pretrained("javierbits/Javi0.3-4B") model = AutoModelForMultimodalLM.from_pretrained("javierbits/Javi0.3-4B", device_map="auto") messages = [ { "role": "user", "content": [ {"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/p-blog/candy.JPG"}, {"type": "text", "text": "What animal is on the candy?"} ] }, ] inputs = processor.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=256) print(processor.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use javierbits/Javi0.3-4B with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "javierbits/Javi0.3-4B" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "javierbits/Javi0.3-4B", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker
docker model run hf.co/javierbits/Javi0.3-4B
- SGLang
How to use javierbits/Javi0.3-4B with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "javierbits/Javi0.3-4B" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "javierbits/Javi0.3-4B", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "javierbits/Javi0.3-4B" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "javierbits/Javi0.3-4B", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }' - Unsloth Desktop
- Docker Model Runner
How to use javierbits/Javi0.3-4B with Docker Model Runner:
docker model run hf.co/javierbits/Javi0.3-4B
File size: 10,670 Bytes
794d4b9 1f7d593 794d4b9 1f7d593 34fc9bb 1f7d593 c05abab 1f7d593 c05abab 1f7d593 | 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 | ---
license: apache-2.0
language:
- en
base_model: Qwen/Qwen3.5-4B
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
A 4B 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 or giving up. Fine-tuned from [Qwen3.5-4B](https://huggingface.co/Qwen/Qwen3.5-4B) 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-4B-GGUF](https://huggingface.co/jvr0x/Javi0.3-4B-GGUF). Smaller sibling: [Javi0.3-2B](https://huggingface.co/jvr0x/Javi0.3-2B).
What changed since [Javi0.2](https://huggingface.co/jvr0x/Javi0.2-4B-GGUF), all measured in [Results](#results):
- **Short, grounded answers.** 325 characters on average (Javi0.2: 2,193; base Qwen3.5-4B: 632). Fewer claims, fewer wrong ones.
- **Almost no made-up links.** 1.1% of answers cite a link no tool returned, counting bare ones without `https://` (Javi0.2: 53%; base Qwen3.5-4B: 1.7%).
- **Works around failures.** When a search is blocked or a page returns 403, it says what failed and the likely cause, then tries another route. On failure types it never saw in training (429 rate limits, timeouts) it recovers 88% of the time, level with base Qwen3.5-4B (89%).
- **Honest when everything is broken.** It says what it tried, gives its best answer clearly labeled as unverified, and names where to check, without inventing a URL.
- **No loops.** It repeats an identical call in 3% of tasks (base: 11%).
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 (two shards, 9.3 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-4B --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-4B --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-4B"
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-4B | Javi0.2-4B | **Javi0.3-4B** |
|---|---|---|---|
| Both judges | 0.45 | 0.17 | **0.50** |
| DeepSeek-V4.1-Flash | 0.50 | 0.21 | 0.64 |
| MiMo-V2.6-Flash | 0.39 | 0.13 | 0.36 |
Against the base model, Javi0.3-4B is ahead by 0.05, but the 95% interval is -0.01 to +0.11: ahead, not proven. The judges split: DeepSeek has Javi ahead by 0.14 (+0.07 to +0.20), MiMo has the base 0.03 ahead, within noise. Against Javi0.2 it gains 0.33.
**Behavior**, counted from all 525 transcripts:
| | Base Qwen3.5-4B | Javi0.2-4B | **Javi0.3-4B** |
|---|---|---|---|
| Finished | 90% | 99% | 98% |
| Answered a time-sensitive question without a tool | 22% | 1% | 0% |
| Cited a link no tool returned, with or without `https://` | 1.7% | 53% | 1.1% |
| Repeated an identical call | 11% | 2% | 3% |
| Said what failed before the next call | 6% | 0% | 79% |
| Recovered after a forced failure | 92% | 62% | 58% |
| Tool calls per task | 3.0 | 1.8 | 1.8 |
| Average answer | 632 chars | 2,193 chars | 325 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-4B | Javi0.2-4B | **Javi0.3-4B** |
|---|---|---|---|
| Core pass | 0.57 | 0.02 | 0.51 |
| Recovered | 89% | 72% | 88% |
| Cited a link no tool returned, with or without `https://` | 0% | 89% | 0% |
**By category** (core pass, Javi0.3-4B vs base): ahead where Javi's habits matter (every route broken 0.52 vs 0.11, lookups 0.43 vs 0.28, undated questions 0.47 vs 0.19, conflicting sources 0.40 vs 0.26, false premises 0.27 vs 0.15); behind on code (0.46 vs 0.67), reasoning puzzles (0.82 vs 0.96) and recovery questions (0.41 vs 0.52).
## 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, prefer a new search or a link from earlier results over a URL typed from memory, 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 three 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, from the second round on, 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. Recoveries where a URL typed from memory just got lucky were dropped.
**Two rewrite passes by the teacher.** First concise: every answer over 500 characters rewritten answer-first, keeping only facts a tool returned, with sources named instead of linked (median answer 703 → 364 characters). Then de-hedge: disclaimers removed from math, code and settled facts (hedged answers 46% → 17%, median answer 289 characters). A rewrite was kept only if it was shorter, had no URL, changed no code block and added no number.
**Training: LoRA SFT with Unsloth on 1,253 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 / 313 |
| 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), 3h28m |
**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 on the main eval is 58%** (base: 92%). After a failure it often fetches a page directly instead of searching again, and about half of those fetches fail.
- **Its lead over base Qwen3.5-4B is within noise,** and it is weaker on code and reasoning puzzles.
- **Hedging is not perfect.** 27% of answers carry an "unverified" hedge; on stable questions, where none is needed, 5% still do.
- **Images:** the vision tower is untouched and untested with Javi's training.
- It's a 4B model. It can still get facts wrong.
- 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.
## What's next
Javi0.4: recovery data built search-first from the start, and a bigger eval that can resolve small gaps.
## License
Apache 2.0, inherited from [Qwen3.5-4B](https://huggingface.co/Qwen/Qwen3.5-4B).
|