@tiens.nguyen/gu-cli 1.0.823 → 1.0.824
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/gonext_agent_chat.py +53 -10
- package/package.json +1 -1
package/gonext_agent_chat.py
CHANGED
|
@@ -2026,7 +2026,11 @@ def _is_greeting_smalltalk(text: str) -> bool:
|
|
|
2026
2026
|
# self-labels as "Thought"/"Thinking Process" up front (a real answer doesn't open by
|
|
2027
2027
|
# naming itself as the model's own reasoning), or it's dense with self-correction
|
|
2028
2028
|
# language a finished answer wouldn't contain.
|
|
2029
|
-
|
|
2029
|
+
# The label may come dressed as markdown — "## Thought" or "**Thought:**" — and is still the
|
|
2030
|
+
# model naming its own reasoning (#139: Kimi K3 wrote "## Thought\n\n… Let me check git status"
|
|
2031
|
+
# and it was delivered as the ANSWER to "commit", so nothing ran).
|
|
2032
|
+
_SCRATCHPAD_START_RE = re.compile(
|
|
2033
|
+
r"^\s*(?:#{1,6}\s*|\*\*|__)?thought(?:\s*:|\s*(?:\*\*|__)\s*:?|\s*\n)", re.I)
|
|
2030
2034
|
_DELIBERATION_RE = re.compile(
|
|
2031
2035
|
r"\b(wait,|actually,|let'?s (try|just|use|list|see|go)|"
|
|
2032
2036
|
r"i (?:need to|should) (?:check|verify|confirm))\b",
|
|
@@ -5224,6 +5228,37 @@ def _has_runnable_block(text) -> bool:
|
|
|
5224
5228
|
return False
|
|
5225
5229
|
|
|
5226
5230
|
|
|
5231
|
+
# Chat-template control tokens a served model sometimes prints instead of acting on (#139,
|
|
5232
|
+
# Kimi K3 on ollama.com: "Thought: …<|close|>think<|sep|><|open|>response<|sep|>"). They mark
|
|
5233
|
+
# where the model's own reasoning ended and its reply began.
|
|
5234
|
+
_TEMPLATE_TOKEN_RE = re.compile(r"<\|[a-z_]{2,24}\|>(?:\s*[a-z_]{2,24}\s*<\|[a-z_]{2,24}\|>)*", re.I)
|
|
5235
|
+
|
|
5236
|
+
|
|
5237
|
+
def _strip_leaked_template(content):
|
|
5238
|
+
"""Cut leaked chat-template tokens out of a reply; keep what came after the last one.
|
|
5239
|
+
|
|
5240
|
+
Text before the last token is the model's reasoning written into the visible channel; text
|
|
5241
|
+
after it is the reply. An EMPTY reply stays empty, so the empty-content nudge asks the model
|
|
5242
|
+
to act instead of the step failing on a plan that ran nothing (seen live: three steps of
|
|
5243
|
+
"Thought: … now final answer.<|close|>think…" before a final_answer). Tokens inside a
|
|
5244
|
+
<code> block are file content, never ours to touch."""
|
|
5245
|
+
if not isinstance(content, str) or "<|" not in content:
|
|
5246
|
+
return content
|
|
5247
|
+
spans = [m.span() for m in re.finditer(_CODE_PAIR_RE, content, re.I)]
|
|
5248
|
+
last = None
|
|
5249
|
+
for m in _TEMPLATE_TOKEN_RE.finditer(content):
|
|
5250
|
+
if not any(a <= m.start() < b for a, b in spans):
|
|
5251
|
+
last = m
|
|
5252
|
+
if last is None:
|
|
5253
|
+
return content
|
|
5254
|
+
reply = content[last.end():].strip()
|
|
5255
|
+
if reply:
|
|
5256
|
+
return reply
|
|
5257
|
+
head = _TEMPLATE_TOKEN_RE.sub("", content[:last.start()]).strip()
|
|
5258
|
+
# A runnable block before the tokens is still the action — keep it.
|
|
5259
|
+
return head if _has_runnable_block(head) else ""
|
|
5260
|
+
|
|
5261
|
+
|
|
5227
5262
|
def _code_nudge_reason(content, openai_backend: bool):
|
|
5228
5263
|
"""Why (if at all) this streamed turn should be re-prompted once with the CODE-NOW
|
|
5229
5264
|
directive — or None to accept it as-is. Pure decision so the three triggers can be
|
|
@@ -5234,14 +5269,16 @@ def _code_nudge_reason(content, openai_backend: bool):
|
|
|
5234
5269
|
merely TRUNCATED at the `</code>` stop sequence is not that (task #119): it is
|
|
5235
5270
|
closed first and accepted as-is, so a step that already called a tool no longer
|
|
5236
5271
|
pays for a second model call to be told to do what it just did.
|
|
5237
|
-
3. prose-only plan — narration of what it is ABOUT to do, no tag at all.
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5272
|
+
3. prose-only plan — narration of what it is ABOUT to do, no tag at all. It
|
|
5273
|
+
requires _looks_like_scratchpad, so a FINISHED prose answer is never nudged into
|
|
5274
|
+
inventing another tool call. It exists because the "</code>" visible in a
|
|
5275
|
+
smolagents trace is appended by smolagents AFTER the model returns, so trigger 2
|
|
5276
|
+
cannot see this shape.
|
|
5277
|
+
|
|
5278
|
+
All three apply on every backend. Trigger 3 was openai-only (kept off small local
|
|
5279
|
+
Ollama models); #139 showed the cost: Kimi K3 served by ollama.com is an "ollama"
|
|
5280
|
+
backend, so each plan-only reply burned a whole failed step instead of one retry.
|
|
5281
|
+
`openai_backend` is kept for callers and no longer changes the decision."""
|
|
5245
5282
|
text = (content or "").strip()
|
|
5246
5283
|
if not text:
|
|
5247
5284
|
return "empty-content"
|
|
@@ -5249,7 +5286,7 @@ def _code_nudge_reason(content, openai_backend: bool):
|
|
|
5249
5286
|
return None # a real block (closing a stop-truncated one first) — nothing to nudge
|
|
5250
5287
|
if re.search(r"</?code[\s>/]", content, re.I):
|
|
5251
5288
|
return "dangling code tag, no valid block"
|
|
5252
|
-
if
|
|
5289
|
+
if _looks_like_scratchpad(text):
|
|
5253
5290
|
return "prose-only plan, no code block"
|
|
5254
5291
|
return None
|
|
5255
5292
|
|
|
@@ -11730,6 +11767,12 @@ def run_agent_chat(cfg):
|
|
|
11730
11767
|
# anything downstream asks "did this turn produce a code block?" — otherwise a
|
|
11731
11768
|
# step that called a tool correctly reads as broken. Gated on the code actually
|
|
11732
11769
|
# compiling, so a genuinely dangling tag still falls through to the nudge below.
|
|
11770
|
+
# Leaked chat-template tokens first (#139): what came before them is reasoning.
|
|
11771
|
+
_unleaked = _strip_leaked_template(content)
|
|
11772
|
+
if _unleaked != content:
|
|
11773
|
+
_log(f"leaked chat-template tokens → kept the reply after them "
|
|
11774
|
+
f"({len(content)} → {len(_unleaked)} chars)")
|
|
11775
|
+
content = _unleaked
|
|
11733
11776
|
content = _close_dangling_code_block(content)
|
|
11734
11777
|
# TAGS FIRST, judgement second. The tag repair used to run much later, so every
|
|
11735
11778
|
# decision in between — is this a code block? does it need a nudge? — was made on
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiens.nguyen/gu-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.824",
|
|
4
4
|
"description": "Gu CLI — the gu terminal plus the local worker that runs agent / OCR / PDF / embedding jobs on your Mac (Ollama / MLX / OpenAI-compatible).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|