Skip to content

feat: extract thinking blocks from LLM responses#91

Merged
CybotTM merged 3 commits intomainfrom
feat/extract-thinking-blocks
Mar 3, 2026
Merged

feat: extract thinking blocks from LLM responses#91
CybotTM merged 3 commits intomainfrom
feat/extract-thinking-blocks

Conversation

@CybotTM
Copy link
Member

@CybotTM CybotTM commented Mar 3, 2026

Summary

  • Add ?string $thinking property and hasThinking() to CompletionResponse for models that emit reasoning content (DeepSeek, Qwen via OpenRouter, Claude extended thinking)
  • Add extractThinkingBlocks() helper to AbstractProvider — strips <think>...</think> tags from content, returns clean text + separated thinking
  • ClaudeProvider: handles both native thinking content blocks (API-level) and inline <think> tags
  • OpenAI/OpenRouter/Gemini: extract inline <think> tags from content strings
  • Backward compatible — $thinking defaults to null, no breaking changes

Test plan

  • CompletionResponse::hasThinking() — 5 new tests (present, null, empty string)
  • extractThinkingBlocks() — 8 new tests (no tags, single, multiple, multiline, empty, case-insensitive, whitespace, trimming)
  • ClaudeProvider thinking extraction — 4 new tests (native blocks, inline tags, no thinking, tools+thinking)
  • OpenAiProvider thinking extraction — 2 new tests (with tags, without tags)
  • All 2563 unit tests pass
  • PHPStan clean, php-cs-fixer clean

Copilot AI review requested due to automatic review settings March 3, 2026 21:00
@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 2026

Thanks for your first pull request! We are excited to have you contribute.
A maintainer will review your PR soon. Please ensure all CI checks pass.

@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the LLM response handling by introducing a dedicated mechanism to capture and expose the 'thinking' or reasoning content that some models provide. This allows applications to access the model's internal thought process, which can be valuable for debugging, understanding, or presenting more detailed responses, without altering the primary content output.

Highlights

  • New thinking property and hasThinking() method: A new nullable string property $thinking has been added to CompletionResponse to store reasoning content from LLMs. A corresponding hasThinking() method allows checking for its presence.
  • Centralized thinking block extraction: A new protected helper method extractThinkingBlocks() was introduced in AbstractProvider. This method uses regular expressions to strip <think>...</think> tags from content, returning the clean text and the extracted thinking content.
  • Provider-specific thinking extraction: The ClaudeProvider now handles both native thinking content blocks (from the API response structure) and inline <think> tags. OpenAI, OpenRouter, and Gemini providers have been updated to extract inline <think> tags from their respective content strings.
  • Backward compatibility: The new $thinking property defaults to null, ensuring no breaking changes for existing implementations.
Changelog
  • Classes/Domain/Model/CompletionResponse.php
    • Added ?string $thinking property to the constructor.
    • Implemented hasThinking() method to check for the presence of thinking content.
  • Classes/Provider/AbstractProvider.php
    • Updated createCompletionResponse to accept an optional ?string $thinking parameter.
    • Introduced protected function extractThinkingBlocks(string $content): array for parsing and removing <think>...</think> tags.
  • Classes/Provider/ClaudeProvider.php
    • Modified chatCompletion to extract both native 'thinking' blocks and inline <think> tags from Claude API responses.
    • Modified chatCompletionWithTools to extract both native 'thinking' blocks and inline <think> tags from Claude API responses when tools are used.
  • Classes/Provider/GeminiProvider.php
    • Updated chatCompletion to utilize extractThinkingBlocks for processing content and populating the thinking property.
    • Updated chatCompletionWithTools to utilize extractThinkingBlocks for processing content and populating the thinking property.
  • Classes/Provider/OpenAiProvider.php
    • Integrated extractThinkingBlocks into chatCompletion to parse inline <think> tags.
    • Integrated extractThinkingBlocks into chatCompletionWithTools to parse inline <think> tags.
  • Classes/Provider/OpenRouterProvider.php
    • Applied extractThinkingBlocks within chatCompletion to handle inline <think> tags.
    • Applied extractThinkingBlocks within chatCompletionWithTools to handle inline <think> tags.
  • Tests/Unit/Domain/Model/CompletionResponseTest.php
    • Added new test cases for the thinking property and hasThinking() method, covering various states (present, null, empty string).
  • Tests/Unit/Provider/AbstractProviderMutationTest.php
    • Added extensive unit tests for the extractThinkingBlocks method, validating its behavior with different tag scenarios, multiline content, case-insensitivity, and trimming.
  • Tests/Unit/Provider/ClaudeProviderTest.php
    • Added tests to verify the correct extraction of native and inline thinking blocks in chatCompletion and chatCompletionWithTools for Claude responses.
  • Tests/Unit/Provider/OpenAiProviderTest.php
    • Added tests to confirm the proper extraction of inline thinking blocks from OpenAI chatCompletion responses.
Activity
  • 5 new tests were added for CompletionResponse::hasThinking() to cover present, null, and empty string scenarios.
  • 8 new tests were implemented for extractThinkingBlocks() to validate its functionality across various edge cases.
  • 4 new tests were added for ClaudeProvider's thinking extraction, including native blocks, inline tags, no thinking, and tools with thinking.
  • 2 new tests were added for OpenAiProvider's thinking extraction, covering cases with and without tags.
  • All 2563 existing unit tests passed successfully.
  • The codebase remains clean according to PHPStan and php-cs-fixer standards.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

github-actions[bot]
github-actions bot previously approved these changes Mar 3, 2026
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval for solo maintainer project

This PR has passed all automated quality gates:

  • ✅ Static analysis (PHPStan)
  • ✅ Code style (PHP-CS-Fixer)
  • ✅ Unit & functional tests
  • ✅ Security scanning
  • ✅ Dependency review

See SECURITY_CONTROLS.md for compensating controls documentation.

@github-actions github-actions bot added tests Test-related changes providers LLM provider changes labels Mar 3, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds first-class support for “thinking/reasoning” content returned by some LLMs, by extracting <think>...</think> blocks (and Claude native thinking blocks) into a dedicated CompletionResponse::$thinking field while keeping content clean.

Changes:

  • Extend CompletionResponse with ?string $thinking and hasThinking().
  • Add AbstractProvider::extractThinkingBlocks() and wire it into OpenAI/OpenRouter/Gemini providers; ClaudeProvider additionally handles native thinking blocks.
  • Add/extend unit tests covering thinking extraction behavior across providers and the domain model.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
Classes/Domain/Model/CompletionResponse.php Adds thinking property and hasThinking() helper.
Classes/Provider/AbstractProvider.php Adds extractThinkingBlocks() helper and passes thinking through createCompletionResponse().
Classes/Provider/ClaudeProvider.php Extracts native Claude thinking blocks + inline <think> tags into CompletionResponse::$thinking.
Classes/Provider/OpenAiProvider.php Extracts inline <think> tags from OpenAI chat completion content.
Classes/Provider/OpenRouterProvider.php Extracts inline <think> tags from OpenRouter content for chat + tools.
Classes/Provider/GeminiProvider.php Extracts inline <think> tags from Gemini content for chat + tools.
Tests/Unit/Domain/Model/CompletionResponseTest.php Adds tests for thinking constructor/default and hasThinking() behavior.
Tests/Unit/Provider/AbstractProviderMutationTest.php Adds mutation-killing tests for extractThinkingBlocks().
Tests/Unit/Provider/ClaudeProviderTest.php Adds tests for native + inline thinking extraction, including tools flow.
Tests/Unit/Provider/OpenAiProviderTest.php Adds tests for inline <think> extraction for OpenAI chat completions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new feature to extract <think> blocks from LLM responses, which is a valuable addition for models that provide reasoning. The implementation is well-structured, adding a thinking property to CompletionResponse and a helper extractThinkingBlocks in AbstractProvider. The changes are consistently applied across various providers like Claude, OpenAI, and Gemini. The test coverage for the new functionality is comprehensive and well-written.

I have one suggestion for improvement. In ClaudeProvider, there's an opportunity to reduce code duplication by extracting the response processing logic into a shared helper method. This is a medium-severity suggestion aimed at improving maintainability.

