Quay lại blog

Khắc phục sự cốHướng dẫn

Câu trả lời AI bị cắt? Sửa finish_reason "length" và max_tokens

Câu trả lời API của bạn dừng giữa câu hoặc trả về JSON hỏng? Kiểm tra finish_reason, tăng max_tokens trong giới hạn và thêm bước tiếp tục.

Bởi CheapTokens TeamĐã xuất bản 7 phút đọc

Ảnh bìa kiểu terminal hiển thị finish_reason length trong một phản hồi API
Trên trang này
  1. Câu trả lời bị cắt trông thế nào trên màn hình
  2. Bước 1: đọc trường cho bạn biết
  3. Bước 2: tăng max_tokens, nhưng không vượt trần
  4. Bước 3: JSON dừng giữa chừng
  5. Bước 4: để model tiếp tục
  6. Một câu trả lời dài tốn bao nhiêu
  7. Trước khi phát hành: một danh sách kiểm tra ngắn

Script của bạn đã chạy, model đã trả lời, và hàm kết thúc giữa một dòng. Hoặc trình phân tích JSON ném lỗi trên một phản hồi trông hoàn toàn bình thường. Thường thì model không hề lỗi: nó đã chạm giới hạn max_tokens bạn đặt (hoặc một giá trị mặc định bạn không hề thấy) và API đã dừng nó giữa từ.

Cách sửa nhanh: đọc finish_reason trong phản hồi. Nếu nó là "length" (API tương thích OpenAI) hoặc stop_reason là "max_tokens" (Anthropic Messages), câu trả lời đã bị cắt. Hãy tăng max_tokens lên tới giới hạn đầu ra của model, hoặc yêu cầu model tiếp tục. Phần còn lại của trang này trình bày cả hai bằng mã bạn có thể dán vào.

Nếu bạn muốn phiên bản dễ hiểu về lý do điều này xảy ra, hãy bắt đầu với vì sao câu trả lời của AI dừng giữa câu. Trang này là phiên bản gỡ lỗi.

Câu trả lời bị cắt trông thế nào trên màn hình#

Triệu chứng khác nhau tùy theo thứ bạn yêu cầu, nhưng nguyên nhân thì giống nhau. Hãy thử đối chiếu những điều sau với những gì bạn đã thấy:

Ghép cặp

Đối chiếu triệu chứng với nguyên nhân khả dĩ

  • Một hàm kết thúc bằng return resuCâu trả lời mã đã chạm max_tokens giữa dòng
  • json.loads lỗi với "Unterminated string" hoặc "Expecting ',' delimiter"JSON bị cắt giữa chừng
  • Một bản dịch dừng sau nửa đầu của văn bảnGiới hạn đầu ra nhỏ hơn bản dịch
  • Phản hồi hoàn chỉnh và trường ghi stopModel đã tự kết thúc; hãy tìm nguyên nhân ở chỗ khác

Dòng cuối cùng đó quan trọng. Nếu finish_reason là stop, giới hạn không phải vấn đề của bạn, và tăng max_tokens sẽ không thay đổi gì.

Bước 1: đọc trường cho bạn biết#

Mọi phản hồi đều cho biết vì sao quá trình sinh kết thúc. Trong phản hồi tương thích OpenAI đó là choices[0].finish_reason: "stop" khi model hoàn tất, "length" khi nó hết token. Trong Anthropic Messages API trường này là stop_reason, và giá trị bị cắt là "max_tokens". Hướng dẫn stop-reason của Anthropic (kiểm tra ngày 28 tháng 10 năm 2026) khuyến nghị đúng hai cách sửa bên dưới: tăng max_tokens hoặc tiếp tục phản hồi.

Đây là cùng một phép kiểm tra ở ba dạng. Base URL là https://api.cheaptokens.dev/v1 cho OpenAI SDK và https://api.cheaptokens.dev (không có /v1) cho Anthropic SDK. max_tokens=200 được cố ý đặt nhỏ, để bạn tự thấy việc bị cắt.

Một phép kiểm tra, ba cách

Python (OpenAI SDK)

python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.cheaptokens.dev/v1",
    api_key=os.environ["CHEAPTOKENS_API_KEY"],
)

resp = client.chat.completions.create(
    model="deepseek-ai/DeepSeek-V4-Flash-0731",
    max_tokens=200,
    messages=[{"role": "user", "content": "Write a Python class for a todo list with tests."}],
)

choice = resp.choices[0]
if choice.finish_reason == "length":
    print("Cut off at the token limit")
print(choice.message.content)
print(resp.usage.completion_tokens)

Python (Anthropic SDK)

python
import os
import anthropic

client = anthropic.Anthropic(
    base_url="https://api.cheaptokens.dev",
    api_key=os.environ["CHEAPTOKENS_API_KEY"],
)

msg = client.messages.create(
    model="deepseek-ai/DeepSeek-V4-Flash-0731",
    max_tokens=200,
    messages=[{"role": "user", "content": "Write a Python class for a todo list with tests."}],
)

if msg.stop_reason == "max_tokens":
    print("Cut off at the token limit")
print(msg.content[0].text)

curl

bash
curl https://api.cheaptokens.dev/v1/chat/completions \
  -H "Authorization: Bearer $CHEAPTOKENS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "deepseek-ai/DeepSeek-V4-Flash-0731",
    "max_tokens": 200,
    "messages": [{"role": "user", "content": "Write a Python class for a todo list with tests."}]
  }'

Hãy tìm "finish_reason": "length" trong JSON trả về.

Khi nó hoạt động như một phép chẩn đoán, bạn sẽ thấy cặp này: giới hạn thấp cho length, giới hạn rộng rãi cho stop.

