@miller-tech/uap 1.76.1 → 1.76.3
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/package.json
CHANGED
|
Binary file
|
|
@@ -6339,6 +6339,22 @@ def _looks_malformed_tool_payload(text: str) -> bool:
|
|
|
6339
6339
|
if not text.strip():
|
|
6340
6340
|
return False
|
|
6341
6341
|
|
|
6342
|
+
# 2026-06-27: Also strip a trailing UNCLOSED <think> (opener present, no
|
|
6343
|
+
# matching </think>). Under `--reasoning auto` the model frequently runs out
|
|
6344
|
+
# of its token budget mid-reasoning, emitting
|
|
6345
|
+
# "<think> ...let me write files via multiple sandbox calls... args="
|
|
6346
|
+
# with no </think>. That is TRUNCATED reasoning, not a malformed tool call —
|
|
6347
|
+
# but the balanced-only strip above left it intact, so the meta-tool talk
|
|
6348
|
+
# inside it tripped the structural-marker / apology branches below
|
|
6349
|
+
# (false-positive malformed_payload that stalled agentic builds, e.g. the
|
|
6350
|
+
# Octopus Invaders generation: ~11 false rejections in 40 min). Drop from the
|
|
6351
|
+
# first opener to end; KEEP any text before it so a genuine malformed payload
|
|
6352
|
+
# preceding the reasoning is still detected.
|
|
6353
|
+
if "<think>" in text and "</think>" not in text:
|
|
6354
|
+
text = text[: text.index("<think>")]
|
|
6355
|
+
if not text.strip():
|
|
6356
|
+
return False
|
|
6357
|
+
|
|
6342
6358
|
# 2026-05-12: Strip orphan </parameter> and </function> closers that
|
|
6343
6359
|
# have no matching opener. Qwen3.6 leaks these training residuals
|
|
6344
6360
|
# after its visible answer when forced into tool_choice='required'
|
|
@@ -561,14 +561,17 @@ class ToolCallClient:
|
|
|
561
561
|
**kwargs,
|
|
562
562
|
}
|
|
563
563
|
|
|
564
|
+
# Non-OpenAI sampling/server params (top_k, min_p, grammar,
|
|
565
|
+
# chat_template_kwargs) MUST be passed via extra_body — the OpenAI
|
|
566
|
+
# SDK rejects them as direct kwargs ("unexpected keyword argument
|
|
567
|
+
# 'top_k'"), which previously made every tool-call test fail before
|
|
568
|
+
# reaching the model. Collect them into one extra_body dict.
|
|
569
|
+
extra_body: dict = {}
|
|
570
|
+
|
|
564
571
|
# Strategy 5: Thinking mode suppression (model-specific)
|
|
565
572
|
if request_config.get("suppress_thinking"):
|
|
566
|
-
|
|
567
|
-
"
|
|
568
|
-
"enable_thinking": request_config.get(
|
|
569
|
-
"enable_thinking", False
|
|
570
|
-
)
|
|
571
|
-
}
|
|
573
|
+
extra_body["chat_template_kwargs"] = {
|
|
574
|
+
"enable_thinking": request_config.get("enable_thinking", False)
|
|
572
575
|
}
|
|
573
576
|
# Version check: llama.cpp >= 3761 supports chat_template_kwargs
|
|
574
577
|
# Older versions will ignore unknown extra_body keys
|
|
@@ -582,14 +585,19 @@ class ToolCallClient:
|
|
|
582
585
|
f"parallel={request_config.get('parallel_tool_calls', True)}"
|
|
583
586
|
)
|
|
584
587
|
|
|
588
|
+
# `stop` is a standard OpenAI param -> top-level kwarg.
|
|
585
589
|
if request_config.get("stop_sequences"):
|
|
586
590
|
request_params["stop"] = request_config["stop_sequences"]
|
|
591
|
+
# llama.cpp sampling params -> extra_body (forwarded by the SDK).
|
|
587
592
|
if request_config.get("top_k") is not None:
|
|
588
|
-
|
|
593
|
+
extra_body["top_k"] = request_config["top_k"]
|
|
589
594
|
if request_config.get("min_p") is not None:
|
|
590
|
-
|
|
595
|
+
extra_body["min_p"] = request_config["min_p"]
|
|
591
596
|
if grammar_text and tools:
|
|
592
|
-
|
|
597
|
+
extra_body["grammar"] = grammar_text
|
|
598
|
+
|
|
599
|
+
if extra_body:
|
|
600
|
+
request_params["extra_body"] = extra_body
|
|
593
601
|
|
|
594
602
|
# Make API call
|
|
595
603
|
response = self._client.chat.completions.create(**request_params)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""An UNCLOSED <think> block must NOT be classified as a malformed tool payload.
|
|
3
|
+
|
|
4
|
+
Under llama-server `--reasoning auto`, Qwen3.6 emits <think> reasoning. When it
|
|
5
|
+
runs out of its token budget mid-thought it produces an *unclosed* block, e.g.
|
|
6
|
+
|
|
7
|
+
"<think> The sandbox works. Now write the remaining files via multiple
|
|
8
|
+
sandbox calls... args="
|
|
9
|
+
|
|
10
|
+
with no </think>. That is TRUNCATED reasoning, not a malformed tool call — but
|
|
11
|
+
the prior detector only stripped *balanced* <think>...</think>, so the meta-tool
|
|
12
|
+
talk inside the unclosed block tripped the structural-marker branch and the proxy
|
|
13
|
+
rejected/retried the turn (observed: ~11 false rejections in 40 min stalling the
|
|
14
|
+
Octopus Invaders agentic build). `_looks_malformed_tool_payload` now also strips a
|
|
15
|
+
trailing unclosed <think> (keeping any text BEFORE the opener so a genuine
|
|
16
|
+
malformed payload preceding the reasoning is still caught).
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import importlib.util
|
|
20
|
+
import unittest
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _load_proxy_module():
|
|
25
|
+
proxy_path = Path(__file__).resolve().parents[1] / "scripts" / "anthropic_proxy.py"
|
|
26
|
+
spec = importlib.util.spec_from_file_location("anthropic_proxy", proxy_path)
|
|
27
|
+
module = importlib.util.module_from_spec(spec)
|
|
28
|
+
spec.loader.exec_module(module)
|
|
29
|
+
return module
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
proxy = _load_proxy_module()
|
|
33
|
+
_malformed = proxy._looks_malformed_tool_payload
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class TestUnclosedThinkNotMalformed(unittest.TestCase):
|
|
37
|
+
def test_unclosed_think_with_meta_tool_talk_is_not_malformed(self):
|
|
38
|
+
# The exact failure shape that stalled the Octopus build.
|
|
39
|
+
text = (
|
|
40
|
+
"<think> The sandbox approach works. Now I need to write all the "
|
|
41
|
+
"remaining files. Let me write them efficiently using multiple "
|
|
42
|
+
"sandbox calls in parallel. I'll write the files in dependency "
|
|
43
|
+
"order: 1. config.js (done) 2. args="
|
|
44
|
+
)
|
|
45
|
+
self.assertFalse(_malformed(text))
|
|
46
|
+
|
|
47
|
+
def test_unclosed_think_with_function_words_is_not_malformed(self):
|
|
48
|
+
# meta-tool talk that mentions <function= / <parameter inside reasoning
|
|
49
|
+
text = "<think> I should call <function=Write> with <parameter=path>..."
|
|
50
|
+
self.assertFalse(_malformed(text))
|
|
51
|
+
|
|
52
|
+
def test_balanced_think_still_not_malformed(self):
|
|
53
|
+
text = "<think> planning... </think>"
|
|
54
|
+
self.assertFalse(_malformed(text))
|
|
55
|
+
|
|
56
|
+
def test_genuine_malformed_payload_without_think_still_detected(self):
|
|
57
|
+
# No <think> at all — a real leaked tool-call fragment must still trip.
|
|
58
|
+
self.assertTrue(_malformed("<function=Write><parameter=path>x</parameter>"))
|
|
59
|
+
|
|
60
|
+
def test_malformed_payload_BEFORE_unclosed_think_still_detected(self):
|
|
61
|
+
# Keep text before the opener: a real malformed payload preceding the
|
|
62
|
+
# reasoning must still be caught.
|
|
63
|
+
text = "<function=Write><parameter=p>v</parameter> <think> now reasoning..."
|
|
64
|
+
self.assertTrue(_malformed(text))
|
|
65
|
+
|
|
66
|
+
def test_clean_prose_with_no_tool_markup_is_not_malformed(self):
|
|
67
|
+
self.assertFalse(_malformed("Here is the summary of what I did."))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == "__main__":
|
|
71
|
+
unittest.main()
|