github-actions[bot]
github-actions bot previously approved these changes Mar 3, 2026
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval for solo maintainer project

This PR has passed all automated quality gates:

  • ✅ Static analysis (PHPStan)
  • ✅ Code style (PHP-CS-Fixer)
  • ✅ Unit & functional tests
  • ✅ Security scanning
  • ✅ Dependency review

See SECURITY_CONTROLS.md for compensating controls documentation.

Copilot AI review requested due to automatic review settings March 3, 2026 22:18
@github-actions github-actions bot added the ci CI/CD changes label Mar 3, 2026
github-actions[bot]
github-actions bot previously approved these changes Mar 3, 2026
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval for solo maintainer project

This PR has passed all automated quality gates:

  • ✅ Static analysis (PHPStan)
  • ✅ Code style (PHP-CS-Fixer)
  • ✅ Unit & functional tests
  • ✅ Security scanning
  • ✅ Dependency review

See SECURITY_CONTROLS.md for compensating controls documentation.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval for solo maintainer project

This PR has passed all automated quality gates:

  • ✅ Static analysis (PHPStan)
  • ✅ Code style (PHP-CS-Fixer)
  • ✅ Unit & functional tests
  • ✅ Security scanning
  • ✅ Dependency review

See SECURITY_CONTROLS.md for compensating controls documentation.

CybotTM added 3 commits March 4, 2026 00:27
Add `thinking` property to CompletionResponse for models that emit
reasoning content (DeepSeek, Qwen via OpenRouter, Claude extended
thinking). The property separates thinking from output so consumers
get clean content without trailing </think> artifacts.

- Add ?string $thinking and hasThinking() to CompletionResponse
- Add extractThinkingBlocks() helper to AbstractProvider
- ClaudeProvider: extract native thinking blocks + inline <think> tags
- OpenAI/OpenRouter/Gemini: extract inline <think> tags from content
- Add comprehensive tests for all extraction paths

Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
- Use trim() in hasThinking() for consistency with providers
- Replace <think> tags with space to prevent word-gluing
- Use array+implode for Claude native thinking blocks
- Add word-gluing prevention test

Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
Use /[ \t]+/ instead of /\s+/ to only normalize horizontal whitespace,
preserving newlines and markdown formatting in completion content.

Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
Copilot AI review requested due to automatic review settings March 3, 2026 23:27
@CybotTM CybotTM force-pushed the feat/extract-thinking-blocks branch from 8c8cd6a to 9fb582a Compare March 3, 2026 23:27
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval for solo maintainer project

This PR has passed all automated quality gates:

  • ✅ Static analysis (PHPStan)
  • ✅ Code style (PHP-CS-Fixer)
  • ✅ Unit & functional tests
  • ✅ Security scanning
  • ✅ Dependency review

See SECURITY_CONTROLS.md for compensating controls documentation.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@CybotTM CybotTM added this pull request to the merge queue Mar 3, 2026
Merged via the queue into main with commit b06460a Mar 3, 2026
41 checks passed
@CybotTM CybotTM deleted the feat/extract-thinking-blocks branch March 3, 2026 23:45
@codecov
Copy link

codecov bot commented Mar 3, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.48%. Comparing base (fa0c008) to head (9fb582a).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff              @@
##               main      #91      +/-   ##
============================================
+ Coverage     92.44%   92.48%   +0.03%     
- Complexity     1928     1937       +9     
============================================
  Files            77       77              
  Lines          6814     6849      +35     
============================================
+ Hits           6299     6334      +35     
  Misses          515      515              
Flag Coverage Δ
unit 92.48% <100.00%> (+0.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
Classes/Provider/AbstractProvider.php 89.39% <100.00%> (+0.68%) ⬆️
Classes/Provider/ClaudeProvider.php 97.12% <100.00%> (+0.16%) ⬆️
Classes/Provider/GeminiProvider.php 100.00% <100.00%> (ø)
Classes/Provider/OpenAiProvider.php 100.00% <100.00%> (ø)
Classes/Provider/OpenRouterProvider.php 98.50% <100.00%> (+0.01%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci CI/CD changes providers LLM provider changes tests Test-related changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants