Skip to content

feat: add provider-agnostic withReasoning() request method#1018

Open
ChrisThompsonTLDR wants to merge 1 commit into
prism-php:mainfrom
ChrisThompsonTLDR:feature/with-reasoning-method
Open

feat: add provider-agnostic withReasoning() request method#1018
ChrisThompsonTLDR wants to merge 1 commit into
prism-php:mainfrom
ChrisThompsonTLDR:feature/with-reasoning-method

Conversation

@ChrisThompsonTLDR

@ChrisThompsonTLDR ChrisThompsonTLDR commented May 2, 2026

Copy link
Copy Markdown
Contributor

Description

Adds a withReasoning(bool $enabled = true) fluent setter on text and structured pending requests so callers can express the semantic intent of "skip reasoning" once and have each provider translate it to the right wire-format key.

Why

Each provider that supports reasoning / thinking exposes it through a different field today:

  • Ollama uses think: true|false
  • OpenAI Responses uses reasoning: { effort: "minimal"|"low"|... }
  • Anthropic uses thinking: { type: "enabled", budget_tokens: ... } (and simply omits the block when disabled)
  • Gemini uses thinkingConfig: { thinkingBudget: 0|... }
  • OpenRouter uses reasoning: { exclude: true|... }
  • Perplexity uses reasoning_effort: "low"|...

To disable hidden chain-of-thought today, a caller has to know each provider's wire format and hand-roll it via withProviderOptions(). Code that wants to be portable across providers (e.g. an agent framework choosing between an OpenAI and an Ollama backend at runtime) has to maintain a translation table. This PR puts that table inside Prism.

What changed

  • New Prism\Prism\Concerns\HasReasoning trait with a ?bool state and withReasoning() / reasoningEnabled() accessors. Composed onto Text\PendingRequest, Text\Request, Structured\PendingRequest, Structured\Request.
  • Each provider's text, stream, and structured handler reads $request->reasoningEnabled() and emits the right key when it is false and the user has not already set the equivalent provider option:
Provider Wire format when withReasoning(false)
Ollama think: false
OpenAI reasoning: { effort: "minimal" }
Anthropic thinking block omitted
Gemini thinkingConfig: { thinkingBudget: 0 }
OpenRouter reasoning: { exclude: true }
Perplexity reasoning_effort: "low"

Other providers (Groq, Mistral, DeepSeek, xAI, etc.) treat the call as a graceful no-op — no error, no extra key sent.

Example

// Before: caller has to know the wire format
$response = Prism::text()
    ->using('ollama', 'qwen3:14b')
    ->withPrompt($prompt)
    ->withProviderOptions(['thinking' => false])
    ->asText();

// After: portable across providers
$response = Prism::text()
    ->using($provider, $model)
    ->withPrompt($prompt)
    ->withReasoning(false)
    ->asText();

Backward compatibility

Fully additive. If withReasoning() is never called the request behaves exactly as it does today. If a caller sets the equivalent provider option explicitly (e.g. withProviderOptions(['reasoning' => ['effort' => 'high']])), that explicit value still wins — withReasoning(false) only fills in the default when no provider option is set.

Tests

  • New per-provider assertions for withReasoning(false) in tests/Providers/Ollama/TextTest.php, tests/Providers/Ollama/StreamTest.php, tests/Providers/OpenAI/TextTest.php, tests/Providers/Anthropic/AnthropicTextRequestTest.php. Each covers (a) the new wire-format key is sent, (b) explicit provider options still win, (c) absence of the key when withReasoning() is not called.
  • Full suite green: vendor/bin/pest --parallel reports 1471 passing / 8 pre-existing skipped.
  • vendor/bin/pint --test and vendor/bin/phpstan analyse both clean.

Naming

Followed existing conventions: withMaxSteps, withMaxTokens, withProviderOptions already use a with* prefix on PendingRequest. withReasoning(bool $enabled = true) mirrors that shape and lets the common case read naturally as ->withReasoning(false).

Introduces a `withReasoning(bool $enabled = true)` fluent setter on the
text and structured pending requests. Calling `->withReasoning(false)`
expresses the semantic intent of "skip reasoning" and is translated to
the right wire-format key by each provider:

| Provider   | Wire format when disabled              |
|------------|----------------------------------------|
| Ollama     | `think: false`                         |
| OpenAI     | `reasoning: { effort: "minimal" }`     |
| Anthropic  | omits the `thinking` block             |
| Gemini     | `thinkingConfig: { thinkingBudget: 0 }`|
| OpenRouter | `reasoning: { exclude: true }`         |
| Perplexity | `reasoning_effort: "low"`              |

Providers that do not support disabling reasoning treat the call as a
graceful no-op. Callers that have set the equivalent key explicitly via
`withProviderOptions()` continue to win, so the change is fully
backward compatible.

The new state lives in a `HasReasoning` concern that mirrors the shape
of `HasProviderOptions` and is composed onto both `PendingRequest` and
`Request` for text and structured generation.
wishborn added a commit to Particle-Academy/prism that referenced this pull request Jul 6, 2026
…quest method

# Conflicts:
#	src/Providers/Anthropic/Handlers/Structured.php
#	src/Providers/Anthropic/Handlers/Text.php
#	src/Providers/Gemini/Handlers/Stream.php
#	src/Providers/Gemini/Handlers/Structured.php
#	tests/Providers/Anthropic/AnthropicTextRequestTest.php
wishborn added a commit to Particle-Academy/prism that referenced this pull request Jul 6, 2026
Completes the prism-php#1018 merge resolution — the gate belongs inside
the prism-php#998 resolveThinking helper on both Text and Structured handlers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
wishborn added a commit to Particle-Academy/prism that referenced this pull request Jul 6, 2026
…table

- Restore the closing brace lost in the Prism facade root during conflict
  resolution.
- The payload-size test from prism-php#951 allocated ten 256MB strings
  and could never run under the default memory limit; the guard now reads an
  overridable config value (prism.anthropic.batch.max_payload_bytes,
  defaulting to the documented 256MB constant) and the test exercises it with
  a 1KB limit.
- Fold the withReasoning() gate (prism-php#1018) into the new shared
  OpenAI BuildsRequestBody concern.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant