ModelEngine-Group / ModelEngine-Group/nexent
`remove_think_blocks` regex deletes content that is not in a `<think>` block
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 5.9k
- Forks
- 731
- Avg merge
- 19h 34m
- Merged PRs (30d)
- 172
Description
backend/utils/str_utils.py:9:
return re.sub(r"(?:<think>)?.*?</think>", "", text, flags=re.DOTALL | re.IGNORECASE)
The intent (per the docstring) is to remove <think>…</think> blocks including their inner content. The actual behaviour is broader: because (?:<think>)? makes the opening tag optional while .*?</think> still requires a closing tag, the regex will happily eat anything that ends with a </think> even if there's no opening tag.
Repro
>>> from utils.str_utils import remove_think_blocks
>>> remove_think_blocks("The model said: hello </think> world")
' world'
>>> remove_think_blocks("regular content ending with </think> stray tag")
' stray tag'
>>> remove_think_blocks("<think>secret</think>visible<think>second</think>tail")
'visibletail' # OK - matches intent
This matters for the streaming path in _process_thinking_tokens (backend/utils/llm_utils.py:15) which handles half-tokens around <think>/</think>; a stray closing tag emitted by a misbehaving provider will cause everything before it on the same chunk to be silently dropped.
Suggested fix
return re.sub(r"<think>.*?</think>", "", text, flags=re.DOTALL | re.IGNORECASE)
If the optional-open intent really was deliberate (e.g., to recover from a provider that streams only the closing tag), keep that behaviour but make it explicit, anchor it, and add a comment + a test.
Severity: Low — the failure mode requires a malformed/partial provider stream, but when it happens the user-visible content is silently truncated.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with backend/utils/str_utils.py:9 and compare remove_think_blocks with the documented behavior and the reproductions in this issue. Read the related streaming handling at backend/utils/llm_utils.py:15, then add a regression test covering tagged blocks and stray closing tags. Done means content outside a complete think block is preserved.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100