diff --git a/studio/backend/core/inference/inference.py b/studio/backend/core/inference/inference.py index 4dca4db7689..f113b7fe291 100644 --- a/studio/backend/core/inference/inference.py +++ b/studio/backend/core/inference/inference.py @@ -982,7 +982,23 @@ def _generate_chat_response_inner( formatted_prompt = self.format_chat_prompt(messages, system_prompt) # Step 3: generate - yield from self.generate_stream( + + # Newer hybrid-thinking templates (Qwen3.5 / Qwen3.6 style) *prefill* the reasoning + # opener in the generation prompt (`...<|im_start|>assistant\n\n`), so the + # model's own output stream is `reasoning...answer` with no opening tag. + # The shared stream protocol marks reasoning with literal ... tags + # (the harmony parser above emits them for gpt-oss, and llama-server handles the + # prefilled-opener convention natively on the GGUF path). Without re-emitting the + # opener here, everything the model thinks renders as answer text on the + # safetensors path (#6815). enable_thinking=False prompts end with an *empty* + # `\n\n` block, not the bare opener, so they are unaffected. + # This generator yields *cumulative* snapshots (consumers diff each value + # against the previous one), so the synthetic opener must remain part of + # every subsequent snapshot rather than be emitted once on its own. + think_prefix = "" if formatted_prompt.rstrip().endswith("") else "" + if think_prefix: + yield think_prefix + for cumulative in self.generate_stream( formatted_prompt, temperature, top_p, @@ -992,7 +1008,8 @@ def _generate_chat_response_inner( repetition_penalty, cancel_event = cancel_event, _adapter_state = _adapter_state, - ) + ): + yield think_prefix + cumulative def _generate_vision_response( self,