> ## Documentation Index
> Fetch the complete documentation index at: https://docs.perceptron.inc/llms.txt
> Use this file to discover all available pages before exploring further.

# Reasoning

> Configure reasoning effort and handle reasoning separately from the final answer.

Use the top-level `reasoning_effort` field to control reasoning in new integrations. Accepted values are `none`, `minimal`, `low`, `medium`, and `high`. These guides use `high` when enabling reasoning and `none` when disabling it. Evaluate answer quality and latency on your task.

`none` requests an answer without reasoning; any other tier enables reasoning without an additional flag. `minimal` currently maps to `low`. Send the exact lowercase values: an unsupported tier is rejected by the SDK before sending. A raw API request with an unsupported tier returns HTTP 400 with `invalid_request_error` and a message naming `reasoning_effort`.

```json theme={null}
{
  "model": "perceptron-mk1.5",
  "messages": [{"role": "user", "content": "Explain how to check whether two observations are consistent."}],
  "reasoning_effort": "high",
  "max_completion_tokens": 2048
}
```

The final answer is in `message.content`. When returned, reasoning is in the separate `message.reasoning_content` extension. In the Perceptron SDK, read the final answer from `response.text` and optional reasoning from `response.reasoning`. Streaming chunks use `delta.reasoning_content` separately from `delta.content`.

The field location depends on the endpoint:

| Endpoint                         | Control                   | Scope                                                                                     |
| -------------------------------- | ------------------------- | ----------------------------------------------------------------------------------------- |
| `/v1/chat/completions`           | `reasoning_effort`        | The completion request, including tool-calling turns.                                     |
| `/v1/chat/completions/multilook` | `reasoning_effort`        | One tier shared by every prompt and completion in the request.                            |
| `/v1/detect`                     | `config.reasoning_effort` | Detection-mode-specific behavior; see [Detect](/capabilities/detect#configure-reasoning). |

## Choose effort for your task

* Use `none` for a baseline without reasoning, including straightforward captions, direct questions, or transcription.
* Set `reasoning_effort="high"` when enabling reasoning for comparisons, counting with ambiguous overlaps, or questions that combine several observations. Evaluate the answer against examples with known results.
* Measure the added token and latency cost. Higher effort does not guarantee a more accurate answer.

Higher tiers can spend more of `max_completion_tokens` on reasoning. A budget of only a few hundred tokens may leave an empty final answer with `finish_reason: "length"`. Start with a larger budget, such as 2,048 tokens in the example below, and adjust to your task within the model's context and output limits.

Evaluate spatial and temporal tasks with the same approach: check the returned geometry, labels, timestamps, and asset references. Do not assume that every task benefits from the same effort level.

## Read a completed answer

Install `perceptron>=0.4.0` and set `PERCEPTRON_API_KEY`. This example keeps reasoning separate from the answer and checks that generation completed:

```python theme={null}
import os

from perceptron import Client, image


client = Client(
    provider="perceptron",
    api_key=os.environ["PERCEPTRON_API_KEY"],
)
messages = [{
    "role": "user",
    "content": [
        image(
            "https://raw.githubusercontent.com/perceptron-ai-inc/perceptron/"
            "main/cookbook/_shared/assets/capabilities/qna/studio_scene.webp"
        ),
        {"type": "text", "text": (
            "Compare the terrain along the coast and farther inland. Support your answer with visible evidence."
        )},
    ],
}]
response = client.chat.completions.create(
    model="perceptron-mk1.5",
    messages=messages,
    reasoning_effort="high",
    max_completion_tokens=2048,
)
choice = response.choices[0]
if choice.finish_reason != "stop" or choice.message.tool_calls:
    raise RuntimeError(f"Expected a completed answer, got: {choice.finish_reason}")

reasoning = response.reasoning or ""
answer = response.text
print("Reasoning:", reasoning)
print("Answer:", answer)
if response.usage is not None:
    print("Usage:", response.usage.to_dict())
```

Reasoning may be absent, in which case `response.reasoning` is `None`. Use `response.text` as the final answer instead of joining the two fields.

## Consume reasoning in a stream

Run this after defining `client` and `messages` above. It collects the two fields independently and consumes the entire stream, including a possible final usage chunk with no choices:

```python theme={null}
reasoning_parts = []
answer_parts = []

with client.chat.completions.create(
    model="perceptron-mk1.5",
    messages=messages,
    reasoning_effort="high",
    max_completion_tokens=2048,
    stream=True,
    stream_options={"include_usage": True},
) as stream:
    for chunk in stream:
        for choice in chunk.choices:
            delta = choice.delta
            if delta is not None:
                if delta.tool_calls:
                    raise RuntimeError("Expected an answer, but received a tool call")
                reasoning_parts.append(delta.reasoning_content or "")
                answer_parts.append(delta.content or "")
    response = stream.get_final_completion()

if not response.complete or response.finish_reason != "stop" or response.tool_calls:
    raise RuntimeError(f"Incomplete answer: {response.finish_reason}")

print("Reasoning:", "".join(reasoning_parts))
print("Answer:", "".join(answer_parts))
if response.usage is not None:
    print("Usage:", response.usage.to_dict())
```

The SDK assembles reasoning, text, and usage in `get_final_completion()`. It raises on streamed API errors or a stream that ends before its completion marker. If iteration raises or the final completion is unsuccessful, treat the accumulated text as incomplete. A `length` finish reason can mean reasoning used the available budget before the answer was finished.

## Retain reasoning in conversation history

For tool conversations, preserve `reasoning_content` on assistant messages when sending the history back. Append `response.message` or `response.message.to_dict()` to history after checking that a message was returned; both retain the reasoning and tool-call fields. The [tool-calling guide](/perceptron-mk1.5/guides/tool-calling) demonstrates this alongside call IDs and tool results.

## Migrating the older flag

For chat completions and Multilook, `vision_config.enable_thinking` is a deprecated compatibility control. If present, it overrides whether reasoning is enabled, even when you also send `reasoning_effort`:

* `false` keeps reasoning off, including with `reasoning_effort: "high"`.
* `true` enables reasoning. An enabled tier still sets the effort; `reasoning_effort: "none"` does not turn reasoning off in this combination and leaves the tier unpinned.

Omit the deprecated flag when using `reasoning_effort` so there is one source of control. Detect has different precedence: its explicit `config.reasoning_effort` wins over `config.enable_thinking`.

Reasoning text consumes output budget. Leave room for the final answer or tool arguments, and check `finish_reason` before using the result. Increasing effort does not replace validation of annotations, extracted facts, or function arguments.

See the [tokenization guide](/perceptron-mk1.5/guides/tokenization) for output budgeting and usage accounting.
