NAME

Langertha::Role::ThinkTag - Configurable think tag filtering for reasoning models

VERSION

version 0.503

SYNOPSIS

# Think tag filter is enabled by default on all engines.
# <think> tags are automatically stripped and thinking preserved:
my $response = $engine->simple_chat('Explain quantum computing');
say $response;                  # clean answer text
say $response->thinking;        # chain-of-thought (if any)

# For APIs with native reasoning (DeepSeek, Anthropic, Gemini),
# thinking is extracted from the API response automatically —
# no tag filtering needed.

# Custom tag name (e.g. for models using <reasoning> tags):
my $engine = Langertha::Engine::vLLM->new(
    url       => $vllm_url,
    model     => 'my-reasoning-model',
    think_tag => 'reasoning',
);

# Disable filtering if you want raw output:
my $engine = Langertha::Engine::OpenAI->new(
    api_key          => $key,
    think_tag_filter => 0,
);

DESCRIPTION

This role provides automatic filtering of <think> tags from LLM responses. Many reasoning models (DeepSeek R1, QwQ, Hermes with reasoning enabled) emit chain-of-thought reasoning wrapped in <think> tags inline with their response text. This role strips those tags and preserves the thinking content on the "thinking" in Langertha::Response attribute.

Composed into Langertha::Role::Chat, so every engine gets it automatically. The filter handles closed pairs (<think>...</think>), a closing tag whose opening tag the chat template put into the prompt, and unclosed tags where the model stopped mid-thought (see "filter_think_content"). A response without think tags is left untouched.

For APIs that provide reasoning content natively (DeepSeek reasoning_content, Anthropic thinking blocks, Gemini thought parts), the thinking is extracted directly from the API response — no tag filtering needed.

think_tag

The XML tag name used for thinking content. Defaults to think. Some models may use different tag names (e.g. reasoning).

think_tag_filter

When true, <think>...</think> blocks are stripped from response text. The thinking content is preserved on the "thinking" in Langertha::Response attribute for inspection. Defaults to 1 (enabled). Set to 0 to pass think tags through unmodified.

filter_think_content

my ($filtered_text, $thinking) = $engine->filter_think_content($text);

Strips <think>...</think> blocks from $text. Handles three shapes:

  • closed pairs, anywhere in the text, each one a thinking block;

  • an orphan closing tag: a </think> with no opening tag before it. Chat templates of DeepSeek-R1 and Qwen3 thinking models put the opening tag into the prompt, so a server without a reasoning parser returns reasoning...</think>answer; everything before that first closing tag is thinking;

  • an unclosed opening tag, where the model stopped mid-thought: everything from it to the end is thinking.

Nested tags are not balanced (the first closing tag ends a block) and the tag match is case-sensitive.

Text without any think tag is returned exactly as given, whitespace included. When blocks were removed, only the whitespace they leave at the start or end of the text is trimmed; after a leading block, the first content line keeps its indentation. Returns the filtered text and the extracted thinking content (or undef if none). Returns the original text unchanged when "think_tag_filter" is false.

Streaming callbacks (chunk_callback) see the raw text, tags included; only the aggregated content and thinking a streaming call returns are filtered.

SEE ALSO

SUPPORT

Issues

Please report bugs and feature requests on GitHub at https://github.com/Getty/langertha/issues.

IRC

Join #langertha on irc.perl.org or message Getty directly.

CONTRIBUTING

Contributions are welcome! Please fork the repository and submit a pull request.

AUTHOR

Torsten Raudssus <getty@cpan.org>

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Torsten Raudssus https://raudssus.de/.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.