Repository navigation
fix(indicator): strip reasoning-model <think> blocks from AI-generated code - #299
Closed
kenylerich wants to merge 2 commits into
Closed
kenylerich wants to merge 2 commits into
kenylerich wants to merge 2 commits into
Conversation
| """ | ||
| text = str(text or "") | ||
| text = _THINK_BLOCK_RE.sub("", text) | ||
| unterminated = text.lower().find("<think>") |
…d code AI code generation always failed with "Unsafe code rejected: Code syntax error" when the configured LLM was a reasoning model (e.g. MiniMax-M3). Those models prepend <think>...</think> chains of thought to the code, and _strip_code_fences() passed the prose through to the safety validator, where ast.parse() rejected it. The auto-fix retry hit the same path and also failed, so users saw a permanent failure even though the generated code itself was valid. Harden _strip_code_fences(): - Remove complete <think>...</think> blocks (case-insensitive, multiline). - Truncate at an unterminated <think> tag (truncated model output). - When any complete fenced block exists, return the last one instead of only handling fences at the very start/end, so prose or explanation fences surrounding the code no longer leak into validation. Plain fenced/plain code behavior is unchanged. Verified end to end against a live MiniMax-M3 endpoint: output now passes ast.parse after stripping. Adds regression tests covering think blocks (terminated, multiline, case-insensitive, unterminated), prose around fenced code, multiple fences, empty inputs, and the unchanged prose-only passthrough.
The previous fix only covered initial candidate generation. The auto-fix repair path in routes/indicator.py (_repair_code_via_llm) carried its own inline startswith/endswith fence strip without think handling, so a reasoning model's repaired code still failed validation with "Unsafe code rejected: Code syntax error" even when the fix itself was correct. Centralize the sanitizer and cover every LLM response parser: - Add shared strip_think_blocks() to app/services/llm.py. - _repair_code_via_llm now reuses the hardened _strip_code_fences(). - _parse_payload() in ai_code_edits.py strips think prose before json.loads() (edit-operation payloads had the same leak). - safe_call_llm() strips think prose before JSON parsing. - indicator_ai_generation.py uses the shared helper (no behavior change). Adds regression tests for strip_think_blocks() and think-prefixed edit payloads. Verified end to end against a live MiniMax-M3 endpoint.
kenylerich
force-pushed
the
fix/strip-llm-think-blocks-in-codegen
branch
from
October 19, 2026 16:33
1961d26 to
3d6ebbb
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
AI-assisted indicator code generation failed permanently with
Security Error: Unsafe code rejected: Code syntax errorwhenever the configured LLM was a reasoning model such as MiniMax-M3. Root cause verified against a live endpoint: those models prepend<think>...</think>chains of thought,_strip_code_fences()passed the prose through, and the safety validator'sast.parse()rejected it. The auto-fix retry re-entered the same path and failed identically, so the UI always reported a broken result even though the generated code itself was valid.Fix
Harden
_strip_code_fences()inapp/services/indicator_ai_generation.py:<think>...</think>blocks (case-insensitive, multiline).<think>tag (truncated model output).Testing
tests/test_indicator_ai_generation_strip.py(12 cases: terminated/multiline/case-insensitive/unterminated think blocks, prose around fenced code, multiple fences, empty inputs, prose-only passthrough) — all pass.tests/test_ai_generation_prompt_contracts.py— 17 pass, no regression.ast.parsesucceeds.