GPT-4 response truncation after token limit exceeded

Problem: GPT‑4 Responses Truncate After Token Limit Is Exceeded

During development in a sandbox environment, engineers observed that GPT‑4 completions stop mid‑sentence, omit closing JSON braces, or otherwise cut off content. The API returns HTTP 200, but the choices[0].text field ends exactly at the configured max_tokens value, and the finish_reason is set to length. Downstream parsers consequently raise JSONDecodeError or similar failures.

Typical symptom log excerpt:

{
  "id": "chatcmpl-7XYZ...",
  "object": "chat.completion",
  "created": 1698421234,
  "model": "gpt-4-0613",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "{\n  \"status\": \"success\",\n  \"data\": [\n    {\"id\": 1, \"value\": \"A\"},\n    {\"id\": 2, \"value\": \"B\"},\n    {\"id\": 3, \"value\": \"C\""
    }
      },
      "finish_reason": "length"
    }
  ],
  "usage": {
    "prompt_tokens": 8000,
    "completion_tokens": 4096,
    "total_tokens": 12096
  }
}

The response stops at token 4096 (the max_tokens set in the request) even though the prompt already consumes most of the model’s context window.

Root Cause Analysis

Context Window Mechanics

GPT‑4’s maximum context length is 8192 tokens for the standard model and 32768 tokens for the gpt-4-32k variant (OpenAI API reference – max_tokens). The model first tokenises the entire request (system prompt + user messages + any function calls). If the combined token count exceeds the model’s window, the API returns InvalidRequestError: This model's maximum context length is 8192 tokens (Error Codes).

When the prompt is within the window but leaves insufficient room for the desired completion, the model respects the max_tokens parameter and stops generation once that ceiling is reached. The finish_reason field is set to length, indicating a hard token limit cutoff rather than a natural stop token.

Why Truncation Occurs in Development Sandboxes

  • Large prompts. Accumulating conversation history, embedded JSON schemas, or code snippets can quickly approach the 8192‑token ceiling.
  • Static max_tokens values. Teams often hard‑code max_tokens: 4096 assuming ample headroom. When the prompt grows, the remaining budget shrinks below the size of the expected answer.
  • Lack of token‑size monitoring. The sandbox lacks middleware to log usage.prompt_tokens before the request, so the overflow is only noticed after a truncated response.

These conditions match the real incident logs where usage.total_tokens equals the model’s context limit (e.g., 12096 = 8192 + 4096) and the response ends abruptly.

Investigation and Debugging

Step 1 – Capture Prompt Token Count

Use the tiktoken library (or the official tokenizer endpoint) to compute token usage before sending the request.

import tiktoken
from openai import OpenAI

def token_count(messages, model="gpt-4"):
    enc = tiktoken.encoding_for_model(model)
    # Approximate token count per message format
    tokens_per_message = 4  # , , delimiters
    total = 0
    for msg in messages:
        total += tokens_per_message + len(enc.encode(msg["content"]))
    return total

messages = [
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user", "content": large_prompt_text},
]

print("Prompt tokens:", token_count(messages))

Expected output (example):

Prompt tokens: 7950

Step 2 – Verify API Response Metadata

Inspect the usage and finish_reason fields.

response = client.chat.completions.create(
    model="gpt-4",
    messages=messages,
    max_tokens=4096,
)

print("finish_reason:", response.choices[0].finish_reason)
print("usage:", response.usage)

Typical output when truncation occurs:

finish_reason: length
usage: Usage(prompt_tokens=7950, completion_tokens=4096, total_tokens=12046)

Step 3 – Reproduce with Minimal Prompt

Strip the prompt to a known‑good size (e.g., 1000 tokens) and confirm the model returns a complete JSON document. This isolates the problem to token budget rather than model bugs.

Step 4 – Review Community Reports

  • GitHub issue “Responses get cut off when max_tokens exceeds model context” (openai/openai-python#123) describes identical symptoms and confirms the length finish reason.
  • Stack Overflow question “OpenAI API truncates response – how to detect and handle token overflow?” (link) recommends dynamic max_tokens calculation based on prompt size.
  • Reddit discussion highlights that the model never emits a “stop” token when the token budget is exhausted.

Resolution

Strategy 1 – Dynamically Adjust max_tokens

Compute the remaining token budget and set max_tokens accordingly. For the standard GPT‑4 model (8192 tokens), leave a safety margin (e.g., 200 tokens) for the stop token and any system overhead.

MAX_CONTEXT = 8192
SAFETY_MARGIN = 200

prompt_tokens = token_count(messages)
available = MAX_CONTEXT - prompt_tokens - SAFETY_MARGIN
if available <= 0:
    raise ValueError(f"Prompt exceeds context window by {-available} tokens")

response = client.chat.completions.create(
    model="gpt-4",
    messages=messages,
    max_tokens=available,
)

Before:

client.chat.completions.create(
    model="gpt-4",
    messages=messages,
    max_tokens=4096,
)

After:

# Dynamically sized max_tokens
client.chat.completions.create(
    model="gpt-4",
    messages=messages,
    max_tokens=available,  # e.g., 146
)

Strategy 2 – Summarise or Trim Conversation History

If retaining full history is not essential, truncate older messages or replace them with a concise summary.

# Keep only last N messages
MAX_MESSAGES = 10
if len(messages) > MAX_MESSAGES:
    # Replace earliest messages with a summary
    summary = summarize(messages[:-MAX_MESSAGES])
    messages = [{"role": "system", "content": summary}] + messages[-MAX_MESSAGES:]

Strategy 3 – Switch to Larger Context Model (if available)

When the workload truly requires >8192 tokens, upgrade to gpt-4-32k (32,768 token window) and adjust MAX_CONTEXT constant accordingly.

Validation

  1. Token budget check. Verify that prompt_tokens + max_tokens < MAX_CONTEXT - SAFETY_MARGIN before each request.
  2. Finish reason. Confirm response.choices[0].finish_reason == "stop". If still “length”, log the remaining budget and adjust.
  3. Structural integrity. Parse the JSON output in a test harness:
    import json
    try:
        data = json.loads(response.choices[0].message.content)
    except json.JSONDecodeError as e:
        raise AssertionError("Truncated JSON output", e)
    
  4. End‑to‑end test. Run the CI pipeline with a deliberately large prompt and assert that the job passes.

Operational Experience & Prevention

  • Misleading symptom: The API returns 200 OK, leading developers to assume success. The real indicator is the finish_reason field.
  • Common incorrect assumption: “Setting max_tokens to a high value guarantees full output.” The model will still stop at the context ceiling.
  • Production edge case: Rate‑limit errors combined with large payloads can cause automatic retries that re‑use the same max_tokens, amplifying truncation risk.
  • Lesson learned: Embed token‑budget logging in every request wrapper; treat a “length” finish reason as a warning, not a success.

Best Practices & Prevention

Practice Implementation
Monitor prompt size Log prompt_tokens and max_tokens for each call.
Dynamic max_tokens Calculate remaining budget as shown in the resolution section.
Safety margin Reserve at least 150–200 tokens for stop token and tokenisation overhead.
History summarisation Periodically replace older messages with a concise summary.
Model selection Use gpt-4-32k for workloads that regularly exceed 8k tokens.
Alert on finish_reason Trigger an alert when finish_reason == "length" appears more than a configurable threshold.

Related Topic Hub: LLM Systems Troubleshooting Hub

FAQ

  1. Why does the API return HTTP 200 even when the output is truncated? The request is syntactically valid; the model successfully generated tokens up to the requested limit. Truncation is signalled via finish_reason: "length", not via an error status.
  2. How can I programmatically detect that a response was cut off? Check response.choices[0].finish_reason. If it equals "length", treat the output as incomplete and optionally retry with a larger context model or reduced prompt.
  3. Is increasing max_tokens enough to fix the problem? No. If the prompt already consumes the majority of the context window, raising max_tokens will only increase the likelihood of hitting the hard ceiling and still return finish_reason: "length".
  4. Can I request the model to continue from where it stopped? Yes. Pass the truncated output back as part of a new user message and request additional tokens, ensuring the combined prompt stays within the context limit.
  5. What are the token limits for the different GPT‑4 variants? Standard gpt-4 supports 8192 tokens; gpt-4-32k supports 32768 tokens. Refer to the API reference for the latest limits.