📌 内容摘要

  • Claude 输出不完整有两种截然不同的原因——弄清楚是哪种,才能用对解决方法。
  • 7个具体技巧从 Prompt 层、参数层、架构层三个维度解决截断问题,覆盖网页端和 API 两种使用场景。
  • 附 stop_reason 判断代码和自动续写实现,API 用户可以直接复制使用。
  • 文末说明什么时候截断是”正确行为”,不需要修复。

一、先搞清楚:是哪种截断?

遇到输出不完整,第一步不是去改设置,而是判断是哪种截断——两种截断的原因和解决方法完全不同:

类型 表现 原因 解决方向
硬截断 句子中途断开,代码只写了一半,列表只有前几条 达到 max_tokens 上限 加大 max_tokens 或分段生成
软截断 结构完整但内容省略,如”……以下类似,不再赘述” Claude 主动省略,认为已经够了 Prompt 层明确要求完整输出

API 用户如何快速判断:检查响应的 stop_reason 字段。

import anthropic

client   = anthropic.Anthropic()
response = client.messages.create(
    model      = "claude-sonnet-4-6",
    max_tokens = 512,
    messages   = [{"role": "user", "content": "写一篇2000字的文章"}]
)

if response.stop_reason == "max_tokens":
    print("❌ 硬截断:达到 max_tokens 上限,需要加大或分段")
    print(f"   已用 {response.usage.output_tokens} tokens,设置上限是 512")
elif response.stop_reason == "end_turn":
    print("✅ 自然结束:Claude 认为已完整")
    print("   如果内容不完整,是软截断,需要改 Prompt")

print(response.content[0].text[-100:])  # 看结尾是否完整

技巧一:加大 max_tokens(解决硬截断最直接的方法)

如果 stop_reason == "max_tokens",最直接的解法就是加大上限。但不是无脑加大——先估算任务实际需要多少 token,设一个合理的值:

"""
输出 token 估算参考:
- 中文:约 1.5 token/字,1000字文章 ≈ 1500 tokens
- 英文:约 1.3 token/词,1000词文章 ≈ 1300 tokens
- 代码:约 1 token/3-4字符,200行代码 ≈ 800-1000 tokens
- JSON:约 1 token/3字符,取决于字段数量

任务 → 建议 max_tokens:
- 一句话回答     →  64
- 短段落(<200字)→  512
- 完整文章(<2000字) → 4096
- 长篇内容(>2000字) → 8192,或分段生成
- 完整代码文件   → 4096-8192
"""

# 自动根据任务类型选择 max_tokens
TASK_TOKENS = {
    "short_answer": 128,
    "paragraph":    512,
    "article":      4096,
    "long_article": 8192,
    "code":         4096,
    "analysis":     2048,
}

def smart_complete(prompt: str, task_type: str = "article") -> str:
    max_tok = TASK_TOKENS.get(task_type, 2048)
    resp    = client.messages.create(
        model      = "claude-sonnet-4-6",
        max_tokens = max_tok,
        messages   = [{"role": "user", "content": prompt}]
    )
    if resp.stop_reason == "max_tokens":
        print(f"⚠️ 仍然触发截断,当前设置 {max_tok},建议升级任务类型或用分段生成")
    return resp.content[0].text

技巧二:Prompt 明确禁止省略(解决软截断)

软截断的本质是 Claude 认为”这里不用写那么详细”。解决方法是在 Prompt 里明确告诉它不能省略:

容易触发省略的 Prompt 写法:
“列出所有方法” → Claude 可能写5个就加”……等其他方法”
“写完整代码” → Claude 可能写到一半加”# 其余部分类似,省略”
防止省略的 Prompt 写法:
“列出所有方法,每一个都要写,不允许用’等’或’以此类推’代替”
“写完整的、可直接运行的代码,不要省略任何函数实现,不要写’# TODO’或’# 类似'”
"""
防省略的关键词汇总,按场景选用:

列表/枚举任务:
  "列出全部X,一个不漏"
  "不允许用'等'、'以此类推'、'……'代替任何条目"
  "即使有很多,也要逐条列出"

代码任务:
  "写出完整可运行的代码,不省略任何函数"
  "不要写 # TODO、# 省略、pass 占位"
  "每个函数都要有完整实现,不能只写函数签名"

文章/分析任务:
  "完整展开每个部分,不要因为字数多而省略"
  "如果内容很长,继续写,不要总结收尾"
  "每个章节都要有实质内容,不能只有标题"
"""

# 针对代码任务的防省略 Prompt 模板
CODE_PROMPT = """请写一个完整的 {功能描述} 的 Python 实现。

严格要求:
- 所有函数必须有完整实现,不能只有 pass 或注释占位
- 不要写"# 其他方法类似"或"# TODO"
- 包含完整的错误处理
- 代码可以直接复制运行,不需要任何补充

{具体需求}"""

技巧三:告诉 Claude 预期的输出长度

Claude 在不知道”应该写多长”的时候,容易保守地少写。明确给出长度预期,能让它更自信地展开:

"""
有效的长度指定方式(从具体到模糊,效果依次递减):

最有效:指定具体字数/行数
  "写一篇1500-2000字的文章"
  "列出至少20个示例,每个一句话描述"
  "代码不少于100行,覆盖所有边界情况"

次有效:指定结构深度
  "每个章节至少300字,不少于5个子要点"
  "每个函数都要有实现、注释和测试用例"

较弱:模糊的"详细"要求
  "详细介绍" → Claude 对"详细"的理解因人而异,容易偏短
"""

技巧四:分段生成(长内容的根本解法)

当内容确实很长(比如完整的代码文件、长篇文章),单次生成不可避免会有截断风险。正确的做法是分段生成,每段控制在合理的 token 范围内:

def generate_long_content(
    topic:       str,
    sections:    list[str],
    words_each:  int = 500,
) -> str:
    """
    分段生成长文章
    先生成大纲确认结构,再逐段展开
    避免单次生成过长导致截断
    """
    all_parts = []

    # 第一步:生成大纲(确保结构完整)
    outline_resp = client.messages.create(
        model      = "claude-sonnet-4-6",
        max_tokens = 512,
        messages   = [{
            "role":    "user",
            "content": f"为文章《{topic}》生成详细大纲,包含以下章节:{', '.join(sections)}。每章节列3-5个要点。"
        }]
    )
    outline = outline_resp.content[0].text
    all_parts.append(f"# {topic}\n\n")

    # 第二步:逐章节展开
    for section in sections:
        print(f"正在生成:{section}...")
        section_resp = client.messages.create(
            model      = "claude-sonnet-4-6",
            max_tokens = int(words_each * 2),   # 中文每字约1.5token,留余量
            messages   = [
                {
                    "role":    "user",
                    "content": f"""根据以下大纲,完整展开文章《{topic}》的【{section}】章节。

大纲参考:
{outline}

要求:
- 只写【{section}】这一章节,约{words_each}字
- 内容完整,不省略任何要点
- 不要写其他章节的内容"""
                }
            ]
        )
        section_text = section_resp.content[0].text

        # 检查是否截断
        if section_resp.stop_reason == "max_tokens":
            print(f"⚠️ {section} 章节被截断,考虑减少每章字数或进一步拆分")

        all_parts.append(f"\n## {section}\n\n{section_text}\n")

    return "".join(all_parts)


# 使用示例
article = generate_long_content(
    topic    = "Python异步编程完全指南",
    sections = ["基础概念", "async/await语法", "并发模式", "实战案例", "性能优化"],
    words_each = 400,
)

技巧五:自动续写(检测截断后继续)

对于不方便提前分段的场景,可以在检测到截断后自动触发续写:

def complete_with_continuation(
    prompt:          str,
    max_tokens:      int = 2048,
    max_continuations: int = 3,
    model:           str = "claude-sonnet-4-6",
) -> tuple[str, int]:
    """
    自动续写:检测到截断后自动继续生成
    返回 (完整内容, 实际续写次数)
    """
    messages      = [{"role": "user", "content": prompt}]
    full_content  = []
    continuations = 0

    for attempt in range(max_continuations + 1):
        resp = client.messages.create(
            model      = model,
            max_tokens = max_tokens,
            messages   = messages,
        )

        chunk = resp.content[0].text
        full_content.append(chunk)

        # 自然结束,不需要续写
        if resp.stop_reason != "max_tokens":
            break

        # 达到上限,触发续写
        if attempt < max_continuations:
            continuations += 1
            print(f"检测到截断,触发第 {continuations} 次续写...")

            # 把已有内容加入对话,让 Claude 接着写
            messages.append({"role": "assistant", "content": chunk})
            messages.append({
                "role":    "user",
                "content": "请继续,从你停下的地方接着写,不要重复已有内容,不要重新开头。"
            })
        else:
            print(f"已达最大续写次数({max_continuations}),停止")

    return "".join(full_content), continuations


# 使用示例
content, times = complete_with_continuation(
    prompt     = "写一篇完整的Python装饰器教程,包含原理、语法、常见用法和实战案例",
    max_tokens = 1500,    # 故意设小,演示续写
    max_continuations = 3,
)
print(f"生成完成,续写了 {times} 次,总字数约 {len(content)}")

技巧六:代码生成的专项处理

代码截断比文章截断更麻烦——文章截断还能读,代码截断直接报错。代码任务有几个专项技巧:

"""
代码任务防截断的三个专项方法:
"""

# 方法 A:先生成框架,再逐函数实现
FRAMEWORK_FIRST = """
第一步:先给我类和函数的签名列表(只有定义,不要实现)
第二步:我会逐个告诉你要实现哪个函数,你完整实现那一个

现在请给我以下功能的类框架:{需求}
"""

# 方法 B:指定每次生成的范围
SCOPED_CODE = """
只写 {函数名} 这一个函数的完整实现。
其他函数不需要写,只需要这一个函数。
函数要完整可运行,包含错误处理和注释。
"""

