FoolDev Claude Fable 5 commited on
Commit
5471e29
·
1 Parent(s): ac021c9

Complete examples/ suite parity with Thanatos-27B (adapted to Janus)

Browse files

Ports the sibling's remaining runnable clients, adapted to Janus:
- examples/ollama_chat.py — chat/stream/tools/OpenAI-compat, MODEL=janus
- examples/transformers_quickstart.py — Heretic safetensors, 4-bit ~20 GB /
bf16 ~70 GB, MoE all-experts-resident note
- examples/llama_cpp_quickstart.py — GGUF via llama-cpp-python, no daemon
- examples/README.md — index rewritten for Janus's direct-script
flow (no Makefile/heal-hf; ./scripts/*.sh,
tag janus, Q4_K_M ~19 GB, mmproj ~903 MB)

README 'What's here' row consolidated to examples/; inline 'Inference
examples' now points at examples/. Supersedes the earlier
'text examples not duplicated' CHANGELOG note.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

CHANGELOG.md CHANGED
@@ -55,19 +55,26 @@ track the **tooling and documentation**, not the underlying base model.
55
  dense sibling's exact wording and drops the ambiguous `262k` rounding
56
  (262 144 = 256 × 1024, not 262 000).
57
 
58
- ### Added (vision tooling — parity with Thanatos-27B)
59
- - **`scripts/fetch_vision.sh`, `examples/llama_cpp_vision.py`, and a `## Vision`
60
- README section.** Mirrors the dense sibling's vision path, adapted to Janus:
61
- bundle `Janus-35B-A3B.Q4_K_M.gguf`, arch `qwen35moe`, projector
62
- `Qwen3.6-35B-A3B-uncensored-heretic-mmproj-BF16.gguf` (~903 MB, BF16 only)
63
- from `llmfan46/Qwen3.6-35B-A3B-uncensored-heretic-GGUF`, with the
64
  `unsloth/Qwen3.6-35B-A3B-GGUF` `mmproj-F16.gguf` as the F16/F32 fallback.
65
- `fetch_vision.sh` pulls the projector; `llama_cpp_vision.py` runs
66
- image-text-to-text via llama-cpp-python. The Vision section documents the
67
- llama.cpp loader path and the Ollama-vision-broken caveat (`unknown model
68
- architecture: 'qwen35moe'`, ollama/ollama#15898). The sibling's **text**
69
- example scripts were deliberately not duplicated — Janus already covers those
70
- inline under "Inference examples". "What's here" table updated.
 
 
 
 
 
 
 
71
 
72
  ### Changed (HF repo rename → FoolDev/Janus-35B-HERETIC)
73
  - **HF repository slug renamed `FoolDev/Janus-35B` → `FoolDev/Janus-35B-HERETIC`.**
 
55
  dense sibling's exact wording and drops the ambiguous `262k` rounding
56
  (262 144 = 256 × 1024, not 262 000).
57
 
58
+ ### Added (examples/ suite + vision tooling — parity with Thanatos-27B)
59
+ - **`examples/` directory, `scripts/fetch_vision.sh`, and a `## Vision`
60
+ README section.** Ports the dense sibling's runnable clients and vision path,
61
+ adapted to Janus: bundle `Janus-35B-A3B.Q4_K_M.gguf`, arch `qwen35moe`,
62
+ projector `Qwen3.6-35B-A3B-uncensored-heretic-mmproj-BF16.gguf` (~903 MB,
63
+ BF16 only) from `llmfan46/Qwen3.6-35B-A3B-uncensored-heretic-GGUF`, with the
64
  `unsloth/Qwen3.6-35B-A3B-GGUF` `mmproj-F16.gguf` as the F16/F32 fallback.
65
+ - `examples/ollama_chat.py` (chat / streaming / tool round-trip /
66
+ OpenAI-compat, default `MODEL=janus`), `examples/transformers_quickstart.py`
67
+ (Heretic safetensors, 4-bit ~20 GB / bf16 ~70 GB, MoE keeps all experts
68
+ resident), `examples/llama_cpp_quickstart.py` (GGUF, no daemon),
69
+ `examples/llama_cpp_vision.py` (image-text-to-text via llama-cpp-python),
70
+ and `examples/README.md`. The index is adapted to Janus's direct-script
71
+ flow (`./scripts/build.sh`, `./scripts/load_bundle.sh`,
72
+ `./scripts/fetch_vision.sh`) — Janus has no Makefile and, being already
73
+ `qwen35moe`-stamped, no heal/rebadge step.
74
+ - `fetch_vision.sh` pulls the projector; the Vision section documents the
75
+ llama.cpp loader path and the Ollama-vision-broken caveat (`unknown model
76
+ architecture: 'qwen35moe'`, ollama/ollama#15898).
77
+ - "What's here" table + "Inference examples" pointer updated.
78
 
79
  ### Changed (HF repo rename → FoolDev/Janus-35B-HERETIC)
80
  - **HF repository slug renamed `FoolDev/Janus-35B` → `FoolDev/Janus-35B-HERETIC`.**
README.md CHANGED
@@ -103,7 +103,7 @@ and `thinking` under Capabilities. Hardware: ~38 GB RAM at default
103
  | `scripts/bench.sh` | Measures tok/s from Ollama's `eval_count` / `eval_duration` over a short/medium/long prompt mix (with a discarded warmup). Defaults to `MODEL=janus`. |
104
  | `scripts/load_bundle.sh` | Loads the bundled `Janus-35B-A3B.Q4_K_M.gguf` into Ollama as a local `janus` tag without an upstream pull (smudges the LFS pointer via `hf download` if needed, checks the arch is `qwen35moe`). |
105
  | `scripts/fetch_vision.sh` | Downloads the vision projector (`Qwen3.6-35B-A3B-uncensored-heretic-mmproj-BF16.gguf`) from the Heretic GGUF repo for llama.cpp image input (Ollama vision is broken upstream — see [Vision](#vision)). |
106
- | `examples/llama_cpp_vision.py` | Minimal image-text-to-text example via `llama-cpp-python`; the projector attaches here, since Ollama can't attach it yet. |
107
 
108
  GGUF-only release. Pull the Heretic safetensors from [`llmfan46/Qwen3.6-35B-A3B-uncensored-heretic`](https://huggingface.co/llmfan46/Qwen3.6-35B-A3B-uncensored-heretic) if you need the `transformers` tree (or the vanilla pre-Heretic base from [`Qwen/Qwen3.6-35B-A3B`](https://huggingface.co/Qwen/Qwen3.6-35B-A3B)).
109
 
@@ -150,7 +150,7 @@ After either path, `ollama show janus` should list `completion`, `tools`, and `t
150
 
151
  ### Inference examples
152
 
153
- Once the model is loaded (via `ollama run janus`, `lms server`, or `llama-server`), all the standard OpenAI-compatible clients work. Examples assume the loader is listening on `http://localhost:11434` (Ollama default) — adjust the port for LM Studio (`:1234`) or llama.cpp (`:8080`).
154
 
155
  #### curl
156
 
 
103
  | `scripts/bench.sh` | Measures tok/s from Ollama's `eval_count` / `eval_duration` over a short/medium/long prompt mix (with a discarded warmup). Defaults to `MODEL=janus`. |
104
  | `scripts/load_bundle.sh` | Loads the bundled `Janus-35B-A3B.Q4_K_M.gguf` into Ollama as a local `janus` tag without an upstream pull (smudges the LFS pointer via `hf download` if needed, checks the arch is `qwen35moe`). |
105
  | `scripts/fetch_vision.sh` | Downloads the vision projector (`Qwen3.6-35B-A3B-uncensored-heretic-mmproj-BF16.gguf`) from the Heretic GGUF repo for llama.cpp image input (Ollama vision is broken upstream — see [Vision](#vision)). |
106
+ | `examples/` | Ready-to-run Python clients for Ollama, Transformers, and llama-cpp-python (text, tools, and vision see `examples/README.md`) |
107
 
108
  GGUF-only release. Pull the Heretic safetensors from [`llmfan46/Qwen3.6-35B-A3B-uncensored-heretic`](https://huggingface.co/llmfan46/Qwen3.6-35B-A3B-uncensored-heretic) if you need the `transformers` tree (or the vanilla pre-Heretic base from [`Qwen/Qwen3.6-35B-A3B`](https://huggingface.co/Qwen/Qwen3.6-35B-A3B)).
109
 
 
150
 
151
  ### Inference examples
152
 
153
+ Once the model is loaded (via `ollama run janus`, `lms server`, or `llama-server`), all the standard OpenAI-compatible clients work. Examples assume the loader is listening on `http://localhost:11434` (Ollama default) — adjust the port for LM Studio (`:1234`) or llama.cpp (`:8080`). Runnable versions of everything below live in [`examples/`](examples/README.md).
154
 
155
  #### curl
156
 
examples/README.md ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Janus-35B examples
2
+
3
+ Four minimal entry points. Pick the one that matches how you run models.
4
+
5
+ | File | Backend | When to use |
6
+ |---|---|---|
7
+ | `ollama_chat.py` | Ollama HTTP API | You already have `ollama serve` running and the `janus` model created from the project `Modelfile`. **Text + tool calling** — vision via Ollama is broken upstream for this arch. |
8
+ | `transformers_quickstart.py` | Hugging Face Transformers | You want to run the Heretic safetensors (`llmfan46/Qwen3.6-35B-A3B-uncensored-heretic`) on GPU, optionally in 4-bit via bitsandbytes. |
9
+ | `llama_cpp_quickstart.py` | llama-cpp-python | You want to invoke a local GGUF directly without a daemon (CI, batch jobs, scripts). Text only. |
10
+ | `llama_cpp_vision.py` | llama-cpp-python + mmproj | **Image input.** Loads a text GGUF + `Qwen3.6-35B-A3B-uncensored-heretic-mmproj-BF16.gguf` and answers questions about an image. The only working vision path right now. |
11
+
12
+ All four apply the same Janus system prompt and sampling defaults
13
+ (`temp=0.6, top_p=0.95, top_k=20, repeat_penalty=1.05`) so behavior should
14
+ be consistent across backends modulo quantization noise. The three
15
+ non-Ollama scripts set them explicitly; `ollama_chat.py` inherits them
16
+ from the `Modelfile` / bridge files.
17
+
18
+ ## Setup
19
+
20
+ ### Ollama
21
+
22
+ Pull straight from HF (gets the bundled Q4_K_M GGUF + this repo's
23
+ root-level `template` / `system` / `params` files via HF's Ollama
24
+ bridge):
25
+
26
+ ```bash
27
+ ollama pull hf.co/FoolDev/Janus-35B-HERETIC # ~19 GB Q4_K_M (only bundled quant)
28
+ pip install requests
29
+ MODEL=hf.co/FoolDev/Janus-35B-HERETIC python ollama_chat.py
30
+ ```
31
+
32
+ Or build a local `janus` tag from this repo's bundled GGUF without going
33
+ through the HF pull:
34
+
35
+ ```bash
36
+ cd .. && ./scripts/load_bundle.sh && cd examples
37
+ python ollama_chat.py # MODEL defaults to `janus`
38
+ ```
39
+
40
+ For a non-bundled quant (e.g. Q3_K_M ~14 GB, Q5_K_M ~24 GB),
41
+ `./scripts/build.sh` downloads it from
42
+ `llmfan46/Qwen3.6-35B-A3B-uncensored-heretic-GGUF`, patches the `Modelfile`
43
+ `FROM` line into a temp copy, and creates the local `janus` tag:
44
+
45
+ ```bash
46
+ cd .. && QUANT=Q5_K_M ./scripts/build.sh && cd examples
47
+ python ollama_chat.py
48
+ ```
49
+
50
+ ### Transformers (safetensors)
51
+
52
+ ```bash
53
+ pip install --upgrade "transformers>=4.45" accelerate sentencepiece bitsandbytes
54
+ python transformers_quickstart.py # 4-bit, ~20 GB VRAM
55
+ python transformers_quickstart.py --no-4bit # bf16, ~70 GB VRAM
56
+ ```
57
+
58
+ ### llama-cpp-python (GGUF, no daemon)
59
+
60
+ ```bash
61
+ pip install llama-cpp-python # CPU-only build
62
+ python llama_cpp_quickstart.py /path/to/Janus-35B-A3B.Q4_K_M.gguf --gpu-layers 99
63
+ ```
64
+
65
+ For GPU offload, rebuild llama-cpp-python with the matching backend — see
66
+ the script header for `CMAKE_ARGS` recipes (CUDA, Metal, ROCm/HIP).
67
+
68
+ ### Vision (image input)
69
+
70
+ ```bash
71
+ # Pull the projector once (~903 MB) into the repo root:
72
+ cd .. && ./scripts/fetch_vision.sh && cd examples
73
+
74
+ pip install llama-cpp-python pillow
75
+ python llama_cpp_vision.py \
76
+ --gguf ../Janus-35B-A3B.Q4_K_M.gguf \
77
+ --mmproj ../Qwen3.6-35B-A3B-uncensored-heretic-mmproj-BF16.gguf \
78
+ --image /path/to/photo.jpg \
79
+ --prompt "Describe this image."
80
+ ```
81
+
82
+ Why not Ollama? Ollama's Go engine has the `qwen35` / `qwen35moe`
83
+ arch entries (text inference works in 0.24+), but the C++ llama.cpp
84
+ fallback that Ollama switches to when an mmproj is attached still
85
+ lacks them. `ollama create` accepts the dual-`FROM` and `ollama show`
86
+ reports `vision` capability, but the first inference call fails with
87
+ `error loading model architecture: unknown model architecture:
88
+ 'qwen35moe'`. Tracked in
89
+ [ollama/ollama#15898](https://github.com/ollama/ollama/issues/15898).
90
+ Until that's fixed, llama.cpp / llama-cpp-python is the working path
91
+ for vision.
examples/llama_cpp_quickstart.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Janus-35B — llama-cpp-python quickstart.
4
+
5
+ Skip Ollama entirely and call the GGUF directly through llama-cpp-python.
6
+ Useful for batch jobs, CI, or environments where you don't want a daemon.
7
+
8
+ Install:
9
+ pip install llama-cpp-python
10
+
11
+ For GPU offload (CUDA / Metal / ROCm), install with the matching extras:
12
+ CMAKE_ARGS="-DGGML_CUDA=on" pip install llama-cpp-python --no-binary :all:
13
+ CMAKE_ARGS="-DGGML_METAL=on" pip install llama-cpp-python --no-binary :all:
14
+ CMAKE_ARGS="-DGGML_HIPBLAS=on" pip install llama-cpp-python --no-binary :all:
15
+
16
+ Usage:
17
+ python llama_cpp_quickstart.py /path/to/Janus-35B-A3B.Q4_K_M.gguf
18
+ python llama_cpp_quickstart.py /path/to/file.gguf --gpu-layers 99
19
+ python llama_cpp_quickstart.py /path/to/file.gguf --prompt "..."
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import argparse
24
+ import sys
25
+
26
+ try:
27
+ from llama_cpp import Llama
28
+ except ImportError: # pragma: no cover
29
+ sys.exit("Missing llama-cpp-python. Install with: pip install llama-cpp-python")
30
+
31
+
32
+ JANUS_SYSTEM = (
33
+ "You are Janus, a precise and capable assistant for reasoning, writing, "
34
+ "coding, and long-form dialogue.\n\n"
35
+ "Behavior rules:\n"
36
+ "- Answer the user's actual request directly.\n"
37
+ "- Be accurate, complete, and structured.\n"
38
+ "- Think before answering, but do not get stuck in repetitive loops.\n"
39
+ "- If the request is ambiguous, state what is missing and make the smallest "
40
+ "reasonable assumption needed to continue.\n"
41
+ "- Finish with a usable answer, not just planning."
42
+ )
43
+
44
+
45
+ def main() -> None:
46
+ ap = argparse.ArgumentParser()
47
+ ap.add_argument("gguf", help="Path to Janus-35B-A3B GGUF (e.g. Q4_K_M).")
48
+ ap.add_argument(
49
+ "--prompt",
50
+ default="Explain the Burrows-Wheeler transform in 200 words.",
51
+ )
52
+ ap.add_argument("--ctx", type=int, default=16384, help="Context window.")
53
+ ap.add_argument(
54
+ "--gpu-layers",
55
+ type=int,
56
+ default=0,
57
+ help="Layers to offload to GPU (-1 or 99 = all).",
58
+ )
59
+ ap.add_argument("--max-tokens", type=int, default=512)
60
+ args = ap.parse_args()
61
+
62
+ llm = Llama(
63
+ model_path=args.gguf,
64
+ n_ctx=args.ctx,
65
+ n_gpu_layers=args.gpu_layers,
66
+ verbose=False,
67
+ )
68
+
69
+ out = llm.create_chat_completion(
70
+ messages=[
71
+ {"role": "system", "content": JANUS_SYSTEM},
72
+ {"role": "user", "content": args.prompt},
73
+ ],
74
+ temperature=0.6,
75
+ top_p=0.95,
76
+ top_k=20,
77
+ repeat_penalty=1.05,
78
+ max_tokens=args.max_tokens,
79
+ )
80
+ print(out["choices"][0]["message"]["content"])
81
+
82
+
83
+ if __name__ == "__main__":
84
+ main()
examples/ollama_chat.py ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Janus-35B — Ollama chat examples.
4
+
5
+ Prerequisites (pick one):
6
+
7
+ A. From the bundled GGUF (default flow):
8
+ $ ../scripts/load_bundle.sh # local `janus` tag from the bundled Q4_K_M
9
+ # or build another quant:
10
+ $ ../scripts/build.sh # default Q4_K_M -> `ollama create janus`
11
+ # or straight from the Modelfile:
12
+ $ ollama create janus -f ../Modelfile
13
+
14
+ B. Pull straight from HF (Q4_K_M is the only bundled quant):
15
+ $ ollama run hf.co/FoolDev/Janus-35B-HERETIC
16
+ # then set MODEL=hf.co/FoolDev/Janus-35B-HERETIC below
17
+
18
+ Then:
19
+ $ ollama serve # usually already running
20
+ $ python ollama_chat.py
21
+
22
+ The model emits <think>...</think> reasoning blocks before its answer.
23
+ Current Ollama (0.24, especially with `OLLAMA_NEW_ENGINE=1`) returns the
24
+ reasoning in a separate `message.thinking` field and keeps `content`
25
+ clean. Older builds put the whole `<think>...</think>` block inside
26
+ `content`. The demo below reads `message.thinking` first and falls
27
+ back to parsing `<think>` tags out of `content` so it works against
28
+ either path.
29
+
30
+ Endpoints used:
31
+ - Native Ollama: http://localhost:11434/api/chat
32
+ - OpenAI-compat: http://localhost:11434/v1/chat/completions
33
+ """
34
+ from __future__ import annotations
35
+
36
+ import json
37
+ import os
38
+ import re
39
+ import sys
40
+ from typing import Any, Iterator
41
+
42
+ import requests
43
+
44
+ MODEL = os.environ.get("MODEL", "janus")
45
+ HOST = os.environ.get("HOST", "http://localhost:11434")
46
+
47
+ _THINK_RE = re.compile(r"<think>.*?</think>\s*", re.DOTALL)
48
+
49
+
50
+ def split_thinking(content: str) -> tuple[str, str]:
51
+ """Return (thinking, final_answer) from a content string."""
52
+ parts = re.findall(r"<think>(.*?)</think>", content, re.DOTALL)
53
+ thinking = "\n".join(p.strip() for p in parts).strip()
54
+ answer = _THINK_RE.sub("", content).strip()
55
+ return thinking, answer
56
+
57
+
58
+ # ---------- 1. Simple chat ----------
59
+
60
+ def chat(prompt: str, system: str | None = None) -> dict[str, Any]:
61
+ msgs: list[dict[str, Any]] = []
62
+ if system:
63
+ msgs.append({"role": "system", "content": system})
64
+ msgs.append({"role": "user", "content": prompt})
65
+ r = requests.post(
66
+ f"{HOST}/api/chat",
67
+ json={"model": MODEL, "messages": msgs, "stream": False},
68
+ timeout=600,
69
+ )
70
+ r.raise_for_status()
71
+ return r.json()
72
+
73
+
74
+ # ---------- 2. Streaming ----------
75
+
76
+ def chat_stream(prompt: str) -> Iterator[str]:
77
+ """Yield content tokens as they arrive."""
78
+ with requests.post(
79
+ f"{HOST}/api/chat",
80
+ json={
81
+ "model": MODEL,
82
+ "messages": [{"role": "user", "content": prompt}],
83
+ "stream": True,
84
+ },
85
+ stream=True,
86
+ timeout=600,
87
+ ) as r:
88
+ r.raise_for_status()
89
+ for line in r.iter_lines():
90
+ if not line:
91
+ continue
92
+ chunk = json.loads(line)
93
+ if "message" in chunk and "content" in chunk["message"]:
94
+ yield chunk["message"]["content"]
95
+ if chunk.get("done"):
96
+ break
97
+
98
+
99
+ # ---------- 3. Tool calling ----------
100
+
101
+ WEATHER_TOOL = {
102
+ "type": "function",
103
+ "function": {
104
+ "name": "get_current_weather",
105
+ "description": "Get the current weather in a given city",
106
+ "parameters": {
107
+ "type": "object",
108
+ "properties": {
109
+ "city": {"type": "string", "description": "City name"},
110
+ "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
111
+ },
112
+ "required": ["city", "unit"],
113
+ },
114
+ },
115
+ }
116
+
117
+
118
+ def fake_weather(city: str, unit: str) -> str:
119
+ """Stand-in tool implementation."""
120
+ return json.dumps(
121
+ {"city": city, "temperature": 14, "unit": unit, "conditions": "light rain"}
122
+ )
123
+
124
+
125
+ def tool_round_trip(prompt: str) -> str:
126
+ """Single-shot tool call: model -> tool -> model -> final answer."""
127
+ history: list[dict[str, Any]] = [{"role": "user", "content": prompt}]
128
+ r = requests.post(
129
+ f"{HOST}/api/chat",
130
+ json={
131
+ "model": MODEL,
132
+ "messages": history,
133
+ "tools": [WEATHER_TOOL],
134
+ "stream": False,
135
+ },
136
+ timeout=600,
137
+ )
138
+ r.raise_for_status()
139
+ msg = r.json()["message"]
140
+
141
+ if not msg.get("tool_calls"):
142
+ return msg["content"]
143
+
144
+ history.append({"role": "assistant", "tool_calls": msg["tool_calls"]})
145
+ for tc in msg["tool_calls"]:
146
+ fn = tc["function"]
147
+ if fn["name"] == "get_current_weather":
148
+ result = fake_weather(**fn["arguments"])
149
+ else:
150
+ result = json.dumps({"error": f"unknown tool {fn['name']}"})
151
+ history.append({"role": "tool", "tool_name": fn["name"], "content": result})
152
+
153
+ r = requests.post(
154
+ f"{HOST}/api/chat",
155
+ json={
156
+ "model": MODEL,
157
+ "messages": history,
158
+ "tools": [WEATHER_TOOL],
159
+ "stream": False,
160
+ },
161
+ timeout=600,
162
+ )
163
+ r.raise_for_status()
164
+ return r.json()["message"]["content"]
165
+
166
+
167
+ # ---------- 4. OpenAI-compatible endpoint ----------
168
+
169
+ def openai_chat(prompt: str) -> str:
170
+ r = requests.post(
171
+ f"{HOST}/v1/chat/completions",
172
+ json={
173
+ "model": MODEL,
174
+ "messages": [{"role": "user", "content": prompt}],
175
+ "temperature": 0.6,
176
+ },
177
+ timeout=600,
178
+ )
179
+ r.raise_for_status()
180
+ return r.json()["choices"][0]["message"]["content"]
181
+
182
+
183
+ # ---------- demo ----------
184
+
185
+ def _demo() -> None:
186
+ print("=== 1. simple chat ===")
187
+ resp = chat("What is 84 * 3 / 2?")
188
+ msg = resp["message"]
189
+ # Prefer the dedicated `thinking` field (Ollama 0.24+ / new engine);
190
+ # fall back to extracting <think>...</think> from `content` for
191
+ # older builds that inline the reasoning.
192
+ thinking = (msg.get("thinking") or "").strip()
193
+ answer = msg.get("content", "")
194
+ if not thinking:
195
+ thinking, answer = split_thinking(answer)
196
+ if thinking:
197
+ print(f"[thinking] {thinking[:200]}...")
198
+ print(f"[answer] {answer}")
199
+
200
+ print("\n=== 2. streaming ===")
201
+ for tok in chat_stream("Count from 1 to 5 in one line."):
202
+ sys.stdout.write(tok)
203
+ sys.stdout.flush()
204
+ print()
205
+
206
+ print("\n=== 3. tool round-trip ===")
207
+ print(tool_round_trip("What is the weather in Paris in celsius?"))
208
+
209
+ print("\n=== 4. OpenAI-compat ===")
210
+ print(openai_chat("Say 'OpenAI endpoint OK' and nothing else."))
211
+
212
+
213
+ if __name__ == "__main__":
214
+ _demo()
examples/transformers_quickstart.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Janus-35B — Hugging Face Transformers quickstart.
4
+
5
+ Loads the Heretic Qwen 3.6 35B-A3B safetensors directly and runs a single
6
+ chat turn using its embedded chat template. Janus-35B is a *wrapper*
7
+ around that base, so for the transformers route there is nothing to
8
+ download from this repo — point at llmfan46/Qwen3.6-35B-A3B-uncensored-heretic
9
+ and apply the same system prompt the Modelfile uses.
10
+
11
+ Set MODEL_ID = "Qwen/Qwen3.6-35B-A3B" to bypass the Heretic abliteration and
12
+ load the vanilla upstream base instead.
13
+
14
+ Note: this is a mixture-of-experts model — 35B total / 3B active per token.
15
+ Transformers keeps all 256 experts resident, so memory tracks the 35B total,
16
+ not the 3B active count.
17
+
18
+ Requirements:
19
+ pip install --upgrade "transformers>=4.45" accelerate sentencepiece bitsandbytes
20
+
21
+ Memory:
22
+ - bf16 full precision: ~70 GB VRAM (multi-GPU or CPU offload).
23
+ - 4-bit (bitsandbytes nf4): ~20 GB VRAM, fits a 24 GB card (3090/4090)
24
+ at modest context.
25
+ - Fall back to device_map="auto" + bnb_4bit on consumer GPUs.
26
+
27
+ Usage:
28
+ python transformers_quickstart.py
29
+ python transformers_quickstart.py --no-4bit # bf16, needs ~70 GB VRAM
30
+ python transformers_quickstart.py --prompt "..." # custom prompt
31
+ """
32
+ from __future__ import annotations
33
+
34
+ import argparse
35
+ import sys
36
+
37
+ try:
38
+ import torch
39
+ from transformers import AutoModelForCausalLM, AutoTokenizer
40
+ except ImportError as e: # pragma: no cover
41
+ sys.exit(
42
+ f"Missing dependency: {e.name}. Install with:\n"
43
+ " pip install --upgrade 'transformers>=4.45' accelerate sentencepiece bitsandbytes"
44
+ )
45
+
46
+
47
+ MODEL_ID = "llmfan46/Qwen3.6-35B-A3B-uncensored-heretic"
48
+
49
+ JANUS_SYSTEM = (
50
+ "You are Janus, a precise and capable assistant for reasoning, writing, "
51
+ "coding, and long-form dialogue.\n\n"
52
+ "Behavior rules:\n"
53
+ "- Answer the user's actual request directly.\n"
54
+ "- Be accurate, complete, and structured.\n"
55
+ "- Think before answering, but do not get stuck in repetitive loops or "
56
+ "meta-commentary.\n"
57
+ "- If the request is ambiguous or incomplete, state what is missing and "
58
+ "make the smallest reasonable assumption needed to continue.\n"
59
+ "- If the user wants creative writing, preserve tone, continuity, and "
60
+ "character consistency.\n"
61
+ "- If the user wants analysis or technical help, prefer concrete steps, "
62
+ "examples, and decisions over fluff.\n"
63
+ "- Finish with a usable answer, not just planning."
64
+ )
65
+
66
+
67
+ def load(use_4bit: bool):
68
+ kwargs: dict = {"device_map": "auto", "torch_dtype": torch.bfloat16}
69
+ if use_4bit:
70
+ from transformers import BitsAndBytesConfig
71
+ kwargs["quantization_config"] = BitsAndBytesConfig(
72
+ load_in_4bit=True,
73
+ bnb_4bit_quant_type="nf4",
74
+ bnb_4bit_compute_dtype=torch.bfloat16,
75
+ bnb_4bit_use_double_quant=True,
76
+ )
77
+ kwargs.pop("torch_dtype", None)
78
+
79
+ tok = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True)
80
+ model = AutoModelForCausalLM.from_pretrained(MODEL_ID, trust_remote_code=True, **kwargs)
81
+ return tok, model
82
+
83
+
84
+ def generate(tok, model, prompt: str, max_new_tokens: int = 512) -> str:
85
+ messages = [
86
+ {"role": "system", "content": JANUS_SYSTEM},
87
+ {"role": "user", "content": prompt},
88
+ ]
89
+ inputs = tok.apply_chat_template(
90
+ messages,
91
+ add_generation_prompt=True,
92
+ return_tensors="pt",
93
+ ).to(model.device)
94
+
95
+ out = model.generate(
96
+ inputs,
97
+ max_new_tokens=max_new_tokens,
98
+ do_sample=True,
99
+ temperature=0.6,
100
+ top_p=0.95,
101
+ top_k=20,
102
+ repetition_penalty=1.05,
103
+ )
104
+ return tok.decode(out[0][inputs.shape[-1]:], skip_special_tokens=True)
105
+
106
+
107
+ def main() -> None:
108
+ ap = argparse.ArgumentParser()
109
+ ap.add_argument("--prompt", default="Explain the Burrows-Wheeler transform in 200 words.")
110
+ ap.add_argument(
111
+ "--no-4bit",
112
+ action="store_true",
113
+ help="Disable 4-bit quantization (requires ~70 GB VRAM in bf16).",
114
+ )
115
+ ap.add_argument("--max-new-tokens", type=int, default=512)
116
+ args = ap.parse_args()
117
+
118
+ print(f"[load] {MODEL_ID} (4bit={'no' if args.no_4bit else 'yes'})")
119
+ tok, model = load(use_4bit=not args.no_4bit)
120
+
121
+ print(f"[gen] prompt: {args.prompt!r}")
122
+ print()
123
+ print(generate(tok, model, args.prompt, args.max_new_tokens))
124
+
125
+
126
+ if __name__ == "__main__":
127
+ main()