Cùng một yêu cầu với max_tokens thấp và cao: finish_reason chuyển từ length sang stop
Hai giá trị finish_reason bạn sẽ gặp thường xuyên nhất

Bước 2: tăng max_tokens, nhưng không vượt trần#

Cách sửa hiển nhiên là một con số lớn hơn. Vấn đề: mỗi model có mức trần riêng cho câu trả lời. Đừng đặt giá trị vượt quá nó: giới hạn là theo từng model, nên hãy xem bảng bên dưới trước. Đây là các giới hạn theo từng câu trả lời trên CheapTokens (số liệu kiểm tra ngày 28 tháng 10 năm 2026):

Model Đầu ra tối đa mỗi câu trả lời Ngữ cảnh
DeepSeek-V4-Flash 32.768 token 380K
GLM-5.3-Flash 8.192 token 390K
MiniMax-M2.7 8.192 token 180K

Một bản dịch 9.000 token cần một model có giới hạn mỗi câu trả lời lớn hơn — DeepSeek-V4-Flash nhận tới 32.768 token mỗi câu trả lời. Với mã dài, JSON dài hoặc cả một tài liệu, deepseek-ai/DeepSeek-V4-Flash-0731 có nhiều chỗ nhất: tới 32.768 token mỗi câu trả lời, một mức giá vào mọi giờ trong ngày. Hãy lấy id model chính xác từ tài liệu CheapTokens và trang models, vì các nhà cung cấp đặt tên khác nhau.

Một quy tắc thực tế: đặt max_tokens bằng câu trả lời dài nhất bạn sẽ chấp nhận, không phải bằng mức tối đa. Bạn trả tiền cho các token model thực sự viết ra, nên một trần cao không tốn gì khi câu trả lời ngắn.

Bước 3: JSON dừng giữa chừng#

Một phản hồi JSON bị cắt là trường hợp khó chịu nhất, vì văn bản trông đúng cho tới khi trình phân tích chạm tới cuối. Giả sử bạn trích xuất các trường từ 40 hóa đơn trong một lần gọi và yêu cầu một mảng. Model viết 31 đối tượng, chạm giới hạn, và mảng không bao giờ đóng lại.

Lỗi json.loads trên một mảng bị cắt tại giới hạn token
Một mảng JSON bị cắt trông thế nào trong trình phân tích

Đừng cố sửa chuỗi bằng cách thêm ]}. Bạn sẽ nhận được JSON hợp lệ nhưng thiếu bản ghi, điều này còn tệ hơn một lỗi. Hãy kiểm tra finish_reason trước; nếu nó là length, hãy tăng giới hạn và chạy lại lần gọi, hoặc chia đầu vào thành các lô nhỏ hơn. Để ép buộc chính định dạng, xem JSON hợp lệ từ một LLM API.

Bước 4: để model tiếp tục#

Đôi khi câu trả lời thực sự dài hơn mọi giới hạn: một chương sách, một lần tái cấu trúc lớn. Khi đó hãy đưa những gì bạn đã có trở lại và yêu cầu model đi tiếp. Vòng lặp bên dưới giữ phần văn bản đã có, yêu cầu phần còn lại, và bỏ cuộc sau vài vòng để một lỗi không thể quay vô hạn.

python
def complete(base_messages, model, max_tokens=4096, rounds=4):
    text = ""
    for _ in range(rounds):
        messages = list(base_messages)
        if text:
            messages += [
                {"role": "assistant", "content": text},
                {"role": "user", "content": "Continue exactly where you stopped. Do not repeat anything."},
            ]
        r = client.chat.completions.create(model=model, max_tokens=max_tokens, messages=messages)
        choice = r.choices[0]
        text += choice.message.content or ""
        if choice.finish_reason != "length":
            return text, True   # finished on its own
    return text, False          # still cut after all rounds

Hai thói quen giữ cho việc này trung thực. Trả về cờ True/False và hiển thị cho người dùng một cảnh báo khi False, thay vì trình bày nửa tài liệu như đã xong. Và hãy nhớ rằng mỗi vòng gửi lại văn bản trước đó làm đầu vào, nên một câu trả lời được tiếp tục tốn kém hơn một câu trả lời lớn. Với dữ liệu có cấu trúc, hãy ưu tiên giới hạn lớn hơn hoặc lô nhỏ hơn thay vì tiếp tục.

Một câu trả lời dài tốn bao nhiêu#

Giả sử một câu trả lời dài là 2.000 token vào và 8.000 token ra. Trên DeepSeek-V4-Flash, yêu cầu đó tốn $0.00083 theo giá hôm nay, và tổng cộng, khi tính cả đầu vào và đầu ra, nó rẻ hơn OpenRouter. Hãy thay các con số bằng tải của riêng bạn:

Máy tính chi phí

Một ngày với 50 câu trả lời dài

Token đầu vào mỗi yêu cầu: 2.000 · Token đầu ra mỗi yêu cầu: 8.000 · Số yêu cầu mỗi ngày: 50

Mô hìnhMỗi ngàyMỗi tháng
GLM-5.3-Flash$0.018$0.54
DeepSeek-V4-Flash$0.0415$1.25
MiniMax-M2.7$0.065$1.95

Điều rút ra: phía đầu ra chiếm ưu thế trong tình huống này, nên dư địa trong max_tokens là bảo hiểm rẻ để tránh phải chạy lại. Một câu trả lời bị cắt mà bạn phải tạo lại thì bị trả tiền hai lần. Thông tin nền về hai phía của hóa đơn có trong token đầu vào so với đầu ra, và danh sách model đầy đủ kèm giá nằm trên trang giá.

Trước khi phát hành: một danh sách kiểm tra ngắn#

Danh sách kiểm tra

Danh sách kiểm tra cắt cụt cho mã của bạn