Skip to content

Reasoning models

Control how much a reasoning model thinks with reasoning_effort. Handle empty or cut-off answers.

Updated

Many Workers AI models write reasoning text before the answer. Reasoning uses tokens, time and neurons. Use reasoning_effort to limit it.

Set reasoning_effort

Cloudflare accepts the OpenAI reasoning_effort field on /compat. Laravel AI does not send it. The package does.

If you do not set it, the model chooses its own effort. Measured on 2026-09-09 on one account, with @cf/openai/gpt-oss-120b and one two-sentence question:

reasoning_effort Reasoning Answer Wall time Neurons
low 20 chars 363 chars 1.4 s 9.6
unset 104 chars 433 chars 2.6 s 11.3
high 1,564 chars 296 chars 7.4 s 31.4

The low answer was not worse. These figures show the order of magnitude, not a guarantee. low is a good default for latency-sensitive work.

Valid values are low, medium and high. Cloudflare returns HTTP 400 for OpenAI none and minimal. The package rejects them before the request, with an InvalidArgumentException.

Set it for all agents

'workers-ai' => [
    // ...
    'reasoning_effort' => 'low',
],

Set it for one agent

use Meirdick\WorkersAi\Attributes\ReasoningEffort;

#[ReasoningEffort(ReasoningEffort::LOW)]
class DraftingAgent implements Agent { /* ... */ }

Order of precedence

From lowest to highest:

  1. reasoning_effort in the provider config.
  2. #[ReasoningEffort] on the agent.
  3. A reasoning_effort key returned by providerOptions().

Which models obey it

Model Result
@cf/openai/gpt-oss-120b Obeys all three levels.
@cf/zai-org/glm-5.3-flash Any value turns reasoning off.
@cf/zai-org/glm-4.7-flash Ignores it.

Test your own model before you rely on these results.

The thinking flag

chat_template_kwargs.thinking is an older control that depends on the model. It works on the Kimi chat template and on models that share it. It had no effect on @cf/zai-org/glm-5.3-flash. Try reasoning_effort first.

To use it, return it from providerOptions():

use Laravel\Ai\Enums\Lab;

// Inside your agent class:
public function providerOptions(Lab|string $provider): array
{
    return $provider === 'workers-ai'
        ? ['chat_template_kwargs' => ['thinking' => false]]
        : [];
}

When thinking is true and the token cap is below 2048, the package raises the cap to 2048 and writes a warning to the log. Reasoning and the answer share one cap, and a small cap leaves no room for the answer.

Empty answers

A reasoning model can use its whole token cap on thinking. Cloudflare then returns HTTP 200 with content: null, and Laravel AI shows an empty string. Your code cannot tell this from a real empty answer.

So the package throws EmptyResponseException. This is on by default. A tool-call turn with no text does not throw.

To receive the empty response instead:

'workers-ai' => [
    // ...
    'throw_on_empty_response' => false,
],

When you see this exception, raise the token cap or lower reasoning_effort.

Cut-off answers

Cloudflare reports a cut-off answer with finish_reason: "length". Laravel AI returns the partial text as a success. To get an exception instead, turn on throw_on_truncation:

'workers-ai' => [
    // ...
    'throw_on_truncation' => true,
],

The package then throws TruncatedResponseException on any step that ends with finish reason length. Use this for extraction and drafting, where a partial answer is worse than none.

Where reasoning text appears

Model Reasoning key in the response
@cf/zai-org/glm-5.3-flash reasoning_content
@cf/openai/gpt-oss-120b reasoning and reasoning_content
@cf/zai-org/glm-4.7-flash reasoning and reasoning_content
@cf/meta/llama-3.3-70b-instruct-fp8-fast reasoning, always null
@cf/qwen/qwq-32b None. Reasoning is inside content.

The package reads all of these keys. On laravel/ai 1.x, it also fills StepResponse::$reasoning. On both laravel/ai lines, it sends reasoning text back between tool-call turns. You do not handle this.

Type to search…

↑↓ navigate↵ selectEsc close