Claude Messages API 消息线程 2026:把 TTFT 砍下来的 Beta

Anthropic 给 Messages API 上线了一项安静但重要的改动:消息线程(message threads),目前在所有 Claude 模型上处于 beta 阶段。卖点很简单——别再每轮都重传整个对话。第一轮发送完整对话,之后每轮只发新增消息。API 把对话在服务端保留几分钟,在调用模型前重建完整上下文,然后照常返回。按 Anthropic 自己的工程数据,在 Claude Code 的长会话中 TTFT 提升约 35%,而你的 token 账单分文不变。

我们在2026 年 11 月 4 日对照 Anthropic 平台文档逐项核验了本文中的每一项事实。

消息线程到底做了什么

Messages API 是无状态的。今天的做法是每轮都重发完整的消息历史——system 提示词、工具定义、以及此前的每一轮交换——API 再把它们全部重新校验、重新分词。在一个长 agent 循环里,这是大量的冗余上传,也是对话变长后 TTFT 持续恶化的主因。

有了线程,你只需在 Messages 请求中加一个 thread 参数。第一轮设为 {"type": "create"},像往常一样发送所有内容,外加 system 提示词和工具。API 会把对话(包括 Claude 的回复)存储在响应 id 之下。后续轮次设为 {"type": "continue", "previous_message_id": "<上一个 id>"},只发送新增消息,system 和 tools 完全不用带——线程会替你记住。请求体积变小,对话越长 TTFT 越稳越低——这在长 agent 循环里价值最大,也正是 Claude Code 用户感受最明显的工作负载。

有一个心智模型值得讲清楚:你的应用仍然是事实来源。线程是 API 已经见过的对话的服务端缓存,在内存里保留几分钟,不是数据库。把它当持久状态用,你迟早丢数据;把它当作自己副本之上的会话级优化,它几乎是免费的收益。

定价与限速:不变

Anthropic 在这一点上说得很明确:线程不改变定价,也不改变限速。你仍按标准 token 费率付费——Claude Opus 5.5 输入 $4/百万 token、输出 $20/百万;Claude Sonnet 5.5 输入 $2/百万、输出 $10/百万;Claude Haiku 5.5 输入 $0.10/百万、输出 $0.50/百万——提示词缓存的常规折扣同样适用。改变的是每轮请求跨过网络的数据量,以及 API 每轮跳过的校验工作量。如果你的工作负载一直被重传大段历史所拖累,即使每个 token 的账单不变,TTFT 也会明显下降。

算一笔账就清楚了。一个 50 轮的 agent 循环,带着 30K token 的 system 提示词、5K token 的工具 schema、平均每轮 8K token 的累计历史:不用线程,整个会话要上传约 210 万输入 token。用了线程,第 1 轮仍然全量上传——约 43K token——但后面 49 次 continue 请求每次只上传新增轮次的消息,通常只有几百个 token。总上传量从 210 万降到远低于 10 万。按 Opus 5.5 的输入费率,这就是每轮真实的延迟收益,也是未缓存输入账单的实质缩减——Claude Code 那 35% 的 TTFT 提升正来自这里。

一个细节:线程在服务端存储完整对话,所以 continue 请求仍然会让模型读完整段历史——这些 token 一旦计入缓存,无论如何都要按输入计费。省下来的是上传带宽、客户端序列化、以及 API 每轮的校验和分词工作,而不是模型自身算力的隐藏折扣。

快速上手:SDK helper

最简单的路径是 SDK 线程 helper。用与普通 Messages 请求相同的字段创建它,然后逐轮喂给它。它会自动添加必需的 beta header,管理 create-then-continue 流程,并在内存中保留完整对话以便线程失效时重放。

import anthropic

client = anthropic.Anthropic()
thread = client.beta.messages.thread(
    model="claude-opus-5-5",
    max_tokens=1024,
    cache_control={"type": "ephemeral"},
    system="You are a support agent for Acme. Keep replies short.",
)

# 第 1 轮:helper 创建线程并发送完整对话
thread.send({"role": "user", "content": "Hi! I have a question about an order."})

# 第 2 轮:helper 继续线程,只发送新消息
second = thread.send({"role": "user", "content": "How long does standard shipping take?"})
for block in second.content:
    if block.type == "text":
        print(block.text)

如果你希望自行发送 thread 字段——用 cURL、CLI 或 SDK 的常规 create 方法——在线程的每个请求上都要加 beta header message-threads-2026-08-12:

# 第 1 轮:发送完整对话并创建线程
curl https://api.anthropic.com/v1/messages \
  -H "Content-Type: application/json" \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "anthropic-beta: message-threads-2026-08-12" \
  -d '{
    "model": "claude-opus-5-5",
    "max_tokens": 1024,
    "system": "You are a support agent for Acme.",
    "messages": [{"role": "user", "content": "Hi! I have a question about an order."}],
    "thread": {"type": "create"}
  }'
import anthropic

client = anthropic.Anthropic()
# 第 2 轮:只发送新消息和第 1 轮响应的 id
second = client.beta.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "How long does shipping take?"}],
    thread={"type": "continue", "previous_message_id": "msg_01XFDUDYJgAACzvnptvVoYEL"},
    betas=["message-threads-2026-08-12"],
)
print(second.content[0].text)

每轮请求该发什么字段

API 把请求字段分为三组。仅创建时可发的字段——system、tools以及它们之上的缓存断点——只能在 create 请求上发送;在 continue 请求上发送其中任何一个(哪怕是 null)都会返回 400。固定字段——model、tool_choice、thinking、顶层 cache_control、以及 anthropic-beta 值集合——在线程的每个请求上必须完全一致;不一致会返回 400,错误码 thread_fingerprint_mismatch,并报出具体是哪个字段。自由字段——max_tokens、stop_sequences、stream、metadata、service_tier——可以逐轮改变。要修改仅创建字段或固定字段,就开一个新线程。

两个细节值得注意。第一,API 不会存储 MCP 服务器 URL、MCP 授权 token 或 container id,所以使用它们时必须在每个请求上都发送 mcp_servers 和 container——MCP 授权 token 在请求之间完全可能合法地变化。第二,在 Fable 5.1、Mythos 5.1、Opus 5.5、Sonnet 5.5 和 Haiku 5.5 家族上,对话中途修改 system 或 tools 会使对话中已有的 thinking 和 redacted_thinking 块失效,携带这些块的 create 请求可能返回 400。安全的做法是发送的对话中不带 thinking 块;模型会在不暴露此前推理过程的情况下作答。

线程上的提示词缓存与流式输出

线程不会替你缓存提示词——缓存机制与不用线程时完全相同,断点由你决定。要缓存每一轮,在线程的每个请求上发送顶层 cache_control;API 会把它应用到请求消息中最后一个可缓存的块上。注意上限:一个线程请求最多携带 4 个 cache_control 断点,顶层那个算一个,system 和 tools 上的断点在每次 continue 请求上都要重复计算。

如果你不用顶层字段、而是在消息块上自行放置断点,规则会更细。create 请求上发送的断点,只要位于 tools、system 或最后一个之前的消息块上,在后续轮次就持续有效。位于请求消息最后一个块上的断点会在该请求上写入缓存——所以要缓存每个新轮次,每个请求都要放一个。断点一旦发送就无法移动或删除,因为 continue 请求无法重发更早的消息。而且在新 create 请求中重放对话时,要先把此前消息上的断点剥掉、只在最后一个块上保留一个,才能让请求保持在 4 个断点的上限内。超限会返回 400,错误码 thread_unsupported_request。

大多数 agent 开发者想要的实用配方很简单:每个请求都带一个顶层 cache_control: {"type": "ephemeral"}。这样不用管理逐块断点就能缓存每一轮,代价是每个请求占用一个断点槽位。只有当你的 system 提示词和工具 schema 极其庞大、希望它们被缓存一次并被反复读取时,才需要更细的控制——这种情况下,在 create 时把断点放在 system 和 tools 上让它们跨轮持久,再用顶层字段做每轮写入。

线程支持流式输出。响应 id 在 message_start 事件中到达,但要等收到 message_stop 事件后才能 continue——过早 continue 可能返回 404。如果 continue 的流在 message_stop 之前断开,重试同一个 continue 请求;如果返回 thread_already_continued 或 thread_continue_in_flight,在新 create 请求中重放完整对话。重放永远是安全的。

五个会咬到你的坑

1. 线程是短暂的。 线程在最后一次请求后 5 分钟过期,并在最后一次使用后不超过 1 小时内被删除。如果你的 agent 中途歇了杯咖啡,大概率就要重放。

2. 线程是尽力而为的。 没有任何响应——无论 create 还是 continue——会告诉你 API 是否真的存了那一轮。你要到下一次 continue 才知道。自己保留每一条消息的副本,并从第一天起就把重放路径写好。SDK helper 的副本存在内存里,不会存到任何其他地方。

3. 只增不改。 线程是 append-only 的,每个响应 id 只能被 continue 一次。永远从最新的响应 id 继续。要编辑或分叉,就从 fork 开新线程;要压缩上下文,就从摘要开新线程——线程不支持按需 compaction。

4. 所有权是严格的。 线程属于创建它的组织、工作区、用户或服务账号。来自不同密钥的请求——包括没有创建该线程的工作区密钥——会返回 thread_not_found。在 Claude Platform on AWS 上,线程中的每个请求都必须用同一个 IAM 角色或用户签名。

5. 拒绝和回退会打断链条。 遇到 stop_reason: "refusal" 后不要继续线程——开一个新线程,去掉或改写被拒绝的那一轮。使用服务端回退时,API 不会存储回退模型写的那一轮,所以你无法从回退响应的 id 继续;检查 usage.iterations 中是否有 fallback_message 条目,如果有就重放完整对话。

线程与替代方案的对比

消息线程不是降低每轮上传量的唯一方式。在决定之前,有三个替代方案值得对比。

纯提示词缓存是现状:在 system 提示词和工具定义上标记缓存断点,API 以远低于列表输入价的费率提供重复前缀——Opus 5.5 上 $0.20/百万 token,对比未缓存的 $4/百万。缓存跨请求持久、兼容所有功能、不需要 beta header。它的弱点恰恰是线程要修的那个:缓存命中即使再完美,每次读取仍要在服务端重新校验、重新分词整个前缀,所以对话越长 TTFT 还是越差。

客户端对话摘要——把较早的轮次压缩成滚动摘要——可以永久降低上传量,且适用于任何提供商。代价是质量:你在让模型压缩它将来可能需要的上下文,而 agent 循环对丢失的工具输出是零容忍的。线程让你在不丢失信息的前提下获得短上传的延迟收益,因为 API 保留了完整对话。

Claude Managed Agents 中的会话线程是最接近的姊妹功能。目标相同——避免重传历史——但它位于托管 agent 框架而非原始 Messages API 中,持久化方式不同,为异步长时间任务而设计。如果你已经采用了 Managed Agents,用它的会话线程;如果你在基于 Messages API 构建自己的 agent 循环,消息线程是原生原语。文档明确指出这是两个不同的功能。

你实际会遇到的错误码

所有已记录的线程错误都在模型运行前返回,不消耗 token,也不改变线程的过期时间:thread_not_found(404——重放完整对话)、thread_fingerprint_mismatch(400——固定字段不一致)、thread_already_continued(400——每个响应 id 只能 continue 一次)、thread_continue_in_flight(400——同一 id 的另一个 continue 正在运行)、以及 thread_unsupported_request(400——请求使用了线程不支持的功能,具体名称在 error.details.unsupported 中)。请求不存在的模型也会返回 404,所以重放之前先检查 error.details.error_code。

有一点值得内化:404 的情况并不等同。重放之后立刻收到 thread_not_found,通常意味着 API 在努力存储那段特定的对话——比如过大的对话。文档给出的兜底方案是完全不带 thread 发送请求;当以 unsupported_request_behavior 设为 continue_unthreaded 创建 SDK helper 时,它会自动这样做,而自建线程的工具运行器默认就用这个设置。有些线程请求的 400 错误根本不携带 error_code——比如不带 previous_message_id 的 continue 请求,或携带 system、tools 的 continue 请求。这些是编程错误,不是运行时状况,修复方向一目了然。

把错误处理设计成决策树,而不是扁平的重试:遇到 thread_not_found 或 thread_already_continued,从自己的副本重放一次;如果重放后的 continue 又返回 thread_not_found,放弃线程、不带线程继续;遇到 thread_fingerprint_mismatch,修正字段或 fork 新线程;遇到 thread_continue_in_flight,退后一步等正在运行的请求完成。每个分支都有各自正确的响应,混淆它们会产生让线程在实际使用中显得不可靠的重试循环。

谁今天就应该打开线程

消息线程是长 agent 循环的默认正确选择——编程 agent、客服 agent、多步研究工作流——凡是对话历史庞大、轮次频繁、TTFT 复利式主导用户体验的地方。Anthropic 报告启用该功能后 Claude Code 会话 TTFT 提升约 35%,而集成成本只是一个参数加一个 beta header。

可用性也很广。线程在所有 Claude 模型上处于 beta,并在 Claude API、Claude Platform on AWS、Claude Models on Google Cloud(Anthropic 托管)和 Microsoft Foundry 上可用。唯一硬性要求是每个请求都带 message-threads-2026-08-12 beta header。如果你已经在跑 Claude Code,这个功能基本就是一次配置变更;如果你在构建自定义 agent 基础设施,SDK helper 已经消掉了大部分集成面,你只需要拥有重放路径和拒绝处理。

以下情况你应该暂时跳过线程:工作负载要求零数据保留或 HIPAA 合规(线程两者都不是);轮次间隔以分钟计(5 分钟过期会迫使你不断重放);或者对话状态必须比进程活得更久(helper 的副本只在内存中)。对其他所有场景,线程都是同一张账单上的免费延迟收益——打开它,保持重放路径经过测试,让 API 替你扛历史。

来源:Anthropic Claude Platform 文档——Message threads、Using the Messages API、Pricing(均核验于 2026 年 11 月 4 日);The New Stack《Anthropic speeds up Claude Code with message threads》(2026 年 11 月 3 日)。