# 方法 C:检测代码是否完整(简单启发式)
def is_code_complete(code: str) -> bool:
    """粗略检查代码是否完整(不是精确解析)"""
    code = code.strip()

    # 检查括号是否配对
    if code.count("{") != code.count("}"):
        return False
    if code.count("(") != code.count(")"):
        return False
    if code.count("[") != code.count("]"):
        return False

    # 检查是否有常见截断标志
    truncation_signs = [
        "# TODO", "# ...", "pass  #", "...\n",
        "# 其他", "# 类似", "# 省略", "# etc"
    ]
    for sign in truncation_signs:
        if sign in code:
            return False

    return True


def generate_complete_code(requirement: str) -> str:
    """生成完整代码,自动检测并修复截断"""
    resp = client.messages.create(
        model      = "claude-sonnet-4-6",
        max_tokens = 4096,
        system     = "你是一个代码生成专家。生成的代码必须完整可运行,不允许使用 TODO、pass 占位或省略号。",
        messages   = [{"role": "user", "content": requirement}]
    )

    code = resp.content[0].text

    if resp.stop_reason == "max_tokens" or not is_code_complete(code):
        # 代码不完整,要求继续
        messages = [
            {"role": "user",      "content": requirement},
            {"role": "assistant", "content": code},
            {"role": "user",      "content": "代码还不完整,请继续补全,从你停下的位置接着写。不要重复已有代码。"}
        ]
        cont_resp = client.messages.create(
            model      = "claude-sonnet-4-6",
            max_tokens = 2048,
            messages   = messages,
        )
        code += "\n" + cont_resp.content[0].text

    return code

技巧七:流式输出时监控截断

使用流式输出时,截断发生在流结束时,需要在流完成后检查:

async def stream_with_truncation_check(
    prompt:     str,
    max_tokens: int = 2048,
    on_text:    callable = None,
) -> dict:
    """
    流式生成并在完成后检查是否截断
    返回 {"content": str, "truncated": bool, "tokens": int}
    """
    full_text   = []
    input_tok   = 0
    output_tok  = 0

    async with client.messages.stream(
        model      = "claude-sonnet-4-6",
        max_tokens = max_tokens,
        messages   = [{"role": "user", "content": prompt}],
    ) as stream:
        async for text in stream.text_stream:
            full_text.append(text)
            if on_text:
                on_text(text)   # 实时回调(用于前端展示)

        # 流结束后获取完整统计
        final     = await stream.get_final_message()
        input_tok = final.usage.input_tokens
        output_tok= final.usage.output_tokens
        truncated = final.stop_reason == "max_tokens"

    content = "".join(full_text)

    if truncated:
        # 通知前端/调用方内容被截断
        print(f"⚠️ 流式输出被截断({output_tok}/{max_tokens} tokens)")

    return {
        "content":   content,
        "truncated": truncated,
        "tokens":    {"input": input_tok, "output": output_tok},
    }

什么时候截断是"正确行为",不需要修复

有时候 stop_reason == "max_tokens" 但不需要处理,因为你的业务就是需要截断到特定长度:

  • 文本摘要:要求"不超过100字的摘要",设 max_tokens=150,命中了是符合预期的
  • 标题生成:生成标题不需要很长,截断通常意味着已经生成了一个完整标题
  • 分类/判断任务:输出只需要一个词,max_tokens=16,不可能也不应该更长
  • 预算控制:有意设置 max_tokens 来控制每次调用成本,截断是设计行为

判断标准:看内容是否完整表达了你需要的信息,而不是看有没有达到 max_tokens。

常见问题

Q:claude.ai 网页端输出省略了怎么办?
网页端没有 stop_reason 可看,只能从内容判断。如果 Claude 写了"……以下类似"或提前结束,直接在对话里回复:"请继续,把刚才省略的部分完整写出来,不要再省略。"如果是硬截断(句子中途断了),说"请从[最后一句话]处继续"效果比"继续"更好。

Q:设了很大的 max_tokens(比如 8192),为什么还是截断?
检查是否是软截断——stop_reasonend_turn 但内容不完整,说明 Claude 认为已经写完了。这不是 max_tokens 的问题,是 Prompt 没有充分表达"需要更多内容"的意图。用技巧二(明确禁止省略)和技巧三(指定长度)来解决。

Q:续写时 Claude 会重复之前的内容怎么处理?
续写 Prompt 里加上"不要重复已有内容,直接从你停下的地方继续",同时在对话历史里把已生成的内容作为 assistant 消息传入(本文技巧五的代码已经这样处理)。如果重复仍然严重,可以在续写请求里附上最后50个字:"请从以下内容之后继续:'……[最后几句话]'"。

总结

解决截断问题的决策路径:先用 stop_reason 判断是硬截断还是软截断;硬截断(max_tokens 到顶)用技巧一(加大上限)或技巧四(分段生成);软截断(Claude 主动省略)用技巧二(Prompt 明确禁止省略)和技巧三(指定预期长度);对于一定会很长的内容,技巧四(分段生成)是最稳的根本解法,不依赖单次生成一次成功。