← Back to Blog
Column

finish_reason length: why LLM responses get cut off

FastMetal

Illustration of a strand of light cut off by a steel blade on a machined rail, representing a response stopped by finish_reason length

The short version

  • finish_reason: "length" means the model stopped because it reached the maximum token count. The status is 200 and nothing raises.
  • The Anthropic equivalent is stop_reason: "max_tokens", or model_context_window_exceeded when the window itself ran out.
  • On reasoning models, thinking draws from the same budget. A model can spend all of it thinking and return an empty answer.
  • Fix it by raising max_tokens, lowering the reasoning effort, or requesting the rest of the answer.

If JSON fails to parse, prose ends mid-word, or content comes back empty, look at finish_reason first.

What the response looks like

Nothing about the shape changes except the one field (values are illustrative):

{
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "The short answer is that this design has three"
      },
      "finish_reason": "length"
    }
  ],
  "usage": {
    "prompt_tokens": 420,
    "completion_tokens": 256,
    "total_tokens": 676
  }
}

OpenAI's API reference defines length as reaching the maximum number of tokens specified in the request.

The same condition in each API

APIField and valueMeaning
OpenAI Chat Completionsfinish_reason: "length"Token limit reached
OpenAI Responsesstatus: "incomplete", incomplete_details.reason: "max_output_tokens"Limit reached
Anthropic Messagesstop_reason: "max_tokens"max_tokens reached
Anthropic Messagesstop_reason: "model_context_window_exceeded"Context window filled

Anthropic lists its values under stop reasons. The Responses behavior is in OpenAI's reasoning guide.

Reasoning tokens use the output budget

Anthropic's docs state that thinking tokens are a subset of max_tokens and are billed as output. OpenAI warns that the limit can arrive before any visible output, and that you still pay for the input and reasoning tokens.

The same holds through FastMetal. Reasoning tokens are billed at the output rate and counted in completion_tokens, with the split in usage.completion_tokens_details.reasoning_tokens, whether or not the reasoning is shown.

The reasoning object controls how much a model thinks. Allowed values and defaults vary by model, and Kimi K3 thinks at maximum unless told otherwise. See the reasoning docs.

Continue a cut-off response

Pass the partial answer back as an assistant turn and ask for the rest. An empty answer means the budget went to thinking, and continuing cannot help, so this raises instead.

import os

from openai import OpenAI

client = OpenAI(
    api_key=os.environ["FASTMETAL_API_KEY"],
    base_url="https://api.fastmetal.ai/v1",
)


def complete_all(messages: list[dict], max_tokens: int = 4096, rounds: int = 3) -> str:
    parts: list[str] = []
    for _ in range(rounds):
        history = list(messages)
        if parts:
            history += [
                {"role": "assistant", "content": "".join(parts)},
                {"role": "user", "content": "Continue exactly from the last character of your previous output."},
            ]
        resp = client.chat.completions.create(
            model="kimi-k3",
            messages=history,
            max_tokens=max_tokens,
            extra_body={"reasoning": {"effort": "low"}},
        )
        choice = resp.choices[0]
        if choice.finish_reason == "length" and not choice.message.content:
            raise RuntimeError("Reasoning used the whole budget. Raise max_tokens.")
        parts.append(choice.message.content or "")
        if choice.finish_reason != "length":
            return "".join(parts)
    raise RuntimeError("Still incomplete after the allowed rounds.")

Stitching breaks structured output easily. For JSON, raise max_tokens and regenerate the whole response.

Comparing models on FastMetal

FastMetal serves many models through OpenAI-compatible and Anthropic-compatible APIs, so you can run this same code against different models and compare how much each one thinks. Rates, including what reasoning costs at the output price, are on the pricing page.

FAQ

Am I charged when finish_reason is length?

Yes. Generated tokens are billed as output, so discarding a cut-off answer and regenerating it costs you twice.

Does raising max_tokens always fix it?

It fixes a too-small limit. If input plus output exceeds the context window, you need to reduce the input too. If reasoning is the cause, adjust reasoning.

Why is content empty with finish_reason length?

The model most likely used the budget on reasoning before writing anything. Check reasoning_tokens, then raise max_tokens or lower the effort.

Can I detect this while streaming?

Yes. The final chunk carries the same finish_reason, so branch on it when the stream ends.

Wrapping up

Since finish_reason: "length" raises nothing, truncated answers flow straight into your app unless you check for them. Test the field on every response.

To compare models through one key, create a free account.

Try the latest AI models right now

the latest AI models is available on FastMetal through one API key. Start in the browser, or call it from the OpenAI SDK.