finish_reason length: why LLM responses get cut off

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", ormodel_context_window_exceededwhen 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
| API | Field and value | Meaning |
|---|---|---|
| OpenAI Chat Completions | finish_reason: "length" | Token limit reached |
| OpenAI Responses | status: "incomplete", incomplete_details.reason: "max_output_tokens" | Limit reached |
| Anthropic Messages | stop_reason: "max_tokens" | max_tokens reached |
| Anthropic Messages | stop_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.