diff --git a/studio/frontend/src/index.css b/studio/frontend/src/index.css
index 8dc159a002..45696b2274 100644
--- a/studio/frontend/src/index.css
+++ b/studio/frontend/src/index.css
@@ -6,21 +6,11 @@
@import "shadcn/tailwind.css";
@import "streamdown/styles.css";
@import "@fontsource-variable/figtree";
-@import "@fontsource-variable/space-grotesk";
@import "@fontsource-variable/inter";
@import "tw-shimmer";
@plugin "@toolwind/corner-shape";
@source "../node_modules/streamdown/dist/*.js";
-@font-face {
- font-family: "Hellix";
- src: url("/fonts/Hellix-SemiBold.woff2") format("woff2"),
- url("/fonts/Hellix-SemiBold.woff") format("woff");
- font-weight: 600;
- font-style: normal;
- font-display: swap;
-}
-
@custom-variant dark (&:is(.dark *));
:root {
@@ -67,7 +57,7 @@
--sidebar-ring: oklch(0.6929 0.1396 166.5513);
--destructive-foreground: oklch(1 0 0);
--font-sans: "Inter Variable", ui-sans-serif, sans-serif, system-ui;
- --font-heading: "Hellix", "Space Grotesk Variable", ui-sans-serif, sans-serif;
+ --font-heading: var(--font-sans);
--font-serif: Source Serif 4, serif;
--font-mono: JetBrains Mono, monospace;
--shadow-color: hsl(0 0% 0%);
@@ -159,7 +149,7 @@
@theme inline {
--font-sans: "Inter Variable", ui-sans-serif, sans-serif, system-ui;
- --font-heading: "Hellix", "Space Grotesk Variable", ui-sans-serif, sans-serif;
+ --font-heading: var(--font-sans);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
@@ -343,10 +333,68 @@
[data-streamdown="code-block"] {
gap: 0;
padding: 0.5rem;
+ /* Wide lines must scroll inside the thread column, not widen past the composer (flex min-width:auto). */
+ max-width: 100%;
+ min-width: 0;
+ overflow-x: auto;
}
[data-streamdown="code-block-header"] {
padding-left: 0.75rem;
}
+
+ /* Chat thread: code slightly smaller by default; step up when the thread column is wide. */
+ .aui-thread-root [data-streamdown="code-block"] {
+ font-size: 0.8125rem;
+ line-height: 1.55;
+ }
+
+ .aui-thread-root [data-streamdown="code-block-header"] {
+ font-size: 0.6875rem;
+ }
+
+ @container (min-width: 36rem) {
+ .aui-thread-root [data-streamdown="code-block"] {
+ font-size: 0.875rem;
+ }
+
+ .aui-thread-root [data-streamdown="code-block-header"] {
+ font-size: 0.75rem;
+ }
+ }
+
+ /* Chat: use the app sans stack for UI + prose. */
+ .aui-thread-root {
+ font-family: var(--font-sans);
+ }
+
+ .aui-thread-root .font-medium,
+ .aui-thread-root .font-semibold,
+ .aui-thread-root .font-bold,
+ .aui-thread-root .font-heading {
+ font-family: var(--font-sans);
+ }
+
+ .aui-thread-root :where(h1, h2, h3, h4, h5, h6) {
+ font-family: var(--font-sans);
+ }
+
+ /* Keep monospace for code fences and inline code (not KaTeX). */
+ .aui-thread-root [data-streamdown="code-block"] pre,
+ .aui-thread-root [data-streamdown="code-block"] code {
+ font-family: var(--font-mono), ui-monospace, monospace;
+ }
+
+ .aui-thread-root :where(p, li, td, th, blockquote, h1, h2, h3, h4, h5, h6) code {
+ font-family: var(--font-mono), ui-monospace, monospace;
+ }
+
+ /* Align fenced code blocks with the main chat column even when nested in lists. */
+ .aui-thread-root [data-streamdown="list-item"] > [data-streamdown="code-block"],
+ .aui-thread-root [data-streamdown="list-item"] [data-streamdown="code-block"] {
+ margin-left: -1.25rem;
+ width: calc(100% + 1.25rem);
+ max-width: calc(100% + 1.25rem);
+ }
}
/* Minimal scrollbar — thumb only, no track */
From ba8081fc96f8253bbeae846961e1c5812491e25b Mon Sep 17 00:00:00 2001
From: AdamPlatin123 <154926636+AdamPlatin123@users.noreply.github.com>
Date: Thu, 2 Apr 2026 11:24:48 +0800
Subject: [PATCH 002/216] fix(chat): correct loading text for cached models
during inference (#4764)
Distinguish between actual network downloads and GPU memory loading for cached LoRA adapters in Studio chat.
- Add isCachedLora detection for local LoRA adapter paths using comprehensive cross-platform regex (Unix, Windows, UNC, WSL, tilde)
- Thread isCachedLora through loadInfo to chat-page inline status for proper 3-way distinction (cached / local LoRA / downloading)
- Skip download progress polling for cached LoRA models (no useless /download-progress API calls)
- Fix initial toast state to use isCachedLoad consistently instead of only checking isDownloaded
- Fix cancelLoading toast to not mention background downloads for cached/local loads
- Keep download-specific text ("Downloading model..." / "Download complete") inside the download-only polling block
---
.../frontend/src/features/chat/chat-page.tsx | 6 +++--
.../chat/hooks/use-chat-model-runtime.ts | 23 +++++++++++++------
2 files changed, 20 insertions(+), 9 deletions(-)
diff --git a/studio/frontend/src/features/chat/chat-page.tsx b/studio/frontend/src/features/chat/chat-page.tsx
index 8d0a9649b4..08450c7ec7 100644
--- a/studio/frontend/src/features/chat/chat-page.tsx
+++ b/studio/frontend/src/features/chat/chat-page.tsx
@@ -866,14 +866,16 @@ export function ChatPage(): ReactElement {
label={
loadProgress?.phase === "starting"
? "Starting model…"
- : loadingModel.isDownloaded
+ : loadingModel.isDownloaded || loadingModel.isCachedLora
? "Loading model…"
: "Downloading model…"
}
title={
loadingModel.isDownloaded
? `Loading ${loadingModel.displayName} from cache.`
- : `Loading ${loadingModel.displayName}. This may include downloading.`
+ : loadingModel.isCachedLora
+ ? `Loading ${loadingModel.displayName} into memory.`
+ : `Loading ${loadingModel.displayName}. This may include downloading.`
}
progressPercent={loadProgress?.percent}
progressLabel={loadProgress?.label}
diff --git a/studio/frontend/src/features/chat/hooks/use-chat-model-runtime.ts b/studio/frontend/src/features/chat/hooks/use-chat-model-runtime.ts
index fc0e392596..8fcd22d0f8 100644
--- a/studio/frontend/src/features/chat/hooks/use-chat-model-runtime.ts
+++ b/studio/frontend/src/features/chat/hooks/use-chat-model-runtime.ts
@@ -168,6 +168,7 @@ export function useChatModelRuntime() {
id: string;
displayName: string;
isDownloaded?: boolean;
+ isCachedLora?: boolean;
} | null>(null);
const [loadToastDismissed, setLoadToastDismissed] = useState(false);
const [loadProgress, setLoadProgress] = useState<{
@@ -294,8 +295,11 @@ export function useChatModelRuntime() {
setLoadToastDismissedState(false);
clearCheckpoint();
if (tid != null) toast.dismiss(tid);
+ const isCachedOrLocal = model.isDownloaded || model.isCachedLora;
toast.info("Stopped loading model", {
- description: "The current download may still finish in the background.",
+ description: isCachedOrLocal
+ ? undefined
+ : "The current download may still finish in the background.",
});
// Fire-and-forget: tell backend to stop, don't block UI
unloadModel({ model_path: model.id }).catch(() => {});
@@ -339,20 +343,24 @@ export function useChatModelRuntime() {
: undefined;
const previousIsLora =
previousModel?.isLora ?? (previousLora ? true : false);
+ // Covers Unix absolute (/), relative (./ ../), tilde (~/), Windows drive (C:\), UNC (\\server)
+ const isLocal = /^(\/|\.{1,2}[\\\/]|~[\\\/]|[A-Za-z]:[\\\/]|\\\\)/.test(modelId);
+ const isCachedLora = isLora && isLocal;
const loadingDescription = [
currentCheckpoint ? "Switching models." : null,
extraLoadingDescription ?? null,
isDownloaded ? "Loading cached model into memory." : null,
+ !isDownloaded && isCachedLora ? "Loading trained model into memory." : null,
]
.filter(Boolean)
.join(" ");
setModelsError(null);
setLoadToastDismissedState(false);
- const loadInfo = { id: modelId, displayName, isDownloaded };
+ const loadInfo = { id: modelId, displayName, isDownloaded, isCachedLora };
setLoadingModel(loadInfo);
useChatRuntimeStore.getState().setModelLoading(true);
setLoadProgress(
- isDownloaded
+ isDownloaded || isCachedLora
? { percent: null, label: null, phase: "starting" }
: { percent: 0, label: "Preparing download", phase: "downloading" },
);
@@ -485,15 +493,16 @@ export function useChatModelRuntime() {
}
}
- const toastTitle = isDownloaded ? "Starting model…" : "Downloading model…";
+ const isCachedLoad = isDownloaded || isCachedLora;
+ const toastTitle = isCachedLoad ? "Starting model…" : "Downloading model…";
const toastId = toast(
null,
{
description: renderLoadDescription(
toastTitle,
loadingDescription,
- isDownloaded ? null : 0,
- isDownloaded ? null : "Preparing download",
+ isCachedLoad ? null : 0,
+ isCachedLoad ? null : "Preparing download",
cancelLoading,
),
duration: Infinity,
@@ -511,7 +520,7 @@ export function useChatModelRuntime() {
// Poll download progress for non-cached models (GGUF and non-GGUF)
let progressInterval: ReturnType
| null = null;
- if (!isDownloaded) {
+ if (!isDownloaded && !isCachedLora) {
const expectedBytes =
typeof selection !== "string" ? selection.expectedBytes ?? 0 : 0;
let hasShownProgress = false;
From 752cef329906d860188cda3fdaf7becaa2413940 Mon Sep 17 00:00:00 2001
From: mateeaaaaaaa
Date: Thu, 2 Apr 2026 10:10:43 +0300
Subject: [PATCH 003/216] fix(security): shell injection in GGML export
conversion (#4768)
* Fix shell injection in GGML conversion paths
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Remove test file from security fix PR
Move test_save_shell_injection.py to a separate PR to keep this PR focused on the security fix itself.
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Daniel Han
---
unsloth/save.py | 26 ++++++++++++++++----------
1 file changed, 16 insertions(+), 10 deletions(-)
diff --git a/unsloth/save.py b/unsloth/save.py
index 178178980d..3c318fab02 100644
--- a/unsloth/save.py
+++ b/unsloth/save.py
@@ -2533,12 +2533,15 @@ def unsloth_convert_lora_to_ggml_and_push_to_hub(
)
print(f"The output file will be {output_file}")
- command = f"python3 llama.cpp/convert-lora-to-ggml.py {lora_directory_push} {output_file} llama"
-
try:
with subprocess.Popen(
- command,
- shell = True,
+ [
+ sys.executable,
+ "llama.cpp/convert-lora-to-ggml.py",
+ lora_directory_push,
+ output_file,
+ "llama",
+ ],
stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
bufsize = 1,
@@ -2550,7 +2553,7 @@ def unsloth_convert_lora_to_ggml_and_push_to_hub(
print(line, end = "", flush = True)
sp.wait()
if sp.returncode != 0:
- raise subprocess.CalledProcessError(sp.returncode, command)
+ raise subprocess.CalledProcessError(sp.returncode, sp.args)
except subprocess.CalledProcessError as e:
print(f"Error: Conversion failed with return code {e.returncode}")
return
@@ -2612,12 +2615,15 @@ def unsloth_convert_lora_to_ggml_and_save_locally(
)
print(f"The output file will be {output_file}")
- command = f"python3 llama.cpp/convert-lora-to-ggml.py {save_directory} {output_file} llama"
-
try:
with subprocess.Popen(
- command,
- shell = True,
+ [
+ sys.executable,
+ "llama.cpp/convert-lora-to-ggml.py",
+ save_directory,
+ output_file,
+ "llama",
+ ],
stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
bufsize = 1,
@@ -2629,7 +2635,7 @@ def unsloth_convert_lora_to_ggml_and_save_locally(
print(line, end = "", flush = True)
sp.wait()
if sp.returncode != 0:
- raise subprocess.CalledProcessError(sp.returncode, command)
+ raise subprocess.CalledProcessError(sp.returncode, sp.args)
except subprocess.CalledProcessError as e:
print(f"Error: Conversion failed with return code {e.returncode}")
return
From dc0729aadfb6aa891468d1432394db59a183eca1 Mon Sep 17 00:00:00 2001
From: Daniel Han
Date: Thu, 2 Apr 2026 00:10:47 -0700
Subject: [PATCH 004/216] Add regression test for shell injection fix in GGML
conversion (#4773)
AST-based test ensures subprocess.Popen calls in GGML conversion functions
use argv lists instead of shell=True. Companion to PR #4768.
---
tests/saving/test_save_shell_injection.py | 74 +++++++++++++++++++++++
1 file changed, 74 insertions(+)
create mode 100644 tests/saving/test_save_shell_injection.py
diff --git a/tests/saving/test_save_shell_injection.py b/tests/saving/test_save_shell_injection.py
new file mode 100644
index 0000000000..c6c2c8fe15
--- /dev/null
+++ b/tests/saving/test_save_shell_injection.py
@@ -0,0 +1,74 @@
+from __future__ import annotations
+
+import ast
+from pathlib import Path
+
+
+SAVE_PY = Path(__file__).resolve().parents[2] / "unsloth" / "save.py"
+
+
+def _function_calls(source: str, function_name: str) -> list[ast.Call]:
+ tree = ast.parse(source, filename = str(SAVE_PY))
+ for node in tree.body:
+ if isinstance(node, ast.FunctionDef) and node.name == function_name:
+ return [child for child in ast.walk(node) if isinstance(child, ast.Call)]
+ raise AssertionError(f"Function {function_name} not found in save.py")
+
+
+def _assert_safe_ggml_calls(calls: list[ast.Call]) -> None:
+ popen_calls = []
+ for call in calls:
+ if isinstance(call.func, ast.Attribute) and call.func.attr == "Popen":
+ if (
+ isinstance(call.func.value, ast.Name)
+ and call.func.value.id == "subprocess"
+ ):
+ popen_calls.append(call)
+
+ assert popen_calls, "Expected at least one subprocess.Popen call"
+
+ ggml_calls = []
+ for call in popen_calls:
+ if not call.args:
+ continue
+ argv = call.args[0]
+ if isinstance(argv, ast.List) and len(argv.elts) >= 2:
+ second_arg = argv.elts[1]
+ if (
+ isinstance(second_arg, ast.Constant)
+ and second_arg.value == "llama.cpp/convert-lora-to-ggml.py"
+ ):
+ ggml_calls.append(call)
+
+ assert ggml_calls, "Expected the GGML conversion subprocess call"
+
+ for call in ggml_calls:
+ shell_kwargs = [
+ keyword
+ for keyword in call.keywords
+ if keyword.arg == "shell"
+ and isinstance(keyword.value, ast.Constant)
+ and keyword.value.value is True
+ ]
+ assert not shell_kwargs, "subprocess.Popen must not use shell=True"
+
+ assert call.args, "subprocess.Popen must receive argv as a positional argument"
+ argv = call.args[0]
+ assert isinstance(
+ argv, ast.List
+ ), "subprocess.Popen must be called with an argv list"
+ assert len(argv.elts) == 5, "GGML conversion argv should have five elements"
+
+ second_arg = argv.elts[1]
+ assert isinstance(second_arg, ast.Constant)
+ assert second_arg.value == "llama.cpp/convert-lora-to-ggml.py"
+
+
+def test_ggml_conversion_paths_do_not_use_shell() -> None:
+ source = SAVE_PY.read_text(encoding = "utf-8")
+ for function_name in (
+ "unsloth_convert_lora_to_ggml_and_push_to_hub",
+ "unsloth_convert_lora_to_ggml_and_save_locally",
+ ):
+ calls = _function_calls(source, function_name)
+ _assert_safe_ggml_calls(calls)
From e4d149923097f25d804177d53e2f2964e7bb1ca7 Mon Sep 17 00:00:00 2001
From: Daniel Han
Date: Thu, 2 Apr 2026 02:11:07 -0700
Subject: [PATCH 005/216] fix(studio): prevent small models from stalling on
tool-calling tasks (#4769)
* fix(studio): prevent small models from stalling on tool-calling tasks
Small GGUF models (< 9B params) in "Think, Search, Code" mode would
often describe what they planned to do ("Let me create this dashboard")
and then stop generating without ever calling a tool.
Three changes:
1. Simplify web_tips for small models: remove the "fetch its full content
by calling web_search with the url parameter" guidance for models < 9B.
This multi-step instruction causes small models to plan elaborate
search-then-fetch-then-code sequences they cannot reliably execute.
2. Add "always call tools directly" imperative to the system prompt nudge
so models act immediately instead of narrating their intentions.
3. Add plan-without-action re-prompt in the agentic loop: when the model
emits planning text (matching patterns like "let me", "I'll", etc.)
without calling any tool, inject a nudge asking it to call the tool
and continue the loop. Capped at 2 re-prompts per request.
Benchmarked with Qwen3.5-4B-GGUF (N=5 trials per variant):
- Baseline: 40% of requests had any tool call
- Combined fix: 100% of requests had at least one tool call
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
---------
Co-authored-by: Daniel Han
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
studio/backend/core/inference/llama_cpp.py | 172 ++++++++++++++------
studio/backend/routes/inference.py | 37 ++++-
studio/backend/utils/models/__init__.py | 2 +
studio/backend/utils/models/model_config.py | 31 ++++
4 files changed, 189 insertions(+), 53 deletions(-)
diff --git a/studio/backend/core/inference/llama_cpp.py b/studio/backend/core/inference/llama_cpp.py
index 5f974b0af3..1b7fe548e4 100644
--- a/studio/backend/core/inference/llama_cpp.py
+++ b/studio/backend/core/inference/llama_cpp.py
@@ -27,6 +27,52 @@ import httpx
logger = get_logger(__name__)
+# ── Pre-compiled patterns for plan-without-action re-prompt ──
+# Forward-looking intent signals that indicate the model is
+# describing what it *will* do rather than giving a final answer.
+_INTENT_SIGNAL = re.compile(
+ r"(?i)("
+ # Direct intent: "I'll ...", "I will ...", "Let me ...", "I am going to ..."
+ # Handles both straight and curly apostrophes.
+ # Excludes "I can", "I should", "I want to", "let's" which
+ # appear frequently in direct answers / explanations.
+ r"\b(i['\u2019](ll|m going to|m gonna)|i am (going to|gonna)|i will|i shall|let me|allow me)\b"
+ r"|"
+ # Step/plan framing: "First ...", "Step 1:", "Here's my plan"
+ r"\b(?:first\b|step \d+:?|here['\u2019]?s (?:my |the |a )?(?:plan|approach))"
+ r"|"
+ # "Now I" / "Next I" patterns
+ r"\b(?:now i|next i)\b"
+ r")"
+)
+_MAX_REPROMPTS = 1
+_REPROMPT_MAX_CHARS = 500
+
+# ── Pre-compiled patterns for GGUF shard detection ───────────
+_SHARD_FULL_RE = re.compile(r"^(.*)-(\d{5})-of-(\d{5})\.gguf$")
+_SHARD_RE = re.compile(r"^(.*)-\d{5}-of-\d{5}\.gguf$")
+
+# Model size extraction (shared with routes/inference.py)
+from utils.models import extract_model_size_b as _extract_model_size_b
+
+# ── Pre-compiled patterns for tool XML stripping ─────────────
+_TOOL_CLOSED_PATS = [
+ re.compile(r".*? ", re.DOTALL),
+ re.compile(r".*? ", re.DOTALL),
+]
+_TOOL_ALL_PATS = _TOOL_CLOSED_PATS + [
+ re.compile(r".*$", re.DOTALL),
+ re.compile(r".*$", re.DOTALL),
+]
+
+# ── Pre-compiled patterns for tool-call XML parsing ──────────
+_TC_JSON_START_RE = re.compile(r"\s*\{")
+_TC_FUNC_START_RE = re.compile(r"\s*")
+_TC_END_TAG_RE = re.compile(r" ")
+_TC_FUNC_CLOSE_RE = re.compile(r"\s* \s*$")
+_TC_PARAM_START_RE = re.compile(r"\s*")
+_TC_PARAM_CLOSE_RE = re.compile(r"\s* \s*$")
+
class LlamaCppBackend:
"""
@@ -242,14 +288,11 @@ class LlamaCppBackend:
@staticmethod
def _get_gguf_size_bytes(model_path: str) -> int:
"""Get total GGUF size in bytes, including split shards."""
- import re
-
main = Path(model_path)
total = main.stat().st_size
# Check for split shards (e.g., model-00001-of-00003.gguf)
- shard_pat = re.compile(r"^(.*)-(\d{5})-of-(\d{5})\.gguf$")
- m = shard_pat.match(main.name)
+ m = _SHARD_FULL_RE.match(main.name)
if m:
prefix, _, num_total = m.group(1), m.group(2), m.group(3)
sibling_pat = re.compile(
@@ -539,8 +582,6 @@ class LlamaCppBackend:
Returns (first_shard_filename, total_size_bytes) or None if nothing fits.
"""
- import re
-
try:
from huggingface_hub import get_paths_info, list_repo_files
@@ -556,10 +597,9 @@ class LlamaCppBackend:
size_map = {p.path: (p.size or 0) for p in path_infos}
# Group files by variant: shards share a prefix before -NNNNN-of-NNNNN
- shard_pat = re.compile(r"^(.*)-\d{5}-of-\d{5}\.gguf$")
variants: dict[str, list[str]] = {}
for f in gguf_files:
- m = shard_pat.match(f)
+ m = _SHARD_RE.match(f)
key = m.group(1) if m else f
variants.setdefault(key, []).append(f)
@@ -810,7 +850,6 @@ class LlamaCppBackend:
gguf_extra_shards: list[str] = []
if hf_variant:
try:
- import re
from huggingface_hub import list_repo_files
files = list_repo_files(hf_repo, token = hf_token)
@@ -825,11 +864,10 @@ class LlamaCppBackend:
)
if gguf_files:
gguf_filename = gguf_files[0]
- shard_pat = re.compile(r"^(.*)-\d{5}-of-(\d{5})\.gguf$")
- m = shard_pat.match(gguf_filename)
+ m = _SHARD_FULL_RE.match(gguf_filename)
if m:
prefix = m.group(1)
- total = m.group(2)
+ total = m.group(3)
sibling_pat = re.compile(
r"^"
+ re.escape(prefix)
@@ -886,10 +924,7 @@ class LlamaCppBackend:
f"falling back to {fallback_file} ({fallback_size / (1024**3):.1f} GB)"
)
gguf_filename = fallback_file
- import re as _re
-
- _shard_pat = _re.compile(r"^(.*)-\d{5}-of-\d{5}\.gguf$")
- _m = _shard_pat.match(gguf_filename)
+ _m = _SHARD_RE.match(gguf_filename)
_prefix = _m.group(1) if _m else None
if _prefix:
gguf_extra_shards = sorted(
@@ -1292,17 +1327,12 @@ class LlamaCppBackend:
# Qwen3.5 models below 9B (0.8B, 2B, 4B) disable thinking by default.
# Only 9B and larger enable thinking.
if self._supports_reasoning:
- import re
-
thinking_default = True
mid = (model_identifier or "").lower()
if "qwen3.5" in mid:
- # Extract size like "0.8b", "4b", "35b" etc.
- size_match = re.search(r"(\d+\.?\d*)\s*b", mid)
- if size_match:
- size_val = float(size_match.group(1))
- if size_val < 9:
- thinking_default = False
+ size_val = _extract_model_size_b(mid)
+ if size_val is not None and size_val < 9:
+ thinking_default = False
self._reasoning_default = thinking_default
cmd.extend(
[
@@ -1775,13 +1805,11 @@ class LlamaCppBackend:
Closing tags ( , , ) are all optional
since models frequently omit them.
"""
- import re
-
tool_calls = []
# Pattern 1: JSON inside tags.
# Use balanced-brace extraction that skips braces inside JSON strings.
- for m in re.finditer(r"\s*\{", content):
+ for m in _TC_JSON_START_RE.finditer(content):
brace_start = m.end() - 1 # position of the opening {
depth, i = 0, brace_start
in_string = False
@@ -1831,7 +1859,7 @@ class LlamaCppBackend:
# boundaries. We avoid using as a boundary because
# code parameter values can contain that literal string.
# After extracting, we trim a trailing if present.
- func_starts = list(re.finditer(r"\s*", content))
+ func_starts = list(_TC_FUNC_START_RE.finditer(content))
for idx, fm in enumerate(func_starts):
func_name = fm.group(1)
body_start = fm.end()
@@ -1841,7 +1869,7 @@ class LlamaCppBackend:
if idx + 1 < len(func_starts)
else len(content)
)
- end_tag = re.search(r" ", content[body_start:])
+ end_tag = _TC_END_TAG_RE.search(content[body_start:])
if end_tag:
body_end = body_start + end_tag.start()
else:
@@ -1849,20 +1877,20 @@ class LlamaCppBackend:
body_end = min(body_end, next_func)
body = content[body_start:body_end]
# Trim trailing if present (it's the real closing tag)
- body = re.sub(r"\s*\s*$", "", body)
+ body = _TC_FUNC_CLOSE_RE.sub("", body)
# Step 2: Extract parameters from body.
# For single-parameter functions (the common case: code, command,
# query), use body end as the only boundary to avoid false matches
# on inside code strings.
arguments = {}
- param_starts = list(re.finditer(r"\s*", body))
+ param_starts = list(_TC_PARAM_START_RE.finditer(body))
if len(param_starts) == 1:
# Single parameter: value is everything from after the tag
# to end of body, trimming any trailing .
pm = param_starts[0]
val = body[pm.end() :]
- val = re.sub(r"\s*\s*$", "", val)
+ val = _TC_PARAM_CLOSE_RE.sub("", val)
arguments[pm.group(1)] = val.strip()
else:
for pidx, pm in enumerate(param_starts):
@@ -1876,7 +1904,7 @@ class LlamaCppBackend:
)
val = body[val_start:next_param]
# Trim trailing if present
- val = re.sub(r"\s*\s*$", "", val)
+ val = _TC_PARAM_CLOSE_RE.sub("", val)
arguments[param_name] = val.strip()
tc = {
@@ -2249,22 +2277,10 @@ class LlamaCppBackend:
_accumulated_predicted_ms = 0.0
_accumulated_predicted_n = 0
- # ── Shared patterns for stripping tool XML from streamed content ──
- import re as _re_tool
-
- _TOOL_CLOSED_PATTERNS = [
- _re_tool.compile(r".*? ", _re_tool.DOTALL),
- _re_tool.compile(r".*? ", _re_tool.DOTALL),
- ]
- _TOOL_ALL_PATTERNS = _TOOL_CLOSED_PATTERNS + [
- _re_tool.compile(r".*$", _re_tool.DOTALL),
- _re_tool.compile(r".*$", _re_tool.DOTALL),
- ]
-
def _strip_tool_markup(text: str, *, final: bool = False) -> str:
if not auto_heal_tool_calls:
return text
- patterns = _TOOL_ALL_PATTERNS if final else _TOOL_CLOSED_PATTERNS
+ patterns = _TOOL_ALL_PATS if final else _TOOL_CLOSED_PATS
for pat in patterns:
text = pat.sub("", text)
return text.strip() if final else text
@@ -2284,7 +2300,19 @@ class LlamaCppBackend:
# identical call succeeded).
_tool_call_history: list[tuple[str, bool]] = [] # (key, failed)
- for iteration in range(max_tool_iterations):
+ # ── Re-prompt on plan-without-action ─────────────────
+ # When the model describes what it intends to do (forward-looking
+ # language) without actually calling a tool, re-prompt once.
+ # Only triggers on responses that signal intent/planning -- a
+ # direct answer like "4" or "Hello!" will not match.
+ # Pattern is compiled once at module level (_INTENT_SIGNAL).
+ _reprompt_count = 0
+
+ # Reserve extra iterations for re-prompts so they don't
+ # consume the caller's tool-call budget. Only add the
+ # extra slot when tool iterations are actually allowed.
+ _extra = _MAX_REPROMPTS if max_tool_iterations > 0 else 0
+ for iteration in range(max_tool_iterations + _extra):
if cancel_event is not None and cancel_event.is_set():
return
@@ -2595,6 +2623,56 @@ class LlamaCppBackend:
content_accum,
)
if not _safety_tc:
+ # ── Re-prompt on plan-without-action ──
+ # If the model described what it intends to do
+ # (forward-looking language) without calling any
+ # tool, nudge it to act. Only fires once per
+ # request and only on short responses that
+ # contain intent signals -- a direct answer
+ # like "4" or "Hello!" won't trigger this.
+ # Use content if available, otherwise fall back
+ # to reasoning text (reasoning-only stalls).
+ _stripped = content_accum.strip()
+ if not _stripped:
+ _stripped = reasoning_accum.strip()
+ if (
+ tools
+ and _reprompt_count < _MAX_REPROMPTS
+ and 0 < len(_stripped) < _REPROMPT_MAX_CHARS
+ and _INTENT_SIGNAL.search(_stripped)
+ ):
+ _reprompt_count += 1
+ logger.info(
+ f"Re-prompt {_reprompt_count}/{_MAX_REPROMPTS}: "
+ f"model responded without calling tools "
+ f"({len(_stripped)} chars)"
+ )
+ conversation.append(
+ {
+ "role": "assistant",
+ "content": _stripped,
+ }
+ )
+ conversation.append(
+ {
+ "role": "user",
+ "content": (
+ "Please use the available tools to complete "
+ "the task instead of describing what to do."
+ ),
+ }
+ )
+ # Accumulate tokens and timing from this iteration
+ _fu_r = _iter_usage or {}
+ _accumulated_completion_tokens += _fu_r.get(
+ "completion_tokens", 0
+ )
+ _it_r = _iter_timings or {}
+ _accumulated_predicted_ms += _it_r.get("predicted_ms", 0)
+ _accumulated_predicted_n += _it_r.get("predicted_n", 0)
+ yield {"type": "status", "text": ""}
+ continue
+
# Content was already streamed. Yield metadata.
yield {"type": "status", "text": ""}
_fu = _iter_usage or {}
diff --git a/studio/backend/routes/inference.py b/studio/backend/routes/inference.py
index 99a52dfe2d..0c1d7e37f9 100644
--- a/studio/backend/routes/inference.py
+++ b/studio/backend/routes/inference.py
@@ -21,6 +21,9 @@ import threading
import re as _re
+# Model size extraction (shared with core/inference/llama_cpp.py)
+from utils.models import extract_model_size_b as _extract_model_size_b
+
def _friendly_error(exc: Exception) -> str:
"""Extract a user-friendly message from known llama-server errors."""
@@ -90,6 +93,12 @@ from datetime import date as _date
router = APIRouter()
+# Appended to tool-use nudge to discourage plan-without-action
+_TOOL_ACTION_NUDGE = (
+ " Always call tools directly."
+ " Never describe what you plan to do -- just call the tool immediately."
+)
+
# Regex for stripping leaked tool-call XML from assistant messages/stream
_TOOL_XML_RE = _re.compile(
r".*? |.*? ",
@@ -1095,12 +1104,20 @@ async def openai_chat_completions(
_date_line = f"The current date is {_date.today().isoformat()}."
- _web_tips = (
- "When you search and find a relevant URL in the results, "
- "fetch its full content by calling web_search with the url parameter. "
- "Do not repeat the same search query. If a search returns "
- "no useful results, try rephrasing or fetching a result URL directly."
- )
+ # Small models (<9B) struggle with multi-step search plans,
+ # so simplify the web tips to avoid plan-then-stall behavior.
+ _model_size_b = _extract_model_size_b(model_name)
+ _is_small_model = _model_size_b is not None and _model_size_b < 9
+
+ if _is_small_model:
+ _web_tips = "Do not repeat the same search query."
+ else:
+ _web_tips = (
+ "When you search and find a relevant URL in the results, "
+ "fetch its full content by calling web_search with the url parameter. "
+ "Do not repeat the same search query. If a search returns "
+ "no useful results, try rephrasing or fetching a result URL directly."
+ )
_code_tips = (
"Use code execution for math, calculations, data processing, "
"or to parse and analyze information from tool results."
@@ -1132,6 +1149,7 @@ async def openai_chat_completions(
_nudge = ""
if _nudge:
+ _nudge += _TOOL_ACTION_NUDGE
# Append nudge to system prompt (preserve user's prompt)
if system_prompt:
system_prompt = system_prompt.rstrip() + "\n\n" + _nudge
@@ -1208,7 +1226,14 @@ async def openai_chat_completions(
break
if event["type"] == "status":
+ # Empty status marks an iteration boundary
+ # in the GGUF tool loop (e.g. after a
+ # re-prompt). Reset the cumulative cursor
+ # so the next assistant turn streams cleanly.
+ if not event["text"]:
+ prev_text = ""
# Emit tool status as a custom SSE event
+ # (including empty ones to clear UI badges)
status_data = json.dumps(
{
"type": "tool_status",
diff --git a/studio/backend/utils/models/__init__.py b/studio/backend/utils/models/__init__.py
index 82236d8013..a81682d8b7 100644
--- a/studio/backend/utils/models/__init__.py
+++ b/studio/backend/utils/models/__init__.py
@@ -19,6 +19,7 @@ from .model_config import (
get_base_model_from_lora,
load_model_config,
list_gguf_variants,
+ extract_model_size_b,
MODEL_NAME_MAPPING,
UI_STATUS_INDICATORS,
)
@@ -38,6 +39,7 @@ __all__ = [
"get_base_model_from_lora",
"load_model_config",
"list_gguf_variants",
+ "extract_model_size_b",
"MODEL_NAME_MAPPING",
"UI_STATUS_INDICATORS",
"scan_checkpoints",
diff --git a/studio/backend/utils/models/model_config.py b/studio/backend/utils/models/model_config.py
index f7d9b33542..df1058abf6 100644
--- a/studio/backend/utils/models/model_config.py
+++ b/studio/backend/utils/models/model_config.py
@@ -31,6 +31,37 @@ import yaml
logger = get_logger(__name__)
+# ── Model size extraction ────────────────────────────────────
+import re as _re
+
+_MODEL_SIZE_RE = _re.compile(
+ r"(?:^|[-_/])(\d+\.?\d*)\s*([bm])(?:$|[-_/])", _re.IGNORECASE
+)
+# MoE active-parameter pattern: matches "A3B", "A3.5B", etc.
+_ACTIVE_SIZE_RE = _re.compile(
+ r"(?:^|[-_/])a(\d+\.?\d*)\s*([bm])(?:$|[-_/])", _re.IGNORECASE
+)
+
+
+def extract_model_size_b(model_id: str) -> float | None:
+ """Extract model size in billions from a model identifier.
+
+ Prefers MoE active-parameter notation (e.g. ``A3B`` in
+ ``Qwen3.5-35B-A3B``) over the total parameter count.
+ Handles both ``B`` (billions) and ``M`` (millions) suffixes.
+ """
+ mid = (model_id or "").lower()
+ active = _ACTIVE_SIZE_RE.search(mid)
+ if active:
+ val = float(active.group(1))
+ return val / 1000.0 if active.group(2).lower() == "m" else val
+ size = _MODEL_SIZE_RE.search(mid)
+ if not size:
+ return None
+ val = float(size.group(1))
+ return val / 1000.0 if size.group(2).lower() == "m" else val
+
+
# Model name mapping: maps all equivalent model names to their canonical YAML config file
# Format: "canonical_model_name.yaml": [list of all equivalent model names]
# Based on the model mapper provided - canonical filename is based on the first model name in the mapper
From 31d6aeb1975c418267547e0baa4ed698564d9940 Mon Sep 17 00:00:00 2001
From: Michael Han <107991372+shimmyshimmer@users.noreply.github.com>
Date: Thu, 2 Apr 2026 02:58:21 -0700
Subject: [PATCH 006/216] Unsloth new logo
---
images/unsloth logo only.png | Bin 57176 -> 60610 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
diff --git a/images/unsloth logo only.png b/images/unsloth logo only.png
index 92340eef059b9b687cf92aea9fe109106906eff3..adaafee48dc80f38582cad3df0b56b0b3e7d7302 100644
GIT binary patch
literal 60610
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