Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions carwatch/grounding.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@

_THINK_BLOCK = re.compile(r"<(think|thinking|reasoning)>.*?</\1>",
re.IGNORECASE | re.DOTALL)
# A reasoning block whose OPENING tag was never in the output: chat templates
# that start the model inside <think> (Qwen3.6 on the VTA, 27 Sep 2026) make
# it emit "...reasoning...</think> answer", or just "</think> answer" when the
# reasoning went to a separate field. The paired pattern above cannot see it,
# and the bare tag was posted to the room and handed to the voice.
_THINK_CLOSE = re.compile(r"</(think|thinking|reasoning)>", re.IGNORECASE)
_BULLET = re.compile(r"^\s*(?:[-*\u2022]|\d+[.)])\s+")


Expand All @@ -68,6 +74,11 @@ def strip_scaffold(text: str) -> str:
if not text:
return text
cleaned = _THINK_BLOCK.sub("", text).strip()
closes = list(_THINK_CLOSE.finditer(cleaned))
if closes:
after = cleaned[closes[-1].end():].strip()
if after:
cleaned = after
Comment on lines +79 to +81

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve literal closing tags in ordinary answers

When an answer legitimately discusses or quotes one of these tokens, this unconditionally discards everything through the last closing tag. For example, Use \` to close the reasoning block.becomes only `` to close the reasoning block.`` Because strip_scaffold processes every model response in both the agent and webchat paths, technical questions or code examples containing these tags are silently corrupted; only treat a closing tag as scaffold when its surrounding position/context indicates an actual leaked reasoning prefix.

Useful? React with 馃憤聽/ 馃憥.

lines = cleaned.splitlines()
i, dropping = 0, False
while i < len(lines):
Expand Down
17 changes: 17 additions & 0 deletions tests/test_scaffold_leak.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,22 @@ def test_prompt_and_stripper_share_the_headings(self):
self.assertIn(CANNOT_HEADING, p)



class StrayThinkClose(unittest.TestCase):
"""VTA 27 Sep: '</think> Kuule, Petrus, ...' reached the room and the voice."""

def test_bare_closing_tag_is_dropped(self):
self.assertEqual(strip_scaffold("</think> Kuule, Petrus."), "Kuule, Petrus.")

def test_reasoning_before_unopened_close_is_dropped(self):
self.assertEqual(strip_scaffold("The user greets me.\nI should reply.</think>\nHello Petrus!"),
"Hello Petrus!")

def test_paired_block_still_works(self):
self.assertEqual(strip_scaffold("<think>hmm</think>Hi."), "Hi.")

def test_only_reasoning_is_not_emptied(self):
self.assertTrue(strip_scaffold("thinking only</think>").strip())

if __name__ == "__main__":
unittest.main()
Loading