@miller-tech/uap 1.216.0 → 1.218.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/llama-patches/dflash2-pr27342.patch +1365 -0
- package/config/llama-profiles/gemma4-26b-a4b-mtp.env +1 -1
- package/config/llama-profiles/qwen36-35b-a3b.env +52 -3
- package/config/llama-profiles/qwen38-27b-dflash2.env +211 -0
- package/config/llama-profiles/qwen38-27b-mtp.env +155 -0
- package/config/model-profiles/qwen35.json +2 -2
- package/config/model-profiles/qwen36.json +1 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/bin/llama-server-optimize.js +3 -3
- package/dist/bin/llama-server-optimize.js.map +1 -1
- package/dist/cli/tool-calls.d.ts.map +1 -1
- package/dist/cli/tool-calls.js +27 -3
- package/dist/cli/tool-calls.js.map +1 -1
- package/docs/INDEX.md +1 -0
- package/docs/design/PRODUCT_NAMING.md +106 -0
- package/docs/guides/QWEN36_LLAMACPP.md +198 -3
- package/package.json +1 -1
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
- package/tools/agents/README.md +13 -3
- package/tools/agents/config/qwen-sharp.jinja +374 -0
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
- package/tools/agents/scripts/anthropic_proxy.py +2 -1
|
@@ -46,12 +46,18 @@ llama-server \
|
|
|
46
46
|
--ctx-size <PER TIER> \
|
|
47
47
|
--cache-type-k <q4_0|q8_0> --cache-type-v <q4_0|q8_0> \ # quantize KV cache to save VRAM
|
|
48
48
|
--flash-attn on \ # faster + less VRAM
|
|
49
|
-
--jinja \ #
|
|
49
|
+
--jinja \ # enable chat-template-driven tool calls (REQUIRED)
|
|
50
|
+
--chat-template-file tools/agents/config/qwen-sharp.jinja \
|
|
50
51
|
--parallel 1
|
|
51
52
|
```
|
|
52
53
|
|
|
53
|
-
> **`--jinja`
|
|
54
|
-
>
|
|
54
|
+
> **`--jinja` plus an explicit `--chat-template-file` is non-negotiable for agent
|
|
55
|
+
> use.** Qwen emits tool calls only when a chat template is active, and `--jinja`
|
|
56
|
+
> alone uses the model's *embedded* template — which still hard-raises
|
|
57
|
+
> `'System message must be at the beginning.'`, breaking any request where UAP's
|
|
58
|
+
> memory or context-injection layers add a system message mid-conversation. Always
|
|
59
|
+
> pass `--chat-template-file` (see
|
|
60
|
+
> [Chat template: `qwen-sharp.jinja`](#chat-template-qwen-sharpjinja-all-qwen-models)).
|
|
55
61
|
> If tools silently never fire, that's the cause — run `uap tool-calls setup`
|
|
56
62
|
> to install/repair the template, and `uap tool-calls status` to check.
|
|
57
63
|
|
|
@@ -74,6 +80,195 @@ Speed extras (any tier): `--flash-attn on` (always), and if your build supports
|
|
|
74
80
|
it, **self-speculation / MTP** (the `*-MTP.gguf` model, or `--draft-*` flags with
|
|
75
81
|
a tiny draft model) for materially faster decode.
|
|
76
82
|
|
|
83
|
+
## Measured: Qwen3.8-27B (dense) + native `draft-mtp`, RTX 3090 24 GB
|
|
84
|
+
|
|
85
|
+
Production switched 2026-08-20 to a **dense** qwen35-arch model
|
|
86
|
+
(`Qwen3.8-27B-UD-IQ4_XS.gguf`, ~13.3 GiB on disk) with a **native MTP head
|
|
87
|
+
baked into the checkpoint** (`blk.64.nextn.*` tensors) — no separate
|
|
88
|
+
`*-MTP.gguf` or `-md` draft file needed; `--spec-type draft-mtp` reads the
|
|
89
|
+
head straight from `--model`. Built at llama.cpp master (`f466cfa38`,
|
|
90
|
+
2026-08-20). See `config/llama-profiles/qwen38-27b-mtp.env` for the full
|
|
91
|
+
resolved profile.
|
|
92
|
+
|
|
93
|
+
Measured VRAM on this card (`q4_0`/`q4_0` KV, `-ngl 99`, `--parallel 2`,
|
|
94
|
+
`--flash-attn on`, no vision projector):
|
|
95
|
+
|
|
96
|
+
| Per-slot ctx (`--ctx-size` total) | VRAM used | VRAM free | Notes |
|
|
97
|
+
|---|---|---|---|
|
|
98
|
+
| 65536 (`131072` total) | ~19.8 GiB | ~4.3 GiB | Comfortable headroom. |
|
|
99
|
+
| 131072 (`262144` total) | ~23.15 GiB | ~0.95 GiB | Verified stable under 2 concurrent long-generation requests (60-91% draft acceptance, no OOM), but headroom is thin on a 24 GiB card with a history of kernel OOM-kills reaping this service under unrelated host memory pressure. If instability appears under real concurrent load, drop back to 65536/slot first. |
|
|
100
|
+
|
|
101
|
+
Doubling `--ctx-size` roughly doubles KV cache size, not the model footprint
|
|
102
|
+
(weights stay fixed at ~13.3 GiB) — so the free-VRAM gap shrinks fast once
|
|
103
|
+
weights + KV together approach the card's total. Always re-check
|
|
104
|
+
`nvidia-smi`/`/props` after raising ctx on this card rather than assuming the
|
|
105
|
+
previous tier's headroom scales.
|
|
106
|
+
|
|
107
|
+
## Chat template: `qwen-sharp.jinja` (all Qwen models)
|
|
108
|
+
|
|
109
|
+
Every Qwen launch path here runs
|
|
110
|
+
[`tools/agents/config/qwen-sharp.jinja`](../../tools/agents/config/qwen-sharp.jinja),
|
|
111
|
+
vendored verbatim from
|
|
112
|
+
[peculiar-ragdoll/Qwen-Sharp-Chat-Templates](https://huggingface.co/peculiar-ragdoll/Qwen-Sharp-Chat-Templates)
|
|
113
|
+
(`template_version` `qwen3.8-froggeric-v22.1`, upstream revision
|
|
114
|
+
`3dc34df52c63dd22ada21f96435e069deaa8d7da`). It targets Qwen 3.5 / 3.6 / 3.8 and
|
|
115
|
+
replaced `qwen3.5-enhanced.jinja` on 2026-08-20. The file's sha256 is asserted in
|
|
116
|
+
`test/chat-template-system-message.test.ts`, so a silent re-vendor from a changed
|
|
117
|
+
upstream fails CI rather than quietly changing the prompt contract.
|
|
118
|
+
|
|
119
|
+
### What it fixes
|
|
120
|
+
|
|
121
|
+
`qwen3.5-enhanced.jinja` hard-raised `'No user query found in messages.'` — a
|
|
122
|
+
server-side failure for the entire request — whenever its backwards scan found no
|
|
123
|
+
user turn that wasn't a `<tool_response>`. Two shapes that occur in ordinary agent
|
|
124
|
+
loops trigger it: a continuation whose only user turns are tool results, and a
|
|
125
|
+
`system`+`assistant` transcript with no user turn at all. `qwen-sharp` renders both.
|
|
126
|
+
|
|
127
|
+
This is *not* the mid-conversation-system-message case — both templates always
|
|
128
|
+
handled that. Earlier notes in this repo attributed the raise to the wrong cause.
|
|
129
|
+
|
|
130
|
+
### What stays compatible
|
|
131
|
+
|
|
132
|
+
Verified by rendering both templates over a fixture table and diffing the output,
|
|
133
|
+
not by reading the source:
|
|
134
|
+
|
|
135
|
+
* the tool-call contract the model is instructed to **emit** is unchanged —
|
|
136
|
+
`<tool_call><function=NAME><parameter=K>V</parameter></function></tool_call>`,
|
|
137
|
+
which `anthropic_proxy.py`'s `_HERMES_FUNCTION_RE` / `_TOOL_CALL_XML_RE` parse
|
|
138
|
+
unchanged;
|
|
139
|
+
* the generation prompt matches the old template in **both** `enable_thinking`
|
|
140
|
+
states, so `--reasoning auto --reasoning-format deepseek` is unaffected;
|
|
141
|
+
* a mid-conversation system message renders inline in both.
|
|
142
|
+
|
|
143
|
+
The template ships **both** a JSON and an XML tool-call emitter and picks one at
|
|
144
|
+
render time via `_tool_format`. That is why the tests assert on rendered output: a
|
|
145
|
+
grep for the XML emitter's source passes even if the default flips to JSON and
|
|
146
|
+
every tool call on the box silently changes wire format.
|
|
147
|
+
|
|
148
|
+
### What actually differs — known and accepted
|
|
149
|
+
|
|
150
|
+
* **History whitespace.** Replaying a *prior* tool call with string-form
|
|
151
|
+
`arguments` (the proxy's path — it `json.dumps` them) emitted `}\n</function>`
|
|
152
|
+
before and `}</function>` now; the separator between two tool calls in one
|
|
153
|
+
assistant turn went `\n` → `\n\n`. Neither changes what the parsers accept.
|
|
154
|
+
* **The injected tool-calling instruction block was rewritten upstream.** It now
|
|
155
|
+
requires a `<think>` block before each tool call and forbids prose before the
|
|
156
|
+
call. Behavioural and deliberate, but it is more than a formatting change.
|
|
157
|
+
* **A system message containing an image now raises.** Unreachable through the
|
|
158
|
+
proxy, which flattens `system` to a text string before dispatch; direct-to-`:8080`
|
|
159
|
+
OpenAI clients can hit it.
|
|
160
|
+
* **Unknown message roles no longer raise** — they render as `[role]: content`.
|
|
161
|
+
A malformed request now proceeds quietly where it used to fail loudly.
|
|
162
|
+
* **Prior `<think>` blocks are retained across turns.** Upstream sells this as a
|
|
163
|
+
prompt-cache win. On the proxy path it is a **no-op**: the proxy already strips
|
|
164
|
+
`<think>` from prior assistant turns on purpose ("Stripping breaks the copy
|
|
165
|
+
cycle"). On the direct path it re-introduces exactly that cycle — pass
|
|
166
|
+
`chat_template_kwargs.preserve_thinking=false` there if it shows up.
|
|
167
|
+
|
|
168
|
+
`chat_template_kwargs.reasoning_effort` accepts
|
|
169
|
+
`none|minimal|low|medium|high|xhigh|max`; note `none` forces thinking off,
|
|
170
|
+
overriding `enable_thinking=true`.
|
|
171
|
+
|
|
172
|
+
Do **not** switch a Qwen profile to `embedded` — the model's own baked-in template
|
|
173
|
+
still carries the `'System message must be at the beginning.'` raise.
|
|
174
|
+
|
|
175
|
+
## Speculative decoding: DFlash2 is the default
|
|
176
|
+
|
|
177
|
+
`config/llama-profiles/qwen38-27b-dflash2.env` is the default profile as of
|
|
178
|
+
2026-08-20. `qwen38-27b-mtp.env` is kept as the fallback — same model, same
|
|
179
|
+
context, same slot count; only the speculative-decoding config differs.
|
|
180
|
+
|
|
181
|
+
Measured on this card (RTX 3090, sm_86, temp 0, 4 prompts, 1032 tokens per arm,
|
|
182
|
+
**same binary across arms** so the spec config is the only variable):
|
|
183
|
+
|
|
184
|
+
| arm | aggregate tok/s | accept rate | tokens/verify step | |
|
|
185
|
+
|---|---|---|---|---|
|
|
186
|
+
| no speculation, `-np 1` | 41.3 | — | 1.00 | baseline |
|
|
187
|
+
| `draft-mtp` n-max 2, `-np 1` | 59.3 | 93.6% | 2.68 | |
|
|
188
|
+
| **DFlash2 n-max 4, `-np 1`** | **68.7** | 67.4% | 3.68 | **1.16×** |
|
|
189
|
+
| `draft-mtp` n-max 2, `-np 2` | 66.0 | 92.5% | 2.67 | |
|
|
190
|
+
| **DFlash2 n-max 4, `-np 2`** | **89.8** | 66.5% | 3.66 | **1.36×** |
|
|
191
|
+
|
|
192
|
+
The `-np 2` rows are two *genuinely concurrent* requests — the production shape.
|
|
193
|
+
Re-verified end-to-end through `run-llama-server-continuity.sh` with the shipped
|
|
194
|
+
profile: **92.6 tok/s**, tool calls parsing, peak 23459 MiB.
|
|
195
|
+
|
|
196
|
+
**Read `1.36×` as a blend, not a constant.** That run pushed 4 prompts through a
|
|
197
|
+
2-slot pool, so the last prompt always finishes alone and the wall covers two
|
|
198
|
+
different regimes. Decomposing it from the server's own `launch_slot_`/`release`
|
|
199
|
+
timeline:
|
|
200
|
+
|
|
201
|
+
| regime | `draft-mtp` | DFlash2 n-max 4 | ratio |
|
|
202
|
+
|---|---|---|---|
|
|
203
|
+
| both slots decoding | 76.4 tok/s | 130.3 tok/s | **1.71×** |
|
|
204
|
+
| one request in flight on the 2-slot server | 57.9 tok/s | 60.4 tok/s | **1.04×** |
|
|
205
|
+
| the measured 4-prompt burst (what `1.36×` reports) | 66.0 | 89.8 | 1.36× |
|
|
206
|
+
|
|
207
|
+
So the gain is real but duty-cycle dependent: it is ~1.7× while both slots are
|
|
208
|
+
busy and close to nothing when only one request is in flight. Add prompts to the
|
|
209
|
+
burst and the headline drifts up toward 1.71×; remove them and it drifts down.
|
|
210
|
+
The `-np 1` row above is a `--parallel 1` server, which this service never runs —
|
|
211
|
+
for the single-request case on the real 2-slot server use the 1.04–1.2× range,
|
|
212
|
+
not 1.16×.
|
|
213
|
+
|
|
214
|
+
Two caveats on the numbers, both unclosed:
|
|
215
|
+
|
|
216
|
+
* **Every measurement was taken at `temperature 0.0`**, while this profile serves
|
|
217
|
+
at `LLAMA_TEMP=0.7 / TOP_P=0.8 / TOP_K=20`. Greedy is the most favourable
|
|
218
|
+
setting for speculative decoding, and it flatters a 4-token block more than a
|
|
219
|
+
2-token one, so `1.36×` is an upper bound for the sampling that actually runs.
|
|
220
|
+
* **Acceptance is strongly prompt-dependent for DFlash2 and flat for MTP.**
|
|
221
|
+
Across the six prompts ever run, DFlash2 ranged 0.40–0.95 and `draft-mtp` sat
|
|
222
|
+
in a 0.91–0.97 band. The two lowest-acceptance prompts (prose explanation) were
|
|
223
|
+
never run against `draft-mtp`, so the regime where DFlash2 could lose is
|
|
224
|
+
untested. `n-max 7 < n-max 4` is direct evidence that drafting cost dominates
|
|
225
|
+
once acceptance falls.
|
|
226
|
+
|
|
227
|
+
Two results here are worth keeping, because both contradict what the upstream
|
|
228
|
+
material predicts:
|
|
229
|
+
|
|
230
|
+
* **DFlash2 does not degrade under concurrency on this card — it improves.** The
|
|
231
|
+
PR thread reports throughput collapsing at `-np >= 2`; here the advantage grows
|
|
232
|
+
from 1.16× to 1.36× going from one slot to two. Do not carry that assumption to
|
|
233
|
+
other hardware in either direction; measure it.
|
|
234
|
+
* **`n-max 4` beats `n-max 7`** (76.7 vs 69.8 tok/s), even though n-max 7 accepts
|
|
235
|
+
a longer block (4.86 vs 3.87 tokens/step). Acceptance falls from 72% to 55% as
|
|
236
|
+
the block lengthens, so the extra drafting outruns what it buys. The drafter
|
|
237
|
+
card's published acceptance length of 5.39 is real but is not the throughput
|
|
238
|
+
optimum.
|
|
239
|
+
|
|
240
|
+
### The cost, and when to roll back
|
|
241
|
+
|
|
242
|
+
Peak VRAM in the production shape is **23459 / 24576 MiB — about 1.1 GiB free**.
|
|
243
|
+
There is **no paired measurement of `draft-mtp` taken the same way**, so treat the
|
|
244
|
+
drafter's marginal cost as un-measured rather than as the ~0.8 GiB the weights
|
|
245
|
+
imply: the table above reports ~0.95 GiB free for `draft-mtp` at this same tier,
|
|
246
|
+
which cannot both be true and leave DFlash2 roomier. Re-measure both profiles
|
|
247
|
+
with one harness before relying on either figure. Note also that 23459 was
|
|
248
|
+
sampled with `nvidia-smi` on a 1 s loop over a ~10 s run whose longest prefill
|
|
249
|
+
was 540 tokens — it never exercised a long-context prefill on two slots, which is
|
|
250
|
+
the real peak event at 131072/slot. The drafter's weights (Q4_K_M,
|
|
251
|
+
1.1 GB) are essentially the whole difference: its KV cache stays around 80 MiB
|
|
252
|
+
because it carries its own 2048-token sliding window rather than scaling with the
|
|
253
|
+
target's 131072/slot. That is what lets this fit without cutting context or slots
|
|
254
|
+
— and it is why `LLAMA_DRAFT_CTX_SIZE` must stay unset.
|
|
255
|
+
|
|
256
|
+
This card has a history of *global* kernel OOM events selecting this service for
|
|
257
|
+
its RSS. The GPU side is now thinner too. **If the server starts failing to load
|
|
258
|
+
or dying, switch `~/.config/uap/llama-server.env` back to `qwen38-27b-mtp.env`
|
|
259
|
+
before changing anything else** — that is what the fallback profile is for.
|
|
260
|
+
|
|
261
|
+
### The engine is an unmerged PR
|
|
262
|
+
|
|
263
|
+
[llama.cpp#27342](https://github.com/ggml-org/llama.cpp/pull/27342) is **open**.
|
|
264
|
+
The build is a cherry-pick of `5ecbe1ac1` onto `f466cfa38` (zero conflicts),
|
|
265
|
+
vendored as `config/llama-patches/dflash2-pr27342.patch` so it is reproducible if
|
|
266
|
+
the build worktree is lost. The merged `draft-dflash` **v1** engine cannot
|
|
267
|
+
substitute: a v2 checkpoint declares the same `general.architecture = "dflash"`,
|
|
268
|
+
so auto-detect selects `draft-dflash` and then fails on the 23 conv/selector
|
|
269
|
+
tensors and `dflash.conv_*` / `dflash.selector_*` metadata keys v1 has no loader
|
|
270
|
+
for. Return to plain upstream master once the PR lands.
|
|
271
|
+
|
|
77
272
|
## Point UAP at it
|
|
78
273
|
|
|
79
274
|
```bash
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
package/tools/agents/README.md
CHANGED
|
@@ -13,9 +13,19 @@ This directory contains tools and configurations for fixing Qwen3.5 tool calling
|
|
|
13
13
|
|
|
14
14
|
## Files
|
|
15
15
|
|
|
16
|
+
### `config/qwen-sharp.jinja`
|
|
17
|
+
|
|
18
|
+
The template every Qwen profile runs (Qwen 3.5 / 3.6 / 3.8), vendored verbatim from
|
|
19
|
+
[peculiar-ragdoll/Qwen-Sharp-Chat-Templates](https://huggingface.co/peculiar-ragdoll/Qwen-Sharp-Chat-Templates)
|
|
20
|
+
and sha256-pinned in `test/chat-template-system-message.test.ts`. See
|
|
21
|
+
[docs/guides/QWEN36_LLAMACPP.md](../../docs/guides/QWEN36_LLAMACPP.md#chat-template-qwen-sharpjinja-all-qwen-models)
|
|
22
|
+
for what it changes and what stays wire-compatible.
|
|
23
|
+
|
|
24
|
+
`uap tool-calls setup` installs it automatically under any `qwen*` profile.
|
|
25
|
+
|
|
16
26
|
### `config/chat_template.jinja`
|
|
17
27
|
|
|
18
|
-
The
|
|
28
|
+
The generic (non-Qwen) fallback: a patched Jinja2 template for Qwen3.5 that adds conditional wrappers around tool call argument iteration.
|
|
19
29
|
|
|
20
30
|
**Key Fix (line 138-144):**
|
|
21
31
|
|
|
@@ -142,7 +152,7 @@ cp tools/agents/scripts/*.py tools/agents/scripts/
|
|
|
142
152
|
```bash
|
|
143
153
|
./llama-server \
|
|
144
154
|
--model ~/models/Qwen3.5-35B-Instruct-Q4_K_M.gguf \
|
|
145
|
-
--chat-template-file tools/agents/config/
|
|
155
|
+
--chat-template-file tools/agents/config/qwen-sharp.jinja \
|
|
146
156
|
--jinja \
|
|
147
157
|
--port 8080 \
|
|
148
158
|
--ctx-size 262144 \
|
|
@@ -161,7 +171,7 @@ cp tools/agents/scripts/*.py tools/agents/scripts/
|
|
|
161
171
|
|
|
162
172
|
```bash
|
|
163
173
|
mkdir -p ~/.opencode/agent
|
|
164
|
-
cp tools/agents/config/
|
|
174
|
+
cp tools/agents/config/qwen-sharp.jinja ~/.opencode/agent/
|
|
165
175
|
```
|
|
166
176
|
|
|
167
177
|
**2. Update `.opencode/config.json`:**
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
{%- set template_version = "qwen3.8-froggeric-v22.1" %}
|
|
2
|
+
{%- set _tool_format = tool_call_format if tool_call_format is defined else 'xml' %}
|
|
3
|
+
{%- set image_count = namespace(value=0) %}
|
|
4
|
+
{%- set video_count = namespace(value=0) %}
|
|
5
|
+
{%- set add_vision_id = add_vision_id if add_vision_id is defined else false %}
|
|
6
|
+
{%- set enable_thinking = enable_thinking if enable_thinking is defined else true %}
|
|
7
|
+
{%- set auto_disable_thinking_with_tools = auto_disable_thinking_with_tools if auto_disable_thinking_with_tools is defined else false %}
|
|
8
|
+
{%- if preserve_reasoning is defined and preserve_reasoning is not none %}
|
|
9
|
+
{%- set _preserve_thinking = preserve_reasoning %}
|
|
10
|
+
{%- elif preserve_thinking is defined and preserve_thinking is not none %}
|
|
11
|
+
{%- set _preserve_thinking = preserve_thinking %}
|
|
12
|
+
{%- else %}
|
|
13
|
+
{%- set _preserve_thinking = true %}
|
|
14
|
+
{%- endif %}
|
|
15
|
+
{%- set max_tool_arg_chars = max_tool_arg_chars if max_tool_arg_chars is defined else 0 %}
|
|
16
|
+
{%- set max_tool_response_chars = max_tool_response_chars if max_tool_response_chars is defined else 0 %}
|
|
17
|
+
{%- set _has_tools = (tools is defined and tools and tools is iterable and tools is not mapping) %}
|
|
18
|
+
{%- set _effort_raw = (reasoning_effort | string | lower) if reasoning_effort is defined and reasoning_effort is not none else 'medium' %}
|
|
19
|
+
{%- set _initial_thinking = enable_thinking %}
|
|
20
|
+
{%- set _initial_effort = 'medium' %}
|
|
21
|
+
{%- if _effort_raw == 'none' %}
|
|
22
|
+
{%- set _initial_thinking = false %}
|
|
23
|
+
{%- set _initial_effort = 'medium' %}
|
|
24
|
+
{%- elif _effort_raw == 'minimal' or _effort_raw == 'low' %}
|
|
25
|
+
{%- set _initial_effort = 'low' %}
|
|
26
|
+
{%- elif _effort_raw == 'high' or _effort_raw == 'xhigh' or _effort_raw == 'max' %}
|
|
27
|
+
{%- set _initial_effort = 'xhigh' %}
|
|
28
|
+
{%- else %}
|
|
29
|
+
{%- set _initial_effort = 'medium' %}
|
|
30
|
+
{%- endif %}
|
|
31
|
+
{%- set ns_state = namespace(thinking=_initial_thinking, effort=_initial_effort) %}
|
|
32
|
+
{%- if auto_disable_thinking_with_tools and _has_tools %}
|
|
33
|
+
{%- set ns_state.thinking = false %}
|
|
34
|
+
{%- endif %}
|
|
35
|
+
{%- for msg in messages %}
|
|
36
|
+
{%- if msg.role == 'system' or msg.role == 'developer' or msg.role == 'user' %}
|
|
37
|
+
{%- if msg.content is string %}
|
|
38
|
+
{%- if '<|think_off|>' in msg.content %}
|
|
39
|
+
{%- set ns_state.thinking = false %}
|
|
40
|
+
{%- elif '<|think_on|>' in msg.content %}
|
|
41
|
+
{%- set ns_state.thinking = true %}
|
|
42
|
+
{%- elif '<|think_xhigh|>' in msg.content or '<|think_high|>' in msg.content %}
|
|
43
|
+
{%- set ns_state.thinking = true %}
|
|
44
|
+
{%- set ns_state.effort = 'xhigh' %}
|
|
45
|
+
{%- elif '<|think_low|>' in msg.content or '<|think_minimal|>' in msg.content %}
|
|
46
|
+
{%- set ns_state.thinking = true %}
|
|
47
|
+
{%- set ns_state.effort = 'low' %}
|
|
48
|
+
{%- elif '<|think_medium|>' in msg.content %}
|
|
49
|
+
{%- set ns_state.thinking = true %}
|
|
50
|
+
{%- set ns_state.effort = 'medium' %}
|
|
51
|
+
{%- endif %}
|
|
52
|
+
{%- elif msg.content is iterable and msg.content is not mapping %}
|
|
53
|
+
{%- for item in msg.content %}
|
|
54
|
+
{%- if item is string %}
|
|
55
|
+
{%- set _item_text = item %}
|
|
56
|
+
{%- elif item is mapping and 'text' in item and item.text is string %}
|
|
57
|
+
{%- set _item_text = item.text %}
|
|
58
|
+
{%- else %}
|
|
59
|
+
{%- set _item_text = '' %}
|
|
60
|
+
{%- endif %}
|
|
61
|
+
{%- if _item_text %}
|
|
62
|
+
{%- if '<|think_off|>' in _item_text %}
|
|
63
|
+
{%- set ns_state.thinking = false %}
|
|
64
|
+
{%- elif '<|think_on|>' in _item_text %}
|
|
65
|
+
{%- set ns_state.thinking = true %}
|
|
66
|
+
{%- elif '<|think_xhigh|>' in _item_text or '<|think_high|>' in _item_text %}
|
|
67
|
+
{%- set ns_state.thinking = true %}
|
|
68
|
+
{%- set ns_state.effort = 'xhigh' %}
|
|
69
|
+
{%- elif '<|think_low|>' in _item_text or '<|think_minimal|>' in _item_text %}
|
|
70
|
+
{%- set ns_state.thinking = true %}
|
|
71
|
+
{%- set ns_state.effort = 'low' %}
|
|
72
|
+
{%- elif '<|think_medium|>' in _item_text %}
|
|
73
|
+
{%- set ns_state.thinking = true %}
|
|
74
|
+
{%- set ns_state.effort = 'medium' %}
|
|
75
|
+
{%- endif %}
|
|
76
|
+
{%- endif %}
|
|
77
|
+
{%- endfor %}
|
|
78
|
+
{%- endif %}
|
|
79
|
+
{%- endif %}
|
|
80
|
+
{%- endfor %}
|
|
81
|
+
{%- set reasoning_instructions = '' %}
|
|
82
|
+
{%- if ns_state.thinking %}
|
|
83
|
+
{%- if ns_state.effort == 'xhigh' %}
|
|
84
|
+
{%- set reasoning_instructions = 'Reasoning effort is set to xhigh. Please think carefully through the task, validate key assumptions, consider plausible alternatives, and prioritize correctness, consistency, and clarity in the final answer.' %}
|
|
85
|
+
{%- elif ns_state.effort == 'low' %}
|
|
86
|
+
{%- set reasoning_instructions = 'Reasoning effort is set to low. Keep your thinking brief and focused, moving directly to the conclusion without unnecessary elaboration.' %}
|
|
87
|
+
{%- endif %}
|
|
88
|
+
{%- endif %}
|
|
89
|
+
{%- macro render_content(content, do_vision_count, is_system_content=false) %}
|
|
90
|
+
{%- if content is string %}
|
|
91
|
+
{{- content }}
|
|
92
|
+
{%- elif content is iterable and content is not mapping %}
|
|
93
|
+
{%- for item in content %}
|
|
94
|
+
{%- if item is mapping %}
|
|
95
|
+
{%- if item.type == 'image' or 'image' in item or 'image_url' in item %}
|
|
96
|
+
{%- if is_system_content %}
|
|
97
|
+
{{- raise_exception('System message cannot contain images.') }}
|
|
98
|
+
{%- endif %}
|
|
99
|
+
{%- if do_vision_count %}
|
|
100
|
+
{%- set image_count.value = image_count.value + 1 %}
|
|
101
|
+
{%- endif %}
|
|
102
|
+
{%- if add_vision_id %}
|
|
103
|
+
{{- 'Picture ' ~ image_count.value ~ ': ' }}
|
|
104
|
+
{%- endif %}
|
|
105
|
+
{{- '<|vision_start|><|image_pad|><|vision_end|>' }}
|
|
106
|
+
{%- elif item.type == 'video' or 'video' in item %}
|
|
107
|
+
{%- if is_system_content %}
|
|
108
|
+
{{- raise_exception('System message cannot contain videos.') }}
|
|
109
|
+
{%- endif %}
|
|
110
|
+
{%- if do_vision_count %}
|
|
111
|
+
{%- set video_count.value = video_count.value + 1 %}
|
|
112
|
+
{%- endif %}
|
|
113
|
+
{%- if add_vision_id %}
|
|
114
|
+
{{- 'Video ' ~ video_count.value ~ ': ' }}
|
|
115
|
+
{%- endif %}
|
|
116
|
+
{{- '<|vision_start|><|video_pad|><|vision_end|>' }}
|
|
117
|
+
{%- elif 'text' in item %}
|
|
118
|
+
{{- item.text }}
|
|
119
|
+
{%- else %}
|
|
120
|
+
{{- raise_exception('Unexpected item type in content.') }}
|
|
121
|
+
{%- endif %}
|
|
122
|
+
{%- else %}
|
|
123
|
+
{{- item | string }}
|
|
124
|
+
{%- endif %}
|
|
125
|
+
{%- endfor %}
|
|
126
|
+
{%- elif content is none or content is undefined %}
|
|
127
|
+
{{- '' }}
|
|
128
|
+
{%- else %}
|
|
129
|
+
{{- raise_exception('Unexpected content type.') }}
|
|
130
|
+
{%- endif %}
|
|
131
|
+
{%- endmacro %}
|
|
132
|
+
{%- if not messages %}
|
|
133
|
+
{{- raise_exception('No messages provided.') }}
|
|
134
|
+
{%- endif %}
|
|
135
|
+
{%- set _first_role = messages[0].role %}
|
|
136
|
+
{%- if _first_role == 'system' or _first_role == 'developer' %}
|
|
137
|
+
{%- set _sys_msg = messages[0] %}
|
|
138
|
+
{%- set _msgs = messages[1:] %}
|
|
139
|
+
{%- else %}
|
|
140
|
+
{%- set _sys_msg = none %}
|
|
141
|
+
{%- set _msgs = messages %}
|
|
142
|
+
{%- endif %}
|
|
143
|
+
{%- set _sc = '' %}
|
|
144
|
+
{%- if _sys_msg is not none %}
|
|
145
|
+
{%- set _sc = render_content(_sys_msg.content, false, true) | trim %}
|
|
146
|
+
{%- if '<|think_off|>' in _sc %}{%- set _sc = _sc.split('<|think_off|>') | join('') | trim %}{%- endif %}
|
|
147
|
+
{%- if '<|think_on|>' in _sc %}{%- set _sc = _sc.split('<|think_on|>') | join('') | trim %}{%- endif %}
|
|
148
|
+
{%- if '<|think_xhigh|>' in _sc %}{%- set _sc = _sc.split('<|think_xhigh|>') | join('') | trim %}{%- endif %}
|
|
149
|
+
{%- if '<|think_high|>' in _sc %}{%- set _sc = _sc.split('<|think_high|>') | join('') | trim %}{%- endif %}
|
|
150
|
+
{%- if '<|think_medium|>' in _sc %}{%- set _sc = _sc.split('<|think_medium|>') | join('') | trim %}{%- endif %}
|
|
151
|
+
{%- if '<|think_low|>' in _sc %}{%- set _sc = _sc.split('<|think_low|>') | join('') | trim %}{%- endif %}
|
|
152
|
+
{%- if '<|think_minimal|>' in _sc %}{%- set _sc = _sc.split('<|think_minimal|>') | join('') | trim %}{%- endif %}
|
|
153
|
+
{%- endif %}
|
|
154
|
+
{%- set _terse %}
|
|
155
|
+
Answer directly, after thinking. Lead with the answer, then only what it needs to be correct and usable.
|
|
156
|
+
Never: open with preamble or pleasantries; restate the question; add filler transitions; hedge with niceties; or repeat a point you've already made.
|
|
157
|
+
Always: keep essential steps, caveats, uncertainties, and specifics — never drop correctness or a needed warning for brevity. Keep the final answer lean. Use the least structure that conveys it (plain prose when short; lists or code only when they earn their place). If genuinely uncertain, say so and explain why — never omit uncertainty for the sake of brevity.
|
|
158
|
+
If a user request is genuinely ambiguous, ask a sharp question, don't guess.
|
|
159
|
+
{%- endset %}
|
|
160
|
+
{%- if not _sc %}
|
|
161
|
+
{%- set _sc = _terse | trim %}
|
|
162
|
+
{%- else %}
|
|
163
|
+
{%- set _sc = (_sc | trim) ~ '\n\n' ~ (_terse | trim) %}
|
|
164
|
+
{%- endif %}
|
|
165
|
+
{%- if _has_tools %}
|
|
166
|
+
{{- '<|im_start|>system\n' }}
|
|
167
|
+
{%- if reasoning_instructions %}
|
|
168
|
+
{{- reasoning_instructions + '\n\n' }}
|
|
169
|
+
{%- endif %}
|
|
170
|
+
{{- '# Tools\n\nYou have access to the following functions:\n\n<tools>' }}
|
|
171
|
+
{%- for tool in tools %}
|
|
172
|
+
{{- '\n' }}
|
|
173
|
+
{{- tool | tojson }}
|
|
174
|
+
{%- endfor %}
|
|
175
|
+
{{- '\n</tools>' }}
|
|
176
|
+
{%- if _tool_format == 'json' %}
|
|
177
|
+
{{- '\n\nIf you choose to call a function ONLY reply in the following format with NO suffix:\n\n<think>\nBrief explanation of tool call\n</think>\n<tool_call>\n{"name": "example_function_name", "arguments": {"example_parameter_1": "value_1", "example_parameter_2": "This is the value for the second parameter"}}\n</tool_call>\n\n<IMPORTANT>\nReminder:\n- You can use the <think></think> block to plan your next tool call OR to synthesize data and formulate your final response to the user.\n- ALL explanation and reasoning MUST be placed strictly inside the <think></think> block.\n- Function calls MUST follow the specified format: a single JSON object with "name" and "arguments" keys inside <tool_call></tool_call> XML tags.\n- If you choose to call a tool, you MUST output the <tool_call> block IMMEDIATELY after thinking, with NO conversational text before it.\n- The <tool_call> tag MUST be at the very beginning of a new line, with NO spaces or indentation before it.\n- To call multiple functions, output a separate, completely closed <tool_call></tool_call> block for EACH function. Do NOT nest <tool_call> blocks.\n- If you have all necessary data, provide your final answer directly to the user without any tool call.\n</IMPORTANT>' }}
|
|
178
|
+
{%- else %}
|
|
179
|
+
{{- '\n\nIf you choose to call a function ONLY reply in the following format with NO suffix:\n\n<think>\nBrief explanation of tool call\n</think>\n<tool_call>\n<function=example_function_name>\n<parameter=example_parameter_1>\nvalue_1\n</parameter>\n<parameter=example_parameter_2>\nThis is the value for the second parameter\nthat can span\nmultiple lines\n</parameter>\n</function>\n</tool_call>\n\n<IMPORTANT>\nReminder:\n- You can use the <think></think> block to plan your next tool call OR to synthesize data and formulate your final response to the user.\n- ALL explanation and reasoning MUST be placed strictly inside the <think></think> block.\n- Function calls MUST follow the specified format: an inner <function=...></function> block must be nested within <tool_call></tool_call> XML tags.\n- If you choose to call a tool, you MUST output the <tool_call> block IMMEDIATELY after thinking, with NO conversational text before it.\n- The <tool_call> and <function> tags MUST be at the very beginning of a new line, with NO spaces or indentation before them.\n- To call multiple functions, output a separate, completely closed <tool_call></tool_call> block for EACH function. Do NOT nest <tool_call> blocks.\n- If you have all necessary data, provide your final answer directly to the user without any tool call.\n</IMPORTANT>' }}
|
|
180
|
+
{%- endif %}
|
|
181
|
+
{%- if _sc %}
|
|
182
|
+
{{- '\n\n' + _sc }}
|
|
183
|
+
{%- endif %}
|
|
184
|
+
{{- '<|im_end|>\n' }}
|
|
185
|
+
{%- else %}
|
|
186
|
+
{%- if _sc %}
|
|
187
|
+
{{- '<|im_start|>system\n' + (reasoning_instructions + '\n\n' if reasoning_instructions else '') + _sc + '<|im_end|>\n' }}
|
|
188
|
+
{%- elif reasoning_instructions %}
|
|
189
|
+
{{- '<|im_start|>system\n' + reasoning_instructions + '<|im_end|>\n' }}
|
|
190
|
+
{%- endif %}
|
|
191
|
+
{%- endif %}
|
|
192
|
+
{%- set _last_idx = _msgs | length - 1 %}
|
|
193
|
+
{%- set ns = namespace(multi_step_tool=true, last_query_index=_last_idx) %}
|
|
194
|
+
{%- for message in _msgs[::-1] %}
|
|
195
|
+
{%- set index = (_msgs | length - 1) - loop.index0 %}
|
|
196
|
+
{%- if ns.multi_step_tool and message.role == 'user' %}
|
|
197
|
+
{%- set _rc = render_content(message.content, false) | trim %}
|
|
198
|
+
{%- if not (_rc.startswith('<tool_response>') and _rc.endswith('</tool_response>')) %}
|
|
199
|
+
{%- set ns.multi_step_tool = false %}
|
|
200
|
+
{%- set ns.last_query_index = index %}
|
|
201
|
+
{%- endif %}
|
|
202
|
+
{%- endif %}
|
|
203
|
+
{%- endfor %}
|
|
204
|
+
{%- if ns.multi_step_tool %}
|
|
205
|
+
{%- if _last_idx > 50 %}
|
|
206
|
+
{%- set ns.last_query_index = _last_idx %}
|
|
207
|
+
{%- else %}
|
|
208
|
+
{%- set ns.last_query_index = 0 %}
|
|
209
|
+
{%- endif %}
|
|
210
|
+
{%- endif %}
|
|
211
|
+
{%- set ns2 = namespace(prev_role='', consecutive_failures=0) %}
|
|
212
|
+
{%- for message in _msgs %}
|
|
213
|
+
{%- set is_system = (message.role == "system" or message.role == "developer") %}
|
|
214
|
+
{%- set content = render_content(message.content, true, is_system) | trim %}
|
|
215
|
+
{%- if is_system or message.role == 'user' %}
|
|
216
|
+
{%- if '<|think_off|>' in content %}{%- set content = content.split('<|think_off|>') | join('') | trim %}{%- endif %}
|
|
217
|
+
{%- if '<|think_on|>' in content %}{%- set content = content.split('<|think_on|>') | join('') | trim %}{%- endif %}
|
|
218
|
+
{%- if '<|think_xhigh|>' in content %}{%- set content = content.split('<|think_xhigh|>') | join('') | trim %}{%- endif %}
|
|
219
|
+
{%- if '<|think_high|>' in content %}{%- set content = content.split('<|think_high|>') | join('') | trim %}{%- endif %}
|
|
220
|
+
{%- if '<|think_medium|>' in content %}{%- set content = content.split('<|think_medium|>') | join('') | trim %}{%- endif %}
|
|
221
|
+
{%- if '<|think_low|>' in content %}{%- set content = content.split('<|think_low|>') | join('') | trim %}{%- endif %}
|
|
222
|
+
{%- if '<|think_minimal|>' in content %}{%- set content = content.split('<|think_minimal|>') | join('') | trim %}{%- endif %}
|
|
223
|
+
{%- endif %}
|
|
224
|
+
{%- if is_system %}
|
|
225
|
+
{{- '<|im_start|>system\n' + content + '<|im_end|>\n' }}
|
|
226
|
+
{%- elif message.role == 'user' %}
|
|
227
|
+
{%- set ns2.consecutive_failures = 0 %}
|
|
228
|
+
{{- '<|im_start|>user\n' + content + '<|im_end|>\n' }}
|
|
229
|
+
{%- elif message.role == 'assistant' %}
|
|
230
|
+
{%- set reasoning_content = '' %}
|
|
231
|
+
{%- if message.reasoning_content is defined and message.reasoning_content is not none %}
|
|
232
|
+
{%- if message.reasoning_content is string %}
|
|
233
|
+
{%- set reasoning_content = message.reasoning_content %}
|
|
234
|
+
{%- else %}
|
|
235
|
+
{%- set reasoning_content = message.reasoning_content | string %}
|
|
236
|
+
{%- endif %}
|
|
237
|
+
{%- elif message.thinking is defined and message.thinking is not none %}
|
|
238
|
+
{%- if message.thinking is string %}
|
|
239
|
+
{%- set reasoning_content = message.thinking %}
|
|
240
|
+
{%- else %}
|
|
241
|
+
{%- set reasoning_content = message.thinking | string %}
|
|
242
|
+
{%- endif %}
|
|
243
|
+
{%- else %}
|
|
244
|
+
{%- set _think_end = '' %}
|
|
245
|
+
{%- if content.startswith('</think>') %}
|
|
246
|
+
{%- set _think_end = '</think>' %}
|
|
247
|
+
{%- elif content.startswith('</thinking>') %}
|
|
248
|
+
{%- set _think_end = '</thinking>' %}
|
|
249
|
+
{%- elif '\n</think>' in content %}
|
|
250
|
+
{%- set _think_end = '\n</think>' %}
|
|
251
|
+
{%- elif '\n</thinking>' in content %}
|
|
252
|
+
{%- set _think_end = '\n</thinking>' %}
|
|
253
|
+
{%- elif '\n</ think>' in content %}
|
|
254
|
+
{%- set _think_end = '\n</ think>' %}
|
|
255
|
+
{%- elif '\n</think >' in content %}
|
|
256
|
+
{%- set _think_end = '\n</think >' %}
|
|
257
|
+
{%- endif %}
|
|
258
|
+
{%- if _think_end %}
|
|
259
|
+
{%- if 'thinking' in _think_end %}
|
|
260
|
+
{%- set _think_start = '<thinking>' %}
|
|
261
|
+
{%- else %}
|
|
262
|
+
{%- set _think_start = '<think>' %}
|
|
263
|
+
{%- endif %}
|
|
264
|
+
{%- set reasoning_content = content.split(_think_end)[0].rstrip('\n') %}
|
|
265
|
+
{%- if _think_start in reasoning_content %}
|
|
266
|
+
{%- set reasoning_content = reasoning_content.split(_think_start)[-1].lstrip('\n') %}
|
|
267
|
+
{%- endif %}
|
|
268
|
+
{%- set content = content.split(_think_end)[-1].lstrip('\n') %}
|
|
269
|
+
{%- endif %}
|
|
270
|
+
{%- endif %}
|
|
271
|
+
{%- set reasoning_content = reasoning_content | trim %}
|
|
272
|
+
{%- if (_preserve_thinking or loop.index0 > ns.last_query_index) and reasoning_content %}
|
|
273
|
+
{{- '<|im_start|>assistant\n<think>\n' + reasoning_content + '\n</think>\n\n' + content }}
|
|
274
|
+
{%- else %}
|
|
275
|
+
{{- '<|im_start|>assistant\n' + content }}
|
|
276
|
+
{%- endif %}
|
|
277
|
+
{%- if message.tool_calls is defined and message.tool_calls and message.tool_calls is iterable and message.tool_calls is not mapping %}
|
|
278
|
+
{%- for tool_call in message.tool_calls %}
|
|
279
|
+
{%- if tool_call.function is defined and tool_call.function is not none %}
|
|
280
|
+
{%- set tc = tool_call.function %}
|
|
281
|
+
{%- else %}
|
|
282
|
+
{%- set tc = tool_call %}
|
|
283
|
+
{%- endif %}
|
|
284
|
+
{%- set tc_name = tc.name if (tc.name is defined and tc.name is not none) else '' %}
|
|
285
|
+
{%- if _tool_format == 'json' %}
|
|
286
|
+
{%- if not loop.first or content | trim %}
|
|
287
|
+
{{- '\n\n' }}
|
|
288
|
+
{%- endif %}
|
|
289
|
+
{%- set _args = '{}' %}
|
|
290
|
+
{%- if tc.arguments is defined and tc.arguments is not none %}
|
|
291
|
+
{%- if tc.arguments is mapping %}
|
|
292
|
+
{%- set _args = tc.arguments | tojson %}
|
|
293
|
+
{%- elif tc.arguments is string and tc.arguments %}
|
|
294
|
+
{%- set _args = tc.arguments %}
|
|
295
|
+
{%- endif %}
|
|
296
|
+
{%- endif %}
|
|
297
|
+
{{- '<tool_call>\n{"name": ' }}{{- tc_name | tojson }}{{- ', "arguments": ' }}{{- _args }}{{- '}\n</tool_call>' }}
|
|
298
|
+
{%- else %}
|
|
299
|
+
{%- if loop.first %}
|
|
300
|
+
{%- if content | trim %}
|
|
301
|
+
{{- '\n\n<tool_call>\n<function=' + tc_name + '>\n' }}
|
|
302
|
+
{%- else %}
|
|
303
|
+
{{- '<tool_call>\n<function=' + tc_name + '>\n' }}
|
|
304
|
+
{%- endif %}
|
|
305
|
+
{%- else %}
|
|
306
|
+
{{- '\n\n<tool_call>\n<function=' + tc_name + '>\n' }}
|
|
307
|
+
{%- endif %}
|
|
308
|
+
{%- if tc.arguments is defined and tc.arguments is not none %}
|
|
309
|
+
{%- if tc.arguments is mapping %}
|
|
310
|
+
{%- for args_name, args_value in tc.arguments.items() %}
|
|
311
|
+
{{- '<parameter=' + args_name + '>\n' }}
|
|
312
|
+
{%- if args_value is mapping or (args_value is sequence and args_value is not string) %}
|
|
313
|
+
{%- set _av = args_value | tojson %}
|
|
314
|
+
{%- else %}
|
|
315
|
+
{%- set _av = args_value | string %}
|
|
316
|
+
{%- endif %}
|
|
317
|
+
{%- if max_tool_arg_chars > 0 and _av | length > max_tool_arg_chars %}
|
|
318
|
+
{{- _av[:max_tool_arg_chars] + '\n[TRUNCATED - original length ' ~ (_av | length | string) ~ ' chars]' }}
|
|
319
|
+
{%- else %}
|
|
320
|
+
{{- _av }}
|
|
321
|
+
{%- endif %}
|
|
322
|
+
{{- '\n</parameter>\n' }}
|
|
323
|
+
{%- endfor %}
|
|
324
|
+
{%- elif tc.arguments is string and tc.arguments %}
|
|
325
|
+
{{- tc.arguments }}
|
|
326
|
+
{%- endif %}
|
|
327
|
+
{%- endif %}
|
|
328
|
+
{{- '</function>\n</tool_call>' }}
|
|
329
|
+
{%- endif %}
|
|
330
|
+
{%- endfor %}
|
|
331
|
+
{%- endif %}
|
|
332
|
+
{{- '<|im_end|>\n' }}
|
|
333
|
+
{%- elif message.role == 'tool' %}
|
|
334
|
+
{%- set _content_lower = content | lower %}
|
|
335
|
+
{%- set _content_head = _content_lower[:80] %}
|
|
336
|
+
{%- if content | length < 500 and '$ ' not in content and 'took ' not in _content_lower and ('"error":' in _content_head or 'error:' in _content_head or 'err!' in _content_head or 'fatal:' in _content_head or 'exception:' in _content_head or 'traceback' in _content_head or 'command not found' in _content_head or 'invalid syntax' in _content_head or 'failed to' in _content_head) %}
|
|
337
|
+
{%- set ns2.consecutive_failures = ns2.consecutive_failures + 1 %}
|
|
338
|
+
{%- else %}
|
|
339
|
+
{%- set ns2.consecutive_failures = 0 %}
|
|
340
|
+
{%- endif %}
|
|
341
|
+
{%- if ns2.prev_role != 'tool' %}
|
|
342
|
+
{{- '<|im_start|>user' }}
|
|
343
|
+
{%- endif %}
|
|
344
|
+
{%- if max_tool_response_chars > 0 and content | length > max_tool_response_chars %}
|
|
345
|
+
{%- set content = content[:max_tool_response_chars] + '\n[TRUNCATED - original length ' ~ (content | length | string) ~ ' chars]' %}
|
|
346
|
+
{%- endif %}
|
|
347
|
+
{{- '\n<tool_response>\n' + content }}
|
|
348
|
+
{%- if ns2.consecutive_failures >= 2 %}
|
|
349
|
+
{{- '\n\n⚠️ SYSTEM WARNING: ' ~ ns2.consecutive_failures ~ ' consecutive tool errors detected. Your previous approach is incorrect. You MUST use a fundamentally different approach or corrected arguments.' }}
|
|
350
|
+
{%- elif ns2.consecutive_failures == 1 %}
|
|
351
|
+
{{- '\n\n⚠️ SYSTEM WARNING: The previous tool call returned an error. Diagnose the failure and retry with completely corrected arguments.' }}
|
|
352
|
+
{%- endif %}
|
|
353
|
+
{{- '\n</tool_response>' }}
|
|
354
|
+
{%- if loop.last %}
|
|
355
|
+
{{- '<|im_end|>\n' }}
|
|
356
|
+
{%- else %}
|
|
357
|
+
{%- set _next_role = _msgs[loop.index0 + 1].role %}
|
|
358
|
+
{%- if _next_role != 'tool' %}
|
|
359
|
+
{{- '<|im_end|>\n' }}
|
|
360
|
+
{%- endif %}
|
|
361
|
+
{%- endif %}
|
|
362
|
+
{%- else %}
|
|
363
|
+
{{- '<|im_start|>user\n[' + message.role + ']: ' + content + '<|im_end|>\n' }}
|
|
364
|
+
{%- endif %}
|
|
365
|
+
{%- set ns2.prev_role = message.role %}
|
|
366
|
+
{%- endfor %}
|
|
367
|
+
{%- if add_generation_prompt %}
|
|
368
|
+
{{- '<|im_start|>assistant\n' }}
|
|
369
|
+
{%- if not ns_state.thinking %}
|
|
370
|
+
{{- '<think>\n\n</think>\n\n' }}
|
|
371
|
+
{%- else %}
|
|
372
|
+
{{- '<think>\n' }}
|
|
373
|
+
{%- endif %}
|
|
374
|
+
{%- endif %}
|