---
title: "ADR-016: Thinking/Reasoning Block Extraction"
manual: "TYPO3 LLM Extension"
version: "0.35"
permalink: "https://docs.typo3.org/permalink/netresearch/nr-llm:adr-016@0.35"
source: "Adr/Adr016ThinkingReasoningBlockExtraction.rst"
modified: "2026-09-16T22:09:16+00:00"
---

# ADR-016: Thinking/Reasoning Block Extraction

-   *Status:* Accepted
-   *Date:* 2025-12
-   *Authors:* Netresearch DTT GmbH

## Context

Modern reasoning models emit structured thinking blocks alongside their final
output. Anthropic Claude uses native `thinking` content blocks in its API
response. DeepSeek, Qwen, and other models wrap reasoning in
`<think>...</think>` XML tags within the text content. These blocks should be
accessible for debugging and transparency but must not
pollute the main response.

## Decision

Extract thinking blocks from LLM responses using a two-tier strategy:

1.  **Native extraction** -- Provider-specific structured thinking blocks
    (Anthropic `type: "thinking"` content blocks).
1.  **Regex fallback** -- `<think>...</think>` tag extraction for models that
    embed reasoning inline (DeepSeek, Qwen, local models via Ollama/OpenRouter).

`CompletionResponse` carries an optional `thinking` property:

**CompletionResponse with thinking support**

```php
final readonly class CompletionResponse
{
    public function __construct(
        public string $content,
        public string $model,
        public UsageStatistics $usage,
        public string $finishReason = 'stop',
        public string $provider = '',
        public ?array $toolCalls = null,
        public ?array $metadata = null,
        public ?string $thinking = null,  // Extracted thinking content
    ) {}

    public function hasThinking(): bool
    {
        return $this->thinking !== null && trim($this->thinking) !== '';
    }
}
```

The base `AbstractProvider` implements the shared regex extraction:

**AbstractProvider::extractThinkingBlocks()**

```php
protected function extractThinkingBlocks(string $content): array
{
    $thinking = null;
    if (preg_match_all('#<think>([\s\S]*?)</think>#i', $content, $matches)) {
        $thinking = trim(implode("\n", $matches[1]));
        $cleaned = preg_replace('#<think>[\s\S]*?</think>#i', ' ', $content);
        $content = trim(preg_replace('/[ \t]+/', ' ', $cleaned));
    }
    return [$content, $thinking !== '' ? $thinking : null];
}
```

Provider-specific integration:

-   **ClaudeProvider** -- Iterates response `content`
    array. Collects `type: "thinking"` blocks natively,
    then runs `extractThinkingBlocks()` on text content.
    Merges both.
-   **OpenAiProvider** -- Runs
    `extractThinkingBlocks()` on message content (covers
    DeepSeek, Qwen via OpenAI-compatible API).
-   **GeminiProvider** -- Runs
    `extractThinkingBlocks()` on first candidate text
    part.
-   **OpenRouterProvider** -- Inherits OpenAI behavior
    (covers all OpenRouter-hosted models).

## Consequences

**Positive:**

-   ●● Thinking content is preserved without polluting main output.
-   ● Two-tier extraction covers both native and inline thinking formats.
-   ● `hasThinking()` convenience method for conditional UI display.
-   ◐ Regex handles multiple `<think>` blocks per response, concatenating them.
-   ◐ Content between tags is cleaned without word-gluing (space insertion).

**Negative:**

-   ◑ Regex extraction adds marginal processing overhead per response.
-   ◑ Non-thinking uses of `<think>` tags would be incorrectly extracted.

**Net Score:** +5.0 (Strong positive)

## Files changed

**Modified:**

-   `Classes/Domain/Model/CompletionResponse.php`
    -- Added `thinking` property and `hasThinking()`.
-   `Classes/Provider/AbstractProvider.php` --
    Added `extractThinkingBlocks()` and
    `createCompletionResponse()` with thinking
    parameter.
-   `Classes/Provider/ClaudeProvider.php` -- Native
    thinking block extraction plus regex fallback.
-   `Classes/Provider/OpenAiProvider.php` --
    Regex-based thinking extraction.
-   `Classes/Provider/GeminiProvider.php` --
    Regex-based thinking extraction.
-   `Classes/Provider/OpenRouterProvider.php` -- Inherits OpenAI behavior.
