Skip to main content

Overview

MaybeThinkParser is a flexible parser that extracts content after </think> tags if present, or returns the text unchanged if no think tags exist. This makes it suitable for models that may optionally include reasoning tags.

Class Signature

class MaybeThinkParser(Parser):
    def __init__(self, extract_fn: Callable[[str], str] = lambda x: x)

Parameters

extract_fn
Callable[[str], str]
default:"lambda x: x"
Optional extraction function to further process the parsed text. For example, you can use this to extract boxed answers from math problems.

Methods

parse

Extracts content after the last </think> tag, or returns the text unchanged.
def parse(self, text: str) -> str
text
str
required
The text to parse, potentially containing <think>...</think> tags
Returns: The content after the last </think> tag if present, otherwise the original text. The result is passed through extract_fn if provided. Behavior:
  • If </think> is found: Returns everything after the last </think> tag (stripped)
  • If </think> is NOT found: Returns the original text unchanged (stripped)
  • Always applies the extract_fn to the result

Usage Examples

Basic Usage with Think Tags

import verifiers as vf

parser = vf.MaybeThinkParser()

# Parse text with think tags
text = "<think>This is a test string with thinking tags</think> This is the final answer"
result = parser.parse(text)
print(result)  # "This is the final answer"

Basic Usage without Think Tags

import verifiers as vf

parser = vf.MaybeThinkParser()

# Parse text without think tags
text = "This is a test string without thinking tags"
result = parser.parse(text)
print(result)  # "This is a test string without thinking tags"

With Custom Extraction Function

import re
import verifiers as vf

def extract_boxed(text: str) -> str:
    """Extract content from \boxed{...}"""
    match = re.search(r'\\boxed\{([^}]+)\}', text)
    return match.group(1) if match else text

parser = vf.MaybeThinkParser(extract_fn=extract_boxed)

# With think tags
text1 = "<think>Reasoning here</think>The answer is \\boxed{42}"
result1 = parser.parse(text1)
print(result1)  # "42"

# Without think tags
text2 = "The answer is \\boxed{42}"
result2 = parser.parse(text2)
print(result2)  # "42"

Parsing Message Completions

import verifiers as vf

parser = vf.MaybeThinkParser()

# With think tags
messages_with_think = [
    {"role": "user", "content": "What is 2+2?"},
    {
        "role": "assistant",
        "content": "<think>This is a test string with thinking tags</think>The answer is 4",
    },
]
result = parser.parse_answer(messages_with_think)
print(result)  # "The answer is 4"

# Without think tags
messages_without_think = [
    {"role": "user", "content": "What is 2+2?"},
    {"role": "assistant", "content": "The answer is 4"},
]
result = parser.parse_answer(messages_without_think)
print(result)  # "The answer is 4"

In a Math Rubric

import re
import verifiers as vf

def extract_boxed_answer(text: str) -> str:
    """Extract the final answer from \boxed{...}"""
    match = re.search(r'\\boxed\{([^}]+)\}', text)
    return match.group(1) if match else text

def check_math_correctness(completion, answer, parser, **kwargs):
    """Check if the parsed mathematical answer is correct"""
    parsed = parser.parse_answer(completion)
    return 1.0 if parsed == str(answer) else 0.0

parser = vf.MaybeThinkParser(extract_fn=extract_boxed_answer)

rubric = vf.Rubric(
    funcs=[check_math_correctness],
    weights=[1.0],
    parser=parser
)

Handling Edge Cases

import verifiers as vf

parser = vf.MaybeThinkParser()

# Empty content after think tag
text1 = "<think>Some thinking</think>"
result1 = parser.parse(text1)
print(result1)  # "" (empty string)

# Multiple think blocks
text2 = "<think>First</think>Middle<think>Second</think>Final"
result2 = parser.parse(text2)
print(result2)  # "Final" (after last </think>)

# Whitespace handling
text3 = "<think>Thinking</think>   Answer with spaces   "
result3 = parser.parse(text3)
print(result3)  # "Answer with spaces" (stripped)

When to Use MaybeThinkParser

Use MaybeThinkParser when:
  • Your model may or may not include <think>...</think> tags
  • You want flexible parsing that works with both reasoning and non-reasoning models
  • You’re working with models that sometimes generate think tags (e.g., during fine-tuning)
  • You need graceful handling when think tags are absent
Key Differences from ThinkParser:
FeatureMaybeThinkParserThinkParser
Missing think tagsReturns original textReturns empty string
EnforcementPermissiveStrict
Use caseOptional reasoningRequired reasoning
Format validationNot providedget_format_reward_func()

Common Use Cases

Math Problem Solving

MaybeThinkParser is commonly used with math rubrics since it can handle both reasoning and direct answers:
import verifiers as vf
from verifiers.utils.math_utils import extract_boxed_answer

parser = vf.MaybeThinkParser(extract_fn=extract_boxed_answer)

# Works with reasoning
text1 = "<think>Let me solve this</think>The answer is \\boxed{42}"
print(parser.parse(text1))  # "42"

# Works without reasoning
text2 = "The answer is \\boxed{42}"
print(parser.parse(text2))  # "42"

Fine-tuning Scenarios

During fine-tuning, models may transition from always using think tags to using them selectively:
import verifiers as vf

parser = vf.MaybeThinkParser()

# Early in training: includes think tags
early_response = "<think>Reasoning step by step</think>Answer"
print(parser.parse(early_response))  # "Answer"

# Later in training: may skip think tags for simple questions
later_response = "Answer"
print(parser.parse(later_response))  # "Answer"

See Also

Build docs developers (and LLMs) love