... sections.
*
* Key properties:
* - Handles token fragmentation (partial tags across tokens)
* - Stateful per stream, stateless per request
* - Does not buffer full responses
* - Deterministic and predictable
*/
final class ThinkSuppressor
{
/** Indicates whether the stream is currently inside a block. */
private bool $insideThink = false;
/** Indicates whether the think section has been fully closed. */
private bool $thinkSectionCompleted = false;
/**
* Rolling buffer for detecting fragmented tags across tokens.
*/
private string $rollingBuffer = '';
/**
* Maximum buffer length needed to safely detect tags.
*/
private int $maxBufferLength = 32;
/**
* Filters a single token from the LLM stream.
*
* @param string $token Raw token from the LLM
* @return string Cleaned token safe for user output
*/
public function filter(string $token): string
{
// Append to rolling buffer
$this->rollingBuffer .= $token;
if (strlen($this->rollingBuffer) > $this->maxBufferLength) {
$this->rollingBuffer = substr($this->rollingBuffer, -$this->maxBufferLength);
}
// If think section is already completed, just strip stray closing tags
if ($this->thinkSectionCompleted) {
return str_replace('', '', $token);
}
// Detect fragmented opening tag
if (!$this->insideThink && str_contains($this->rollingBuffer, '')) {
$this->insideThink = true;
return '';
}
// Detect fragmented closing tag
if ($this->insideThink && str_contains($this->rollingBuffer, '')) {
$this->insideThink = false;
$this->thinkSectionCompleted = true;
// Emit a single line break after think section ends
return "\n";
}
// Suppress all content while inside ...
if ($this->insideThink) {
return '';
}
return $token;
}
/**
* Resets the suppressor state.
* Must be called before starting a new stream.
*/
public function reset(): void
{
$this->insideThink = false;
$this->thinkSectionCompleted = false;
$this->rollingBuffer = '';
}
}