128K 输出实战:不上 streaming 就等着 HTTP 超时

128K 输出实战:不上 streaming 就等着 HTTP 超时

Opus 4.6 把最大输出 token 从 64K 提到了 128K。翻了一倍。

这意味着你可以让模型一次生成一个完整的长文件——一整个 React 组件库、一份 50 页的分析报告、或者一大段数据处理脚本——不用再拆成多次请求拼接。

但 128K 输出有一个硬性前提:必须用 streaming。不用 streaming 的话,HTTP 连接大概率会超时,请求直接失败。这不是建议,是现实。

为什么必须 streaming

一个 128K token 的输出,模型大概要生成 30-90 秒(取决于复杂度和 effort 设置)。普通的 HTTP 请求有超时时间——客户端、负载均衡器、API 网关都有各自的超时配置。大部分默认值在 30-60 秒。

不用 streaming 时,客户端发出请求后干等。服务端在后台生成,生成完才一次性返回。如果生成时间超过任何一层的超时配置,连接断开,你什么都拿不到。

Streaming 模式下,服务端边生成边发——每生成一小段就推送一个 event。HTTP 连接保持活跃,不会被超时机制杀掉。

Anthropic 的 SDK 文档里也明确说了:当 max_tokens 设得很大时,必须用 streaming。

基本用法

import anthropic

client = anthropic.Anthropic()

# 方式一:用 stream() 处理每个 event
with client.messages.stream(
    model="claude-opus-4-6",
    max_tokens=128000,
    thinking={"type": "adaptive"},
    messages=[{
        "role": "user", 
        "content": "生成一个完整的 Express.js REST API 项目,包含用户认证、CRUD 操作、错误处理和测试文件。"
    }]
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)
# 方式二:不想逐块处理,只要最终结果
with client.messages.stream(
    model="claude-opus-4-6",
    max_tokens=128000,
    thinking={"type": "adaptive"},
    messages=[{"role": "user", "content": "..."}]
) as stream:
    message = stream.get_final_message()
    print(message.content[0].text)

第二种方式用 get_final_message(),虽然你不逐块处理输出,但底层仍然是 streaming 接收,HTTP 连接不会超时。

成本估算

128K 输出 token,按 Opus 4.6 的 $25/MTok 计算:128K × $25 = $3.20 纯输出成本。

加上输入成本(假设 5K token 的 prompt):5K × $5 = $0.025。

再加上 adaptive thinking 的成本(这是 output token,high effort 下可能有 20K-50K 的思考 token):假设 30K × $25 = $0.75。

一次完整的 128K 输出请求,总成本大约 $4.00。不便宜。

如果你用 max effort,思考 token 可能更多,总成本轻松到 $5-6。

什么时候值得用 128K 输出

128K token 大约对应 8-10 万字中文、或 5-7 万字英文。这个量足够生成:

  • 一个中等复杂度的完整项目骨架(多个文件)
  • 一份详细的技术方案文档
  • 一个包含几十个函数的工具库
  • 一份对长文档的逐段分析报告

值得用的判断标准:你之前需要拆成 3-5 次请求、每次生成一部分、然后手动拼接。现在一次搞定,省了多次请求的 token 浪费(每次请求都要重复发 system prompt 和上下文),也避免了拼接时的上下文不连贯。

不值得用的判断标准:你的输出本来就不长(几千 token),没必要把 max_tokens 设到 128K。max_tokens 只是上限,不是"一定要生成这么多"。设大了不会多花钱(按实际输出计费),但可能影响模型的行为——有时候模型知道自己有很大的输出空间,会倾向于写更多。

处理"输出半截"的问题

即使用了 streaming,也有可能遇到输出被截断。看 stop_reason

  • end_turn:正常结束。
  • max_tokens:输出达到了 max_tokens 上限,被截断了。如果你设了 128K 而且真的用满了,说明内容确实太长,考虑拆分任务。
  • model_context_window_exceeded:输入 + 输出一起撞了上下文窗口上限(200K 或 1M)。输入太多,留给输出的空间不够了。
with client.messages.stream(
    model="claude-opus-4-6",
    max_tokens=128000,
    messages=[{"role": "user", "content": "..."}]
) as stream:
    message = stream.get_final_message()
    
    if message.stop_reason == "max_tokens":
        print("输出被截断了,考虑拆分任务")
    elif message.stop_reason == "model_context_window_exceeded":
        print("上下文满了,减少输入或开 1M beta")

一个经验:如果你的输入已经有 150K token,留给输出的空间只有 50K(200K 窗口下)。想要 128K 输出,输入得控制在 72K 以内。用 1M 窗口的话就不用这么纠结,但要承受长上下文溢价。

实际开发中的建议

1. 不要盲目设 128K

# 别这样——除非你真的需要超长输出
max_tokens=128000

# 按任务估一下够用的值
max_tokens=16000  # 一般的代码生成/分析
max_tokens=32000  # 较长的文档/多文件生成
max_tokens=64000  # 大型项目骨架
max_tokens=128000  # 确认需要极长输出时才用

2. streaming 的错误处理

长时间的 streaming 连接更容易遇到网络中断。加重试逻辑:

import time

def generate_with_retry(messages, max_retries=3):
    for attempt in range(max_retries):
        try:
            with client.messages.stream(
                model="claude-opus-4-6",
                max_tokens=128000,
                messages=messages
            ) as stream:
                return stream.get_final_message()
        except anthropic.APIConnectionError:
            if attempt < max_retries - 1:
                time.sleep(2 ** attempt)  # 指数退避
            else:
                raise

3. 前端展示

如果你在做用户可见的产品,128K 输出会持续几十秒甚至几分钟。streaming 模式下用户能看到内容逐步出现,体验远好于干等。但要注意前端渲染的性能——几万字的实时渲染可能卡顿,做好虚拟滚动或分段渲染。

4. 输出的 token 计费包括思考

adaptive thinking 产生的思考 token 算 output token,从 max_tokens 里扣。如果你设了 max_tokens=128000,模型花了 30K 在思考上,实际可用于回答的只有 98K。

如果你发现输出总是比预期短,检查一下响应里的 thinking token 消耗——可能不是模型"偷懒",是思考占了太多空间。降低 effort 可以减少思考 token,留更多空间给实际输出。


参考链接

  • Streaming 文档:https://platform.claude.com/docs/en/build-with-claude/streaming
  • What's New in Claude 4.6(128K 输出说明):https://platform.claude.com/docs/en/about-claude/models/whats-new-claude-4-6
← 返回博客列表