Skip to content

fix: Check response prefix. - #423

Open
Vinay-Umrethe wants to merge 9 commits into
p-e-w:masterfrom
Vinay-Umrethe:fix/response-prefix
Open

fix: Check response prefix.#423
Vinay-Umrethe wants to merge 9 commits into
p-e-w:masterfrom
Vinay-Umrethe:fix/response-prefix

Conversation

@Vinay-Umrethe

Copy link
Copy Markdown
Collaborator

In case if the model adds <think> at the end of user prompt and only generates </think> end in the model response, then the detection goes wrong.

LiquidAI/LFM2.5-2.6B generates response like this:

USER_PROMPT + <think> tag added to it at last from chat_template via tokenizer.apply_chat_template:

{%- if add_generation_prompt -%}
    {{- "<|im_start|>assistant\n<think>" -}}
{%- endif -%}

For fixing, we just create a dummy prompt to see whatever model is used with heretic...
"does its apply_chat_template(add_generation_prompt=True) method adds the tag or not to the prompt".
If yes, then we extract only the end part (like </think>) and set it as the response_prefix.

Before

Checking for common response prefix...
* Prefix found: 'The user'

After

Checking for common response prefix...
* Closed Chain-of-Thought block: '</think>'
* Rechecking with prefix...

In case if the model adds <think> at the end of user prompt and only generates </think> end, then the detection goes wrong.
@Vinay-Umrethe
Vinay-Umrethe requested a review from p-e-w August 6, 2026 10:32
Comment thread src/heretic/main.py Outdated
Comment thread src/heretic/main.py Outdated
for cot_initializer, closed_cot_block in settings.chain_of_thought_skips:
if settings.response_prefix.startswith(cot_initializer):
settings.response_prefix = closed_cot_block
if dummy_prompt.endswith(

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

This is too brittle. For example if the chat template ends with <think>\n instead of <think> this logic fails.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

good catch, I think applying rstrip() might be enough for it.

and I also expect this to fix CoT detection for cases like qwen3.5 models (which I almost never saw get properly detected) because qwen3.5 template has:

{%- if add_generation_prompt %}
    {{- '<|im_start|>assistant\n' }}
    {%- if enable_thinking is defined and enable_thinking is false %}
        {{- '<think>\n\n</think>\n\n' }}
    {%- else %}
        {{- '<think>\n' }}
    {%- endif %}
{%- endif %}

I'll test it and push if it works...

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

rstrip should do the trick in this case, but it's basically a hack. For example, the template could end with </think>\nOkay,, and we're back to zero. The correct approach would be to get the complete text (prompt + response) and then do a regex match. But this is probably not worth the complexity.

At some point we should revisit trying to set enable_thinking to False in the template, which would make the whole prefix detection unnecessary for many models. I've looked into this in the past, but there were several inconsistencies that might have been fixed in the meantime.

Comment thread src/heretic/main.py Outdated
@Vinay-Umrethe

Vinay-Umrethe commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author

Tested Qwen/Qwen3.5-4B

because variants which are <4B (like 2B or 0.8B) are non-reasoning by default, but 4B and above do the reasoning.

Before (v1.4 release)

Checking for common response prefix...
* None found

After

Checking for common response prefix...
* Closed Chain-of-Thought block: '</think>'
* Rechecking with prefix...
* Extended prefix found: '</think>\n\n'

So I think now both LFM and Qwen3.5 models are fixed and possibly a few more cases...

TODO:

  • Add new hashes (or update existing ones) since this changes a major logic.

@Vinay-Umrethe

Copy link
Copy Markdown
Collaborator Author
Checking for common response prefix...
* Closed Chain-of-Thought block: '</think>\n'
* Rechecking with prefix...

got this now for Qwen/Qwen3.5-4B, probably better than rstrip() because it detected in first pass without needing extended check.

@p-e-w yet I have still not decided what to do with hashes for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants