---
title: "Reasoning models"
description: "Control how much a reasoning model thinks with reasoning_effort. Handle empty or cut-off answers."
---

> Documentation Index
> Fetch the complete documentation index at: https://laravel-cf-workersai-docs.mdick85.workers.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Reasoning models

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

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

### Set it for one agent

```php
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()`:

```php
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:

```php
'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`:

```php
'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.

Source: https://laravel-cf-workersai-docs.mdick85.workers.dev/docs/guides/reasoning/index.mdx
