fix: make streaming reasoning-tag filter case-insensitive

The streaming think-tag suppressors in cli.py (_stream_delta) and
gateway/stream_consumer.py (_filter_and_accumulate) matched tag names
with case-sensitive str.find(), so only the exact-case literals in the
tag tuples were caught. Mixed-case variants a model may emit — <Think>,
<ThInK>, <REASONING>, <Thought> — slipped through and leaked raw
reasoning into the user-visible stream.

Match against a lowercased view of the buffer with lowercased tag names
at all three sites (open-tag boundary search, partial-tag hold-back,
close-tag search) in both paths. Only KNOWN tag names are matched — no
substring matching — and the block-boundary gating that protects prose
mentions of <think> is preserved.

- 6 parametrized case-insensitive regression tests in each of
  tests/gateway/test_stream_consumer.py and
  tests/cli/test_stream_delta_think_tag.py.

Salvaged from PR #27289 by @YLChen-007.
This commit is contained in:
YLChen-007 2026-07-01 03:11:53 -07:00 committed by Teknium
parent f049227f31
commit e23f723389
5 changed files with 46 additions and 7 deletions

14
cli.py
View file

@ -5443,10 +5443,14 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin):
self._stream_last_was_newline = True # start of stream = boundary
if not getattr(self, "_in_reasoning_block", False):
# Case-insensitive matching against a lowercased view so
# mixed-case tag variants (<Think>, <THINKING>, …) are caught.
prefilt_lower = self._stream_prefilt.lower()
for tag in _OPEN_TAGS:
tag_lower = tag.lower()
search_start = 0
while True:
idx = self._stream_prefilt.find(tag, search_start)
idx = prefilt_lower.find(tag_lower, search_start)
if idx == -1:
break
# Check if this is a block boundary position
@ -5486,11 +5490,12 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin):
# Could also be a partial open tag at the end — hold it back
if not getattr(self, "_in_reasoning_block", False):
# Check for partial tag match at the end
# Check for partial tag match at the end (case-insensitive)
safe = self._stream_prefilt
for tag in _OPEN_TAGS:
tag_lower = tag.lower()
for i in range(1, len(tag)):
if self._stream_prefilt.endswith(tag[:i]):
if prefilt_lower.endswith(tag_lower[:i]):
safe = self._stream_prefilt[:-i]
break
if safe:
@ -5503,8 +5508,9 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin):
# Keep accumulating _stream_prefilt because close tags can arrive
# split across multiple tokens (e.g. "</REASONING_SCRATCH" + "PAD>...").
if getattr(self, "_in_reasoning_block", False):
prefilt_lower = self._stream_prefilt.lower()
for tag in _CLOSE_TAGS:
idx = self._stream_prefilt.find(tag)
idx = prefilt_lower.find(tag.lower())
if idx != -1:
self._in_reasoning_block = False
# When show_reasoning is on, route inner content to